HCL로 다음 도구 구축

28528 단어 hcltutrigoprogramming
이 게시물에서는 HCL 형식을 사용하여 자신의 도구를 구현하는 방법을 보여주고자 합니다.
HCL 구성 형식은 Terraform , VaultNomad 과 같은 모든 놀라운 HasiCorp 도구에서 사용됩니다.

오늘날 대부분의 최신 애플리케이션과 도구는 일반적으로 읽기 쉬운 형식인 YAML을 사용하지만 공백 감도로 인해 많은 문제를 일으킬 수도 있습니다. 반면에 HCL은 명확한 구조와 변수 보간 및 인라인 함수 호출과 같은 추가 기능으로 읽기 쉬운 형식을 제공합니다.

HCL을 구문 분석하는 정말 간단한 예제부터 시작하겠습니다.

task "first_task" {
    exec "list_current_dir" {
        command = "ls ."
    }

    exec "list_var_dir" {
        command = "ls /var"
    }
}


HCL을 구조체에 매핑하기 위해 구조체 태그를 사용할 수 있습니다.

type Config struct {
    Tasks []*Task `hcl:"task,block"`
}
type Task struct {
    Name  string     `hcl:"name,label"`
    Steps []*ExecStep `hcl:"exec,block"`
}

type ExecStep struct {
    Name    string `hcl:"name,label"`
    Command string `hcl:"command"`
}

func (s *ExecStep) Run() error {
    return nil
}



HCL을 디코딩하기 위해 decode 패키지의 hclsimple 함수를 사용할 수 있습니다.

import (
    "fmt"
    "os"
    "github.com/hashicorp/hcl/v2/hclsimple"
)

var (
    exampleHCL = `
        task "first_task" {
            exec "list_current_dir" {
                command = "ls ."
            }

            exec "list_var_dir" {
                command = "ls /var"
            }
        }
    `
)

func main() {
    config := &Config{}
    err := hclsimple.Decode("example.hcl", []byte(exampleHCL), nil, config)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    for _, task := range config.Tasks {
        fmt.Printf("Task: %s\n", task.Name)
        for _, step := range task.Steps {
            fmt.Printf("    Step: %s %q\n", step.Name, step.Command)
            err = step.Run()
            if err != nil {
                fmt.Println(err)
                os.Exit(1)
            }
        }
    }
}



그것은 쉽다!

그러나 다른 단계 유형을 지원하려면 어떻게 해야 합니까? 쉽게 디렉토리를 생성하기 위해 mkdir를 지원하고 싶다고 가정해 보겠습니다.

task "first_task" {
    mkdir "build_dir" {
        path = "./build"
    }
    exec "list_var_dir" {
        command = "ls /var"
    }
}


도구를 실행하면 다음 오류가 발생합니다.

example.hcl:3,4-9: Unsupported block type; Blocks of type "mkdir" are not expected here.


Task 구조체를 업데이트하고 "mkdir"에 대한 차단 목록을 추가할 수 있습니다.

type Task struct {
    Name      string       `hcl:"name,label"`
    ExecSteps []*ExecStep  `hcl:"exec,block"`
    MkdirStep []*MkdirStep `hcl:"mkdir,block"`
}


그러나 두 개의 별도 목록이 있으므로 실행 순서를 잃게 됩니다. 이것은 우리에게 효과가 없을 것입니다.

대체 솔루션으로 구성을 변경하고 단계 유형을 레이블로 만들 수 있습니다.

task "first_task" {
    step "mkdir" "build_dir" {
        path = "./build/"
    }
    step "exec" "list_build_dir" {
        command = "ls ./build/"
    }
}


구성 변경을 구조체에 반영해 보겠습니다.

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 {
    return nil
}

type MkdirStep struct {
    Path string `hcl:"path"`
}

func (s *MkdirStep) Run() error {
    return nil
}


보시다시피 새로운 Step 구조체를 추가하고 Tasks 대신 ExecStep 구조체에서 사용합니다.Step 구조체에는 Remain 라는 추가 필드가 있습니다. 이 필드는 실제 단계 구현의 모든 필드를 캡처하는 데 필요합니다. 나중에 Remain 필드를 사용하여 필드를 실제 단계 구현으로 디코딩하는 방법을 살펴보겠습니다.

마지막으로 Step 구현을 실행할 수 있는 새 인터페이스를 추가합니다.

type Runner interface {
    Run() error
}


위에서 모든 단계가 Runner 인터페이스를 구현하는 것을 볼 수 있습니다.

이제 구문 분석 코드를 수정해야 합니다.

import (
    "fmt"
    "os"

    "github.com/hashicorp/hcl/v2"
    "github.com/hashicorp/hcl/v2/gohcl"
    "github.com/hashicorp/hcl/v2/hclsimple"
)

var (
    exampleHCL = `
        task "first_task" {
            step "mkdir" "build_dir" {
                path = "./build/"
            }
            step "exec" "list_build_dir" {
                command = "ls ./build/"
            }
        }
    `
)

func main() {
    config := &Config{}
    err := hclsimple.Decode("example.hcl", []byte(exampleHCL), nil, config)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    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)
            // The actual step implementation 
            // which implements the Runner interface
            var runner Runner

            // Determine the step implementation
            switch step.Type {
            case "mkdir":
                runner = &MkdirStep{}
            case "exec":
                runner = &ExecStep{}
            default:
                fmt.Printf("Unknown step type %q\n", step.Type)
                os.Exit(1)
            }

            // Decode remaining fields into our step implementation.
            diags := gohcl.DecodeBody(step.Remain, nil, runner)
            if diags.HasErrors() {
                fmt.Println(diags)
                os.Exit(1)
            }

            err = runner.Run()
            if err != nil {
                fmt.Println(err)
                os.Exit(1)
            }
        }
    }
}



구문 분석은 switch 문에서 단계 유형을 결정하고 Struct의 인스턴스를 만듭니다. 그런 다음 구조체는 나머지 필드를 디코딩하는 gohcl.DecodeBody(step.Remain, nil, runner)의 대상입니다.

Voilà, 우리는 확장하기 쉬운 작업 실행 엔진을 가지고 있습니다.

자원



Godocs:
  • hcl
  • hclsimple
  • gohcl

  • 기타:
  • buildmedia.readthedocs.org/hcl.pdf

  • 출처 요지:
  • Example 1
  • Example 2

  • 전체 소스 코드




    package main
    
    import (
        "fmt"
        "os"
    
        "github.com/hashicorp/hcl/v2"
        "github.com/hashicorp/hcl/v2/gohcl"
        "github.com/hashicorp/hcl/v2/hclsimple"
    )
    
    var (
        exampleHCL = `
            task "first_task" {
                step "mkdir" "build_dir" {
                    path = "./build/"
                }
                step "exec" "list_build_dir" {
                    command = "ls ./build/"
                }
            }
        `
    )
    
    func main() {
        config := &Config{}
        err := hclsimple.Decode("example.hcl", []byte(exampleHCL), nil, config)
        if err != nil {
            fmt.Println(err)
            os.Exit(1)
        }
        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:
                    fmt.Printf("Unknown step type %q\n", step.Type)
                    os.Exit(1)
                }
    
                diags := gohcl.DecodeBody(step.Remain, nil, runner)
                if diags.HasErrors() {
                    fmt.Println(diags)
                    os.Exit(1)
                }
                err = runner.Run()
                if err != nil {
                    fmt.Println(err)
                    os.Exit(1)
                }
            }
        }
    }
    
    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 {
        // Implement me
        return nil
    }
    
    type MkdirStep struct {
        Path string `hcl:"path"`
    }
    
    func (s *MkdirStep) Run() error {
        // Implement me
        return nil
    }
    
    type Runner interface {
        Run() error
    }
    

    좋은 웹페이지 즐겨찾기