github.com/djenriquez/nomad-1@v0.8.1/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  }