Skip to content

Commit a32fdb9

Browse files
committed
[3.1.4] Service Tags support added
1 parent 636a66c commit a32fdb9

File tree

12 files changed

+709
-14
lines changed

12 files changed

+709
-14
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [3.1.4] - 2025-07-17
4+
### Features
5+
- service `tags` support added.
6+
37
## [3.1.3] - 2024-07-25
48
### Package Updates
59
- upgraded go 1.19 => 1.21

examples/azure-private-link/main.tf

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@ resource "skysql_service" "this" {
3030
version = data.skysql_versions.this.versions[0].name
3131
endpoint_mechanism = "privateconnect"
3232
endpoint_allowed_accounts = [data.azurerm_subscription.current.subscription_id]
33-
wait_for_creation = true
33+
tags = {
34+
"name" = var.skysql_service_name
35+
"environment" = "demo"
36+
"connectivity" = "private-link"
37+
"cloud-provider" = "azure"
38+
}
39+
wait_for_creation = true
3440
# The following line will be required when tearing down the skysql service
3541
# deletion_protection = false
3642
}

examples/global-replication/main.tf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ resource "skysql_service" "primary" {
3333
storage = 100
3434
ssl_enabled = true
3535
version = local.sky_versions_filtered[0].name
36+
tags = {
37+
"name" = "my-primary-service"
38+
"environment" = "production"
39+
"role" = "primary"
40+
"replication" = "enabled"
41+
}
3642
# The service create is an asynchronous operation.
3743
# if you want to wait for the service to be created set wait_for_creation to true
3844
wait_for_creation = true
@@ -54,6 +60,12 @@ resource "skysql_service" "replica" {
5460
version = local.sky_versions_filtered[0].name
5561
primary_host = skysql_service.primary.id
5662
replication_enabled = true
63+
tags = {
64+
"name" = "my-replica-service"
65+
"environment" = "production"
66+
"role" = "replica"
67+
"replication" = "enabled"
68+
}
5769
# The service create is an asynchronous operation.
5870
# if you want to wait for the service to be created set wait_for_creation to true
5971
wait_for_creation = true

examples/main.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ resource "skysql_service" "default" {
3535
"comment" : "localhost"
3636
}
3737
]
38+
tags = {
39+
"name" = "myservice"
40+
"environment" = "development"
41+
"team" = "engineering"
42+
"project" = "demo"
43+
}
3844
wait_for_creation = true
3945
}
4046

examples/private-service-connect/main.tf

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ resource "skysql_service" "this" {
2121
version = data.skysql_versions.this.versions[0].name
2222
endpoint_mechanism = "privateconnect"
2323
endpoint_allowed_accounts = [data.google_project.this.number]
24-
wait_for_creation = true
24+
tags = {
25+
"name" = var.skysql_service_name
26+
"environment" = "demo"
27+
"connectivity" = "private-service-connect"
28+
"cloud-provider" = "gcp"
29+
}
30+
wait_for_creation = true
2531
# The following line will be required when tearing down the skysql service
2632
# deletion_protection = false
2733
}

examples/privateconnect/main.tf

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,16 @@ resource "skysql_service" "this" {
2121
version = data.skysql_versions.this.versions[0].name
2222
endpoint_mechanism = "privateconnect"
2323
endpoint_allowed_accounts = [data.aws_caller_identity.this.account_id]
24-
wait_for_creation = true
25-
volume_type = "gp3"
26-
volume_iops = 3000
27-
volume_throughput = 125
24+
tags = {
25+
"name" = var.skysql_service_name
26+
"environment" = "demo"
27+
"connectivity" = "privateconnect"
28+
"cloud-provider" = "aws"
29+
}
30+
wait_for_creation = true
31+
volume_type = "gp3"
32+
volume_iops = 3000
33+
volume_throughput = 125
2834
# The following line will be required when tearing down the skysql service
2935
# deletion_protection = false
3036
}

examples/resources/skysql_service.tf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ resource "skysql_service" "default" {
1515
volume_type = "gp3"
1616
volume_iops = 3000
1717
volume_throughput = 125
18+
# Tags for organizing and managing services
19+
tags = {
20+
"name" = "myservice"
21+
"environment" = "production"
22+
"team" = "backend"
23+
"cost-center" = "engineering"
24+
}
1825
# The service create is an asynchronous operation.
1926
# if you want to wait for the service to be created set wait_for_creation to true
2027
wait_for_creation = true

internal/provider/service_resource.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ type ServiceResourceModel struct {
9898
MaxscaleSize types.String `tfsdk:"maxscale_size"`
9999
FQDN types.String `tfsdk:"fqdn"`
100100
AvailabilityZone types.String `tfsdk:"availability_zone"`
101+
Tags types.Map `tfsdk:"tags"`
101102
}
102103

103104
// ServiceResourceNamedPortModel is an endpoint port
@@ -403,6 +404,12 @@ var serviceResourceSchemaV0 = schema.Schema{
403404
stringplanmodifier.RequiresReplace(),
404405
},
405406
},
407+
"tags": schema.MapAttribute{
408+
Optional: true,
409+
Computed: true,
410+
ElementType: types.StringType,
411+
Description: "Tags associated with the service",
412+
},
406413
},
407414
Blocks: map[string]schema.Block{
408415
"timeouts": timeouts.Block(context.Background(), timeouts.Opts{
@@ -721,6 +728,11 @@ func (r *ServiceResource) readServiceState(ctx context.Context, data *ServiceRes
721728
if !(data.MaxscaleNodes.IsUnknown() || data.MaxscaleSize.IsNull()) {
722729
data.MaxscaleNodes = types.Int64Value(int64(service.MaxscaleNodes))
723730
}
731+
if service.Tags != nil {
732+
data.Tags, _ = types.MapValueFrom(ctx, types.StringType, service.Tags)
733+
} else {
734+
data.Tags = types.MapNull(types.StringType)
735+
}
724736
return nil
725737
}
726738

@@ -783,6 +795,11 @@ func (r *ServiceResource) Update(ctx context.Context, req resource.UpdateRequest
783795
return
784796
}
785797

798+
r.updateServiceTags(ctx, plan, state, resp)
799+
if resp.Diagnostics.HasError() {
800+
return
801+
}
802+
786803
err := r.readServiceState(ctx, state)
787804
if err != nil {
788805
if errors.Is(err, skysql.ErrorServiceNotFound) {
@@ -1035,6 +1052,63 @@ func (r *ServiceResource) updateServicePowerState(ctx context.Context, plan *Ser
10351052
}
10361053
}
10371054

1055+
func (r *ServiceResource) updateServiceTags(ctx context.Context, plan *ServiceResourceModel, state *ServiceResourceModel, resp *resource.UpdateResponse) {
1056+
if !plan.Tags.IsUnknown() {
1057+
var planTags map[string]string
1058+
diags := plan.Tags.ElementsAs(ctx, &planTags, false)
1059+
if diags.HasError() {
1060+
// Log warning but don't fail the update to protect against state incompatibility
1061+
tflog.Warn(ctx, "Failed to parse plan tags, skipping tag update", map[string]interface{}{
1062+
"id": state.ID.ValueString(),
1063+
"error": diags.Errors(),
1064+
})
1065+
return
1066+
}
1067+
1068+
var stateTags map[string]string
1069+
diags = state.Tags.ElementsAs(ctx, &stateTags, false)
1070+
if diags.HasError() {
1071+
// For backward compatibility, if state tags can't be parsed (e.g., from older provider version),
1072+
// treat as empty map and continue with the update
1073+
tflog.Warn(ctx, "Failed to parse state tags, treating as empty", map[string]interface{}{
1074+
"id": state.ID.ValueString(),
1075+
"error": diags.Errors(),
1076+
})
1077+
stateTags = make(map[string]string)
1078+
}
1079+
1080+
if !reflect.DeepEqual(planTags, stateTags) {
1081+
tflog.Info(ctx, "Updating service tags", map[string]interface{}{
1082+
"id": state.ID.ValueString(),
1083+
})
1084+
1085+
err := r.client.UpdateServiceTags(ctx, state.ID.ValueString(), planTags)
1086+
if err != nil {
1087+
if errors.Is(err, skysql.ErrorServiceNotFound) {
1088+
tflog.Warn(ctx, "SkySQL service not found, removing from state", map[string]interface{}{
1089+
"id": state.ID.ValueString(),
1090+
})
1091+
resp.State.RemoveResource(ctx)
1092+
return
1093+
}
1094+
// Log warning but don't fail the update to protect against tag API errors
1095+
tflog.Warn(ctx, "Failed to update service tags, continuing with other updates", map[string]interface{}{
1096+
"id": state.ID.ValueString(),
1097+
"error": err.Error(),
1098+
})
1099+
return
1100+
}
1101+
1102+
state.Tags = plan.Tags
1103+
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
1104+
if resp.Diagnostics.HasError() {
1105+
return
1106+
}
1107+
r.waitForUpdate(ctx, state, resp)
1108+
}
1109+
}
1110+
}
1111+
10381112
var serviceUpdateWaitStates = []string{"ready", "failed", "stopped"}
10391113

10401114
func (r *ServiceResource) waitForUpdate(ctx context.Context, state *ServiceResourceModel, resp *resource.UpdateResponse) {

0 commit comments

Comments
 (0)