hclconfig

package module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 7, 2022 License: MIT Imports: 21 Imported by: 2

README

hclconfig

Documentation Latest GitHub tag Github Actions test Go Report Card License

go utility package for loading HCL(HashiCorp configuration language) files


Features

  • Local Variables
  • Implicit variables
  • Built-in functions using standard libraries
  • Interfaces to implement additional restrictions

Requirements

  • Go 1.18 or higher. support the 3 latest versions of Go.

See godoc.org/github.com/mashiike/hclconfig.


Installation

$ go get -u github.com/mashiike/hclconfig

Usage

config/config.hcl

io_mode = "readonly"

service "http" "hoge" {
  addr = "http://127.0.0.1"
  port = 8080
}
package main

import (
	"github.com/mashiike/hclconfig"
)

type Config struct {
	IOMode   string          `hcl:"io_mode"`
	Services []ServiceConfig `hcl:"service,block"`
}

type ServiceConfig struct {
	Type string `hcl:"type,label"`
	Name string `hcl:"name,label"`
	Addr string `hcl:"addr"`
	Port int    `hcl:"port"`
}

func main() {
	var cfg Config
	if err := hclconfig.Load(&cfg, "./config"); err != nil {
		panic(err)
	}
	//...
}
Local Variables

For example, the following statements are possible

config/config.hcl

locals {
    addr = "http://127.0.0.1"   
}
io_mode = "readonly"

service "http" "hoge" {
  addr = local.addr 
  port = 8080
}

service "http" "fuga" {
  addr = local.addr 
  port = 8081
}
Implicit variables

For example, the following statements are possible

config/config.hcl

io_mode = "readonly"

service "http" "hoge" {
  addr = "http://127.0.0.1"
  port = 8080
}

service "http" "fuga" {
  addr = "http://127.0.0.1"
  port = service.http.hoge.port + 1
}

This is the ability to refer to other blocks and attributes as implicit variables.
The evaluation of this implicit variable is done recursively. Up to 100 nests can be evaluated.

Built-in functions

You can use the same functions that you use most often.

For example, you can use the must_env or env functions to read environment variables. To read from a file, you can use the file function.

env   = must_env("ENV")
shell = env("SHELL", "bash")
text  = file("memo.txt")

You can also use other functions from "github.com/zclconf/go-cty/cty/function/stdlib" such as jsonencode and join.

Additional restrictions

If the following interfaces are met, functions can be called after decoding to implement additional restrictions.

type Restrictor interface {
	Restrict(bodyContent *hcl.BodyContent, ctx *hcl.EvalContext) hcl.Diagnostics
}

example code

package main

import (
	"github.com/hashicorp/hcl/v2"
	"github.com/mashiike/hclconfig"
)

type Config struct {
	IOMode   string          `hcl:"io_mode"`
	Services []ServiceConfig `hcl:"service,block"`
}

type ServiceConfig struct {
	Type string `hcl:"type,label"`
	Name string `hcl:"name,label"`
	Addr string `hcl:"addr"`
	Port int    `hcl:"port"`
}

func (cfg *Config) Restrict(content *hcl.BodyContent, ctx *hcl.EvalContext) hcl.Diagnostics {
	var diags hcl.Diagnostics
	if cfg.IOMode != "readwrite" && cfg.IOMode != "readonly" {
		diags = append(diags, hclconfig.NewDiagnosticError(
			"Invalid io_mode",
			"Possible values for io_mode are readwrite or readonly",
			hclconfig.AttributeRange(content, "io_mode"),
		))
	}
	diags = append(diags, hclconfig.RestrictUniqueBlockLabels(content)...)
	return diags
}

func main() {
	var cfg Config
	if err := hclconfig.Load(&cfg, "./config"); err != nil {
		panic(err)
	}
	//...
}

In this case, if anything other than readonly and readwrite is entered in the IOMode, the following message is output.

io_mode = "public"

service "http" "hoge" {
  addr = "http://127.0.0.1"
  port = 8080
}

service "http" "hoge" {
  addr = "http://127.0.0.1"
  port = 8080
}
Error: Invalid io_mode

  on config/config.hcl line 1:
   1: io_mode = "public"

Possible values for io_mode are readwrite or readonly

Error: Duplicate service "http" configuration

  on config/config.hcl line 8, in service "http" "hoge":
   8: service "http" "hoge" {

A http service named "hoge" was already declared at config/config.hcl:3,1-22. service names must unique per type in a configuration
Custom Decode

If the given Config satisfies the following interfaces, call the customized decoding process after calculating Local Variables and Implicit Variables

type BodyDecoder interface {
	DecodeBody(hcl.Body, *hcl.EvalContext) hcl.Diagnostics
}

In this case, it should be noted that Restrictor does not work because all DecodeBody is replaced. When using the BodyDecocder interface, additional restrictions, etc., should also be implemented in the DecodeBody function.

LICENSE

MIT

Documentation

Overview

Example
package main

import (
	"fmt"
	"os"

	"github.com/mashiike/hclconfig"
)

func main() {
	os.Setenv("HCLCONFIG_VAR", "hoge")
	type ExampleConfig struct {
		Value  string `hcl:"value"`
		Groups []*struct {
			Type  string `hcl:"type,label"`
			Name  string `hcl:"name,label"`
			Value int    `hcl:"value"`
		} `hcl:"group,block"`
	}
	var cfg ExampleConfig
	err := hclconfig.LoadWithBytes(&cfg, "config.hcl", []byte(`
value = must_env("HCLCONFIG_VAR")

group "type1" "default" {
	value = 1
}

group "type2" "default" {
	value = group.type1.default.value + 1
}
	`))
	if err != nil {
		fmt.Println(err)
	}

	fmt.Println("value = ", cfg.Value)
	for _, g := range cfg.Groups {
		fmt.Printf("group.%s.%s.value = %v\n", g.Type, g.Name, g.Value)
	}
}
Output:

value =  hoge
group.type1.default.value = 1
group.type2.default.value = 2

Index

Examples

Constants

This section is empty.

Variables

View Source
var DurationFunc = function.New(&function.Spec{
	Params: []function.Parameter{
		{
			Name:        "d",
			Type:        cty.String,
			AllowMarked: true,
		},
	},
	Type: function.StaticReturnType(cty.Number),
	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
		durationArg, durationMarks := args[0].Unmark()
		durationStr := durationArg.AsString()
		d, err := time.ParseDuration(durationStr)
		if err != nil {
			return cty.UnknownVal(cty.Number), err
		}
		return cty.NumberFloatVal(float64(d) / float64(time.Second)).WithMarks(durationMarks), nil
	},
})
View Source
var EnvFunc = function.New(&function.Spec{
	Params: []function.Parameter{
		{
			Name:        "key",
			Type:        cty.String,
			AllowMarked: true,
		},
		{
			Name:      "default",
			Type:      cty.String,
			AllowNull: true,
		},
	},
	Type: function.StaticReturnType(cty.String),
	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
		keyArg, keyMarks := args[0].Unmark()
		key := keyArg.AsString()
		if value := os.Getenv(key); value != "" {
			return cty.StringVal(value).WithMarks(keyMarks), nil
		}
		if args[1].IsNull() {
			return cty.StringVal("").WithMarks(keyMarks), nil
		}
		return cty.StringVal(args[1].AsString()).WithMarks(keyMarks), nil
	},
})
View Source
var MustEnvFunc = function.New(&function.Spec{
	Params: []function.Parameter{
		{
			Name:        "key",
			Type:        cty.String,
			AllowMarked: true,
		},
	},
	Type: function.StaticReturnType(cty.String),
	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
		keyArg, keyMarks := args[0].Unmark()
		key := keyArg.AsString()
		value := os.Getenv(key)
		if value == "" {
			err := function.NewArgError(0, fmt.Errorf("env `%s` is not set", key))
			return cty.UnknownVal(cty.String), err
		}
		return cty.StringVal(value).WithMarks(keyMarks), nil
	},
})
View Source
var NowFunc = function.New(&function.Spec{
	Params: []function.Parameter{},
	Type:   function.StaticReturnType(cty.Number),
	Impl: func(_ []cty.Value, retType cty.Type) (cty.Value, error) {
		return cty.NumberFloatVal(nowUnixSeconds()), nil
	},
})
View Source
var StrftimeFunc = function.New(&function.Spec{
	Params: []function.Parameter{
		{
			Name:        "layout",
			Type:        cty.String,
			AllowMarked: true,
		},
		{
			Name:        "unixSeconds",
			Type:        cty.Number,
			AllowMarked: true,
			AllowNull:   true,
		},
	},
	Type: function.StaticReturnType(cty.String),
	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
		layoutArg, layoutMarks := args[0].Unmark()
		layout := layoutArg.AsString()

		unixSecondsArg, unixSeconcsMarks := args[1].Unmark()
		var unixSeconds float64
		if unixSecondsArg.IsNull() {
			unixSeconds = nowUnixSeconds()
		} else {
			f := unixSecondsArg.AsBigFloat()
			unixSeconds, _ = f.Float64()
		}

		t, err := Strftime(layout, time.Local, unixSecondsToTime(unixSeconds))
		if err != nil {
			return cty.UnknownVal(cty.String), err
		}
		return cty.StringVal(t).WithMarks(layoutMarks, unixSeconcsMarks), nil
	},
})
View Source
var StrftimeInZoneFunc = function.New(&function.Spec{
	Params: []function.Parameter{
		{
			Name:        "layout",
			Type:        cty.String,
			AllowMarked: true,
		},
		{
			Name:        "timeZone",
			Type:        cty.String,
			AllowMarked: true,
		},
		{
			Name:        "unixSeconds",
			Type:        cty.Number,
			AllowMarked: true,
			AllowNull:   true,
		},
	},
	Type: function.StaticReturnType(cty.String),
	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
		layoutArg, layoutMarks := args[0].Unmark()
		layout := layoutArg.AsString()

		zoneArg, zoneMarks := args[1].Unmark()
		zone := zoneArg.AsString()

		unixSecondsArg, unixSeconcsMarks := args[2].Unmark()
		var unixSeconds float64
		if unixSecondsArg.IsNull() {
			unixSeconds = nowUnixSeconds()
		} else {
			f := unixSecondsArg.AsBigFloat()
			unixSeconds, _ = f.Float64()
		}

		t, err := StrftimeInZone(layout, zone, unixSecondsToTime(unixSeconds))
		if err != nil {
			return cty.UnknownVal(cty.String), err
		}
		return cty.StringVal(t).WithMarks(layoutMarks, zoneMarks, unixSeconcsMarks), nil
	},
})

Functions

func AttributeRange

func AttributeRange(content *hcl.BodyContent, tagName string) *hcl.Range

func DecodeBody added in v0.1.0

func DecodeBody(body hcl.Body, ctx *hcl.EvalContext, cfg interface{}) hcl.Diagnostics

func DecodeExpression added in v0.1.0

func DecodeExpression(expr hcl.Expression, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics

DecodeExpression is an extension of gohcl.DecodeExpression, which supports Decode to interface{}, etc. when the ExpressionDecoder interface is satisfied.

func DefaultDiagnosticOutput

func DefaultDiagnosticOutput(w io.Writer)

DefaultDiagnosticOutput specifies the standard diagnostic output destination. If a separate DiagnosticWriter is specified, that setting takes precedence.

func DiagnosticWriter

func DiagnosticWriter(w hcl.DiagnosticWriter)

DiagnosticWriter sets up a Writer to write the diagnostic when an error occurs in the Loader.

func Functions

func Functions(functions map[string]function.Function)

Functions adds functions used during HCL decoding.

func Load

func Load(cfg interface{}, paths ...string) error

Load considers `paths` as a configuration file county written in HCL and reads *.hcl and *.hcl.json. and assigns the decoded values to the `cfg` values.

func LoadWithBody

func LoadWithBody(cfg interface{}, ctx *hcl.EvalContext, body hcl.Body) hcl.Diagnostics

LoadWithBody assigns a value to `val` using a parsed hcl.Body and hcl.EvalContext. mainly used to achieve partial loading when implementing Restrict functions.

func LoadWithBytes

func LoadWithBytes(cfg interface{}, filename string, src []byte) error

func MakeFileFunc added in v0.3.0

func MakeFileFunc(basePaths ...string) function.Function

func MakeTemplateFileFunc added in v0.4.0

func MakeTemplateFileFunc(newEvalContext func() *hcl.EvalContext, basePaths ...string) function.Function

func NewDiagnosticError

func NewDiagnosticError(summary string, detail string, r *hcl.Range) *hcl.Diagnostic

NewDiagnosticError generates a new diagnostic error.

func NewDiagnosticWarn

func NewDiagnosticWarn(summary string, detail string, r *hcl.Range) *hcl.Diagnostic

NewDiagnosticError generates a new diagnostic warn.

func NewEvalContext

func NewEvalContext(paths ...string) *hcl.EvalContext

NewEvalContext creates a new evaluation context.

func RestrictOnlyOneBlock added in v0.2.0

func RestrictOnlyOneBlock(content *hcl.BodyContent, blockTypes ...string) hcl.Diagnostics

RestrictOnlyOneBlock implements the restriction that unique block per block type

func RestrictRequiredBlock added in v0.6.0

func RestrictRequiredBlock(content *hcl.BodyContent, blockTypes ...string) hcl.Diagnostics

RestrictRequiredBlock implements the restriction that required block per block type

func RestrictUniqueBlockLabels

func RestrictUniqueBlockLabels(content *hcl.BodyContent, blockTypes ...string) hcl.Diagnostics

RestrictUniqueBlockLabels implements the restriction that labels for each Block be unique.

func Strftime added in v0.5.0

func Strftime(layout string, loc *time.Location, t time.Time) (string, error)

func StrftimeInZone added in v0.5.0

func StrftimeInZone(layout string, zone string, t time.Time) (string, error)

func Variables

func Variables(variables map[string]cty.Value)

Variables adds variables used during HCL decoding.

Types

type BodyDecoder added in v0.1.0

type BodyDecoder interface {
	DecodeBody(hcl.Body, *hcl.EvalContext) hcl.Diagnostics
}

BodyDecoder is an interface for custom decoding methods. If the load target does not satisfy this interface, gohcl.DecodeBody is used, but if it does, the functions of this interface are used.

type DiagnosticWriterFunc

type DiagnosticWriterFunc func(diag *hcl.Diagnostic) error

DiagnosticWriterFunc is an alias for a function that specifies how to output diagnostics.

func (DiagnosticWriterFunc) WriteDiagnostic

func (f DiagnosticWriterFunc) WriteDiagnostic(diag *hcl.Diagnostic) error

func (DiagnosticWriterFunc) WriteDiagnostics

func (f DiagnosticWriterFunc) WriteDiagnostics(diags hcl.Diagnostics) error

type ExpressionDecoder added in v0.1.0

type ExpressionDecoder interface {
	DecodeExpression(expr hcl.Expression, ctx *hcl.EvalContext) hcl.Diagnostics
}

ExpressionDecoder is used for special decoding. Note that it is not used with gohcl.DecodeExpression.

type Loader

type Loader struct {
	// contains filtered or unexported fields
}

Loader represents config loader.

func New

func New() *Loader

New creates a Loader instance.

func (*Loader) DefaultDiagnosticOutput

func (l *Loader) DefaultDiagnosticOutput(w io.Writer)

DefaultDiagnosticOutput specifies the standard diagnostic output destination. If a separate DiagnosticWriter is specified, that setting takes precedence.

func (*Loader) DiagnosticWriter

func (l *Loader) DiagnosticWriter(w hcl.DiagnosticWriter)

DiagnosticWriter sets up a Writer to write the diagnostic when an error occurs in the Loader.

func (*Loader) Functions

func (l *Loader) Functions(functions map[string]function.Function)

Functions adds functions used during HCL decoding.

func (*Loader) Load

func (l *Loader) Load(cfg interface{}, paths ...string) error

Load considers `paths` as a configuration file county written in HCL and reads *.hcl and *.hcl.json. and assigns the decoded values to the `cfg` values.

func (*Loader) LoadWithBody

func (l *Loader) LoadWithBody(cfg interface{}, ctx *hcl.EvalContext, body hcl.Body) hcl.Diagnostics

LoadWithBody assigns a value to `val` using a parsed hcl.Body and hcl.EvalContext. mainly used to achieve partial loading when implementing Restrict functions.

func (*Loader) LoadWithBytes

func (l *Loader) LoadWithBytes(cfg interface{}, filename string, src []byte) error

func (*Loader) NewEvalContext

func (l *Loader) NewEvalContext(paths ...string) *hcl.EvalContext

NewEvalContext creates a new evaluation context.

func (*Loader) Variables

func (l *Loader) Variables(variables map[string]cty.Value)

Variables adds variables used during HCL decoding.

type Restrictor

type Restrictor interface {
	Restrict(bodyContent *hcl.BodyContent, ctx *hcl.EvalContext) hcl.Diagnostics
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL