Skip to content

Commit aa1a370

Browse files
feat(serverupdate): Onboard enable update service (#1337)
* feat(serverupdate): Onboard enable update service STACKITTPR-586 Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de> * Update stackit/internal/services/serverupdate/schedule/resource.go Co-authored-by: cgoetz-inovex <carlo.goetz@inovex.de> --------- Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de> Co-authored-by: cgoetz-inovex <carlo.goetz@inovex.de>
1 parent a44cfba commit aa1a370

File tree

14 files changed

+799
-24
lines changed

14 files changed

+799
-24
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "stackit_server_update_enable Data Source - stackit"
4+
subcategory: ""
5+
description: |-
6+
Server update enable datasource schema. Must have a region specified in the provider configuration.
7+
---
8+
9+
# stackit_server_update_enable (Data Source)
10+
11+
Server update enable datasource schema. Must have a `region` specified in the provider configuration.
12+
13+
14+
15+
<!-- schema generated by tfplugindocs -->
16+
## Schema
17+
18+
### Required
19+
20+
- `project_id` (String) STACKIT Project ID to which the server update enable is associated.
21+
- `server_id` (String) Server ID to which the server update enable is associated.
22+
23+
### Optional
24+
25+
- `region` (String) The resource region. If not defined, the provider region is used.
26+
27+
### Read-Only
28+
29+
- `enabled` (Boolean) Set to true if the service is enabled.
30+
- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`server_id`,`region`".
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "stackit_server_update_enable Resource - stackit"
4+
subcategory: ""
5+
description: |-
6+
Server update enable resource schema. Must have a region specified in the provider configuration. Always use only one enable resource per server.
7+
---
8+
9+
# stackit_server_update_enable (Resource)
10+
11+
Server update enable resource schema. Must have a `region` specified in the provider configuration. Always use only one enable resource per server.
12+
13+
14+
15+
<!-- schema generated by tfplugindocs -->
16+
## Schema
17+
18+
### Required
19+
20+
- `project_id` (String) STACKIT Project ID to which the server update enable is associated.
21+
- `server_id` (String) Server ID to which the server update enable is associated.
22+
23+
### Optional
24+
25+
- `region` (String) The resource region. If not defined, the provider region is used.
26+
- `update_policy_id` (String) The update policy ID.
27+
28+
### Read-Only
29+
30+
- `enabled` (Boolean) Set to true if the service is enabled.
31+
- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`server_id`,`region`".

docs/resources/server_update_schedule.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ resource "stackit_server_update_schedule" "example" {
2020
rrule = "DTSTART;TZID=Europe/Sofia:20200803T023000 RRULE:FREQ=DAILY;INTERVAL=1"
2121
enabled = true
2222
maintenance_window = 1
23+
depends_on = [
24+
stackit_server_update_enable.enable
25+
]
26+
}
27+
28+
resource "stackit_server_update_enable" "enable" {
29+
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
30+
server_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
2331
}
2432
2533
# Only use the import statement, if you want to import an existing server update schedule

examples/resources/stackit_server_update_schedule/resource.tf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ resource "stackit_server_update_schedule" "example" {
55
rrule = "DTSTART;TZID=Europe/Sofia:20200803T023000 RRULE:FREQ=DAILY;INTERVAL=1"
66
enabled = true
77
maintenance_window = 1
8+
depends_on = [
9+
stackit_server_update_enable.enable
10+
]
11+
}
12+
13+
resource "stackit_server_update_enable" "enable" {
14+
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
15+
server_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
816
}
917

1018
# Only use the import statement, if you want to import an existing server update schedule
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package enable
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/hashicorp/terraform-plugin-framework/datasource"
9+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
10+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
12+
"github.com/hashicorp/terraform-plugin-log/tflog"
13+
"github.com/stackitcloud/stackit-sdk-go/services/serverupdate"
14+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
15+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
16+
serverUpdateUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/serverupdate/utils"
17+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
18+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
19+
)
20+
21+
// Ensure the implementation satisfies the expected interfaces.
22+
var (
23+
_ datasource.DataSource = &serverUpdateEnableDataSource{}
24+
)
25+
26+
type DataModel struct {
27+
Id types.String `tfsdk:"id"` // needed by TF
28+
ProjectId types.String `tfsdk:"project_id"`
29+
ServerId types.String `tfsdk:"server_id"`
30+
Enabled types.Bool `tfsdk:"enabled"`
31+
Region types.String `tfsdk:"region"`
32+
}
33+
34+
// NewServerUpdateEnableDataSource is a helper function to simplify the provider implementation.
35+
func NewServerUpdateEnableDataSource() datasource.DataSource {
36+
return &serverUpdateEnableDataSource{}
37+
}
38+
39+
// serverUpdateEnableDataSource is the data source implementation.
40+
type serverUpdateEnableDataSource struct {
41+
client *serverupdate.APIClient
42+
providerData core.ProviderData
43+
}
44+
45+
// Metadata returns the data source type name.
46+
func (d *serverUpdateEnableDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
47+
resp.TypeName = req.ProviderTypeName + "_server_update_enable"
48+
}
49+
50+
// Configure adds the provider configured client to the data source.
51+
func (d *serverUpdateEnableDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
52+
var ok bool
53+
d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
54+
if !ok {
55+
return
56+
}
57+
58+
apiClient := serverUpdateUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
59+
if resp.Diagnostics.HasError() {
60+
return
61+
}
62+
d.client = apiClient
63+
tflog.Info(ctx, "Server update client client configured")
64+
}
65+
66+
// Schema defines the schema for the resource.
67+
func (d *serverUpdateEnableDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
68+
descriptions := map[string]string{
69+
"main": "Server update enable datasource schema. Must have a `region` specified in the provider configuration.",
70+
"id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`server_id`,`region`\".",
71+
"project_id": "STACKIT Project ID to which the server update enable is associated.",
72+
"server_id": "Server ID to which the server update enable is associated.",
73+
"enabled": "Set to true if the service is enabled.",
74+
"region": "The resource region. If not defined, the provider region is used.",
75+
}
76+
77+
resp.Schema = schema.Schema{
78+
Description: descriptions["main"],
79+
Attributes: map[string]schema.Attribute{
80+
"id": schema.StringAttribute{
81+
Description: descriptions["id"],
82+
Computed: true,
83+
},
84+
"project_id": schema.StringAttribute{
85+
Description: descriptions["project_id"],
86+
Required: true,
87+
Validators: []validator.String{
88+
validate.UUID(),
89+
validate.NoSeparator(),
90+
},
91+
},
92+
"server_id": schema.StringAttribute{
93+
Description: descriptions["server_id"],
94+
Required: true,
95+
Validators: []validator.String{
96+
validate.UUID(),
97+
validate.NoSeparator(),
98+
},
99+
},
100+
"enabled": schema.BoolAttribute{
101+
Description: descriptions["enabled"],
102+
Computed: true,
103+
},
104+
"region": schema.StringAttribute{
105+
Optional: true,
106+
// the region cannot be found automatically, so it has to be passed
107+
Description: descriptions["region"],
108+
},
109+
},
110+
}
111+
}
112+
113+
// Read refreshes the Terraform state with the latest data.
114+
func (d *serverUpdateEnableDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
115+
var model DataModel
116+
diags := req.Config.Get(ctx, &model)
117+
resp.Diagnostics.Append(diags...)
118+
if resp.Diagnostics.HasError() {
119+
return
120+
}
121+
122+
ctx = core.InitProviderContext(ctx)
123+
124+
projectId := model.ProjectId.ValueString()
125+
serverId := model.ServerId.ValueString()
126+
region := d.providerData.GetRegionWithOverride(model.Region)
127+
128+
ctx = tflog.SetField(ctx, "project_id", projectId)
129+
ctx = tflog.SetField(ctx, "server_id", serverId)
130+
ctx = tflog.SetField(ctx, "region", region)
131+
132+
serviceResp, err := d.client.GetServiceResource(ctx, projectId, serverId, region).Execute()
133+
if err != nil {
134+
utils.LogError(
135+
ctx,
136+
&resp.Diagnostics,
137+
err,
138+
"Reading server update enable",
139+
fmt.Sprintf("Server update enable does not exist for this server %q.", serverId),
140+
map[int]string{
141+
http.StatusForbidden: fmt.Sprintf("Project with ID %q or server with ID %q not found or forbidden access", projectId, serverId),
142+
},
143+
)
144+
resp.State.RemoveResource(ctx)
145+
return
146+
}
147+
148+
ctx = core.LogResponse(ctx)
149+
150+
// Map response body to schema
151+
err = mapDataFields(serviceResp, &model, region)
152+
if err != nil {
153+
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading server update enable", fmt.Sprintf("Processing API payload: %v", err))
154+
return
155+
}
156+
157+
// Set refreshed state
158+
diags = resp.State.Set(ctx, model)
159+
resp.Diagnostics.Append(diags...)
160+
if resp.Diagnostics.HasError() {
161+
return
162+
}
163+
tflog.Info(ctx, "Server update enable read")
164+
}
165+
166+
func mapDataFields(serviceResp *serverupdate.GetUpdateServiceResponse, model *DataModel, region string) error {
167+
if serviceResp == nil {
168+
return fmt.Errorf("response input is nil")
169+
}
170+
if model == nil {
171+
return fmt.Errorf("model input is nil")
172+
}
173+
174+
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.ServerId.ValueString(), region)
175+
model.Region = types.StringValue(region)
176+
model.Enabled = types.BoolPointerValue(serviceResp.Enabled)
177+
178+
return nil
179+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package enable
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/hashicorp/terraform-plugin-framework/types"
9+
"github.com/stackitcloud/stackit-sdk-go/core/utils"
10+
"github.com/stackitcloud/stackit-sdk-go/services/serverupdate"
11+
)
12+
13+
func TestDataMapFields(t *testing.T) {
14+
const testRegion = "eu01"
15+
id := fmt.Sprintf("%s,%s,%s", "pid", "sid", testRegion)
16+
tests := []struct {
17+
description string
18+
input *serverupdate.GetUpdateServiceResponse
19+
expected DataModel
20+
isValid bool
21+
}{
22+
{
23+
"default_values",
24+
&serverupdate.GetUpdateServiceResponse{},
25+
DataModel{
26+
Id: types.StringValue(id),
27+
ProjectId: types.StringValue("pid"),
28+
ServerId: types.StringValue("sid"),
29+
Region: types.StringValue("eu01"),
30+
},
31+
true,
32+
},
33+
{
34+
"simple_values",
35+
&serverupdate.GetUpdateServiceResponse{
36+
Enabled: utils.Ptr(true),
37+
},
38+
DataModel{
39+
Id: types.StringValue(id),
40+
ProjectId: types.StringValue("pid"),
41+
ServerId: types.StringValue("sid"),
42+
Region: types.StringValue("eu01"),
43+
Enabled: types.BoolValue(true),
44+
},
45+
true,
46+
},
47+
{
48+
"nil_response",
49+
nil,
50+
DataModel{},
51+
false,
52+
},
53+
}
54+
for _, tt := range tests {
55+
t.Run(tt.description, func(t *testing.T) {
56+
model := &DataModel{
57+
ProjectId: tt.expected.ProjectId,
58+
ServerId: tt.expected.ServerId,
59+
}
60+
err := mapDataFields(tt.input, model, "eu01")
61+
if !tt.isValid && err == nil {
62+
t.Fatalf("Should have failed")
63+
}
64+
if tt.isValid && err != nil {
65+
t.Fatalf("Should not have failed: %v", err)
66+
}
67+
if tt.isValid {
68+
diff := cmp.Diff(model, &tt.expected)
69+
if diff != "" {
70+
t.Fatalf("Data does not match: %s", diff)
71+
}
72+
}
73+
})
74+
}
75+
}

0 commit comments

Comments
 (0)