1package command
2
3import (
4	"flag"
5	"os"
6	"strings"
7
8	"github.com/hashicorp/nomad/api"
9	"github.com/mitchellh/cli"
10	"github.com/mitchellh/colorstring"
11	"github.com/posener/complete"
12	"golang.org/x/crypto/ssh/terminal"
13)
14
15const (
16	// Constants for CLI identifier length
17	shortId = 8
18	fullId  = 36
19)
20
21// FlagSetFlags is an enum to define what flags are present in the
22// default FlagSet returned by Meta.FlagSet.
23type FlagSetFlags uint
24
25const (
26	FlagSetNone    FlagSetFlags = 0
27	FlagSetClient  FlagSetFlags = 1 << iota
28	FlagSetDefault              = FlagSetClient
29)
30
31// Meta contains the meta-options and functionality that nearly every
32// Nomad command inherits.
33type Meta struct {
34	Ui cli.Ui
35
36	// These are set by the command line flags.
37	flagAddress string
38
39	// Whether to not-colorize output
40	noColor bool
41
42	// The region to send API requests
43	region string
44
45	// namespace to send API requests
46	namespace string
47
48	// token is used for ACLs to access privileged information
49	token string
50
51	caCert        string
52	caPath        string
53	clientCert    string
54	clientKey     string
55	tlsServerName string
56	insecure      bool
57}
58
59// FlagSet returns a FlagSet with the common flags that every
60// command implements. The exact behavior of FlagSet can be configured
61// using the flags as the second parameter, for example to disable
62// server settings on the commands that don't talk to a server.
63func (m *Meta) FlagSet(n string, fs FlagSetFlags) *flag.FlagSet {
64	f := flag.NewFlagSet(n, flag.ContinueOnError)
65
66	// FlagSetClient is used to enable the settings for specifying
67	// client connectivity options.
68	if fs&FlagSetClient != 0 {
69		f.StringVar(&m.flagAddress, "address", "", "")
70		f.StringVar(&m.region, "region", "", "")
71		f.StringVar(&m.namespace, "namespace", "", "")
72		f.BoolVar(&m.noColor, "no-color", false, "")
73		f.StringVar(&m.caCert, "ca-cert", "", "")
74		f.StringVar(&m.caPath, "ca-path", "", "")
75		f.StringVar(&m.clientCert, "client-cert", "", "")
76		f.StringVar(&m.clientKey, "client-key", "", "")
77		f.BoolVar(&m.insecure, "insecure", false, "")
78		f.StringVar(&m.tlsServerName, "tls-server-name", "", "")
79		f.BoolVar(&m.insecure, "tls-skip-verify", false, "")
80		f.StringVar(&m.token, "token", "", "")
81
82	}
83
84	f.SetOutput(&uiErrorWriter{ui: m.Ui})
85
86	return f
87}
88
89// AutocompleteFlags returns a set of flag completions for the given flag set.
90func (m *Meta) AutocompleteFlags(fs FlagSetFlags) complete.Flags {
91	if fs&FlagSetClient == 0 {
92		return nil
93	}
94
95	return complete.Flags{
96		"-address":         complete.PredictAnything,
97		"-region":          complete.PredictAnything,
98		"-namespace":       NamespacePredictor(m.Client, nil),
99		"-no-color":        complete.PredictNothing,
100		"-ca-cert":         complete.PredictFiles("*"),
101		"-ca-path":         complete.PredictDirs("*"),
102		"-client-cert":     complete.PredictFiles("*"),
103		"-client-key":      complete.PredictFiles("*"),
104		"-insecure":        complete.PredictNothing,
105		"-tls-server-name": complete.PredictNothing,
106		"-tls-skip-verify": complete.PredictNothing,
107		"-token":           complete.PredictAnything,
108	}
109}
110
111// ApiClientFactory is the signature of a API client factory
112type ApiClientFactory func() (*api.Client, error)
113
114// Client is used to initialize and return a new API client using
115// the default command line arguments and env vars.
116func (m *Meta) clientConfig() *api.Config {
117	config := api.DefaultConfig()
118	if m.flagAddress != "" {
119		config.Address = m.flagAddress
120	}
121	if m.region != "" {
122		config.Region = m.region
123	}
124	if m.namespace != "" {
125		config.Namespace = m.namespace
126	}
127
128	// If we need custom TLS configuration, then set it
129	if m.caCert != "" || m.caPath != "" || m.clientCert != "" || m.clientKey != "" || m.tlsServerName != "" || m.insecure {
130		t := &api.TLSConfig{
131			CACert:        m.caCert,
132			CAPath:        m.caPath,
133			ClientCert:    m.clientCert,
134			ClientKey:     m.clientKey,
135			TLSServerName: m.tlsServerName,
136			Insecure:      m.insecure,
137		}
138		config.TLSConfig = t
139	}
140
141	if m.token != "" {
142		config.SecretID = m.token
143	}
144
145	return config
146}
147
148func (m *Meta) Client() (*api.Client, error) {
149	return api.NewClient(m.clientConfig())
150}
151
152func (m *Meta) allNamespaces() bool {
153	return m.clientConfig().Namespace == api.AllNamespacesNamespace
154}
155
156func (m *Meta) Colorize() *colorstring.Colorize {
157	return &colorstring.Colorize{
158		Colors:  colorstring.DefaultColors,
159		Disable: m.noColor || !terminal.IsTerminal(int(os.Stdout.Fd())),
160		Reset:   true,
161	}
162}
163
164type usageOptsFlags uint8
165
166const (
167	usageOptsDefault     usageOptsFlags = 0
168	usageOptsNoNamespace                = 1 << iota
169)
170
171// generalOptionsUsage returns the help string for the global options.
172func generalOptionsUsage(usageOpts usageOptsFlags) string {
173
174	helpText := `
175  -address=<addr>
176    The address of the Nomad server.
177    Overrides the NOMAD_ADDR environment variable if set.
178    Default = http://127.0.0.1:4646
179
180  -region=<region>
181    The region of the Nomad servers to forward commands to.
182    Overrides the NOMAD_REGION environment variable if set.
183    Defaults to the Agent's local region.
184`
185
186	namespaceText := `
187  -namespace=<namespace>
188    The target namespace for queries and actions bound to a namespace.
189    Overrides the NOMAD_NAMESPACE environment variable if set.
190    If set to '*', job and alloc subcommands query all namespaces authorized
191    to user.
192    Defaults to the "default" namespace.
193`
194
195	// note: that although very few commands use color explicitly, all of them
196	// return red-colored text on error so we don't want to make this
197	// configurable
198	remainingText := `
199  -no-color
200    Disables colored command output. Alternatively, NOMAD_CLI_NO_COLOR may be
201    set.
202
203  -ca-cert=<path>
204    Path to a PEM encoded CA cert file to use to verify the
205    Nomad server SSL certificate.  Overrides the NOMAD_CACERT
206    environment variable if set.
207
208  -ca-path=<path>
209    Path to a directory of PEM encoded CA cert files to verify
210    the Nomad server SSL certificate. If both -ca-cert and
211    -ca-path are specified, -ca-cert is used. Overrides the
212    NOMAD_CAPATH environment variable if set.
213
214  -client-cert=<path>
215    Path to a PEM encoded client certificate for TLS authentication
216    to the Nomad server. Must also specify -client-key. Overrides
217    the NOMAD_CLIENT_CERT environment variable if set.
218
219  -client-key=<path>
220    Path to an unencrypted PEM encoded private key matching the
221    client certificate from -client-cert. Overrides the
222    NOMAD_CLIENT_KEY environment variable if set.
223
224  -tls-server-name=<value>
225    The server name to use as the SNI host when connecting via
226    TLS. Overrides the NOMAD_TLS_SERVER_NAME environment variable if set.
227
228  -tls-skip-verify
229    Do not verify TLS certificate. This is highly not recommended. Verification
230    will also be skipped if NOMAD_SKIP_VERIFY is set.
231
232  -token
233    The SecretID of an ACL token to use to authenticate API requests with.
234    Overrides the NOMAD_TOKEN environment variable if set.
235`
236
237	if usageOpts&usageOptsNoNamespace == 0 {
238		helpText = helpText + namespaceText
239	}
240
241	helpText = helpText + remainingText
242	return strings.TrimSpace(helpText)
243}
244
245// funcVar is a type of flag that accepts a function that is the string given
246// by the user.
247type funcVar func(s string) error
248
249func (f funcVar) Set(s string) error { return f(s) }
250func (f funcVar) String() string     { return "" }
251func (f funcVar) IsBoolFlag() bool   { return false }
252