feat(iaas): support for v2 API (#1070)

relates to STACKITTPR-313
This commit is contained in:
Ruben Hönle 2025-12-17 15:40:46 +01:00 committed by GitHub
parent 460c18c202
commit 53a3697850
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
124 changed files with 8342 additions and 6042 deletions

View file

@ -30,6 +30,7 @@ var (
type DataSourceModel struct {
Id types.String `tfsdk:"id"` // needed by TF
ProjectId types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
ServerId types.String `tfsdk:"server_id"`
MachineType types.String `tfsdk:"machine_type"`
Name types.String `tfsdk:"name"`
@ -58,7 +59,8 @@ func NewServerDataSource() datasource.DataSource {
// serverDataSource is the data source implementation.
type serverDataSource struct {
client *iaas.APIClient
client *iaas.APIClient
providerData core.ProviderData
}
// Metadata returns the data source type name.
@ -67,12 +69,13 @@ func (d *serverDataSource) Metadata(_ context.Context, req datasource.MetadataRe
}
func (d *serverDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
var ok bool
d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
if !ok {
return
}
apiClient := iaasUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics)
apiClient := iaasUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
@ -81,14 +84,14 @@ func (d *serverDataSource) Configure(ctx context.Context, req datasource.Configu
}
// Schema defines the schema for the datasource.
func (r *serverDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
func (d *serverDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
description := "Server datasource schema. Must have a `region` specified in the provider configuration."
resp.Schema = schema.Schema{
MarkdownDescription: description,
Description: description,
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`server_id`\".",
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`server_id`\".",
Computed: true,
},
"project_id": schema.StringAttribute{
@ -99,6 +102,11 @@ func (r *serverDataSource) Schema(_ context.Context, _ datasource.SchemaRequest,
validate.NoSeparator(),
},
},
"region": schema.StringAttribute{
Description: "The resource region. If not defined, the provider region is used.",
// the region cannot be found, so it has to be passed
Optional: true,
},
"server_id": schema.StringAttribute{
Description: "The server ID.",
Required: true,
@ -175,8 +183,8 @@ func (r *serverDataSource) Schema(_ context.Context, _ datasource.SchemaRequest,
}
}
// // Read refreshes the Terraform state with the latest data.
func (r *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
// Read refreshes the Terraform state with the latest data.
func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model DataSourceModel
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
@ -184,14 +192,16 @@ func (r *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
return
}
projectId := model.ProjectId.ValueString()
region := d.providerData.GetRegionWithOverride(model.Region)
serverId := model.ServerId.ValueString()
ctx = core.InitProviderContext(ctx)
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "server_id", serverId)
serverReq := r.client.GetServer(ctx, projectId, serverId)
serverReq := d.client.GetServer(ctx, projectId, region, serverId)
serverReq = serverReq.Details(true)
serverResp, err := serverReq.Execute()
if err != nil {
@ -212,7 +222,7 @@ func (r *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
ctx = core.LogResponse(ctx)
// Map response body to schema
err = mapDataSourceFields(ctx, serverResp, &model)
err = mapDataSourceFields(ctx, serverResp, &model, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading server", fmt.Sprintf("Processing API payload: %v", err))
return
@ -226,7 +236,7 @@ func (r *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
tflog.Info(ctx, "server read")
}
func mapDataSourceFields(ctx context.Context, serverResp *iaas.Server, model *DataSourceModel) error {
func mapDataSourceFields(ctx context.Context, serverResp *iaas.Server, model *DataSourceModel, region string) error {
if serverResp == nil {
return fmt.Errorf("response input is nil")
}
@ -243,7 +253,8 @@ func mapDataSourceFields(ctx context.Context, serverResp *iaas.Server, model *Da
return fmt.Errorf("server id not present")
}
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), serverId)
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, serverId)
model.Region = types.StringValue(region)
labels, err := iaasUtils.MapLabels(ctx, serverResp.Labels, model.Labels)
if err != nil {

View file

@ -12,24 +12,31 @@ import (
)
func TestMapDataSourceFields(t *testing.T) {
type args struct {
state DataSourceModel
input *iaas.Server
region string
}
tests := []struct {
description string
state DataSourceModel
input *iaas.Server
args args
expected DataSourceModel
isValid bool
}{
{
"default_values",
DataSourceModel{
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
description: "default_values",
args: args{
state: DataSourceModel{
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
},
input: &iaas.Server{
Id: utils.Ptr("sid"),
},
region: "eu01",
},
&iaas.Server{
Id: utils.Ptr("sid"),
},
DataSourceModel{
Id: types.StringValue("pid,sid"),
expected: DataSourceModel{
Id: types.StringValue("pid,eu01,sid"),
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
Name: types.StringNull(),
@ -43,40 +50,45 @@ func TestMapDataSourceFields(t *testing.T) {
CreatedAt: types.StringNull(),
UpdatedAt: types.StringNull(),
LaunchedAt: types.StringNull(),
Region: types.StringValue("eu01"),
},
true,
isValid: true,
},
{
"simple_values",
DataSourceModel{
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
},
&iaas.Server{
Id: utils.Ptr("sid"),
Name: utils.Ptr("name"),
AvailabilityZone: utils.Ptr("zone"),
Labels: &map[string]interface{}{
"key": "value",
description: "simple_values",
args: args{
state: DataSourceModel{
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
Region: types.StringValue("eu01"),
},
ImageId: utils.Ptr("image_id"),
Nics: &[]iaas.ServerNetwork{
{
NicId: utils.Ptr("nic1"),
input: &iaas.Server{
Id: utils.Ptr("sid"),
Name: utils.Ptr("name"),
AvailabilityZone: utils.Ptr("zone"),
Labels: &map[string]interface{}{
"key": "value",
},
{
NicId: utils.Ptr("nic2"),
ImageId: utils.Ptr("image_id"),
Nics: &[]iaas.ServerNetwork{
{
NicId: utils.Ptr("nic1"),
},
{
NicId: utils.Ptr("nic2"),
},
},
KeypairName: utils.Ptr("keypair_name"),
AffinityGroup: utils.Ptr("group_id"),
CreatedAt: utils.Ptr(testTimestamp()),
UpdatedAt: utils.Ptr(testTimestamp()),
LaunchedAt: utils.Ptr(testTimestamp()),
Status: utils.Ptr("active"),
},
KeypairName: utils.Ptr("keypair_name"),
AffinityGroup: utils.Ptr("group_id"),
CreatedAt: utils.Ptr(testTimestamp()),
UpdatedAt: utils.Ptr(testTimestamp()),
LaunchedAt: utils.Ptr(testTimestamp()),
Status: utils.Ptr("active"),
region: "eu02",
},
DataSourceModel{
Id: types.StringValue("pid,sid"),
expected: DataSourceModel{
Id: types.StringValue("pid,eu02,sid"),
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
Name: types.StringValue("name"),
@ -94,21 +106,25 @@ func TestMapDataSourceFields(t *testing.T) {
CreatedAt: types.StringValue(testTimestampValue),
UpdatedAt: types.StringValue(testTimestampValue),
LaunchedAt: types.StringValue(testTimestampValue),
Region: types.StringValue("eu02"),
},
true,
isValid: true,
},
{
"empty_labels",
DataSourceModel{
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
description: "empty_labels",
args: args{
state: DataSourceModel{
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
},
input: &iaas.Server{
Id: utils.Ptr("sid"),
},
region: "eu01",
},
&iaas.Server{
Id: utils.Ptr("sid"),
},
DataSourceModel{
Id: types.StringValue("pid,sid"),
expected: DataSourceModel{
Id: types.StringValue("pid,eu01,sid"),
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
Name: types.StringNull(),
@ -122,29 +138,26 @@ func TestMapDataSourceFields(t *testing.T) {
CreatedAt: types.StringNull(),
UpdatedAt: types.StringNull(),
LaunchedAt: types.StringNull(),
Region: types.StringValue("eu01"),
},
true,
isValid: true,
},
{
"response_nil_fail",
DataSourceModel{},
nil,
DataSourceModel{},
false,
description: "response_nil_fail",
},
{
"no_resource_id",
DataSourceModel{
ProjectId: types.StringValue("pid"),
description: "no_resource_id",
args: args{
state: DataSourceModel{
ProjectId: types.StringValue("pid"),
},
input: &iaas.Server{},
},
&iaas.Server{},
DataSourceModel{},
false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := mapDataSourceFields(context.Background(), tt.input, &tt.state)
err := mapDataSourceFields(context.Background(), tt.args.input, &tt.args.state, tt.args.region)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
@ -152,7 +165,7 @@ func TestMapDataSourceFields(t *testing.T) {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(tt.state, tt.expected)
diff := cmp.Diff(tt.args.state, tt.expected)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}

View file

@ -42,6 +42,7 @@ var (
_ resource.Resource = &serverResource{}
_ resource.ResourceWithConfigure = &serverResource{}
_ resource.ResourceWithImportState = &serverResource{}
_ resource.ResourceWithModifyPlan = &serverResource{}
supportedSourceTypes = []string{"volume", "image"}
desiredStatusOptions = []string{modelStateActive, modelStateInactive, modelStateDeallocated}
@ -56,6 +57,7 @@ const (
type Model struct {
Id types.String `tfsdk:"id"` // needed by TF
ProjectId types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
ServerId types.String `tfsdk:"server_id"`
MachineType types.String `tfsdk:"machine_type"`
Name types.String `tfsdk:"name"`
@ -100,7 +102,8 @@ func NewServerResource() resource.Resource {
// serverResource is the resource implementation.
type serverResource struct {
client *iaas.APIClient
client *iaas.APIClient
providerData core.ProviderData
}
// Metadata returns the resource type name.
@ -108,7 +111,37 @@ func (r *serverResource) Metadata(_ context.Context, req resource.MetadataReques
resp.TypeName = req.ProviderTypeName + "_server"
}
func (r serverResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
// ModifyPlan implements resource.ResourceWithModifyPlan.
// Use the modifier to set the effective region in the current plan.
func (r *serverResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform
var configModel Model
// skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() {
return
}
resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...)
if resp.Diagnostics.HasError() {
return
}
var planModel Model
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
if resp.Diagnostics.HasError() {
return
}
utils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp)
if resp.Diagnostics.HasError() {
return
}
resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...)
if resp.Diagnostics.HasError() {
return
}
}
func (r *serverResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
var model Model
resp.Diagnostics.Append(req.Config.Get(ctx, &model)...)
if resp.Diagnostics.HasError() {
@ -129,6 +162,10 @@ func (r serverResource) ValidateConfig(ctx context.Context, req resource.Validat
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring server", "You can only provide `delete_on_termination` for `source_type` `image`.")
}
}
if model.NetworkInterfaces.IsNull() || model.NetworkInterfaces.IsUnknown() || len(model.NetworkInterfaces.Elements()) < 1 {
core.LogAndAddWarning(ctx, &resp.Diagnostics, "No network interfaces configured", "You have no network interfaces configured for this server. This will be a problem when you want to (re-)create this server. Please note that modifying the network interfaces for an existing server will result in a replacement of the resource. We will provide a clear migration path soon.")
}
}
// ConfigValidators validates the resource configuration
@ -147,12 +184,13 @@ func (r *serverResource) ConfigValidators(_ context.Context) []resource.ConfigVa
// Configure adds the provider configured client to the resource.
func (r *serverResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
var ok bool
r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
if !ok {
return
}
apiClient := iaasUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics)
apiClient := iaasUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
@ -167,7 +205,7 @@ func (r *serverResource) Schema(_ context.Context, _ resource.SchemaRequest, res
Description: "Server resource schema. Must have a `region` specified in the provider configuration.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`server_id`\".",
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`server_id`\".",
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
@ -184,6 +222,15 @@ func (r *serverResource) Schema(_ context.Context, _ resource.SchemaRequest, res
validate.NoSeparator(),
},
},
"region": schema.StringAttribute{
Description: "The resource region. If not defined, the provider region is used.",
Optional: true,
// must be computed to allow for storing the override value from the provider
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"server_id": schema.StringAttribute{
Description: "The server ID.",
Computed: true,
@ -297,7 +344,7 @@ func (r *serverResource) Schema(_ context.Context, _ resource.SchemaRequest, res
},
},
"network_interfaces": schema.ListAttribute{
Description: "The IDs of network interfaces which should be attached to the server. Updating it will recreate the server.",
Description: "The IDs of network interfaces which should be attached to the server. Updating it will recreate the server. **Required when (re-)creating servers. Still marked as optional in the schema to not introduce breaking changes. There will be a migration path for this field soon.**",
Optional: true,
ElementType: types.StringType,
Validators: []validator.List{
@ -428,11 +475,12 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
}
projectId := model.ProjectId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = core.InitProviderContext(ctx)
ctx = tflog.SetField(ctx, "project_id", projectId)
// Generate API request body from model
payload, err := toCreatePayload(ctx, &model)
if err != nil {
@ -442,7 +490,7 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
// Create new server
server, err := r.client.CreateServer(ctx, projectId).CreateServerPayload(*payload).Execute()
server, err := r.client.CreateServer(ctx, projectId, region).CreateServerPayload(*payload).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating server", fmt.Sprintf("Calling API: %v", err))
return
@ -451,7 +499,7 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
ctx = core.LogResponse(ctx)
serverId := *server.Id
_, err = wait.CreateServerWaitHandler(ctx, r.client, projectId, serverId).WaitWithContext(ctx)
_, err = wait.CreateServerWaitHandler(ctx, r.client, projectId, region, serverId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating server", fmt.Sprintf("server creation waiting: %v", err))
return
@ -459,7 +507,7 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
ctx = tflog.SetField(ctx, "server_id", serverId)
// Get Server with details
serverReq := r.client.GetServer(ctx, projectId, serverId)
serverReq := r.client.GetServer(ctx, projectId, region, serverId)
serverReq = serverReq.Details(true)
server, err = serverReq.Execute()
if err != nil {
@ -467,14 +515,14 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
}
// Map response body to schema
err = mapFields(ctx, server, &model)
err = mapFields(ctx, server, &model, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating server", fmt.Sprintf("Processing API payload: %v", err))
return
}
if err := updateServerStatus(ctx, r.client, server.Status, &model); err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creting server", fmt.Sprintf("update server state: %v", err))
if err := updateServerStatus(ctx, r.client, server.Status, &model, region); err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating server", fmt.Sprintf("update server state: %v", err))
return
}
@ -491,41 +539,41 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
// client operations in [updateServerStatus]
type serverControlClient interface {
wait.APIClientInterface
StartServerExecute(ctx context.Context, projectId string, serverId string) error
StopServerExecute(ctx context.Context, projectId string, serverId string) error
DeallocateServerExecute(ctx context.Context, projectId string, serverId string) error
StartServerExecute(ctx context.Context, projectId string, region string, serverId string) error
StopServerExecute(ctx context.Context, projectId string, region string, serverId string) error
DeallocateServerExecute(ctx context.Context, projectId string, region string, serverId string) error
}
func startServer(ctx context.Context, client serverControlClient, projectId, serverId string) error {
func startServer(ctx context.Context, client serverControlClient, projectId, region, serverId string) error {
tflog.Debug(ctx, "starting server to enter active state")
if err := client.StartServerExecute(ctx, projectId, serverId); err != nil {
if err := client.StartServerExecute(ctx, projectId, region, serverId); err != nil {
return fmt.Errorf("cannot start server: %w", err)
}
_, err := wait.StartServerWaitHandler(ctx, client, projectId, serverId).WaitWithContext(ctx)
_, err := wait.StartServerWaitHandler(ctx, client, projectId, region, serverId).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("cannot check started server: %w", err)
}
return nil
}
func stopServer(ctx context.Context, client serverControlClient, projectId, serverId string) error {
func stopServer(ctx context.Context, client serverControlClient, projectId, region, serverId string) error {
tflog.Debug(ctx, "stopping server to enter inactive state")
if err := client.StopServerExecute(ctx, projectId, serverId); err != nil {
if err := client.StopServerExecute(ctx, projectId, region, serverId); err != nil {
return fmt.Errorf("cannot stop server: %w", err)
}
_, err := wait.StopServerWaitHandler(ctx, client, projectId, serverId).WaitWithContext(ctx)
_, err := wait.StopServerWaitHandler(ctx, client, projectId, region, serverId).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("cannot check stopped server: %w", err)
}
return nil
}
func deallocatServer(ctx context.Context, client serverControlClient, projectId, serverId string) error {
func deallocateServer(ctx context.Context, client serverControlClient, projectId, region, serverId string) error {
tflog.Debug(ctx, "deallocating server to enter shelved state")
if err := client.DeallocateServerExecute(ctx, projectId, serverId); err != nil {
if err := client.DeallocateServerExecute(ctx, projectId, region, serverId); err != nil {
return fmt.Errorf("cannot deallocate server: %w", err)
}
_, err := wait.DeallocateServerWaitHandler(ctx, client, projectId, serverId).WaitWithContext(ctx)
_, err := wait.DeallocateServerWaitHandler(ctx, client, projectId, region, serverId).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("cannot check deallocated server: %w", err)
}
@ -533,7 +581,7 @@ func deallocatServer(ctx context.Context, client serverControlClient, projectId,
}
// updateServerStatus applies the appropriate server state changes for the actual current and the intended state
func updateServerStatus(ctx context.Context, client serverControlClient, currentState *string, model *Model) error {
func updateServerStatus(ctx context.Context, client serverControlClient, currentState *string, model *Model, region string) error {
if currentState == nil {
tflog.Warn(ctx, "no current state available, not updating server state")
return nil
@ -542,52 +590,52 @@ func updateServerStatus(ctx context.Context, client serverControlClient, current
case wait.ServerActiveStatus:
switch strings.ToUpper(model.DesiredStatus.ValueString()) {
case wait.ServerInactiveStatus:
if err := stopServer(ctx, client, model.ProjectId.ValueString(), model.ServerId.ValueString()); err != nil {
if err := stopServer(ctx, client, model.ProjectId.ValueString(), region, model.ServerId.ValueString()); err != nil {
return err
}
case wait.ServerDeallocatedStatus:
if err := deallocatServer(ctx, client, model.ProjectId.ValueString(), model.ServerId.ValueString()); err != nil {
if err := deallocateServer(ctx, client, model.ProjectId.ValueString(), region, model.ServerId.ValueString()); err != nil {
return err
}
default:
tflog.Debug(ctx, fmt.Sprintf("nothing to do for status value %q", model.DesiredStatus.ValueString()))
if _, err := client.GetServerExecute(ctx, model.ProjectId.ValueString(), model.ServerId.ValueString()); err != nil {
if _, err := client.GetServerExecute(ctx, model.ProjectId.ValueString(), region, model.ServerId.ValueString()); err != nil {
return err
}
}
case wait.ServerInactiveStatus:
switch strings.ToUpper(model.DesiredStatus.ValueString()) {
case wait.ServerActiveStatus:
if err := startServer(ctx, client, model.ProjectId.ValueString(), model.ServerId.ValueString()); err != nil {
if err := startServer(ctx, client, model.ProjectId.ValueString(), region, model.ServerId.ValueString()); err != nil {
return err
}
case wait.ServerDeallocatedStatus:
if err := deallocatServer(ctx, client, model.ProjectId.ValueString(), model.ServerId.ValueString()); err != nil {
if err := deallocateServer(ctx, client, model.ProjectId.ValueString(), region, model.ServerId.ValueString()); err != nil {
return err
}
default:
tflog.Debug(ctx, fmt.Sprintf("nothing to do for status value %q", model.DesiredStatus.ValueString()))
if _, err := client.GetServerExecute(ctx, model.ProjectId.ValueString(), model.ServerId.ValueString()); err != nil {
if _, err := client.GetServerExecute(ctx, model.ProjectId.ValueString(), region, model.ServerId.ValueString()); err != nil {
return err
}
}
case wait.ServerDeallocatedStatus:
switch strings.ToUpper(model.DesiredStatus.ValueString()) {
case wait.ServerActiveStatus:
if err := startServer(ctx, client, model.ProjectId.ValueString(), model.ServerId.ValueString()); err != nil {
if err := startServer(ctx, client, model.ProjectId.ValueString(), region, model.ServerId.ValueString()); err != nil {
return err
}
case wait.ServerInactiveStatus:
if err := stopServer(ctx, client, model.ProjectId.ValueString(), model.ServerId.ValueString()); err != nil {
if err := stopServer(ctx, client, model.ProjectId.ValueString(), region, model.ServerId.ValueString()); err != nil {
return err
}
default:
tflog.Debug(ctx, fmt.Sprintf("nothing to do for status value %q", model.DesiredStatus.ValueString()))
if _, err := client.GetServerExecute(ctx, model.ProjectId.ValueString(), model.ServerId.ValueString()); err != nil {
if _, err := client.GetServerExecute(ctx, model.ProjectId.ValueString(), region, model.ServerId.ValueString()); err != nil {
return err
}
}
@ -598,7 +646,7 @@ func updateServerStatus(ctx context.Context, client serverControlClient, current
return nil
}
// // Read refreshes the Terraform state with the latest data.
// Read refreshes the Terraform state with the latest data.
func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.State.Get(ctx, &model)
@ -607,14 +655,16 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res
return
}
projectId := model.ProjectId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
serverId := model.ServerId.ValueString()
ctx = core.InitProviderContext(ctx)
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "server_id", serverId)
serverReq := r.client.GetServer(ctx, projectId, serverId)
serverReq := r.client.GetServer(ctx, projectId, region, serverId)
serverReq = serverReq.Details(true)
serverResp, err := serverReq.Execute()
if err != nil {
@ -630,7 +680,7 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res
ctx = core.LogResponse(ctx)
// Map response body to schema
err = mapFields(ctx, serverResp, &model)
err = mapFields(ctx, serverResp, &model, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading server", fmt.Sprintf("Processing API payload: %v", err))
return
@ -644,7 +694,7 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res
tflog.Info(ctx, "server read")
}
func (r *serverResource) updateServerAttributes(ctx context.Context, model, stateModel *Model) (*iaas.Server, error) {
func (r *serverResource) updateServerAttributes(ctx context.Context, model, stateModel *Model, region string) (*iaas.Server, error) {
// Generate API request body from model
payload, err := toUpdatePayload(ctx, model, stateModel.Labels)
if err != nil {
@ -655,7 +705,7 @@ func (r *serverResource) updateServerAttributes(ctx context.Context, model, stat
var updatedServer *iaas.Server
// Update existing server
updatedServer, err = r.client.UpdateServer(ctx, projectId, serverId).UpdateServerPayload(*payload).Execute()
updatedServer, err = r.client.UpdateServer(ctx, projectId, region, serverId).UpdateServerPayload(*payload).Execute()
if err != nil {
return nil, fmt.Errorf("Calling API: %w", err)
}
@ -666,12 +716,12 @@ func (r *serverResource) updateServerAttributes(ctx context.Context, model, stat
payload := iaas.ResizeServerPayload{
MachineType: modelMachineType,
}
err := r.client.ResizeServer(ctx, projectId, serverId).ResizeServerPayload(payload).Execute()
err := r.client.ResizeServer(ctx, projectId, region, serverId).ResizeServerPayload(payload).Execute()
if err != nil {
return nil, fmt.Errorf("Resizing the server, calling API: %w", err)
}
_, err = wait.ResizeServerWaitHandler(ctx, r.client, projectId, serverId).WaitWithContext(ctx)
_, err = wait.ResizeServerWaitHandler(ctx, r.client, projectId, region, serverId).WaitWithContext(ctx)
if err != nil {
return nil, fmt.Errorf("server resize waiting: %w", err)
}
@ -691,11 +741,13 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
return
}
projectId := model.ProjectId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
serverId := model.ServerId.ValueString()
ctx = core.InitProviderContext(ctx)
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "server_id", serverId)
// Retrieve values from state
@ -710,14 +762,14 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
server *iaas.Server
err error
)
if server, err = r.client.GetServer(ctx, model.ProjectId.ValueString(), model.ServerId.ValueString()).Execute(); err != nil {
if server, err = r.client.GetServer(ctx, projectId, region, serverId).Execute(); err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error retrieving server state", fmt.Sprintf("Getting server state: %v", err))
}
if model.DesiredStatus.ValueString() == modelStateDeallocated {
// if the target state is "deallocated", we have to perform the server update first
// and then shelve it afterwards. A shelved server cannot be updated
_, err = r.updateServerAttributes(ctx, &model, &stateModel)
_, err = r.updateServerAttributes(ctx, &model, &stateModel, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating server", err.Error())
return
@ -725,18 +777,18 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
ctx = core.LogResponse(ctx)
if err := updateServerStatus(ctx, r.client, server.Status, &model); err != nil {
if err := updateServerStatus(ctx, r.client, server.Status, &model, region); err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating server", err.Error())
return
}
} else {
// potentially unfreeze first and update afterwards
if err := updateServerStatus(ctx, r.client, server.Status, &model); err != nil {
if err := updateServerStatus(ctx, r.client, server.Status, &model, region); err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating server", err.Error())
return
}
_, err = r.updateServerAttributes(ctx, &model, &stateModel)
_, err = r.updateServerAttributes(ctx, &model, &stateModel, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating server", err.Error())
return
@ -746,7 +798,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
}
// Re-fetch the server data, to get the details values.
serverReq := r.client.GetServer(ctx, projectId, serverId)
serverReq := r.client.GetServer(ctx, projectId, region, serverId)
serverReq = serverReq.Details(true)
updatedServer, err := serverReq.Execute()
if err != nil {
@ -754,7 +806,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
return
}
err = mapFields(ctx, updatedServer, &model)
err = mapFields(ctx, updatedServer, &model, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating server", fmt.Sprintf("Processing API payload: %v", err))
return
@ -779,15 +831,17 @@ func (r *serverResource) Delete(ctx context.Context, req resource.DeleteRequest,
}
projectId := model.ProjectId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
serverId := model.ServerId.ValueString()
ctx = core.InitProviderContext(ctx)
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "server_id", serverId)
// Delete existing server
err := r.client.DeleteServer(ctx, projectId, serverId).Execute()
err := r.client.DeleteServer(ctx, projectId, region, serverId).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting server", fmt.Sprintf("Calling API: %v", err))
return
@ -795,7 +849,7 @@ func (r *serverResource) Delete(ctx context.Context, req resource.DeleteRequest,
ctx = core.LogResponse(ctx)
_, err = wait.DeleteServerWaitHandler(ctx, r.client, projectId, serverId).WaitWithContext(ctx)
_, err = wait.DeleteServerWaitHandler(ctx, r.client, projectId, region, serverId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting server", fmt.Sprintf("server deletion waiting: %v", err))
return
@ -809,25 +863,24 @@ func (r *serverResource) Delete(ctx context.Context, req resource.DeleteRequest,
func (r *serverResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idParts := strings.Split(req.ID, core.Separator)
if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" {
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing server",
fmt.Sprintf("Expected import identifier with format: [project_id],[server_id] Got: %q", req.ID),
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[server_id] Got: %q", req.ID),
)
return
}
projectId := idParts[0]
serverId := idParts[1]
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "server_id", serverId)
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
"project_id": idParts[0],
"region": idParts[1],
"server_id": idParts[2],
})
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("server_id"), serverId)...)
tflog.Info(ctx, "server state imported")
}
func mapFields(ctx context.Context, serverResp *iaas.Server, model *Model) error {
func mapFields(ctx context.Context, serverResp *iaas.Server, model *Model, region string) error {
if serverResp == nil {
return fmt.Errorf("response input is nil")
}
@ -844,7 +897,8 @@ func mapFields(ctx context.Context, serverResp *iaas.Server, model *Model) error
return fmt.Errorf("server id not present")
}
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), serverId)
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, serverId)
model.Region = types.StringValue(region)
labels, err := iaasUtils.MapLabels(ctx, serverResp.Labels, model.Labels)
if err != nil {
@ -981,9 +1035,9 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateServerPaylo
return nil, fmt.Errorf("converting to Go map: %w", err)
}
var bootVolumePayload *iaas.CreateServerPayloadBootVolume
var bootVolumePayload *iaas.ServerBootVolume
if !bootVolume.SourceId.IsNull() && !bootVolume.SourceType.IsNull() {
bootVolumePayload = &iaas.CreateServerPayloadBootVolume{
bootVolumePayload = &iaas.ServerBootVolume{
PerformanceClass: conversion.StringValueToPointer(bootVolume.PerformanceClass),
Size: conversion.Int64ValueToPointer(bootVolume.Size),
Source: &iaas.BootVolumeSource{
@ -1005,22 +1059,22 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateServerPaylo
userData = &encodedUserData
}
var network *iaas.CreateServerPayloadNetworking
if !model.NetworkInterfaces.IsNull() && !model.NetworkInterfaces.IsUnknown() {
var nicIds []string
for _, nic := range model.NetworkInterfaces.Elements() {
nicString, ok := nic.(types.String)
if !ok {
return nil, fmt.Errorf("type assertion failed")
}
nicIds = append(nicIds, nicString.ValueString())
if model.NetworkInterfaces.IsNull() || model.NetworkInterfaces.IsUnknown() {
return nil, fmt.Errorf("nil network interfaces")
}
var nicIds []string
for _, nic := range model.NetworkInterfaces.Elements() {
nicString, ok := nic.(types.String)
if !ok {
return nil, fmt.Errorf("type assertion failed")
}
nicIds = append(nicIds, nicString.ValueString())
}
network = &iaas.CreateServerPayloadNetworking{
CreateServerNetworkingWithNics: &iaas.CreateServerNetworkingWithNics{
NicIds: &nicIds,
},
}
network := &iaas.CreateServerPayloadAllOfNetworking{
CreateServerNetworkingWithNics: &iaas.CreateServerNetworkingWithNics{
NicIds: &nicIds,
},
}
return &iaas.CreateServerPayload{

View file

@ -26,24 +26,31 @@ func testTimestamp() time.Time {
}
func TestMapFields(t *testing.T) {
type args struct {
state Model
input *iaas.Server
region string
}
tests := []struct {
description string
state Model
input *iaas.Server
args args
expected Model
isValid bool
}{
{
"default_values",
Model{
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
description: "default_values",
args: args{
state: Model{
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
},
input: &iaas.Server{
Id: utils.Ptr("sid"),
},
region: "eu01",
},
&iaas.Server{
Id: utils.Ptr("sid"),
},
Model{
Id: types.StringValue("pid,sid"),
expected: Model{
Id: types.StringValue("pid,eu01,sid"),
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
Name: types.StringNull(),
@ -57,40 +64,45 @@ func TestMapFields(t *testing.T) {
CreatedAt: types.StringNull(),
UpdatedAt: types.StringNull(),
LaunchedAt: types.StringNull(),
Region: types.StringValue("eu01"),
},
true,
isValid: true,
},
{
"simple_values",
Model{
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
},
&iaas.Server{
Id: utils.Ptr("sid"),
Name: utils.Ptr("name"),
AvailabilityZone: utils.Ptr("zone"),
Labels: &map[string]interface{}{
"key": "value",
description: "simple_values",
args: args{
state: Model{
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
Region: types.StringValue("eu01"),
},
ImageId: utils.Ptr("image_id"),
Nics: &[]iaas.ServerNetwork{
{
NicId: utils.Ptr("nic1"),
input: &iaas.Server{
Id: utils.Ptr("sid"),
Name: utils.Ptr("name"),
AvailabilityZone: utils.Ptr("zone"),
Labels: &map[string]interface{}{
"key": "value",
},
{
NicId: utils.Ptr("nic2"),
ImageId: utils.Ptr("image_id"),
Nics: &[]iaas.ServerNetwork{
{
NicId: utils.Ptr("nic1"),
},
{
NicId: utils.Ptr("nic2"),
},
},
KeypairName: utils.Ptr("keypair_name"),
AffinityGroup: utils.Ptr("group_id"),
CreatedAt: utils.Ptr(testTimestamp()),
UpdatedAt: utils.Ptr(testTimestamp()),
LaunchedAt: utils.Ptr(testTimestamp()),
Status: utils.Ptr("active"),
},
KeypairName: utils.Ptr("keypair_name"),
AffinityGroup: utils.Ptr("group_id"),
CreatedAt: utils.Ptr(testTimestamp()),
UpdatedAt: utils.Ptr(testTimestamp()),
LaunchedAt: utils.Ptr(testTimestamp()),
Status: utils.Ptr("active"),
region: "eu02",
},
Model{
Id: types.StringValue("pid,sid"),
expected: Model{
Id: types.StringValue("pid,eu02,sid"),
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
Name: types.StringValue("name"),
@ -105,21 +117,25 @@ func TestMapFields(t *testing.T) {
CreatedAt: types.StringValue(testTimestampValue),
UpdatedAt: types.StringValue(testTimestampValue),
LaunchedAt: types.StringValue(testTimestampValue),
Region: types.StringValue("eu02"),
},
true,
isValid: true,
},
{
"empty_labels",
Model{
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
description: "empty_labels",
args: args{
state: Model{
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
},
input: &iaas.Server{
Id: utils.Ptr("sid"),
},
region: "eu01",
},
&iaas.Server{
Id: utils.Ptr("sid"),
},
Model{
Id: types.StringValue("pid,sid"),
expected: Model{
Id: types.StringValue("pid,eu01,sid"),
ProjectId: types.StringValue("pid"),
ServerId: types.StringValue("sid"),
Name: types.StringNull(),
@ -133,29 +149,26 @@ func TestMapFields(t *testing.T) {
CreatedAt: types.StringNull(),
UpdatedAt: types.StringNull(),
LaunchedAt: types.StringNull(),
Region: types.StringValue("eu01"),
},
true,
isValid: true,
},
{
"response_nil_fail",
Model{},
nil,
Model{},
false,
description: "response_nil_fail",
},
{
"no_resource_id",
Model{
ProjectId: types.StringValue("pid"),
description: "no_resource_id",
args: args{
state: Model{
ProjectId: types.StringValue("pid"),
},
input: &iaas.Server{},
},
&iaas.Server{},
Model{},
false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := mapFields(context.Background(), tt.input, &tt.state)
err := mapFields(context.Background(), tt.args.input, &tt.args.state, tt.args.region)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
@ -163,7 +176,7 @@ func TestMapFields(t *testing.T) {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(tt.state, tt.expected)
diff := cmp.Diff(tt.args.state, tt.expected)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
@ -180,8 +193,8 @@ func TestToCreatePayload(t *testing.T) {
isValid bool
}{
{
"ok",
&Model{
description: "ok",
input: &Model{
Name: types.StringValue("name"),
AvailabilityZone: types.StringValue("zone"),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
@ -199,14 +212,18 @@ func TestToCreatePayload(t *testing.T) {
KeypairName: types.StringValue("keypair"),
MachineType: types.StringValue("machine_type"),
UserData: types.StringValue(userData),
NetworkInterfaces: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("nic1"),
types.StringValue("nic2"),
}),
},
&iaas.CreateServerPayload{
expected: &iaas.CreateServerPayload{
Name: utils.Ptr("name"),
AvailabilityZone: utils.Ptr("zone"),
Labels: &map[string]interface{}{
"key": "value",
},
BootVolume: &iaas.CreateServerPayloadBootVolume{
BootVolume: &iaas.ServerBootVolume{
PerformanceClass: utils.Ptr("class"),
Size: utils.Ptr(int64(1)),
Source: &iaas.BootVolumeSource{
@ -218,12 +235,17 @@ func TestToCreatePayload(t *testing.T) {
KeypairName: utils.Ptr("keypair"),
MachineType: utils.Ptr("machine_type"),
UserData: utils.Ptr([]byte(base64EncodedUserData)),
Networking: &iaas.CreateServerPayloadAllOfNetworking{
CreateServerNetworkingWithNics: &iaas.CreateServerNetworkingWithNics{
NicIds: &[]string{"nic1", "nic2"},
},
},
},
true,
isValid: true,
},
{
"delete on termination is set to true",
&Model{
description: "delete on termination is set to true",
input: &Model{
Name: types.StringValue("name"),
AvailabilityZone: types.StringValue("zone"),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
@ -241,14 +263,18 @@ func TestToCreatePayload(t *testing.T) {
KeypairName: types.StringValue("keypair"),
MachineType: types.StringValue("machine_type"),
UserData: types.StringValue(userData),
NetworkInterfaces: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("nic1"),
types.StringValue("nic2"),
}),
},
&iaas.CreateServerPayload{
expected: &iaas.CreateServerPayload{
Name: utils.Ptr("name"),
AvailabilityZone: utils.Ptr("zone"),
Labels: &map[string]interface{}{
"key": "value",
},
BootVolume: &iaas.CreateServerPayloadBootVolume{
BootVolume: &iaas.ServerBootVolume{
PerformanceClass: utils.Ptr("class"),
Size: utils.Ptr(int64(1)),
Source: &iaas.BootVolumeSource{
@ -261,8 +287,13 @@ func TestToCreatePayload(t *testing.T) {
KeypairName: utils.Ptr("keypair"),
MachineType: utils.Ptr("machine_type"),
UserData: utils.Ptr([]byte(base64EncodedUserData)),
Networking: &iaas.CreateServerPayloadAllOfNetworking{
CreateServerNetworkingWithNics: &iaas.CreateServerNetworkingWithNics{
NicIds: &[]string{"nic1", "nic2"},
},
},
},
true,
isValid: true,
},
}
for _, tt := range tests {
@ -327,47 +358,47 @@ func TestToUpdatePayload(t *testing.T) {
}
}
var _ serverControlClient = (*mockServerControlClient)(nil)
var _ serverControlClient = &mockServerControlClient{}
// mockServerControlClient mocks the [serverControlClient] interface with
// pluggable functions
type mockServerControlClient struct {
wait.APIClientInterface
startServerCalled int
startServerExecute func(callNo int, ctx context.Context, projectId, serverId string) error
startServerExecute func(callNo int, ctx context.Context, projectId, region, serverId string) error
stopServerCalled int
stopServerExecute func(callNo int, ctx context.Context, projectId, serverId string) error
stopServerExecute func(callNo int, ctx context.Context, projectId, region, serverId string) error
deallocateServerCalled int
deallocateServerExecute func(callNo int, ctx context.Context, projectId, serverId string) error
deallocateServerExecute func(callNo int, ctx context.Context, projectId, region, serverId string) error
getServerCalled int
getServerExecute func(callNo int, ctx context.Context, projectId, serverId string) (*iaas.Server, error)
getServerExecute func(callNo int, ctx context.Context, projectId, region, serverId string) (*iaas.Server, error)
}
// DeallocateServerExecute implements serverControlClient.
func (t *mockServerControlClient) DeallocateServerExecute(ctx context.Context, projectId, serverId string) error {
func (t *mockServerControlClient) DeallocateServerExecute(ctx context.Context, projectId, region, serverId string) error {
t.deallocateServerCalled++
return t.deallocateServerExecute(t.deallocateServerCalled, ctx, projectId, serverId)
return t.deallocateServerExecute(t.deallocateServerCalled, ctx, projectId, region, serverId)
}
// GetServerExecute implements serverControlClient.
func (t *mockServerControlClient) GetServerExecute(ctx context.Context, projectId, serverId string) (*iaas.Server, error) {
func (t *mockServerControlClient) GetServerExecute(ctx context.Context, projectId, region, serverId string) (*iaas.Server, error) {
t.getServerCalled++
return t.getServerExecute(t.getServerCalled, ctx, projectId, serverId)
return t.getServerExecute(t.getServerCalled, ctx, projectId, region, serverId)
}
// StartServerExecute implements serverControlClient.
func (t *mockServerControlClient) StartServerExecute(ctx context.Context, projectId, serverId string) error {
func (t *mockServerControlClient) StartServerExecute(ctx context.Context, projectId, region, serverId string) error {
t.startServerCalled++
return t.startServerExecute(t.startServerCalled, ctx, projectId, serverId)
return t.startServerExecute(t.startServerCalled, ctx, projectId, region, serverId)
}
// StopServerExecute implements serverControlClient.
func (t *mockServerControlClient) StopServerExecute(ctx context.Context, projectId, serverId string) error {
func (t *mockServerControlClient) StopServerExecute(ctx context.Context, projectId, region, serverId string) error {
t.stopServerCalled++
return t.stopServerExecute(t.stopServerCalled, ctx, projectId, serverId)
return t.stopServerExecute(t.stopServerCalled, ctx, projectId, region, serverId)
}
func Test_serverResource_updateServerStatus(t *testing.T) {
@ -379,6 +410,7 @@ func Test_serverResource_updateServerStatus(t *testing.T) {
type args struct {
currentState *string
model Model
region string
}
type want struct {
err bool
@ -398,7 +430,7 @@ func Test_serverResource_updateServerStatus(t *testing.T) {
name: "no desired status",
fields: fields{
client: &mockServerControlClient{
getServerExecute: func(_ int, _ context.Context, _, _ string) (*iaas.Server, error) {
getServerExecute: func(_ int, _ context.Context, _, _, _ string) (*iaas.Server, error) {
return &iaas.Server{
Id: utils.Ptr(serverId.ValueString()),
Status: utils.Ptr(wait.ServerActiveStatus),
@ -422,7 +454,7 @@ func Test_serverResource_updateServerStatus(t *testing.T) {
name: "desired inactive state",
fields: fields{
client: &mockServerControlClient{
getServerExecute: func(no int, _ context.Context, _, _ string) (*iaas.Server, error) {
getServerExecute: func(no int, _ context.Context, _, _, _ string) (*iaas.Server, error) {
var state string
if no <= 1 {
state = wait.ServerActiveStatus
@ -434,7 +466,7 @@ func Test_serverResource_updateServerStatus(t *testing.T) {
Status: &state,
}, nil
},
stopServerExecute: func(_ int, _ context.Context, _, _ string) error { return nil },
stopServerExecute: func(_ int, _ context.Context, _, _, _ string) error { return nil },
},
},
args: args{
@ -455,7 +487,7 @@ func Test_serverResource_updateServerStatus(t *testing.T) {
name: "desired deallocated state",
fields: fields{
client: &mockServerControlClient{
getServerExecute: func(no int, _ context.Context, _, _ string) (*iaas.Server, error) {
getServerExecute: func(no int, _ context.Context, _, _, _ string) (*iaas.Server, error) {
var state string
switch no {
case 1:
@ -470,7 +502,7 @@ func Test_serverResource_updateServerStatus(t *testing.T) {
Status: &state,
}, nil
},
deallocateServerExecute: func(_ int, _ context.Context, _, _ string) error { return nil },
deallocateServerExecute: func(_ int, _ context.Context, _, _, _ string) error { return nil },
},
},
args: args{
@ -491,7 +523,7 @@ func Test_serverResource_updateServerStatus(t *testing.T) {
name: "don't call start if active",
fields: fields{
client: &mockServerControlClient{
getServerExecute: func(_ int, _ context.Context, _, _ string) (*iaas.Server, error) {
getServerExecute: func(_ int, _ context.Context, _, _, _ string) (*iaas.Server, error) {
return &iaas.Server{
Id: utils.Ptr(serverId.ValueString()),
Status: utils.Ptr(wait.ServerActiveStatus),
@ -516,7 +548,7 @@ func Test_serverResource_updateServerStatus(t *testing.T) {
name: "don't call stop if inactive",
fields: fields{
client: &mockServerControlClient{
getServerExecute: func(_ int, _ context.Context, _, _ string) (*iaas.Server, error) {
getServerExecute: func(_ int, _ context.Context, _, _, _ string) (*iaas.Server, error) {
return &iaas.Server{
Id: utils.Ptr(serverId.ValueString()),
Status: utils.Ptr(wait.ServerInactiveStatus),
@ -541,7 +573,7 @@ func Test_serverResource_updateServerStatus(t *testing.T) {
name: "don't call dealloacate if deallocated",
fields: fields{
client: &mockServerControlClient{
getServerExecute: func(_ int, _ context.Context, _, _ string) (*iaas.Server, error) {
getServerExecute: func(_ int, _ context.Context, _, _, _ string) (*iaas.Server, error) {
return &iaas.Server{
Id: utils.Ptr(serverId.ValueString()),
Status: utils.Ptr(wait.ServerDeallocatedStatus),
@ -566,7 +598,7 @@ func Test_serverResource_updateServerStatus(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := updateServerStatus(context.Background(), tt.fields.client, tt.args.currentState, &tt.args.model)
err := updateServerStatus(context.Background(), tt.fields.client, tt.args.currentState, &tt.args.model, tt.args.region)
if (err != nil) != tt.want.err {
t.Errorf("inconsistent error, want %v and got %v", tt.want.err, err)
}