diff --git a/config.go b/config.go index 12bf912..b8bdd1a 100644 --- a/config.go +++ b/config.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/jumppad-labs/hclconfig/errors" + "github.com/jumppad-labs/hclconfig/resources" "github.com/jumppad-labs/hclconfig/types" "github.com/silas/dag" ) @@ -82,7 +83,7 @@ func (c *Config) FindResource(path string) (types.Resource, error) { // local version of FindResource that does not lock the config func (c *Config) findResource(path string) (types.Resource, error) { - fqdn, err := types.ParseFQRN(path) + fqdn, err := resources.ParseFQRN(path) if err != nil { return nil, err } @@ -108,7 +109,7 @@ func (c *Config) FindRelativeResource(path string, parentModule string) (types.R c.sync.Lock() defer c.sync.Unlock() - fqdn, err := types.ParseFQRN(path) + fqdn, err := resources.ParseFQRN(path) if err != nil { return nil, err } @@ -157,12 +158,12 @@ func (c *Config) FindModuleResources(module string, includeSubModules bool) ([]t c.sync.Lock() defer c.sync.Unlock() - fqdn, err := types.ParseFQRN(module) + fqdn, err := resources.ParseFQRN(module) if err != nil { return nil, err } - if fqdn.Type != types.TypeModule { + if fqdn.Type != resources.TypeModule { return nil, fmt.Errorf("resource %s is not a module reference", module) } @@ -206,7 +207,7 @@ func (c *Config) AppendResourcesFromConfig(new *Config) error { defer c.sync.Unlock() for _, r := range new.Resources { - fqdn := types.FQDNFromResource(r).String() + fqdn := resources.FQRNFromResource(r).String() // does the resource already exist? if _, err := c.findResource(fqdn); err == nil { @@ -420,7 +421,7 @@ func (c *Config) Walk(wf WalkCallback, reverse bool) error { } // if this is the root module or is disabled skip - if (r.Metadata().Type == types.TypeRoot || r.Metadata().Type == types.TypeModule) || r.GetDisabled() { + if (r.Metadata().Type == resources.TypeRoot || r.Metadata().Type == resources.TypeModule) || r.GetDisabled() { return nil } @@ -493,7 +494,7 @@ func (c *Config) walk(wf dag.WalkFunc, reverse bool) []error { } func (c *Config) addResource(r types.Resource, ctx *hcl.EvalContext, b *hclsyntax.Body) error { - fqdn := types.FQDNFromResource(r) + fqdn := resources.FQRNFromResource(r) // set the ID r.Metadata().ID = fqdn.String() diff --git a/config_test.go b/config_test.go index 0149bea..f4bd96d 100644 --- a/config_test.go +++ b/config_test.go @@ -5,18 +5,19 @@ import ( "sync" "testing" + "github.com/jumppad-labs/hclconfig/resources" "github.com/jumppad-labs/hclconfig/test_fixtures/structs" "github.com/jumppad-labs/hclconfig/types" "github.com/stretchr/testify/require" ) func testSetupConfig(t *testing.T) (*Config, []types.Resource) { - typs := types.DefaultTypes() + typs := resources.DefaultResources() typs[structs.TypeNetwork] = &structs.Network{} typs[structs.TypeContainer] = &structs.Container{} typs[structs.TypeTemplate] = &structs.Template{} - var1, _ := typs.CreateResource(types.TypeVariable, "var1") + var1, _ := typs.CreateResource(resources.TypeVariable, "var1") var1.Metadata().Checksum = types.Checksum{ Parsed: "123", Processed: "abc", @@ -28,21 +29,21 @@ func testSetupConfig(t *testing.T) (*Config, []types.Resource) { Processed: "bcd", } - mod1, _ := typs.CreateResource(types.TypeModule, "module1") + mod1, _ := typs.CreateResource(resources.TypeModule, "module1") mod1.SetDependsOn([]string{"resource.network.cloud"}) mod1.Metadata().Checksum = types.Checksum{ Parsed: "345", Processed: "cde", } - var2, _ := typs.CreateResource(types.TypeVariable, "var2") + var2, _ := typs.CreateResource(resources.TypeVariable, "var2") var2.Metadata().Module = "module1" var2.Metadata().Checksum = types.Checksum{ Parsed: "456", Processed: "def", } - mod2, _ := typs.CreateResource(types.TypeModule, "module2") + mod2, _ := typs.CreateResource(resources.TypeModule, "module2") mod2.Metadata().Module = "module1" mod2.Metadata().Checksum = types.Checksum{ Parsed: "567", @@ -87,14 +88,14 @@ func testSetupConfig(t *testing.T) (*Config, []types.Resource) { Processed: "ijk", } - out1, _ := typs.CreateResource(types.TypeOutput, "fqdn") + out1, _ := typs.CreateResource(resources.TypeOutput, "fqdn") out1.Metadata().Module = "module1.module2" out1.Metadata().Checksum = types.Checksum{ Parsed: "0ab", Processed: "jkl", } - out2, _ := typs.CreateResource(types.TypeOutput, "out") + out2, _ := typs.CreateResource(resources.TypeOutput, "out") out2.SetDependsOn([]string{"resource.network.cloud.id", "resource.container.test_dev"}) out2.Metadata().Checksum = types.Checksum{ Parsed: "abc", @@ -293,7 +294,7 @@ func TestRemoveResourceRemoves(t *testing.T) { } func TestRemoveResourceNotFoundReturnsError(t *testing.T) { - typs := types.DefaultTypes() + typs := resources.DefaultResources() typs[structs.TypeNetwork] = &structs.Network{} c, _ := testSetupConfig(t) @@ -315,7 +316,7 @@ func TestToJSONSerializesJSON(t *testing.T) { } func TestAppendResourcesMerges(t *testing.T) { - typs := types.DefaultTypes() + typs := resources.DefaultResources() typs[structs.TypeNetwork] = &structs.Network{} c, _ := testSetupConfig(t) @@ -334,7 +335,7 @@ func TestAppendResourcesMerges(t *testing.T) { } func TestAppendResourcesWhenExistsReturnsError(t *testing.T) { - typs := types.DefaultTypes() + typs := resources.DefaultResources() typs[structs.TypeNetwork] = &structs.Network{} c, _ := testSetupConfig(t) @@ -357,7 +358,7 @@ func TestProcessForwardExecutesCallbacksInCorrectOrder(t *testing.T) { func(r types.Resource) error { callSync.Lock() - calls = append(calls, types.ResourceFQRN{ + calls = append(calls, resources.FQRN{ Module: r.Metadata().Module, Resource: r.Metadata().Name, Type: r.Metadata().Type, @@ -389,7 +390,7 @@ func TestProcessReverseExecutesCallbacksInCorrectOrder(t *testing.T) { func(r types.Resource) error { callSync.Lock() - calls = append(calls, types.ResourceFQRN{ + calls = append(calls, resources.FQRN{ Module: r.Metadata().Module, Resource: r.Metadata().Name, Type: r.Metadata().Type, @@ -418,7 +419,7 @@ func TestProcessCallbackErrorHaltsExecution(t *testing.T) { err := c.Walk( func(r types.Resource) error { callSync.Lock() - calls = append(calls, types.ResourceFQRN{ + calls = append(calls, resources.FQRN{ Module: r.Metadata().Module, Resource: r.Metadata().Name, Type: r.Metadata().Type, @@ -458,8 +459,8 @@ func TestDiffReturnsResourcesAdded(t *testing.T) { c, _ := testSetupConfig(t) new := copyConfig(t, c) - typs := types.DefaultTypes() - var1, _ := typs.CreateResource(types.TypeVariable, "var22") + typs := resources.DefaultResources() + var1, _ := typs.CreateResource(resources.TypeVariable, "var22") var1.Metadata().Checksum = types.Checksum{ Parsed: "zzz", Processed: "111", diff --git a/dag.go b/dag.go index 495456b..73b718c 100644 --- a/dag.go +++ b/dag.go @@ -8,6 +8,7 @@ import ( "github.com/jumppad-labs/hclconfig/convert" "github.com/jumppad-labs/hclconfig/errors" + "github.com/jumppad-labs/hclconfig/resources" "github.com/jumppad-labs/hclconfig/types" "github.com/silas/dag" "github.com/zclconf/go-cty/cty" @@ -21,13 +22,13 @@ func doYaLikeDAGs(c *Config) (*dag.AcyclicGraph, error) { graph := &dag.AcyclicGraph{} // add a root node for the graph - root, _ := types.DefaultTypes().CreateResource(types.TypeRoot, "root") + root, _ := resources.DefaultResources().CreateResource(resources.TypeRoot, "root") graph.Add(root) // Loop over all resources and add to graph for _, resource := range c.Resources { // ignore variables - if resource.Metadata().Type != types.TypeVariable { + if resource.Metadata().Type != resources.TypeVariable { graph.Add(resource) } } @@ -37,7 +38,7 @@ func doYaLikeDAGs(c *Config) (*dag.AcyclicGraph, error) { hasDeps := false // do nothing with variables - if resource.Metadata().Type == types.TypeVariable { + if resource.Metadata().Type == resources.TypeVariable { continue } @@ -62,7 +63,7 @@ func doYaLikeDAGs(c *Config) (*dag.AcyclicGraph, error) { for _, d := range resource.GetDependsOn() { var err error - fqdn, err := types.ParseFQRN(d) + fqdn, err := resources.ParseFQRN(d) if err != nil { pe := errors.ParserError{} pe.Line = resource.Metadata().Line @@ -75,7 +76,7 @@ func doYaLikeDAGs(c *Config) (*dag.AcyclicGraph, error) { } // when the dependency is a module, depend on all resources in the module - if fqdn.Type == types.TypeModule { + if fqdn.Type == resources.TypeModule { // assume that all dependencies references have been written with no // knowledge of their parent module. Therefore if the parent module is // "module1" and the reference is "module.module2.resource.container.mine.id" @@ -100,7 +101,7 @@ func doYaLikeDAGs(c *Config) (*dag.AcyclicGraph, error) { } // when the dependency is a resource, depend on the resource - if fqdn.Type != types.TypeModule { + if fqdn.Type != resources.TypeModule { // assume that all dependencies references have been written with no // knowledge of their parent module. Therefore if the parent module is // "module1" and the reference is "module.module2.resource.container.mine.id" @@ -173,7 +174,7 @@ func createCallback(c *Config, wf WalkCallback) func(v dag.Vertex) (diags dag.Di } // if this is the root module or is disabled skip or is a variable - if (r.Metadata().Type == types.TypeRoot) || r.GetDisabled() || r.Metadata().Type == types.TypeVariable { + if (r.Metadata().Type == resources.TypeRoot) || r.GetDisabled() || r.Metadata().Type == resources.TypeVariable { return nil } @@ -191,7 +192,7 @@ func createCallback(c *Config, wf WalkCallback) func(v dag.Vertex) (diags dag.Di // all linked values should now have been processed as the graph // will have handled them first for _, v := range r.Metadata().Links { - fqrn, err := types.ParseFQRN(v) + fqrn, err := resources.ParseFQRN(v) if err != nil { pe := errors.ParserError{} pe.Filename = r.Metadata().File @@ -221,11 +222,11 @@ func createCallback(c *Config, wf WalkCallback) func(v dag.Vertex) (diags dag.Di // once we have found a resource convert it to a cty type and then // set it on the context switch l.Metadata().Type { - case types.TypeLocal: - loc := l.(*types.Local) + case resources.TypeLocal: + loc := l.(*resources.Local) ctyRes = loc.CtyValue - case types.TypeOutput: - out := l.(*types.Output) + case resources.TypeOutput: + out := l.(*resources.Output) ctyRes = out.CtyValue default: ctyRes, err = convert.GoToCtyValue(l) @@ -278,7 +279,7 @@ func createCallback(c *Config, wf WalkCallback) func(v dag.Vertex) (diags dag.Di // if the type is a module the potentially we only just found out that we should be // disabled // as an additional check, set all module resources to disabled if the module is disabled - if r.GetDisabled() && r.Metadata().Type == types.TypeModule { + if r.GetDisabled() && r.Metadata().Type == resources.TypeModule { // find all dependent resources dr, err := c.FindModuleResources(r.Metadata().ID, true) if err != nil { @@ -304,7 +305,7 @@ func createCallback(c *Config, wf WalkCallback) func(v dag.Vertex) (diags dag.Di // // if disabled was set through interpolation, the value has only been set here // we need to handle an additional check - if !r.GetDisabled() && r.Metadata().Type != types.TypeModule { + if !r.GetDisabled() && r.Metadata().Type != resources.TypeModule { // call the callbacks if wf != nil { @@ -324,8 +325,8 @@ func createCallback(c *Config, wf WalkCallback) func(v dag.Vertex) (diags dag.Di // if the type is a module we need to add the variables to the // context - if r.Metadata().Type == types.TypeModule { - mod := r.(*types.Module) + if r.Metadata().Type == resources.TypeModule { + mod := r.(*resources.Module) var mapVars map[string]cty.Value if att, ok := mod.Variables.(*hcl.Attribute); ok { @@ -340,16 +341,16 @@ func createCallback(c *Config, wf WalkCallback) func(v dag.Vertex) (diags dag.Di // if this is an output or local we need to convert the value into // a go type - if r.Metadata().Type == types.TypeOutput { - o := r.(*types.Output) + if r.Metadata().Type == resources.TypeOutput { + o := r.(*resources.Output) if !o.CtyValue.IsNull() { o.Value = castVar(o.CtyValue) } } - if r.Metadata().Type == types.TypeLocal { - o := r.(*types.Local) + if r.Metadata().Type == resources.TypeLocal { + o := r.(*resources.Local) if !o.CtyValue.IsNull() { o.Value = castVar(o.CtyValue) diff --git a/example/main.go b/example/main.go index d1e7c15..e8f13ca 100644 --- a/example/main.go +++ b/example/main.go @@ -7,6 +7,7 @@ import ( "os" "github.com/jumppad-labs/hclconfig" + "github.com/jumppad-labs/hclconfig/resources" "github.com/jumppad-labs/hclconfig/types" ) @@ -89,7 +90,7 @@ func printConfig(c *hclconfig.Config) { fmt.Println(printPostgres(t, 2)) case "output": - t := r.(*types.Output) + t := r.(*resources.Output) fmt.Printf(" Postgres %s\n", t.Meta.Name) fmt.Printf(" Module %s\n", t.Meta.Module) fmt.Printf(" --- Value: %s\n", t.Value) diff --git a/parse_test.go b/parse_test.go index e33e156..6b8f6a2 100644 --- a/parse_test.go +++ b/parse_test.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/jumppad-labs/hclconfig/errors" + "github.com/jumppad-labs/hclconfig/resources" "github.com/jumppad-labs/hclconfig/test_fixtures/embedded" "github.com/jumppad-labs/hclconfig/test_fixtures/structs" "github.com/jumppad-labs/hclconfig/types" @@ -157,7 +158,7 @@ func TestParseResolvesArrayReferences(t *testing.T) { require.NoError(t, err) require.NotNil(t, r) - out := r.(*types.Output) + out := r.(*resources.Output) require.Equal(t, "10.6.0.200", out.Value) // check variable has been interpolated @@ -165,14 +166,14 @@ func TestParseResolvesArrayReferences(t *testing.T) { require.NoError(t, err) require.NotNil(t, r) - out = r.(*types.Output) + out = r.(*resources.Output) require.Equal(t, "10.7.0.201", out.Value) r, err = c.FindResource("output.ip_addresses") require.NoError(t, err) require.NotNil(t, r) - out = r.(*types.Output) + out = r.(*resources.Output) require.Equal(t, "10.6.0.200", out.Value.([]interface{})[0].(string)) require.Equal(t, "10.7.0.201", out.Value.([]interface{})[1].(string)) require.Equal(t, float64(12), out.Value.([]interface{})[2].(float64)) @@ -275,34 +276,34 @@ func TestResourceReferencesInExpressionsAreEvaluated(t *testing.T) { r, err = c.FindResource("output.splat") require.NoError(t, err) - cont := r.(*types.Output) + cont := r.(*resources.Output) require.Equal(t, "/cache", cont.Value.([]interface{})[0]) require.Equal(t, "/cache2", cont.Value.([]interface{})[1]) r, err = c.FindResource("output.splat_with_null") require.NoError(t, err) - cont = r.(*types.Output) + cont = r.(*resources.Output) require.Equal(t, "test1", cont.Value.([]interface{})[0]) require.Equal(t, "test2", cont.Value.([]interface{})[1]) r, err = c.FindResource("output.function") require.NoError(t, err) - cont = r.(*types.Output) + cont = r.(*resources.Output) require.Equal(t, float64(2), cont.Value) r, err = c.FindResource("output.binary") require.NoError(t, err) - cont = r.(*types.Output) + cont = r.(*resources.Output) require.Equal(t, false, cont.Value) r, err = c.FindResource("output.condition") require.NoError(t, err) - cont = r.(*types.Output) + cont = r.(*resources.Output) require.Equal(t, "/cache", cont.Value) r, err = c.FindResource("output.template") require.NoError(t, err) - cont = r.(*types.Output) + cont = r.(*resources.Output) require.Equal(t, "abc/2", cont.Value) } @@ -373,20 +374,20 @@ func TestParseModuleCreatesOutputs(t *testing.T) { // check output value from module is equal to the module variable // which is set as an interpolated value of the container base - require.Equal(t, float64(4096), cont.(*types.Output).Value) + require.Equal(t, float64(4096), cont.(*resources.Output).Value) cont, err = c.FindResource("output.module2_container_resources_cpu") require.NoError(t, err) // check output value from module is equal to the module variable // which is set as the variable for the config - require.Equal(t, float64(512), cont.(*types.Output).Value) + require.Equal(t, float64(512), cont.(*resources.Output).Value) cont, err = c.FindResource("output.module3_container_resources_cpu") require.NoError(t, err) // check the output variable is set to the default value for the module - require.Equal(t, float64(2048), cont.(*types.Output).Value) + require.Equal(t, float64(2048), cont.(*resources.Output).Value) cont, err = c.FindResource("output.module1_from_list_1") require.NoError(t, err) @@ -396,8 +397,8 @@ func TestParseModuleCreatesOutputs(t *testing.T) { // check an element can be obtained from a list of values // returned from a output - require.Equal(t, float64(0), cont.(*types.Output).Value) - require.Equal(t, float64(4096), cont2.(*types.Output).Value) + require.Equal(t, float64(0), cont.(*resources.Output).Value) + require.Equal(t, float64(4096), cont2.(*resources.Output).Value) // check an element can be obtained from a map of values // returned from a output @@ -409,15 +410,15 @@ func TestParseModuleCreatesOutputs(t *testing.T) { // check element can be obtained from a map of values // returned in the output - require.Equal(t, "consul", cont.(*types.Output).Value) - require.Equal(t, float64(4096), cont2.(*types.Output).Value) + require.Equal(t, "consul", cont.(*resources.Output).Value) + require.Equal(t, float64(4096), cont2.(*resources.Output).Value) cont, err = c.FindResource("output.object") require.NoError(t, err) // check element can be obtained from a map of values // returned in the output - meta := cont.(*types.Output).Value.(map[string]interface{})["meta"].(map[string]interface{}) + meta := cont.(*resources.Output).Value.(map[string]interface{})["meta"].(map[string]interface{}) require.Equal(t, "base", meta["name"]) } @@ -727,7 +728,7 @@ func TestParserStopsParseOnCallbackError(t *testing.T) { o.Callback = func(r types.Resource) error { callSync.Lock() - calls = append(calls, types.ResourceFQRN{ + calls = append(calls, resources.FQRN{ Module: r.Metadata().Module, Resource: r.Metadata().Name, Type: r.Metadata().Type, @@ -886,8 +887,8 @@ func TestParserGeneratesChecksums(t *testing.T) { require.Equal(t, r3.Metadata().Checksum.Parsed, c3.Metadata().Checksum.Parsed) } -func TestParserHandlesCyclicalReference(t *testing.T) { - f, pathErr := filepath.Abs("./test_fixtures/cyclical/cyclical.hcl") +func TestParserCyclicalReferenceReturnsError(t *testing.T) { + f, pathErr := filepath.Abs("./test_fixtures/cyclical/fail/cyclical.hcl") if pathErr != nil { t.Fatal(pathErr) } @@ -900,6 +901,18 @@ func TestParserHandlesCyclicalReference(t *testing.T) { require.ErrorContains(t, err, "'resource.container.one' depends on 'resource.network.two'") } +func TestParserNoCyclicalReferenceReturns(t *testing.T) { + f, pathErr := filepath.Abs("./test_fixtures/cyclical/pass/cyclical.hcl") + if pathErr != nil { + t.Fatal(pathErr) + } + + p := setupParser(t) + + _, err := p.ParseFile(f) + require.NoError(t, err) +} + func TestParseDirectoryReturnsConfigErrorWhenParseDirectoryFails(t *testing.T) { f, pathErr := filepath.Abs("./test_fixtures/invalid") if pathErr != nil { diff --git a/parser.go b/parser.go index c2a07c9..6477b91 100644 --- a/parser.go +++ b/parser.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/hcl/v2/hclparse" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/jumppad-labs/hclconfig/errors" + "github.com/jumppad-labs/hclconfig/resources" "github.com/jumppad-labs/hclconfig/types" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" @@ -88,7 +89,7 @@ func NewParser(options *ParserOptions) *Parser { o = DefaultOptions() } - return &Parser{options: *o, registeredTypes: types.DefaultTypes(), registeredFunctions: map[string]function.Function{}} + return &Parser{options: *o, registeredTypes: resources.DefaultResources(), registeredFunctions: map[string]function.Function{}} } // RegisterType type registers a struct that implements Resource with the given name @@ -138,7 +139,7 @@ func (p *Parser) ParseFile(file string) (*Config, error) { de.Line = rt.Metadata().Line de.Column = rt.Metadata().Column de.Filename = rt.Metadata().File - de.Message = fmt.Sprintf(`error parsing resource "%s" %s`, types.FQDNFromResource(rt).String(), err) + de.Message = fmt.Sprintf(`error parsing resource "%s" %s`, resources.FQRNFromResource(rt).String(), err) ce.AppendError(de) } @@ -181,7 +182,7 @@ func (p *Parser) ParseDirectory(dir string) (*Config, error) { de.Line = rt.Metadata().Line de.Column = rt.Metadata().Column de.Filename = rt.Metadata().File - de.Message = fmt.Sprintf(`error parsing resource "%s" %s`, types.FQDNFromResource(rt).String(), err) + de.Message = fmt.Sprintf(`error parsing resource "%s" %s`, resources.FQRNFromResource(rt).String(), err) ce.AppendError(de) } @@ -402,9 +403,9 @@ func (p *Parser) parseVariablesInFile(ctx *hcl.EvalContext, file string, c *Conf for _, b := range body.Blocks { switch b.Type { - case types.TypeVariable: - r, _ := p.registeredTypes.CreateResource(types.TypeVariable, b.Labels[0]) - v := r.(*types.Variable) + case resources.TypeVariable: + r, _ := p.registeredTypes.CreateResource(resources.TypeVariable, b.Labels[0]) + v := r.(*resources.Variable) // add the checksum for the resource cs, err := ReadFileLocation(b.Range().Filename, b.Range().Start.Line, b.TypeRange.Start.Column, b.Range().End.Line, b.Range().End.Column) @@ -466,16 +467,16 @@ func (p *Parser) parseResourcesInFile(ctx *hcl.EvalContext, file string, c *Conf // create the registered type if not a variable or output // variables and outputs are processed in a separate run switch b.Type { - case types.TypeVariable: + case resources.TypeVariable: continue - case types.TypeModule: + case resources.TypeModule: err := p.parseModule(ctx, c, file, b, moduleName, dependsOn) if err != nil { return err } - case types.TypeOutput: + case resources.TypeOutput: fallthrough - case types.TypeLocal: + case resources.TypeLocal: fallthrough case types.TypeResource: err := p.parseResource(ctx, c, file, b, moduleName, dependsOn, disabled) @@ -531,7 +532,7 @@ func setDependsOn(ctx *hcl.EvalContext, r types.Resource, b *hclsyntax.Body, dep // depends on is a slice of string dependsOnSlice := dependsOnVal.AsValueSlice() for _, d := range dependsOnSlice { - _, err := types.ParseFQRN(d.AsString()) + _, err := resources.ParseFQRN(d.AsString()) if err != nil { return fmt.Errorf("invalid dependency %s, %s", d.AsString(), err) } @@ -568,7 +569,7 @@ func (p *Parser) parseModule(ctx *hcl.EvalContext, c *Config, file string, b *hc return []error{&de} } - rt, _ := types.DefaultTypes().CreateResource(string(types.TypeModule), b.Labels[0]) + rt, _ := resources.DefaultResources().CreateResource(string(resources.TypeModule), b.Labels[0]) rt.Metadata().Module = moduleName rt.Metadata().File = file @@ -651,7 +652,7 @@ func (p *Parser) parseModule(ctx *hcl.EvalContext, c *Config, file string, b *hc return errs } - rt.(*types.Module).SubContext = subContext + rt.(*resources.Module).SubContext = subContext // add the module c.addResource(rt, ctx, b.Body) @@ -729,7 +730,7 @@ func (p *Parser) parseResource(ctx *hcl.EvalContext, c *Config, file string, b * return &de } - case types.TypeLocal: + case resources.TypeLocal: // if the type is local check there is one label if len(b.Labels) != 1 { de := errors.ParserError{} @@ -752,7 +753,7 @@ func (p *Parser) parseResource(ctx *hcl.EvalContext, c *Config, file string, b * return &de } - rt, err = p.registeredTypes.CreateResource(types.TypeLocal, name) + rt, err = p.registeredTypes.CreateResource(resources.TypeLocal, name) if err != nil { de := errors.ParserError{} de.Line = b.TypeRange.Start.Line @@ -763,7 +764,7 @@ func (p *Parser) parseResource(ctx *hcl.EvalContext, c *Config, file string, b * return &de } - case types.TypeOutput: + case resources.TypeOutput: // if the type is output check there is one label if len(b.Labels) != 1 { de := errors.ParserError{} @@ -786,7 +787,7 @@ func (p *Parser) parseResource(ctx *hcl.EvalContext, c *Config, file string, b * return &de } - rt, err = p.registeredTypes.CreateResource(types.TypeOutput, name) + rt, err = p.registeredTypes.CreateResource(resources.TypeOutput, name) if err != nil { de := errors.ParserError{} de.Line = b.TypeRange.Start.Line @@ -834,7 +835,7 @@ func (p *Parser) parseResource(ctx *hcl.EvalContext, c *Config, file string, b * de.Line = b.TypeRange.Start.Line de.Column = b.TypeRange.Start.Column de.Filename = file - de.Message = fmt.Sprintf(`unable to add resource "%s" to config %s`, types.FQDNFromResource(rt).String(), err) + de.Message = fmt.Sprintf(`unable to add resource "%s" to config %s`, resources.FQRNFromResource(rt).String(), err) return &de } @@ -1089,7 +1090,7 @@ func decodeBody(ctx *hcl.EvalContext, config *Config, path string, b *hclsyntax. // if variable process the body, everything else // lazy process on dag walk - if b.Type == string(types.TypeVariable) { + if b.Type == string(resources.TypeVariable) { diag := gohcl.DecodeBody(b.Body, ctx, p) if diag.HasErrors() { pe := errors.ParserError{} @@ -1161,12 +1162,18 @@ func getDependentResources(b *hclsyntax.Block, ctx *hcl.EvalContext, c *Config, // the references might not exist yet, we are parsing flat // but if there is a cyclical reference, one end of the circle will be found d, err := c.FindResource(dep) - //fqdnD := types.FQDNFromResource(me) + //fqdnD := resources.FQDNFromResource(me) if err == nil { // check the deps on the linked resource for _, cdep := range d.Metadata().Links { - fqdn, err := types.ParseFQRN(cdep) - fqdn.Attribute = "" + + fqrn, err := resources.ParseFQRN(cdep) + fqrn.Attribute = "" + + // append the parent module to the link as they are relative + if d.Metadata().Module != "" { + fqrn.Module = d.Metadata().Module + } if err != nil { pe := errors.ParserError{} @@ -1178,15 +1185,15 @@ func getDependentResources(b *hclsyntax.Block, ctx *hcl.EvalContext, c *Config, return nil, &pe } - if me.Metadata().Name == fqdn.Resource && - me.Metadata().Type == fqdn.Type && - me.Metadata().Module == fqdn.Module { + if me.Metadata().Name == fqrn.Resource && + me.Metadata().Type == fqrn.Type && + me.Metadata().Module == fqrn.Module { pe := errors.ParserError{} pe.Column = b.Body.SrcRange.Start.Column pe.Line = b.Body.SrcRange.Start.Line pe.Filename = b.Body.SrcRange.Filename - pe.Message = fmt.Sprintf("'%s' depends on '%s' which creates a cyclical dependency, remove the dependency from one of the resources", fqdn.String(), d.Metadata().ID) + pe.Message = fmt.Sprintf("'%s' depends on '%s' which creates a cyclical dependency, remove the dependency from one of the resources", fqrn.String(), d.Metadata().ID) pe.Level = errors.ParserErrorLevelError return nil, &pe diff --git a/resources/default.go b/resources/default.go new file mode 100644 index 0000000..1d033af --- /dev/null +++ b/resources/default.go @@ -0,0 +1,14 @@ +package resources + +import "github.com/jumppad-labs/hclconfig/types" + +// DefaultResources is a collection of the default config resources +func DefaultResources() types.RegisteredTypes { + return types.RegisteredTypes{ + "variable": &Variable{}, + "output": &Output{}, + "local": &Local{}, + "module": &Module{}, + "root": &Root{}, + } +} diff --git a/types/default_test.go b/resources/default_test.go similarity index 84% rename from types/default_test.go rename to resources/default_test.go index 9934a86..8daf518 100644 --- a/types/default_test.go +++ b/resources/default_test.go @@ -1,20 +1,21 @@ -package types +package resources import ( "reflect" "testing" + "github.com/jumppad-labs/hclconfig/types" "github.com/stretchr/testify/require" ) func TestCreateResouceReturnsNotRegisteredError(t *testing.T) { - rt := RegisteredTypes{} + rt := types.RegisteredTypes{} _, err := rt.CreateResource("foo", "bar") require.Error(t, err) } func TestDefaultTypes(t *testing.T) { - dt := DefaultTypes() + dt := DefaultResources() require.Equal(t, reflect.TypeOf(dt["variable"]), reflect.TypeOf(&Variable{})) require.Equal(t, reflect.TypeOf(dt["local"]), reflect.TypeOf(&Local{})) @@ -23,7 +24,7 @@ func TestDefaultTypes(t *testing.T) { } func TestCreateResourceCreatesType(t *testing.T) { - dt := DefaultTypes() + dt := DefaultResources() r, e := dt.CreateResource(TypeVariable, "test") require.NoError(t, e) diff --git a/types/fqrn.go b/resources/fqrn.go similarity index 91% rename from types/fqrn.go rename to resources/fqrn.go index 89922db..a654f41 100644 --- a/types/fqrn.go +++ b/resources/fqrn.go @@ -1,13 +1,15 @@ -package types +package resources import ( "fmt" "regexp" "strings" + + "github.com/jumppad-labs/hclconfig/types" ) -// ResourceFQRN is the fully qualified resource name -type ResourceFQRN struct { +// FQRN is the fully qualified resource name +type FQRN struct { // Name of the module Module string // Type of the resource @@ -47,7 +49,7 @@ type ResourceFQRN struct { // // get the "module" resource called module1 in the root "module" // // module1 -func ParseFQRN(fqrn string) (*ResourceFQRN, error) { +func ParseFQRN(fqrn string) (*FQRN, error) { moduleName := "" typeName := "" resourceName := "" @@ -130,7 +132,7 @@ func ParseFQRN(fqrn string) (*ResourceFQRN, error) { typeName = TypeModule } - return &ResourceFQRN{ + return &FQRN{ Module: moduleName, Type: typeName, Resource: resourceName, @@ -144,8 +146,8 @@ func formatErrorString(fqdn string) string { // AppendParentModule creates a new FQRN by adding the parent module // to the reference. -func (f *ResourceFQRN) AppendParentModule(parent string) ResourceFQRN { - newFQRN := ResourceFQRN{} +func (f *FQRN) AppendParentModule(parent string) FQRN { + newFQRN := FQRN{} newFQRN.Module = f.Module if parent != "" { @@ -160,16 +162,16 @@ func (f *ResourceFQRN) AppendParentModule(parent string) ResourceFQRN { return newFQRN } -// FQDNFromResource returns the ResourceFQDN for the given Resource -func FQDNFromResource(r Resource) *ResourceFQRN { - return &ResourceFQRN{ +// FQRNFromResource returns the ResourceFQDN for the given Resource +func FQRNFromResource(r types.Resource) *FQRN { + return &FQRN{ Module: r.Metadata().Module, Resource: r.Metadata().Name, Type: r.Metadata().Type, } } -func (f ResourceFQRN) String() string { +func (f FQRN) String() string { modulePart := "" if f.Module != "" { modulePart = fmt.Sprintf("module.%s.", f.Module) diff --git a/types/fqrn_test.go b/resources/fqrn_test.go similarity index 97% rename from types/fqrn_test.go rename to resources/fqrn_test.go index 450a1b7..6c89ccd 100644 --- a/types/fqrn_test.go +++ b/resources/fqrn_test.go @@ -1,8 +1,9 @@ -package types +package resources import ( "testing" + "github.com/jumppad-labs/hclconfig/types" "github.com/stretchr/testify/require" ) @@ -10,7 +11,7 @@ var typeTestContainer = "container" type testContainer struct { // embedded type holding name, etc - ResourceBase `hcl:",remain"` + types.ResourceBase `hcl:",remain"` } func TestParseFQRNParsesComponents(t *testing.T) { @@ -245,7 +246,7 @@ func TestParseFQRNWithIndexAndModuleReturnsCorrectData(t *testing.T) { } func TestFQRNFromResourceReturnsCorrectData(t *testing.T) { - dt := DefaultTypes() + dt := DefaultResources() dt[typeTestContainer] = &testContainer{} r, err := dt.CreateResource(typeTestContainer, "mytest") @@ -253,7 +254,7 @@ func TestFQRNFromResourceReturnsCorrectData(t *testing.T) { r.Metadata().Module = "mymodule" - fqrn := FQDNFromResource(r) + fqrn := FQRNFromResource(r) require.Equal(t, "mymodule", fqrn.Module) require.Equal(t, typeTestContainer, fqrn.Type) @@ -264,14 +265,14 @@ func TestFQRNFromResourceReturnsCorrectData(t *testing.T) { } func TestFQRNFromVariableReturnsCorrectData(t *testing.T) { - dt := DefaultTypes() + dt := DefaultResources() r, err := dt.CreateResource(TypeVariable, "mytest") require.NoError(t, err) //r.Metadata().Module = "mymodule" - fqrn := FQDNFromResource(r) + fqrn := FQRNFromResource(r) require.Equal(t, "", fqrn.Module) require.Equal(t, TypeVariable, fqrn.Type) @@ -282,14 +283,14 @@ func TestFQRNFromVariableReturnsCorrectData(t *testing.T) { } func TestFQRNFromVariableInModuleReturnsCorrectData(t *testing.T) { - dt := DefaultTypes() + dt := DefaultResources() r, err := dt.CreateResource(TypeVariable, "mytest") require.NoError(t, err) r.Metadata().Module = "mymodule" - fqrn := FQDNFromResource(r) + fqrn := FQRNFromResource(r) require.Equal(t, "mymodule", fqrn.Module) require.Equal(t, TypeVariable, fqrn.Type) diff --git a/types/locals.go b/resources/locals.go similarity index 61% rename from types/locals.go rename to resources/locals.go index 4bf8ae3..d0093fc 100644 --- a/types/locals.go +++ b/resources/locals.go @@ -1,12 +1,15 @@ -package types +package resources -import "github.com/zclconf/go-cty/cty" +import ( + "github.com/jumppad-labs/hclconfig/types" + "github.com/zclconf/go-cty/cty" +) const TypeLocal = "local" // Output defines an output variable which can be set by a module type Local struct { - ResourceBase `hcl:",remain"` + types.ResourceBase `hcl:",remain"` CtyValue cty.Value `hcl:"value,optional"` // value of the output Value interface{} `json:"value"` diff --git a/types/module.go b/resources/module.go similarity index 77% rename from types/module.go rename to resources/module.go index fcfda16..69dae98 100644 --- a/types/module.go +++ b/resources/module.go @@ -1,6 +1,9 @@ -package types +package resources -import "github.com/hashicorp/hcl/v2" +import ( + "github.com/hashicorp/hcl/v2" + "github.com/jumppad-labs/hclconfig/types" +) // TypeModule is the resource string for a Module resource const TypeModule = "module" @@ -8,7 +11,7 @@ const TypeModule = "module" // Module allows Shipyard configuration to be imported from external folder or // GitHub repositories type Module struct { - ResourceBase `hcl:",remain"` + types.ResourceBase `hcl:",remain"` Source string `hcl:"source" json:"source"` diff --git a/types/output.go b/resources/output.go similarity index 70% rename from types/output.go rename to resources/output.go index 69cc2c6..66357aa 100644 --- a/types/output.go +++ b/resources/output.go @@ -1,12 +1,15 @@ -package types +package resources -import "github.com/zclconf/go-cty/cty" +import ( + "github.com/jumppad-labs/hclconfig/types" + "github.com/zclconf/go-cty/cty" +) const TypeOutput = "output" // Output defines an output variable which can be set by a module type Output struct { - ResourceBase `hcl:",remain"` + types.ResourceBase `hcl:",remain"` CtyValue cty.Value `hcl:"value,optional"` // value of the output Value interface{} `json:"value"` diff --git a/types/root.go b/resources/root.go similarity index 66% rename from types/root.go rename to resources/root.go index 90b24cc..dc1f23f 100644 --- a/types/root.go +++ b/resources/root.go @@ -1,4 +1,6 @@ -package types +package resources + +import "github.com/jumppad-labs/hclconfig/types" // TypeModule is the resource string for a Module resource const TypeRoot = "root" @@ -6,5 +8,5 @@ const TypeRoot = "root" // Module allows Shipyard configuration to be imported from external folder or // GitHub repositories type Root struct { - ResourceBase `hcl:",remain"` + types.ResourceBase `hcl:",remain"` } diff --git a/resources/variable.go b/resources/variable.go new file mode 100644 index 0000000..38b4194 --- /dev/null +++ b/resources/variable.go @@ -0,0 +1,12 @@ +package resources + +import "github.com/jumppad-labs/hclconfig/types" + +const TypeVariable = "variable" + +// Output defines an output variable which can be set by a module +type Variable struct { + types.ResourceBase `hcl:",remain"` + Default interface{} `hcl:"default" json:"default"` // default value for a variable + Description string `hcl:"description,optional" json:"description,omitempty"` // description of the variable +} diff --git a/test_fixtures/cyclical/cyclical.hcl b/test_fixtures/cyclical/fail/cyclical.hcl similarity index 100% rename from test_fixtures/cyclical/cyclical.hcl rename to test_fixtures/cyclical/fail/cyclical.hcl diff --git a/test_fixtures/cyclical/pass/cyclical.hcl b/test_fixtures/cyclical/pass/cyclical.hcl new file mode 100644 index 0000000..20fa9e8 --- /dev/null +++ b/test_fixtures/cyclical/pass/cyclical.hcl @@ -0,0 +1,7 @@ +module "cyclical" { + source = "./module" +} + +resource "network" "one" { + subnet = module.cyclical.output.network.subnet +} \ No newline at end of file diff --git a/test_fixtures/cyclical/pass/module/cyclical.hcl b/test_fixtures/cyclical/pass/module/cyclical.hcl new file mode 100644 index 0000000..f381b1a --- /dev/null +++ b/test_fixtures/cyclical/pass/module/cyclical.hcl @@ -0,0 +1,7 @@ +resource "network" "one" { + subnet = "10.0.0.2/16" +} + +output "network" { + value = resource.network.one +} \ No newline at end of file diff --git a/types/default.go b/types/register.go similarity index 78% rename from types/default.go rename to types/register.go index 19c2a4d..bf276a2 100644 --- a/types/default.go +++ b/types/register.go @@ -19,17 +19,6 @@ func NewTypeNotRegisteredError(t string) *ErrTypeNotRegistered { type RegisteredTypes map[string]Resource -// DefaultTypes is a collection of the default config types -func DefaultTypes() RegisteredTypes { - return RegisteredTypes{ - "variable": &Variable{}, - "output": &Output{}, - "local": &Local{}, - "module": &Module{}, - "root": &Root{}, - } -} - // CreateResource creates a new instance of a resource from one of the registered types. func (r RegisteredTypes) CreateResource(resourceType, resourceName string) (Resource, error) { // check that the type exists diff --git a/types/variable.go b/types/variable.go deleted file mode 100644 index 6735263..0000000 --- a/types/variable.go +++ /dev/null @@ -1,10 +0,0 @@ -package types - -const TypeVariable = "variable" - -// Output defines an output variable which can be set by a module -type Variable struct { - ResourceBase `hcl:",remain"` - Default interface{} `hcl:"default" json:"default"` // default value for a variable - Description string `hcl:"description,optional" json:"description,omitempty"` // description of the variable -}