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

VPC support for Grafana workspaces #28308

Merged
merged 6 commits into from
Dec 13, 2022
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
3 changes: 3 additions & 0 deletions .changelog/28308.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_grafana_workspace: Add `vpc_configuration` argument.
```
66 changes: 66 additions & 0 deletions internal/service/grafana/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,28 @@ func ResourceWorkspace() *schema.Resource {
},
"tags": tftags.TagsSchema(),
"tags_all": tftags.TagsSchemaComputed(),
"vpc_configuration": {
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"security_group_ids": {
Type: schema.TypeSet,
Required: true,
MaxItems: 100,
Elem: &schema.Schema{Type: schema.TypeString},
},
"subnet_ids": {
Type: schema.TypeSet,
Required: true,
MinItems: 2,
MaxItems: 100,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
},
},

CustomizeDiff: verify.SetTagsDiff,
Expand Down Expand Up @@ -167,6 +189,10 @@ func resourceWorkspaceCreate(d *schema.ResourceData, meta interface{}) error {
input.WorkspaceRoleArn = aws.String(v.(string))
}

if v, ok := d.GetOk("vpc_configuration"); ok {
input.VpcConfiguration = expandVPCConfiguration(v.([]interface{}))
}

log.Printf("[DEBUG] Creating Grafana Workspace: %s", input)
output, err := conn.CreateWorkspace(input)

Expand Down Expand Up @@ -224,6 +250,10 @@ func resourceWorkspaceRead(d *schema.ResourceData, meta interface{}) error {
d.Set("saml_configuration_status", workspace.Authentication.SamlConfigurationStatus)
d.Set("stack_set_name", workspace.StackSetName)

if err := d.Set("vpc_configuration", flattenVPCConfiguration(workspace.VpcConfiguration)); err != nil {
return fmt.Errorf("error setting vpc_configuration: %w", err)
}

tags := KeyValueTags(workspace.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

//lintignore:AWSR002
Expand Down Expand Up @@ -330,3 +360,39 @@ func resourceWorkspaceDelete(d *schema.ResourceData, meta interface{}) error {

return nil
}

func expandVPCConfiguration(cfg []interface{}) *managedgrafana.VpcConfiguration {
if len(cfg) < 1 {
return nil
}

conf := cfg[0].(map[string]interface{})

out := managedgrafana.VpcConfiguration{}

if v, ok := conf["security_group_ids"].(*schema.Set); ok && v.Len() > 0 {
out.SecurityGroupIds = flex.ExpandStringSet(v)
}

if v, ok := conf["subnet_ids"].(*schema.Set); ok && v.Len() > 0 {
out.SubnetIds = flex.ExpandStringSet(v)
}

return &out
}

func flattenVPCConfiguration(rs *managedgrafana.VpcConfiguration) []interface{} {
if rs == nil {
return []interface{}{}
}

m := make(map[string]interface{})
if rs.SecurityGroupIds != nil {
m["security_group_ids"] = flex.FlattenStringSet(rs.SecurityGroupIds)
}
if rs.SubnetIds != nil {
m["subnet_ids"] = flex.FlattenStringSet(rs.SubnetIds)
}

return []interface{}{m}
}
141 changes: 96 additions & 45 deletions internal/service/grafana/workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func TestAccGrafana_serial(t *testing.T) {
"permissionType": testAccWorkspace_permissionType,
"notificationDestinations": testAccWorkspace_notificationDestinations,
"tags": testAccWorkspace_tags,
"vpc": testAccWorkspace_vpc,
},
"ApiKey": {
"basic": testAccWorkspaceAPIKey_basic,
Expand Down Expand Up @@ -96,6 +97,35 @@ func testAccWorkspace_saml(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "saml_configuration_status", managedgrafana.SamlConfigurationStatusNotConfigured),
resource.TestCheckResourceAttr(resourceName, "stack_set_name", ""),
resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
resource.TestCheckResourceAttr(resourceName, "vpc_configuration.#", "0"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
Comment on lines +103 to +107
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👋 New to provider code, hoping you could answer some questions for clarity. What does this do? I've been seeing it in tests and occasionally they have another test step after but as well. Also why if there's another step, is it always after this block?

},
})
}

func testAccWorkspace_vpc(t *testing.T) {
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_grafana_workspace.test"

resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(managedgrafana.EndpointsID, t) },
ErrorCheck: acctest.ErrorCheck(t, managedgrafana.EndpointsID),
CheckDestroy: testAccCheckWorkspaceDestroy,
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccWorkspaceConfig_vpc(rName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckWorkspaceExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "vpc_configuration.#", "1"),
resource.TestCheckResourceAttr(resourceName, "vpc_configuration.0.security_group_ids.#", "1"),
resource.TestCheckResourceAttr(resourceName, "vpc_configuration.0.subnet_ids.#", "2"),
),
},
{
Expand Down Expand Up @@ -371,7 +401,49 @@ func testAccWorkspace_notificationDestinations(t *testing.T) {
})
}

func testAccWorkspaceRole(rName string) string {
func testAccCheckWorkspaceExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No Grafana Workspace ID is set")
}

conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaConn

_, err := tfgrafana.FindWorkspaceByID(conn, rs.Primary.ID)

return err
}
}

func testAccCheckWorkspaceDestroy(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaConn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_grafana_workspace" {
continue
}

_, err := tfgrafana.FindWorkspaceByID(conn, rs.Primary.ID)

if tfresource.NotFound(err) {
continue
}

if err != nil {
return err
}

return fmt.Errorf("Grafana Workspace %s still exists", rs.Primary.ID)
}
return nil
}

func testAccWorkspaceConfig_base(rName string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "test" {
name = %[1]q
Expand All @@ -394,7 +466,7 @@ resource "aws_iam_role" "test" {
}

func testAccWorkspaceConfig_authenticationProvider(rName, authenticationProvider string) string {
return acctest.ConfigCompose(testAccWorkspaceRole(rName), fmt.Sprintf(`
return acctest.ConfigCompose(testAccWorkspaceConfig_base(rName), fmt.Sprintf(`
resource "aws_grafana_workspace" "test" {
account_access_type = "CURRENT_ACCOUNT"
authentication_providers = [%[1]q]
Expand All @@ -405,7 +477,7 @@ resource "aws_grafana_workspace" "test" {
}

func testAccWorkspaceConfig_organization(rName string) string {
return acctest.ConfigCompose(testAccWorkspaceRole(rName), fmt.Sprintf(`
return acctest.ConfigCompose(testAccWorkspaceConfig_base(rName), fmt.Sprintf(`
resource "aws_grafana_workspace" "test" {
account_access_type = "ORGANIZATION"
authentication_providers = ["SAML"]
Expand All @@ -424,7 +496,7 @@ resource "aws_organizations_organizational_unit" "test" {
}

func testAccWorkspaceConfig_tags1(rName, tagKey1, tagValue1 string) string {
return acctest.ConfigCompose(testAccWorkspaceRole(rName), fmt.Sprintf(`
return acctest.ConfigCompose(testAccWorkspaceConfig_base(rName), fmt.Sprintf(`
resource "aws_grafana_workspace" "test" {
account_access_type = "CURRENT_ACCOUNT"
authentication_providers = ["SAML"]
Expand All @@ -441,7 +513,7 @@ resource "aws_grafana_workspace" "test" {
}

func testAccWorkspaceConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string {
return acctest.ConfigCompose(testAccWorkspaceRole(rName), fmt.Sprintf(`
return acctest.ConfigCompose(testAccWorkspaceConfig_base(rName), fmt.Sprintf(`
resource "aws_grafana_workspace" "test" {
account_access_type = "CURRENT_ACCOUNT"
authentication_providers = ["SAML"]
Expand All @@ -459,7 +531,7 @@ resource "aws_grafana_workspace" "test" {
}

func testAccWorkspaceConfig_dataSources(rName string) string {
return acctest.ConfigCompose(testAccWorkspaceRole(rName), fmt.Sprintf(`
return acctest.ConfigCompose(testAccWorkspaceConfig_base(rName), fmt.Sprintf(`
resource "aws_grafana_workspace" "test" {
account_access_type = "CURRENT_ACCOUNT"
authentication_providers = ["SAML"]
Expand All @@ -473,7 +545,7 @@ resource "aws_grafana_workspace" "test" {
}

func testAccWorkspaceConfig_permissionType(rName, permissionType string) string {
return acctest.ConfigCompose(testAccWorkspaceRole(rName), fmt.Sprintf(`
return acctest.ConfigCompose(testAccWorkspaceConfig_base(rName), fmt.Sprintf(`
resource "aws_grafana_workspace" "test" {
account_access_type = "CURRENT_ACCOUNT"
authentication_providers = ["SAML"]
Expand All @@ -484,7 +556,7 @@ resource "aws_grafana_workspace" "test" {
}

func testAccWorkspaceConfig_notificationDestinations(rName string) string {
return acctest.ConfigCompose(testAccWorkspaceRole(rName), fmt.Sprintf(`
return acctest.ConfigCompose(testAccWorkspaceConfig_base(rName), fmt.Sprintf(`
resource "aws_grafana_workspace" "test" {
account_access_type = "CURRENT_ACCOUNT"
authentication_providers = ["SAML"]
Expand All @@ -497,44 +569,23 @@ resource "aws_grafana_workspace" "test" {
`, rName))
}

func testAccCheckWorkspaceExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No Grafana Workspace ID is set")
}

conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaConn

_, err := tfgrafana.FindWorkspaceByID(conn, rs.Primary.ID)

return err
}
func testAccWorkspaceConfig_vpc(rName string) string {
return acctest.ConfigCompose(testAccWorkspaceConfig_base(rName), acctest.ConfigVPCWithSubnets(rName, 2), fmt.Sprintf(`
resource "aws_security_group" "test" {
description = %[1]q
vpc_id = aws_vpc.test.id
}

func testAccCheckWorkspaceDestroy(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaConn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_grafana_workspace" {
continue
}

_, err := tfgrafana.FindWorkspaceByID(conn, rs.Primary.ID)

if tfresource.NotFound(err) {
continue
}

if err != nil {
return err
}
resource "aws_grafana_workspace" "test" {
account_access_type = "CURRENT_ACCOUNT"
authentication_providers = ["SAML"]
permission_type = "SERVICE_MANAGED"
role_arn = aws_iam_role.test.arn

return fmt.Errorf("Grafana Workspace %s still exists", rs.Primary.ID)
}
return nil
vpc_configuration {
subnet_ids = aws_subnet.test[*].id
security_group_ids = [aws_security_group.test.id]
}
}
`, rName))
}
6 changes: 6 additions & 0 deletions website/docs/r/grafana_workspace.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ The following arguments are optional:
* `role_arn` - (Optional) The IAM role ARN that the workspace assumes.
* `stack_set_name` - (Optional) The AWS CloudFormation stack set name that provisions IAM roles to be used by the workspace.
* `tags` - (Optional) Key-value mapping of resource tags. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.
* `vpc_configuration` - (Optional) The configuration settings for an Amazon VPC that contains data sources for your Grafana workspace to connect to. See [VPC Configuration](#vpc-configuration) below.

### VPC Configuration

* `security_group_ids` - (Required) - The list of Amazon EC2 security group IDs attached to the Amazon VPC for your Grafana workspace to connect.
* `subnet_ids` - (Required) - The list of Amazon EC2 subnet IDs created in the Amazon VPC for your Grafana workspace to connect.

## Attributes Reference

Expand Down