1. 程式人生 > 實用技巧 >hcl v2 golang 使用的一個參考demo

hcl v2 golang 使用的一個參考demo

程式碼內容來自hashcorp 公司的一個分享,此demo 裡邊的一些實踐很不錯,很值得參考(實際上consul,vault,packer,terroform。。。都是值得參考的)

程式碼結構

├── README.md
├── go.mod
├── go.sum
├── ink.jpg
├── main.go
├── pet.go
├── pet_test.go
├── pets.hcl
└── testdata
  ├── basic.hcl
  ├── function.hcl
  └── variables.hcl

一個參考配置

pet "Ink" {
 type = "cat"
 characteristics {
  sound = "${env.CAT_SOUND}s ${random("evilly", "lazily", "sleepily", "gracefully")}"
  }
}
pet "Swinney" {
 type = "dog"
 characteristics {
  breed = "Dachshund"
  }
}

pet.go 核心處理程式碼

簡單說明,pet 對於hcl 檔案的處理,不是全家桶模式的,而是進行了拆分,基於hcl 實現了一個類似面向物件的處理

pet 氛圍cat 以及dog,所以定義了一個interface pet cat 以及dog 實現了say 以及act 方法,因為每個型別會有自己的
欄位,所以每個欄位有自己的hcl golang tag 處理,demo 中沒有像我們平時直接定義一個通用struct 的模式,而且定義
了一個通用的對於不同的地方直接使用hcl 的remain,然後各自的type 處理自己的解析,好處是簡化了型別的判斷到一個
複雜而且大的struct 中
hcl 使用內部函式提供解析,參考

// 建立解析器
parser := hclparse.NewParser()
// 基於檔案內容,解析hcl content
srcHCL, diag := parser.ParseHCL(src, filename)
// 解析部分定義的hcl 內容,可以靈活擴充套件
gohcl.DecodeBody(srcHCL.Body, evalContext, petsHCL)

pet hcl dsl 定義
拆分為了PetHCLBodies 以及CharacteristicsHCL 好處是基於業務模型拆分,每部分擁有自己的實現

type PetsHCL struct {
  PetHCLBodies []*struct {
    Name        string `hcl:",label"`
    Type        string `hcl:"type"`
    CharacteristicsHCL *struct {
      HCL hcl.Body `hcl:",remain"`
     } `hcl:"characteristics,block"`
   } `hcl:"pet,block"`
}

核心讀取部分
實現了env 以及function 的功能

// ReadConfig decodes the HCL file at filename into a slice of Pets and returns
// it.
func ReadConfig(filename string) ([]Pet, error) {
  // First, open a file handle to the input filename.
  input, err := os.Open(filename)
  if err != nil {
    return []Pet{}, fmt.Errorf(
      "error in ReadConfig openin pet config file: %w", err,
     )
   }
  defer input.Close()
  // Next, read that file into a byte slice for use as a buffer. Because HCL
  // decoding must happen in the context of a whole file, it does not take an
  // io.Reader as an input, instead relying on byte slices.
  src, err := ioutil.ReadAll(input)
  if err != nil {
    return []Pet{}, fmt.Errorf(
      "error in ReadConfig reading input `%s`: %w", filename, err,
     )
   }
  // Instantiate an HCL parser with the source byte slice.
  parser := hclparse.NewParser()
  srcHCL, diag := parser.ParseHCL(src, filename)
  if diag.HasErrors() {
    return []Pet{}, fmt.Errorf(
      "error in ReadConfig parsing HCL: %w", diag,
     )
   }
  // Call a helper function which creates an HCL context for use in
  // decoding the parsed HCL.
  evalContext, err := createContext()
  if err != nil {
    return []Pet{}, fmt.Errorf(
      "error in ReadConfig creating HCL evaluation context: %w", err,
     )
   }
  // Start the first pass of decoding. This decodes all pet blocks into
  // a generic form, with a Type field for use in determining whether they
  // are cats or dogs. The configuration in the characteristics will be left
  // undecoded in an hcl.Body. This Body will be decoded into different pet
  // types later, once the context of the Type is known.
  petsHCL := &PetsHCL{}
  if diag := gohcl.DecodeBody(srcHCL.Body, evalContext, petsHCL); diag.HasErrors() {
    return []Pet{}, fmt.Errorf(
      "error in ReadConfig decoding HCL configuration: %w", diag,
     )
   }
  // Iterate through the generic pets, switch on type, then decode the
  // hcl.Body into the correct pet type. This allows "polymorphism" in the
  // pet blocks.
  pets := []Pet{}
  for _, p := range petsHCL.PetHCLBodies {
    switch petType := p.Type; petType {
    case "cat":
      cat := &Cat{Name: p.Name, Sound: defaultCatSound}
      if p.CharacteristicsHCL != nil {
        if diag := gohcl.DecodeBody(p.CharacteristicsHCL.HCL, evalContext, cat); diag.HasErrors() {
          return []Pet{}, fmt.Errorf(
            "error in ReadConfig decoding cat HCL configuration: %w", diag,
           )
         }
       }
      pets = append(pets, cat)
    case "dog":
      dog := &Dog{Name: p.Name, Breed: defaultDogBreed}
      if p.CharacteristicsHCL != nil {
        if diag := gohcl.DecodeBody(p.CharacteristicsHCL.HCL, evalContext, dog); diag.HasErrors() {
          return []Pet{}, fmt.Errorf(
            "error in ReadConfig decoding dog HCL configuration: %w", diag,
           )
         }
       }
      pets = append(pets, dog)
    default:
      // Error in the case of an unknown type. In the future, more types
      // could be added to the switch to support, for example, fish
      // owners.
      return []Pet{}, fmt.Errorf("error in ReadConfig: unknown pet type `%s`", petType)
     }
   }
  return pets, nil
}
// createContext is a helper function that creates an *hcl.EvalContext to be
// used in decoding HCL. It creates a set of variables at env.KEY
// (namely, CAT_SOUND). It also creates a function "random(...string)" that can
// be used to assign a random value in an HCL config.
func createContext() (*hcl.EvalContext, error) {
  // Extract the sound cats make from the environment, with a default.
  catSound := defaultCatSound
  if os.Getenv(catSoundKey) != "" {
    catSound = os.Getenv(catSoundKey)
   }
  // variables is a list of cty.Value for use in Decoding HCL. These will
  // be nested by using ObjectVal as a value. For istance:
  //  env.CAT_SOUND => "meow"
  variables := map[string]cty.Value{
    environmentKey: cty.ObjectVal(map[string]cty.Value{
      catSoundKey: cty.StringVal(catSound),
     }),
   }
  // functions is a list of cty.Functions for use in Decoding HCL.
  functions := map[string]function.Function{
    "random": function.New(&function.Spec{
      // Params represents required positional arguments, of which random
      // has none.
      Params: []function.Parameter{},
      // VarParam allows a "VarArgs" type input, in this case, of
      // strings.
      VarParam: &function.Parameter{Type: cty.String},
      // Type is used to determine the output type from the inputs. In
      // the case of Random it only accepts strings and only returns
      // strings.
      Type: function.StaticReturnType(cty.String),
      // Impl is the actual function. A "VarArgs" number of cty.String
      // will be passed in and a random one returned, also as a
      // cty.String.
      Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
        resp := args[rand.Intn(len(args))]
        return cty.StringVal(resp.AsString()), nil
       },
     }),
   }
  // Return the constructed hcl.EvalContext.
  return &hcl.EvalContext{
    Variables: variables,
    Functions: functions,
   }, nil
}

說明

以上是官方hcl v2 golang 的一個demo 學習還是很值得看看的

參考資料

https://github.com/hashicorp/hcl/tree/hcl2
https://github.com/RussellRollins/pet-sounds