diff --git a/pkg/terra/blocks.go b/pkg/terra/blocks.go index 8c06deb..0fae4c9 100644 --- a/pkg/terra/blocks.go +++ b/pkg/terra/blocks.go @@ -16,12 +16,13 @@ import ( ) // Resource represents a Terraform Resource. -// The generated Go structs from a Terraform provider resource will implement this interface -// and be used when exported to Terraform Configuration +// The generated Go structs from a Terraform provider resource will implement +// this interface and be used when exported to Terraform Configuration type Resource interface { // Type returns the resource type, e.g. aws_iam_role Type() string - // LocalName returns the unique name of the resource as it will be stored in the state + // LocalName returns the unique name of the resource as it will be stored in + // the state LocalName() string // Configuration returns the arguments for the resource Configuration() interface{} @@ -29,13 +30,15 @@ type Resource interface { Dependencies() Dependencies // LifecycleManagement returns the lifecycle configuration for this resource LifecycleManagement() *Lifecycle - // ImportState takes the given attributes value map (from a Terraform state) and imports it + // ImportState takes the given attributes value map (from a Terraform state) + // and imports it // into this resource ImportState(attributes io.Reader) error } // DataSource represents a Terraform DataSource. -// The generated Go structs from a Terraform provider data resource will implement this interface +// The generated Go structs from a Terraform provider data resource will +// implement this interface. type DataSource interface { DataSource() string LocalName() string @@ -43,7 +46,8 @@ type DataSource interface { } // Provider represents a Terraform Provider. -// The generated Go structs from a Terraform provider configuration will implement this interface +// The generated Go structs from a Terraform provider configuration will +// implement this interface. type Provider interface { LocalName() string Source() string @@ -52,12 +56,13 @@ type Provider interface { } // Backend represents a Terraform Backend. -// Users will define their backends to implement this interface +// Users will define their backends to implement this interface. type Backend interface { BackendType() string } -// stackObjects contains all the blocks that are extracted from a user-defined stack +// stackObjects contains all the blocks that are extracted from a user-defined +// stack. type stackObjects struct { Backend Backend Providers []Provider @@ -71,16 +76,20 @@ const ( var ( ErrNoBackendBlock = errors.New("stack must have a backend block") - ErrMultipleBackendBlocks = errors.New("stack cannot have multiple backend blocks") - ErrNoProviderBlock = errors.New("stack must have a provider block") - ErrNotExportedField = errors.New("stack has non-exported (private) field") - ErrUnknownPublicField = errors.New("unknown public field") + ErrMultipleBackendBlocks = errors.New( + "stack cannot have multiple backend blocks", + ) + ErrNoProviderBlock = errors.New("stack must have a provider block") + ErrNotExportedField = errors.New( + "stack has non-exported (private) field", + ) + ErrUnknownPublicField = errors.New("unknown public field") ) // StackImportState imports the Terraform state into the Terraform Stack. -// A bool is returned indicating whether all the resources have state. If the bool is true, -// every resource in the stack will have some state. If the bool is false, the state is -// incomplete meaning some resources may have state. +// A bool is returned indicating whether all the resources have state. If the +// bool is true, every resource in the stack will have some state. If the bool +// is false, the state is incomplete meaning some resources may have state. func StackImportState(stack Exporter, tfState *tfjson.State) (bool, error) { sb, err := objectsFromStack(stack) if err != nil { @@ -88,14 +97,15 @@ func StackImportState(stack Exporter, tfState *tfjson.State) (bool, error) { } isFullState := true stateResources := tfState.Values.RootModule.Resources - // Iterate over the resources in the Stack and try to find the corresponding resource in the state. + // Iterate over the resources in the Stack and try to find the corresponding + // resource in the state. // If it exists, import the state into the Stack. for _, res := range sb.Resources { resFound := false for _, sr := range stateResources { - // Find the resource in the state. It is the same resource if the resource type - // and resource local name match because that is how Terraform uniquely identifies - // resources in its state. + // Find the resource in the state. It is the same resource if the + // resource type and resource local name match because that is how + // Terraform uniquely identifies resources in its state. if res.Type() == sr.Type && res.LocalName() == sr.Name { resFound = true var b bytes.Buffer @@ -127,7 +137,7 @@ func objectsFromStack(stack Exporter) (*stackObjects, error) { } rv := reflect.ValueOf(stack) - // Exporter is a pointer so expect a pointer + // Exporter is a pointer so expect a pointer. if rv.Kind() == reflect.Ptr { rv = rv.Elem() } @@ -139,13 +149,13 @@ func objectsFromStack(stack Exporter) (*stackObjects, error) { return &sb, nil } -// parseStackStructFields takes a struct reflect.Value and appends any Terraform objects that it -// finds to the provider stackObjects +// parseStackStructFields takes a struct reflect.Value and appends any Terraform +// objects that it finds to the provider stackObjects. func parseStackStructFields(rv reflect.Value, sb *stackObjects) error { for i := 0; i < rv.NumField(); i++ { sf := rv.Type().Field(i) fv := rv.Field(i) - // Handle embedded (i.e. anonymous) structs + // Handle embedded (i.e. anonymous) structs. if sf.Anonymous { if !sf.IsExported() { return fmt.Errorf( @@ -156,16 +166,20 @@ func parseStackStructFields(rv reflect.Value, sb *stackObjects) error { sf.Type, ) } - // Proceed and parse the embedded struct + // Handle embedded structs as pointers. + for fv.Kind() == reflect.Ptr { + fv = fv.Elem() + } + // Proceed and parse the embedded struct. if err := parseStackStructFields(fv, sb); err != nil { return err } continue } - // Check if field has a terriyaki struct tag + // Check if field has a terriyaki struct tag. if tkiTag, ok := sf.Tag.Lookup(tagLingon); ok { - // Ignore fields with the "-" value + // Ignore fields with the "-" value. if tkiTag == "-" { continue } @@ -181,11 +195,11 @@ func parseStackStructFields(rv reflect.Value, sb *stackObjects) error { } tkiObjects := make([]interface{}, 0) - // Handle slices and arrays + // Handle slices and arrays. switch fv.Kind() { case reflect.Array, reflect.Slice: // If it's an array or slice, iterate over the elements and process - // them individually + // them individually. for j := 0; j < fv.Len(); j++ { tkiObjects = append(tkiObjects, fv.Index(j).Interface()) } @@ -206,7 +220,8 @@ func parseStackStructFields(rv reflect.Value, sb *stackObjects) error { } sb.Backend = v default: - // Not sure if this should be an error, but rather be explicit at this point + // Not sure if this should be an error, but rather be explicit + // at this point. return fmt.Errorf( "%w: %s, type: %s", ErrUnknownPublicField, diff --git a/pkg/terra/blocks_test.go b/pkg/terra/blocks_test.go index 21fe5a3..40fcc5a 100644 --- a/pkg/terra/blocks_test.go +++ b/pkg/terra/blocks_test.go @@ -85,6 +85,20 @@ func TestExtractBlocks_PrivateField(t *testing.T) { tu.ErrorIs(t, err, ErrNotExportedField) } +func TestExtractBlocks_EmbedPointer(t *testing.T) { + type Composition struct{} + type simpleStack struct { + DummyStack + *Composition + } + st := simpleStack{ + DummyStack: newDummyBaseStack(), + Composition: &Composition{}, + } + _, err := objectsFromStack(&st) + tu.AssertNoError(t, err) +} + func newDummyBaseStack() DummyStack { return DummyStack{ Backend: &dummyBackend{},