Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/data-sources/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ data "stackit_server" "example" {
### Read-Only

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

<a id="nestedatt--agent"></a>
### Nested Schema for `agent`

Read-Only:

- `provisioned` (Boolean) Whether a STACKIT Server Agent is provisioned at the server


<a id="nestedatt--boot_volume"></a>
### Nested Schema for `boot_volume`

Expand Down
13 changes: 13 additions & 0 deletions docs/resources/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ import {
### Optional

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

<a id="nestedatt--agent"></a>
### Nested Schema for `agent`

Optional:

- `provisioning_policy` (String) Agent provisioning policy: "ALWAYS", "NEVER", or "INHERIT". "INHERIT" follows the image default value.

Read-Only:

- `provisioned` (Boolean) Whether a STACKIT Server Agent should be provisioned at the server


<a id="nestedatt--boot_volume"></a>
### Nested Schema for `boot_volume`

Expand Down
22 changes: 21 additions & 1 deletion stackit/internal/services/iaas/iaas_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ var testConfigServerVarsMax = config.Variables{
"service_account_mail": config.StringVariable(testutil.TestProjectServiceAccountEmail),
"public_key": config.StringVariable(keypairPublicKey),
"desired_status": config.StringVariable("active"),
"agent_policy": config.StringVariable("ALWAYS"),
}

var testConfigServerVarsMaxUpdated = func() config.Variables {
Expand All @@ -158,6 +159,7 @@ var testConfigServerVarsMaxUpdated = func() config.Variables {
updatedConfig["machine_type"] = config.StringVariable("t1.2")
updatedConfig["label"] = config.StringVariable("updated")
updatedConfig["desired_status"] = config.StringVariable("inactive")
updatedConfig["agent_policy"] = config.StringVariable("NEVER")
return updatedConfig
}()

Expand Down Expand Up @@ -2223,6 +2225,8 @@ func TestAccServerMin(t *testing.T) {
resource.TestCheckResourceAttrSet("stackit_server.server", "created_at"),
resource.TestCheckResourceAttrSet("stackit_server.server", "launched_at"),
resource.TestCheckResourceAttrSet("stackit_server.server", "updated_at"),
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioning_policy", "INHERIT"),
resource.TestCheckNoResourceAttr("stackit_server.server", "agent.provisioned"),
),
},
// Data source
Expand Down Expand Up @@ -2275,6 +2279,7 @@ func TestAccServerMin(t *testing.T) {
resource.TestCheckResourceAttrSet("data.stackit_server.server", "created_at"),
resource.TestCheckResourceAttrSet("data.stackit_server.server", "launched_at"),
resource.TestCheckResourceAttrSet("data.stackit_server.server", "updated_at"),
resource.TestCheckNoResourceAttr("data.stackit_server.server", "agent.provisioned"),
),
},
// Import
Expand Down Expand Up @@ -2328,6 +2333,8 @@ func TestAccServerMin(t *testing.T) {
resource.TestCheckResourceAttrSet("stackit_server.server", "created_at"),
resource.TestCheckResourceAttrSet("stackit_server.server", "launched_at"),
resource.TestCheckResourceAttrSet("stackit_server.server", "updated_at"),
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioning_policy", "INHERIT"),
resource.TestCheckNoResourceAttr("stackit_server.server", "agent.provisioned"),
),
},
// Deletion is done by the framework implicitly
Expand All @@ -2352,6 +2359,10 @@ func TestAccServerMax(t *testing.T) {
resource.TestCheckResourceAttr("stackit_affinity_group.affinity_group", "policy", testutil.ConvertConfigVariable(testConfigServerVarsMax["policy"])),
resource.TestCheckResourceAttrSet("stackit_affinity_group.affinity_group", "affinity_group_id"),

// Agent
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioning_policy", testutil.ConvertConfigVariable(testConfigServerVarsMax["agent_policy"])),
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioned", "true"),

// Volume base
resource.TestCheckResourceAttr("stackit_volume.base_volume", "project_id", testutil.ConvertConfigVariable(testConfigServerVarsMax["project_id"])),
resource.TestCheckResourceAttr("stackit_volume.base_volume", "availability_zone", testutil.ConvertConfigVariable(testConfigServerVarsMax["availability_zone"])),
Expand Down Expand Up @@ -2500,6 +2511,7 @@ func TestAccServerMax(t *testing.T) {
"stackit_key_pair.key_pair", "name",
"data.stackit_server.server", "keypair_name",
),
resource.TestCheckResourceAttr("data.stackit_server.server", "agent.provisioned", "true"),
// All network interface which was are attached appear here
resource.TestCheckResourceAttr("data.stackit_server.server", "network_interfaces.#", "2"),
resource.TestCheckTypeSetElemAttrPair(
Expand Down Expand Up @@ -2725,7 +2737,7 @@ func TestAccServerMax(t *testing.T) {
},
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"boot_volume", "desired_status", "network_interfaces"}, // Field is not mapped as it is only relevant on creation
ImportStateVerifyIgnore: []string{"boot_volume", "desired_status", "network_interfaces", "agent"}, // Field is not mapped as it is only relevant on creation
},
// Update
{
Expand All @@ -2738,6 +2750,10 @@ func TestAccServerMax(t *testing.T) {
resource.TestCheckResourceAttr("stackit_affinity_group.affinity_group", "policy", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdated["policy"])),
resource.TestCheckResourceAttrSet("stackit_affinity_group.affinity_group", "affinity_group_id"),

// Agent
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioning_policy", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdated["agent_policy"])),
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioned", "false"),

// Volume base
resource.TestCheckResourceAttr("stackit_volume.base_volume", "project_id", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdated["project_id"])),
resource.TestCheckResourceAttr("stackit_volume.base_volume", "availability_zone", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdated["availability_zone"])),
Expand Down Expand Up @@ -2843,6 +2859,10 @@ func TestAccServerMax(t *testing.T) {
resource.TestCheckResourceAttr("stackit_affinity_group.affinity_group", "policy", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdatedDesiredStatus["policy"])),
resource.TestCheckResourceAttrSet("stackit_affinity_group.affinity_group", "affinity_group_id"),

// Agent
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioning_policy", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdatedDesiredStatus["agent_policy"])),
resource.TestCheckResourceAttr("stackit_server.server", "agent.provisioned", "false"),

// Volume base
resource.TestCheckResourceAttr("stackit_volume.base_volume", "project_id", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdatedDesiredStatus["project_id"])),
resource.TestCheckResourceAttr("stackit_volume.base_volume", "availability_zone", testutil.ConvertConfigVariable(testConfigServerVarsMaxUpdatedDesiredStatus["availability_zone"])),
Expand Down
27 changes: 27 additions & 0 deletions stackit/internal/services/iaas/server/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type DataSourceModel struct {
ServerId types.String `tfsdk:"server_id"`
MachineType types.String `tfsdk:"machine_type"`
Name types.String `tfsdk:"name"`
Agent types.Object `tfsdk:"agent"`
AvailabilityZone types.String `tfsdk:"availability_zone"`
BootVolume types.Object `tfsdk:"boot_volume"`
ImageId types.String `tfsdk:"image_id"`
Expand All @@ -53,6 +54,10 @@ var bootVolumeDataTypes = map[string]attr.Type{
"delete_on_termination": basetypes.BoolType{},
}

var agentDataTypes = map[string]attr.Type{
"provisioned": basetypes.BoolType{},
}

// NewServerDataSource is a helper function to simplify the provider implementation.
func NewServerDataSource() datasource.DataSource {
return &serverDataSource{}
Expand Down Expand Up @@ -124,6 +129,16 @@ func (d *serverDataSource) Schema(_ context.Context, _ datasource.SchemaRequest,
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/)",
Computed: true,
},
"agent": schema.SingleNestedAttribute{
Comment thread
aeter marked this conversation as resolved.
Description: "STACKIT Server Agent as setup on the server",
Computed: true,
Attributes: map[string]schema.Attribute{
"provisioned": schema.BoolAttribute{
Description: "Whether a STACKIT Server Agent is provisioned at the server",
Computed: true,
Comment thread
aeter marked this conversation as resolved.
},
},
},
"availability_zone": schema.StringAttribute{
Description: "The availability zone of the server.",
Computed: true,
Expand Down Expand Up @@ -305,6 +320,18 @@ func mapDataSourceFields(ctx context.Context, serverResp *iaas.Server, model *Da
model.BootVolume = types.ObjectNull(bootVolumeDataTypes)
}

agentProvisioned := types.BoolNull()
if serverResp.Agent != nil && serverResp.Agent.Provisioned != nil {
agentProvisioned = types.BoolPointerValue(serverResp.Agent.Provisioned)
}
agent, diags := types.ObjectValue(agentDataTypes, map[string]attr.Value{
"provisioned": agentProvisioned,
})
if diags.HasError() {
return fmt.Errorf("failed to map agent: %w", core.DiagsToError(diags))
}
model.Agent = agent

if serverResp.UserData != nil && len(*serverResp.UserData) > 0 {
model.UserData = types.StringValue(string(*serverResp.UserData))
}
Expand Down
16 changes: 14 additions & 2 deletions stackit/internal/services/iaas/server/datasource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import (
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)

var expectedNullAgentData, _ = types.ObjectValue(agentDataTypes, map[string]attr.Value{
"provisioned": types.BoolNull(),
})

func TestMapDataSourceFields(t *testing.T) {
type args struct {
state DataSourceModel
Expand Down Expand Up @@ -40,6 +44,7 @@ func TestMapDataSourceFields(t *testing.T) {
ServerId: types.StringValue("sid"),
Name: types.StringNull(),
AvailabilityZone: types.StringNull(),
Agent: expectedNullAgentData,
Labels: types.MapNull(types.StringType),
ImageId: types.StringNull(),
NetworkInterfaces: types.ListNull(types.StringType),
Expand Down Expand Up @@ -77,7 +82,10 @@ func TestMapDataSourceFields(t *testing.T) {
NicId: new("nic2"),
},
},
KeypairName: new("keypair_name"),
KeypairName: new("keypair_name"),
Agent: &iaas.ServerAgent{
Provisioned: new(true),
},
AffinityGroup: new("group_id"),
CreatedAt: new(testTimestamp()),
UpdatedAt: new(testTimestamp()),
Expand All @@ -100,7 +108,10 @@ func TestMapDataSourceFields(t *testing.T) {
types.StringValue("nic1"),
types.StringValue("nic2"),
}),
KeypairName: types.StringValue("keypair_name"),
KeypairName: types.StringValue("keypair_name"),
Agent: types.ObjectValueMust(agentDataTypes, map[string]attr.Value{
"provisioned": types.BoolValue(true),
}),
AffinityGroup: types.StringValue("group_id"),
CreatedAt: types.StringValue(testTimestampValue),
UpdatedAt: types.StringValue(testTimestampValue),
Expand Down Expand Up @@ -131,6 +142,7 @@ func TestMapDataSourceFields(t *testing.T) {
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
ImageId: types.StringNull(),
NetworkInterfaces: types.ListNull(types.StringType),
Agent: expectedNullAgentData,
KeypairName: types.StringNull(),
AffinityGroup: types.StringNull(),
UserData: types.StringNull(),
Expand Down
87 changes: 87 additions & 0 deletions stackit/internal/services/iaas/server/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
Expand Down Expand Up @@ -64,6 +65,7 @@ type Model struct {
ServerId types.String `tfsdk:"server_id"`
MachineType types.String `tfsdk:"machine_type"`
Name types.String `tfsdk:"name"`
Agent types.Object `tfsdk:"agent"`
AvailabilityZone types.String `tfsdk:"availability_zone"`
BootVolume types.Object `tfsdk:"boot_volume"`
ImageId types.String `tfsdk:"image_id"`
Expand All @@ -78,6 +80,12 @@ type Model struct {
DesiredStatus types.String `tfsdk:"desired_status"`
}

// Struct corresponding to Model.Agent
type agentModel struct {
Provisioned types.Bool `tfsdk:"provisioned"`
ProvisioningPolicy types.String `tfsdk:"provisioning_policy"`
}

// Struct corresponding to Model.BootVolume
type bootVolumeModel struct {
Id types.String `tfsdk:"id"`
Expand All @@ -98,6 +106,12 @@ var bootVolumeTypes = map[string]attr.Type{
"id": basetypes.StringType{},
}

// Types corresponding to agentModel
var agentTypes = map[string]attr.Type{
"provisioned": basetypes.BoolType{},
"provisioning_policy": basetypes.StringType{},
}

// NewServerResource is a helper function to simplify the provider implementation.
func NewServerResource() resource.Resource {
return &serverResource{}
Expand Down Expand Up @@ -276,6 +290,35 @@ func (r *serverResource) Schema(_ context.Context, _ resource.SchemaRequest, res
Optional: true,
Computed: true,
},
"agent": schema.SingleNestedAttribute{
Description: "The STACKIT Server Agent configured for the server",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Object{
objectplanmodifier.RequiresReplace(),
},
Attributes: map[string]schema.Attribute{
"provisioned": schema.BoolAttribute{
Description: "Whether a STACKIT Server Agent should be provisioned at the server",
Computed: true,
PlanModifiers: []planmodifier.Bool{
boolplanmodifier.UseStateForUnknown(),
},
},
"provisioning_policy": schema.StringAttribute{
Description: "Agent provisioning policy: \"ALWAYS\", \"NEVER\", or \"INHERIT\". \"INHERIT\" follows the image default value.",
Optional: true,
Computed: true,
Default: stringdefault.StaticString("INHERIT"),
Validators: []validator.String{
stringvalidator.OneOf("ALWAYS", "NEVER", "INHERIT"),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(), // trigger recreation on change
},
},
},
},
"boot_volume": schema.SingleNestedAttribute{
Description: "The boot volume for the server",
Optional: true,
Expand Down Expand Up @@ -982,6 +1025,31 @@ func mapFields(ctx context.Context, serverResp *iaas.Server, model *Model, regio
model.NetworkInterfaces = types.ListNull(types.StringType)
}

// agent{...} block, determine the intent policy from Terraform state
currentPolicy := types.StringValue("INHERIT")
if !(model.Agent.IsNull() || model.Agent.IsUnknown()) {
var currentAgent agentModel
if diags := model.Agent.As(ctx, &currentAgent, basetypes.ObjectAsOptions{}); !diags.HasError() {
if !currentAgent.ProvisioningPolicy.IsNull() {
currentPolicy = currentAgent.ProvisioningPolicy
}
}
}
// agent{...} block, determine the 'provisioned' field from API response
agentProvisioned := types.BoolNull()
if serverResp.Agent != nil && serverResp.Agent.Provisioned != nil {
agentProvisioned = types.BoolPointerValue(serverResp.Agent.Provisioned)
}
// agent{...} block, finalizing
agent, diags := types.ObjectValue(agentTypes, map[string]attr.Value{
"provisioned": agentProvisioned,
"provisioning_policy": currentPolicy,
})
if diags.HasError() {
return fmt.Errorf("failed to map agent: %w", core.DiagsToError(diags))
}
model.Agent = agent

if serverResp.BootVolume != nil {
// convert boot volume model
var bootVolumeModel = &bootVolumeModel{}
Expand Down Expand Up @@ -1050,6 +1118,14 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateServerPaylo
}
}

var agent = &agentModel{}
if !(model.Agent.IsNull() || model.Agent.IsUnknown()) {
diags := model.Agent.As(ctx, agent, basetypes.ObjectAsOptions{})
if diags.HasError() {
return nil, fmt.Errorf("convert agent object to struct: %w", core.DiagsToError(diags))
}
}

labels, err := conversion.ToStringInterfaceMap(ctx, model.Labels)
if err != nil {
return nil, fmt.Errorf("converting to Go map: %w", err)
Expand All @@ -1071,6 +1147,16 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateServerPaylo
}
}

var agentPayload *iaas.ServerAgent
switch agent.ProvisioningPolicy.ValueString() {
case "ALWAYS":
agentPayload = &iaas.ServerAgent{Provisioned: conversion.BoolValueToPointer(types.BoolValue(true))}
case "NEVER":
agentPayload = &iaas.ServerAgent{Provisioned: conversion.BoolValueToPointer(types.BoolValue(false))}
case "INHERIT":
agentPayload = nil // "agent" key is omitted from JSON thanks to omitempty
}

var userData *[]byte
if !model.UserData.IsNull() && !model.UserData.IsUnknown() {
src := []byte(model.UserData.ValueString())
Expand Down Expand Up @@ -1100,6 +1186,7 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateServerPaylo
return &iaas.CreateServerPayload{
AffinityGroup: conversion.StringValueToPointer(model.AffinityGroup),
AvailabilityZone: conversion.StringValueToPointer(model.AvailabilityZone),
Agent: agentPayload,
BootVolume: bootVolumePayload,
ImageId: conversion.StringValueToPointer(model.ImageId),
KeypairName: conversion.StringValueToPointer(model.KeypairName),
Expand Down
Loading