Skip to content

Commit

Permalink
support embedding pointer of struct in terra stack
Browse files Browse the repository at this point in the history
  • Loading branch information
jlarfors committed Apr 16, 2024
1 parent 0bf5a7f commit 8610c01
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 29 deletions.
73 changes: 44 additions & 29 deletions pkg/terra/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,38 @@ 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{}
// Dependencies returns the list of resources that this resource depends_on
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
Configuration() 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
Expand All @@ -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
Expand All @@ -71,31 +76,36 @@ 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 {
return false, err
}
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
Expand Down Expand Up @@ -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()
}
Expand All @@ -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(
Expand All @@ -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
}
Expand All @@ -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())
}
Expand All @@ -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,
Expand Down
14 changes: 14 additions & 0 deletions pkg/terra/blocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{},
Expand Down

0 comments on commit 8610c01

Please sign in to comment.