16997: Test map key order in check/dump. Fix duplicate warnings.
[arvados.git] / lib / config / cmd.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package config
6
7 import (
8         "bytes"
9         "flag"
10         "fmt"
11         "io"
12         "os"
13         "os/exec"
14
15         "git.arvados.org/arvados.git/sdk/go/ctxlog"
16         "github.com/ghodss/yaml"
17         "github.com/sirupsen/logrus"
18 )
19
20 var DumpCommand dumpCommand
21
22 type dumpCommand struct{}
23
24 func (dumpCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
25         var err error
26         defer func() {
27                 if err != nil {
28                         fmt.Fprintf(stderr, "%s\n", err)
29                 }
30         }()
31
32         loader := &Loader{
33                 Stdin:  stdin,
34                 Logger: ctxlog.New(stderr, "text", "info"),
35         }
36
37         flags := flag.NewFlagSet("", flag.ContinueOnError)
38         flags.SetOutput(stderr)
39         loader.SetupFlags(flags)
40
41         err = flags.Parse(args)
42         if err == flag.ErrHelp {
43                 err = nil
44                 return 0
45         } else if err != nil {
46                 return 2
47         }
48
49         if len(flags.Args()) != 0 {
50                 flags.Usage()
51                 return 2
52         }
53
54         cfg, err := loader.Load()
55         if err != nil {
56                 return 1
57         }
58         out, err := yaml.Marshal(cfg)
59         if err != nil {
60                 return 1
61         }
62         _, err = stdout.Write(out)
63         if err != nil {
64                 return 1
65         }
66         return 0
67 }
68
69 var CheckCommand checkCommand
70
71 type checkCommand struct{}
72
73 func (checkCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
74         var err error
75         var logbuf = &bytes.Buffer{}
76         defer func() {
77                 io.Copy(stderr, logbuf)
78                 if err != nil {
79                         fmt.Fprintf(stderr, "%s\n", err)
80                 }
81         }()
82
83         logger := logrus.New()
84         logger.Out = logbuf
85         loader := &Loader{
86                 Stdin:  stdin,
87                 Logger: logger,
88         }
89
90         flags := flag.NewFlagSet("", flag.ContinueOnError)
91         flags.SetOutput(stderr)
92         loader.SetupFlags(flags)
93         strict := flags.Bool("strict", true, "Strict validation of configuration file (warnings result in non-zero exit code)")
94
95         err = flags.Parse(args)
96         if err == flag.ErrHelp {
97                 err = nil
98                 return 0
99         } else if err != nil {
100                 return 2
101         }
102
103         if len(flags.Args()) != 0 {
104                 flags.Usage()
105                 return 2
106         }
107
108         // Load the config twice -- once without loading deprecated
109         // keys/files, once with -- and then compare the two resulting
110         // configs. This reveals whether the deprecated keys/files
111         // have any effect on the final configuration.
112         //
113         // If they do, show the operator how to update their config
114         // such that the deprecated keys/files are superfluous and can
115         // be deleted.
116         loader.SkipDeprecated = true
117         loader.SkipLegacy = true
118         withoutDepr, err := loader.Load()
119         if err != nil {
120                 return 1
121         }
122         // Reset() to avoid printing the same warnings twice when they
123         // are logged by both without-legacy and with-legacy loads.
124         logbuf.Reset()
125         loader.SkipDeprecated = false
126         loader.SkipLegacy = false
127         withDepr, err := loader.Load()
128         if err != nil {
129                 return 1
130         }
131         cmd := exec.Command("diff", "-u", "--label", "without-deprecated-configs", "--label", "relying-on-deprecated-configs", "/dev/fd/3", "/dev/fd/4")
132         for _, obj := range []interface{}{withoutDepr, withDepr} {
133                 y, _ := yaml.Marshal(obj)
134                 pr, pw, err := os.Pipe()
135                 if err != nil {
136                         return 1
137                 }
138                 defer pr.Close()
139                 go func() {
140                         io.Copy(pw, bytes.NewBuffer(y))
141                         pw.Close()
142                 }()
143                 cmd.ExtraFiles = append(cmd.ExtraFiles, pr)
144         }
145         diff, err := cmd.CombinedOutput()
146         if bytes.HasPrefix(diff, []byte("--- ")) {
147                 fmt.Fprintln(stdout, "Your configuration is relying on deprecated entries. Suggest making the following changes.")
148                 stdout.Write(diff)
149                 err = nil
150                 if *strict {
151                         return 1
152                 }
153         } else if len(diff) > 0 {
154                 fmt.Fprintf(stderr, "Unexpected diff output:\n%s", diff)
155                 if *strict {
156                         return 1
157                 }
158         } else if err != nil {
159                 return 1
160         }
161         if logbuf.Len() > 0 {
162                 if *strict {
163                         return 1
164                 }
165         }
166         return 0
167 }
168
169 var DumpDefaultsCommand defaultsCommand
170
171 type defaultsCommand struct{}
172
173 func (defaultsCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
174         _, err := stdout.Write(DefaultYAML)
175         if err != nil {
176                 fmt.Fprintln(stderr, err)
177                 return 1
178         }
179         return 0
180 }