Skip to content

Commit

Permalink
Don't force resource replacement when expiration is unset during SDK-…
Browse files Browse the repository at this point in the history
…PF migratino
  • Loading branch information
tobio committed Oct 24, 2024
1 parent f58fb83 commit 1ac9498
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 3 deletions.
2 changes: 1 addition & 1 deletion internal/clients/elasticsearch/security.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ func UpdateApiKey(apiClient *clients.ApiClient, apikey models.ApiKey) fwdiag.Dia
return utils.FrameworkDiagFromError(err)
}
defer res.Body.Close()
if diags := utils.CheckError(res, "Unable to create apikey"); diags.HasError() {
if diags := utils.CheckError(res, "Unable to update apikey"); diags.HasError() {
return utils.FrameworkDiagsFromSDK(diags)
}

Expand Down
99 changes: 99 additions & 0 deletions internal/elasticsearch/security/api_key/acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,82 @@ func SkipWhenApiKeysAreNotSupportedOrRestrictionsAreSupported(minApiKeySupported
}
}

func TestAccResourceSecurityApiKeyFromSDK(t *testing.T) {
// generate a random name
apiKeyName := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum)
var initialApiKey string

resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
CheckDestroy: checkResourceSecurityApiKeyDestroy,
Steps: []resource.TestStep{
{
// Create the api_key with the last provider version where the api_key resource was built on the SDK
ExternalProviders: map[string]resource.ExternalProvider{
"elasticstack": {
Source: "elastic/elasticstack",
VersionConstraint: "0.11.9",
},
},
ProtoV6ProviderFactories: acctest.Providers,
SkipFunc: versionutils.CheckIfVersionIsUnsupported(api_key.MinVersion),
Config: testAccResourceSecurityApiKeyWithoutExpiration(apiKeyName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_api_key.test", "name", apiKeyName),
resource.TestCheckResourceAttrWith("elasticstack_elasticsearch_security_api_key.test", "role_descriptors", func(testValue string) error {
var testRoleDescriptor map[string]models.ApiKeyRoleDescriptor
if err := json.Unmarshal([]byte(testValue), &testRoleDescriptor); err != nil {
return err
}

expectedRoleDescriptor := map[string]models.ApiKeyRoleDescriptor{
"role-a": {
Cluster: []string{"all"},
Indices: []models.IndexPerms{{
Names: []string{"index-a*"},
Privileges: []string{"read"},
AllowRestrictedIndices: utils.Pointer(false),
}},
},
}

if !reflect.DeepEqual(testRoleDescriptor, expectedRoleDescriptor) {
return fmt.Errorf("%v doesn't match %v", testRoleDescriptor, expectedRoleDescriptor)
}

return nil
}),
resource.TestCheckResourceAttrWith("elasticstack_elasticsearch_security_api_key.test", "api_key", func(value string) error {
initialApiKey = value

if value == "" {
return fmt.Errorf("expected api_key to be non-empty")
}

return nil
}),
resource.TestCheckResourceAttrSet("elasticstack_elasticsearch_security_api_key.test", "encoded"),
resource.TestCheckResourceAttrSet("elasticstack_elasticsearch_security_api_key.test", "id"),
),
},
{
ProtoV6ProviderFactories: acctest.Providers,
SkipFunc: versionutils.CheckIfVersionIsUnsupported(api_key.MinVersion),
Config: testAccResourceSecurityApiKeyWithoutExpiration(apiKeyName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrWith("elasticstack_elasticsearch_security_api_key.test", "api_key", func(value string) error {
if value != initialApiKey {
return fmt.Errorf("expected api_key to be unchanged")
}

return nil
}),
),
},
},
})
}

func testAccResourceSecurityApiKeyCreate(apiKeyName string) string {
return fmt.Sprintf(`
provider "elasticstack" {
Expand Down Expand Up @@ -291,6 +367,29 @@ resource "elasticstack_elasticsearch_security_api_key" "test" {
`, apiKeyName)
}

func testAccResourceSecurityApiKeyWithoutExpiration(apiKeyName string) string {
return fmt.Sprintf(`
provider "elasticstack" {
elasticsearch {}
}
resource "elasticstack_elasticsearch_security_api_key" "test" {
name = "%s"
role_descriptors = jsonencode({
role-a = {
cluster = ["all"]
indices = [{
names = ["index-a*"]
privileges = ["read"]
allow_restricted_indices = false
}]
}
})
}
`, apiKeyName)
}

func testAccResourceSecurityApiKeyRemoteIndices(apiKeyName string) string {
return fmt.Sprintf(`
provider "elasticstack" {
Expand Down
1 change: 1 addition & 0 deletions internal/elasticsearch/security/api_key/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
// Ensure provider defined types fully satisfy framework interfaces
var _ resource.Resource = &Resource{}
var _ resource.ResourceWithConfigure = &Resource{}
var _ resource.ResourceWithUpgradeState = &Resource{}
var (
MinVersion = version.Must(version.NewVersion("8.0.0")) // Enabled in 8.0
MinVersionWithUpdate = version.Must(version.NewVersion("8.4.0"))
Expand Down
15 changes: 13 additions & 2 deletions internal/elasticsearch/security/api_key/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ import (
providerschema "github.com/elastic/terraform-provider-elasticstack/internal/schema"
)

const currentSchemaVersion int64 = 1

func (r *Resource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = r.getSchema()
resp.Schema = r.getSchema(currentSchemaVersion)
}

func (r *Resource) getSchema() schema.Schema {
func (r *Resource) getSchema(version int64) schema.Schema {
return schema.Schema{
Version: version,
Description: "Creates an API key for access without requiring basic authentication. See, https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html",
Blocks: map[string]schema.Block{
"elasticsearch_connection": providerschema.GetEsFWConnectionBlock("elasticsearch_connection", false),
Expand Down Expand Up @@ -67,6 +70,14 @@ func (r *Resource) getSchema() schema.Schema {
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
// stringplanmodifier.RequiresReplaceIf(
// func(ctx context.Context, req planmodifier.StringRequest, resp *stringplanmodifier.RequiresReplaceIfFuncResponse) {
// // Checking ValueString ensures that expiration does not trigger replacement for "" != null
// resp.RequiresReplace = req.ConfigValue.ValueString() != req.StateValue.ValueString()
// },
// "Requires replace if the configured expiration value changes",
// "Requires replace if the configured expiration value changes",
// ),
},
},
"expiration_timestamp": schema.Int64Attribute{
Expand Down
30 changes: 30 additions & 0 deletions internal/elasticsearch/security/api_key/state_upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package api_key

import (
"context"

"github.com/elastic/terraform-provider-elasticstack/internal/utils"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)

func (r *Resource) UpgradeState(context.Context) map[int64]resource.StateUpgrader {
return map[int64]resource.StateUpgrader{
0: {
PriorSchema: utils.Pointer(r.getSchema(0)),
StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
var model tfModel
resp.Diagnostics.Append(req.State.Get(ctx, &model)...)
if resp.Diagnostics.HasError() {
return
}

if utils.IsKnown(model.Expiration) && model.Expiration.ValueString() == "" {
model.Expiration = basetypes.NewStringNull()
}

resp.State.Set(ctx, model)
},
},
}
}

0 comments on commit 1ac9498

Please sign in to comment.