Skip to content

Commit 2c898d1

Browse files
committed
feature(serverbackup): Onboard enable backup service
STACKITTPR-543 Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de>
1 parent e855937 commit 2c898d1

File tree

14 files changed

+802
-25
lines changed

14 files changed

+802
-25
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_backup_enable Data Source - stackit"
4+
subcategory: ""
5+
description: |-
6+
Server backup enable datasource schema. Must have a region specified in the provider configuration.
7+
---
8+
9+
# stackit_server_backup_enable (Data Source)
10+
11+
Server backup 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 backup enable is associated.
21+
- `server_id` (String) Server ID to which the server backup 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_backup_enable Resource - stackit"
4+
subcategory: ""
5+
description: |-
6+
Server backup 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_backup_enable (Resource)
10+
11+
Server backup 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 backup enable is associated.
21+
- `server_id` (String) Server ID to which the server backup enable is associated.
22+
23+
### Optional
24+
25+
- `backup_policy_id` (String) The backup policy ID.
26+
- `region` (String) The resource region. If not defined, the provider region is used.
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_backup_schedule.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ resource "stackit_server_backup_schedule" "example" {
2424
retention_period = 14
2525
volume_ids = null
2626
}
27+
depends_on = [
28+
stackit_server_backup_enable.enable
29+
]
30+
}
31+
32+
resource "stackit_server_backup_enable" "enable" {
33+
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
34+
server_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
2735
}
2836
2937
# Only use the import statement, if you want to import an existing server backup schedule

examples/resources/stackit_server_backup_schedule/resource.tf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ resource "stackit_server_backup_schedule" "example" {
99
retention_period = 14
1010
volume_ids = null
1111
}
12+
depends_on = [
13+
stackit_server_backup_enable.enable
14+
]
15+
}
16+
17+
resource "stackit_server_backup_enable" "enable" {
18+
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
19+
server_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
1220
}
1321

1422
# Only use the import statement, if you want to import an existing server backup 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/serverbackup"
14+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
15+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
16+
serverBackupUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/serverbackup/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 = &serverBackupEnableDataSource{}
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+
// NewServerBackupEnableDataSource is a helper function to simplify the provider implementation.
35+
func NewServerBackupEnableDataSource() datasource.DataSource {
36+
return &serverBackupEnableDataSource{}
37+
}
38+
39+
// serverBackupEnableDataSource is the data source implementation.
40+
type serverBackupEnableDataSource struct {
41+
client *serverbackup.APIClient
42+
providerData core.ProviderData
43+
}
44+
45+
// Metadata returns the data source type name.
46+
func (d *serverBackupEnableDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
47+
resp.TypeName = req.ProviderTypeName + "_server_backup_enable"
48+
}
49+
50+
// Configure adds the provider configured client to the data source.
51+
func (d *serverBackupEnableDataSource) 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 := serverBackupUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
59+
if resp.Diagnostics.HasError() {
60+
return
61+
}
62+
d.client = apiClient
63+
tflog.Info(ctx, "Server backup client client configured")
64+
}
65+
66+
// Schema defines the schema for the resource.
67+
func (d *serverBackupEnableDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
68+
descriptions := map[string]string{
69+
"main": "Server backup 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 backup enable is associated.",
72+
"server_id": "Server ID to which the server backup 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 *serverBackupEnableDataSource) 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 backup enable",
139+
fmt.Sprintf("Server backup 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 backup 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 backup enable read")
164+
}
165+
166+
func mapDataFields(serviceResp *serverbackup.GetBackupServiceResponse, 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/serverbackup"
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 *serverbackup.GetBackupServiceResponse
19+
expected DataModel
20+
isValid bool
21+
}{
22+
{
23+
"default_values",
24+
&serverbackup.GetBackupServiceResponse{},
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+
&serverbackup.GetBackupServiceResponse{
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)