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

     1  package jobspec2
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/hashicorp/hcl/v2"
    14  	"github.com/hashicorp/hcl/v2/hclsyntax"
    15  	hcljson "github.com/hashicorp/hcl/v2/json"
    16  	"github.com/hashicorp/nomad/api"
    17  )
    18  
    19  func Parse(path string, r io.Reader) (*api.Job, error) {
    20  	if path == "" {
    21  		if f, ok := r.(*os.File); ok {
    22  			path = f.Name()
    23  		}
    24  	}
    25  
    26  	var buf bytes.Buffer
    27  	_, err := io.Copy(&buf, r)
    28  	if err != nil {
    29  		return nil, err
    30  	}
    31  
    32  	return ParseWithConfig(&ParseConfig{
    33  		Path:    path,
    34  		Body:    buf.Bytes(),
    35  		AllowFS: false,
    36  		Strict:  true,
    37  	})
    38  }
    39  
    40  func ParseWithConfig(args *ParseConfig) (*api.Job, error) {
    41  	args.normalize()
    42  
    43  	c := newJobConfig(args)
    44  	err := decode(c)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	normalizeJob(c)
    50  	return c.Job, nil
    51  }
    52  
    53  type ParseConfig struct {
    54  	Path    string
    55  	BaseDir string
    56  
    57  	// Body is the HCL body
    58  	Body []byte
    59  
    60  	// AllowFS enables HCL functions that require file system accecss
    61  	AllowFS bool
    62  
    63  	// ArgVars is the CLI -var arguments
    64  	ArgVars []string
    65  
    66  	// VarFiles is the paths of variable data files
    67  	VarFiles []string
    68  
    69  	// Envs represent process environment variable
    70  	Envs []string
    71  
    72  	Strict bool
    73  
    74  	// parsedVarFiles represent parsed HCL AST of the passed EnvVars
    75  	parsedVarFiles []*hcl.File
    76  }
    77  
    78  func (c *ParseConfig) normalize() {
    79  	if c.BaseDir == "" {
    80  		c.BaseDir = filepath.Dir(c.Path)
    81  	}
    82  }
    83  
    84  func decode(c *jobConfig) error {
    85  	config := c.ParseConfig
    86  
    87  	file, diags := parseHCLOrJSON(config.Body, config.Path)
    88  
    89  	for _, varFile := range config.VarFiles {
    90  		parsedVarFile, ds := parseFile(varFile)
    91  		config.parsedVarFiles = append(config.parsedVarFiles, parsedVarFile)
    92  		diags = append(diags, ds...)
    93  	}
    94  
    95  	diags = append(diags, c.decodeBody(file.Body)...)
    96  
    97  	if diags.HasErrors() {
    98  		var str strings.Builder
    99  		for i, diag := range diags {
   100  			if i != 0 {
   101  				str.WriteByte('\n')
   102  			}
   103  			str.WriteString(diag.Error())
   104  		}
   105  		return errors.New(str.String())
   106  	}
   107  
   108  	diags = append(diags, decodeMapInterfaceType(&c.Job, c.EvalContext())...)
   109  	diags = append(diags, decodeMapInterfaceType(&c.Tasks, c.EvalContext())...)
   110  	diags = append(diags, decodeMapInterfaceType(&c.Vault, c.EvalContext())...)
   111  
   112  	if diags.HasErrors() {
   113  		return diags
   114  	}
   115  
   116  	return nil
   117  }
   118  
   119  func parseFile(path string) (*hcl.File, hcl.Diagnostics) {
   120  	body, err := ioutil.ReadFile(path)
   121  	if err != nil {
   122  		return nil, hcl.Diagnostics{
   123  			&hcl.Diagnostic{
   124  				Severity: hcl.DiagError,
   125  				Summary:  "Failed to read file",
   126  				Detail:   fmt.Sprintf("failed to read %q: %v", path, err),
   127  			},
   128  		}
   129  	}
   130  
   131  	return parseHCLOrJSON(body, path)
   132  }
   133  
   134  func parseHCLOrJSON(src []byte, filename string) (*hcl.File, hcl.Diagnostics) {
   135  	if isJSON(src) {
   136  		return hcljson.Parse(src, filename)
   137  	}
   138  
   139  	return hclsyntax.ParseConfig(src, filename, hcl.Pos{Line: 1, Column: 1})
   140  }
   141  
   142  func isJSON(src []byte) bool {
   143  	for _, c := range src {
   144  		if c == ' ' {
   145  			continue
   146  		}
   147  
   148  		return c == '{'
   149  	}
   150  	return false
   151  }