diff --git a/go.mod b/go.mod index f862c91c0..94de91860 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-hclog v1.0.0 github.com/hashicorp/go-multierror v1.1.1 + github.com/hashicorp/go-retryablehttp v0.7.0 github.com/hashicorp/go-secure-stdlib/awsutil v0.1.5 github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2 github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.0 diff --git a/util/util.go b/util/util.go index 97a75166e..20ca23406 100644 --- a/util/util.go +++ b/util/util.go @@ -1,9 +1,11 @@ package util import ( + "context" "encoding/json" "fmt" "log" + "net/http" "os" "reflect" "regexp" @@ -11,9 +13,11 @@ import ( "testing" "time" + "github.com/hashicorp/go-retryablehttp" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/vault/api" ) func JsonDiffSuppress(k, old, new string, d *schema.ResourceData) bool { @@ -328,3 +332,53 @@ func PathParameters(endpoint, vaultPath string) (map[string]string, error) { } return result, nil } + +// StatusCheckRetry for any response having a status code in statusCode. +func StatusCheckRetry(statusCodes ...int) retryablehttp.CheckRetry { + return func(ctx context.Context, resp *http.Response, err error) (bool, error) { + // ensure that the client controlled consistency policy is honoured. + if retry, err := api.DefaultRetryPolicy(ctx, resp, err); err != nil || retry { + return retry, err + } + + if resp != nil { + for _, code := range statusCodes { + if code == resp.StatusCode { + return true, nil + } + } + } + return false, nil + } +} + +// SetupCCCRetryClient for handling Client Controlled Consistency related +// requests. +func SetupCCCRetryClient(client *api.Client, maxRetry int) { + if !client.ReadYourWrites() { + client.SetReadYourWrites(true) + } + + client.SetMaxRetries(maxRetry) + client.SetCheckRetry(StatusCheckRetry(http.StatusNotFound)) + + // ensure that the clone has the reasonable backoff min/max durations set. + if client.MinRetryWait() == 0 { + client.SetMinRetryWait(time.Millisecond * 1000) + } + if client.MaxRetryWait() == 0 { + client.SetMaxRetryWait(time.Millisecond * 1500) + } + if client.MaxRetryWait() < client.MinRetryWait() { + client.SetMaxRetryWait(client.MinRetryWait()) + } + + bo := retryablehttp.LinearJitterBackoff + client.SetBackoff(bo) + + to := time.Duration(0) + for i := 0; i < client.MaxRetries(); i++ { + to += bo(client.MaxRetryWait(), client.MaxRetryWait(), i, nil) + } + client.SetClientTimeout(to + time.Second*30) +} diff --git a/vault/provider.go b/vault/provider.go index ab8049328..359535409 100644 --- a/vault/provider.go +++ b/vault/provider.go @@ -29,8 +29,17 @@ const ( // versions of Vault. // We aim to deprecate items in this category. UnknownPath = "unknown" + + // DefaultMaxHTTPRetries is used for configuring the api.Client's MaxRetries. + DefaultMaxHTTPRetries = 2 + + // DefaultMaxHTTPRetriesCCC is used for configuring the api.Client's MaxRetries + // for Client Controlled Consistency related operations. + DefaultMaxHTTPRetriesCCC = 10 ) +var maxHTTPRetriesCCC int + // This is a global MutexKV for use within this provider. // Use this when you need to have multiple resources or even multiple instances // of the same resource write to the same path in Vault. @@ -157,16 +166,20 @@ func Provider() *schema.Provider { // significantly longer, so that any leases are revoked shortly // after Terraform has finished running. DefaultFunc: schema.EnvDefaultFunc("TERRAFORM_VAULT_MAX_TTL", 1200), - Description: "Maximum TTL for secret leases requested by this provider.", }, "max_retries": { - Type: schema.TypeInt, - Optional: true, - - DefaultFunc: schema.EnvDefaultFunc("VAULT_MAX_RETRIES", 2), + Type: schema.TypeInt, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("VAULT_MAX_RETRIES", DefaultMaxHTTPRetries), Description: "Maximum number of retries when a 5xx error code is encountered.", }, + "max_retries_ccc": { + Type: schema.TypeInt, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("VAULT_MAX_RETRIES_CCC", DefaultMaxHTTPRetriesCCC), + Description: "Maximum number of retries for Client Controlled Consistency related operations", + }, "namespace": { Type: schema.TypeString, Optional: true, @@ -757,6 +770,9 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { // enable ReadYourWrites to support read-after-write on Vault Enterprise clientConfig.ReadYourWrites = true + // set default MaxRetries + clientConfig.MaxRetries = DefaultMaxHTTPRetries + client, err := api.NewClient(clientConfig) if err != nil { return nil, fmt.Errorf("failed to configure Vault API: %s", err) @@ -782,6 +798,8 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { client.SetMaxRetries(d.Get("max_retries").(int)) + maxHTTPRetriesCCC = d.Get("max_retries_ccc").(int) + // Try an get the token from the config or token helper token, err := providerToken(d) if err != nil { diff --git a/vault/resource_identity_entity.go b/vault/resource_identity_entity.go index ed682fb33..606402539 100644 --- a/vault/resource_identity_entity.go +++ b/vault/resource_identity_entity.go @@ -1,16 +1,20 @@ package vault import ( + "errors" "fmt" "log" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-provider-vault/util" "github.com/hashicorp/vault/api" + + "github.com/hashicorp/terraform-provider-vault/util" ) const identityEntityPath = "/identity/entity" +var errEntityNotFound = errors.New("entity not found") + func identityEntityResource() *schema.Resource { return &schema.Resource{ Create: identityEntityCreate, @@ -117,7 +121,6 @@ func identityEntityCreate(d *schema.ResourceData, meta interface{}) error { identityEntityUpdateFields(d, data, true) resp, err := client.Logical().Write(path, data) - if err != nil { return fmt.Errorf("error writing IdentityEntity to %q: %s", name, err) } @@ -155,7 +158,6 @@ func identityEntityUpdate(d *schema.ResourceData, meta interface{}) error { identityEntityUpdateFields(d, data, false) _, err := client.Logical().Write(path, data) - if err != nil { return fmt.Errorf("error updating IdentityEntity %q: %s", id, err) } @@ -168,14 +170,15 @@ func identityEntityRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*api.Client) id := d.Id() - resp, err := readIdentityEntity(client, id) + resp, err := readIdentityEntity(client, id, d.IsNewResource()) if err != nil { // We need to check if the secret_id has expired - if util.IsExpiredTokenErr(err) { + if resp == nil && util.IsExpiredTokenErr(err) { return nil } return fmt.Errorf("error reading IdentityEntity %q: %s", id, err) } + log.Printf("[DEBUG] Read IdentityEntity %s", id) if resp == nil { log.Printf("[WARN] IdentityEntity %q not found, removing from state", id) @@ -242,13 +245,10 @@ func identityEntityIDPath(id string) string { } func readIdentityEntityPolicies(client *api.Client, entityID string) ([]interface{}, error) { - resp, err := readIdentityEntity(client, entityID) + resp, err := readIdentityEntity(client, entityID, false) if err != nil { return nil, err } - if resp == nil { - return nil, fmt.Errorf("error IdentityEntity %s does not exist", entityID) - } if v, ok := resp.Data["policies"]; ok && v != nil { return v.([]interface{}), nil @@ -256,14 +256,37 @@ func readIdentityEntityPolicies(client *api.Client, entityID string) ([]interfac return make([]interface{}, 0), nil } -// May return nil if entity does not exist -func readIdentityEntity(client *api.Client, entityID string) (*api.Secret, error) { +func readIdentityEntity(client *api.Client, entityID string, retry bool) (*api.Secret, error) { path := identityEntityIDPath(entityID) - log.Printf("[DEBUG] Reading Entity %s from %q", entityID, path) + log.Printf("[DEBUG] Reading Entity %q from %q", entityID, path) + + return readEntity(client, path, retry) +} + +func readEntity(client *api.Client, path string, retry bool) (*api.Secret, error) { + log.Printf("[DEBUG] Reading Entity from %q", path) + + var err error + if retry { + client, err = client.Clone() + if err != nil { + return nil, err + } + util.SetupCCCRetryClient(client, maxHTTPRetriesCCC) + } resp, err := client.Logical().Read(path) if err != nil { - return resp, fmt.Errorf("failed reading IdentityEntity %s from %s", entityID, path) + return resp, fmt.Errorf("failed reading %q", path) + } + + if resp == nil { + return nil, fmt.Errorf("%w: %q", errEntityNotFound, path) } + return resp, nil } + +func isIdentityNotFoundError(err error) bool { + return err != nil && errors.Is(err, errEntityNotFound) +} diff --git a/vault/resource_identity_entity_policies.go b/vault/resource_identity_entity_policies.go index f22bb6fa5..d59d1054d 100644 --- a/vault/resource_identity_entity_policies.go +++ b/vault/resource_identity_entity_policies.go @@ -5,8 +5,9 @@ import ( "log" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-provider-vault/util" "github.com/hashicorp/vault/api" + + "github.com/hashicorp/terraform-provider-vault/util" ) func identityEntityPoliciesResource() *schema.Resource { @@ -96,7 +97,7 @@ func identityEntityPoliciesRead(d *schema.ResourceData, meta interface{}) error client := meta.(*api.Client) id := d.Id() - resp, err := readIdentityEntity(client, id) + resp, err := readIdentityEntity(client, id, d.IsNewResource()) if err != nil { return err } diff --git a/vault/resource_identity_entity_policies_test.go b/vault/resource_identity_entity_policies_test.go index ae54add59..bfbaa79bb 100644 --- a/vault/resource_identity_entity_policies_test.go +++ b/vault/resource_identity_entity_policies_test.go @@ -77,13 +77,13 @@ func testAccCheckidentityEntityPoliciesDestroy(s *terraform.State) error { continue } - entity, err := readIdentityEntity(client, rs.Primary.ID) - if err != nil { + if _, err := readIdentityEntity(client, rs.Primary.ID, false); err != nil { + if isIdentityNotFoundError(err) { + continue + } return err } - if entity == nil { - continue - } + apiPolicies, err := readIdentityEntityPolicies(client, rs.Primary.ID) if err != nil { return err diff --git a/vault/resource_identity_entity_test.go b/vault/resource_identity_entity_test.go index 8ec78ac19..779c0ccef 100644 --- a/vault/resource_identity_entity_test.go +++ b/vault/resource_identity_entity_test.go @@ -3,6 +3,9 @@ package vault import ( "encoding/json" "fmt" + "net" + "net/http" + "reflect" "strconv" "strings" "testing" @@ -259,3 +262,230 @@ resource "vault_identity_entity" "entity" { external_policies = true }`, entityName) } + +func TestReadEntity(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + path string + maxRetries int + expectedRetries int + wantError error + retryHandler *testRetryHandler + }{ + { + name: "retry-none", + retryHandler: &testRetryHandler{ + okAtCount: 1, + // retryStatus: http.StatusNotFound, + respData: []byte(`{"data": {"foo": "baz"}}`), + }, + maxRetries: 4, + expectedRetries: 0, + }, + { + name: "retry-ok-404", + retryHandler: &testRetryHandler{ + okAtCount: 3, + retryStatus: http.StatusNotFound, + respData: []byte(`{"data": {"foo": "baz"}}`), + }, + maxRetries: 4, + expectedRetries: 2, + }, + { + name: "retry-ok-412", + retryHandler: &testRetryHandler{ + okAtCount: 3, + retryStatus: http.StatusPreconditionFailed, + respData: []byte(`{"data": {"foo": "baz"}}`), + }, + maxRetries: 4, + expectedRetries: 2, + }, + { + name: "retry-exhausted-default-max-404", + path: identityEntityIDPath("retry-exhausted-default-max-404"), + retryHandler: &testRetryHandler{ + okAtCount: 0, + retryStatus: http.StatusNotFound, + }, + maxRetries: DefaultMaxHTTPRetriesCCC, + expectedRetries: DefaultMaxHTTPRetriesCCC, + wantError: fmt.Errorf(`%w: %q`, errEntityNotFound, + identityEntityIDPath("retry-exhausted-default-max-404")), + }, + { + name: "retry-exhausted-default-max-412", + path: identityEntityIDPath("retry-exhausted-default-max-412"), + retryHandler: &testRetryHandler{ + okAtCount: 0, + retryStatus: http.StatusPreconditionFailed, + }, + maxRetries: DefaultMaxHTTPRetriesCCC, + expectedRetries: DefaultMaxHTTPRetriesCCC, + wantError: fmt.Errorf(`failed reading %q`, + identityEntityIDPath("retry-exhausted-default-max-412")), + }, + { + name: "retry-exhausted-custom-max-404", + path: identityEntityIDPath("retry-exhausted-custom-max-404"), + retryHandler: &testRetryHandler{ + okAtCount: 0, + retryStatus: http.StatusNotFound, + }, + maxRetries: 5, + expectedRetries: 5, + wantError: fmt.Errorf(`%w: %q`, errEntityNotFound, + identityEntityIDPath("retry-exhausted-custom-max-404")), + }, + { + name: "retry-exhausted-custom-max-412", + path: identityEntityIDPath("retry-exhausted-custom-max-412"), + retryHandler: &testRetryHandler{ + okAtCount: 0, + retryStatus: http.StatusPreconditionFailed, + }, + maxRetries: 5, + expectedRetries: 5, + wantError: fmt.Errorf(`failed reading %q`, + identityEntityIDPath("retry-exhausted-custom-max-412")), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + maxHTTPRetriesCCC = DefaultMaxHTTPRetriesCCC + }() + maxHTTPRetriesCCC = tt.maxRetries + + r := tt.retryHandler + + config, ln := testHTTPServer(t, r.handler()) + defer ln.Close() + + config.Address = fmt.Sprintf("http://%s", ln.Addr()) + c, err := api.NewClient(config) + if err != nil { + t.Fatal(err) + } + + path := tt.path + if path == "" { + path = tt.name + } + + actualResp, err := readEntity(c, path, true) + + if tt.wantError != nil { + if err == nil { + t.Fatal("expected an error") + } + + if tt.wantError.Error() != err.Error() { + t.Errorf("expected err %q, actual %q", tt.wantError, err) + } + + if tt.retryHandler.retryStatus == http.StatusNotFound { + if !isIdentityNotFoundError(err) { + t.Errorf("expected an errEntityNotFound err %q, actual %q", errEntityNotFound, err) + } + } + } else { + if err != nil { + t.Fatal("unexpected error", err) + } + + var data map[string]interface{} + if err := json.Unmarshal(tt.retryHandler.respData, &data); err != nil { + t.Fatalf("invalid test data %#v, err=%s", tt.retryHandler.respData, err) + } + + expectedResp := &api.Secret{ + Data: data["data"].(map[string]interface{}), + } + + if !reflect.DeepEqual(expectedResp, actualResp) { + t.Errorf("expected secret %#v, actual %#v", expectedResp, actualResp) + } + } + + retries := r.requests - 1 + if tt.expectedRetries != retries { + t.Fatalf("expected %d retries, actual %d", tt.expectedRetries, retries) + } + }) + } +} + +func TestIsEntityNotFoundError(t *testing.T) { + tests := []struct { + name string + err error + expected bool + }{ + { + name: "default", + err: errEntityNotFound, + expected: true, + }, + { + name: "wrapped", + err: fmt.Errorf("%w: foo", errEntityNotFound), + expected: true, + }, + { + name: "not", + err: fmt.Errorf("%s: foo", errEntityNotFound), + expected: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := isIdentityNotFoundError(tt.err) + if actual != tt.expected { + t.Fatalf("isIdentityNotFoundError(): expected %v, actual %v", tt.expected, actual) + } + }) + } +} + +type testRetryHandler struct { + requests int + okAtCount int + respData []byte + retryStatus int +} + +func (t *testRetryHandler) handler() http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + t.requests++ + if t.okAtCount > 0 && (t.requests >= t.okAtCount) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(t.respData) + return + } else { + w.WriteHeader(t.retryStatus) + } + } +} + +// testHTTPServer creates a test HTTP server that handles requests until +// the listener returned is closed. +// XXX: copied from github.com/hashicorp/vault/api/client_test.go +func testHTTPServer(t *testing.T, handler http.Handler) (*api.Config, net.Listener) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("err: %s", err) + } + + server := &http.Server{Handler: handler} + go server.Serve(ln) + + config := api.DefaultConfig() + config.Address = fmt.Sprintf("http://%s", ln.Addr()) + + return config, ln +} diff --git a/vault/resource_identity_group.go b/vault/resource_identity_group.go index eb791624d..d6e4116ee 100644 --- a/vault/resource_identity_group.go +++ b/vault/resource_identity_group.go @@ -5,8 +5,9 @@ import ( "log" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-provider-vault/util" "github.com/hashicorp/vault/api" + + "github.com/hashicorp/terraform-provider-vault/util" ) const identityGroupPath = "/identity/group" @@ -176,7 +177,6 @@ func identityGroupCreate(d *schema.ResourceData, meta interface{}) error { } resp, err := client.Logical().Write(path, data) - if err != nil { return fmt.Errorf("error writing IdentityGroup to %q: %s", name, err) } @@ -216,7 +216,6 @@ func identityGroupUpdate(d *schema.ResourceData, meta interface{}) error { } _, err := client.Logical().Write(path, data) - if err != nil { return fmt.Errorf("error updating IdentityGroup %q: %s", id, err) } @@ -229,7 +228,7 @@ func identityGroupRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*api.Client) id := d.Id() - resp, err := readIdentityGroup(client, id) + resp, err := readIdentityGroup(client, id, d.IsNewResource()) if err != nil { // We need to check if the secret_id has expired if util.IsExpiredTokenErr(err) { @@ -285,7 +284,7 @@ func identityGroupExists(d *schema.ResourceData, meta interface{}) (bool, error) } log.Printf("[DEBUG] Checking if IdentityGroup %q exists", key) - resp, err := readIdentityGroup(client, id) + resp, err := readIdentityGroup(client, id, true) if err != nil { return true, fmt.Errorf("error checking if IdentityGroup %q exists: %s", key, err) } @@ -301,14 +300,11 @@ func identityGroupIDPath(id string) string { return fmt.Sprintf("%s/id/%s", identityGroupPath, id) } -func readIdentityGroupPolicies(client *api.Client, groupID string) ([]interface{}, error) { - resp, err := readIdentityGroup(client, groupID) +func readIdentityGroupPolicies(client *api.Client, groupID string, retry bool) ([]interface{}, error) { + resp, err := readIdentityGroup(client, groupID, retry) if err != nil { return nil, err } - if resp == nil { - return nil, fmt.Errorf("error IdentityGroup %s does not exist", groupID) - } if v, ok := resp.Data["policies"]; ok && v != nil { return v.([]interface{}), nil @@ -316,8 +312,8 @@ func readIdentityGroupPolicies(client *api.Client, groupID string) ([]interface{ return make([]interface{}, 0), nil } -func readIdentityGroupMemberEntityIds(client *api.Client, groupID string) ([]interface{}, error) { - resp, err := readIdentityGroup(client, groupID) +func readIdentityGroupMemberEntityIds(client *api.Client, groupID string, retry bool) ([]interface{}, error) { + resp, err := readIdentityGroup(client, groupID, retry) if err != nil { return nil, err } @@ -332,13 +328,9 @@ func readIdentityGroupMemberEntityIds(client *api.Client, groupID string) ([]int } // This function may return `nil` for the IdentityGroup if it does not exist -func readIdentityGroup(client *api.Client, groupID string) (*api.Secret, error) { +func readIdentityGroup(client *api.Client, groupID string, retry bool) (*api.Secret, error) { path := identityGroupIDPath(groupID) log.Printf("[DEBUG] Reading IdentityGroup %s from %q", groupID, path) - resp, err := client.Logical().Read(path) - if err != nil { - return resp, fmt.Errorf("failed reading IdentityGroup %s from %s", groupID, path) - } - return resp, nil + return readEntity(client, path, retry) } diff --git a/vault/resource_identity_group_member_entity_ids.go b/vault/resource_identity_group_member_entity_ids.go index e30a22d54..c6dc395c1 100644 --- a/vault/resource_identity_group_member_entity_ids.go +++ b/vault/resource_identity_group_member_entity_ids.go @@ -5,8 +5,9 @@ import ( "log" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-provider-vault/util" "github.com/hashicorp/vault/api" + + "github.com/hashicorp/terraform-provider-vault/util" ) func identityGroupMemberEntityIdsResource() *schema.Resource { @@ -61,7 +62,7 @@ func identityGroupMemberEntityIdsUpdate(d *schema.ResourceData, meta interface{} data := make(map[string]interface{}) memberEntityIds := d.Get("member_entity_ids").(*schema.Set).List() - resp, err := readIdentityGroup(client, id) + resp, err := readIdentityGroup(client, id, d.IsNewResource()) if err != nil { return err } @@ -71,7 +72,7 @@ func identityGroupMemberEntityIdsUpdate(d *schema.ResourceData, meta interface{} if d.Get("exclusive").(bool) { data["member_entity_ids"] = memberEntityIds } else { - apiMemberEntityIds, err := readIdentityGroupMemberEntityIds(client, id) + apiMemberEntityIds, err := readIdentityGroupMemberEntityIds(client, id, d.IsNewResource()) if err != nil { return err } @@ -104,16 +105,16 @@ func identityGroupMemberEntityIdsRead(d *schema.ResourceData, meta interface{}) client := meta.(*api.Client) id := d.Id() - resp, err := readIdentityGroup(client, id) + log.Printf("[DEBUG] Read IdentityGroupMemberEntityIds %s", id) + resp, err := readIdentityGroup(client, id, d.IsNewResource()) if err != nil { + if isIdentityNotFoundError(err) { + log.Printf("[WARN] IdentityGroupMemberEntityIds %q not found, removing from state", id) + d.SetId("") + return nil + } return err } - log.Printf("[DEBUG] Read IdentityGroupMemberEntityIds %s", id) - if resp == nil { - log.Printf("[WARN] IdentityGroupMemberEntityIds %q not found, removing from state", id) - d.SetId("") - return nil - } d.Set("group_id", id) d.Set("group_name", resp.Data["name"]) @@ -152,8 +153,11 @@ func identityGroupMemberEntityIdsDelete(d *schema.ResourceData, meta interface{} data := make(map[string]interface{}) - resp, err := readIdentityGroup(client, id) + resp, err := readIdentityGroup(client, id, false) if err != nil { + if isIdentityNotFoundError(err) { + return nil + } return err } @@ -162,7 +166,7 @@ func identityGroupMemberEntityIdsDelete(d *schema.ResourceData, meta interface{} if d.Get("exclusive").(bool) { data["member_entity_ids"] = make([]string, 0) } else { - apiMemberEntityIds, err := readIdentityGroupMemberEntityIds(client, id) + apiMemberEntityIds, err := readIdentityGroupMemberEntityIds(client, id, false) if err != nil { return err } diff --git a/vault/resource_identity_group_member_entity_ids_test.go b/vault/resource_identity_group_member_entity_ids_test.go index 85fc6cd34..514097feb 100644 --- a/vault/resource_identity_group_member_entity_ids_test.go +++ b/vault/resource_identity_group_member_entity_ids_test.go @@ -216,14 +216,14 @@ func testAccCheckidentityGroupMemberEntityIdsDestroy(s *terraform.State) error { continue } - group, err := readIdentityGroup(client, rs.Primary.ID) - if err != nil { + if _, err := readIdentityGroup(client, rs.Primary.ID, false); err != nil { + if isIdentityNotFoundError(err) { + continue + } return err } - if group == nil { - continue - } - apiMemberEntityIds, err := readIdentityGroupMemberEntityIds(client, rs.Primary.ID) + + apiMemberEntityIds, err := readIdentityGroupMemberEntityIds(client, rs.Primary.ID, false) if err != nil { return err } diff --git a/vault/resource_identity_group_policies.go b/vault/resource_identity_group_policies.go index fa14e7545..9f039015f 100644 --- a/vault/resource_identity_group_policies.go +++ b/vault/resource_identity_group_policies.go @@ -5,8 +5,9 @@ import ( "log" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-provider-vault/util" "github.com/hashicorp/vault/api" + + "github.com/hashicorp/terraform-provider-vault/util" ) func identityGroupPoliciesResource() *schema.Resource { @@ -64,7 +65,7 @@ func identityGroupPoliciesUpdate(d *schema.ResourceData, meta interface{}) error if d.Get("exclusive").(bool) { data["policies"] = policies } else { - apiPolicies, err := readIdentityGroupPolicies(client, id) + apiPolicies, err := readIdentityGroupPolicies(client, id, d.IsNewResource()) if err != nil { return err } @@ -96,7 +97,7 @@ func identityGroupPoliciesRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*api.Client) id := d.Id() - resp, err := readIdentityGroup(client, id) + resp, err := readIdentityGroup(client, id, d.IsNewResource()) if err != nil { return err } @@ -152,7 +153,7 @@ func identityGroupPoliciesDelete(d *schema.ResourceData, meta interface{}) error if d.Get("exclusive").(bool) { data["policies"] = make([]string, 0) } else { - apiPolicies, err := readIdentityGroupPolicies(client, id) + apiPolicies, err := readIdentityGroupPolicies(client, id, false) if err != nil { return err } diff --git a/vault/resource_identity_group_policies_test.go b/vault/resource_identity_group_policies_test.go index 1753f6abb..1d53e6c0b 100644 --- a/vault/resource_identity_group_policies_test.go +++ b/vault/resource_identity_group_policies_test.go @@ -74,14 +74,14 @@ func testAccCheckidentityGroupPoliciesDestroy(s *terraform.State) error { continue } - group, err := readIdentityGroup(client, rs.Primary.ID) - if err != nil { + if _, err := readIdentityGroup(client, rs.Primary.ID, false); err != nil { + if isIdentityNotFoundError(err) { + continue + } return err } - if group == nil { - continue - } - apiPolicies, err := readIdentityGroupPolicies(client, rs.Primary.ID) + + apiPolicies, err := readIdentityGroupPolicies(client, rs.Primary.ID, false) if err != nil { return err } diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index 88e30819c..a4746b5bc 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -160,9 +160,15 @@ variables in order to keep credential information out of the configuration. for the implications of this setting. * `max_retries` - (Optional) Used as the maximum number of retries when a 5xx - error code is encountered. Defaults to 2 retries and may be set via the + error code is encountered. Defaults to `2` retries and may be set via the `VAULT_MAX_RETRIES` environment variable. +* `max_retries_ccc` - (Optional) Maximum number of retries for _Client Controlled Consistency_ + related operations. Defaults to `10` retries and may also be set via the + `VAULT_MAX_RETRIES_CCC` environment variable. See + [Vault Eventual Consistency](https://www.vaultproject.io/docs/enterprise/consistency#vault-eventual-consistency) + for more information. + * `namespace` - (Optional) Set the namespace to use. May be set via the `VAULT_NAMESPACE` environment variable. *Available only for Vault Enterprise*.