// Copyright (C) The Arvados Authors. All rights reserved. // // SPDX-License-Identifier: AGPL-3.0 package config import ( "bytes" "errors" "flag" "fmt" "io" "os" "os/exec" "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 dumpCommand type dumpCommand struct{} func (dumpCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int { var err error defer func() { if err != nil { fmt.Fprintf(stderr, "%s\n", err) } }() 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 } cfg, err := loader.Load() if err != nil { return 1 } out, err := yaml.Marshal(cfg) if err != nil { return 1 } _, err = stdout.Write(out) if err != nil { return 1 } 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 }