Skip to content

Commit

Permalink
feat: add governance_subscription_resource [GOV-630]
Browse files Browse the repository at this point in the history
for managing governance subscriptions, beta as prone to change.
  • Loading branch information
roope-kar committed Mar 5, 2025
1 parent 9e71ee6 commit 531e329
Show file tree
Hide file tree
Showing 7 changed files with 766 additions and 1 deletion.
78 changes: 78 additions & 0 deletions docs/resources/governance_subscription.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "aiven_governance_subscription Resource - terraform-provider-aiven"
subcategory: ""
description: |-
Creates and manages governance subscriptions for an organization. Governance subscriptions provide convenient management and access to service resources and is part of Aiven Kafka Governance.
This resource is in the beta stage and may change without notice. Set
the PROVIDER_AIVEN_ENABLE_BETA environment variable to use the resource.
---

# aiven_governance_subscription (Resource)

Creates and manages governance subscriptions for an organization. Governance subscriptions provide convenient management and access to service resources and is part of Aiven Kafka Governance.

**This resource is in the beta stage and may change without notice.** Set
the `PROVIDER_AIVEN_ENABLE_BETA` environment variable to use the resource.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `organization_id` (String) The ID of the organization. Changing this property forces recreation of the resource.
- `owner_user_group_id` (String) The ID of the user group that owns the subscription. Maximum length: `54`. Changing this property forces recreation of the resource.
- `subscription_data` (Block List, Min: 1, Max: 1) The data defined by subscription_type. Changing this property forces recreation of the resource. (see [below for nested schema](#nestedblock--subscription_data))
- `subscription_name` (String) The name to describe the subscription. Maximum length: `54`. Changing this property forces recreation of the resource.
- `subscription_type` (String) The type of subscription. For example KAFKA. The possible value is `KAFKA`. Changing this property forces recreation of the resource.

### Optional

- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))

### Read-Only

- `id` (String) The ID of this resource.
- `susbcription_id` (String) The ID of the subscription.

<a id="nestedblock--subscription_data"></a>
### Nested Schema for `subscription_data`

Required:

- `acls` (Block Set, Min: 1, Max: 10) The permissions granted to the assigned service user. Maximum length: `54`. Changing this property forces recreation of the resource. (see [below for nested schema](#nestedblock--subscription_data--acls))
- `project` (String) The name of the project this resource belongs to. To set up proper dependencies please refer to this variable as a reference. Changing this property forces recreation of the resource.
- `service_name` (String) The name of the service that this resource belongs to. To set up proper dependencies please refer to this variable as a reference. Changing this property forces recreation of the resource.
- `username` (String) The name that will be used for the new service user assigned to the subscription. If not provided, name is automatically generated. Maximum length: `54`. Changing this property forces recreation of the resource.

<a id="nestedblock--subscription_data--acls"></a>
### Nested Schema for `subscription_data.acls`

Required:

- `host` (String) The IP address from which a principal is allowed or denied access to the resource. Use `*` for all hosts. Maximum length: `256`. Changing this property forces recreation of the resource.
- `operation` (String) The action that will be allowed for the service user. The possible values are `Read` and `Write`. Changing this property forces recreation of the resource.
- `permission_type` (String) Specifies whether the action is explicitly allowed or denied for the service user on the specified resource. The possible value is `ALLOW`. Changing this property forces recreation of the resource.
- `resource_name` (String) The name of the resource the permission applies to, such as the topic name or group ID in Kafka service. Maximum length: `256`. Changing this property forces recreation of the resource.
- `resource_type` (String) The type of resource on the service. The possible value is `Topic`. Changing this property forces recreation of the resource.

Read-Only:

- `id` (String) The acl id.
- `pattern_type` (String) Resource pattern used to match specified resources. The possible value is `LITERAL`.
- `principal` (String) Identities in `user:name` format that the permissions apply to.



<a id="nestedblock--timeouts"></a>
### Nested Schema for `timeouts`

Optional:

- `create` (String)
- `default` (String)
- `delete` (String)
- `read` (String)
- `update` (String)
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.23.0

require (
github.com/aiven/aiven-go-client/v2 v2.34.0
github.com/aiven/go-client-codegen v0.90.0
github.com/aiven/go-client-codegen v0.90.1-0.20250305073645-6ec47e26d177
github.com/avast/retry-go v3.0.0+incompatible
github.com/dave/jennifer v1.7.1
github.com/docker/go-units v0.5.0
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,18 @@ github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7l
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/aiven/aiven-go-client/v2 v2.34.0 h1:Iq3GZVoqrAPb4XcA3pJVL8MpRtiXep+LYA7y6aoi5xQ=
github.com/aiven/aiven-go-client/v2 v2.34.0/go.mod h1:qXBgER0dtjJa1V3l7kzpizuAGjFCkgahhHL5OpoM2ZM=
github.com/aiven/go-api-schemas v1.113.0 h1:MTCtltdKeq/WKvpmzFrgZaM3hqyKW3bOaQO6teMblG8=
github.com/aiven/go-api-schemas v1.113.0/go.mod h1:7nvQuvnOOK/1IKxa7SCxmIM+gS8owzOldBaZPWRqNOI=
github.com/aiven/go-api-schemas v1.115.0 h1:aMQsEdZK+Xs5veBiCAva1o7JgIKE1t8g4EfMjD13w2w=
github.com/aiven/go-api-schemas v1.115.0/go.mod h1:xP0ofLFFxIRsIXRxeg6wumK1V4aP+k4Ru4EiyL+E+fg=
github.com/aiven/go-client-codegen v0.86.0 h1:/zWjNQith6/U2dShEY0HC3OGpaUObIICWilBFWr1uFY=
github.com/aiven/go-client-codegen v0.86.0/go.mod h1:QKN/GgLMGWd6+gPEucXlZPi5vC3C6RpD3UeBRQOLI1Y=
github.com/aiven/go-client-codegen v0.86.1-0.20250304112059-05b7109a2615 h1:gbHHdErqYScg3VQxOO85DiMTZkb5Becsgv9/iMm04Uk=
github.com/aiven/go-client-codegen v0.86.1-0.20250304112059-05b7109a2615/go.mod h1:QKN/GgLMGWd6+gPEucXlZPi5vC3C6RpD3UeBRQOLI1Y=
github.com/aiven/go-client-codegen v0.90.0 h1:5kZNwSq+te2EPXYqXrZwHtKomG+9QADSxo7D8NO60cM=
github.com/aiven/go-client-codegen v0.90.0/go.mod h1:QvrvsO2+2HiM1FMEY+2t76GbusXw6F/GxULWQr8Ag6Q=
github.com/aiven/go-client-codegen v0.90.1-0.20250305073645-6ec47e26d177 h1:iPJq7UCGmDEVYgFpgNbSeqbCIUDvxm99LI16J5l9ugM=
github.com/aiven/go-client-codegen v0.90.1-0.20250305073645-6ec47e26d177/go.mod h1:QvrvsO2+2HiM1FMEY+2t76GbusXw6F/GxULWQr8Ag6Q=
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=
Expand Down
5 changes: 5 additions & 0 deletions internal/sdkprovider/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/aiven/terraform-provider-aiven/internal/sdkprovider/service/connectionpool"
"github.com/aiven/terraform-provider-aiven/internal/sdkprovider/service/dragonfly"
"github.com/aiven/terraform-provider-aiven/internal/sdkprovider/service/flink"
"github.com/aiven/terraform-provider-aiven/internal/sdkprovider/service/governance"
"github.com/aiven/terraform-provider-aiven/internal/sdkprovider/service/grafana"
"github.com/aiven/terraform-provider-aiven/internal/sdkprovider/service/influxdb"
"github.com/aiven/terraform-provider-aiven/internal/sdkprovider/service/kafka"
Expand Down Expand Up @@ -300,6 +301,9 @@ func Provider(version string) (*schema.Provider, error) {
// valkey
"aiven_valkey": valkey.ResourceValkey(),
"aiven_valkey_user": valkey.ResourceValkeyUser(),

// Governance
"aiven_governance_subscription": governance.ResourceGovernanceSubscription(),
},
}

Expand All @@ -316,6 +320,7 @@ func Provider(version string) (*schema.Provider, error) {
"aiven_aws_org_vpc_peering_connection",
"aiven_gcp_org_vpc_peering_connection",
"aiven_azure_org_vpc_peering_connection",
"aiven_governance_subscription",
}

betaDataSources := []string{
Expand Down
238 changes: 238 additions & 0 deletions internal/sdkprovider/service/governance/governance_subscription.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
package governance

import (
"context"

avngen "github.com/aiven/go-client-codegen"
"github.com/aiven/go-client-codegen/handler/governance"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"

"github.com/aiven/terraform-provider-aiven/internal/common"
"github.com/aiven/terraform-provider-aiven/internal/schemautil"
"github.com/aiven/terraform-provider-aiven/internal/schemautil/userconfig"
)

var aivenGovernanceKafkaSubscriptionSchema = map[string]*schema.Schema{
"project": schemautil.CommonSchemaProjectReference,
"service_name": schemautil.CommonSchemaServiceNameReference,
"username": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringLenBetween(1, 54),
Description: userconfig.Desc("The name that will be used for the new service user assigned to the subscription. If not provided, name is automatically generated").ForceNew().MaxLen(54).Build(),
},
"acls": {
Type: schema.TypeSet,
Required: true,
ForceNew: true,
MaxItems: 10,
Description: userconfig.Desc("The permissions granted to the assigned service user").ForceNew().MaxLen(54).Build(),
Elem: &schema.Resource{Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Computed: true,
Description: userconfig.Desc("The acl id").Build(),
},
"resource_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringLenBetween(1, 256),
Description: userconfig.Desc("The name of the resource the permission applies to, such as the topic name or group ID in Kafka service").ForceNew().MaxLen(256).Build(),
},
"resource_type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice(governance.ResourceTypeChoices(), false),
Description: userconfig.Desc("The type of resource on the service.").ForceNew().PossibleValuesString(governance.ResourceTypeChoices()...).Build(),
},
"pattern_type": {
Type: schema.TypeString,
Computed: true,
Description: userconfig.Desc("Resource pattern used to match specified resources").PossibleValuesString(governance.PatternTypeChoices()...).Build(),
},
"principal": {
Type: schema.TypeString,
Computed: true,
Description: userconfig.Desc("Identities in `user:name` format that the permissions apply to").Build(),
},
"operation": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice(governance.OperationTypeChoices(), false),
Description: userconfig.Desc("The action that will be allowed for the service user.").ForceNew().PossibleValuesString(governance.OperationTypeChoices()...).Build(),
},
"permission_type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice(governance.PermissionTypeChoices(), false),
Description: userconfig.Desc("Specifies whether the action is explicitly allowed or denied for the service user on the specified resource").ForceNew().PossibleValuesString(governance.PermissionTypeChoices()...).Build(),
},
"host": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringLenBetween(1, 256),
Description: userconfig.Desc("The IP address from which a principal is allowed or denied access to the resource. Use `*` for all hosts").ForceNew().MaxLen(256).Build(),
},
}},
},
}

var aivenGovernanceSubscriptionSchema = map[string]*schema.Schema{
"organization_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: userconfig.Desc("The ID of the organization").ForceNew().Build(),
},
"susbcription_id": {
Type: schema.TypeString,
Computed: true,
Description: userconfig.Desc("The ID of the subscription").Build(),
},
"subscription_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringLenBetween(1, 54),
Description: userconfig.Desc("The name to describe the subscription").ForceNew().MaxLen(54).Build(),
},
"subscription_type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice(governance.SubscriptionTypeChoices(), false),
Description: userconfig.Desc("The type of subscription. For example KAFKA.").ForceNew().PossibleValuesString(governance.SubscriptionTypeChoices()...).Build(),
},
"subscription_data": {
Type: schema.TypeList,
Description: userconfig.Desc("The data defined by subscription_type.").ForceNew().Build(),
Required: true,
ForceNew: true,
DiffSuppressFunc: schemautil.EmptyObjectDiffSuppressFunc,
Elem: &schema.Resource{
Schema: aivenGovernanceKafkaSubscriptionSchema,
},
MaxItems: 1,
},
"owner_user_group_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringLenBetween(1, 54),
Description: userconfig.Desc("The ID of the user group that owns the subscription").ForceNew().MaxLen(54).Build(),
},
}

func ResourceGovernanceSubscription() *schema.Resource {
return &schema.Resource{
Description: userconfig.Desc(`Creates and manages governance subscriptions for an organization. Governance subscriptions provide convenient management and access to service resources and is part of Aiven Kafka Governance`).Build(),
CreateContext: common.WithGenClient(resourceGovernanceSubscriptionCreate),
ReadContext: common.WithGenClient(resourceGovernanceSubscriptionRead),
DeleteContext: common.WithGenClient(resourceGovernanceSubscriptionDelete),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Timeouts: schemautil.DefaultResourceTimeouts(),
Schema: aivenGovernanceSubscriptionSchema,
}
}

func resourceGovernanceSubscriptionCreate(ctx context.Context, d *schema.ResourceData, client avngen.Client) error {
var req governance.OrganizationGovernanceSubscriptionCreateIn

req.SubscriptionName = d.Get("subscription_name").(string)
req.SubscriptionType = governance.SubscriptionType(d.Get("subscription_type").(string))

req.SubscriptionData = governance.SubscriptionDataIn{
ProjectName: d.Get("subscription_data.0.project").(string),
ServiceName: d.Get("subscription_data.0.service_name").(string),
}

for _, v := range d.Get("subscription_data.0.acls").(*schema.Set).List() {
acl := v.(map[string]interface{})
req.SubscriptionData.Acls = append(req.SubscriptionData.Acls, governance.AclIn{
ResourceName: acl["resource_name"].(string),
ResourceType: governance.ResourceType(acl["resource_type"].(string)),
Operation: governance.OperationType(acl["operation"].(string)),
PermissionType: governance.PermissionType(acl["permission_type"].(string)),
Host: acl["host"].(string),
})
}

subscription, err := client.OrganizationGovernanceSubscriptionCreate(
ctx,
d.Get("organization_id").(string),
&req,
)
if err != nil {
return err
}

d.SetId(schemautil.BuildResourceID(d.Get("organization_id").(string), subscription.SubscriptionId))

return resourceGovernanceSubscriptionRead(ctx, d, client)
}

func resourceGovernanceSubscriptionRead(ctx context.Context, d *schema.ResourceData, client avngen.Client) error {
organizationID, subscriptionID, err := schemautil.SplitResourceID2(d.Id())
if err != nil {
return err
}
subscription, err := client.OrganizationGovernanceSubscriptionGet(
ctx,
organizationID,
subscriptionID,
)
if err != nil {
return err
}

if err := d.Set("subscription_name", subscription.SubscriptionName); err != nil {
return err
}

if err := d.Set("subscription_type", subscription.SubscriptionType); err != nil {
return err
}

if err := d.Set("owner_user_group_id", subscription.OwnerUserGroupId); err != nil {
return err
}

subscriptionData := make([]map[string]any, 1)
subscriptionData[0] = make(map[string]any, 1)
subscriptionData[0]["project"] = subscription.SubscriptionData.ProjectName
subscriptionData[0]["service_name"] = subscription.SubscriptionData.ServiceName
subscriptionData[0]["username"] = subscription.SubscriptionData.Username

acls := make([]map[string]string, len(subscription.SubscriptionData.Acls))
for i, acl := range subscription.SubscriptionData.Acls {
acls[i] = make(map[string]string, 1)
acls[i]["id"] = acl.Id
acls[i]["resource_name"] = acl.ResourceName
acls[i]["resource_type"] = string(acl.ResourceType)
acls[i]["pattern_type"] = string(acl.PatternType)
acls[i]["operation"] = string(acl.Operation)
acls[i]["permission_type"] = string(acl.PermissionType)
acls[i]["host"] = acl.Host
}
subscriptionData[0]["acls"] = acls

return d.Set("subscription_data", subscriptionData)
}

func resourceGovernanceSubscriptionDelete(ctx context.Context, d *schema.ResourceData, client avngen.Client) error {
_, err := client.OrganizationGovernanceSubscriptionDelete(
ctx,
d.Get("organization_id").(string),
d.Id(),
)
return err
}
Loading

0 comments on commit 531e329

Please sign in to comment.