Skip to content

Instantly share code, notes, and snippets.

@weakpixel
Created April 10, 2022 13:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save weakpixel/40147127fdcac0e11f74967a9ef5aaad to your computer and use it in GitHub Desktop.
Save weakpixel/40147127fdcac0e11f74967a9ef5aaad to your computer and use it in GitHub Desktop.
package main
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclsimple"
"github.com/zclconf/go-cty/cty/function"
)
var (
exampleHCL = `
task "first_task" {
step "mkdir" "build_dir" {
path = upper(var.buildDir)
}
step "exec" "list_build_dir" {
command = "ls ${ upper(var.buildDir) }"
}
}
`
)
func main() {
root := cobra.Command{
Use: "taskexec",
}
root.AddCommand(newRunCommand())
err := root.Execute()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func newRunCommand() *cobra.Command {
vars := []string{}
cmd := cobra.Command{
Use: "run",
Short: "Executes tasks",
RunE: func(cmd *cobra.Command, args []string) error {
ctx, err := newEvalContext(vars)
if err != nil {
return err
}
config := &Config{}
err = hclsimple.Decode("example.hcl", []byte(exampleHCL), ctx, config)
if err != nil {
return err
}
for _, task := range config.Tasks {
fmt.Printf("Task: %s\n", task.Name)
for _, step := range task.Steps {
fmt.Printf(" Step: %s %s\n", step.Type, step.Name)
var runner Runner
switch step.Type {
case "mkdir":
runner = &MkdirStep{}
case "exec":
runner = &ExecStep{}
default:
return fmt.Errorf("unknown step type %q", step.Type)
}
diags := gohcl.DecodeBody(step.Remain, ctx, runner)
if diags.HasErrors() {
return diags
}
err = runner.Run()
if err != nil {
return err
}
}
}
return nil
},
}
cmd.Flags().StringArrayVar(&vars, "var", nil, "Sets variable. Format <name>=<value>")
return &cmd
}
func newEvalContext(vars []string) (*hcl.EvalContext, error) {
varMap := map[string]cty.Value{}
for _, v := range vars {
el := strings.Split(v, "=")
if len(el) != 2 {
return nil, fmt.Errorf("invalid format: %s", v)
}
varMap[el[0]] = cty.StringVal(el[1])
}
ctx := &hcl.EvalContext{}
ctx.Variables = map[string]cty.Value{
"var": cty.ObjectVal(varMap),
}
ctx.Functions = map[string]function.Function{
"upper": upperFn,
}
return ctx, nil
}
var upperFn = function.New(&function.Spec{
// Define the required parameters.
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
AllowDynamicType: true,
},
},
// Define the return type
Type: function.StaticReturnType(cty.String),
// Function implementation:
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
in := args[0].AsString()
out := strings.ToUpper(in)
return cty.StringVal(out), nil
},
})
type Config struct {
Tasks []*Task `hcl:"task,block"`
}
type Task struct {
Name string `hcl:"name,label"`
Steps []*Step `hcl:"step,block"`
}
type Step struct {
Type string `hcl:"type,label"`
Name string `hcl:"name,label"`
Remain hcl.Body `hcl:",remain"`
}
type ExecStep struct {
Command string `hcl:"command"`
}
func (s *ExecStep) Run() error {
fmt.Println("\tCommand: " + s.Command)
return nil
}
type MkdirStep struct {
Path string `hcl:"path"`
}
func (s *MkdirStep) Run() error {
fmt.Println("\tPath:" + s.Path)
return nil
}
type Runner interface {
Run() error
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment