hcl: Added hil string interpolation to hcl frontend
This commit is contained in:
9
examples/hil.hcl
Normal file
9
examples/hil.hcl
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
resource "file" "file1" {
|
||||||
|
path = "/tmp/mgmt-hello-world"
|
||||||
|
content = "${exec.sleep.Output}"
|
||||||
|
state = "exists"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "exec" "sleep" {
|
||||||
|
cmd = "echo hello"
|
||||||
|
}
|
||||||
62
hcl/parse.go
62
hcl/parse.go
@@ -25,7 +25,9 @@ import (
|
|||||||
|
|
||||||
"github.com/hashicorp/hcl"
|
"github.com/hashicorp/hcl"
|
||||||
"github.com/hashicorp/hcl/hcl/ast"
|
"github.com/hashicorp/hcl/hcl/ast"
|
||||||
|
"github.com/hashicorp/hil"
|
||||||
"github.com/purpleidea/mgmt/gapi"
|
"github.com/purpleidea/mgmt/gapi"
|
||||||
|
hv "github.com/purpleidea/mgmt/hil"
|
||||||
"github.com/purpleidea/mgmt/pgraph"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
"github.com/purpleidea/mgmt/resources"
|
"github.com/purpleidea/mgmt/resources"
|
||||||
)
|
)
|
||||||
@@ -68,6 +70,7 @@ type Resource struct {
|
|||||||
resource resources.Res
|
resource resources.Res
|
||||||
Meta resources.MetaParams
|
Meta resources.MetaParams
|
||||||
deps []*Edge
|
deps []*Edge
|
||||||
|
rcv map[string]*hv.ResourceVariable
|
||||||
}
|
}
|
||||||
|
|
||||||
type key struct {
|
type key struct {
|
||||||
@@ -205,6 +208,29 @@ func graphFromConfig(c *Config, data gapi.Data) (*pgraph.Graph, error) {
|
|||||||
}
|
}
|
||||||
graph.AddEdge(from, to, edge)
|
graph.AddEdge(from, to, edge)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recv := make(map[string]*resources.Send)
|
||||||
|
// build Rcv's from resource variables
|
||||||
|
for k, v := range r.rcv {
|
||||||
|
send, ok := lookup[key{strings.ToLower(v.Kind), v.Name}]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("resource not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
recv[strings.ToUpper(string(k[0]))+k[1:]] = &resources.Send{
|
||||||
|
Res: resources.VtoR(send),
|
||||||
|
Key: v.Field,
|
||||||
|
}
|
||||||
|
|
||||||
|
to := lookup[key{strings.ToLower(r.Kind), r.Name}]
|
||||||
|
edge := &resources.Edge{
|
||||||
|
Name: v.Name,
|
||||||
|
Notify: true,
|
||||||
|
}
|
||||||
|
graph.AddEdge(send, to, edge)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.resource.SetRecv(recv)
|
||||||
}
|
}
|
||||||
|
|
||||||
return graph, nil
|
return graph, nil
|
||||||
@@ -296,6 +322,41 @@ func loadResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var config map[string]interface{}
|
||||||
|
if err := hcl.DecodeObject(&config, item.Val); err != nil {
|
||||||
|
log.Printf("HCL: unable to decode body: %v", err)
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Error reading config for %s: %s",
|
||||||
|
name,
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(config, "meta")
|
||||||
|
delete(config, "depends_on")
|
||||||
|
|
||||||
|
rcv := make(map[string]*hv.ResourceVariable)
|
||||||
|
// parse strings for hil
|
||||||
|
for k, v := range config {
|
||||||
|
n, err := hil.Parse(v.(string))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse fields: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
variables, err := hv.ParseVariables(n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse variables: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range variables {
|
||||||
|
val, ok := v.(*hv.ResourceVariable)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rcv[k] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res, err := resources.NewResource(kind)
|
res, err := resources.NewResource(kind)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("HCLParse: unable to parse resource: %v", err)
|
log.Printf("HCLParse: unable to parse resource: %v", err)
|
||||||
@@ -320,6 +381,7 @@ func loadResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
|
|||||||
Kind: kind,
|
Kind: kind,
|
||||||
resource: res,
|
resource: res,
|
||||||
deps: edges,
|
deps: edges,
|
||||||
|
rcv: rcv,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
89
hil/interpolate.go
Normal file
89
hil/interpolate.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 2013-2017+ James Shubin and the project contributors
|
||||||
|
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package hil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hil/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Variable defines an interpolated variable.
|
||||||
|
type Variable interface {
|
||||||
|
Key() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceVariable defines a variable type used to reference fields of a resource
|
||||||
|
// e.g. ${file.file1.Content}
|
||||||
|
type ResourceVariable struct {
|
||||||
|
Kind, Name, Field string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key returns a string representation of the variable key.
|
||||||
|
func (r *ResourceVariable) Key() string {
|
||||||
|
return fmt.Sprintf("%s.%s.%s", r.Kind, r.Name, r.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInterpolatedVariable takes a variable key and return the interpolated variable
|
||||||
|
// of the required type.
|
||||||
|
func NewInterpolatedVariable(k string) (Variable, error) {
|
||||||
|
// for now resource variables are the only thing.
|
||||||
|
parts := strings.SplitN(k, ".", 3)
|
||||||
|
|
||||||
|
return &ResourceVariable{
|
||||||
|
Kind: parts[0],
|
||||||
|
Name: parts[1],
|
||||||
|
Field: parts[2],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseVariables will traverse a HIL tree looking for variables and returns a
|
||||||
|
// list of them.
|
||||||
|
func ParseVariables(tree ast.Node) ([]Variable, error) {
|
||||||
|
var result []Variable
|
||||||
|
var finalErr error
|
||||||
|
|
||||||
|
visitor := func(n ast.Node) ast.Node {
|
||||||
|
if finalErr != nil {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
switch nt := n.(type) {
|
||||||
|
case *ast.VariableAccess:
|
||||||
|
v, err := NewInterpolatedVariable(nt.Name)
|
||||||
|
if err != nil {
|
||||||
|
finalErr = err
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
result = append(result, v)
|
||||||
|
default:
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.Accept(visitor)
|
||||||
|
|
||||||
|
if finalErr != nil {
|
||||||
|
return nil, finalErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
@@ -139,6 +139,7 @@ type Base interface {
|
|||||||
Refresh() bool // is there a pending refresh to run?
|
Refresh() bool // is there a pending refresh to run?
|
||||||
SetRefresh(bool) // set the refresh state of this resource
|
SetRefresh(bool) // set the refresh state of this resource
|
||||||
SendRecv(Res) (map[string]bool, error) // send->recv data passing function
|
SendRecv(Res) (map[string]bool, error) // send->recv data passing function
|
||||||
|
SetRecv(map[string]*Send)
|
||||||
IsStateOK() bool
|
IsStateOK() bool
|
||||||
StateOK(b bool)
|
StateOK(b bool)
|
||||||
GroupCmp(Res) bool // TODO: is there a better name for this?
|
GroupCmp(Res) bool // TODO: is there a better name for this?
|
||||||
|
|||||||
@@ -173,6 +173,11 @@ type Send struct {
|
|||||||
Changed bool // set to true if this key was updated, read only!
|
Changed bool // set to true if this key was updated, read only!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRecv sets the Res Recv field to given map of Send structs
|
||||||
|
func (obj *BaseRes) SetRecv(recv map[string]*Send) {
|
||||||
|
obj.Recv = recv
|
||||||
|
}
|
||||||
|
|
||||||
// SendRecv pulls in the sent values into the receive slots. It is called by the
|
// SendRecv pulls in the sent values into the receive slots. It is called by the
|
||||||
// receiver and must be given as input the full resource struct to receive on.
|
// receiver and must be given as input the full resource struct to receive on.
|
||||||
func (obj *BaseRes) SendRecv(res Res) (map[string]bool, error) {
|
func (obj *BaseRes) SendRecv(res Res) (map[string]bool, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user