347e8519a9717dff33eaefee1a3ed2570a4d013c
[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/arvados"
16         "git.arvados.org/arvados.git/sdk/go/ctxlog"
17         "github.com/ghodss/yaml"
18         "github.com/sirupsen/logrus"
19 )
20
21 var DumpCommand dumpCommand
22
23 type dumpCommand struct{}
24
25 func (dumpCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
26         var err error
27         defer func() {
28                 if err != nil {
29                         fmt.Fprintf(stderr, "%s\n", err)
30                 }
31         }()
32
33         loader := &Loader{
34                 Stdin:  stdin,
35                 Logger: ctxlog.New(stderr, "text", "info"),
36         }
37
38         flags := flag.NewFlagSet("", flag.ContinueOnError)
39         flags.SetOutput(stderr)
40         loader.SetupFlags(flags)
41
42         err = flags.Parse(args)
43         if err == flag.ErrHelp {
44                 err = nil
45                 return 0
46         } else if err != nil {
47                 return 2
48         }
49
50         if len(flags.Args()) != 0 {
51                 flags.Usage()
52                 return 2
53         }
54
55         cfg, err := loader.Load()
56         if err != nil {
57                 return 1
58         }
59         out, err := yaml.Marshal(cfg)
60         if err != nil {
61                 return 1
62         }
63         _, err = stdout.Write(out)
64         if err != nil {
65                 return 1
66         }
67         return 0
68 }
69
70 var CheckCommand checkCommand
71
72 type checkCommand struct{}
73
74 func (checkCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
75         var err error
76         var logbuf = &bytes.Buffer{}
77         defer func() {
78                 io.Copy(stderr, logbuf)
79                 if err != nil {
80                         fmt.Fprintf(stderr, "%s\n", err)
81                 }
82         }()
83
84         logger := logrus.New()
85         logger.Out = logbuf
86         loader := &Loader{
87                 Stdin:  stdin,
88                 Logger: logger,
89         }
90
91         flags := flag.NewFlagSet("", flag.ContinueOnError)
92         flags.SetOutput(stderr)
93         loader.SetupFlags(flags)
94         strict := flags.Bool("strict", true, "Strict validation of configuration file (warnings result in non-zero exit code)")
95
96         err = flags.Parse(args)
97         if err == flag.ErrHelp {
98                 err = nil
99                 return 0
100         } else if err != nil {
101                 return 2
102         }
103
104         if len(flags.Args()) != 0 {
105                 flags.Usage()
106                 return 2
107         }
108
109         // Load the config twice -- once without loading deprecated
110         // keys/files, once with -- and then compare the two resulting
111         // configs. This reveals whether the deprecated keys/files
112         // have any effect on the final configuration.
113         //
114         // If they do, show the operator how to update their config
115         // such that the deprecated keys/files are superfluous and can
116         // be deleted.
117         loader.SkipDeprecated = true
118         loader.SkipLegacy = true
119         withoutDepr, err := loader.Load()
120         if err != nil {
121                 return 1
122         }
123         loader.SkipDeprecated = false
124         loader.SkipLegacy = false
125         withDepr, err := loader.Load()
126         if err != nil {
127                 return 1
128         }
129         problems := false
130         if warnAboutProblems(logger, withDepr) {
131                 problems = true
132         }
133         cmd := exec.Command("diff", "-u", "--label", "without-deprecated-configs", "--label", "relying-on-deprecated-configs", "/dev/fd/3", "/dev/fd/4")
134         for _, obj := range []interface{}{withoutDepr, withDepr} {
135                 y, _ := yaml.Marshal(obj)
136                 pr, pw, err := os.Pipe()
137                 if err != nil {
138                         return 1
139                 }
140                 defer pr.Close()
141                 go func() {
142                         io.Copy(pw, bytes.NewBuffer(y))
143                         pw.Close()
144                 }()
145                 cmd.ExtraFiles = append(cmd.ExtraFiles, pr)
146         }
147         diff, err := cmd.CombinedOutput()
148         if bytes.HasPrefix(diff, []byte("--- ")) {
149                 fmt.Fprintln(stdout, "Your configuration is relying on deprecated entries. Suggest making the following changes.")
150                 stdout.Write(diff)
151                 err = nil
152                 if *strict {
153                         return 1
154                 }
155         } else if len(diff) > 0 {
156                 fmt.Fprintf(stderr, "Unexpected diff output:\n%s", diff)
157                 if *strict {
158                         return 1
159                 }
160         } else if err != nil {
161                 return 1
162         }
163         if logbuf.Len() > 0 {
164                 if *strict {
165                         return 1
166                 }
167         }
168
169         if problems {
170                 return 1
171         }
172         return 0
173 }
174
175 func warnAboutProblems(logger logrus.FieldLogger, cfg *arvados.Config) bool {
176         warned := false
177         for id, cc := range cfg.Clusters {
178                 if cc.SystemRootToken == "" {
179                         logger.Warnf("Clusters.%s.SystemRootToken is empty; see https://doc.arvados.org/master/install/install-keepstore.html", id)
180                         warned = true
181                 }
182                 if cc.ManagementToken == "" {
183                         logger.Warnf("Clusters.%s.ManagementToken is empty; see https://doc.arvados.org/admin/management-token.html", id)
184                         warned = true
185                 }
186         }
187         return warned
188 }
189
190 var DumpDefaultsCommand defaultsCommand
191
192 type defaultsCommand struct{}
193
194 func (defaultsCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
195         _, err := stdout.Write(DefaultYAML)
196         if err != nil {
197                 fmt.Fprintln(stderr, err)
198                 return 1
199         }
200         return 0
201 }