X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/50d3c3897f0fad1bfcc4fc86096155d15d25483e..4e110f379fe26b646c75cb3164f882567fdb17fd:/lib/config/cmd.go diff --git a/lib/config/cmd.go b/lib/config/cmd.go index aa3e3bca12..528d748c86 100644 --- a/lib/config/cmd.go +++ b/lib/config/cmd.go @@ -5,15 +5,22 @@ package config import ( + "bytes" + "errors" + "flag" "fmt" "io" + "os" + "os/exec" - "git.curoverse.com/arvados.git/lib/cmd" - "git.curoverse.com/arvados.git/sdk/go/ctxlog" + "git.arvados.org/arvados.git/lib/cmd" + "git.arvados.org/arvados.git/sdk/go/arvados" + "git.arvados.org/arvados.git/sdk/go/ctxlog" "github.com/ghodss/yaml" + "github.com/sirupsen/logrus" ) -var DumpCommand cmd.Handler = dumpCommand{} +var DumpCommand dumpCommand type dumpCommand struct{} @@ -24,12 +31,19 @@ func (dumpCommand) RunCommand(prog string, args []string, stdin io.Reader, stdou fmt.Fprintf(stderr, "%s\n", err) } }() - if len(args) != 0 { - err = fmt.Errorf("usage: %s config-min.yaml", prog) - return 2 + + loader := &Loader{ + Stdin: stdin, + Logger: ctxlog.New(stderr, "text", "info"), + } + + flags := flag.NewFlagSet("", flag.ContinueOnError) + loader.SetupFlags(flags) + + if ok, code := cmd.ParseFlags(flags, prog, args, "", stderr); !ok { + return code } - log := ctxlog.New(stderr, "text", "info") - cfg, err := Load(stdin, log) + cfg, err := loader.Load() if err != nil { return 1 } @@ -43,3 +57,133 @@ func (dumpCommand) RunCommand(prog string, args []string, stdin io.Reader, stdou } return 0 } + +var CheckCommand checkCommand + +type checkCommand struct{} + +func (checkCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int { + var err error + var logbuf = &bytes.Buffer{} + defer func() { + io.Copy(stderr, logbuf) + if err != nil { + fmt.Fprintf(stderr, "%s\n", err) + } + }() + + logger := logrus.New() + logger.Out = logbuf + loader := &Loader{ + Stdin: stdin, + Logger: logger, + } + + flags := flag.NewFlagSet(prog, flag.ContinueOnError) + loader.SetupFlags(flags) + strict := flags.Bool("strict", true, "Strict validation of configuration file (warnings result in non-zero exit code)") + if ok, code := cmd.ParseFlags(flags, prog, args, "", stderr); !ok { + return code + } + + // Load the config twice -- once without loading deprecated + // keys/files, once with -- and then compare the two resulting + // configs. This reveals whether the deprecated keys/files + // have any effect on the final configuration. + // + // If they do, show the operator how to update their config + // such that the deprecated keys/files are superfluous and can + // be deleted. + loader.SkipDeprecated = true + loader.SkipLegacy = true + withoutDepr, err := loader.Load() + if err != nil { + return 1 + } + // Reset() to avoid printing the same warnings twice when they + // are logged by both without-legacy and with-legacy loads. + logbuf.Reset() + loader.SkipDeprecated = false + loader.SkipLegacy = false + withDepr, err := loader.Load() + if err != nil { + return 1 + } + + // Check for configured vocabulary validity. + for id, cc := range withDepr.Clusters { + if cc.API.VocabularyPath == "" { + continue + } + vd, err := os.ReadFile(cc.API.VocabularyPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + // If the vocabulary path doesn't exist, it might mean that + // the current node isn't the controller; so it's not an + // error. + continue + } + logger.Errorf("Error reading vocabulary file %q for cluster %s: %s\n", cc.API.VocabularyPath, id, err) + continue + } + mk := make([]string, 0, len(cc.Collections.ManagedProperties)) + for k := range cc.Collections.ManagedProperties { + mk = append(mk, k) + } + _, err = arvados.NewVocabulary(vd, mk) + if err != nil { + logger.Errorf("Error loading vocabulary file %q for cluster %s:\n%s\n", cc.API.VocabularyPath, id, err) + continue + } + } + + cmd := exec.Command("diff", "-u", "--label", "without-deprecated-configs", "--label", "relying-on-deprecated-configs", "/dev/fd/3", "/dev/fd/4") + for _, obj := range []interface{}{withoutDepr, withDepr} { + y, _ := yaml.Marshal(obj) + pr, pw, err := os.Pipe() + if err != nil { + return 1 + } + defer pr.Close() + go func() { + io.Copy(pw, bytes.NewBuffer(y)) + pw.Close() + }() + cmd.ExtraFiles = append(cmd.ExtraFiles, pr) + } + diff, err := cmd.CombinedOutput() + if bytes.HasPrefix(diff, []byte("--- ")) { + fmt.Fprintln(stdout, "Your configuration is relying on deprecated entries. Suggest making the following changes.") + stdout.Write(diff) + err = nil + if *strict { + return 1 + } + } else if len(diff) > 0 { + fmt.Fprintf(stderr, "Unexpected diff output:\n%s", diff) + if *strict { + return 1 + } + } else if err != nil { + return 1 + } + if logbuf.Len() > 0 { + if *strict { + return 1 + } + } + return 0 +} + +var DumpDefaultsCommand defaultsCommand + +type defaultsCommand struct{} + +func (defaultsCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int { + _, err := stdout.Write(DefaultYAML) + if err != nil { + fmt.Fprintln(stderr, err) + return 1 + } + return 0 +}