github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/command/var_init.go (about) 1 package command 2 3 import ( 4 "errors" 5 "fmt" 6 "io/fs" 7 "os" 8 "regexp" 9 "strings" 10 11 "github.com/muesli/reflow/wordwrap" 12 "github.com/posener/complete" 13 ) 14 15 const ( 16 // DefaultHclVarInitName is the default name we use when initializing the 17 // example var file in HCL format 18 DefaultHclVarInitName = "spec.nv.hcl" 19 20 // DefaultHclVarInitName is the default name we use when initializing the 21 // example var file in JSON format 22 DefaultJsonVarInitName = "spec.nv.json" 23 ) 24 25 // VarInitCommand generates a new variable specification 26 type VarInitCommand struct { 27 Meta 28 } 29 30 func (c *VarInitCommand) Help() string { 31 helpText := ` 32 Usage: nomad var init <filename> 33 34 Creates an example variable specification file that can be used as a starting 35 point to customize further. When no filename is supplied, a default filename 36 of "spec.nv.hcl" or "spec.nv.json" will be used depending on the output 37 format. 38 39 Init Options: 40 41 -out (hcl | json) 42 Format of generated variable specification. Defaults to "hcl". 43 44 ` 45 return strings.TrimSpace(helpText) 46 } 47 48 func (c *VarInitCommand) Synopsis() string { 49 return "Create an example variable specification file" 50 } 51 52 func (c *VarInitCommand) AutocompleteFlags() complete.Flags { 53 return complete.Flags{ 54 "-out": complete.PredictSet("hcl", "json"), 55 } 56 } 57 58 func (c *VarInitCommand) AutocompleteArgs() complete.Predictor { 59 return complete.PredictNothing 60 } 61 62 func (c *VarInitCommand) Name() string { return "var init" } 63 64 func (c *VarInitCommand) Run(args []string) int { 65 var outFmt string 66 var quiet bool 67 68 flags := c.Meta.FlagSet(c.Name(), FlagSetClient) 69 flags.Usage = func() { c.Ui.Output(c.Help()) } 70 flags.StringVar(&outFmt, "out", "hcl", "") 71 72 if err := flags.Parse(args); err != nil { 73 return 1 74 } 75 76 // Check that we get no arguments 77 args = flags.Args() 78 if l := len(args); l > 1 { 79 c.Ui.Error("This command takes no arguments or one: <filename>") 80 c.Ui.Error(commandErrorText(c)) 81 return 1 82 } 83 var fileName, fileContent string 84 switch outFmt { 85 case "hcl": 86 fileName = DefaultHclVarInitName 87 fileContent = defaultHclVarSpec 88 case "json": 89 fileName = DefaultJsonVarInitName 90 fileContent = defaultJsonVarSpec 91 } 92 93 if len(args) == 1 { 94 fileName = args[0] 95 } 96 97 // Check if the file already exists 98 _, err := os.Stat(fileName) 99 if err == nil { 100 c.Ui.Error(fmt.Sprintf("File %q already exists", fileName)) 101 return 1 102 } 103 if err != nil && !errors.Is(err, fs.ErrNotExist) { 104 c.Ui.Error(fmt.Sprintf("Failed to stat %q: %v", fileName, err)) 105 return 1 106 } 107 108 // Write out the example 109 err = os.WriteFile(fileName, []byte(fileContent), 0660) 110 if err != nil { 111 c.Ui.Error(fmt.Sprintf("Failed to write %q: %v", fileName, err)) 112 return 1 113 } 114 115 // Success 116 if !quiet { 117 if outFmt == "json" { 118 c.Ui.Info(wrapString(tidyRawString(strings.ReplaceAll(msgOnlyItemsRequired, "items", "Items")), 70)) 119 c.Ui.Warn(wrapString(tidyRawString(strings.ReplaceAll(msgWarnKeys, "items", "Items")), 70)) 120 } 121 c.Ui.Output(fmt.Sprintf("Example variable specification written to %s", fileName)) 122 } 123 return 0 124 } 125 126 const ( 127 msgWarnKeys = ` 128 REMINDER: While keys in the items map can contain dots, using them in 129 templates is easier when they do not. As a best practice, avoid dotted 130 keys when possible.` 131 msgOnlyItemsRequired = ` 132 The items map is the only strictly required part of a variable 133 specification, since path and namespace can be set via other means. It 134 contains the sensitive material to encrypt and store as a Nomad variable. 135 The entire items map is encrypted and decrypted as a single unit.` 136 ) 137 138 var defaultHclVarSpec = strings.TrimSpace(` 139 # A variable path can be specified in the specification file 140 # and will be used when writing the variable without specifying a 141 # path in the command or when writing JSON directly to the `+"`/var/`"+` 142 # HTTP API endpoint 143 # path = "path/to/variable" 144 145 # The Namespace to write the variable can be included in the specification. This 146 # value can be overridden by specifying the "-namespace" flag on the "put" 147 # command. 148 # namespace = "default" 149 150 `+makeHCLComment(msgOnlyItemsRequired)+` 151 152 `+makeHCLComment(msgWarnKeys)+` 153 items { 154 key1 = "value 1" 155 key2 = "value 2" 156 } 157 `) + "\n" 158 159 var defaultJsonVarSpec = strings.TrimSpace(` 160 { 161 "Namespace": "default", 162 "Path": "path/to/variable", 163 "Items": { 164 "key1": "value 1", 165 "key2": "value 2" 166 } 167 } 168 `) + "\n" 169 170 // makeHCLComment is a helper function that will take the contents of a raw 171 // string, tidy them, wrap them to 68 characters and add a leading comment 172 // marker plus a space. 173 func makeHCLComment(in string) string { 174 return wrapAndPrepend(tidyRawString(in), 70, "# ") 175 } 176 177 // wrapString is a convenience func to abstract away the word wrapping 178 // implementation 179 func wrapString(input string, lineLen int) string { 180 return wordwrap.String(input, lineLen) 181 } 182 183 // wrapAndPrepend will word wrap the input string to lineLen characters and 184 // prepend the provided prefix to every line. The total length of each returned 185 // line will be at most len(input[line])+len(prefix) 186 func wrapAndPrepend(input string, lineLen int, prefix string) string { 187 ss := strings.Split(wrapString(input, lineLen-len(prefix)), "\n") 188 prefixStringList(ss, prefix) 189 return strings.Join(ss, "\n") 190 } 191 192 // tidyRawString will convert a wrapped and indented raw string into a single 193 // long string suitable for rewrapping with another tool. It trims leading and 194 // trailing whitespace and then consume groups of tabs, newlines, and spaces 195 // replacing them with a single space 196 func tidyRawString(raw string) string { 197 re := regexp.MustCompile("[\t\n ]+") 198 return re.ReplaceAllString(strings.TrimSpace(raw), " ") 199 } 200 201 // prefixStringList is a helper function that prepends each item in a slice of 202 // string with a provided prefix. 203 func prefixStringList(ss []string, prefix string) []string { 204 for i, s := range ss { 205 ss[i] = prefix + s 206 } 207 return ss 208 }