Skip to content

Commit

Permalink
Add environment tag management (#196)
Browse files Browse the repository at this point in the history
  • Loading branch information
Khyme committed Apr 29, 2024
1 parent e3f3743 commit 7ef0035
Show file tree
Hide file tree
Showing 12 changed files with 110 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs/data-sources/service.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Service data source

### Optional

- `environment_tag` (String) Environment tag for this service.
- `vpc_id` (Number) VPC ID this service is linked to.

### Read-Only
Expand Down
1 change: 1 addition & 0 deletions docs/resources/service.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ resource "timescale_service" "read_replica" {

- `connection_pooler_enabled` (Boolean) Set connection pooler status for this service.
- `enable_ha_replica` (Boolean) Enable HA Replica
- `environment_tag` (String) Set environment tag for this service.
- `memory_gb` (Number) Memory GB
- `milli_cpu` (Number) Milli CPU
- `name` (String) Service Name is the configurable name assigned to this resource. If none is provided, a default will be generated by the provider.
Expand Down
2 changes: 2 additions & 0 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ var (
ToggleServiceMutation string
//go:embed queries/toggle_connection_pooler.graphql
ToggleConnectionPoolerMutation string
//go:embed queries/set_env_tag.graphql
SetEnvironmentTagMutation string
//go:embed queries/get_all_services.graphql
GetAllServicesQuery string
//go:embed queries/get_service.graphql
Expand Down
8 changes: 6 additions & 2 deletions internal/client/queries/create_service.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mutation CreateService($projectId: ID!, $name: String!, $type: Type!, $resourceConfig:
ResourceConfig, $regionCode: String!, $vpcId: ID, $forkConfig: ForkConfig,
$enableConnectionPooler: Boolean) {
$enableConnectionPooler: Boolean, $environmentTag:ServiceEnvironment) {
createService(data:{
projectId:$projectId,
name:$name,
Expand All @@ -9,7 +9,8 @@ mutation CreateService($projectId: ID!, $name: String!, $type: Type!, $resourceC
regionCode:$regionCode,
forkConfig:$forkConfig,
enableConnectionPooler: $enableConnectionPooler,
vpcId: $vpcId
vpcId: $vpcId,
environmentTag: $environmentTag
}){
initialPassword
service {
Expand Down Expand Up @@ -42,6 +43,9 @@ mutation CreateService($projectId: ID!, $name: String!, $type: Type!, $resourceC
}
}
regionCode
metadata {
environment
}
}
}
}
3 changes: 3 additions & 0 deletions internal/client/queries/get_all_services.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,8 @@ query GetAllServices($projectId: ID!) {
serviceId
isStandby
}
metadata {
environment
}
}
}
3 changes: 3 additions & 0 deletions internal/client/queries/get_service.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,8 @@ query GetService($projectId: ID!, $serviceId: ID!) {
serviceId
isStandby
}
metadata {
environment
}
}
}
7 changes: 7 additions & 0 deletions internal/client/queries/set_env_tag.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mutation SetEnvironmentTag($projectId: ID!, $serviceId: ID!, $environment: ServiceEnvironment!) {
setServiceEnvironmentTag (data:{
serviceId: $serviceId,
projectId: $projectId,
environment: $environment
})
}
34 changes: 33 additions & 1 deletion internal/client/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Service struct {
ReplicaStatus string `json:"replicaStatus"`
VPCEndpoint *VPCEndpoint `json:"vpcEndpoint"`
ForkSpec *ForkSpec `json:"forkedFromId"`
Metadata *Metadata `json:"metadata"`
}

type ServiceSpec struct {
Expand All @@ -49,6 +50,10 @@ type ResourceSpec struct {
} `json:"spec"`
}

type Metadata struct {
Environment string `json:"environment"`
}

type CreateServiceRequest struct {
Name string
MilliCPU string
Expand All @@ -62,6 +67,7 @@ type CreateServiceRequest struct {
ForkConfig *ForkConfig

EnableConnectionPooler bool
EnvironmentTag string
}

type ForkConfig struct {
Expand Down Expand Up @@ -133,7 +139,9 @@ func (c *Client) CreateService(ctx context.Context, request CreateServiceRequest
if request.ForkConfig != nil {
variables["forkConfig"] = request.ForkConfig
}

if request.EnvironmentTag != "" {
variables["environmentTag"] = request.EnvironmentTag
}
req := map[string]interface{}{
"operationName": "CreateService",
"query": CreateServiceMutation,
Expand Down Expand Up @@ -352,3 +360,27 @@ func (c *Client) ToggleConnectionPooler(ctx context.Context, serviceID string, e
}
return nil
}

func (c *Client) SetEnvironmentTag(ctx context.Context, serviceID, environment string) error {
tflog.Trace(ctx, "Client.SetEnvironmentTag")
req := map[string]interface{}{
"operationName": "SetEnvironmentTag",
"query": SetEnvironmentTagMutation,
"variables": map[string]any{
"projectId": c.projectID,
"serviceId": serviceID,
"environment": environment,
},
}
var resp Response[any]
if err := c.do(ctx, req, &resp); err != nil {
return err
}
if len(resp.Errors) > 0 {
return resp.Errors[0]
}
if resp.Data == nil {
return errors.New("no response found")
}
return nil
}
9 changes: 9 additions & 0 deletions internal/provider/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,19 @@ type ServiceConfig struct {
VpcID int64
ReadReplicaSource string
Pooler bool
Environment string
}

func (c *ServiceConfig) WithName(name string) *ServiceConfig {
c.Name = name
return c
}

func (c *ServiceConfig) WithEnvironment(name string) *ServiceConfig {
c.Environment = name
return c
}

func (c *ServiceConfig) WithSpec(milliCPU, memoryGB int64) *ServiceConfig {
c.MilliCPU = milliCPU
c.MemoryGB = memoryGB
Expand Down Expand Up @@ -218,6 +224,9 @@ func (c *ServiceConfig) String(t *testing.T) string {
if c.Pooler {
write("connection_pooler_enabled = %t \n", c.Pooler)
}
if c.Environment != "" {
write("environment_tag = %q \n", c.Environment)
}
if c.RegionCode != "" {
write("region_code = %q \n", c.RegionCode)
}
Expand Down
14 changes: 14 additions & 0 deletions internal/provider/service_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import (
"fmt"
"strconv"

"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"

Expand Down Expand Up @@ -37,6 +39,8 @@ type ServiceDataSourceModel struct {
Resources []ResourceModel `tfsdk:"resources"`
Created types.String `tfsdk:"created"`
VpcID types.Int64 `tfsdk:"vpc_id"`

EnvironmentTag types.String `tfsdk:"environment_tag"`
}

type SpecModel struct {
Expand Down Expand Up @@ -153,6 +157,13 @@ func (d *ServiceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
Optional: true,
Computed: true,
},
"environment_tag": schema.StringAttribute{
MarkdownDescription: "Environment tag for this service.",
Description: "Environment tag for this service.",
Optional: true,
Computed: true,
Validators: []validator.String{stringvalidator.OneOf("DEV", "PROD")},
},
},
}
}
Expand Down Expand Up @@ -234,5 +245,8 @@ func serviceToDataModel(diag diag.Diagnostics, s *tsClient.Service) ServiceDataS
},
})
}
if s.Metadata != nil {
serviceModel.EnvironmentTag = types.StringValue(s.Metadata.Environment)
}
return serviceModel
}
24 changes: 23 additions & 1 deletion internal/provider/service_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
Expand Down Expand Up @@ -77,7 +78,8 @@ type serviceResourceModel struct {
ReadReplicaSource types.String `tfsdk:"read_replica_source"`
VpcID types.Int64 `tfsdk:"vpc_id"`

ConnectionPoolerEnabled types.Bool `tfsdk:"connection_pooler_enabled"`
ConnectionPoolerEnabled types.Bool `tfsdk:"connection_pooler_enabled"`
EnvironmentTag types.String `tfsdk:"environment_tag"`
}

func (r *ServiceResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
Expand Down Expand Up @@ -194,6 +196,16 @@ The change has been taken into account but must still be propagated. You can run
boolplanmodifier.UseStateForUnknown(),
},
},
"environment_tag": schema.StringAttribute{
MarkdownDescription: "Set environment tag for this service.",
Description: "Set environment tag for this service.",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
Validators: []validator.String{stringvalidator.OneOf("DEV", "PROD")},
},
"username": schema.StringAttribute{
Description: "The Postgres user for this service",
MarkdownDescription: "The Postgres user for this service",
Expand Down Expand Up @@ -277,6 +289,7 @@ func (r *ServiceResource) Create(ctx context.Context, req resource.CreateRequest
RegionCode: plan.RegionCode.ValueString(),
ReplicaCount: strconv.FormatInt(replicaCount, 10),
EnableConnectionPooler: plan.ConnectionPoolerEnabled.ValueBool(),
EnvironmentTag: plan.EnvironmentTag.ValueString(),
}
if !plan.VpcID.IsNull() {
request.VpcID = plan.VpcID.ValueInt64()
Expand Down Expand Up @@ -469,6 +482,12 @@ func (r *ServiceResource) Update(ctx context.Context, req resource.UpdateRequest
return
}
}
if plan.EnvironmentTag != state.EnvironmentTag {
if err := r.client.SetEnvironmentTag(ctx, serviceID, plan.EnvironmentTag.ValueString()); err != nil {
resp.Diagnostics.AddError("Failed to set environment tag", err.Error())
return
}
}
if !plan.PoolerHostname.IsUnknown() {
resp.Diagnostics.AddError(ErrUpdateService, "Do not support pooler hostname change")
return
Expand Down Expand Up @@ -614,6 +633,9 @@ func serviceToResource(diag diag.Diagnostics, s *tsClient.Service, state service
model.Hostname = types.StringValue(s.VPCEndpoint.Host)
model.Port = types.Int64Value(s.VPCEndpoint.Port)
}
if s.Metadata != nil {
model.EnvironmentTag = types.StringValue(s.Metadata.Environment)
}

return model
}
8 changes: 8 additions & 0 deletions internal/provider/service_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func TestServiceResource_Default_Success(t *testing.T) {
resource.TestCheckResourceAttr("timescale_service.resource", "region_code", "us-east-1"),
resource.TestCheckResourceAttr("timescale_service.resource", "enable_ha_replica", "false"),
resource.TestCheckResourceAttr("timescale_service.resource", "connection_pooler_enabled", "false"),
resource.TestCheckResourceAttr("timescale_service.resource", "environment_tag", "DEV"),
resource.TestCheckNoResourceAttr("timescale_service.resource", "vpc_id"),
),
},
Expand All @@ -55,6 +56,13 @@ func TestServiceResource_Default_Success(t *testing.T) {
resource.TestCheckResourceAttr("timescale_service.resource", "name", "service resource test update"),
),
},
// Update tag
{
Config: getServiceConfig(t, config.WithEnvironment("PROD")),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("timescale_service.resource", "environment_tag", "PROD"),
),
},
// Enable pooler
{
Config: getServiceConfig(t, config.WithPooler(true)),
Expand Down

0 comments on commit 7ef0035

Please sign in to comment.