From 52b6d971ef4c36079829c53193eb87fa3e4f808d Mon Sep 17 00:00:00 2001 From: Zack Elliott Date: Mon, 10 Oct 2022 13:52:14 -0500 Subject: [PATCH 1/3] In progress --- go.mod | 2 + resource_alks_iamrole.go | 5 +- resource_alks_ltk.go | 90 ++++++++++- .../Cox-Automotive/alks-go/iam_ltk.go | 148 ++++++++++++++++-- vendor/modules.txt | 3 +- 5 files changed, 231 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index cbb0dc91..230af3f7 100644 --- a/go.mod +++ b/go.mod @@ -57,3 +57,5 @@ require ( google.golang.org/grpc v1.48.0 // indirect google.golang.org/protobuf v1.28.1 // indirect ) + +replace github.com/Cox-Automotive/alks-go => /Users/Zachary.Elliott/code/alks-go diff --git a/resource_alks_iamrole.go b/resource_alks_iamrole.go index 8a838a5f..39ff1a86 100644 --- a/resource_alks_iamrole.go +++ b/resource_alks_iamrole.go @@ -76,6 +76,7 @@ func resourceAlksIamRole() *schema.Resource { Type: schema.TypeBool, Default: false, Optional: true, + ForceNew: true, }, "template_fields": { Type: schema.TypeMap, @@ -263,7 +264,7 @@ func resourceAlksIamRoleUpdate(ctx context.Context, d *schema.ResourceData, meta if d.HasChange("tags_all") { // try updating enable_alks_access - if err := updateIamTags(d, meta); err != nil { + if err := updateIamRoleTags(d, meta); err != nil { return diag.FromErr(err) } } @@ -297,7 +298,7 @@ func updateAlksAccess(d *schema.ResourceData, meta interface{}) error { return nil } -func updateIamTags(d *schema.ResourceData, meta interface{}) error { +func updateIamRoleTags(d *schema.ResourceData, meta interface{}) error { providerStruct := meta.(*AlksClient) client := providerStruct.client diff --git a/resource_alks_ltk.go b/resource_alks_ltk.go index e4767530..6747a126 100644 --- a/resource_alks_ltk.go +++ b/resource_alks_ltk.go @@ -4,9 +4,9 @@ import ( "context" "log" + "github.com/Cox-Automotive/alks-go" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - - // "github.com/Cox-Automotive/alks-go" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -39,7 +39,13 @@ func resourceAlksLtk() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "tags": TagsSchema(), + "tags_all": TagsSchemaComputed(), }, + CustomizeDiff: customdiff.All( + SetTagsDiff, + trustPoliciesWithIncludeDefaultPolicies, + ), } } @@ -47,14 +53,22 @@ func resourceAlksLtkCreate(ctx context.Context, d *schema.ResourceData, meta int log.Printf("[INFO] ALKS LTK User Create") var iamUsername = d.Get("iam_username").(string) + var tags = d.Get("tags").(map[string]interface{}) providerStruct := meta.(*AlksClient) client := providerStruct.client + + allTags := tagMapToSlice(combineTagMaps(providerStruct.defaultTags, tags)) + + options := &alks.CreateLongTermKeyOptions{ + IamUserName: &iamUsername, + Tags: &allTags, + } if err := validateIAMEnabled(client); err != nil { return diag.FromErr(err) } - resp, err := client.CreateLongTermKey(iamUsername) + resp, err := client.CreateLongTermKey(options) if err != nil { return diag.FromErr(err) } @@ -75,6 +89,9 @@ func resourceAlksLtkRead(ctx context.Context, d *schema.ResourceData, meta inter providerStruct := meta.(*AlksClient) client := providerStruct.client + defaultTags := providerStruct.defaultTags + ignoreTags := providerStruct.ignoreTags + // Check if role exists. if d.Id() == "" || d.Id() == "none" { return nil @@ -82,6 +99,8 @@ func resourceAlksLtkRead(ctx context.Context, d *schema.ResourceData, meta inter resp, err := client.GetLongTermKey(d.Id()) + //TODO: Figure out what alks core does here and if it returns the same way then fix alks go and this statement to handle it the same way + if err != nil { d.SetId("") return nil @@ -92,9 +111,40 @@ func resourceAlksLtkRead(ctx context.Context, d *schema.ResourceData, meta inter _ = d.Set("iam_username", resp.UserName) _ = d.Set("access_key", resp.AccessKeyID) + allTags := tagSliceToMap(resp.Tags) + localTags := removeIgnoredTags(allTags, *ignoreTags) + + if err := d.Set("tags_all", localTags); err != nil { + return diag.FromErr(err) + } + + ltkSpecificTags := removeDefaultTags(localTags, defaultTags) + + if err := d.Set("tags", ltkSpecificTags); err != nil { + return diag.FromErr(err) + } + return nil } +func resourceAlksLtkUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + log.Printf("[INFO] ALKS LTK Update") + + // enable partial state mode + d.Partial(true) + + if d.HasChange("tags_all") { + // try updating enable_alks_access + if err := updateLtkTags(d, meta); err != nil { + return diag.FromErr(err) + } + } + + d.Partial(false) + + return resourceAlksLtkRead(ctx, d, meta) +} + func resourceAlksLtkDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { log.Printf("[INFO] ALKS LTK User Delete") @@ -110,3 +160,37 @@ func resourceAlksLtkDelete(ctx context.Context, d *schema.ResourceData, meta int return nil } + +func updateLtkTags(d *schema.ResourceData, meta interface{}) error { + providerStruct := meta.(*AlksClient) + client := providerStruct.client + + if err := validateIAMEnabled(client); err != nil { + return err + } + + //Do a read to get existing tags. If any of those are in ignore_tags, then they are externally managed + //and they should be included in the update so they don't get removed. + ltk, err := client.GetLongTermKey(d.Id()) + + if err != nil { + return err + } + + existingTags := tagSliceToMap(ltk.Tags) + externalTags := getExternalyManagedTags(existingTags, *providerStruct.ignoreTags) + internalTags := d.Get("tags_all").(map[string]interface{}) + + //Tags includes default tags, role specific tags, and tags that exist externally on the role itself and are specified in ignored_tags + tags := tagMapToSlice(combineTagMaps(internalTags, externalTags)) + + options := alks.UpdateLongTermKeyRequest{ + IamUserName: <k.LongTermKey.UserName, + Tags: &tags, + } + + if _, err := client.UpdateLongTermKey(&options); err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/Cox-Automotive/alks-go/iam_ltk.go b/vendor/github.com/Cox-Automotive/alks-go/iam_ltk.go index f08688e0..836e9722 100644 --- a/vendor/github.com/Cox-Automotive/alks-go/iam_ltk.go +++ b/vendor/github.com/Cox-Automotive/alks-go/iam_ltk.go @@ -26,6 +26,7 @@ type GetLongTermKeysResponse struct { type GetLongTermKeyResponse struct { BaseResponse LongTermKey `json:"longTermKey"` + Tags []Tag `json:"tags"` } // BaseLongTermKeyResponse encapsulates shared response fields @@ -35,6 +36,11 @@ type BaseLongTermKeyResponse struct { PartialError bool `json:"partialError,omitempty"` } +type CreateLongTermKeyOptions struct { + IamUserName *string + Tags *[]Tag +} + // CreateLongTermKey represents the response from API type CreateLongTermKey struct { IAMUserName string `json:"iamUserName"` @@ -43,8 +49,14 @@ type CreateLongTermKey struct { SecretKey string `json:"secretKey"` } -// LongTermKeyRequest is used to represent the request body to create or delete LTKs -type LongTermKeyRequest struct { +// CreateLongTermKeyRequest is used to represent the request body to create or delete LTKs +type CreateLongTermKeyRequest struct { + IamUserName string `json:"iamUserName"` + Tags []Tag `json:"tags,omitempty"` +} + +// DeleteLongTermKeyRequest is used to represent the request body to delete LTKs +type DeleteLongTermKeyRequest struct { AccountDetails IamUserName string `json:"iamUserName"` } @@ -64,6 +76,18 @@ type DeleteLongTermKeyResponse struct { BaseLongTermKeyResponse } +type UpdateLongTermKeyRequest struct { + IamUserName *string `json:"roleName"` + Tags *[]Tag `json:"tags"` +} + +type UpdateLongTermKeyResponse struct { + AccountDetails + BaseResponse + Tags *[]Tag `json:"tags"` + Exists *bool `json:"roleExists"` +} + // GetLongTermKeys gets the LTKs for an account // If no error is returned then you will receive a list of LTKs func (c *Client) GetLongTermKeys() (*GetLongTermKeysResponse, error) { @@ -208,23 +232,52 @@ func (c *Client) GetLongTermKey(iamUsername string) (*GetLongTermKeyResponse, er return cr, nil } +func NewLongTermKeyRequest(options *CreateLongTermKeyOptions) (*CreateLongTermKeyRequest, error) { + if options.IamUserName == nil { + return nil, fmt.Errorf("IamUserName option must not be nil") + } + + ltk := &CreateLongTermKeyRequest{ + IamUserName: *options.IamUserName, + } + + if options.Tags != nil { + ltk.Tags = *options.Tags + } else { + ltk.Tags = nil + } + + return ltk, nil +} + // CreateLongTermKey creates an LTK user for an account. // If no error is returned, then you will receive an appropriate success message. -func (c *Client) CreateLongTermKey(iamUsername string) (*CreateLongTermKeyResponse, error) { - log.Printf("[INFO] Creating long term key: %s", iamUsername) - - request := LongTermKeyRequest{ - AccountDetails: c.AccountDetails, - IamUserName: iamUsername, +func (c *Client) CreateLongTermKey(options *CreateLongTermKeyOptions) (*CreateLongTermKeyResponse, error) { + request, err := NewLongTermKeyRequest(options) + if err != nil { + return nil, err } + log.Printf("[INFO] Creating long term key: %s", *options.IamUserName) - reqBody, err := json.Marshal(request) + // request.AccountDetails = c.AccountDetails + + b, err := json.Marshal(struct { + CreateLongTermKeyRequest + AccountDetails + }{*request, c.AccountDetails}) + + // request := LongTermKeyRequest{ + // AccountDetails: c.AccountDetails, + // IamUserName: iamUsername, + // } + + // reqBody, err := json.Marshal(request) if err != nil { return nil, fmt.Errorf("error encoding LTK create JSON: %s", err) } - req, err := c.NewRequest(reqBody, "POST", "/accessKeys") + req, err := c.NewRequest(b, "POST", "/accessKeys") if err != nil { return nil, err } @@ -283,7 +336,7 @@ func (c *Client) CreateLongTermKey(iamUsername string) (*CreateLongTermKeyRespon func (c *Client) DeleteLongTermKey(iamUsername string) (*DeleteLongTermKeyResponse, error) { log.Printf("[INFO] Deleting long term key: %s", iamUsername) - request := LongTermKeyRequest{ + request := DeleteLongTermKeyRequest{ AccountDetails: c.AccountDetails, IamUserName: iamUsername, } @@ -348,3 +401,76 @@ func (c *Client) DeleteLongTermKey(iamUsername string) (*DeleteLongTermKeyRespon return cr, nil } + +func (c *Client) UpdateLongTermKey(options *UpdateLongTermKeyRequest) (*UpdateLongTermKeyResponse, error) { + if err := options.updateLongTermKeyValidate(); err != nil { + return nil, err + } + log.Printf("[INFO] update LTK %s with Tags: %v", *options.IamUserName, *options.Tags) + + b, err := json.Marshal(struct { + UpdateLongTermKeyRequest + AccountDetails + }{*options, c.AccountDetails}) + if err != nil { + return nil, err + } + req, err := c.NewRequest(b, "PATCH", "/IAMUser/") + if err != nil { + return nil, err + } + resp, err := c.http.Do(req) + if err != nil { + return nil, err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + updateErr := new(AlksError) + err = decodeBody(resp, &updateErr) + + if err != nil { + if reqID := GetRequestID(resp); reqID != "" { + return nil, fmt.Errorf(ParseErrorReqId, reqID, err) + } + + return nil, fmt.Errorf(ParseError, err) + } + + if updateErr.Errors != nil { + if reqID := GetRequestID(resp); reqID != "" { + return nil, fmt.Errorf(ErrorStringFull, reqID, resp.StatusCode, updateErr.Errors) + } + + return nil, fmt.Errorf(ErrorStringNoReqId, resp.StatusCode, updateErr.Errors) + } + + if reqID := GetRequestID(resp); reqID != "" { + return nil, fmt.Errorf(ErrorStringOnlyCodeAndReqId, reqID, resp.StatusCode) + } + + return nil, fmt.Errorf(ErrorStringOnlyCode, resp.StatusCode) + } + + respObj := &UpdateLongTermKeyResponse{} + if err = decodeBody(resp, respObj); err != nil { + if reqID := GetRequestID(resp); reqID != "" { + return nil, fmt.Errorf("error parsing update ltk response: [%s] %s", reqID, err) + } + return nil, fmt.Errorf("error parsing update ltk response: %s", err) + } + if respObj.RequestFailed() { + return nil, fmt.Errorf("error from update IAM ltk request: [%s] %s", respObj.RequestID, strings.Join(respObj.GetErrors(), ", ")) + } + + return respObj, nil +} + +func (req *UpdateLongTermKeyRequest) updateLongTermKeyValidate() error { + if req.IamUserName == nil { + return fmt.Errorf("User name option must not be nil") + } + if req.Tags == nil { + return fmt.Errorf("tags option must not be nil") + } + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index acca1862..09c9e565 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# github.com/Cox-Automotive/alks-go v0.0.0-20221004204541-a25fb5c4f655 +# github.com/Cox-Automotive/alks-go v0.0.0-20221004204541-a25fb5c4f655 => /Users/Zachary.Elliott/code/alks-go ## explicit; go 1.16 github.com/Cox-Automotive/alks-go # github.com/agext/levenshtein v1.2.2 @@ -370,3 +370,4 @@ google.golang.org/protobuf/types/known/anypb google.golang.org/protobuf/types/known/durationpb google.golang.org/protobuf/types/known/emptypb google.golang.org/protobuf/types/known/timestamppb +# github.com/Cox-Automotive/alks-go => /Users/Zachary.Elliott/code/alks-go From 33c124cf8e994fae1e52208576c9dd3c3d8849a0 Mon Sep 17 00:00:00 2001 From: Zack Elliott Date: Wed, 19 Oct 2022 14:14:42 -0500 Subject: [PATCH 2/3] Tagging and updating ltk's implemented --- Makefile | 2 +- assume_role_policy_test.go | 12 +- go.mod | 3 +- go.sum | 2 + resource_alks_ltk.go | 54 +- resource_alks_ltk_test.go | 233 +++++++- .../Cox-Automotive/alks-go/iam_ltk.go | 476 --------------- .../Cox-Automotive/alks-go/iam_role.go | 4 +- .../Cox-Automotive/alks-go/iam_user.go | 558 ++++++++++++++++++ vendor/modules.txt | 3 +- 10 files changed, 825 insertions(+), 522 deletions(-) delete mode 100644 vendor/github.com/Cox-Automotive/alks-go/iam_ltk.go create mode 100644 vendor/github.com/Cox-Automotive/alks-go/iam_user.go diff --git a/Makefile b/Makefile index 28535b27..2a07f600 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ build: go build -v -o examples/terraform-provider-alks -mod=vendor . test: - go test -v . + go test -timeout 1200s -v . plan: @terraform plan diff --git a/assume_role_policy_test.go b/assume_role_policy_test.go index f5c9fb3a..94eab302 100644 --- a/assume_role_policy_test.go +++ b/assume_role_policy_test.go @@ -15,8 +15,8 @@ func TestSuppressEquivalentTrustPolicyDiffs(t *testing.T) { { policy1: string(` { - "Version": "1234", - "Id": "Something", + "Version": "1234", + "Id": "Something", "Statement": [ { "Action": "sts:AssumeRole", @@ -34,7 +34,7 @@ func TestSuppressEquivalentTrustPolicyDiffs(t *testing.T) { policy2: string(` { "Id": "Something", - "Version": "1234", + "Version": "1234", "Statement": [ { "Action": "sts:AssumeRole", @@ -54,8 +54,8 @@ func TestSuppressEquivalentTrustPolicyDiffs(t *testing.T) { { policy1: string(` { - "Version": "1234", - "Id": "Something", + "Version": "1234", + "Id": "Something", "Statement": [ { "Action": "sts:AssumeRole", @@ -73,7 +73,7 @@ func TestSuppressEquivalentTrustPolicyDiffs(t *testing.T) { policy2: string(` { "Id": "Something", - "Version": "1234", + "Version": "1234", "Statement": [ { "Action": "sts:AssumeRole", diff --git a/go.mod b/go.mod index 9be141db..a0e36d66 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Cox-Automotive/terraform-provider-alks go 1.18 require ( - github.com/Cox-Automotive/alks-go v0.0.0-20221010204605-136b6e9b6530 + github.com/Cox-Automotive/alks-go v0.0.0-20221019181202-84b27abafb6b github.com/aws/aws-sdk-go v1.31.15 github.com/hashicorp/terraform-plugin-sdk/v2 v2.21.0 github.com/mitchellh/go-homedir v1.1.0 @@ -57,4 +57,3 @@ require ( google.golang.org/grpc v1.48.0 // indirect google.golang.org/protobuf v1.28.1 // indirect ) - diff --git a/go.sum b/go.sum index 8945acc9..d4f2c6c1 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ github.com/Cox-Automotive/alks-go v0.0.0-20221004204541-a25fb5c4f655 h1:akQkFItS github.com/Cox-Automotive/alks-go v0.0.0-20221004204541-a25fb5c4f655/go.mod h1:jJNgXthl59Vt2tJHSC3WZ0vlopV9xqdclfQuLgwHjOw= github.com/Cox-Automotive/alks-go v0.0.0-20221010204605-136b6e9b6530 h1:8j3NYoLnFy2PGw+UX47C8jC2j3CCkFeXqlaMfKu9Bh8= github.com/Cox-Automotive/alks-go v0.0.0-20221010204605-136b6e9b6530/go.mod h1:jJNgXthl59Vt2tJHSC3WZ0vlopV9xqdclfQuLgwHjOw= +github.com/Cox-Automotive/alks-go v0.0.0-20221019181202-84b27abafb6b h1:9Ey7kdUL+/f5EY2KOpTawWMw4P7fhZxNmo8gXIuBQzw= +github.com/Cox-Automotive/alks-go v0.0.0-20221019181202-84b27abafb6b/go.mod h1:jJNgXthl59Vt2tJHSC3WZ0vlopV9xqdclfQuLgwHjOw= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= diff --git a/resource_alks_ltk.go b/resource_alks_ltk.go index 6747a126..13095b7e 100644 --- a/resource_alks_ltk.go +++ b/resource_alks_ltk.go @@ -14,6 +14,7 @@ func resourceAlksLtk() *schema.Resource { return &schema.Resource{ CreateContext: resourceAlksLtkCreate, ReadContext: resourceAlksLtkRead, + UpdateContext: resourceAlksLtkUpdate, DeleteContext: resourceAlksLtkDelete, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, @@ -44,7 +45,6 @@ func resourceAlksLtk() *schema.Resource { }, CustomizeDiff: customdiff.All( SetTagsDiff, - trustPoliciesWithIncludeDefaultPolicies, ), } } @@ -60,7 +60,7 @@ func resourceAlksLtkCreate(ctx context.Context, d *schema.ResourceData, meta int allTags := tagMapToSlice(combineTagMaps(providerStruct.defaultTags, tags)) - options := &alks.CreateLongTermKeyOptions{ + options := &alks.IamUserOptions{ IamUserName: &iamUsername, Tags: &allTags, } @@ -68,9 +68,9 @@ func resourceAlksLtkCreate(ctx context.Context, d *schema.ResourceData, meta int return diag.FromErr(err) } - resp, err := client.CreateLongTermKey(options) + resp, err := client.CreateIamUser(options) if err != nil { - return diag.FromErr(err) + return diag.FromErr(err.Err) } d.SetId(iamUsername) @@ -97,30 +97,34 @@ func resourceAlksLtkRead(ctx context.Context, d *schema.ResourceData, meta inter return nil } - resp, err := client.GetLongTermKey(d.Id()) - - //TODO: Figure out what alks core does here and if it returns the same way then fix alks go and this statement to handle it the same way + resp, err := client.GetIamUser(d.Id()) if err != nil { - d.SetId("") - return nil + //If error is 404, UserNotFound, we log it and let terraform decide how to handle it. + //All other errors cause a failure + if err.StatusCode == 404 { + log.Printf("[Error] %s", err.Err) + d.SetId("") + return nil + } + return diag.FromErr(err.Err) } log.Printf("[INFO] alks_ltk.id: %v", d.Id()) - _ = d.Set("iam_username", resp.UserName) - _ = d.Set("access_key", resp.AccessKeyID) + _ = d.Set("iam_username", resp.User.UserName) + _ = d.Set("access_key", resp.User.AccessKey) - allTags := tagSliceToMap(resp.Tags) + allTags := tagSliceToMap(resp.User.Tags) localTags := removeIgnoredTags(allTags, *ignoreTags) if err := d.Set("tags_all", localTags); err != nil { return diag.FromErr(err) } - ltkSpecificTags := removeDefaultTags(localTags, defaultTags) + userSpecificTags := removeDefaultTags(localTags, defaultTags) - if err := d.Set("tags", ltkSpecificTags); err != nil { + if err := d.Set("tags", userSpecificTags); err != nil { return diag.FromErr(err) } @@ -135,7 +139,7 @@ func resourceAlksLtkUpdate(ctx context.Context, d *schema.ResourceData, meta int if d.HasChange("tags_all") { // try updating enable_alks_access - if err := updateLtkTags(d, meta); err != nil { + if err := updateUserTags(d, meta); err != nil { return diag.FromErr(err) } } @@ -154,14 +158,14 @@ func resourceAlksLtkDelete(ctx context.Context, d *schema.ResourceData, meta int return diag.FromErr(err) } - if _, err := client.DeleteLongTermKey(d.Id()); err != nil { - return diag.FromErr(err) + if _, err := client.DeleteIamUser(d.Id()); err != nil { + return diag.FromErr(err.Err) } return nil } -func updateLtkTags(d *schema.ResourceData, meta interface{}) error { +func updateUserTags(d *schema.ResourceData, meta interface{}) error { providerStruct := meta.(*AlksClient) client := providerStruct.client @@ -171,26 +175,26 @@ func updateLtkTags(d *schema.ResourceData, meta interface{}) error { //Do a read to get existing tags. If any of those are in ignore_tags, then they are externally managed //and they should be included in the update so they don't get removed. - ltk, err := client.GetLongTermKey(d.Id()) + resp, err := client.GetIamUser(d.Id()) if err != nil { return err } - existingTags := tagSliceToMap(ltk.Tags) + existingTags := tagSliceToMap(resp.User.Tags) externalTags := getExternalyManagedTags(existingTags, *providerStruct.ignoreTags) internalTags := d.Get("tags_all").(map[string]interface{}) //Tags includes default tags, role specific tags, and tags that exist externally on the role itself and are specified in ignored_tags tags := tagMapToSlice(combineTagMaps(internalTags, externalTags)) - options := alks.UpdateLongTermKeyRequest{ - IamUserName: <k.LongTermKey.UserName, - Tags: &tags, + options := alks.IamUserOptions{ + IamUserName: &resp.User.UserName, + Tags: &tags, } - if _, err := client.UpdateLongTermKey(&options); err != nil { - return err + if _, err := client.UpdateIamUser(&options); err != nil { + return err.Err } return nil } diff --git a/resource_alks_ltk_test.go b/resource_alks_ltk_test.go index e135592f..567566f0 100644 --- a/resource_alks_ltk_test.go +++ b/resource_alks_ltk_test.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "log" "testing" "github.com/Cox-Automotive/alks-go" @@ -10,7 +11,7 @@ import ( ) func TestAlksLTKCreate(t *testing.T) { - var resp alks.CreateLongTermKeyResponse + var resp alks.CreateIamUserResponse resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -22,16 +23,172 @@ func TestAlksLTKCreate(t *testing.T) { Config: testAlksLTKCreateConfig, Check: resource.ComposeTestCheckFunc(resource.TestCheckResourceAttr("alks_ltk.foo", "iam_username", "TEST_LTK_USER")), }, - // Update the resource + }, + }) +} + +func TestAlksLTKCreateWithTags(t *testing.T) { + var resp alks.CreateIamUserResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAlksLtkDestroy(&resp), + Steps: []resource.TestStep{ + // Create the resource + { + Config: testAlksLTKCreateWithTagsConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("alks_ltk.foo", "iam_username", "TEST_LTK_USER"), + resource.TestCheckResourceAttr("alks_ltk.foo", "tags.foo", "bar"), + resource.TestCheckResourceAttr("alks_ltk.foo", "tags.cloud", "railway"), + ), + }, + }, + }) +} + +func TestAlksLTKCreateWithTagsEmptyList(t *testing.T) { + var resp alks.CreateIamUserResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAlksLtkDestroy(&resp), + Steps: []resource.TestStep{ + // Create the resource + { + Config: testAlksLTKCreateWithTagsEmptyListConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("alks_ltk.foo", "iam_username", "TEST_LTK_USER"), + resource.TestCheckResourceAttr("alks_ltk.foo", "tags_all.%", "0"), + ), + }, + }, + }) +} + +func TestAccAlksLTKCreate_DefaultTags(t *testing.T) { + var resp alks.CreateIamUserResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAlksLtkDestroy(&resp), + Steps: []resource.TestStep{ + { + // create resource with tags + Config: testAccCheckAlksLtkCreateWithDefaultTags, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags.cloud", "railway"), + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags_all.cloud", "railway"), + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags_all.defaultTagKey1", "defaultTagValue1"), + ), + }, + }, + }) +} + +func TestAccAlksLTKUpdate_DefaultTags(t *testing.T) { + var resp alks.CreateIamUserResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAlksLtkDestroy(&resp), + Steps: []resource.TestStep{ { - Config: testAlksLTKUpdateConfig, - Check: resource.ComposeTestCheckFunc(resource.TestCheckResourceAttr("alks_ltk.foo", "iam_username", "TEST_LTK_USER_2")), + // create resource with tags + Config: testAccCheckAlksLtkCreateWithDefaultTags, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags.cloud", "railway"), + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags_all.cloud", "railway"), + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags_all.defaultTagKey1", "defaultTagValue1"), + ), + }, + { + // update resource with new tags + Config: testAccCheckAlksLtkUpdateWithDefaultTags, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags.cloud2", "railway2"), + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags_all.cloud2", "railway2"), + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags_all.%", "1"), + ), }, }, }) } -func testAlksLtkDestroy(ltk *alks.CreateLongTermKeyResponse) resource.TestCheckFunc { +func TestAccAlksLTK_IgnoreTags(t *testing.T) { + var resp alks.CreateIamUserResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAlksLtkDestroy(&resp), + Steps: []resource.TestStep{ + { + // create resource with tags + Config: testAccCheckAlksLtkCreateWithDefaultTags, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags.cloud", "railway"), + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags_all.cloud", "railway"), + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags_all.defaultTagKey1", "defaultTagValue1"), + ), + }, + { + //Add tags externally. These should not trigger an update because they are excluded by ignore_tags + PreConfig: func() { + client := testAccProvider.Meta().(*AlksClient).client + tags := TagMap{ + "defaultTagKey1": "defaultTagValue1", + "cloud": "railway", + "ignorePrefix:testKey1": "testValue1", + "ignoreFullKey": "testValue1", + } + userName := "TEST_LTK_USER" + + tagSlice := tagMapToSlice(tags) + options := alks.IamUserOptions{ + IamUserName: &userName, + Tags: &tagSlice, + } + + if _, err := client.UpdateIamUser(&options); err != nil { + log.Printf("[INFO] Error in UpdateRole from test") + return + } + }, + Config: testAccCheckAlksLtkUpdateWithTagsWithIgnoredTags, + PlanOnly: true, //This PlanOnly ensures there are no changes happening on this step. Any changes will cause the test to error out because of uncompleted plan + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags_all.defaultTagKey1", "defaultTagValue1"), + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags.cloud", "railway"), + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags_all.cloud", "railway"), + resource.TestCheckResourceAttr( + "alks_ltk.foo", "tags_all.%", "2"), + ), + }, + }, + }) +} + +func testAlksLtkDestroy(ltk *alks.CreateIamUserResponse) resource.TestCheckFunc { return func(s *terraform.State) error { providerStruct := testAccProvider.Meta().(*AlksClient) client := providerStruct.client @@ -41,9 +198,9 @@ func testAlksLtkDestroy(ltk *alks.CreateLongTermKeyResponse) resource.TestCheckF continue } - resp, err := client.GetLongTermKey(rs.Primary.ID) + resp, err := client.GetIamUser(rs.Primary.ID) if resp != nil { - return fmt.Errorf("long term key still exists: %#v (%v)", resp, err) + return fmt.Errorf("Iam User still exists: %#v (%v)", resp, err) } } @@ -57,8 +214,66 @@ const testAlksLTKCreateConfig = ` } ` -const testAlksLTKUpdateConfig = ` +const testAlksLTKCreateWithTagsConfig = ` resource "alks_ltk" "foo" { - iam_username = "TEST_LTK_USER_2" + iam_username = "TEST_LTK_USER" + tags = { + foo = "bar" + cloud = "railway" + } } ` + +const testAlksLTKCreateWithTagsEmptyListConfig = ` + resource "alks_ltk" "foo" { + iam_username = "TEST_LTK_USER" + tags = {} + } +` + +const testAccCheckAlksLtkCreateWithDefaultTags = ` + provider "alks" { + default_tags { + tags = { + defaultTagKey1 = "defaultTagValue1" + } + } + } + resource "alks_ltk" "foo" { + iam_username = "TEST_LTK_USER" + tags = { + cloud = "railway" + } + } +` + +const testAccCheckAlksLtkUpdateWithDefaultTags = ` + provider "alks" { + } + resource "alks_ltk" "foo" { + iam_username = "TEST_LTK_USER" + tags = { + cloud2 = "railway2" + } + } +` + +const testAccCheckAlksLtkUpdateWithTagsWithIgnoredTags = ` + provider "alks" { + default_tags { + tags = { + defaultTagKey1 = "defaultTagValue1" + } + } + ignore_tags { + keys = ["ignoreFullKey"] + key_prefixes = ["ignorePrefix"] + } + } + resource "alks_ltk" "foo" { + iam_username = "TEST_LTK_USER" + tags = { + cloud = "railway" + } + } +` diff --git a/vendor/github.com/Cox-Automotive/alks-go/iam_ltk.go b/vendor/github.com/Cox-Automotive/alks-go/iam_ltk.go deleted file mode 100644 index 148f0e20..00000000 --- a/vendor/github.com/Cox-Automotive/alks-go/iam_ltk.go +++ /dev/null @@ -1,476 +0,0 @@ -package alks - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - "strings" -) - -// LongTermKey represents a long term key -type LongTermKey struct { - UserName string `json:"userName"` - AccessKeyID string `json:"accessKeyId"` - Status string `json:"status"` - CreateDate string `json:"createDate"` -} - -// GetLongTermKeysResponse is used to represent the list of long term keys -type GetLongTermKeysResponse struct { - BaseResponse - LongTermKeys []LongTermKey `json:"longTermKeys"` -} - -// GetLongTermKeyResponse is used to represent a single long term key. -type GetLongTermKeyResponse struct { - BaseResponse - LongTermKey `json:"longTermKey"` - Tags []Tag `json:"tags"` -} - -// BaseLongTermKeyResponse encapsulates shared response fields -type BaseLongTermKeyResponse struct { - Action string `json:"action,omitempty"` - AddedIAMUserToGroup bool `json:"addedIAMUserToGroup,omitempty"` - PartialError bool `json:"partialError,omitempty"` -} - -type CreateLongTermKeyOptions struct { - IamUserName *string - Tags *[]Tag -} - -// CreateLongTermKey represents the response from API -type CreateLongTermKey struct { - IAMUserName string `json:"iamUserName"` - IAMUserArn string `json:"iamUserArn"` - AccessKey string `json:"accessKey"` - SecretKey string `json:"secretKey"` -} - -// CreateLongTermKeyRequest is used to represent the request body to create or delete LTKs -type CreateLongTermKeyRequest struct { - IamUserName string `json:"iamUserName"` - Tags []Tag `json:"tags,omitempty"` -} - -// DeleteLongTermKeyRequest is used to represent the request body to delete LTKs -type DeleteLongTermKeyRequest struct { - AccountDetails - IamUserName string `json:"iamUserName"` -} - -// CreateLongTermKeyResponse is the response to the CLI client -type CreateLongTermKeyResponse struct { - AccountDetails - BaseResponse - BaseLongTermKeyResponse - CreateLongTermKey -} - -// DeleteLongTermKeyResponse is the response to the CLI client -type DeleteLongTermKeyResponse struct { - AccountDetails - BaseResponse - BaseLongTermKeyResponse -} - -type UpdateLongTermKeyRequest struct { - IamUserName *string `json:"roleName"` - Tags *[]Tag `json:"tags"` -} - -type UpdateLongTermKeyResponse struct { - AccountDetails - BaseResponse - Tags *[]Tag `json:"tags"` - Exists *bool `json:"roleExists"` -} - -// GetLongTermKeys gets the LTKs for an account -// If no error is returned then you will receive a list of LTKs -func (c *Client) GetLongTermKeys() (*GetLongTermKeysResponse, error) { - log.Printf("[INFO] Getting long term keys") - - accountID, err := c.AccountDetails.GetAccountNumber() - if err != nil { - return nil, fmt.Errorf("Error reading Account value: %s", err) - } - - roleName, err := c.AccountDetails.GetRoleName(false) - if err != nil { - return nil, fmt.Errorf("Error reading Role value: %s", err) - } - - req, err := c.NewRequest(nil, "GET", "/ltks/"+accountID+"/"+roleName) - if err != nil { - return nil, err - } - - resp, err := c.http.Do(req) - if err != nil { - return nil, err - } - - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - keyErr := new(AlksResponseError) - err = decodeBody(resp, &keyErr) - if err != nil { - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf(ParseErrorReqId, reqID, err) - } - - return nil, fmt.Errorf(ParseError, err) - } - - if keyErr.Errors != nil { - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf(ErrorStringFull, reqID, resp.StatusCode, keyErr.Errors) - } - - return nil, fmt.Errorf(ErrorStringNoReqId, resp.StatusCode, keyErr.Errors) - } - - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf(ErrorStringOnlyCodeAndReqId, reqID, resp.StatusCode) - } - - return nil, fmt.Errorf(ErrorStringOnlyCode, resp.StatusCode) - } - - cr := new(GetLongTermKeysResponse) - err = decodeBody(resp, &cr) - - if err != nil { - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf("Error parsing GetLongTermKeysResponse: [%s] %s", reqID, err) - } - - return nil, fmt.Errorf("Error parsing GetLongTermKeysResponse: %s", err) - } - - if cr.RequestFailed() { - return nil, fmt.Errorf("Error getting long term keys: [%s] %s", cr.BaseResponse.RequestID, strings.Join(cr.GetErrors(), ", ")) - } - - return cr, nil -} - -// GetLongTermKey gets a single LTK for an account -// If no error is returned, then you will receive an LTK for the given account. -func (c *Client) GetLongTermKey(iamUsername string) (*GetLongTermKeyResponse, error) { - log.Printf("[INFO] Getting long term key") - - var req *http.Request - var err error - - if c.IsUsingSTSCredentials() { - req, err = c.NewRequest(nil, "GET", "/ltk/search/"+iamUsername) - } else { - accountID, err := c.AccountDetails.GetAccountNumber() - if err != nil { - return nil, fmt.Errorf("error reading Account value: %s", err) - } - - roleName, err := c.AccountDetails.GetRoleName(false) - if err != nil { - return nil, fmt.Errorf("error reading Role value: %s", err) - } - - req, err = c.NewRequest(nil, "GET", "/ltk/"+accountID+"/"+roleName+"/search/"+iamUsername) - } - - if err != nil { - return nil, err - } - - resp, err := c.http.Do(req) - if err != nil { - return nil, err - } - - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - keyErr := new(AlksResponseError) - err = decodeBody(resp, &keyErr) - if err != nil { - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf(ParseErrorReqId, reqID, err) - } - - return nil, fmt.Errorf(ParseError, err) - } - - if keyErr.Errors != nil { - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf(ErrorStringFull, reqID, resp.StatusCode, keyErr.Errors) - } - - return nil, fmt.Errorf(ErrorStringNoReqId, resp.StatusCode, keyErr.Errors) - } - - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf(ErrorStringOnlyCodeAndReqId, reqID, resp.StatusCode) - } - - return nil, fmt.Errorf(ErrorStringOnlyCode, resp.StatusCode) - } - - cr := new(GetLongTermKeyResponse) - err = decodeBody(resp, &cr) - - if err != nil { - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf("error parsing GetLongTermKeyResponse: [%s] %s", reqID, err) - } - } - - if cr.RequestFailed() { - return nil, fmt.Errorf("error getting long term keys: [%s] %s", cr.BaseResponse.RequestID, strings.Join(cr.GetErrors(), ", ")) - } - - return cr, nil -} - -func NewLongTermKeyRequest(options *CreateLongTermKeyOptions) (*CreateLongTermKeyRequest, error) { - if options.IamUserName == nil { - return nil, fmt.Errorf("IamUserName option must not be nil") - } - - ltk := &CreateLongTermKeyRequest{ - IamUserName: *options.IamUserName, - } - - if options.Tags != nil { - ltk.Tags = *options.Tags - } else { - ltk.Tags = nil - } - - return ltk, nil -} - -// CreateLongTermKey creates an LTK user for an account. -// If no error is returned, then you will receive an appropriate success message. -func (c *Client) CreateLongTermKey(options *CreateLongTermKeyOptions) (*CreateLongTermKeyResponse, error) { - request, err := NewLongTermKeyRequest(options) - if err != nil { - return nil, err - } - log.Printf("[INFO] Creating long term key: %s", *options.IamUserName) - - // request.AccountDetails = c.AccountDetails - - b, err := json.Marshal(struct { - CreateLongTermKeyRequest - AccountDetails - }{*request, c.AccountDetails}) - - // request := LongTermKeyRequest{ - // AccountDetails: c.AccountDetails, - // IamUserName: iamUsername, - // } - - // reqBody, err := json.Marshal(request) - - if err != nil { - return nil, fmt.Errorf("error encoding LTK create JSON: %s", err) - } - - req, err := c.NewRequest(b, "POST", "/accessKeys") - if err != nil { - return nil, err - } - - resp, err := c.http.Do(req) - if err != nil { - return nil, err - } - - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - keyErr := new(AlksResponseError) - err = decodeBody(resp, &keyErr) - if err != nil { - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf(ParseErrorReqId, reqID, err) - } - - return nil, fmt.Errorf(ParseError, err) - } - - if keyErr.Errors != nil { - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf(ErrorStringFull, reqID, resp.StatusCode, keyErr.Errors) - } - - return nil, fmt.Errorf(ErrorStringNoReqId, resp.StatusCode, keyErr.Errors) - } - - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf(ErrorStringOnlyCodeAndReqId, reqID, resp.StatusCode) - } - - return nil, fmt.Errorf(ErrorStringOnlyCode, resp.StatusCode) - } - - cr := new(CreateLongTermKeyResponse) - err = decodeBody(resp, &cr) - - if err != nil { - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf("error parsing CreateLongTermKeyResponse: [%s] %s", reqID, err) - } - - return nil, fmt.Errorf("error parsing CreateLongTermKeyResponse: %s", err) - } - - if cr.RequestFailed() { - return nil, fmt.Errorf("error creating long term key: [%s] %s", cr.BaseResponse.RequestID, strings.Join(cr.GetErrors(), ", ")) - } - - return cr, nil -} - -// DeleteLongTermKey deletes an LTK user for an account. -// If no error is returned, then you will receive an appropriate success message. -func (c *Client) DeleteLongTermKey(iamUsername string) (*DeleteLongTermKeyResponse, error) { - log.Printf("[INFO] Deleting long term key: %s", iamUsername) - - request := DeleteLongTermKeyRequest{ - AccountDetails: c.AccountDetails, - IamUserName: iamUsername, - } - - reqBody, err := json.Marshal(request) - - if err != nil { - return nil, fmt.Errorf("error encoding LTK delete JSON: %s", err) - } - - req, err := c.NewRequest(reqBody, "DELETE", "/IAMUser") - if err != nil { - return nil, err - } - - resp, err := c.http.Do(req) - if err != nil { - return nil, err - } - - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - keyErr := new(AlksResponseError) - err = decodeBody(resp, &keyErr) - if err != nil { - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf(ParseErrorReqId, reqID, err) - } - - return nil, fmt.Errorf(ParseError, err) - } - - if keyErr.Errors != nil { - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf(ErrorStringFull, reqID, resp.StatusCode, keyErr.Errors) - } - - return nil, fmt.Errorf(ErrorStringNoReqId, resp.StatusCode, keyErr.Errors) - } - - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf(ErrorStringOnlyCodeAndReqId, reqID, resp.StatusCode) - } - - return nil, fmt.Errorf(ErrorStringOnlyCode, resp.StatusCode) - } - - cr := new(DeleteLongTermKeyResponse) - err = decodeBody(resp, &cr) - - if err != nil { - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf("error parsing DeleteLongTermKeyResponse: [%s] %s", reqID, err) - } - - return nil, fmt.Errorf("error parsing DeleteLongTermKeyResponse: %s", err) - } - - if cr.RequestFailed() { - return nil, fmt.Errorf("error deleting long term key: [%s] %s", cr.BaseResponse.RequestID, strings.Join(cr.GetErrors(), ", ")) - } - - return cr, nil - -} - -func (c *Client) UpdateLongTermKey(options *UpdateLongTermKeyRequest) (*UpdateLongTermKeyResponse, error) { - if err := options.updateLongTermKeyValidate(); err != nil { - return nil, err - } - log.Printf("[INFO] update LTK %s with Tags: %v", *options.IamUserName, *options.Tags) - - b, err := json.Marshal(struct { - UpdateLongTermKeyRequest - AccountDetails - }{*options, c.AccountDetails}) - if err != nil { - return nil, err - } - req, err := c.NewRequest(b, "PATCH", "/IAMUser/") - if err != nil { - return nil, err - } - resp, err := c.http.Do(req) - if err != nil { - return nil, err - } - - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - updateErr := new(AlksError) - err = decodeBody(resp, &updateErr) - - if err != nil { - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf(ParseErrorReqId, reqID, err) - } - - return nil, fmt.Errorf(ParseError, err) - } - - if updateErr.Errors != nil { - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf(ErrorStringFull, reqID, resp.StatusCode, updateErr.Errors) - } - - return nil, fmt.Errorf(ErrorStringNoReqId, resp.StatusCode, updateErr.Errors) - } - - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf(ErrorStringOnlyCodeAndReqId, reqID, resp.StatusCode) - } - - return nil, fmt.Errorf(ErrorStringOnlyCode, resp.StatusCode) - } - - respObj := &UpdateLongTermKeyResponse{} - if err = decodeBody(resp, respObj); err != nil { - if reqID := GetRequestID(resp); reqID != "" { - return nil, fmt.Errorf("error parsing update ltk response: [%s] %s", reqID, err) - } - return nil, fmt.Errorf("error parsing update ltk response: %s", err) - } - if respObj.RequestFailed() { - return nil, fmt.Errorf("error from update IAM ltk request: [%s] %s", respObj.RequestID, strings.Join(respObj.GetErrors(), ", ")) - } - - return respObj, nil -} - -func (req *UpdateLongTermKeyRequest) updateLongTermKeyValidate() error { - if req.IamUserName == nil { - return fmt.Errorf("User name option must not be nil") - } - if req.Tags == nil { - return fmt.Errorf("tags option must not be nil") - } - return nil -} diff --git a/vendor/github.com/Cox-Automotive/alks-go/iam_role.go b/vendor/github.com/Cox-Automotive/alks-go/iam_role.go index 7efe3dbe..1585193c 100644 --- a/vendor/github.com/Cox-Automotive/alks-go/iam_role.go +++ b/vendor/github.com/Cox-Automotive/alks-go/iam_role.go @@ -380,7 +380,9 @@ type UpdateIamRoleResponse struct { func (c *Client) UpdateIamRole(options *UpdateIamRoleRequest) (*UpdateIamRoleResponse, *AlksError) { if err := options.updateIamRoleValidate(); err != nil { return nil, &AlksError{ - Err: err, + StatusCode: 0, + RequestId: "", + Err: err, } } log.Printf("[INFO] update IAM role %s with Tags: %v", *options.RoleName, *options.Tags) diff --git a/vendor/github.com/Cox-Automotive/alks-go/iam_user.go b/vendor/github.com/Cox-Automotive/alks-go/iam_user.go new file mode 100644 index 00000000..79e00c2d --- /dev/null +++ b/vendor/github.com/Cox-Automotive/alks-go/iam_user.go @@ -0,0 +1,558 @@ +package alks + +import ( + "encoding/json" + "fmt" + "log" + + // "net/http" + "strings" +) + +//Represents iamUser returned by iam-user endpoint +type IamUser struct { + ARN string `json:"arn"` + AccountId string `json:"accountId"` + UserName string `json:"userName"` + AccessKey string `json:"accessKey"` + Tags []Tag `json:"tags"` +} + +// AllIamUsersResponseType represents iamUser returned by ltks endpoint +type AllIamUsersResponseType struct { + UserName string `json:"userName"` + AccessKeyID string `json:"accessKeyId"` + Status string `json:"status"` + CreateDate string `json:"createDate"` +} + +// GetIamUsersResponse is used to represent the list of long term keys +type GetIamUsersResponse struct { + BaseResponse + IamUsers []AllIamUsersResponseType `json:"longTermKeys"` +} + +// GetIamUserResponse is used to represent a single long term key. +type GetIamUserResponse struct { + BaseResponse + User IamUser `json:"item"` +} + +// BaseIamUserResponse encapsulates shared response fields +type BaseIamUserResponse struct { + Action string `json:"action,omitempty"` + AddedIAMUserToGroup bool `json:"addedIAMUserToGroup,omitempty"` + PartialError bool `json:"partialError,omitempty"` +} + +// CreateIamUserApiResponse represents the response from API +type CreateIamUserApiResponse struct { + IAMUserName string `json:"iamUserName"` + IAMUserArn string `json:"iamUserArn"` + AccessKey string `json:"accessKey"` + SecretKey string `json:"secretKey"` +} + +type CreateIamUserRequest struct { + AccountDetails + IamUserName string `json:"iamUserName"` + Tags []Tag `json:"tags,omitempty"` +} + +// CreateIamUserResponse is the response to the CLI client +type CreateIamUserResponse struct { + AccountDetails + BaseResponse + BaseIamUserResponse + CreateIamUserApiResponse +} + +//Used as options for create and update iamUser +type IamUserOptions struct { + IamUserName *string + Tags *[]Tag +} + +// DeleteIamUserRequest is used to represent the request body to delete LTKs +type DeleteIamUserRequest struct { + AccountDetails + IamUserName string `json:"iamUserName"` +} + +type DeleteIamUserResponse struct { + AccountDetails + BaseResponse + BaseIamUserResponse +} + +type UpdateIamUserRequest struct { + User struct { + Tags []Tag `json:"tags"` + } `json:"user"` +} + +type UpdateIamUserResponse struct { + BaseResponse + User IamUser `json:"item"` +} + +// GetIamUsers gets the LTKs for an account +// If no error is returned then you will receive a list of LTKs +func (c *Client) GetIamUsers() (*GetIamUsersResponse, *AlksError) { + log.Printf("[INFO] Getting long term keys") + + accountID, err := c.AccountDetails.GetAccountNumber() + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: fmt.Errorf("Error reading Account value: %s", err), + } + } + + roleName, err := c.AccountDetails.GetRoleName(false) + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: fmt.Errorf("Error reading Role value: %s", err), + } + } + + req, err := c.NewRequest(nil, "GET", "/ltks/"+accountID+"/"+roleName) + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: err, + } + } + + resp, err := c.http.Do(req) + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: err, + } + } + + reqID := GetRequestID(resp) + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + keyErr := new(AlksResponseError) + err = decodeBody(resp, &keyErr) + if err != nil { + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf(ParseErrorReqId, reqID, err), + } + } + + if keyErr.Errors != nil { + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf(ErrorStringFull, reqID, resp.StatusCode, strings.Join(keyErr.Errors, ", ")), + } + } + + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf(ErrorStringOnlyCodeAndReqId, reqID, resp.StatusCode), + } + } + + cr := new(GetIamUsersResponse) + err = decodeBody(resp, &cr) + + if err != nil { + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf("Error parsing GetLongTermKeysResponse: [%s] %s", reqID, err), + } + } + + return cr, nil +} + +// GetIamUser gets a single LTK for an account +// If no error is returned, then you will receive an LTK for the given account. +func (c *Client) GetIamUser(iamUsername string) (*GetIamUserResponse, *AlksError) { + log.Printf("[INFO] Getting long term key") + + accountID, err := c.AccountDetails.GetAccountNumber() + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: fmt.Errorf("Error reading Account value: %s", err), + } + } + + req, err := c.NewRequest(nil, "GET", "/iam-users/id/"+accountID+"/"+iamUsername) + + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: fmt.Errorf("Error creating request object: %s", err), + } + } + + resp, err := c.http.Do(req) + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: fmt.Errorf("Error during request: %s", err), + } + } + + reqID := GetRequestID(resp) + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + keyErr := new(AlksResponseError) + err = decodeBody(resp, &keyErr) + if err != nil { + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf(ParseErrorReqId, reqID, err), + } + } + + if keyErr.Errors != nil { + if reqID := GetRequestID(resp); reqID != "" { + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf(ErrorStringFull, reqID, resp.StatusCode, keyErr.Errors), + } + } + } + + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf(ErrorStringOnlyCodeAndReqId, reqID, resp.StatusCode), + } + } + + cr := new(GetIamUserResponse) + err = decodeBody(resp, &cr) + + if err != nil { + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf("error parsing GetLongTermKeyResponse: [%s] %s", reqID, err), + } + } + + return cr, nil +} + +func NewCreateIamUserRequest(options *IamUserOptions) (*CreateIamUserRequest, error) { + if options.IamUserName == nil { + return nil, fmt.Errorf("IamUserName option must not be nil") + } + + iamUser := &CreateIamUserRequest{} + iamUser.IamUserName = *options.IamUserName + + if options.Tags != nil { + iamUser.Tags = *options.Tags + } else { + iamUser.Tags = nil + } + + return iamUser, nil +} + +// CreateIamUser creates an iamUser and secret key for an account. +// If no error is returned, then you will receive an appropriate success message. +func (c *Client) CreateIamUser(options *IamUserOptions) (*CreateIamUserResponse, *AlksError) { + request, err := NewCreateIamUserRequest(options) + + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: err, + } + } + log.Printf("[INFO] Creating long term key: %s", *options.IamUserName) + + request.AccountDetails = c.AccountDetails + + log.Printf("[INFO] The request body is %v", *request) + + b, err := json.Marshal(struct { + CreateIamUserRequest + }{*request}) + + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: fmt.Errorf("error encoding LTK create JSON: %s", err), + } + } + + log.Printf("[INFO] Request Body: %v", string(b)) + + req, err := c.NewRequest(b, "POST", "/accessKeys") + + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: err, + } + } + + resp, err := c.http.Do(req) + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: err, + } + } + + reqID := GetRequestID(resp) + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + keyErr := new(AlksResponseError) + err = decodeBody(resp, &keyErr) + + if err != nil { + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf(ParseErrorReqId, reqID, err), + } + } + + if keyErr.Errors != nil { + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf(ErrorStringFull, reqID, resp.StatusCode, keyErr.Errors), + } + } + + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf(ErrorStringOnlyCodeAndReqId, reqID, resp.StatusCode), + } + } + + cr := new(CreateIamUserResponse) + err = decodeBody(resp, &cr) + + if err != nil { + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf("error parsing CreateLongTermKeyResponse: [%s] %s", reqID, err), + } + } + return cr, nil +} + +// DeleteIamUser deletes an LTK user for an account. +// If no error is returned, then you will receive an appropriate success message. +func (c *Client) DeleteIamUser(iamUsername string) (*DeleteIamUserResponse, *AlksError) { + log.Printf("[INFO] Deleting long term key: %s", iamUsername) + + request := DeleteIamUserRequest{ + AccountDetails: c.AccountDetails, + IamUserName: iamUsername, + } + + reqBody, err := json.Marshal(request) + + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: fmt.Errorf("error encoding iamUser delete JSON: %s", err), + } + } + + req, err := c.NewRequest(reqBody, "DELETE", "/IAMUser") + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: err, + } + } + + resp, err := c.http.Do(req) + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: err, + } + } + + reqID := GetRequestID(resp) + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + keyErr := new(AlksResponseError) + err = decodeBody(resp, &keyErr) + if err != nil { + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf(ParseErrorReqId, reqID, err), + } + } + + if keyErr.Errors != nil { + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf(ErrorStringNoReqId, resp.StatusCode, keyErr.Errors), + } + } + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf(ErrorStringOnlyCodeAndReqId, reqID, resp.StatusCode), + } + } + + cr := new(DeleteIamUserResponse) + err = decodeBody(resp, &cr) + + if err != nil { + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf("error parsing DeleteLongTermKeyResponse: [%s] %s", reqID, err), + } + } + return cr, nil +} + +func NewUpdateIamUserRequest(options *IamUserOptions) (*UpdateIamUserRequest, error) { + if options.IamUserName == nil { + return nil, fmt.Errorf("IamUserName option must not be nil") + } else if *options.IamUserName == "" { + return nil, fmt.Errorf("IamUserName must contain a value") + } + + iamUser := &UpdateIamUserRequest{} + + if options.Tags != nil { + iamUser.User.Tags = *options.Tags + } else { + return nil, fmt.Errorf("Tags must not be nil on update request, include empty list to remove all non-protected tags") + } + + return iamUser, nil +} + +func (c *Client) UpdateIamUser(options *IamUserOptions) (*UpdateIamUserResponse, *AlksError) { + request, err := NewUpdateIamUserRequest(options) + + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: err, + } + } + + log.Printf("[INFO] update IamUser %s with Tags: %v", *options.IamUserName, *options.Tags) + + accountID, err := c.AccountDetails.GetAccountNumber() + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: fmt.Errorf("Error reading Account value: %s", err), + } + } + + b, err := json.Marshal(struct { + UpdateIamUserRequest + }{*request}) + + log.Printf("[INFO] Request Body %v:\n", string(b)) + + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: err, + } + } + req, err := c.NewRequest(b, "PATCH", "/iam-users/id/"+accountID+"/"+*options.IamUserName) + + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: err, + } + } + resp, err := c.http.Do(req) + if err != nil { + return nil, &AlksError{ + StatusCode: 0, + RequestId: "", + Err: err, + } + } + + reqID := GetRequestID(resp) + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + updateErr := new(AlksResponseError) + err = decodeBody(resp, &updateErr) + + if err != nil { + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf(ParseErrorReqId, reqID, err), + } + } + + if updateErr.Errors != nil { + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf(ErrorStringNoReqId, resp.StatusCode, updateErr.Errors), + } + } + + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf(ErrorStringOnlyCodeAndReqId, reqID, resp.StatusCode), + } + + } + + respObj := &UpdateIamUserResponse{} + if err = decodeBody(resp, respObj); err != nil { + return nil, &AlksError{ + StatusCode: resp.StatusCode, + RequestId: reqID, + Err: fmt.Errorf("error parsing update ltk response: %s", err), + } + } + + return respObj, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3b84a465..568b3d13 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# github.com/Cox-Automotive/alks-go v0.0.0-20221010204605-136b6e9b6530 +# github.com/Cox-Automotive/alks-go v0.0.0-20221019181202-84b27abafb6b ## explicit; go 1.16 github.com/Cox-Automotive/alks-go # github.com/agext/levenshtein v1.2.2 @@ -370,4 +370,3 @@ google.golang.org/protobuf/types/known/anypb google.golang.org/protobuf/types/known/durationpb google.golang.org/protobuf/types/known/emptypb google.golang.org/protobuf/types/known/timestamppb -# github.com/Cox-Automotive/alks-go => /Users/Zachary.Elliott/code/alks-go From 023a0e007bbb8a40793d71496e641cfef7fed77a Mon Sep 17 00:00:00 2001 From: Zack Elliott Date: Wed, 19 Oct 2022 14:21:28 -0500 Subject: [PATCH 3/3] Documentation --- docs/guides/local_installation.md | 10 +++++----- docs/resources/alks_ltk.md | 11 +++++++++++ examples/alks.tf | 3 +++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/docs/guides/local_installation.md b/docs/guides/local_installation.md index 7099d5cb..7cb4d46a 100644 --- a/docs/guides/local_installation.md +++ b/docs/guides/local_installation.md @@ -47,11 +47,11 @@ mkdir -p ~/.terraform.d/plugins && **One-liner download for macOS / Linux:** ```sh -mkdir -p ~/.terraform.d/plugins/Cox-Automotive/engineering-enablement/alks/2.6.1/darwin_amd64 && - curl -Ls https://api.github.com/repos/Cox-Automotive/terraform-provider-alks/releases | jq -r --arg release "v2.6.1" --arg arch "$(uname -s | tr A-Z a-z)" '.[] | select(.tag_name | contains($release)) | .assets[]| select(.browser_download_url | contains($arch)) | select(.browser_download_url | contains("amd64")) | .browser_download_url' | - xargs -n 1 curl -Lo ~/.terraform.d/plugins/Cox-Automotive/engineering-enablement/alks/2.6.1/darwin_amd64/terraform-provider-alks.zip && - pushd ~/.terraform.d/plugins/Cox-Automotive/engineering-enablement/alks/2.6.1/darwin_amd64 && - unzip ~/.terraform.d/plugins/Cox-Automotive/engineering-enablement/alks/2.6.1/darwin_amd64/terraform-provider-alks.zip -d terraform-provider-alks-tmp && +mkdir -p ~/.terraform.d/plugins/Cox-Automotive/engineering-enablement/alks/2.7.0/darwin_amd64 && + curl -Ls https://api.github.com/repos/Cox-Automotive/terraform-provider-alks/releases | jq -r --arg release "v2.7.0" --arg arch "$(uname -s | tr A-Z a-z)" '.[] | select(.tag_name | contains($release)) | .assets[]| select(.browser_download_url | contains($arch)) | select(.browser_download_url | contains("amd64")) | .browser_download_url' | + xargs -n 1 curl -Lo ~/.terraform.d/plugins/Cox-Automotive/engineering-enablement/alks/2.7.0/darwin_amd64/terraform-provider-alks.zip && + pushd ~/.terraform.d/plugins/Cox-Automotive/engineering-enablement/alks/2.7.0/darwin_amd64 && + unzip ~/.terraform.d/plugins/Cox-Automotive/engineering-enablement/alks/2.7.0/darwin_amd64/terraform-provider-alks.zip -d terraform-provider-alks-tmp && mv terraform-provider-alks-tmp/terraform-provider-alks* . && chmod +x terraform-provider-alks* && rm -rf terraform-provider-alks-tmp && diff --git a/docs/resources/alks_ltk.md b/docs/resources/alks_ltk.md index 4d469b27..c194af07 100644 --- a/docs/resources/alks_ltk.md +++ b/docs/resources/alks_ltk.md @@ -11,10 +11,21 @@ resource "alks_ltk" "test_ltk_user" { } ``` +### ALKS IAM Role Creation With Tags +```hcl +resource "alks_ltk" "test_ltk_user" { + iam_username = "My_LTK_User_Name" + tags = { + "tagKey" = "tagValue" + } +} +``` + ## Argument Reference The following arguments are supported: * `iam_username` - (Required) The name of the IAM user to create. This parameter allows a string of characters consisting of upper and lowercase alphanumeric characters with no spaces. You can also include any of the following characters: =,.@-. User names are not distinguished by case. +* `tags` - (Optional) If present, will add specified tags onto role. * `iam_user_arn` - (Computed) The ARN associated with the LTK user. * `access_key` - (Computed) Generated access key for the LTK user. Note: This is saved in the state file, so please be aware of this. * `secret_key` - (Computed) Generated secret key for the LTK user. Note: This is saved in the state file, so please be aware of this. diff --git a/examples/alks.tf b/examples/alks.tf index c64fd143..e829f4d6 100644 --- a/examples/alks.tf +++ b/examples/alks.tf @@ -102,4 +102,7 @@ resource "aws_iam_role_policy_attachment" "sr-attach" { # CREATE LTK USER resource "alks_ltk" "ltk" { iam_username = "TEST-LTK-USER" + tags = { + TagKey = "TagValue" + } }