github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/jobspec2/types.config.go (about)

     1  package jobspec2
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/hashicorp/hcl/v2/ext/dynblock"
     9  	"github.com/hashicorp/nomad/api"
    10  	"github.com/hashicorp/nomad/jobspec2/hclutil"
    11  	"github.com/zclconf/go-cty/cty"
    12  )
    13  
    14  const (
    15  	variablesLabel = "variables"
    16  	variableLabel  = "variable"
    17  	localsLabel    = "locals"
    18  	vaultLabel     = "vault"
    19  	taskLabel      = "task"
    20  
    21  	inputVariablesAccessor = "var"
    22  	localsAccessor         = "local"
    23  )
    24  
    25  type jobConfig struct {
    26  	JobID string `hcl:",label"`
    27  	Job   *api.Job
    28  
    29  	ParseConfig *ParseConfig
    30  
    31  	Vault *api.Vault  `hcl:"vault,block"`
    32  	Tasks []*api.Task `hcl:"task,block"`
    33  
    34  	InputVariables Variables
    35  	LocalVariables Variables
    36  
    37  	LocalBlocks []*LocalBlock
    38  }
    39  
    40  func newJobConfig(parseConfig *ParseConfig) *jobConfig {
    41  	return &jobConfig{
    42  		ParseConfig: parseConfig,
    43  
    44  		InputVariables: Variables{},
    45  		LocalVariables: Variables{},
    46  	}
    47  }
    48  
    49  var jobConfigSchema = &hcl.BodySchema{
    50  	Blocks: []hcl.BlockHeaderSchema{
    51  		{Type: variablesLabel},
    52  		{Type: variableLabel, LabelNames: []string{"name"}},
    53  		{Type: localsLabel},
    54  		{Type: "job", LabelNames: []string{"name"}},
    55  	},
    56  }
    57  
    58  func (c *jobConfig) decodeBody(body hcl.Body) hcl.Diagnostics {
    59  	content, diags := body.Content(jobConfigSchema)
    60  	if len(diags) != 0 {
    61  		return diags
    62  	}
    63  
    64  	diags = append(diags, c.decodeInputVariables(content)...)
    65  	diags = append(diags, c.parseLocalVariables(content)...)
    66  	diags = append(diags, c.collectInputVariableValues(c.ParseConfig.Envs, c.ParseConfig.parsedVarFiles, toVars(c.ParseConfig.ArgVars))...)
    67  
    68  	_, moreDiags := c.InputVariables.Values()
    69  	diags = append(diags, moreDiags...)
    70  	_, moreDiags = c.LocalVariables.Values()
    71  	diags = append(diags, moreDiags...)
    72  	diags = append(diags, c.evaluateLocalVariables(c.LocalBlocks)...)
    73  
    74  	// Errors at this point are likely syntax errors which can result in
    75  	// invalid state when we try to decode the rest of the job. If we continue
    76  	// we may panic and that will obscure the error, so return early so the
    77  	// user can be told how to fix their jobspec.
    78  	if diags.HasErrors() {
    79  		return diags
    80  	}
    81  	nctx := c.EvalContext()
    82  
    83  	diags = append(diags, c.decodeJob(content, nctx)...)
    84  	return diags
    85  }
    86  
    87  // decodeInputVariables looks in the found blocks for 'variables' and
    88  // 'variable' blocks. It should be called firsthand so that other blocks can
    89  // use the variables.
    90  func (c *jobConfig) decodeInputVariables(content *hcl.BodyContent) hcl.Diagnostics {
    91  	var diags hcl.Diagnostics
    92  
    93  	for _, block := range content.Blocks {
    94  		switch block.Type {
    95  		case variableLabel:
    96  			moreDiags := c.InputVariables.decodeVariableBlock(block, nil)
    97  			diags = append(diags, moreDiags...)
    98  		case variablesLabel:
    99  			attrs, moreDiags := block.Body.JustAttributes()
   100  			diags = append(diags, moreDiags...)
   101  			for key, attr := range attrs {
   102  				moreDiags = c.InputVariables.decodeVariable(key, attr, nil)
   103  				diags = append(diags, moreDiags...)
   104  			}
   105  		}
   106  	}
   107  	return diags
   108  }
   109  
   110  // parseLocalVariables looks in the found blocks for 'locals' blocks. It
   111  // should be called after parsing input variables so that they can be
   112  // referenced.
   113  func (c *jobConfig) parseLocalVariables(content *hcl.BodyContent) hcl.Diagnostics {
   114  	var diags hcl.Diagnostics
   115  
   116  	for _, block := range content.Blocks {
   117  		switch block.Type {
   118  		case localsLabel:
   119  			attrs, moreDiags := block.Body.JustAttributes()
   120  			diags = append(diags, moreDiags...)
   121  			for name, attr := range attrs {
   122  				if _, found := c.LocalVariables[name]; found {
   123  					diags = append(diags, &hcl.Diagnostic{
   124  						Severity: hcl.DiagError,
   125  						Summary:  "Duplicate value in " + localsLabel,
   126  						Detail:   "Duplicate " + name + " definition found.",
   127  						Subject:  attr.NameRange.Ptr(),
   128  						Context:  block.DefRange.Ptr(),
   129  					})
   130  					return diags
   131  				}
   132  				c.LocalBlocks = append(c.LocalBlocks, &LocalBlock{
   133  					Name: name,
   134  					Expr: attr.Expr,
   135  				})
   136  			}
   137  		}
   138  	}
   139  
   140  	return diags
   141  }
   142  
   143  func (c *jobConfig) decodeTopLevelExtras(content *hcl.BodyContent, ctx *hcl.EvalContext) hcl.Diagnostics {
   144  	var diags hcl.Diagnostics
   145  
   146  	var foundVault *hcl.Block
   147  	for _, b := range content.Blocks {
   148  		if b.Type == vaultLabel {
   149  			if foundVault != nil {
   150  				diags = append(diags, &hcl.Diagnostic{
   151  					Severity: hcl.DiagError,
   152  					Summary:  fmt.Sprintf("Duplicate %s block", b.Type),
   153  					Detail: fmt.Sprintf(
   154  						"Only one block of type %q is allowed. Previous definition was at %s.",
   155  						b.Type, foundVault.DefRange.String(),
   156  					),
   157  					Subject: &b.DefRange,
   158  				})
   159  				continue
   160  			}
   161  			foundVault = b
   162  
   163  			v := &api.Vault{}
   164  			diags = append(diags, hclDecoder.DecodeBody(b.Body, ctx, v)...)
   165  			c.Vault = v
   166  
   167  		} else if b.Type == taskLabel {
   168  			t := &api.Task{}
   169  			diags = append(diags, hclDecoder.DecodeBody(b.Body, ctx, t)...)
   170  			if len(b.Labels) == 1 {
   171  				t.Name = b.Labels[0]
   172  				c.Tasks = append(c.Tasks, t)
   173  			}
   174  		}
   175  	}
   176  
   177  	return diags
   178  }
   179  
   180  func (c *jobConfig) evaluateLocalVariables(locals []*LocalBlock) hcl.Diagnostics {
   181  	var diags hcl.Diagnostics
   182  
   183  	if len(locals) > 0 && c.LocalVariables == nil {
   184  		c.LocalVariables = Variables{}
   185  	}
   186  
   187  	var retry, previousL int
   188  	for len(locals) > 0 {
   189  		local := locals[0]
   190  		moreDiags := c.evaluateLocalVariable(local)
   191  		if moreDiags.HasErrors() {
   192  			if len(locals) == 1 {
   193  				// If this is the only local left there's no need
   194  				// to try evaluating again
   195  				return append(diags, moreDiags...)
   196  			}
   197  			if previousL == len(locals) {
   198  				if retry == 100 {
   199  					// To get to this point, locals must have a circle dependency
   200  					return append(diags, moreDiags...)
   201  				}
   202  				retry++
   203  			}
   204  			previousL = len(locals)
   205  
   206  			// If local uses another local that has not been evaluated yet this could be the reason of errors
   207  			// Push local to the end of slice to be evaluated later
   208  			locals = append(locals, local)
   209  		} else {
   210  			retry = 0
   211  			diags = append(diags, moreDiags...)
   212  		}
   213  		// Remove local from slice
   214  		locals = append(locals[:0], locals[1:]...)
   215  	}
   216  
   217  	return diags
   218  }
   219  
   220  func (c *jobConfig) evaluateLocalVariable(local *LocalBlock) hcl.Diagnostics {
   221  	var diags hcl.Diagnostics
   222  
   223  	value, moreDiags := local.Expr.Value(c.EvalContext())
   224  	diags = append(diags, moreDiags...)
   225  	if moreDiags.HasErrors() {
   226  		return diags
   227  	}
   228  	c.LocalVariables[local.Name] = &Variable{
   229  		Name: local.Name,
   230  		Values: []VariableAssignment{{
   231  			Value: value,
   232  			Expr:  local.Expr,
   233  			From:  "default",
   234  		}},
   235  		Type: value.Type(),
   236  	}
   237  
   238  	return diags
   239  }
   240  
   241  func (c *jobConfig) decodeJob(content *hcl.BodyContent, ctx *hcl.EvalContext) hcl.Diagnostics {
   242  	var diags hcl.Diagnostics
   243  
   244  	c.Job = &api.Job{}
   245  
   246  	var found *hcl.Block
   247  	for _, b := range content.Blocks {
   248  		if b.Type != "job" {
   249  			continue
   250  		}
   251  
   252  		body := hclutil.BlocksAsAttrs(b.Body)
   253  		body = dynblock.Expand(body, ctx)
   254  
   255  		if found != nil {
   256  			diags = append(diags, &hcl.Diagnostic{
   257  				Severity: hcl.DiagError,
   258  				Summary:  fmt.Sprintf("Duplicate %s block", b.Type),
   259  				Detail: fmt.Sprintf(
   260  					"Only one block of type %q is allowed. Previous definition was at %s.",
   261  					b.Type, found.DefRange.String(),
   262  				),
   263  				Subject: &b.DefRange,
   264  			})
   265  			continue
   266  		}
   267  		found = b
   268  
   269  		c.JobID = b.Labels[0]
   270  
   271  		metaAttr, body, mdiags := decodeAsAttribute(body, ctx, "meta")
   272  		diags = append(diags, mdiags...)
   273  
   274  		extra, remain, mdiags := body.PartialContent(&hcl.BodySchema{
   275  			Blocks: []hcl.BlockHeaderSchema{
   276  				{Type: "vault"},
   277  				{Type: "task", LabelNames: []string{"name"}},
   278  			},
   279  		})
   280  
   281  		diags = append(diags, mdiags...)
   282  		diags = append(diags, c.decodeTopLevelExtras(extra, ctx)...)
   283  		diags = append(diags, hclDecoder.DecodeBody(remain, ctx, c.Job)...)
   284  
   285  		if metaAttr != nil {
   286  			c.Job.Meta = metaAttr
   287  		}
   288  	}
   289  
   290  	if found == nil {
   291  		diags = append(diags, &hcl.Diagnostic{
   292  			Severity: hcl.DiagError,
   293  			Summary:  "Missing job block",
   294  			Detail:   "A job block is required",
   295  		})
   296  	}
   297  
   298  	return diags
   299  
   300  }
   301  
   302  func (c *jobConfig) EvalContext() *hcl.EvalContext {
   303  	vars, _ := c.InputVariables.Values()
   304  	locals, _ := c.LocalVariables.Values()
   305  	return &hcl.EvalContext{
   306  		Functions: Functions(c.ParseConfig.BaseDir, c.ParseConfig.AllowFS),
   307  		Variables: map[string]cty.Value{
   308  			inputVariablesAccessor: cty.ObjectVal(vars),
   309  			localsAccessor:         cty.ObjectVal(locals),
   310  		},
   311  		UnknownVariable: func(expr string) (cty.Value, error) {
   312  			v := "${" + expr + "}"
   313  			return cty.StringVal(v), nil
   314  		},
   315  	}
   316  }
   317  
   318  func toVars(vars []string) map[string]string {
   319  	attrs := make(map[string]string, len(vars))
   320  	for _, arg := range vars {
   321  		parts := strings.SplitN(arg, "=", 2)
   322  		if len(parts) == 2 {
   323  			attrs[parts[0]] = parts[1]
   324  		}
   325  	}
   326  
   327  	return attrs
   328  }