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/hcl/ast"
|
||||
"github.com/hashicorp/hil"
|
||||
"github.com/purpleidea/mgmt/gapi"
|
||||
hv "github.com/purpleidea/mgmt/hil"
|
||||
"github.com/purpleidea/mgmt/pgraph"
|
||||
"github.com/purpleidea/mgmt/resources"
|
||||
)
|
||||
@@ -68,6 +70,7 @@ type Resource struct {
|
||||
resource resources.Res
|
||||
Meta resources.MetaParams
|
||||
deps []*Edge
|
||||
rcv map[string]*hv.ResourceVariable
|
||||
}
|
||||
|
||||
type key struct {
|
||||
@@ -205,6 +208,29 @@ func graphFromConfig(c *Config, data gapi.Data) (*pgraph.Graph, error) {
|
||||
}
|
||||
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
|
||||
@@ -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)
|
||||
if err != nil {
|
||||
log.Printf("HCLParse: unable to parse resource: %v", err)
|
||||
@@ -320,6 +381,7 @@ func loadResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
|
||||
Kind: kind,
|
||||
resource: res,
|
||||
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?
|
||||
SetRefresh(bool) // set the refresh state of this resource
|
||||
SendRecv(Res) (map[string]bool, error) // send->recv data passing function
|
||||
SetRecv(map[string]*Send)
|
||||
IsStateOK() bool
|
||||
StateOK(b bool)
|
||||
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!
|
||||
}
|
||||
|
||||
// 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
|
||||
// receiver and must be given as input the full resource struct to receive on.
|
||||
func (obj *BaseRes) SendRecv(res Res) (map[string]bool, error) {
|
||||
|
||||
Reference in New Issue
Block a user