Skip to content

Commit

Permalink
fix: migrate AWS Cloudwatch alert channel to API v2 (#186)
Browse files Browse the repository at this point in the history
* refactor: Add name to cloudwatch schema
* refactor: Add missing files
* refactor: fix schema and reformat code
* refactor: fix tests and how environment variable is read
* refactor: add missing dependency
* refactor: Apply suggestions from code review
* refactor: address pr feedback
  • Loading branch information
vatasha authored Oct 5, 2021
1 parent 6d86c83 commit eeb55a7
Show file tree
Hide file tree
Showing 11 changed files with 180 additions and 245 deletions.
32 changes: 28 additions & 4 deletions examples/resource_lacework_alert_channel_aws_cloudwatch/main.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,31 @@
terraform {
required_providers {
lacework = {
source = "lacework/lacework"
}
}
}

provider "lacework" {}

resource "lacework_alert_channel_aws_cloudwatch" "example" {
name = "My AWS CloudWatch Alert Channel Example"
event_bus_arn = "arn:aws:events:us-west-2:1234567890:event-bus/default"
group_issues_by = "Resources"
variable "event_bus_arn" {
description = "The ARN for the event bus"
type = string
sensitive = true
}

variable "name" {
description = "The name of the alert channel"
type = string
}

resource "lacework_alert_channel_aws_cloudwatch" "example" {
name = var.name
event_bus_arn = var.event_bus_arn
group_issues_by = "Events"
// test_integration input is used in this example only for testing
// purposes, it help us avoid sending a "test" request to the
// system we are integrating to. In production, this should remain
// turned on ("true") which is the default setting
test_integration = false
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.7.1
github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce // indirect
github.com/lacework/go-sdk v0.16.0
github.com/lacework/go-sdk v0.16.1-0.20211001144102-1e93e91beb7a
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/pkg/errors v0.9.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+
github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
github.com/lacework/go-sdk v0.16.0 h1:y8Qfsb+4rxjzIcdBuYsntwel4YAgiLU+3O4zD+dFqto=
github.com/lacework/go-sdk v0.16.0/go.mod h1:GBMBmO5cRumszYQdUlHFcr6ZXliPO7y6g4aZ6rhdT3g=
github.com/lacework/go-sdk v0.16.1-0.20211001144102-1e93e91beb7a h1:SgukhBuKAhAD+77xSi5/ACXP/7JRTcSnyt3nBTJQdxA=
github.com/lacework/go-sdk v0.16.1-0.20211001144102-1e93e91beb7a/go.mod h1:GBMBmO5cRumszYQdUlHFcr6ZXliPO7y6g4aZ6rhdT3g=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
Expand Down
9 changes: 9 additions & 0 deletions integration/cloudwatch_event_bus_arn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package integration

import (
"os"
)

func cloudwatchEnvVarsDefault() string {
return os.Getenv("CLOUDWATCH_EVENT_BUS_ARN")
}
38 changes: 38 additions & 0 deletions integration/resource_lacework_alert_channel_aws_cloudwatch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package integration

import (
"testing"

"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)

// TestAlertChannelCloudWatchCreate applies integration terraform:
// => '../examples/resource_lacework_alert_channel_aws_cloudwatch'
//
// It uses the go-sdk to verify the created integration,
// applies an update with new alert channel name and destroys it
func TestAlertChannelCloudWatchCreate(t *testing.T) {
eventBusArn := cloudwatchEnvVarsDefault()
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../examples/resource_lacework_alert_channel_aws_cloudwatch",
Vars: map[string]interface{}{
"name": "AWS Cloudwatch Alert Channel Example",
},
EnvVars: map[string]string{
"TF_VAR_event_bus_arn": eventBusArn,
},
})
defer terraform.Destroy(t, terraformOptions)

// Create new CloudwatchEb Alert Channel
create := terraform.InitAndApply(t, terraformOptions)
assert.Equal(t, "AWS Cloudwatch Alert Channel Example", GetIntegrationName(create))

// Update CloudwatchEb Alert Channel
terraformOptions.Vars = map[string]interface{}{
"name": "AWS Cloudwatch Alert Channel Updated"}

update := terraform.Apply(t, terraformOptions)
assert.Equal(t, "AWS Cloudwatch Alert Channel Updated", GetIntegrationName(update))
}
152 changes: 51 additions & 101 deletions lacework/resource_lacework_alert_channel_aws_cloudwatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,20 @@ func resourceLaceworkAlertChannelAwsCloudWatch() *schema.Resource {

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"intg_guid": {
Type: schema.TypeString,
Computed: true,
Type: schema.TypeString,
Required: true,
Description: "The integration name",
},
"enabled": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "The state of the external integration",
},
"event_bus_arn": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Required: true,
Description: "The ARN of your AWS CloudWatch event bus",
},
"group_issues_by": {
Type: schema.TypeString,
Expand All @@ -54,13 +53,19 @@ func resourceLaceworkAlertChannelAwsCloudWatch() *schema.Resource {
}
}
},
Description: "Defines how Lacework compliance events get grouped. Must be one of Events or Resources. Defaults to Events",
},
"test_integration": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Whether to test the integration of an alert channel upon creation and modification",
},
"intg_guid": {
Type: schema.TypeString,
Computed: true,
Description: "The integration unique identifier",
},
"created_or_updated_time": {
Type: schema.TypeString,
Computed: true,
Expand All @@ -84,8 +89,9 @@ func resourceLaceworkAlertChannelAwsCloudWatch() *schema.Resource {
func resourceLaceworkAlertChannelAwsCloudWatchCreate(d *schema.ResourceData, meta interface{}) error {
var (
lacework = meta.(*api.Client)
alert = api.NewAwsCloudWatchAlertChannel(d.Get("name").(string),
api.AwsCloudWatchData{
alert = api.NewAlertChannel(d.Get("name").(string),
api.CloudwatchEbAlertChannelType,
api.CloudwatchEbDataV2{
EventBusArn: d.Get("event_bus_arn").(string),
IssueGrouping: d.Get("group_issues_by").(string),
},
Expand All @@ -96,77 +102,64 @@ func resourceLaceworkAlertChannelAwsCloudWatchCreate(d *schema.ResourceData, met
}

log.Printf("[INFO] Creating %s integration with data:\n%+v\n", api.AwsCloudWatchIntegration, alert)
response, err := lacework.Integrations.CreateAwsCloudWatchAlertChannel(alert)
if err != nil {
return err
}

log.Println("[INFO] Verifying server response data")
err = validateAwsCloudWatchAlertChannelResponse(&response)
response, err := lacework.V2.AlertChannels.Create(alert)
if err != nil {
return err
}

// @afiune at this point of time, we know the data field has a single value
integration := response.Data[0]
integration := response.Data
d.SetId(integration.IntgGuid)
d.Set("name", integration.Name)
d.Set("intg_guid", integration.IntgGuid)
d.Set("enabled", integration.Enabled == 1)
d.Set("created_or_updated_time", integration.CreatedOrUpdatedTime)
d.Set("created_or_updated_by", integration.CreatedOrUpdatedBy)
d.Set("type_name", integration.TypeName)
d.Set("type_name", integration.Type)
d.Set("org_level", integration.IsOrg == 1)

if d.Get("test_integration").(bool) {
log.Printf("[INFO] Testing %s integration for guid %s\n", api.AwsCloudWatchIntegration, d.Id())
log.Printf("[INFO] Testing %s integration for guid %s\n", api.CloudwatchEbAlertChannelType, d.Id())
if err := VerifyAlertChannelAndRollback(d.Id(), lacework); err != nil {
return err
}
log.Printf("[INFO] Tested %s integration with guid %s successfully\n", api.AwsCloudWatchIntegration, d.Id())
log.Printf("[INFO] Tested %s integration with guid %s successfully\n", api.CloudwatchEbAlertChannelType, d.Id())
}

log.Printf("[INFO] Created %s integration with guid %s\n", api.AwsCloudWatchIntegration, integration.IntgGuid)
log.Printf("[INFO] Created %s integration with guid %s\n", api.CloudwatchEbAlertChannelType, integration.IntgGuid)
return nil
}

func resourceLaceworkAlertChannelAwsCloudWatchRead(d *schema.ResourceData, meta interface{}) error {
lacework := meta.(*api.Client)

log.Printf("[INFO] Reading %s integration with guid %s\n", api.AwsCloudWatchIntegration, d.Id())
response, err := lacework.Integrations.GetAwsCloudWatchAlertChannel(d.Id())
log.Printf("[INFO] Reading %s integration with guid %s\n", api.CloudwatchEbAlertChannelType, d.Id())
response, err := lacework.V2.AlertChannels.GetCloudwatchEb(d.Id())
if err != nil {
return err
}

for _, integration := range response.Data {
if integration.IntgGuid == d.Id() {
d.Set("name", integration.Name)
d.Set("intg_guid", integration.IntgGuid)
d.Set("enabled", integration.Enabled == 1)
d.Set("created_or_updated_time", integration.CreatedOrUpdatedTime)
d.Set("created_or_updated_by", integration.CreatedOrUpdatedBy)
d.Set("type_name", integration.TypeName)
d.Set("org_level", integration.IsOrg == 1)
d.Set("name", response.Data.Name)
d.Set("intg_guid", response.Data.IntgGuid)
d.Set("enabled", response.Data.Enabled == 1)
d.Set("created_or_updated_time", response.Data.CreatedOrUpdatedTime)
d.Set("created_or_updated_by", response.Data.CreatedOrUpdatedBy)
d.Set("type_name", response.Data.Type)
d.Set("org_level", response.Data.IsOrg == 1)

d.Set("event_bus_arn", integration.Data.EventBusArn)
d.Set("group_issues_by", integration.Data.IssueGrouping)
d.Set("event_bus_arn", response.Data.Data.EventBusArn)
d.Set("group_issues_by", response.Data.Data.IssueGrouping)

log.Printf("[INFO] Read %s integration with guid %s\n",
api.AwsCloudWatchIntegration, integration.IntgGuid)
return nil
}
}

d.SetId("")
log.Printf("[INFO] Read %s integration with guid %s\n",
api.CloudwatchEbAlertChannelType, response.Data.IntgGuid)
return nil
}

func resourceLaceworkAlertChannelAwsCloudWatchUpdate(d *schema.ResourceData, meta interface{}) error {
var (
lacework = meta.(*api.Client)
alert = api.NewAwsCloudWatchAlertChannel(d.Get("name").(string),
api.AwsCloudWatchData{
alert = api.NewAlertChannel(d.Get("name").(string),
api.CloudwatchEbAlertChannelType,
api.CloudwatchEbDataV2{
EventBusArn: d.Get("event_bus_arn").(string),
IssueGrouping: d.Get("group_issues_by").(string),
},
Expand All @@ -179,85 +172,42 @@ func resourceLaceworkAlertChannelAwsCloudWatchUpdate(d *schema.ResourceData, met

alert.IntgGuid = d.Id()

log.Printf("[INFO] Updating %s integration with data:\n%+v\n", api.AwsCloudWatchIntegration, alert)
response, err := lacework.Integrations.UpdateAwsCloudWatchAlertChannel(alert)
log.Printf("[INFO] Updating %s integration with data:\n%+v\n", api.CloudwatchEbAlertChannelType, alert)
response, err := lacework.V2.AlertChannels.UpdateCloudwatchEb(alert)
if err != nil {
return err
}

log.Println("[INFO] Verifying server response data")
err = validateAwsCloudWatchAlertChannelResponse(&response)
if err != nil {
return err
}

// @afiune at this point of time, we know the data field has a single value
integration := response.Data[0]
integration := response.Data
d.Set("name", integration.Name)
d.Set("intg_guid", integration.IntgGuid)
d.Set("enabled", integration.Enabled == 1)
d.Set("created_or_updated_time", integration.CreatedOrUpdatedTime)
d.Set("created_or_updated_by", integration.CreatedOrUpdatedBy)
d.Set("type_name", integration.TypeName)
d.Set("type_name", integration.Type)
d.Set("org_level", integration.IsOrg == 1)

if d.Get("test_integration").(bool) {
log.Printf("[INFO] Testing %s integration for guid %s\n", api.AwsCloudWatchIntegration, d.Id())
log.Printf("[INFO] Testing %s integration for guid %s\n", api.CloudwatchEbAlertChannelType, d.Id())
if err := lacework.V2.AlertChannels.Test(d.Id()); err != nil {
return err
}
log.Printf("[INFO] Tested %s integration with guid %s successfully\n", api.AwsCloudWatchIntegration, d.Id())
log.Printf("[INFO] Tested %s integration with guid %s successfully\n", api.CloudwatchEbAlertChannelType, d.Id())
}

log.Printf("[INFO] Updated %s integration with guid %s\n", api.AwsCloudWatchIntegration, d.Id())
log.Printf("[INFO] Updated %s integration with guid %s\n", api.CloudwatchEbAlertChannelType, d.Id())
return nil
}

func resourceLaceworkAlertChannelAwsCloudWatchDelete(d *schema.ResourceData, meta interface{}) error {
lacework := meta.(*api.Client)

log.Printf("[INFO] Deleting %s integration with guid %s\n", api.AwsCloudWatchIntegration, d.Id())
_, err := lacework.Integrations.Delete(d.Id())
log.Printf("[INFO] Deleting %s integration with guid %s\n", api.CloudwatchEbAlertChannelType, d.Id())
err := lacework.V2.AlertChannels.Delete(d.Id())
if err != nil {
return err
}

log.Printf("[INFO] Deleted %s integration with guid %s\n", api.AwsCloudWatchIntegration, d.Id())
return nil
}

// validateAwsCloudWatchAlertChannelResponse checks weather or not the server response has
// any inconsistent data, it returns a friendly error message describing the
// problem and how to report it
func validateAwsCloudWatchAlertChannelResponse(response *api.AwsCloudWatchResponse) error {
if len(response.Data) == 0 {
// @afiune this edge case should never happen, if we land here it means that
// something went wrong in the server side of things (Lacework API), so let
// us inform that to our users
msg := `
Unable to read sever response data. (empty 'data' field)
This was an unexpected behavior, verify that your integration has been
created successfully and report this issue to support@lacework.net
`
return fmt.Errorf(msg)
}

if len(response.Data) > 1 {
// @afiune if we are creating a single integration and the server returns
// more than one integration inside the 'data' field, it is definitely another
// edge case that should never happen
msg := `
There is more that one integration inside the server response data.
List of integrations:
`
for _, integration := range response.Data {
msg = msg + fmt.Sprintf("\t%s: %s\n", integration.IntgGuid, integration.Name)
}
msg = msg + unexpectedBehaviorMsg()
return fmt.Errorf(msg)
}

log.Printf("[INFO] Deleted %s integration with guid %s\n", api.CloudwatchEbAlertChannelType, d.Id())
return nil
}
Loading

0 comments on commit eeb55a7

Please sign in to comment.