github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/command/quota_apply.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "strings" 10 11 multierror "github.com/hashicorp/go-multierror" 12 "github.com/hashicorp/hcl" 13 "github.com/hashicorp/hcl/hcl/ast" 14 "github.com/hashicorp/nomad/api" 15 "github.com/hashicorp/nomad/helper" 16 "github.com/mitchellh/mapstructure" 17 "github.com/posener/complete" 18 ) 19 20 type QuotaApplyCommand struct { 21 Meta 22 } 23 24 func (c *QuotaApplyCommand) Help() string { 25 helpText := ` 26 Usage: nomad quota apply [options] <input> 27 28 Apply is used to create or update a quota specification. The specification file 29 will be read from stdin by specifying "-", otherwise a path to the file is 30 expected. 31 32 General Options: 33 34 ` + generalOptionsUsage() + ` 35 36 Apply Options: 37 38 -json 39 Parse the input as a JSON quota specification. 40 ` 41 42 return strings.TrimSpace(helpText) 43 } 44 45 func (c *QuotaApplyCommand) AutocompleteFlags() complete.Flags { 46 return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), 47 complete.Flags{ 48 "-json": complete.PredictNothing, 49 }) 50 } 51 52 func (c *QuotaApplyCommand) AutocompleteArgs() complete.Predictor { 53 return complete.PredictFiles("*") 54 } 55 56 func (c *QuotaApplyCommand) Synopsis() string { 57 return "Create or update a quota specification" 58 } 59 60 func (c *QuotaApplyCommand) Run(args []string) int { 61 var jsonInput bool 62 flags := c.Meta.FlagSet("quota apply", FlagSetClient) 63 flags.Usage = func() { c.Ui.Output(c.Help()) } 64 flags.BoolVar(&jsonInput, "json", false, "") 65 66 if err := flags.Parse(args); err != nil { 67 return 1 68 } 69 70 // Check that we get exactly one argument 71 args = flags.Args() 72 if l := len(args); l != 1 { 73 c.Ui.Error(c.Help()) 74 return 1 75 } 76 77 // Read the file contents 78 file := args[0] 79 var rawQuota []byte 80 var err error 81 if file == "-" { 82 rawQuota, err = ioutil.ReadAll(os.Stdin) 83 if err != nil { 84 c.Ui.Error(fmt.Sprintf("Failed to read stdin: %v", err)) 85 return 1 86 } 87 } else { 88 rawQuota, err = ioutil.ReadFile(file) 89 if err != nil { 90 c.Ui.Error(fmt.Sprintf("Failed to read file: %v", err)) 91 return 1 92 } 93 } 94 95 var spec *api.QuotaSpec 96 if jsonInput { 97 var jsonSpec api.QuotaSpec 98 dec := json.NewDecoder(bytes.NewBuffer(rawQuota)) 99 if err := dec.Decode(&jsonSpec); err != nil { 100 c.Ui.Error(fmt.Sprintf("Failed to parse quota: %v", err)) 101 return 1 102 } 103 spec = &jsonSpec 104 } else { 105 hclSpec, err := parseQuotaSpec(rawQuota) 106 if err != nil { 107 c.Ui.Error(fmt.Sprintf("Error parsing quota specification: %s", err)) 108 return 1 109 } 110 111 spec = hclSpec 112 } 113 114 // Get the HTTP client 115 client, err := c.Meta.Client() 116 if err != nil { 117 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 118 return 1 119 } 120 121 _, err = client.Quotas().Register(spec, nil) 122 if err != nil { 123 c.Ui.Error(fmt.Sprintf("Error applying quota specification: %s", err)) 124 return 1 125 } 126 127 c.Ui.Output(fmt.Sprintf("Successfully applied quota specification %q!", spec.Name)) 128 return 0 129 } 130 131 // parseQuotaSpec is used to parse the quota specification from HCL 132 func parseQuotaSpec(input []byte) (*api.QuotaSpec, error) { 133 root, err := hcl.ParseBytes(input) 134 if err != nil { 135 return nil, err 136 } 137 138 // Top-level item should be a list 139 list, ok := root.Node.(*ast.ObjectList) 140 if !ok { 141 return nil, fmt.Errorf("error parsing: root should be an object") 142 } 143 144 var spec api.QuotaSpec 145 if err := parseQuotaSpecImpl(&spec, list); err != nil { 146 return nil, err 147 } 148 149 return &spec, nil 150 } 151 152 // parseQuotaSpecImpl parses the quota spec taking as input the AST tree 153 func parseQuotaSpecImpl(result *api.QuotaSpec, list *ast.ObjectList) error { 154 // Check for invalid keys 155 valid := []string{ 156 "name", 157 "description", 158 "limit", 159 } 160 if err := helper.CheckHCLKeys(list, valid); err != nil { 161 return err 162 } 163 164 // Decode the full thing into a map[string]interface for ease 165 var m map[string]interface{} 166 if err := hcl.DecodeObject(&m, list); err != nil { 167 return err 168 } 169 170 // Manually parse 171 delete(m, "limit") 172 173 // Decode the rest 174 if err := mapstructure.WeakDecode(m, result); err != nil { 175 return err 176 } 177 178 // Parse limits 179 if o := list.Filter("limit"); len(o.Items) > 0 { 180 if err := parseQuotaLimits(&result.Limits, o); err != nil { 181 return multierror.Prefix(err, "limit ->") 182 } 183 } 184 185 return nil 186 } 187 188 // parseQuotaLimits parses the quota limits 189 func parseQuotaLimits(result *[]*api.QuotaLimit, list *ast.ObjectList) error { 190 for _, o := range list.Elem().Items { 191 // Check for invalid keys 192 valid := []string{ 193 "region", 194 "region_limit", 195 } 196 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 197 return err 198 } 199 200 var m map[string]interface{} 201 if err := hcl.DecodeObject(&m, o.Val); err != nil { 202 return err 203 } 204 205 // Manually parse 206 delete(m, "region_limit") 207 208 // Decode the rest 209 var limit api.QuotaLimit 210 if err := mapstructure.WeakDecode(m, &limit); err != nil { 211 return err 212 } 213 214 // We need this later 215 var listVal *ast.ObjectList 216 if ot, ok := o.Val.(*ast.ObjectType); ok { 217 listVal = ot.List 218 } else { 219 return fmt.Errorf("limit should be an object") 220 } 221 222 // Parse limits 223 if o := listVal.Filter("region_limit"); len(o.Items) > 0 { 224 limit.RegionLimit = new(api.Resources) 225 if err := parseQuotaResource(limit.RegionLimit, o); err != nil { 226 return multierror.Prefix(err, "region_limit ->") 227 } 228 } 229 230 *result = append(*result, &limit) 231 } 232 233 return nil 234 } 235 236 // parseQuotaResource parses the region_limit resources 237 func parseQuotaResource(result *api.Resources, list *ast.ObjectList) error { 238 list = list.Elem() 239 if len(list.Items) == 0 { 240 return nil 241 } 242 if len(list.Items) > 1 { 243 return fmt.Errorf("only one 'region_limit' block allowed per limit") 244 } 245 246 // Get our resource object 247 o := list.Items[0] 248 249 // We need this later 250 var listVal *ast.ObjectList 251 if ot, ok := o.Val.(*ast.ObjectType); ok { 252 listVal = ot.List 253 } else { 254 return fmt.Errorf("resource: should be an object") 255 } 256 257 // Check for invalid keys 258 valid := []string{ 259 "cpu", 260 "memory", 261 } 262 if err := helper.CheckHCLKeys(listVal, valid); err != nil { 263 return multierror.Prefix(err, "resources ->") 264 } 265 266 var m map[string]interface{} 267 if err := hcl.DecodeObject(&m, o.Val); err != nil { 268 return err 269 } 270 271 if err := mapstructure.WeakDecode(m, result); err != nil { 272 return err 273 } 274 275 return nil 276 }