c852b0b545af0e91ea856a004d7cdc2dc7850b92
[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         } else if flags.NArg() != 0 {
48                 err = fmt.Errorf("unrecognized command line arguments: %v", flags.Args())
49                 return 2
50         }
51
52         cfg, err := loader.Load()
53         if err != nil {
54                 return 1
55         }
56         out, err := yaml.Marshal(cfg)
57         if err != nil {
58                 return 1
59         }
60         _, err = stdout.Write(out)
61         if err != nil {
62                 return 1
63         }
64         return 0
65 }
66
67 var CheckCommand checkCommand
68
69 type checkCommand struct{}
70
71 func (checkCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
72         var err error
73         var logbuf = &bytes.Buffer{}
74         defer func() {
75                 io.Copy(stderr, logbuf)
76                 if err != nil {
77                         fmt.Fprintf(stderr, "%s\n", err)
78                 }
79         }()
80
81         logger := logrus.New()
82         logger.Out = logbuf
83         loader := &Loader{
84                 Stdin:  stdin,
85                 Logger: logger,
86         }
87
88         flags := flag.NewFlagSet("", flag.ContinueOnError)
89         flags.SetOutput(stderr)
90         loader.SetupFlags(flags)
91         strict := flags.Bool("strict", true, "Strict validation of configuration file (warnings result in non-zero exit code)")
92
93         err = flags.Parse(args)
94         if err == flag.ErrHelp {
95                 err = nil
96                 return 0
97         } else if err != nil {
98                 return 2
99         } else if flags.NArg() != 0 {
100                 err = fmt.Errorf("unrecognized command line arguments: %v", flags.Args())
101                 return 2
102         }
103
104         // Load the config twice -- once without loading deprecated
105         // keys/files, once with -- and then compare the two resulting
106         // configs. This reveals whether the deprecated keys/files
107         // have any effect on the final configuration.
108         //
109         // If they do, show the operator how to update their config
110         // such that the deprecated keys/files are superfluous and can
111         // be deleted.
112         loader.SkipDeprecated = true
113         loader.SkipLegacy = true
114         withoutDepr, err := loader.Load()
115         if err != nil {
116                 return 1
117         }
118         // Reset() to avoid printing the same warnings twice when they
119         // are logged by both without-legacy and with-legacy loads.
120         logbuf.Reset()
121         loader.SkipDeprecated = false
122         loader.SkipLegacy = false
123         withDepr, err := loader.Load()
124         if err != nil {
125                 return 1
126         }
127         cmd := exec.Command("diff", "-u", "--label", "without-deprecated-configs", "--label", "relying-on-deprecated-configs", "/dev/fd/3", "/dev/fd/4")
128         for _, obj := range []interface{}{withoutDepr, withDepr} {
129                 y, _ := yaml.Marshal(obj)
130                 pr, pw, err := os.Pipe()
131                 if err != nil {
132                         return 1
133                 }
134                 defer pr.Close()
135                 go func() {
136                         io.Copy(pw, bytes.NewBuffer(y))
137                         pw.Close()
138                 }()
139                 cmd.ExtraFiles = append(cmd.ExtraFiles, pr)
140         }
141         diff, err := cmd.CombinedOutput()
142         if bytes.HasPrefix(diff, []byte("--- ")) {
143                 fmt.Fprintln(stdout, "Your configuration is relying on deprecated entries. Suggest making the following changes.")
144                 stdout.Write(diff)
145                 err = nil
146                 if *strict {
147                         return 1
148                 }
149         } else if len(diff) > 0 {
150                 fmt.Fprintf(stderr, "Unexpected diff output:\n%s", diff)
151                 if *strict {
152                         return 1
153                 }
154         } else if err != nil {
155                 return 1
156         }
157         if logbuf.Len() > 0 {
158                 if *strict {
159                         return 1
160                 }
161         }
162         return 0
163 }
164
165 var DumpDefaultsCommand defaultsCommand
166
167 type defaultsCommand struct{}
168
169 func (defaultsCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
170         _, err := stdout.Write(DefaultYAML)
171         if err != nil {
172                 fmt.Fprintln(stderr, err)
173                 return 1
174         }
175         return 0
176 }