Skip to content

Commit

Permalink
feat(GROW-2876): support terraform output blocks in lwgenerate (#1609)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Matt Cadorette committed Apr 9, 2024
1 parent 8271a9f commit 5440c57
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 3 deletions.
22 changes: 21 additions & 1 deletion lwgenerate/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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(
Expand All @@ -758,7 +777,8 @@ func (args *GenerateAwsTfConfigurationArgs) Generate() (string, error) {
laceworkProvider,
configModule,
cloudTrailModule,
agentlessModule),
agentlessModule,
outputBlocks),
)
return hclBlocks, nil
}
Expand Down
21 changes: 19 additions & 2 deletions lwgenerate/aws/aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"testing"

"github.com/lacework/go-sdk/lwgenerate"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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()))
}

Expand Down Expand Up @@ -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 = {
Expand Down
42 changes: 42 additions & 0 deletions lwgenerate/hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions lwgenerate/hcl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down

0 comments on commit 5440c57

Please sign in to comment.