package main import ( "fmt" "io/ioutil" "log" "os" "path/filepath" "gopkg.in/yaml.v2" ) const currentSchemaVersion = 2 // used for upgrading from old configs to new config // Performs necessary upgrade operations if needed func upgradeConfig() error { // read a config file into an interface map, so we can manipulate values without losing any configFile := filepath.Join(config.ourBinaryDir, config.ourConfigFilename) if _, err := os.Stat(configFile); os.IsNotExist(err) { log.Printf("config file %s does not exist, nothing to upgrade", configFile) return nil } diskConfig := map[string]interface{}{} body, err := ioutil.ReadFile(configFile) if err != nil { log.Printf("Couldn't read config file '%s': %s", configFile, err) return err } err = yaml.Unmarshal(body, &diskConfig) if err != nil { log.Printf("Couldn't parse config file '%s': %s", configFile, err) return err } schemaVersionInterface, ok := diskConfig["schema_version"] log.Printf("%s(): got schema version %v", _Func(), schemaVersionInterface) if !ok { // no schema version, set it to 0 schemaVersionInterface = 0 } schemaVersion, ok := schemaVersionInterface.(int) if !ok { err = fmt.Errorf("configuration file contains non-integer schema_version, abort") log.Println(err) return err } if schemaVersion == currentSchemaVersion { // do nothing return nil } return upgradeConfigSchema(schemaVersion, &diskConfig) } // Upgrade from oldVersion to newVersion func upgradeConfigSchema(oldVersion int, diskConfig *map[string]interface{}) error { switch oldVersion { case 0: err := upgradeSchema0to2(diskConfig) if err != nil { return err } case 1: err := upgradeSchema1to2(diskConfig) if err != nil { return err } default: err := fmt.Errorf("configuration file contains unknown schema_version, abort") log.Println(err) return err } configFile := filepath.Join(config.ourBinaryDir, config.ourConfigFilename) body, err := yaml.Marshal(diskConfig) if err != nil { log.Printf("Couldn't generate YAML file: %s", err) return err } err = safeWriteFile(configFile, body) if err != nil { log.Printf("Couldn't save YAML config: %s", err) return err } return nil } // The first schema upgrade: // No more "dnsfilter.txt", filters are now kept in data/filters/ func upgradeSchema0to1(diskConfig *map[string]interface{}) error { log.Printf("%s(): called", _Func()) dnsFilterPath := filepath.Join(config.ourBinaryDir, "dnsfilter.txt") if _, err := os.Stat(dnsFilterPath); !os.IsNotExist(err) { log.Printf("Deleting %s as we don't need it anymore", dnsFilterPath) err = os.Remove(dnsFilterPath) if err != nil { log.Printf("Cannot remove %s due to %s", dnsFilterPath, err) // not fatal, move on } } (*diskConfig)["schema_version"] = 1 return nil } // Second schema upgrade: // coredns is now dns in config // delete 'Corefile', since we don't use that anymore func upgradeSchema1to2(diskConfig *map[string]interface{}) error { log.Printf("%s(): called", _Func()) coreFilePath := filepath.Join(config.ourBinaryDir, "Corefile") if _, err := os.Stat(coreFilePath); !os.IsNotExist(err) { log.Printf("Deleting %s as we don't need it anymore", coreFilePath) err = os.Remove(coreFilePath) if err != nil { log.Printf("Cannot remove %s due to %s", coreFilePath, err) // not fatal, move on } } (*diskConfig)["dns"] = (*diskConfig)["coredns"] delete((*diskConfig), "coredns") (*diskConfig)["schema_version"] = 2 return nil } // jump two schemas at once -- this time we just do it sequentially func upgradeSchema0to2(diskConfig *map[string]interface{}) error { err := upgradeSchema0to1(diskConfig) if err != nil { return err } return upgradeSchema1to2(diskConfig) }