diff --git a/examples/hil.hcl b/examples/hil.hcl new file mode 100644 index 00000000..3b938765 --- /dev/null +++ b/examples/hil.hcl @@ -0,0 +1,9 @@ +resource "file" "file1" { + path = "/tmp/mgmt-hello-world" + content = "${exec.sleep.Output}" + state = "exists" +} + +resource "exec" "sleep" { + cmd = "echo hello" +} diff --git a/hcl/parse.go b/hcl/parse.go index ece7c124..7c1cf327 100644 --- a/hcl/parse.go +++ b/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, }) } diff --git a/hil/interpolate.go b/hil/interpolate.go new file mode 100644 index 00000000..35dd79fe --- /dev/null +++ b/hil/interpolate.go @@ -0,0 +1,89 @@ +// Mgmt +// Copyright (C) 2013-2017+ James Shubin and the project contributors +// Written by James Shubin 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 . + +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 +} diff --git a/resources/resources.go b/resources/resources.go index 70c4522a..338f4136 100644 --- a/resources/resources.go +++ b/resources/resources.go @@ -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? diff --git a/resources/sendrecv.go b/resources/sendrecv.go index 22421813..9d599219 100644 --- a/resources/sendrecv.go +++ b/resources/sendrecv.go @@ -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) {