Skip to content

Commit

Permalink
feat: add ibm_event_streams_schema_global_rule (#5621)
Browse files Browse the repository at this point in the history
* schema global compatibility rule

* review comments

* shorter name
  • Loading branch information
kccox authored Oct 22, 2024
1 parent 0fc55fc commit 4cc01dd
Show file tree
Hide file tree
Showing 7 changed files with 463 additions and 0 deletions.
2 changes: 2 additions & 0 deletions ibm/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ func Provider() *schema.Provider {
"ibm_dns_secondary": classicinfrastructure.DataSourceIBMDNSSecondary(),
"ibm_event_streams_topic": eventstreams.DataSourceIBMEventStreamsTopic(),
"ibm_event_streams_schema": eventstreams.DataSourceIBMEventStreamsSchema(),
"ibm_event_streams_schema_global_rule": eventstreams.DataSourceIBMEventStreamsSchemaGlobalCompatibilityRule(),
"ibm_event_streams_quota": eventstreams.DataSourceIBMEventStreamsQuota(),
"ibm_hpcs": hpcs.DataSourceIBMHPCS(),
"ibm_hpcs_managed_key": hpcs.DataSourceIbmManagedKey(),
Expand Down Expand Up @@ -1123,6 +1124,7 @@ func Provider() *schema.Provider {
"ibm_dns_record": classicinfrastructure.ResourceIBMDNSRecord(),
"ibm_event_streams_topic": eventstreams.ResourceIBMEventStreamsTopic(),
"ibm_event_streams_schema": eventstreams.ResourceIBMEventStreamsSchema(),
"ibm_event_streams_schema_global_rule": eventstreams.ResourceIBMEventStreamsSchemaGlobalCompatibilityRule(),
"ibm_event_streams_quota": eventstreams.ResourceIBMEventStreamsQuota(),
"ibm_firewall": classicinfrastructure.ResourceIBMFirewall(),
"ibm_firewall_policy": classicinfrastructure.ResourceIBMFirewallPolicy(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright IBM Corp. 2024 All Rights Reserved.
// Licensed under the Mozilla Public License v2.0

package eventstreams

import (
"context"
"fmt"
"log"
"strings"

"github.com/IBM-Cloud/terraform-provider-ibm/ibm/conns"
"github.com/IBM-Cloud/terraform-provider-ibm/ibm/flex"
"github.com/IBM/eventstreams-go-sdk/pkg/schemaregistryv1"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

const (
schemaGlobalCompatibilityRuleResourceType = "schema-global-compatibility-rule"
schemaCompatibilityRuleType = "COMPATIBILITY"
schemaCompatibilityDefaultValue = "NONE"
)

var (
schemaCompatiblityRuleValidConfigValues = []string{schemaCompatibilityDefaultValue, "FULL", "FULL_TRANSITIVE", "FORWARD", "FORWARD_TRANSITIVE", "BACKWARD", "BACKWARD_TRANSITIVE"}
)

// The global compatibility rule in an Event Streams service instance.
// The ID is the CRN with the last two components "schema-global-compatibility:".
func DataSourceIBMEventStreamsSchemaGlobalCompatibilityRule() *schema.Resource {
return &schema.Resource{
ReadContext: dataSourceIBMEventStreamsSchemaGlobalCompatibilityRuleRead,

Schema: map[string]*schema.Schema{
"resource_instance_id": {
Type: schema.TypeString,
Required: true,
Description: "The ID or CRN of the Event Streams service instance",
},
"config": {
Type: schema.TypeString,
Computed: true,
Description: "The value of the global schema compatibility rule",
},
},
}
}

// read global compatibility rule properties using the schema registry API
func dataSourceIBMEventStreamsSchemaGlobalCompatibilityRuleRead(context context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
schemaregistryClient, err := meta.(conns.ClientSession).ESschemaRegistrySession()
if err != nil {
tfErr := flex.TerraformErrorf(err, "Error getting Event Streams schema registry session", "ibm_event_streams_schema_global_rule", "read")
log.Printf("[DEBUG]\n%s", tfErr.GetDebugMessage())
return tfErr.GetDiag()
}
adminURL, instanceCRN, err := getSchemaRuleInstanceURL(d, meta)
if err != nil {
tfErr := flex.TerraformErrorf(err, "Error getting Event Streams schema registry URL", "ibm_event_streams_schema_global_rule", "read")
log.Printf("[DEBUG]\n%s", tfErr.GetDebugMessage())
return tfErr.GetDiag()
}
schemaregistryClient.SetServiceURL(adminURL)

getOpts := &schemaregistryv1.GetGlobalRuleOptions{}
getOpts.SetRule(schemaCompatibilityRuleType)
rule, _, err := schemaregistryClient.GetGlobalRuleWithContext(context, getOpts)
if err != nil {
tfErr := flex.TerraformErrorf(err, "GetGlobalRule returned error", "ibm_event_streams_schema_global_rule", "read")
log.Printf("[DEBUG]\n%s", tfErr.GetDebugMessage())
return tfErr.GetDiag()
}
if rule.Config == nil {
tfErr := flex.TerraformErrorf(err, "Unexpected nil config when getting global compatibility rule", "ibm_event_streams_schema_global_rule", "read")
log.Printf("[DEBUG]\n%s", tfErr.GetDebugMessage())
return tfErr.GetDiag()
}
d.SetId(getSchemaGlobalCompatibilityRuleID(instanceCRN))
d.Set("resource_instance_id", instanceCRN)
d.Set("config", *rule.Config)
return nil
}

func getSchemaRuleInstanceURL(d *schema.ResourceData, meta interface{}) (string, string, error) {
instanceCRN := d.Get("resource_instance_id").(string)
if instanceCRN == "" { // importing
id := d.Id()
crnSegments := strings.Split(id, ":")
if len(crnSegments) != 10 || crnSegments[8] != schemaGlobalCompatibilityRuleResourceType {
return "", "", fmt.Errorf("ID '%s' is not a schema global compatibility resource", id)
}
crnSegments[8] = ""
crnSegments[9] = ""
instanceCRN = strings.Join(crnSegments, ":")
d.Set("resource_instance_id", instanceCRN)
}

instance, err := getInstanceDetails(instanceCRN, meta)
if err != nil {
return "", "", err
}
adminURL := instance.Extensions["kafka_http_url"].(string)
planID := *instance.ResourcePlanID
valid := strings.Contains(planID, "enterprise")
if !valid {
return "", "", fmt.Errorf("schema registry is not supported by the Event Streams %s plan, enterprise plan is expected",
planID)
}
return adminURL, instanceCRN, nil
}

func getSchemaGlobalCompatibilityRuleID(instanceCRN string) string {
crnSegments := strings.Split(instanceCRN, ":")
crnSegments[8] = schemaGlobalCompatibilityRuleResourceType
crnSegments[9] = ""
return strings.Join(crnSegments, ":")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright IBM Corp. 2024 All Rights Reserved.
// Licensed under the Mozilla Public License v2.0

package eventstreams_test

import (
"fmt"
"strings"
"testing"

acc "github.com/IBM-Cloud/terraform-provider-ibm/ibm/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func TestAccIBMEventStreamsSchemaGlobalCompatibilityRuleDataSource(t *testing.T) {
resource.Test(t, resource.TestCase{
Providers: acc.TestAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckIBMEventStreamsSchemaGlobalCompatibilityRuleDataSourceConfig(getTestInstanceName(mzrKey)),
Check: resource.ComposeTestCheckFunc(
testAccCheckIBMEventStreamsSchemaGlobalCompatibilityRuleProperties("data.ibm_event_streams_schema_global_rule.es_globalrule", ""),
),
},
},
})
}

func testAccCheckIBMEventStreamsSchemaGlobalCompatibilityRuleDataSourceConfig(instanceName string) string {
return fmt.Sprintf(`
data "ibm_resource_group" "group" {
is_default=true
}
data "ibm_resource_instance" "es_instance" {
resource_group_id = data.ibm_resource_group.group.id
name = "%s"
}
data "ibm_event_streams_schema_global_rule" "es_globalrule" {
resource_instance_id = data.ibm_resource_instance.es_instance.id
}`, instanceName)
}

// check properties of the global compatibility rule data source or resource object
func testAccCheckIBMEventStreamsSchemaGlobalCompatibilityRuleProperties(name string, expectConfig string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}
gcrID := rs.Primary.ID
if gcrID == "" {
return fmt.Errorf("[ERROR] Global compatibility rule ID is not set")
}
if !strings.HasSuffix(gcrID, ":schema-global-compatibility-rule:") {
return fmt.Errorf("[ERROR] Global compatibility rule ID %s not expected CRN", gcrID)
}
config := rs.Primary.Attributes["config"]
if config == "" {
return fmt.Errorf("[ERROR] Global compatibility config is not defined")
}
if expectConfig != "" && config != expectConfig {
return fmt.Errorf("[ERROR] Global compatibility config is %s, expected %s", config, expectConfig)
}
return nil
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright IBM Corp. 2024 All Rights Reserved.
// Licensed under the Mozilla Public License v2.0

package eventstreams

import (
"context"
"log"

"github.com/IBM-Cloud/terraform-provider-ibm/ibm/conns"
"github.com/IBM-Cloud/terraform-provider-ibm/ibm/flex"
"github.com/IBM/eventstreams-go-sdk/pkg/schemaregistryv1"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

// The global compatibility rule in an Event Streams service instance.
// The ID is the CRN with the last two components "schema-global-compatibility:".
// The rule is the schema compatibility rule, one of the validRules values.
func ResourceIBMEventStreamsSchemaGlobalCompatibilityRule() *schema.Resource {
return &schema.Resource{
CreateContext: resourceIBMEventStreamsSchemaGlobalCompatibilityRuleUpdate,
ReadContext: resourceIBMEventStreamsSchemaGlobalCompatibilityRuleRead,
UpdateContext: resourceIBMEventStreamsSchemaGlobalCompatibilityRuleUpdate,
DeleteContext: resourceIBMEventStreamsSchemaGlobalCompatibilityRuleDelete,
Importer: &schema.ResourceImporter{},

Schema: map[string]*schema.Schema{
"resource_instance_id": {
Type: schema.TypeString,
Description: "The ID or the CRN of the Event Streams service instance",
Required: true,
ForceNew: true,
},
"config": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(schemaCompatiblityRuleValidConfigValues, true),
Description: "The value of the global schema compatibility rule",
},
},
}
}

func resourceIBMEventStreamsSchemaGlobalCompatibilityRuleRead(context context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
return dataSourceIBMEventStreamsSchemaGlobalCompatibilityRuleRead(context, d, meta)
}

// The global compatibility rule is always defined in the schema registry,
// so create and update have the same behavior
func resourceIBMEventStreamsSchemaGlobalCompatibilityRuleUpdate(context context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
schemaregistryClient, err := meta.(conns.ClientSession).ESschemaRegistrySession()
if err != nil {
tfErr := flex.TerraformErrorf(err, "Error getting Event Streams schema registry session", "ibm_event_streams_schema_global_rule", "update")
log.Printf("[DEBUG]\n%s", tfErr.GetDebugMessage())
return tfErr.GetDiag()
}
adminURL, _, err := getSchemaRuleInstanceURL(d, meta)
if err != nil {
tfErr := flex.TerraformErrorf(err, "Error getting Event Streams schema registry URL", "ibm_event_streams_schema_global_rule", "update")
log.Printf("[DEBUG]\n%s", tfErr.GetDebugMessage())
return tfErr.GetDiag()
}
schemaregistryClient.SetServiceURL(adminURL)

updateOpts := &schemaregistryv1.UpdateGlobalRuleOptions{}
updateOpts.SetType(schemaCompatibilityRuleType)
updateOpts.SetRule(schemaCompatibilityRuleType)
updateOpts.SetConfig(d.Get("config").(string))

_, _, err = schemaregistryClient.UpdateGlobalRuleWithContext(context, updateOpts)
if err != nil {
tfErr := flex.TerraformErrorf(err, "UpdateGlobalRule returned error", "ibm_event_streams_schema_global_rule", "update")
log.Printf("[DEBUG]\n%s", tfErr.GetDebugMessage())
return tfErr.GetDiag()
}
return resourceIBMEventStreamsSchemaGlobalCompatibilityRuleRead(context, d, meta)
}

// The global rule can't be deleted, but we reset it to the default NONE.
func resourceIBMEventStreamsSchemaGlobalCompatibilityRuleDelete(context context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
schemaregistryClient, err := meta.(conns.ClientSession).ESschemaRegistrySession()
if err != nil {
tfErr := flex.TerraformErrorf(err, "Error getting Event Streams schema registry session", "ibm_event_streams_schema_global_rule", "delete")
log.Printf("[DEBUG]\n%s", tfErr.GetDebugMessage())
return tfErr.GetDiag()
}
adminURL, _, err := getSchemaRuleInstanceURL(d, meta)
if err != nil {
tfErr := flex.TerraformErrorf(err, "Error getting Event Streams schema registry URL", "ibm_event_streams_schema_global_rule", "delete")
log.Printf("[DEBUG]\n%s", tfErr.GetDebugMessage())
return tfErr.GetDiag()
}
schemaregistryClient.SetServiceURL(adminURL)

updateOpts := &schemaregistryv1.UpdateGlobalRuleOptions{}
updateOpts.SetType(schemaCompatibilityRuleType)
updateOpts.SetRule(schemaCompatibilityRuleType)
updateOpts.SetConfig("NONE")

_, _, err = schemaregistryClient.UpdateGlobalRuleWithContext(context, updateOpts)
if err != nil {
tfErr := flex.TerraformErrorf(err, "UpdateGlobalRule returned error", "ibm_event_streams_schema_global_rule", "delete")
log.Printf("[DEBUG]\n%s", tfErr.GetDebugMessage())
return tfErr.GetDiag()
}

d.SetId("")
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright IBM Corp. 2024 All Rights Reserved.
// Licensed under the Mozilla Public License v2.0

package eventstreams_test

import (
"fmt"
"testing"

acc "github.com/IBM-Cloud/terraform-provider-ibm/ibm/acctest"
"github.com/IBM-Cloud/terraform-provider-ibm/ibm/conns"
"github.com/IBM/eventstreams-go-sdk/pkg/schemaregistryv1"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func TestAccIBMEventStreamsSchemaGlobalCompatibilityRuleResource(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { acc.TestAccPreCheck(t) },
Providers: acc.TestAccProviders,
CheckDestroy: testAccCheckIBMEventStreamsGlobalCompatibilityRuleResetToNoneInInstance(),
Steps: []resource.TestStep{
{
Config: testAccCheckIBMEventStreamsSchemaGlobalCompatibilityRuleResourceConfig(getTestInstanceName(mzrKey), "FORWARD"),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckIBMEventStreamsSchemaGlobalCompatibilityRuleProperties("ibm_event_streams_schema_global_rule.es_globalrule", "FORWARD"),
),
},
{
ResourceName: "ibm_event_streams_schema_global_rule.es_globalrule",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckIBMEventStreamsSchemaGlobalCompatibilityRuleResourceConfig(instanceName string, rule string) string {
return fmt.Sprintf(`
data "ibm_resource_group" "group" {
is_default=true
}
data "ibm_resource_instance" "es_instance" {
resource_group_id = data.ibm_resource_group.group.id
name = "%s"
}
resource "ibm_event_streams_schema_global_rule" "es_globalrule" {
resource_instance_id = data.ibm_resource_instance.es_instance.id
config = "%s"
}`, instanceName, rule)
}

func testAccCheckIBMEventStreamsGlobalCompatibilityRuleResetToNoneInInstance() resource.TestCheckFunc {
return func(s *terraform.State) error {
schemaregistryClient, err := acc.TestAccProvider.Meta().(conns.ClientSession).ESschemaRegistrySession()
if err != nil {
return err
}
getOpts := &schemaregistryv1.GetGlobalRuleOptions{}
getOpts.SetRule("COMPATIBILITY")
rule, _, err := schemaregistryClient.GetGlobalRule(getOpts)
if err != nil {
return err
}
if rule.Config == nil || *rule.Config != "NONE" {
return fmt.Errorf("[ERROR] Expected global compatibility rule reset to NONE after deletion")
}
return nil
}
}
Loading

0 comments on commit 4cc01dd

Please sign in to comment.