From 8287c873fc58d46a0ee2f70ae754337a386e03c1 Mon Sep 17 00:00:00 2001 From: Myroslav Vivcharyk Date: Mon, 23 Dec 2024 17:14:11 +0100 Subject: [PATCH] feat(kafka_quota): added support for kafka quota --- Makefile | 7 +- go.mod | 2 +- go.sum | 2 + internal/acctest/plancheck.go | 98 +++++++ internal/acctest/template_test.go | 112 +++++++- .../sdkprovider/service/kafka/kafka_quota.go | 31 ++- .../service/kafka/kafka_quota_test.go | 249 ++++++++++++++---- 7 files changed, 426 insertions(+), 75 deletions(-) create mode 100644 internal/acctest/plancheck.go diff --git a/Makefile b/Makefile index e6eb454ac..a81334d07 100644 --- a/Makefile +++ b/Makefile @@ -112,10 +112,12 @@ lint: lint-go lint-test lint-docs lint-go: $(GOLANGCILINT) $(GOLANGCILINT) run --build-tags all --timeout=30m ./... +# Exclude files that use templates from linting +TERRAFMT_EXCLUDE = -not -path "./internal/acctest/*" \ + -not -path "./internal/sdkprovider/service/kafka/kafka_quota_test.go" lint-test: $(TERRAFMT) - $(TERRAFMT) diff ./internal -cfq - + find ./internal -type f $(TERRAFMT_EXCLUDE) -exec $(TERRAFMT) diff {} -cfq \; lint-docs: $(TFPLUGINDOCS) PROVIDER_AIVEN_ENABLE_BETA=1 $(TFPLUGINDOCS) generate --rendered-website-dir tmp @@ -132,7 +134,6 @@ lint-docs: $(TFPLUGINDOCS) fmt: fmt-test fmt-imports - fmt-test: $(TERRAFMT) $(TERRAFMT) fmt ./internal -fv diff --git a/go.mod b/go.mod index 68ecc7b81..b7d5a171c 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23 require ( github.com/aiven/aiven-go-client/v2 v2.33.0 - github.com/aiven/go-client-codegen v0.73.0 + github.com/aiven/go-client-codegen v0.74.0 github.com/avast/retry-go v3.0.0+incompatible github.com/dave/jennifer v1.7.1 github.com/docker/go-units v0.5.0 diff --git a/go.sum b/go.sum index 8a5c1aebf..9efb127a0 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/aiven/go-client-codegen v0.71.0 h1:SGiHrfbU8RiqVegQGV3BStnbIdFke+15lx github.com/aiven/go-client-codegen v0.71.0/go.mod h1:QKN/GgLMGWd6+gPEucXlZPi5vC3C6RpD3UeBRQOLI1Y= github.com/aiven/go-client-codegen v0.73.0 h1:1xk7zmAqKxQYHWE4ARWFlKHZg8FB4VTDGxVua7iruRg= github.com/aiven/go-client-codegen v0.73.0/go.mod h1:QKN/GgLMGWd6+gPEucXlZPi5vC3C6RpD3UeBRQOLI1Y= +github.com/aiven/go-client-codegen v0.74.0 h1:CqZUq8aGdhgcKJuM+YbC8RVUVgnCw9aA6yk0hMfvXWg= +github.com/aiven/go-client-codegen v0.74.0/go.mod h1:QKN/GgLMGWd6+gPEucXlZPi5vC3C6RpD3UeBRQOLI1Y= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= diff --git a/internal/acctest/plancheck.go b/internal/acctest/plancheck.go new file mode 100644 index 000000000..894a132e3 --- /dev/null +++ b/internal/acctest/plancheck.go @@ -0,0 +1,98 @@ +package acctest + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +type attributeChangeCheck struct { + resourceAddr string + attrs []string +} + +func ExpectOnlyAttributesChanged(resourceAddr string, attrs ...string) plancheck.PlanCheck { + return &attributeChangeCheck{ + resourceAddr: resourceAddr, + attrs: attrs, + } +} + +func (c *attributeChangeCheck) CheckPlan(_ context.Context, req plancheck.CheckPlanRequest, resp *plancheck.CheckPlanResponse) { + var targetResource *tfjson.ResourceChange + + // Find our resource in the changes + for _, rc := range req.Plan.ResourceChanges { + if rc.Address == c.resourceAddr { + targetResource = rc + break + } + } + + if targetResource == nil { + resp.Error = fmt.Errorf("resource %s not found in plan", c.resourceAddr) + return + } + + if targetResource.Change == nil { + resp.Error = fmt.Errorf("no changes found for resource %s", c.resourceAddr) + return + } + + // Convert Before and After to maps + before, ok := targetResource.Change.Before.(map[string]interface{}) + if !ok { + resp.Error = fmt.Errorf("before state for resource %s is not a map", c.resourceAddr) + return + } + + after, ok := targetResource.Change.After.(map[string]interface{}) + if !ok { + resp.Error = fmt.Errorf("after state for resource %s is not a map", c.resourceAddr) + + return + } + + // Create a set of expected changes + expectedChanges := make(map[string]struct{}) + for _, attr := range c.attrs { + expectedChanges[attr] = struct{}{} + } + + // Check all attributes in the after state + for key, afterValue := range after { + beforeValue, existsInBefore := before[key] + + // If value changed + if !existsInBefore || beforeValue != afterValue { + // Check if this change was expected + if _, expected := expectedChanges[key]; !expected { + resp.Error = fmt.Errorf( + "unexpected change in attribute %q for resource %s: before=%v, after=%v", + key, + c.resourceAddr, + beforeValue, + afterValue, + ) + return + } + // Remove from expected changes as we found it + delete(expectedChanges, key) + } + } + + // Check if all expected changes were found + if len(expectedChanges) > 0 { + remaining := make([]string, 0, len(expectedChanges)) + for attr := range expectedChanges { + remaining = append(remaining, attr) + } + resp.Error = fmt.Errorf( + "expected changes in attributes %v for resource %s were not found in plan", + remaining, + c.resourceAddr, + ) + } +} diff --git a/internal/acctest/template_test.go b/internal/acctest/template_test.go index 747ee483f..7ffdc769c 100644 --- a/internal/acctest/template_test.go +++ b/internal/acctest/template_test.go @@ -1,7 +1,7 @@ package acctest import ( - "regexp" + "fmt" "strings" "testing" @@ -182,6 +182,65 @@ resource "aiven_project" "example_project" { assert.Equal(t, normalizeHCL(expected), normalizeHCL(result)) } +func TestTemplateNilAndMissingValues(t *testing.T) { + registry := NewTemplateRegistry("test") + + templateStr := `resource "aiven_service" "example" { + project = "{{ .project }}" + service_name = "{{ .service_name }}" + {{- if .maintenance_window_dow }} + maintenance_window_dow = "{{ .maintenance_window_dow }}" + {{- end }} + {{- if .maintenance_window_time }} + maintenance_window_time = "{{ .maintenance_window_time }}" + {{- end }} +}` + + registry.MustAddTemplate(t, "service", templateStr) + + tests := []struct { + name string + config map[string]any + expected string + }{ + { + name: "explicit_nil_value", + config: map[string]any{ + "project": "test-project", + "service_name": "test-service", + "maintenance_window_dow": nil, + "maintenance_window_time": "10:00:00", + }, + expected: `resource "aiven_service" "example" { + project = "test-project" + service_name = "test-service" + maintenance_window_time = "10:00:00" +}`, + }, + { + name: "missing_key", + config: map[string]any{ + "project": "test-project", + "service_name": "test-service", + // maintenance_window_dow is not in config at all + "maintenance_window_time": "10:00:00", + }, + expected: `resource "aiven_service" "example" { + project = "test-project" + service_name = "test-service" + maintenance_window_time = "10:00:00" +}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := registry.MustRender(t, "service", tt.config) + assert.Equal(t, normalizeHCL(tt.expected), normalizeHCL(result)) + }) + } +} + func TestCompositionBuilder(t *testing.T) { tests := []struct { name string @@ -368,18 +427,51 @@ resource "aiven_kafka_user" "example_service_user" { // normalizeHCL function remains the same func normalizeHCL(s string) string { - // Remove all whitespace between blocks - s = regexp.MustCompile(`}\s+resource`).ReplaceAllString(s, "}\nresource") - s = regexp.MustCompile(`}\s+data`).ReplaceAllString(s, "}\ndata") + // Split into lines for processing + lines := strings.Split(s, "\n") + var normalized = make([]string, 0, len(lines)) - // Remove all empty lines - s = regexp.MustCompile(`(?m)^\s*$`).ReplaceAllString(s, "") + for _, line := range lines { + // Trim spaces from both ends + line = strings.TrimSpace(line) + + // Skip empty lines + if line == "" { + continue + } + + // Handle lines with just closing braces + if line == "}" { + normalized = append(normalized, line) + continue + } + + // For lines with content, normalize internal spacing + if strings.Contains(line, "=") { + // Split by = and trim spaces + parts := strings.Split(line, "=") + if len(parts) == 2 { + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + // Reconstruct with consistent spacing + line = fmt.Sprintf(" %s = %s", key, value) + } + } else if strings.HasPrefix(line, "resource") || strings.HasPrefix(line, "data") { + // Handle resource and data block declarations + line = strings.TrimSpace(line) + } else if !strings.HasPrefix(line, "}") { + // Add consistent indentation for other non-empty lines + line = " " + strings.TrimSpace(line) + } + + normalized = append(normalized, line) + } - // Remove leading/trailing whitespace - s = strings.TrimSpace(s) + // Join lines with newlines + result := strings.Join(normalized, "\n") // Normalize line endings - s = strings.ReplaceAll(s, "\r\n", "\n") + result = strings.ReplaceAll(result, "\r\n", "\n") - return s + return result } diff --git a/internal/sdkprovider/service/kafka/kafka_quota.go b/internal/sdkprovider/service/kafka/kafka_quota.go index 6299b57c4..35a265b9b 100644 --- a/internal/sdkprovider/service/kafka/kafka_quota.go +++ b/internal/sdkprovider/service/kafka/kafka_quota.go @@ -43,7 +43,6 @@ It is possible to set default quotas for each (user, client-id), user or client- "consumer_byte_rate": { Type: schema.TypeInt, Optional: true, - ForceNew: true, Description: ` Defines the bandwidth limit in bytes/sec for each group of clients sharing a quota. Every distinct client group is allocated a specific quota, as defined by the cluster, on a per-broker basis. @@ -53,7 +52,6 @@ Exceeding this limit results in client throttling.`, "producer_byte_rate": { Type: schema.TypeInt, Optional: true, - ForceNew: true, Description: ` Defines the bandwidth limit in bytes/sec for each group of clients sharing a quota. Every distinct client group is allocated a specific quota, as defined by the cluster, on a per-broker basis. @@ -63,7 +61,6 @@ Exceeding this limit results in client throttling.`, "request_percentage": { Type: schema.TypeInt, Optional: true, - ForceNew: true, Description: ` Sets the maximum percentage of CPU time that a client group can use on request handler I/O and network threads per broker within a quota window. Exceeding this limit triggers throttling. @@ -75,8 +72,9 @@ The quota, expressed as a percentage, also indicates the total allowable CPU usa func ResourceKafkaQuota() *schema.Resource { return &schema.Resource{ Description: "Creates and manages quotas for an Aiven for Apache Kafka® service user.", - CreateContext: common.WithGenClient(resourceKafkaQuotaCreate), ReadContext: common.WithGenClient(resourceKafkaQuotaRead), + CreateContext: common.WithGenClient(resourceKafkaQuotaCreate), + UpdateContext: common.WithGenClient(resourceKafkaQuotaUpdate), DeleteContext: common.WithGenClient(resourceKafkaQuotaDelete), Timeouts: schemautil.DefaultResourceTimeouts(), @@ -112,6 +110,31 @@ func resourceKafkaQuotaCreate(ctx context.Context, d *schema.ResourceData, clien return resourceKafkaQuotaRead(ctx, d, client) } +func resourceKafkaQuotaUpdate(ctx context.Context, d *schema.ResourceData, client avngen.Client) error { + var ( + req kafka.ServiceKafkaQuotaCreateIn + ) + + project, service, _, _, err := schemautil.SplitResourceID4(d.Id()) + if err != nil { + return err + } + + if err := schemautil.ResourceDataGet( + d, + &req, + schemautil.RenameAlias("client_id", "client-id"), + ); err != nil { + return err + } + + if err := client.ServiceKafkaQuotaCreate(ctx, project, service, &req); err != nil { + return err + } + + return resourceKafkaQuotaRead(ctx, d, client) +} + func resourceKafkaQuotaRead(ctx context.Context, d *schema.ResourceData, client avngen.Client) error { project, serviceName, clientID, user, err := schemautil.SplitResourceID4(d.Id()) if err != nil { diff --git a/internal/sdkprovider/service/kafka/kafka_quota_test.go b/internal/sdkprovider/service/kafka/kafka_quota_test.go index 2a808f0e0..b1e794dd1 100644 --- a/internal/sdkprovider/service/kafka/kafka_quota_test.go +++ b/internal/sdkprovider/service/kafka/kafka_quota_test.go @@ -10,6 +10,7 @@ import ( "github.com/aiven/go-client-codegen/handler/kafka" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" acc "github.com/aiven/terraform-provider-aiven/internal/acctest" @@ -46,40 +47,25 @@ resource "aiven_kafka" "bar" { maintenance_window_time = "10:00:00" }`) - registry.MustAddTemplate(t, "kafka_quota_full", ` + registry.MustAddTemplate(t, "kafka_quota", ` resource "aiven_kafka_quota" "{{ .resource_name }}" { - project = data.aiven_project.foo.project - service_name = aiven_kafka.bar.service_name - user = "{{ .user }}" - client_id = "{{ .client_id }}" - consumer_byte_rate = {{ .consumer_byte_rate }} - producer_byte_rate = {{ .producer_byte_rate }} - request_percentage = {{ .request_percentage }} -}`) - - registry.MustAddTemplate(t, "kafka_quota_user", ` -resource "aiven_kafka_quota" "{{ .resource_name }}" { - project = data.aiven_project.foo.project - service_name = aiven_kafka.bar.service_name - user = "{{ .user }}" - request_percentage = {{ .request_percentage }} -}`) - - registry.MustAddTemplate(t, "kafka_quota_client_id", ` -resource "aiven_kafka_quota" "{{ .resource_name }}" { - project = data.aiven_project.foo.project - service_name = aiven_kafka.bar.service_name - client_id = "{{ .client_id }}" - producer_byte_rate = {{ .producer_byte_rate }} -}`) - - registry.MustAddTemplate(t, "wrong_configuration", ` -resource "aiven_kafka_quota" "{{ .resource_name }}" { - project = data.aiven_project.foo.project - service_name = aiven_kafka.bar.service_name - consumer_byte_rate = {{ .consumer_byte_rate }} - producer_byte_rate = {{ .producer_byte_rate }} - request_percentage = {{ .request_percentage }} + project = data.aiven_project.foo.project + service_name = aiven_kafka.bar.service_name + {{- if .user }} + user = "{{ .user }}" + {{- end }} + {{- if .client_id }} + client_id = "{{ .client_id }}" + {{- end }} + {{- if .consumer_byte_rate }} + consumer_byte_rate = {{ .consumer_byte_rate }} + {{- end }} + {{- if .producer_byte_rate }} + producer_byte_rate = {{ .producer_byte_rate }} + {{- end }} + {{- if .request_percentage }} + request_percentage = {{ .request_percentage }} + {{- end }} }`) var newComposition = func() *acc.CompositionBuilder { @@ -98,21 +84,22 @@ resource "aiven_kafka_quota" "{{ .resource_name }}" { Steps: []resource.TestStep{ { Config: newComposition(). - Add("kafka_quota_full", map[string]any{ + Add("kafka_quota", map[string]any{ "resource_name": "full", "service_name": serviceName, "user": user, "client_id": clientID, "consumer_byte_rate": 1000, "producer_byte_rate": 1000, - "request_percentage": 101, + "request_percentage": 101, // invalid value }). MustRender(t), ExpectError: regexp.MustCompile(`expected .+ to be in the range \(\d+ - \d+\), got \d+`), }, { + // missing user and client_id Config: newComposition(). - Add("wrong_configuration", map[string]any{ + Add("kafka_quota", map[string]any{ "resource_name": "full", "service_name": serviceName, "consumer_byte_rate": 1000, @@ -123,8 +110,9 @@ resource "aiven_kafka_quota" "{{ .resource_name }}" { ExpectError: regexp.MustCompile(`at least one of user or client_id must be specified`), }, { + // valid configuration Config: newComposition(). - Add("kafka_quota_full", map[string]any{ + Add("kafka_quota", map[string]any{ "resource_name": "full", "service_name": serviceName, "user": user, @@ -133,59 +121,206 @@ resource "aiven_kafka_quota" "{{ .resource_name }}" { "producer_byte_rate": 1000, "request_percentage": 10, }). - Add("kafka_quota_user", map[string]any{ - "resource_name": "user", + MustRender(t), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "project", projectName), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "service_name", serviceName), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "user", user), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "client_id", clientID), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "consumer_byte_rate", "1000"), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "producer_byte_rate", "1000"), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "request_percentage", "10"), + ), + }, + { + // check that the resource is not updated without changes + Config: newComposition(). + Add("kafka_quota", map[string]any{ + "resource_name": "full", "service_name": serviceName, "user": user, - "request_percentage": 20, + "client_id": clientID, + "consumer_byte_rate": 1000, + "producer_byte_rate": 1000, + "request_percentage": 10, + }). + MustRender(t), + PlanOnly: true, + ExpectNonEmptyPlan: false, + }, + { + // check plan that resource should be updated + Config: newComposition(). + Add("kafka_quota", map[string]any{ + "resource_name": "full", + "service_name": serviceName, + "user": user, + "client_id": clientID, + "consumer_byte_rate": 2000, + "producer_byte_rate": 2000, + "request_percentage": 100, }). MustRender(t), + PlanOnly: true, + ExpectNonEmptyPlan: true, + }, + { + // check that the update action is triggered, only changed attributes are updated + Config: newComposition(). + Add("kafka_quota", map[string]any{ + "resource_name": "full", + "service_name": serviceName, + "user": user, + "client_id": clientID, + "consumer_byte_rate": 3000, + "producer_byte_rate": 3000, + "request_percentage": 10, + }). + MustRender(t), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(fmt.Sprintf("%s.full", kafkaQuotaResource), plancheck.ResourceActionUpdate), + acc.ExpectOnlyAttributesChanged(fmt.Sprintf("%s.full", kafkaQuotaResource), "consumer_byte_rate", "producer_byte_rate"), + }, + }, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "project", projectName), resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "service_name", serviceName), resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "user", user), resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "client_id", clientID), - resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "consumer_byte_rate", "1000"), - resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "producer_byte_rate", "1000"), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "consumer_byte_rate", "3000"), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "producer_byte_rate", "3000"), resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "request_percentage", "10"), + ), + }, + { + // check that resource is replaced when user is updated + Config: newComposition(). + Add("kafka_quota", map[string]any{ + "resource_name": "full", + "service_name": serviceName, + "user": fmt.Sprintf("%s_updated", user), + "client_id": clientID, + "consumer_byte_rate": 3000, + "producer_byte_rate": 3000, + "request_percentage": 10, + }). + MustRender(t), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectNonEmptyPlan(), + plancheck.ExpectResourceAction(fmt.Sprintf("%s.full", kafkaQuotaResource), plancheck.ResourceActionReplace), + }, + }, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "project", projectName), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "service_name", serviceName), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "user", fmt.Sprintf("%s_updated", user)), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "client_id", clientID), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "consumer_byte_rate", "3000"), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "producer_byte_rate", "3000"), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "request_percentage", "10"), + ), + }, + { + // create new resource with only consumer_byte_rate set + Config: newComposition(). + Add("kafka_quota", map[string]any{ + "resource_name": "byte_rate", + "service_name": serviceName, + "user": user, + "client_id": clientID, + "consumer_byte_rate": 100, + }). + MustRender(t), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), "project", projectName), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), "service_name", serviceName), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), "user", user), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), "client_id", clientID), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), "consumer_byte_rate", "100"), - resource.TestCheckResourceAttr(fmt.Sprintf("%s.user", kafkaQuotaResource), "project", projectName), - resource.TestCheckResourceAttr(fmt.Sprintf("%s.user", kafkaQuotaResource), "service_name", serviceName), - resource.TestCheckResourceAttr(fmt.Sprintf("%s.user", kafkaQuotaResource), "user", user), - resource.TestCheckResourceAttr(fmt.Sprintf("%s.user", kafkaQuotaResource), "request_percentage", "20"), + resource.TestCheckNoResourceAttr(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), "producer_byte_rate"), + resource.TestCheckNoResourceAttr(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), "request_percentage"), ), }, { + // check that the resource is updated and only consumer_byte_rate was modified Config: newComposition(). - Add("kafka_quota_client_id", map[string]any{ - "resource_name": "client", + Add("kafka_quota", map[string]any{ + "resource_name": "byte_rate", "service_name": serviceName, + "user": user, "client_id": clientID, - "producer_byte_rate": 1000, + "consumer_byte_rate": 3000, }). MustRender(t), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), plancheck.ResourceActionUpdate), + acc.ExpectOnlyAttributesChanged(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), "consumer_byte_rate"), + }, + }, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(fmt.Sprintf("%s.client", kafkaQuotaResource), "project", projectName), - resource.TestCheckResourceAttr(fmt.Sprintf("%s.client", kafkaQuotaResource), "service_name", serviceName), - resource.TestCheckResourceAttr(fmt.Sprintf("%s.client", kafkaQuotaResource), "client_id", clientID), - resource.TestCheckResourceAttr(fmt.Sprintf("%s.client", kafkaQuotaResource), "producer_byte_rate", "1000"), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), "project", projectName), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), "service_name", serviceName), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), "user", user), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), "client_id", clientID), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), "consumer_byte_rate", "3000"), + + resource.TestCheckNoResourceAttr(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), "producer_byte_rate"), + resource.TestCheckNoResourceAttr(fmt.Sprintf("%s.byte_rate", kafkaQuotaResource), "request_percentage"), ), }, { - Taint: []string{fmt.Sprintf("%s.client", kafkaQuotaResource)}, + // craete multiple resources with different configurations Config: newComposition(). - Add("kafka_quota_client_id", map[string]any{ + Add("kafka_quota", map[string]any{ + "resource_name": "full", + "service_name": serviceName, + "user": user, + "client_id": clientID, + "consumer_byte_rate": 4000, + "producer_byte_rate": 4000, + "request_percentage": 40, + }). + Add("kafka_quota", map[string]any{ + "resource_name": "user", + "service_name": serviceName, + "user": user, + "request_percentage": 20, + }). + Add("kafka_quota", map[string]any{ "resource_name": "client", "service_name": serviceName, "client_id": clientID, - "producer_byte_rate": 1000, + "producer_byte_rate": 2000, }). MustRender(t), Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "project", projectName), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "service_name", serviceName), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "user", user), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "client_id", clientID), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "consumer_byte_rate", "4000"), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "producer_byte_rate", "4000"), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.full", kafkaQuotaResource), "request_percentage", "40"), + + resource.TestCheckResourceAttr(fmt.Sprintf("%s.user", kafkaQuotaResource), "project", projectName), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.user", kafkaQuotaResource), "service_name", serviceName), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.user", kafkaQuotaResource), "user", user), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.user", kafkaQuotaResource), "request_percentage", "20"), + resource.TestCheckNoResourceAttr(fmt.Sprintf("%s.user", kafkaQuotaResource), "client_id"), + resource.TestCheckNoResourceAttr(fmt.Sprintf("%s.user", kafkaQuotaResource), "consumer_byte_rate"), + resource.TestCheckNoResourceAttr(fmt.Sprintf("%s.user", kafkaQuotaResource), "producer_byte_rate"), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.client", kafkaQuotaResource), "project", projectName), resource.TestCheckResourceAttr(fmt.Sprintf("%s.client", kafkaQuotaResource), "service_name", serviceName), resource.TestCheckResourceAttr(fmt.Sprintf("%s.client", kafkaQuotaResource), "client_id", clientID), - resource.TestCheckResourceAttr(fmt.Sprintf("%s.client", kafkaQuotaResource), "producer_byte_rate", "1000"), + resource.TestCheckResourceAttr(fmt.Sprintf("%s.client", kafkaQuotaResource), "producer_byte_rate", "2000"), + resource.TestCheckNoResourceAttr(fmt.Sprintf("%s.client", kafkaQuotaResource), "user"), + resource.TestCheckNoResourceAttr(fmt.Sprintf("%s.client", kafkaQuotaResource), "consumer_byte_rate"), + resource.TestCheckNoResourceAttr(fmt.Sprintf("%s.client", kafkaQuotaResource), "request_percentage"), ), }, },