From 5440c57d49156c11cbf1b87f9474d3e5b7134f50 Mon Sep 17 00:00:00 2001 From: Matt Cadorette Date: Tue, 9 Apr 2024 13:31:07 -0400 Subject: [PATCH] feat(GROW-2876): support terraform output blocks in lwgenerate (#1609) * feat(GROW-2876): support terraform output blocks in lwgenerate * Add generic functionality for hcl output blocks * Add hooks to AWS to add outputs Outputs are arbitrary, meaning the author needs to understand the resultant traversal (and that it is valid) otherwise the resultant code will be unusable. * chore: fix comment --- lwgenerate/aws/aws.go | 22 +++++++++++++++++++- lwgenerate/aws/aws_test.go | 21 +++++++++++++++++-- lwgenerate/hcl.go | 42 ++++++++++++++++++++++++++++++++++++++ lwgenerate/hcl_test.go | 17 +++++++++++++++ 4 files changed, 99 insertions(+), 3 deletions(-) diff --git a/lwgenerate/aws/aws.go b/lwgenerate/aws/aws.go index 99b89ef8e..3b4e51185 100644 --- a/lwgenerate/aws/aws.go +++ b/lwgenerate/aws/aws.go @@ -207,6 +207,9 @@ type GenerateAwsTfConfigurationArgs struct { // Config resource prefix ConfigOrgCfResourcePrefix string + // Custom outputs + CustomOutputs []lwgenerate.HclOutput + // Supply an AWS region for where to find the cloudtrail resources // TODO @ipcrm future: support split regions for resources (s3 one place, sns another, etc) AwsRegion string @@ -538,6 +541,13 @@ func WithConfigOrgUnits(orgUnits []string) AwsTerraformModifier { } } +// WithConfigOutputs Set Custom Terraform Outputs +func WithCustomOutputs(outputs []lwgenerate.HclOutput) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.CustomOutputs = outputs + } +} + // WithConfigOrgCfResourcePrefix Set Config org resource prefix func WithConfigOrgCfResourcePrefix(resourcePrefix string) AwsTerraformModifier { return func(c *GenerateAwsTfConfigurationArgs) { @@ -750,6 +760,15 @@ func (args *GenerateAwsTfConfigurationArgs) Generate() (string, error) { return "", errors.Wrap(err, "failed to generate aws agentless global module") } + outputBlocks := []*hclwrite.Block{} + for _, output := range args.CustomOutputs { + outputBlock, err := output.ToBlock() + if err != nil { + return "", errors.Wrap(err, "failed to add custom output") + } + outputBlocks = append(outputBlocks, outputBlock) + } + // Render hclBlocks := lwgenerate.CreateHclStringOutput( lwgenerate.CombineHclBlocks( @@ -758,7 +777,8 @@ func (args *GenerateAwsTfConfigurationArgs) Generate() (string, error) { laceworkProvider, configModule, cloudTrailModule, - agentlessModule), + agentlessModule, + outputBlocks), ) return hclBlocks, nil } diff --git a/lwgenerate/aws/aws_test.go b/lwgenerate/aws/aws_test.go index ea424a07c..a8ca91cd1 100644 --- a/lwgenerate/aws/aws_test.go +++ b/lwgenerate/aws/aws_test.go @@ -4,6 +4,7 @@ import ( "fmt" "testing" + "github.com/lacework/go-sdk/lwgenerate" "github.com/stretchr/testify/assert" ) @@ -77,6 +78,17 @@ func TestGenerationConfig(t *testing.T) { assert.Equal(t, reqProviderAndRegion(moduleImportConfig), hcl) } +func TestGenerationConfigWithOutputs(t *testing.T) { + hcl, err := NewTerraform( + false, false, true, false, WithAwsRegion("us-east-2"), + WithCustomOutputs([]lwgenerate.HclOutput{ + *lwgenerate.NewOutput("test", []string{"module", "aws_config", "lacework_integration_guid"}, "test description"), + })).Generate() + assert.Nil(t, err) + assert.NotNil(t, hcl) + assert.Equal(t, reqProviderAndRegion(moduleImportConfig)+"\n"+customOutput, hcl) +} + func TestGenerationConfigWithMultipleAccounts(t *testing.T) { hcl, err := NewTerraform(false, false, true, false, WithAwsProfile("main"), @@ -135,8 +147,7 @@ func TestGenerationCloudtrailConsolidated(t *testing.T) { ConsolidatedCloudtrail: true, }) assert.Nil(t, err) - assert.Equal(t, - "consolidated_trail=true\n", + assert.Equal(t, "consolidated_trail=true\n", string(data.Body().GetAttribute("consolidated_trail").BuildTokens(nil).Bytes())) } @@ -674,6 +685,12 @@ var moduleImportConfig = `module "aws_config" { } ` +var customOutput = `output "test" { + description = "test description" + value = module.aws_config.lacework_integration_guid +} +` + var moduleImportConfigWithMultipleAccounts = `terraform { required_providers { lacework = { diff --git a/lwgenerate/hcl.go b/lwgenerate/hcl.go index 58a7663c4..cf3c52a48 100644 --- a/lwgenerate/hcl.go +++ b/lwgenerate/hcl.go @@ -108,6 +108,48 @@ type ForEach struct { value map[string]string } +type HclOutput struct { + // required, name of the resultant output + name string + + // required, converted into a traversal + // e.g. []string{"a", "b", "c"} as input results in traversal having value a.b.c + value []string + + // optional + description string +} + +func (m *HclOutput) ToBlock() (*hclwrite.Block, error) { + if m.value == nil { + return nil, errors.New("value must be supplied") + } + + attributes := map[string]interface{}{ + "value": CreateSimpleTraversal(m.value), + } + + if m.description != "" { + attributes["description"] = m.description + } + + block, err := HclCreateGenericBlock( + "output", + []string{m.name}, + attributes, + ) + if err != nil { + return nil, err + } + + return block, nil +} + +// NewOutput Create a provider statement in the HCL output +func NewOutput(name string, value []string, description string) *HclOutput { + return &HclOutput{name: name, description: description, value: value} +} + type HclModule struct { // Required, module name name string diff --git a/lwgenerate/hcl_test.go b/lwgenerate/hcl_test.go index 91611cd59..9c360e4b0 100644 --- a/lwgenerate/hcl_test.go +++ b/lwgenerate/hcl_test.go @@ -175,6 +175,23 @@ func TestModuleBlockWithComplexAttributes(t *testing.T) { assert.NoError(t, err) } +func TestOutputBlockCreation(t *testing.T) { + t.Run("should generate correct block for simple output with no description", func(t *testing.T) { + o := lwgenerate.NewOutput("test", []string{"test", "one", "two"}, "") + b, err := o.ToBlock() + assert.NoError(t, err) + str := lwgenerate.CreateHclStringOutput([]*hclwrite.Block{b}) + assert.Equal(t, "output \"test\" {\n value = test.one.two\n}\n", str) + }) + t.Run("should generate correct block for simple output with description", func(t *testing.T) { + o := lwgenerate.NewOutput("test", []string{"test", "one", "two"}, "test description") + b, err := o.ToBlock() + assert.NoError(t, err) + str := lwgenerate.CreateHclStringOutput([]*hclwrite.Block{b}) + assert.Equal(t, "output \"test\" {\n description = \"test description\"\n value = test.one.two\n}\n", str) + }) +} + var testRequiredProvider = `terraform { required_providers { bar = {