package jobspec import ( "fmt" "os" "strings" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/gohcl" "github.com/hashicorp/hcl/v2/hclsimple" ) type Spec struct { Job JobSpec `hcl:"job,block"` Tasks []TaskSpec `hcl:"task,block"` } type JobSpec struct { Name string `hcl:"name,label"` Type string `hcl:"type,optional"` } type TaskSpec struct { Name string `hcl:"name,label"` Command string `hcl:"command"` Args []string `hcl:"args,optional"` Env []string `hcl:"env,optional"` } func ParseFile(path string) (*Spec, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read spec file: %w", err) } return Parse(data, path) } func Parse(data []byte, filename string) (*Spec, error) { var spec Spec err := hclsimple.Decode(filename, data, nil, &spec) if err != nil { return nil, fmt.Errorf("decode hcl: %w", err) } if spec.Job.Name == "" { return nil, fmt.Errorf("spec missing job name") } if len(spec.Tasks) == 0 { return nil, fmt.Errorf("spec must have at least one task") } for i, t := range spec.Tasks { if t.Command == "" { return nil, fmt.Errorf("task[%d] (%s) missing command", i, t.Name) } } return &spec, nil } func (s *Spec) Validate() error { if strings.TrimSpace(s.Job.Name) == "" { return fmt.Errorf("job name is required") } if len(s.Tasks) == 0 { return fmt.Errorf("at least one task is required") } return nil } var _ = hcl.Diagnostics{} var _ = gohcl.DecodeBody