Skip to content

Commit 093748b

Browse files
authored
feat(kafka_instance): support multiple security groups (#101)
* feat(kafka_instance): support multiple security groups * test(kafka_instance): add test cases for compute_specs security_groups
1 parent 88f4bb9 commit 093748b

File tree

9 files changed

+302
-2
lines changed

9 files changed

+302
-2
lines changed

client/model_kafka_instance.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type SpecificationParam struct {
1919
NodeConfig *NodeConfigParam `json:"nodeConfig,omitempty"`
2020
Networks []InstanceNetworkParam `json:"networks,omitempty"`
2121
KubernetesNodeGroups []KubernetesNodeGroupParam `json:"kubernetesNodeGroups,omitempty"`
22-
SecurityGroup *string `json:"securityGroup,omitempty"`
22+
SecurityGroups []string `json:"securityGroups,omitempty"`
2323
Template *string `json:"template,omitempty"`
2424
FileSystem *FileSystemParam `json:"fileSystemForFsWal,omitempty"`
2525
DeployType *string `json:"deployType,omitempty"`
@@ -223,6 +223,7 @@ type SpecificationVO struct {
223223
BucketProfiles []BucketProfileSummaryVO `json:"bucketProfiles,omitempty"`
224224
DataBuckets []BucketProfileVO `json:"dataBuckets,omitempty"`
225225
SecurityGroupId *string `json:"securityGroupId,omitempty"`
226+
SecurityGroups []string `json:"securityGroups,omitempty"`
226227
FileSystem *FileSystemVO `json:"fileSystemForFsWal,omitempty"`
227228
Provider *string `json:"provider,omitempty"`
228229
Region *string `json:"region,omitempty"`
@@ -365,7 +366,7 @@ type InstanceConfigParam struct {
365366
type SpecificationUpdateParam struct {
366367
ReservedAku *int32 `json:"reservedAku,omitempty"`
367368
NodeConfig *NodeConfigParam `json:"nodeConfig,omitempty"`
368-
SecurityGroup *string `json:"securityGroup,omitempty"`
369+
SecurityGroups []string `json:"securityGroups,omitempty"`
369370
Template *string `json:"template,omitempty"`
370371
Networks []InstanceNetworkParam `json:"networks,omitempty"`
371372
KubernetesNodeGroups []KubernetesNodeGroupParam `json:"kubernetesNodeGroups,omitempty"`

docs/data-sources/kafka_instance.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ Read-Only:
7171
- `kubernetes_service_account` (String)
7272
- `networks` (Attributes List) To configure the network settings for an instance, you need to specify the availability zone(s) and subnet information. Currently, you can set either one availability zone or three availability zones. (see [below for nested schema](#nestedatt--compute_specs--networks))
7373
- `reserved_aku` (Number) AutoMQ defines AKU (AutoMQ Kafka Unit) to measure the scale of the cluster. Each AKU provides 20 MiB/s of read/write throughput. For more details on AKU, please refer to the [documentation](https://docs.automq.com/automq-cloud/subscriptions-and-billings/byoc-env-billings/billing-instructions-for-byoc#indicator-constraints). The currently supported AKU specifications are 6, 8, 10, 12, 14, 16, 18, 20, 22, and 24. If an invalid AKU value is set, the instance cannot be created.
74+
- `security_groups` (List of String) Security groups for the instance
7475

7576
<a id="nestedatt--compute_specs--data_buckets"></a>
7677
### Nested Schema for `compute_specs.data_buckets`

docs/resources/kafka_instance.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ resource "automq_kafka_instance" "example" {
4040
bucket_name = "automq-data-bucket"
4141
}
4242
]
43+
44+
# security_groups = ["sg-example123"] # Optional. Omit this field entirely to let backend auto-generate. If specified, must contain at least one security group.
4345
}
4446
4547
features = {
@@ -155,6 +157,7 @@ Optional:
155157
- `kubernetes_namespace` (String)
156158
- `kubernetes_node_groups` (Attributes List) Node groups (or node pools) are units for unified configuration management of physical nodes in Kubernetes. Different Kubernetes providers may use different terms for node groups. Select target node groups that must be created in advance and configured for either single-AZ or three-AZ deployment. The instance node type must meet the requirements specified in the documentation. If you select a single-AZ node group, the AutoMQ instance will be deployed in a single availability zone; if you select a three-AZ node group, the instance will be deployed across three availability zones. (see [below for nested schema](#nestedatt--compute_specs--kubernetes_node_groups))
157159
- `kubernetes_service_account` (String)
160+
- `security_groups` (List of String) Security groups for the instance. Omit this field entirely to let backend auto-generate. If specified, must contain at least one security group.
158161

159162
<a id="nestedatt--compute_specs--networks"></a>
160163
### Nested Schema for `compute_specs.networks`

examples/resources/automq_kafka_instance/resource.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ resource "automq_kafka_instance" "example" {
2020
bucket_name = "automq-data-bucket"
2121
}
2222
]
23+
24+
# security_groups = ["sg-example123"] # Optional. Omit this field entirely to let backend auto-generate. If specified, must contain at least one security group.
2325
}
2426

2527
features = {

internal/models/kafka_instance.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ type ComputeSpecsModel struct {
7777
KubernetesServiceAcct types.String `tfsdk:"kubernetes_service_account"`
7878
InstanceRole types.String `tfsdk:"instance_role"`
7979
DataBuckets types.List `tfsdk:"data_buckets"`
80+
SecurityGroups types.List `tfsdk:"security_groups"`
8081
FileSystemParam *FileSystemParamModel `tfsdk:"file_system_param"`
8182
}
8283

@@ -316,6 +317,16 @@ func ExpandKafkaInstanceResource(ctx context.Context, instance KafkaInstanceReso
316317
request.Spec.DataBuckets = dataBuckets
317318
}
318319

320+
// Security Groups for compute specs
321+
if !instance.ComputeSpecs.SecurityGroups.IsNull() &&
322+
!instance.ComputeSpecs.SecurityGroups.IsUnknown() {
323+
var securityGroups []string
324+
diags := instance.ComputeSpecs.SecurityGroups.ElementsAs(ctx, &securityGroups, false)
325+
if !diags.HasError() && len(securityGroups) > 0 {
326+
request.Spec.SecurityGroups = securityGroups
327+
}
328+
}
329+
319330
// File System Parameters for FSWAL
320331
if instance.ComputeSpecs.FileSystemParam != nil {
321332
fileSystemParam := &client.FileSystemParam{
@@ -654,6 +665,7 @@ func FlattenKafkaInstanceModel(ctx context.Context, instance *client.InstanceVO,
654665
Networks: []NetworkModel{},
655666
KubernetesNodeGroups: []NodeGroupModel{},
656667
DataBuckets: types.ListNull(DataBucketObjectType),
668+
SecurityGroups: types.ListNull(types.StringType),
657669
DeployType: types.StringNull(),
658670
DnsZone: types.StringNull(),
659671
KubernetesClusterID: types.StringNull(),
@@ -742,6 +754,18 @@ func FlattenKafkaInstanceModel(ctx context.Context, instance *client.InstanceVO,
742754
resource.ComputeSpecs.DataBuckets = types.ListNull(DataBucketObjectType)
743755
}
744756

757+
// Security Groups for compute specs
758+
if len(instance.Spec.SecurityGroups) > 0 {
759+
securityGroupsList, sgDiags := types.ListValueFrom(ctx, types.StringType, instance.Spec.SecurityGroups)
760+
if !sgDiags.HasError() {
761+
resource.ComputeSpecs.SecurityGroups = securityGroupsList
762+
}
763+
} else if previousSpecs != nil && !previousSpecs.SecurityGroups.IsNull() {
764+
resource.ComputeSpecs.SecurityGroups = previousSpecs.SecurityGroups
765+
} else {
766+
resource.ComputeSpecs.SecurityGroups = types.ListNull(types.StringType)
767+
}
768+
745769
// File System Parameters for FSWAL
746770
var previousFileSystemParam *FileSystemParamModel
747771
if previousSpecs != nil {

internal/models/kafka_instance_test.go

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,58 @@ func TestExpandKafkaInstanceResource(t *testing.T) {
259259
},
260260
},
261261
},
262+
{
263+
name: "Configuration with compute_specs security_groups",
264+
input: KafkaInstanceResourceModel{
265+
Name: types.StringValue("instance-with-sg"),
266+
Version: types.StringValue("1.0.0"),
267+
ComputeSpecs: &ComputeSpecsModel{
268+
ReservedAku: types.Int64Value(4),
269+
SecurityGroups: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("sg-abc123"), types.StringValue("sg-def456")}),
270+
},
271+
Features: &FeaturesModel{
272+
WalMode: types.StringValue("EBSWAL"),
273+
},
274+
},
275+
expected: client.InstanceCreateParam{
276+
Name: "instance-with-sg",
277+
Version: "1.0.0",
278+
Spec: client.SpecificationParam{
279+
ReservedAku: 4,
280+
NodeConfig: &client.NodeConfigParam{},
281+
SecurityGroups: []string{"sg-abc123", "sg-def456"},
282+
},
283+
Features: &client.InstanceFeatureParam{
284+
WalMode: stringPtr("EBSWAL"),
285+
},
286+
},
287+
},
288+
{
289+
name: "Configuration with null compute_specs security_groups",
290+
input: KafkaInstanceResourceModel{
291+
Name: types.StringValue("instance-no-sg"),
292+
Version: types.StringValue("1.0.0"),
293+
ComputeSpecs: &ComputeSpecsModel{
294+
ReservedAku: types.Int64Value(4),
295+
SecurityGroups: types.ListNull(types.StringType),
296+
},
297+
Features: &FeaturesModel{
298+
WalMode: types.StringValue("EBSWAL"),
299+
},
300+
},
301+
expected: client.InstanceCreateParam{
302+
Name: "instance-no-sg",
303+
Version: "1.0.0",
304+
Spec: client.SpecificationParam{
305+
ReservedAku: 4,
306+
NodeConfig: &client.NodeConfigParam{},
307+
SecurityGroups: nil, // Should not be included when null
308+
},
309+
Features: &client.InstanceFeatureParam{
310+
WalMode: stringPtr("EBSWAL"),
311+
},
312+
},
313+
},
262314
}
263315

264316
for _, tt := range tests {
@@ -635,3 +687,132 @@ func timePtr(s string) *time.Time {
635687
t, _ := time.Parse(time.RFC3339, s)
636688
return &t
637689
}
690+
691+
func TestFlattenKafkaInstanceModel_ComputeSpecsSecurityGroups(t *testing.T) {
692+
tests := []struct {
693+
name string
694+
input *client.InstanceVO
695+
expected *KafkaInstanceResourceModel
696+
}{
697+
{
698+
name: "Instance with compute_specs security_groups",
699+
input: &client.InstanceVO{
700+
InstanceId: strPtr("instance-with-sg"),
701+
Name: strPtr("test-with-sg"),
702+
Description: strPtr("Instance with security groups"),
703+
Version: strPtr("1.0.0"),
704+
State: strPtr("Running"),
705+
Spec: &client.SpecificationVO{
706+
ReservedAku: int32Ptr(4),
707+
SecurityGroups: []string{"sg-abc123", "sg-def456"},
708+
},
709+
Features: &client.InstanceFeatureVO{
710+
WalMode: strPtr("EBSWAL"),
711+
},
712+
},
713+
expected: &KafkaInstanceResourceModel{
714+
InstanceID: types.StringValue("instance-with-sg"),
715+
Name: types.StringValue("test-with-sg"),
716+
Description: types.StringValue("Instance with security groups"),
717+
Version: types.StringValue("1.0.0"),
718+
InstanceStatus: types.StringValue("Running"),
719+
ComputeSpecs: &ComputeSpecsModel{
720+
ReservedAku: types.Int64Value(4),
721+
SecurityGroups: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("sg-abc123"), types.StringValue("sg-def456")}),
722+
},
723+
Features: &FeaturesModel{
724+
WalMode: types.StringValue("EBSWAL"),
725+
},
726+
},
727+
},
728+
{
729+
name: "Instance without compute_specs security_groups",
730+
input: &client.InstanceVO{
731+
InstanceId: strPtr("instance-no-sg"),
732+
Name: strPtr("test-no-sg"),
733+
Description: strPtr("Instance without security groups"),
734+
Version: strPtr("1.0.0"),
735+
State: strPtr("Running"),
736+
Spec: &client.SpecificationVO{
737+
ReservedAku: int32Ptr(4),
738+
SecurityGroups: nil,
739+
},
740+
Features: &client.InstanceFeatureVO{
741+
WalMode: strPtr("EBSWAL"),
742+
},
743+
},
744+
expected: &KafkaInstanceResourceModel{
745+
InstanceID: types.StringValue("instance-no-sg"),
746+
Name: types.StringValue("test-no-sg"),
747+
Description: types.StringValue("Instance without security groups"),
748+
Version: types.StringValue("1.0.0"),
749+
InstanceStatus: types.StringValue("Running"),
750+
ComputeSpecs: &ComputeSpecsModel{
751+
ReservedAku: types.Int64Value(4),
752+
SecurityGroups: types.ListNull(types.StringType),
753+
},
754+
Features: &FeaturesModel{
755+
WalMode: types.StringValue("EBSWAL"),
756+
},
757+
},
758+
},
759+
{
760+
name: "Instance with single security group",
761+
input: &client.InstanceVO{
762+
InstanceId: strPtr("instance-single-sg"),
763+
Name: strPtr("test-single-sg"),
764+
Description: strPtr("Instance with single security group"),
765+
Version: strPtr("1.0.0"),
766+
State: strPtr("Running"),
767+
Spec: &client.SpecificationVO{
768+
ReservedAku: int32Ptr(6),
769+
SecurityGroups: []string{"sg-only-one"},
770+
},
771+
Features: &client.InstanceFeatureVO{
772+
WalMode: strPtr("S3WAL"),
773+
},
774+
},
775+
expected: &KafkaInstanceResourceModel{
776+
InstanceID: types.StringValue("instance-single-sg"),
777+
Name: types.StringValue("test-single-sg"),
778+
Description: types.StringValue("Instance with single security group"),
779+
Version: types.StringValue("1.0.0"),
780+
InstanceStatus: types.StringValue("Running"),
781+
ComputeSpecs: &ComputeSpecsModel{
782+
ReservedAku: types.Int64Value(6),
783+
SecurityGroups: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("sg-only-one")}),
784+
},
785+
Features: &FeaturesModel{
786+
WalMode: types.StringValue("S3WAL"),
787+
},
788+
},
789+
},
790+
}
791+
792+
for _, tt := range tests {
793+
t.Run(tt.name, func(t *testing.T) {
794+
resource := &KafkaInstanceResourceModel{}
795+
diags := FlattenKafkaInstanceModel(context.Background(), tt.input, resource)
796+
797+
assert.False(t, diags.HasError(), "FlattenKafkaInstanceModel should not return errors")
798+
799+
// Check basic fields
800+
assert.Equal(t, tt.expected.InstanceID, resource.InstanceID)
801+
assert.Equal(t, tt.expected.Name, resource.Name)
802+
assert.Equal(t, tt.expected.Description, resource.Description)
803+
assert.Equal(t, tt.expected.Version, resource.Version)
804+
assert.Equal(t, tt.expected.InstanceStatus, resource.InstanceStatus)
805+
806+
// Check compute specs
807+
assert.NotNil(t, resource.ComputeSpecs)
808+
assert.Equal(t, tt.expected.ComputeSpecs.ReservedAku, resource.ComputeSpecs.ReservedAku)
809+
810+
// Check security groups
811+
assert.Equal(t, tt.expected.ComputeSpecs.SecurityGroups, resource.ComputeSpecs.SecurityGroups)
812+
813+
// Check features
814+
assert.NotNil(t, resource.Features)
815+
assert.Equal(t, tt.expected.Features.WalMode, resource.Features.WalMode)
816+
})
817+
}
818+
}

internal/provider/datasource_instance.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ func (r *KafkaInstanceDataSource) Schema(_ context.Context, _ datasource.SchemaR
112112
},
113113
},
114114
},
115+
"security_groups": schema.ListAttribute{
116+
ElementType: types.StringType,
117+
Computed: true,
118+
Description: "Security groups for the instance",
119+
},
115120
"file_system_param": schema.SingleNestedAttribute{
116121
Computed: true,
117122
Description: "File system configuration for FSWAL mode",

internal/provider/resource_instance.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,19 @@ func (r *KafkaInstanceResource) Schema(ctx context.Context, req resource.SchemaR
255255
stringplanmodifier.UseStateForUnknown(),
256256
},
257257
},
258+
"security_groups": schema.ListAttribute{
259+
ElementType: types.StringType,
260+
Optional: true,
261+
Computed: true,
262+
Description: "Security groups for the instance. Omit this field entirely to let backend auto-generate. If specified, must contain at least one security group.",
263+
PlanModifiers: []planmodifier.List{
264+
listplanmodifier.RequiresReplace(),
265+
listplanmodifier.UseStateForUnknown(),
266+
},
267+
Validators: []validator.List{
268+
listvalidator.SizeAtLeast(1),
269+
},
270+
},
258271
"file_system_param": schema.SingleNestedAttribute{
259272
Optional: true,
260273
Description: "File system configuration for FSWAL mode",

0 commit comments

Comments
 (0)