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