Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support embedding pointer of struct in terra stack #13

Merged
merged 1 commit into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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