/
flags.go
190 lines (170 loc) · 5.07 KB
/
flags.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package common
import (
"fmt"
"io"
"os"
"strconv"
"strings"
"github.com/juju/cmd/v3"
"github.com/juju/errors"
"github.com/juju/utils/v3"
"gopkg.in/yaml.v2"
"github.com/juju/juju/core/constraints"
)
// ConfigFlag records k=v attributes from command arguments
// and/or specified files containing key values.
type ConfigFlag struct {
files []string
attrs map[string]interface{}
}
// Set implements gnuflag.Value.Set.
// TODO (stickupkid): Clean this up to correctly handle stdin. Additionally the
// method is confusing and cryptic, we should improve this at some point!
func (f *ConfigFlag) Set(s string) error {
if s == "" {
return errors.NotValidf("empty string")
}
// Attempt to work out if we're dealing with key value pairs or a string
// for a file.
// If the key value pairs don't contain a `=` sign, then it's assumed it's
// a file, so append that and bail out.
fields := strings.SplitN(s, "=", 2)
if len(fields) == 1 {
f.files = append(f.files, fields[0])
return nil
}
var value interface{} = fields[1]
if f.attrs == nil {
f.attrs = make(map[string]interface{})
}
f.attrs[fields[0]] = value
return nil
}
// SetAttrsFromReader sets the attributes from a slice of bytes. The bytes are
// expected to be YAML parsable and align to the attrs type of
// map[string]interface{}.
// This will over write any attributes that already exist if found in the YAML
// configuration.
func (f *ConfigFlag) SetAttrsFromReader(reader io.Reader) error {
if reader == nil {
return errors.NotValidf("empty reader")
}
attrs := make(map[string]interface{})
if err := yaml.NewDecoder(reader).Decode(&attrs); err != nil {
return errors.Trace(err)
}
if f.attrs == nil {
f.attrs = make(map[string]interface{})
}
for k, attr := range attrs {
f.attrs[k] = attr
}
return nil
}
// ReadAttrs reads attributes from the specified files, and then overlays
// the results with the k=v attributes.
// TODO (stickupkid): This should only know about io.Readers and correctly
// handle the various path ways from that abstraction.
func (f *ConfigFlag) ReadAttrs(ctx *cmd.Context) (map[string]interface{}, error) {
attrs := make(map[string]interface{})
for _, f := range f.files {
path, err := utils.NormalizePath(f)
if err != nil {
return nil, errors.Trace(err)
}
data, err := os.ReadFile(ctx.AbsPath(path))
if err != nil {
return nil, errors.Trace(err)
}
if err := yaml.Unmarshal(data, &attrs); err != nil {
return nil, err
}
}
for k, v := range f.attrs {
attrs[k] = v
}
return attrs, nil
}
// ReadConfigPairs returns just the k=v attributes.
func (f *ConfigFlag) ReadConfigPairs(ctx *cmd.Context) (map[string]interface{}, error) {
attrs := make(map[string]interface{})
for k, v := range f.attrs {
attrs[k] = v
}
return attrs, nil
}
// AbsoluteFileNames returns the absolute path of any file names specified.
func (f *ConfigFlag) AbsoluteFileNames(ctx *cmd.Context) ([]string, error) {
files := make([]string, len(f.files))
for i, f := range f.files {
path, err := utils.NormalizePath(f)
if err != nil {
return nil, errors.Trace(err)
}
files[i] = ctx.AbsPath(path)
}
return files, nil
}
// String implements gnuflag.Value.String.
func (f *ConfigFlag) String() string {
strs := make([]string, 0, len(f.attrs)+len(f.files))
strs = append(strs, f.files...)
for k, v := range f.attrs {
strs = append(strs, fmt.Sprintf("%s=%v", k, v))
}
return strings.Join(strs, " ")
}
// WarnConstraintAliases shows a warning to the user that they have used an
// alias for a constraint that might go away sometime.
func WarnConstraintAliases(ctx *cmd.Context, aliases map[string]string) {
for alias, canonical := range aliases {
ctx.Infof("Warning: constraint %q is deprecated in favor of %q.\n", alias, canonical)
}
}
// ParseConstraints parses the given constraints and uses WarnConstraintAliases
// if any aliases were used.
func ParseConstraints(ctx *cmd.Context, cons string) (constraints.Value, error) {
if cons == "" {
return constraints.Value{}, nil
}
constraint, aliases, err := constraints.ParseWithAliases(cons)
// we always do these, even on errors, so that the error messages have
// context.
for alias, canonical := range aliases {
ctx.Infof("Warning: constraint %q is deprecated in favor of %q.\n", alias, canonical)
}
if err != nil {
return constraints.Value{}, err
}
return constraint, nil
}
// AutoBoolValue is like gnuflag.boolValue, but remembers
// whether or not a value has been set, so its behaviour
// can be determined dynamically, during command execution.
type AutoBoolValue struct {
b *bool
}
// Set implements flag.Value.
func (b *AutoBoolValue) Set(s string) error {
v, err := strconv.ParseBool(s)
if err != nil {
return err
}
b.b = &v
return nil
}
// Get returns the value.
func (b *AutoBoolValue) Get() *bool {
return b.b
}
// String implements flag.Value.
func (b *AutoBoolValue) String() string {
if b.b != nil {
return fmt.Sprint(*b.b)
}
return "nil"
}
// IsBoolFlag implements flag.Value.
func (b *AutoBoolValue) IsBoolFlag() bool { return true }