Skip to content

Commit 0815689

Browse files
committed
[3.2.0] Multi-organization support added
1 parent db0682b commit 0815689

File tree

8 files changed

+663
-60
lines changed

8 files changed

+663
-60
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.2.0] - 2025-10-23
4+
### Added
5+
- Multi-organization support.
6+
37
## [3.1.5] - 2025-10-23
48
### Fixed
59
- prevent start/stop operations on serverless-standalone services.

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,9 @@ See the examples in `/docs` in this repository for usage examples.
2020
```shell
2121
go install
2222
```
23+
24+
## Documentation
25+
26+
See the [documentation](docs/) for usage examples and detailed guides, including:
27+
28+
- [Multi-Organization Support](docs/guides/multi-organization-support.md) - Managing services across multiple SkySQL organizations
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
---
2+
page_title: "Multi-Organization Support - SkySQL Terraform Provider"
3+
description: |-
4+
Guide for managing services across multiple SkySQL organizations using the org_id parameter
5+
---
6+
7+
# Multi-Organization Support
8+
9+
**Version 3.2.0+** supports managing services across multiple organizations using the `org_id` parameter.
10+
11+
## Overview
12+
13+
When a user belongs to multiple SkySQL organizations, the provider can manage services in different organizations by specifying the `org_id` parameter on each service resource. This parameter adds the `X-MDB-Org` header to all API requests for that service, allowing the backend to operate in the context of the specified organization.
14+
15+
## Usage
16+
17+
### Single Organization (Default Behavior)
18+
19+
If you only work with one organization or want to use the organization associated with your API key, simply omit the `org_id` parameter:
20+
21+
```hcl
22+
resource "skysql_service" "my_service" {
23+
name = "my-database"
24+
service_type = "transactional"
25+
topology = "standalone"
26+
cloud_provider = "aws"
27+
region = "us-east-1"
28+
size = "sky-2x8"
29+
storage = 100
30+
ssl_enabled = true
31+
# org_id is not specified - uses the API key's default organization
32+
}
33+
```
34+
35+
### Multiple Organizations
36+
37+
When managing services across multiple organizations, specify the `org_id` for each service:
38+
39+
```hcl
40+
# Production database in organization A
41+
resource "skysql_service" "prod_db" {
42+
org_id = "org-12345-production"
43+
name = "prod-database"
44+
service_type = "transactional"
45+
topology = "standalone"
46+
cloud_provider = "aws"
47+
region = "us-east-1"
48+
size = "sky-4x16"
49+
storage = 500
50+
ssl_enabled = true
51+
}
52+
53+
# Development database in organization B
54+
resource "skysql_service" "dev_db" {
55+
org_id = "org-67890-development"
56+
name = "dev-database"
57+
service_type = "transactional"
58+
topology = "standalone"
59+
cloud_provider = "gcp"
60+
region = "us-central1"
61+
size = "sky-2x8"
62+
storage = 100
63+
ssl_enabled = true
64+
}
65+
66+
# Staging database in organization C
67+
resource "skysql_service" "staging_db" {
68+
org_id = "org-11223-staging"
69+
name = "staging-database"
70+
service_type = "analytical"
71+
topology = "standalone"
72+
cloud_provider = "aws"
73+
region = "eu-west-1"
74+
size = "sky-2x8"
75+
storage = 200
76+
ssl_enabled = true
77+
}
78+
```
79+
80+
## Important Notes
81+
82+
1. **Replacement Required**: Changing the `org_id` of an existing service will destroy and recreate the service. Plan carefully when setting this value.
83+
2. **API Key Permissions**: Your API key must have access to all organizations specified in your Terraform configuration. If the API key doesn't have access to an organization, requests will fail with a forbidden error.
84+
3. **Backward Compatibility**: The `org_id` parameter is optional. Existing Terraform configurations will continue to work without modification.
85+
86+
## Finding Your Organization ID
87+
88+
You can find your organization IDs through:
89+
90+
- The SkySQL web console (Organization Settings)
91+
- The SkySQL API (`/organization/v1/orgs` endpoint)
92+
- Your organization administrator
93+
94+
## Example: Team-Based Infrastructure
95+
96+
```hcl
97+
# Variables for organization IDs
98+
variable "team_a_org_id" {
99+
description = "Organization ID for Team A"
100+
type = string
101+
}
102+
103+
variable "team_b_org_id" {
104+
description = "Organization ID for Team B"
105+
type = string
106+
}
107+
108+
# Team A's production database
109+
resource "skysql_service" "team_a_prod" {
110+
org_id = var.team_a_org_id
111+
name = "team-a-prod"
112+
service_type = "transactional"
113+
topology = "standalone"
114+
cloud_provider = "aws"
115+
region = "us-east-1"
116+
size = "sky-8x32"
117+
storage = 1000
118+
ssl_enabled = true
119+
}
120+
121+
# Team B's production database
122+
resource "skysql_service" "team_b_prod" {
123+
org_id = var.team_b_org_id
124+
name = "team-b-prod"
125+
service_type = "transactional"
126+
topology = "standalone"
127+
cloud_provider = "aws"
128+
region = "us-west-2"
129+
size = "sky-4x16"
130+
storage = 500
131+
ssl_enabled = true
132+
}
133+
```
134+
135+
## Troubleshooting
136+
137+
**Error: "access to this organization is forbidden"**
138+
- Your API key doesn't have permission to access the specified organization
139+
- Verify the `org_id` value is correct
140+
- Check with your organization administrator to ensure your user account has access
141+
142+
**Service created in wrong organization**
143+
- Double-check the `org_id` value in your configuration
144+
- Remember that omitting `org_id` uses the API key's default organization
145+
146+
**Unexpected replacement during plan**
147+
- Changing `org_id` requires resource replacement - this is by design
148+
- Services cannot be moved between organizations; they must be recreated

docs/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,10 @@ Bye
337337

338338
Run `terraform destroy` to destroy the service.
339339

340+
## Guides
341+
342+
- [Multi-Organization Support](guides/multi-organization-support.md) - Learn how to manage services across multiple SkySQL organizations
343+
340344
## SkySQL provider configuration
341345

342346
Configuration for the SkySQL provider can be derived from several sources,

docs/resources/skysql_service.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ resource "skysql_service" "default" {
6363
- `maxscale_size` (String) The size of the MaxScale nodes. Valid values are: sky-2x4, sky-2x8 etc
6464
- `nodes` (Number) The number of nodes
6565
- `nosql_enabled` (Boolean) Whether to enable NoSQL. Valid values are: true or false
66+
- `org_id` (String) The organization ID to create the service in. When specified, adds the X-MDB-Org header to API requests. **Note:** Changing this value will destroy and recreate the service. See the [Multi-Organization Support guide](../guides/multi-organization-support.md) for more information.
6667
- `primary_host` (String) The primary host of the service
6768
- `project_id` (String) The ID of the project to create the service in
6869
- `replication_enabled` (Boolean) Whether to enable global replication. Valid values are: true or false. Works for xpand-direct topology only

internal/provider/service_resource.go

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ type ServiceResourceModel struct {
9999
FQDN types.String `tfsdk:"fqdn"`
100100
AvailabilityZone types.String `tfsdk:"availability_zone"`
101101
Tags types.Map `tfsdk:"tags"`
102+
OrgID types.String `tfsdk:"org_id"`
102103
}
103104

104105
// ServiceResourceNamedPortModel is an endpoint port
@@ -410,6 +411,13 @@ var serviceResourceSchemaV0 = schema.Schema{
410411
&tagsNamePlanModifier{},
411412
},
412413
},
414+
"org_id": schema.StringAttribute{
415+
Optional: true,
416+
Description: "The organization ID to use for this service. When specified, all API requests will include the X-MDB-Org header to operate in this organization's context. This allows managing services across multiple organizations.",
417+
PlanModifiers: []planmodifier.String{
418+
stringplanmodifier.RequiresReplace(),
419+
},
420+
},
413421
},
414422
Blocks: map[string]schema.Block{
415423
"timeouts": timeouts.Block(context.Background(), timeouts.Opts{
@@ -532,7 +540,7 @@ func (r *ServiceResource) Create(ctx context.Context, req resource.CreateRequest
532540
}
533541
}
534542

535-
service, err := r.client.CreateService(ctx, createServiceRequest)
543+
service, err := r.client.CreateService(ctx, createServiceRequest, skysql.WithOrgID(state.OrgID.ValueString()))
536544
if err != nil {
537545
resp.Diagnostics.AddError("Error creating service", err.Error())
538546
return
@@ -595,7 +603,7 @@ func (r *ServiceResource) Create(ctx context.Context, req resource.CreateRequest
595603
}
596604

597605
err = sdkresource.RetryContext(ctx, createTimeout, func() *sdkresource.RetryError {
598-
service, err := r.client.GetServiceByID(ctx, service.ID)
606+
service, err := r.client.GetServiceByID(ctx, service.ID, skysql.WithOrgID(state.OrgID.ValueString()))
599607
if err != nil {
600608
return sdkresource.NonRetryableError(fmt.Errorf("error retrieving service details: %v", err))
601609
}
@@ -688,7 +696,7 @@ func (r *ServiceResource) Read(ctx context.Context, req resource.ReadRequest, re
688696
}
689697

690698
func (r *ServiceResource) readServiceState(ctx context.Context, data *ServiceResourceModel) error {
691-
service, err := r.client.GetServiceByID(ctx, data.ID.ValueString())
699+
service, err := r.client.GetServiceByID(ctx, data.ID.ValueString(), skysql.WithOrgID(data.OrgID.ValueString()))
692700
if err != nil {
693701
return err
694702
}
@@ -863,7 +871,7 @@ func (r *ServiceResource) updateServiceStorage(ctx context.Context, plan *Servic
863871
"throughput_to": plan.VolumeThroughput.ValueInt64(),
864872
})
865873

866-
err := r.client.ModifyServiceStorage(ctx, state.ID.ValueString(), plan.Storage.ValueInt64(), plan.VolumeIOPS.ValueInt64(), plan.VolumeThroughput.ValueInt64())
874+
err := r.client.ModifyServiceStorage(ctx, state.ID.ValueString(), plan.Storage.ValueInt64(), plan.VolumeIOPS.ValueInt64(), plan.VolumeThroughput.ValueInt64(), skysql.WithOrgID(state.OrgID.ValueString()))
867875
if err != nil {
868876
resp.Diagnostics.AddError("Error updating a storage for the service",
869877
fmt.Sprintf("Unable to update a storage size for the service, got error: %s", err))
@@ -888,7 +896,7 @@ func (r *ServiceResource) updateNumberOfNodeForService(ctx context.Context, plan
888896
"to": plan.Nodes.ValueInt64(),
889897
})
890898

891-
err := r.client.ModifyServiceNodeNumber(ctx, state.ID.ValueString(), plan.Nodes.ValueInt64())
899+
err := r.client.ModifyServiceNodeNumber(ctx, state.ID.ValueString(), plan.Nodes.ValueInt64(), skysql.WithOrgID(state.OrgID.ValueString()))
892900
if err != nil {
893901
resp.Diagnostics.AddError("Error updating a number of nodes for the service", fmt.Sprintf("Unable to update a nodes number for the service, got error: %s", err))
894902
return
@@ -911,7 +919,7 @@ func (r *ServiceResource) updateServiceSize(ctx context.Context, plan *ServiceRe
911919
"to": plan.Size.ValueString(),
912920
})
913921

914-
err := r.client.ModifyServiceSize(ctx, state.ID.ValueString(), plan.Size.ValueString())
922+
err := r.client.ModifyServiceSize(ctx, state.ID.ValueString(), plan.Size.ValueString(), skysql.WithOrgID(state.OrgID.ValueString()))
915923
if err != nil {
916924
resp.Diagnostics.AddError("Error updating service size", fmt.Sprintf("Unable to update service size, got error: %s", err))
917925
return
@@ -963,7 +971,8 @@ func (r *ServiceResource) updateServiceEndpoints(ctx context.Context, plan *Serv
963971
state.ID.ValueString(),
964972
plan.Mechanism.ValueString(),
965973
planAllowedAccounts,
966-
visibility)
974+
visibility,
975+
skysql.WithOrgID(state.OrgID.ValueString()))
967976
if err != nil {
968977
resp.Diagnostics.AddError("Can not update service", err.Error())
969978
return
@@ -1010,7 +1019,7 @@ func (r *ServiceResource) updateAllowList(ctx context.Context, plan *ServiceReso
10101019
})
10111020
}
10121021

1013-
allowListResp, err := r.client.UpdateServiceAllowListByID(ctx, plan.ID.ValueString(), allowListUpdateRequest)
1022+
allowListResp, err := r.client.UpdateServiceAllowListByID(ctx, plan.ID.ValueString(), allowListUpdateRequest, skysql.WithOrgID(plan.OrgID.ValueString()))
10141023
if err != nil {
10151024
if errors.Is(err, skysql.ErrorServiceNotFound) {
10161025
tflog.Warn(ctx, "SkySQL service not found, removing from state", map[string]interface{}{
@@ -1048,7 +1057,7 @@ func (r *ServiceResource) updateServicePowerState(ctx context.Context, plan *Ser
10481057
"id": state.ID.ValueString(),
10491058
"is_active": plan.IsActive.ValueBool(),
10501059
})
1051-
err := r.client.SetServicePowerState(ctx, state.ID.ValueString(), plan.IsActive.ValueBool())
1060+
err := r.client.SetServicePowerState(ctx, state.ID.ValueString(), plan.IsActive.ValueBool(), skysql.WithOrgID(state.OrgID.ValueString()))
10521061
if err != nil {
10531062
resp.Diagnostics.AddError("Can not update service", err.Error())
10541063
return
@@ -1093,7 +1102,7 @@ func (r *ServiceResource) updateServiceTags(ctx context.Context, plan *ServiceRe
10931102
"id": state.ID.ValueString(),
10941103
})
10951104

1096-
err := r.client.UpdateServiceTags(ctx, state.ID.ValueString(), planTags)
1105+
err := r.client.UpdateServiceTags(ctx, state.ID.ValueString(), planTags, skysql.WithOrgID(state.OrgID.ValueString()))
10971106
if err != nil {
10981107
if errors.Is(err, skysql.ErrorServiceNotFound) {
10991108
tflog.Warn(ctx, "SkySQL service not found, removing from state", map[string]interface{}{
@@ -1125,7 +1134,7 @@ var serviceUpdateWaitStates = []string{"ready", "failed", "stopped"}
11251134
func (r *ServiceResource) waitForUpdate(ctx context.Context, state *ServiceResourceModel, resp *resource.UpdateResponse) {
11261135
if state.WaitForUpdate.ValueBool() {
11271136
err := sdkresource.RetryContext(ctx, defaultUpdateTimeout, func() *sdkresource.RetryError {
1128-
service, err := r.client.GetServiceByID(ctx, state.ID.ValueString())
1137+
service, err := r.client.GetServiceByID(ctx, state.ID.ValueString(), skysql.WithOrgID(state.OrgID.ValueString()))
11291138
if err != nil {
11301139
return sdkresource.NonRetryableError(fmt.Errorf("error retrieving service details: %v", err))
11311140
}
@@ -1162,7 +1171,7 @@ func (r *ServiceResource) Delete(ctx context.Context, req resource.DeleteRequest
11621171
return
11631172
}
11641173

1165-
err := r.client.DeleteServiceByID(ctx, state.ID.ValueString())
1174+
err := r.client.DeleteServiceByID(ctx, state.ID.ValueString(), skysql.WithOrgID(state.OrgID.ValueString()))
11661175
if err != nil {
11671176
if errors.Is(err, skysql.ErrorServiceNotFound) {
11681177
tflog.Warn(ctx, "SkySQL service not found, removing from state", map[string]interface{}{
@@ -1183,7 +1192,7 @@ func (r *ServiceResource) Delete(ctx context.Context, req resource.DeleteRequest
11831192
}
11841193

11851194
err = sdkresource.RetryContext(ctx, deleteTimeout, func() *sdkresource.RetryError {
1186-
service, err := r.client.GetServiceByID(ctx, state.ID.ValueString())
1195+
service, err := r.client.GetServiceByID(ctx, state.ID.ValueString(), skysql.WithOrgID(state.OrgID.ValueString()))
11871196
if err != nil {
11881197
if errors.Is(err, skysql.ErrorServiceNotFound) {
11891198
return nil

0 commit comments

Comments
 (0)