From ecb4b5aada4a1df5553a79a2efb28109c9126b7f Mon Sep 17 00:00:00 2001 From: Jan Schumann Date: Thu, 21 Jul 2016 01:29:33 +0200 Subject: [PATCH] providers/aws: Opsworks permission resource (#6304) * add opsworks permission resource * add docs * remove permission from state if the permission object could not be found * remove nil validate function. validation is done in schema.Resource. * add id to the list of exported values * renge over permission to check that we have found got the correct one * removed comment * removed set id * fix unknown region us-east-1c * add user_profile resource * add docs * add default value --- builtin/providers/aws/provider.go | 2 + .../aws/resource_aws_opsworks_permission.go | 155 ++++++++++++++++++ .../resource_aws_opsworks_permission_test.go | 41 +++++ .../aws/resource_aws_opsworks_stack_test.go | 6 +- .../aws/resource_aws_opsworks_user_profile.go | 136 +++++++++++++++ ...resource_aws_opsworks_user_profile_test.go | 37 +++++ .../aws/r/opsworks_permission.html.markdown | 39 +++++ .../aws/r/opsworks_user_profile.html.markdown | 35 ++++ 8 files changed, 448 insertions(+), 3 deletions(-) create mode 100644 builtin/providers/aws/resource_aws_opsworks_permission.go create mode 100644 builtin/providers/aws/resource_aws_opsworks_permission_test.go create mode 100644 builtin/providers/aws/resource_aws_opsworks_user_profile.go create mode 100644 builtin/providers/aws/resource_aws_opsworks_user_profile_test.go create mode 100644 website/source/docs/providers/aws/r/opsworks_permission.html.markdown create mode 100644 website/source/docs/providers/aws/r/opsworks_user_profile.html.markdown diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 6ae9d5320..cfe8033e0 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -234,6 +234,8 @@ func Provider() terraform.ResourceProvider { "aws_opsworks_ganglia_layer": resourceAwsOpsworksGangliaLayer(), "aws_opsworks_custom_layer": resourceAwsOpsworksCustomLayer(), "aws_opsworks_instance": resourceAwsOpsworksInstance(), + "aws_opsworks_user_profile": resourceAwsOpsworksUserProfile(), + "aws_opsworks_permission": resourceAwsOpsworksPermission(), "aws_placement_group": resourceAwsPlacementGroup(), "aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(), "aws_rds_cluster": resourceAwsRDSCluster(), diff --git a/builtin/providers/aws/resource_aws_opsworks_permission.go b/builtin/providers/aws/resource_aws_opsworks_permission.go new file mode 100644 index 000000000..7493a4c20 --- /dev/null +++ b/builtin/providers/aws/resource_aws_opsworks_permission.go @@ -0,0 +1,155 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/opsworks" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsOpsworksPermission() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsOpsworksPermissionCreate, + Update: resourceAwsOpsworksPermissionCreate, + Delete: resourceAwsOpsworksPermissionDelete, + Read: resourceAwsOpsworksPermissionRead, + + Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "allow_ssh": &schema.Schema{ + Type: schema.TypeBool, + Computed: true, + Optional: true, + }, + "allow_sudo": &schema.Schema{ + Type: schema.TypeBool, + Computed: true, + Optional: true, + }, + "user_arn": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + // one of deny, show, deploy, manage, iam_only + "level": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Optional: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + expected := [5]string{"deny", "show", "deploy", "manage", "iam_only"} + + found := false + for _, b := range expected { + if b == value { + found = true + } + } + if !found { + errors = append(errors, fmt.Errorf( + "%q has to be one of [deny, show, deploy, manage, iam_only]", k)) + } + return + }, + }, + "stack_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + }, + } +} + +func resourceAwsOpsworksPermissionDelete(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func resourceAwsOpsworksPermissionRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AWSClient).opsworksconn + + req := &opsworks.DescribePermissionsInput{ + IamUserArn: aws.String(d.Get("user_arn").(string)), + StackId: aws.String(d.Get("stack_id").(string)), + } + + log.Printf("[DEBUG] Reading OpsWorks prermissions for: %s on stack: %s", d.Get("user_arn"), d.Get("stack_id")) + + resp, err := client.DescribePermissions(req) + if err != nil { + if awserr, ok := err.(awserr.Error); ok { + if awserr.Code() == "ResourceNotFoundException" { + log.Printf("[INFO] Permission not found") + d.SetId("") + return nil + } + } + return err + } + + found := false + id := "" + for _, permission := range resp.Permissions { + id = *permission.IamUserArn + *permission.StackId + + if d.Get("user_arn").(string)+d.Get("stack_id").(string) == id { + found = true + d.SetId(id) + d.Set("id", id) + d.Set("allow_ssh", permission.AllowSudo) + d.Set("allow_sodo", permission.AllowSudo) + d.Set("user_arn", permission.IamUserArn) + d.Set("stack_id", permission.StackId) + } + + } + + if false == found { + d.SetId("") + log.Printf("[INFO] The correct permission could not be found for: %s on stack: %s", d.Get("user_arn"), d.Get("stack_id")) + } + + return nil +} + +func resourceAwsOpsworksPermissionCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AWSClient).opsworksconn + + req := &opsworks.SetPermissionInput{ + AllowSudo: aws.Bool(d.Get("allow_sudo").(bool)), + AllowSsh: aws.Bool(d.Get("allow_ssh").(bool)), + IamUserArn: aws.String(d.Get("user_arn").(string)), + StackId: aws.String(d.Get("stack_id").(string)), + } + + var resp *opsworks.SetPermissionOutput + err := resource.Retry(2*time.Minute, func() *resource.RetryError { + var cerr error + resp, cerr = client.SetPermission(req) + if cerr != nil { + log.Printf("[INFO] client error") + if opserr, ok := cerr.(awserr.Error); ok { + // XXX: handle errors + log.Printf("[ERROR] OpsWorks error: %s message: %s", opserr.Code(), opserr.Message()) + return resource.RetryableError(cerr) + } + return resource.NonRetryableError(cerr) + } + return nil + }) + + if err != nil { + return err + } + + return resourceAwsOpsworksPermissionRead(d, meta) +} diff --git a/builtin/providers/aws/resource_aws_opsworks_permission_test.go b/builtin/providers/aws/resource_aws_opsworks_permission_test.go new file mode 100644 index 000000000..7c17bfc57 --- /dev/null +++ b/builtin/providers/aws/resource_aws_opsworks_permission_test.go @@ -0,0 +1,41 @@ +package aws + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAWSOpsworksPermission(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAwsOpsworksPermissionCreate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "aws_opsworks_permission.tf-acc-perm", "allow_ssh", "true", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_permission.tf-acc-perm", "allow_sudo", "true", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_permission.tf-acc-perm", "level", "iam_only", + ), + ), + }, + }, + }) +} + +var testAccAwsOpsworksPermissionCreate = testAccAwsOpsworksUserProfileCreate + ` +resource "aws_opsworks_permission" "tf-acc-perm" { + stack_id = "${aws_opsworks_stack.tf-acc.id}" + + allow_ssh = true + allow_sudo = true + user_arn = "${aws_opsworks_user_profile.user.user_arn}" + level = "iam_only" +} +` diff --git a/builtin/providers/aws/resource_aws_opsworks_stack_test.go b/builtin/providers/aws/resource_aws_opsworks_stack_test.go index 0a23273df..1bb9bba14 100644 --- a/builtin/providers/aws/resource_aws_opsworks_stack_test.go +++ b/builtin/providers/aws/resource_aws_opsworks_stack_test.go @@ -26,7 +26,7 @@ func TestAccAWSOpsworksStackNoVpc(t *testing.T) { Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAwsOpsworksStackConfigNoVpcCreate(stackName), - Check: testAccAwsOpsworksStackCheckResourceAttrsCreate("us-east-1c", stackName), + Check: testAccAwsOpsworksStackCheckResourceAttrsCreate("us-east-1a", stackName), }, // resource.TestStep{ // Config: testAccAWSOpsworksStackConfigNoVpcUpdate(stackName), @@ -236,7 +236,7 @@ resource "aws_opsworks_stack" "tf-acc" { region = "us-east-1" service_role_arn = "${aws_iam_role.opsworks_service.arn}" default_instance_profile_arn = "${aws_iam_instance_profile.opsworks_instance.arn}" - default_availability_zone = "us-east-1c" + default_availability_zone = "us-east-1a" default_os = "Amazon Linux 2014.09" default_root_device_type = "ebs" custom_json = "{\"key\": \"value\"}" @@ -317,7 +317,7 @@ resource "aws_opsworks_stack" "tf-acc" { region = "us-east-1" service_role_arn = "${aws_iam_role.opsworks_service.arn}" default_instance_profile_arn = "${aws_iam_instance_profile.opsworks_instance.arn}" - default_availability_zone = "us-east-1c" + default_availability_zone = "us-east-1a" default_os = "Amazon Linux 2014.09" default_root_device_type = "ebs" custom_json = "{\"key\": \"value\"}" diff --git a/builtin/providers/aws/resource_aws_opsworks_user_profile.go b/builtin/providers/aws/resource_aws_opsworks_user_profile.go new file mode 100644 index 000000000..fc4a8e246 --- /dev/null +++ b/builtin/providers/aws/resource_aws_opsworks_user_profile.go @@ -0,0 +1,136 @@ +package aws + +import ( + "log" + + "github.com/hashicorp/terraform/helper/schema" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/opsworks" +) + +func resourceAwsOpsworksUserProfile() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsOpsworksUserProfileCreate, + Read: resourceAwsOpsworksUserProfileRead, + Update: resourceAwsOpsworksUserProfileUpdate, + Delete: resourceAwsOpsworksUserProfileDelete, + + Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "user_arn": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "allow_self_management": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "ssh_username": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "ssh_public_key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func resourceAwsOpsworksUserProfileRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AWSClient).opsworksconn + + req := &opsworks.DescribeUserProfilesInput{ + IamUserArns: []*string{ + aws.String(d.Id()), + }, + } + + log.Printf("[DEBUG] Reading OpsWorks user profile: %s", d.Id()) + + resp, err := client.DescribeUserProfiles(req) + if err != nil { + if awserr, ok := err.(awserr.Error); ok { + if awserr.Code() == "ResourceNotFoundException" { + log.Printf("[DEBUG] OpsWorks user profile (%s) not found", d.Id()) + d.SetId("") + return nil + } + } + return err + } + + for _, profile := range resp.UserProfiles { + d.Set("allow_self_management", profile.AllowSelfManagement) + d.Set("user_arn", profile.IamUserArn) + d.Set("ssh_public_key", profile.SshPublicKey) + d.Set("ssh_username", profile.SshUsername) + break + } + + return nil +} + +func resourceAwsOpsworksUserProfileCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AWSClient).opsworksconn + + req := &opsworks.CreateUserProfileInput{ + AllowSelfManagement: aws.Bool(d.Get("allow_self_management").(bool)), + IamUserArn: aws.String(d.Get("user_arn").(string)), + SshPublicKey: aws.String(d.Get("ssh_public_key").(string)), + SshUsername: aws.String(d.Get("ssh_username").(string)), + } + + resp, err := client.CreateUserProfile(req) + if err != nil { + return err + } + + d.SetId(*resp.IamUserArn) + + return resourceAwsOpsworksUserProfileUpdate(d, meta) +} + +func resourceAwsOpsworksUserProfileUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AWSClient).opsworksconn + + req := &opsworks.UpdateUserProfileInput{ + AllowSelfManagement: aws.Bool(d.Get("allow_self_management").(bool)), + IamUserArn: aws.String(d.Get("user_arn").(string)), + SshPublicKey: aws.String(d.Get("ssh_public_key").(string)), + SshUsername: aws.String(d.Get("ssh_username").(string)), + } + + log.Printf("[DEBUG] Updating OpsWorks user profile: %s", req) + + _, err := client.UpdateUserProfile(req) + if err != nil { + return err + } + + return resourceAwsOpsworksUserProfileRead(d, meta) +} + +func resourceAwsOpsworksUserProfileDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AWSClient).opsworksconn + + req := &opsworks.DeleteUserProfileInput{ + IamUserArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting OpsWorks user profile: %s", d.Id()) + + _, err := client.DeleteUserProfile(req) + + return err +} diff --git a/builtin/providers/aws/resource_aws_opsworks_user_profile_test.go b/builtin/providers/aws/resource_aws_opsworks_user_profile_test.go new file mode 100644 index 000000000..68c4f30ea --- /dev/null +++ b/builtin/providers/aws/resource_aws_opsworks_user_profile_test.go @@ -0,0 +1,37 @@ +package aws + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAWSOpsworksUserProfile(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAwsOpsworksUserProfileCreate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "aws_opsworks_user_profile.user", "ssh_public_key", "", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_user_profile.user", "ssh_username", "test-user", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_user_profile.user", "allow_self_management", "false", + ), + ), + }, + }, + }) +} + +var testAccAwsOpsworksUserProfileCreate = testAccAWSUserConfig + testAccAwsOpsworksStackConfigNoVpcCreate("tf-ops-acc-user-profile") + ` +resource "aws_opsworks_user_profile" "user" { + user_arn = "${aws_iam_user.user.arn}" + ssh_username = "${aws_iam_user.user.name}" +} +` diff --git a/website/source/docs/providers/aws/r/opsworks_permission.html.markdown b/website/source/docs/providers/aws/r/opsworks_permission.html.markdown new file mode 100644 index 000000000..3739a35be --- /dev/null +++ b/website/source/docs/providers/aws/r/opsworks_permission.html.markdown @@ -0,0 +1,39 @@ +--- +layout: "aws" +page_title: "AWS: aws_opsworks_permission" +sidebar_current: "docs-aws-resource-opsworks-permission" +description: |- + Provides an OpsWorks permission resource. +------------------------------------------- + +# aws\_opsworks\_permission + +Provides an OpsWorks permission resource. + +## Example Usage + +``` +resource "aws_opsworks_permission" "my_stack_permission" { + allow_ssh = true + allow_sudo = true + level = "iam_only" + user_arn = "${aws_iam_user.user.arn}" + stack_id = "${aws_opsworks_stack.stack.id}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `allow_ssh` - (Optional) Whethe the user is allowed to use SSH to communicate with the instance +* `allow_sudo` - (Optional) Whethe the user is allowed to use sudo to elevate privileges +* `user_arn` - (Required) The user's IAM ARN to set permissions for +* `level` - (Optional) The users permission level. Mus be one of `deny`, `show`, `deploy`, `manage`, `iam_only` +* `stack_id` - (Required) The stack to set the permissions for + +## Attributes Reference + +The following attributes are exported: + +* `id` - The computed id of the permission. Please note that this is only used internally to identify the permission. This value is not used in aws. \ No newline at end of file diff --git a/website/source/docs/providers/aws/r/opsworks_user_profile.html.markdown b/website/source/docs/providers/aws/r/opsworks_user_profile.html.markdown new file mode 100644 index 000000000..781fae028 --- /dev/null +++ b/website/source/docs/providers/aws/r/opsworks_user_profile.html.markdown @@ -0,0 +1,35 @@ +--- +layout: "aws" +page_title: "AWS: aws_opsworks_user_profile_" +sidebar_current: "docs-aws-resource-opsworks-user-profile" +description: |- + Provides an OpsWorks User Profile resource. +--------------------------------------------- + +# aws\_opsworks\_user\_profile + +Provides an OpsWorks User Profile resource. + +## Example Usage + +``` +resource "aws_opsworks_user_profile" "my_profile" { + user_arn = "${aws_iam_user.user.arn}" + ssh_username = "my_user" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `user_arn` - (Required) The user's IAM ARN +* `allow_self_management` - (Optional) Whether users can specify their own SSH public key through the My Settings page +* `ssh_username` - (Required) The ssh username, with witch this user wants to log in +* `ssh_public_key` - (Optional) The users public key + +## Attributes Reference + +The following attributes are exported: + +* `id` - Same value as `user_arn`