From 894814b93ce81ea14d59e4ea65022372a83bc94c Mon Sep 17 00:00:00 2001 From: ipcrm Date: Mon, 8 Apr 2024 20:32:40 +0000 Subject: [PATCH 1/2] 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. --- 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..995e7fd7e 100644 --- a/lwgenerate/aws/aws.go +++ b/lwgenerate/aws/aws.go @@ -207,6 +207,9 @@ type GenerateAwsTfConfigurationArgs struct { // Config resource prefix ConfigOrgCfResourcePrefix string + // Config 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 = { From e9f2b967cc4ecb816ca34818baa589d970e3a7c2 Mon Sep 17 00:00:00 2001 From: ipcrm Date: Tue, 9 Apr 2024 12:55:20 +0000 Subject: [PATCH 2/2] chore: fix comment --- lwgenerate/aws/aws.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lwgenerate/aws/aws.go b/lwgenerate/aws/aws.go index 995e7fd7e..3b4e51185 100644 --- a/lwgenerate/aws/aws.go +++ b/lwgenerate/aws/aws.go @@ -207,7 +207,7 @@ type GenerateAwsTfConfigurationArgs struct { // Config resource prefix ConfigOrgCfResourcePrefix string - // Config custom outputs + // Custom outputs CustomOutputs []lwgenerate.HclOutput // Supply an AWS region for where to find the cloudtrail resources