Skip to content

Commit b7e25e0

Browse files
Adrian Nackovaeter
authored andcommitted
STACKITRCO-187 - Add option iaas API param agent
Adds a terraform `stackit_server` option for the iaas ( _create server_ ) API param: `"agent": {"provisioned": true}` ref STACKITRCO-187 ref: stackitcloud/stackit-cli#1213 review url: #1113 --- Tests: * ran `make fmt`, `make generate-docs`, `make lint` * ran unit tests ``` [~/terraform-provider-stackit] go test stackit/internal/services/iaas/server/* ok command-line-arguments 15.005s [~/terraform-provider-stackit] make test ... ok github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/server 15.006s coverage: 33.0% of statements ... [~/terraform-provider-stackit] ``` * Tested: with a locally-configured terraform, by adding, changing, deleting `agent`-related parts of a tf config. Signed-off-by: Adrian Nackov <adrian.nackov@digits.schwarz>
1 parent cb9405d commit b7e25e0

8 files changed

Lines changed: 210 additions & 8 deletions

File tree

docs/data-sources/server.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ data "stackit_server" "example" {
3434
### Read-Only
3535

3636
- `affinity_group` (String) The affinity group the server is assigned to.
37+
- `agent` (Attributes) STACKIT Server Agent as setup on the server (see [below for nested schema](#nestedatt--agent))
3738
- `availability_zone` (String) The availability zone of the server.
3839
- `boot_volume` (Attributes) The boot volume for the server (see [below for nested schema](#nestedatt--boot_volume))
3940
- `created_at` (String) Date-time when the server was created
@@ -48,6 +49,14 @@ data "stackit_server" "example" {
4849
- `updated_at` (String) Date-time when the server was updated
4950
- `user_data` (String) User data that is passed via cloud-init to the server.
5051

52+
<a id="nestedatt--agent"></a>
53+
### Nested Schema for `agent`
54+
55+
Read-Only:
56+
57+
- `provisioned` (Boolean) Whether a STACKIT Server Agent is provisioned at the server
58+
59+
5160
<a id="nestedatt--boot_volume"></a>
5261
### Nested Schema for `boot_volume`
5362

docs/resources/server.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ import {
404404
### Optional
405405

406406
- `affinity_group` (String) The affinity group the server is assigned to.
407+
- `agent` (Attributes) The STACKIT Server Agent configured for the server (see [below for nested schema](#nestedatt--agent))
407408
- `availability_zone` (String) The availability zone of the server.
408409
- `boot_volume` (Attributes) The boot volume for the server (see [below for nested schema](#nestedatt--boot_volume))
409410
- `desired_status` (String) The desired status of the server resource. Possible values are: `active`, `inactive`, `deallocated`.
@@ -422,6 +423,18 @@ import {
422423
- `server_id` (String) The server ID.
423424
- `updated_at` (String) Date-time when the server was updated
424425

426+
<a id="nestedatt--agent"></a>
427+
### Nested Schema for `agent`
428+
429+
Optional:
430+
431+
- `provisioning_policy` (String) Agent provisioning policy: "ALWAYS", "NEVER", or "INHERIT". "INHERIT" follows the image default value.
432+
433+
Read-Only:
434+
435+
- `provisioned` (Boolean) Whether a STACKIT Server Agent is provisioned at the server
436+
437+
425438
<a id="nestedatt--boot_volume"></a>
426439
### Nested Schema for `boot_volume`
427440

stackit/internal/services/iaas/iaas_acc_test.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ var testConfigServerVarsMax = config.Variables{
149149
"service_account_mail": config.StringVariable(testutil.TestProjectServiceAccountEmail),
150150
"public_key": config.StringVariable(keypairPublicKey),
151151
"desired_status": config.StringVariable("active"),
152+
"agent_policy": config.StringVariable("ALWAYS"),
152153
}
153154

154155
var testConfigServerVarsMaxUpdated = func() config.Variables {
@@ -158,6 +159,7 @@ var testConfigServerVarsMaxUpdated = func() config.Variables {
158159
updatedConfig["machine_type"] = config.StringVariable("t1.2")
159160
updatedConfig["label"] = config.StringVariable("updated")
160161
updatedConfig["desired_status"] = config.StringVariable("inactive")
162+
updatedConfig["agent_policy"] = config.StringVariable("NEVER")
161163
return updatedConfig
162164
}()
163165

@@ -2223,6 +2225,8 @@ func TestAccServerMin(t *testing.T) {
22232225
resource.TestCheckResourceAttrSet("stackit_server.server", "created_at"),
22242226
resource.TestCheckResourceAttrSet("stackit_server.server", "launched_at"),
22252227
resource.TestCheckResourceAttrSet("stackit_server.server", "updated_at"),
2228+
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioning_policy", "INHERIT"),
2229+
resource.TestCheckNoResourceAttr("stackit_server.server", "agent.provisioned"),
22262230
),
22272231
},
22282232
// Data source
@@ -2275,6 +2279,7 @@ func TestAccServerMin(t *testing.T) {
22752279
resource.TestCheckResourceAttrSet("data.stackit_server.server", "created_at"),
22762280
resource.TestCheckResourceAttrSet("data.stackit_server.server", "launched_at"),
22772281
resource.TestCheckResourceAttrSet("data.stackit_server.server", "updated_at"),
2282+
resource.TestCheckNoResourceAttr("data.stackit_server.server", "agent.provisioned"),
22782283
),
22792284
},
22802285
// Import
@@ -2328,6 +2333,8 @@ func TestAccServerMin(t *testing.T) {
23282333
resource.TestCheckResourceAttrSet("stackit_server.server", "created_at"),
23292334
resource.TestCheckResourceAttrSet("stackit_server.server", "launched_at"),
23302335
resource.TestCheckResourceAttrSet("stackit_server.server", "updated_at"),
2336+
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioning_policy", "INHERIT"),
2337+
resource.TestCheckNoResourceAttr("stackit_server.server", "agent.provisioned"),
23312338
),
23322339
},
23332340
// Deletion is done by the framework implicitly
@@ -2352,6 +2359,10 @@ func TestAccServerMax(t *testing.T) {
23522359
resource.TestCheckResourceAttr("stackit_affinity_group.affinity_group", "policy", testutil.ConvertConfigVariable(testConfigServerVarsMax["policy"])),
23532360
resource.TestCheckResourceAttrSet("stackit_affinity_group.affinity_group", "affinity_group_id"),
23542361

2362+
// Agent
2363+
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioning_policy", testutil.ConvertConfigVariable(testConfigServerVarsMax["agent_policy"])),
2364+
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioned", "true"),
2365+
23552366
// Volume base
23562367
resource.TestCheckResourceAttr("stackit_volume.base_volume", "project_id", testutil.ConvertConfigVariable(testConfigServerVarsMax["project_id"])),
23572368
resource.TestCheckResourceAttr("stackit_volume.base_volume", "availability_zone", testutil.ConvertConfigVariable(testConfigServerVarsMax["availability_zone"])),
@@ -2500,6 +2511,7 @@ func TestAccServerMax(t *testing.T) {
25002511
"stackit_key_pair.key_pair", "name",
25012512
"data.stackit_server.server", "keypair_name",
25022513
),
2514+
resource.TestCheckResourceAttr("data.stackit_server.server", "agent.provisioned", "true"),
25032515
// All network interface which was are attached appear here
25042516
resource.TestCheckResourceAttr("data.stackit_server.server", "network_interfaces.#", "2"),
25052517
resource.TestCheckTypeSetElemAttrPair(
@@ -2725,7 +2737,7 @@ func TestAccServerMax(t *testing.T) {
27252737
},
27262738
ImportState: true,
27272739
ImportStateVerify: true,
2728-
ImportStateVerifyIgnore: []string{"boot_volume", "desired_status", "network_interfaces"}, // Field is not mapped as it is only relevant on creation
2740+
ImportStateVerifyIgnore: []string{"boot_volume", "desired_status", "network_interfaces", "agent"}, // Field is not mapped as it is only relevant on creation
27292741
},
27302742
// Update
27312743
{
@@ -2738,6 +2750,10 @@ func TestAccServerMax(t *testing.T) {
27382750
resource.TestCheckResourceAttr("stackit_affinity_group.affinity_group", "policy", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdated["policy"])),
27392751
resource.TestCheckResourceAttrSet("stackit_affinity_group.affinity_group", "affinity_group_id"),
27402752

2753+
// Agent
2754+
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioning_policy", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdated["agent_policy"])),
2755+
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioned", "false"),
2756+
27412757
// Volume base
27422758
resource.TestCheckResourceAttr("stackit_volume.base_volume", "project_id", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdated["project_id"])),
27432759
resource.TestCheckResourceAttr("stackit_volume.base_volume", "availability_zone", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdated["availability_zone"])),
@@ -2843,6 +2859,10 @@ func TestAccServerMax(t *testing.T) {
28432859
resource.TestCheckResourceAttr("stackit_affinity_group.affinity_group", "policy", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdatedDesiredStatus["policy"])),
28442860
resource.TestCheckResourceAttrSet("stackit_affinity_group.affinity_group", "affinity_group_id"),
28452861

2862+
// Agent
2863+
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioning_policy", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdatedDesiredStatus["agent_policy"])),
2864+
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioned", "false"),
2865+
28462866
// Volume base
28472867
resource.TestCheckResourceAttr("stackit_volume.base_volume", "project_id", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdatedDesiredStatus["project_id"])),
28482868
resource.TestCheckResourceAttr("stackit_volume.base_volume", "availability_zone", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdatedDesiredStatus["availability_zone"])),

stackit/internal/services/iaas/server/datasource.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type DataSourceModel struct {
3535
ServerId types.String `tfsdk:"server_id"`
3636
MachineType types.String `tfsdk:"machine_type"`
3737
Name types.String `tfsdk:"name"`
38+
Agent types.Object `tfsdk:"agent"`
3839
AvailabilityZone types.String `tfsdk:"availability_zone"`
3940
BootVolume types.Object `tfsdk:"boot_volume"`
4041
ImageId types.String `tfsdk:"image_id"`
@@ -53,6 +54,10 @@ var bootVolumeDataTypes = map[string]attr.Type{
5354
"delete_on_termination": basetypes.BoolType{},
5455
}
5556

57+
var agentDataTypes = map[string]attr.Type{
58+
"provisioned": basetypes.BoolType{},
59+
}
60+
5661
// NewServerDataSource is a helper function to simplify the provider implementation.
5762
func NewServerDataSource() datasource.DataSource {
5863
return &serverDataSource{}
@@ -124,6 +129,16 @@ func (d *serverDataSource) Schema(_ context.Context, _ datasource.SchemaRequest,
124129
MarkdownDescription: "Name of the type of the machine for the server. Possible values are documented in [Virtual machine flavors](https://docs.stackit.cloud/products/compute-engine/server/basics/machine-types/)",
125130
Computed: true,
126131
},
132+
"agent": schema.SingleNestedAttribute{
133+
Description: "STACKIT Server Agent as setup on the server",
134+
Computed: true,
135+
Attributes: map[string]schema.Attribute{
136+
"provisioned": schema.BoolAttribute{
137+
Description: "Whether a STACKIT Server Agent is provisioned at the server",
138+
Computed: true,
139+
},
140+
},
141+
},
127142
"availability_zone": schema.StringAttribute{
128143
Description: "The availability zone of the server.",
129144
Computed: true,
@@ -305,6 +320,18 @@ func mapDataSourceFields(ctx context.Context, serverResp *iaas.Server, model *Da
305320
model.BootVolume = types.ObjectNull(bootVolumeDataTypes)
306321
}
307322

323+
agentProvisioned := types.BoolNull()
324+
if serverResp.Agent != nil && serverResp.Agent.Provisioned != nil {
325+
agentProvisioned = types.BoolPointerValue(serverResp.Agent.Provisioned)
326+
}
327+
agent, diags := types.ObjectValue(agentDataTypes, map[string]attr.Value{
328+
"provisioned": agentProvisioned,
329+
})
330+
if diags.HasError() {
331+
return fmt.Errorf("failed to map agent: %w", core.DiagsToError(diags))
332+
}
333+
model.Agent = agent
334+
308335
if serverResp.UserData != nil && len(*serverResp.UserData) > 0 {
309336
model.UserData = types.StringValue(string(*serverResp.UserData))
310337
}

stackit/internal/services/iaas/server/datasource_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import (
1010
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
1111
)
1212

13+
var expectedNullAgentData, _ = types.ObjectValue(agentDataTypes, map[string]attr.Value{
14+
"provisioned": types.BoolNull(),
15+
})
16+
1317
func TestMapDataSourceFields(t *testing.T) {
1418
type args struct {
1519
state DataSourceModel
@@ -40,6 +44,7 @@ func TestMapDataSourceFields(t *testing.T) {
4044
ServerId: types.StringValue("sid"),
4145
Name: types.StringNull(),
4246
AvailabilityZone: types.StringNull(),
47+
Agent: expectedNullAgentData,
4348
Labels: types.MapNull(types.StringType),
4449
ImageId: types.StringNull(),
4550
NetworkInterfaces: types.ListNull(types.StringType),
@@ -77,7 +82,10 @@ func TestMapDataSourceFields(t *testing.T) {
7782
NicId: new("nic2"),
7883
},
7984
},
80-
KeypairName: new("keypair_name"),
85+
KeypairName: new("keypair_name"),
86+
Agent: &iaas.ServerAgent{
87+
Provisioned: new(true),
88+
},
8189
AffinityGroup: new("group_id"),
8290
CreatedAt: new(testTimestamp()),
8391
UpdatedAt: new(testTimestamp()),
@@ -100,7 +108,10 @@ func TestMapDataSourceFields(t *testing.T) {
100108
types.StringValue("nic1"),
101109
types.StringValue("nic2"),
102110
}),
103-
KeypairName: types.StringValue("keypair_name"),
111+
KeypairName: types.StringValue("keypair_name"),
112+
Agent: types.ObjectValueMust(agentDataTypes, map[string]attr.Value{
113+
"provisioned": types.BoolValue(true),
114+
}),
104115
AffinityGroup: types.StringValue("group_id"),
105116
CreatedAt: types.StringValue(testTimestampValue),
106117
UpdatedAt: types.StringValue(testTimestampValue),
@@ -131,6 +142,7 @@ func TestMapDataSourceFields(t *testing.T) {
131142
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
132143
ImageId: types.StringNull(),
133144
NetworkInterfaces: types.ListNull(types.StringType),
145+
Agent: expectedNullAgentData,
134146
KeypairName: types.StringNull(),
135147
AffinityGroup: types.StringNull(),
136148
UserData: types.StringNull(),

stackit/internal/services/iaas/server/resource.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
2626
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
2727
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
28+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
2829
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
2930
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
3031
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -64,6 +65,7 @@ type Model struct {
6465
ServerId types.String `tfsdk:"server_id"`
6566
MachineType types.String `tfsdk:"machine_type"`
6667
Name types.String `tfsdk:"name"`
68+
Agent types.Object `tfsdk:"agent"`
6769
AvailabilityZone types.String `tfsdk:"availability_zone"`
6870
BootVolume types.Object `tfsdk:"boot_volume"`
6971
ImageId types.String `tfsdk:"image_id"`
@@ -78,6 +80,12 @@ type Model struct {
7880
DesiredStatus types.String `tfsdk:"desired_status"`
7981
}
8082

83+
// Struct corresponding to Model.Agent
84+
type agentModel struct {
85+
Provisioned types.Bool `tfsdk:"provisioned"`
86+
ProvisioningPolicy types.String `tfsdk:"provisioning_policy"`
87+
}
88+
8189
// Struct corresponding to Model.BootVolume
8290
type bootVolumeModel struct {
8391
Id types.String `tfsdk:"id"`
@@ -98,6 +106,12 @@ var bootVolumeTypes = map[string]attr.Type{
98106
"id": basetypes.StringType{},
99107
}
100108

109+
// Types corresponding to agentModel
110+
var agentTypes = map[string]attr.Type{
111+
"provisioned": basetypes.BoolType{},
112+
"provisioning_policy": basetypes.StringType{},
113+
}
114+
101115
// NewServerResource is a helper function to simplify the provider implementation.
102116
func NewServerResource() resource.Resource {
103117
return &serverResource{}
@@ -276,6 +290,35 @@ func (r *serverResource) Schema(_ context.Context, _ resource.SchemaRequest, res
276290
Optional: true,
277291
Computed: true,
278292
},
293+
"agent": schema.SingleNestedAttribute{
294+
Description: "The STACKIT Server Agent configured for the server",
295+
Optional: true,
296+
Computed: true,
297+
PlanModifiers: []planmodifier.Object{
298+
objectplanmodifier.RequiresReplace(),
299+
},
300+
Attributes: map[string]schema.Attribute{
301+
"provisioned": schema.BoolAttribute{
302+
Description: "Whether a STACKIT Server Agent is provisioned at the server",
303+
Computed: true,
304+
PlanModifiers: []planmodifier.Bool{
305+
boolplanmodifier.UseStateForUnknown(),
306+
},
307+
},
308+
"provisioning_policy": schema.StringAttribute{
309+
Description: "Agent provisioning policy: \"ALWAYS\", \"NEVER\", or \"INHERIT\". \"INHERIT\" follows the image default value.",
310+
Optional: true,
311+
Computed: true,
312+
Default: stringdefault.StaticString("INHERIT"),
313+
Validators: []validator.String{
314+
stringvalidator.OneOf("ALWAYS", "NEVER", "INHERIT"),
315+
},
316+
PlanModifiers: []planmodifier.String{
317+
stringplanmodifier.RequiresReplace(), // trigger recreation on change
318+
},
319+
},
320+
},
321+
},
279322
"boot_volume": schema.SingleNestedAttribute{
280323
Description: "The boot volume for the server",
281324
Optional: true,
@@ -982,6 +1025,33 @@ func mapFields(ctx context.Context, serverResp *iaas.Server, model *Model, regio
9821025
model.NetworkInterfaces = types.ListNull(types.StringType)
9831026
}
9841027

1028+
// agent{...} block, determine the intent policy from Terraform state
1029+
currentPolicy := types.StringValue("INHERIT")
1030+
if !(model.Agent.IsNull() || model.Agent.IsUnknown()) {
1031+
var currentAgent agentModel
1032+
diags := model.Agent.As(ctx, &currentAgent, basetypes.ObjectAsOptions{})
1033+
if diags.HasError() {
1034+
return fmt.Errorf("failed to map agent: %w", core.DiagsToError(diags))
1035+
}
1036+
if !currentAgent.ProvisioningPolicy.IsNull() {
1037+
currentPolicy = currentAgent.ProvisioningPolicy
1038+
}
1039+
}
1040+
// agent{...} block, determine the 'provisioned' field from API response
1041+
agentProvisioned := types.BoolNull()
1042+
if serverResp.Agent != nil && serverResp.Agent.Provisioned != nil {
1043+
agentProvisioned = types.BoolPointerValue(serverResp.Agent.Provisioned)
1044+
}
1045+
// agent{...} block, finalizing
1046+
agent, diags := types.ObjectValue(agentTypes, map[string]attr.Value{
1047+
"provisioned": agentProvisioned,
1048+
"provisioning_policy": currentPolicy,
1049+
})
1050+
if diags.HasError() {
1051+
return fmt.Errorf("failed to map agent: %w", core.DiagsToError(diags))
1052+
}
1053+
model.Agent = agent
1054+
9851055
if serverResp.BootVolume != nil {
9861056
// convert boot volume model
9871057
var bootVolumeModel = &bootVolumeModel{}
@@ -1050,6 +1120,14 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateServerPaylo
10501120
}
10511121
}
10521122

1123+
var agent = &agentModel{}
1124+
if !(model.Agent.IsNull() || model.Agent.IsUnknown()) {
1125+
diags := model.Agent.As(ctx, agent, basetypes.ObjectAsOptions{})
1126+
if diags.HasError() {
1127+
return nil, fmt.Errorf("convert agent object to struct: %w", core.DiagsToError(diags))
1128+
}
1129+
}
1130+
10531131
labels, err := conversion.ToStringInterfaceMap(ctx, model.Labels)
10541132
if err != nil {
10551133
return nil, fmt.Errorf("converting to Go map: %w", err)
@@ -1071,6 +1149,16 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateServerPaylo
10711149
}
10721150
}
10731151

1152+
var agentPayload *iaas.ServerAgent
1153+
switch agent.ProvisioningPolicy.ValueString() {
1154+
case "ALWAYS":
1155+
agentPayload = &iaas.ServerAgent{Provisioned: conversion.BoolValueToPointer(types.BoolValue(true))}
1156+
case "NEVER":
1157+
agentPayload = &iaas.ServerAgent{Provisioned: conversion.BoolValueToPointer(types.BoolValue(false))}
1158+
case "INHERIT":
1159+
agentPayload = nil // "agent" key is omitted from JSON thanks to omitempty
1160+
}
1161+
10741162
var userData *[]byte
10751163
if !model.UserData.IsNull() && !model.UserData.IsUnknown() {
10761164
src := []byte(model.UserData.ValueString())
@@ -1100,6 +1188,7 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateServerPaylo
11001188
return &iaas.CreateServerPayload{
11011189
AffinityGroup: conversion.StringValueToPointer(model.AffinityGroup),
11021190
AvailabilityZone: conversion.StringValueToPointer(model.AvailabilityZone),
1191+
Agent: agentPayload,
11031192
BootVolume: bootVolumePayload,
11041193
ImageId: conversion.StringValueToPointer(model.ImageId),
11051194
KeypairName: conversion.StringValueToPointer(model.KeypairName),

0 commit comments

Comments
 (0)