package confix import ( "bytes" "context" "errors" "fmt" "os" "strings" "github.com/creachadair/atomicfile" "github.com/creachadair/tomledit" "github.com/creachadair/tomledit/transform" "github.com/spf13/viper" clientcfg "github.com/cosmos/cosmos-sdk/client/config" ) // Upgrade reads the configuration file at configPath and applies any // transformations necessary to Upgrade it to the current version. If this // succeeds, the transformed output is written to outputPath. As a special // case, if outputPath == "" the output is written to stdout. // // It is safe if outputPath == inputPath. If a regular file outputPath already // exists, it is overwritten. In case of error, the output is not written. // // Upgrade is a convenience wrapper for calls to LoadConfig, ApplyFixes, and // CheckValid. If the caller requires more control over the behavior of the // Upgrade, call those functions directly. func Upgrade(ctx context.Context, plan transform.Plan, doc *tomledit.Document, configPath, outputPath string, skipValidate bool) error { if configPath == "" { return errors.New("empty input configuration path") } // transforms doc and reports whether it succeeded. if err := plan.Apply(ctx, doc); err != nil { return fmt.Errorf("updating %q: %w", configPath, err) } var buf bytes.Buffer if err := tomledit.Format(&buf, doc); err != nil { return fmt.Errorf("formatting config: %w", err) } // allow to skip validation if !skipValidate { // verify that file is valid after applying fixes if err := CheckValid(configPath, buf.Bytes()); err != nil { return fmt.Errorf("updated config is invalid: %w", err) } } var err error if outputPath == "" { _, err = os.Stdout.Write(buf.Bytes()) } else { err = atomicfile.WriteData(outputPath, buf.Bytes(), 0o600) } return err } // CheckValid checks whether the specified config appears to be a valid Cosmos SDK config file. // It tries to unmarshal the config into both the server and client config structs. func CheckValid(fileName string, data []byte) error { v := viper.New() v.SetConfigType("toml") if err := v.ReadConfig(bytes.NewReader(data)); err != nil { return fmt.Errorf("reading config: %w", err) } switch { case strings.HasSuffix(fileName, AppConfig): // no validation of server config as v1 and v2 configs are both valid. // any app.toml is simply considered as a server config. case strings.HasSuffix(fileName, ClientConfig): var cfg clientcfg.ClientConfig if err := v.Unmarshal(&cfg); err != nil { return fmt.Errorf("failed to unmarshal as client config: %w", err) } if cfg.ChainID == "" { return errors.New("client config invalid: chain-id is empty") } case strings.HasSuffix(fileName, CMTConfig): return errors.New("cometbft config is not supported") default: return fmt.Errorf("unknown config: %s", fileName) } return nil }