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 }