From 22194d437a45ec2d8b059066eaaf87e749da6e36 Mon Sep 17 00:00:00 2001 From: Charlie Voiselle <464492+angrycub@users.noreply.github.com> Date: Mon, 15 Aug 2022 13:43:29 -0400 Subject: [PATCH] SV CLI: var init (#13820) * Nomad dep: add museli/reflow * SV CLI: var init --- command/commands.go | 5 ++ command/var_init.go | 193 ++++++++++++++++++++++++++++++++++++++++++++ go.mod | 5 +- go.sum | 8 +- 4 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 command/var_init.go diff --git a/command/commands.go b/command/commands.go index 2418f1033..3add86fa9 100644 --- a/command/commands.go +++ b/command/commands.go @@ -911,6 +911,11 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory { Meta: meta, }, nil }, + "var init": func() (cli.Command, error) { + return &VarInitCommand{ + Meta: meta, + }, nil + }, "version": func() (cli.Command, error) { return &VersionCommand{ Version: version.GetVersion(), diff --git a/command/var_init.go b/command/var_init.go new file mode 100644 index 000000000..965777e0a --- /dev/null +++ b/command/var_init.go @@ -0,0 +1,193 @@ +package command + +import ( + "fmt" + "io/ioutil" + "os" + "regexp" + "strings" + + "github.com/muesli/reflow/wordwrap" + "github.com/posener/complete" +) + +const ( + // DefaultHclVarInitName is the default name we use when initializing the + // example var file in HCL format + DefaultHclVarInitName = "spec.nsv.hcl" + + // DefaultHclVarInitName is the default name we use when initializing the + // example var file in JSON format + DefaultJsonVarInitName = "spec.nsv.json" +) + +// VarInitCommand generates a new secure variable specification +type VarInitCommand struct { + Meta +} + +func (c *VarInitCommand) Help() string { + helpText := ` +Usage: nomad var init + + Creates an example secure variable specification file that can be used as a + starting point to customize further. If no filename is given, the default of + "spec.nsv.hcl" or "spec.nsv.json" will be used. + +Init Options: + + -json + Create an example JSON secure variable specification. + + -q + Suppress non-error output +` + return strings.TrimSpace(helpText) +} + +func (c *VarInitCommand) Synopsis() string { + return "Create an example secure variable specification file" +} + +func (c *VarInitCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + "-json": complete.PredictNothing, + } +} + +func (c *VarInitCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *VarInitCommand) Name() string { return "var init" } + +func (c *VarInitCommand) Run(args []string) int { + var jsonOutput bool + var quiet bool + + flags := c.Meta.FlagSet(c.Name(), FlagSetClient) + flags.Usage = func() { c.Ui.Output(c.Help()) } + flags.BoolVar(&jsonOutput, "json", false, "") + flags.BoolVar(&quiet, "q", false, "") + + if err := flags.Parse(args); err != nil { + return 1 + } + + // Check that we get no arguments + args = flags.Args() + if l := len(args); l > 1 { + c.Ui.Error("This command takes no arguments or one: ") + c.Ui.Error(commandErrorText(c)) + return 1 + } + + fileName := DefaultHclVarInitName + fileContent := defaultHclVarSpec + if jsonOutput { + fileName = DefaultJsonVarInitName + fileContent = defaultJsonVarSpec + } + if len(args) == 1 { + fileName = args[0] + } + + // Check if the file already exists + _, err := os.Stat(fileName) + if err != nil && !os.IsNotExist(err) { + c.Ui.Error(fmt.Sprintf("Failed to stat %q: %v", fileName, err)) + return 1 + } + if !os.IsNotExist(err) { + c.Ui.Error(fmt.Sprintf("File %q already exists", fileName)) + return 1 + } + + // Write out the example + err = ioutil.WriteFile(fileName, []byte(fileContent), 0660) + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to write %q: %v", fileName, err)) + return 1 + } + + // Success + if !quiet { + c.Ui.Warn(WrapAndPrepend(TidyRawString(msgWarnKeys), 70, "")) + c.Ui.Output(fmt.Sprintf("Example secure variable specification written to %s", fileName)) + } + return 0 +} + +const ( + msgWarnKeys = ` + REMINDER: While keys in the items map can contain dots, using them in + templates is easier when they do not. As a best practice, avoid dotted + keys when possible.` +) + +var defaultHclVarSpec = strings.TrimSpace(` +# A secure variable path can be specified in the specification file +# and will be used when writing the variable without specifying a +# path in the command or when writing JSON directly to the `+"`/var/`"+` +# HTTP API endpoint +# path = "path/to/variable" + +# The Namespace to write the variable can be included in the specification +# and is the highest precedence way to set the namespace value. +# namespace = "default" + +# The items map is the only strictly required part of a secure variable +# specification, since path and namespace can be set via other means. It +# contains the sensitive material to encrypt and store as a Nomad secure +# variable. The entire items map is encrypted and decrypted as a single unit. + +`+warnInHCLFile()+` +items { + key1 = "value 1" + key2 = "value 2" +} +`) + "\n" + +var defaultJsonVarSpec = strings.TrimSpace(` +{ + "Items": { + "key1": "value 1", + "key2": "value 2" + } +} +`) + "\n" + +func warnInHCLFile() string { + return WrapAndPrepend(TidyRawString(msgWarnKeys), 70, "# ") +} + +// WrapString is a convienience func to abstract away the word wrapping +// implementation +func WrapString(input string, lineLen int) string { + return wordwrap.String(input, lineLen) +} + +// WrapAndPrepend will word wrap the input string to lineLen characters and +// prepend the provided prefix to every line. The total length of each returned +// line will be at most len(input[line])+len(prefix) +func WrapAndPrepend(input string, lineLen int, prefix string) string { + ss := strings.Split(wordwrap.String(input, lineLen), "\n") + prefixStringList(ss, prefix) + return strings.Join(ss, "\n") +} + +// TidyRawString will convert a wrapped and indented raw string into a single +// long string suitable for rewrapping with another tool. It trims leading and +// trailing whitespace and then consume groups of tabs, newlines, and spaces +// replacing them with a single space +func TidyRawString(raw string) string { + re := regexp.MustCompile("[\t\n ]+") + return re.ReplaceAllString(strings.TrimSpace(raw), " ") +} + +func prefixStringList(ss []string, prefix string) []string { + for i, s := range ss { + ss[i] = prefix + s + } + return ss +} diff --git a/go.mod b/go.mod index c99b24033..11e6c6f7c 100644 --- a/go.mod +++ b/go.mod @@ -223,13 +223,14 @@ require ( github.com/linode/linodego v0.7.1 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mattn/go-isatty v0.0.14 // indirect - github.com/mattn/go-runewidth v0.0.7 // indirect + github.com/mattn/go-runewidth v0.0.12 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/pointerstructure v1.2.1 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/mrunalp/fileutils v0.5.0 // indirect + github.com/muesli/reflow v0.3.0 github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 // indirect github.com/oklog/run v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -278,3 +279,5 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +require github.com/rivo/uniseg v0.2.0 // indirect diff --git a/go.sum b/go.sum index 8a6458e43..c3a1dbfeb 100644 --- a/go.sum +++ b/go.sum @@ -937,8 +937,9 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -1017,6 +1018,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.0 h1:NKzVxiH7eSk+OQ4M+ZYW1K6h27RUV3MI6NUTsHhU6Z4= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -1164,6 +1167,9 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rboyer/safeio v0.2.1/go.mod h1:Cq/cEPK+YXFn622lsQ0K4KsPZSPtaptHHEldsy7Fmig= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOekn6B5G/+GMtks9UKbvRU/CMM/o= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=