Merge branch 'stackitcloud:main' into alpha
This commit is contained in:
commit
c07c81b091
125 changed files with 8395 additions and 6044 deletions
|
|
@ -33,16 +33,18 @@ func NewAffinityGroupDatasource() datasource.DataSource {
|
|||
}
|
||||
|
||||
type affinityGroupDatasource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
func (d *affinityGroupDatasource) 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
|
||||
}
|
||||
|
|
@ -61,7 +63,7 @@ func (d *affinityGroupDatasource) Schema(_ context.Context, _ datasource.SchemaR
|
|||
MarkdownDescription: descriptionMain,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal resource identifier. It is structured as \"`project_id`,`affinity_group_id`\".",
|
||||
Description: "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`affinity_group_id`\".",
|
||||
Computed: true,
|
||||
},
|
||||
"project_id": schema.StringAttribute{
|
||||
|
|
@ -72,6 +74,11 @@ func (d *affinityGroupDatasource) Schema(_ context.Context, _ datasource.SchemaR
|
|||
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,
|
||||
},
|
||||
"affinity_group_id": schema.StringAttribute{
|
||||
Description: "The affinity group ID.",
|
||||
Required: true,
|
||||
|
|
@ -117,14 +124,16 @@ func (d *affinityGroupDatasource) Read(ctx context.Context, req datasource.ReadR
|
|||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := d.providerData.GetRegionWithOverride(model.Region)
|
||||
affinityGroupId := model.AffinityGroupId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "affinity_group_id", affinityGroupId)
|
||||
|
||||
affinityGroupResp, err := d.client.GetAffinityGroupExecute(ctx, projectId, affinityGroupId)
|
||||
affinityGroupResp, err := d.client.GetAffinityGroupExecute(ctx, projectId, region, affinityGroupId)
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
|
|
@ -142,7 +151,7 @@ func (d *affinityGroupDatasource) Read(ctx context.Context, req datasource.ReadR
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapFields(ctx, affinityGroupResp, &model)
|
||||
err = mapFields(ctx, affinityGroupResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading affinity group", fmt.Sprintf("Processing API payload: %v", err))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
|
|
@ -33,12 +32,14 @@ var (
|
|||
_ resource.Resource = &affinityGroupResource{}
|
||||
_ resource.ResourceWithConfigure = &affinityGroupResource{}
|
||||
_ resource.ResourceWithImportState = &affinityGroupResource{}
|
||||
_ resource.ResourceWithModifyPlan = &affinityGroupResource{}
|
||||
)
|
||||
|
||||
// Model is the provider's internal model
|
||||
type Model struct {
|
||||
Id types.String `tfsdk:"id"`
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
AffinityGroupId types.String `tfsdk:"affinity_group_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
Policy types.String `tfsdk:"policy"`
|
||||
|
|
@ -51,7 +52,8 @@ func NewAffinityGroupResource() resource.Resource {
|
|||
|
||||
// affinityGroupResource is the resource implementation.
|
||||
type affinityGroupResource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
|
|
@ -59,14 +61,45 @@ func (r *affinityGroupResource) Metadata(_ context.Context, req resource.Metadat
|
|||
resp.TypeName = req.ProviderTypeName + "_affinity_group"
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
func (r *affinityGroupResource) 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
|
||||
}
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *affinityGroupResource) 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
|
||||
}
|
||||
|
|
@ -75,13 +108,13 @@ func (r *affinityGroupResource) Configure(ctx context.Context, req resource.Conf
|
|||
}
|
||||
|
||||
func (r *affinityGroupResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
description := "Affinity Group schema. Must have a `region` specified in the provider configuration."
|
||||
description := "Affinity Group schema."
|
||||
resp.Schema = schema.Schema{
|
||||
Description: description,
|
||||
MarkdownDescription: description + "\n\n" + exampleUsageWithServer + policies,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal resource identifier. It is structured as \"`project_id`,`affinity_group_id`\".",
|
||||
Description: "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`affinity_group_id`\".",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
|
|
@ -98,6 +131,15 @@ func (r *affinityGroupResource) Schema(_ context.Context, _ resource.SchemaReque
|
|||
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(),
|
||||
},
|
||||
},
|
||||
"affinity_group_id": schema.StringAttribute{
|
||||
Description: "The affinity group ID.",
|
||||
Computed: true,
|
||||
|
|
@ -153,19 +195,21 @@ func (r *affinityGroupResource) Create(ctx context.Context, req resource.CreateR
|
|||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// Create new affinityGroup
|
||||
payload, err := toCreatePayload(&model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating affinity group", fmt.Sprintf("Creating API payload: %v", err))
|
||||
return
|
||||
}
|
||||
affinityGroupResp, err := r.client.CreateAffinityGroup(ctx, projectId).CreateAffinityGroupPayload(*payload).Execute()
|
||||
affinityGroupResp, err := r.client.CreateAffinityGroup(ctx, projectId, region).CreateAffinityGroupPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating affinity group", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -176,7 +220,7 @@ func (r *affinityGroupResource) Create(ctx context.Context, req resource.CreateR
|
|||
ctx = tflog.SetField(ctx, "affinity_group_id", affinityGroupResp.Id)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, affinityGroupResp, &model)
|
||||
err = mapFields(ctx, affinityGroupResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating affinity group", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -199,14 +243,16 @@ func (r *affinityGroupResource) Read(ctx context.Context, req resource.ReadReque
|
|||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
affinityGroupId := model.AffinityGroupId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "affinity_group_id", affinityGroupId)
|
||||
|
||||
affinityGroupResp, err := r.client.GetAffinityGroupExecute(ctx, projectId, affinityGroupId)
|
||||
affinityGroupResp, err := r.client.GetAffinityGroupExecute(ctx, projectId, region, affinityGroupId)
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
|
|
@ -219,7 +265,7 @@ func (r *affinityGroupResource) Read(ctx context.Context, req resource.ReadReque
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapFields(ctx, affinityGroupResp, &model)
|
||||
err = mapFields(ctx, affinityGroupResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading affinity group", fmt.Sprintf("Processing API payload: %v", err))
|
||||
}
|
||||
|
|
@ -247,15 +293,17 @@ func (r *affinityGroupResource) Delete(ctx context.Context, req resource.DeleteR
|
|||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
affinityGroupId := model.AffinityGroupId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "affinity_group_id", affinityGroupId)
|
||||
|
||||
// Delete existing affinity group
|
||||
err := r.client.DeleteAffinityGroupExecute(ctx, projectId, affinityGroupId)
|
||||
err := r.client.DeleteAffinityGroupExecute(ctx, projectId, region, affinityGroupId)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting affinity group", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -269,21 +317,20 @@ func (r *affinityGroupResource) Delete(ctx context.Context, req resource.DeleteR
|
|||
func (r *affinityGroupResource) 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 affinity group",
|
||||
fmt.Sprintf("Expected import indentifier with format: [project_id],[affinity_group_id], got: %q", req.ID),
|
||||
fmt.Sprintf("Expected import indentifier with format: [project_id],[region],[affinity_group_id], got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
affinityGroupId := idParts[1]
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "affinity_group_id", affinityGroupId)
|
||||
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
|
||||
"project_id": idParts[0],
|
||||
"region": idParts[1],
|
||||
"affinity_group_id": idParts[2],
|
||||
})
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("affinity_group_id"), affinityGroupId)...)
|
||||
tflog.Info(ctx, "affinity group state imported")
|
||||
}
|
||||
|
||||
|
|
@ -301,7 +348,7 @@ func toCreatePayload(model *Model) (*iaas.CreateAffinityGroupPayload, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func mapFields(ctx context.Context, affinityGroupResp *iaas.AffinityGroup, model *Model) error {
|
||||
func mapFields(ctx context.Context, affinityGroupResp *iaas.AffinityGroup, model *Model, region string) error {
|
||||
if affinityGroupResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
|
|
@ -319,7 +366,8 @@ func mapFields(ctx context.Context, affinityGroupResp *iaas.AffinityGroup, model
|
|||
return fmt.Errorf("affinity group id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), affinityGroupId)
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, affinityGroupId)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
if affinityGroupResp.Members != nil && len(*affinityGroupResp.Members) > 0 {
|
||||
members, diags := types.ListValueFrom(ctx, types.StringType, *affinityGroupResp.Members)
|
||||
|
|
|
|||
|
|
@ -11,52 +11,56 @@ import (
|
|||
)
|
||||
|
||||
func TestMapFields(t *testing.T) {
|
||||
type args struct {
|
||||
state Model
|
||||
input *iaas.AffinityGroup
|
||||
region string
|
||||
}
|
||||
tests := []struct {
|
||||
description string
|
||||
state Model
|
||||
input *iaas.AffinityGroup
|
||||
args args
|
||||
expected Model
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"default_values",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
AffinityGroupId: types.StringValue("aid"),
|
||||
description: "default_values",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
AffinityGroupId: types.StringValue("aid"),
|
||||
},
|
||||
input: &iaas.AffinityGroup{
|
||||
Id: utils.Ptr("aid"),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.AffinityGroup{
|
||||
Id: utils.Ptr("aid"),
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,aid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,aid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
AffinityGroupId: types.StringValue("aid"),
|
||||
Name: types.StringNull(),
|
||||
Policy: types.StringNull(),
|
||||
Members: types.ListNull(types.StringType),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"response_nil_fail",
|
||||
Model{},
|
||||
nil,
|
||||
Model{},
|
||||
false,
|
||||
description: "response_nil_fail",
|
||||
},
|
||||
{
|
||||
"no_affinity_group_id",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
description: "no_affinity_group_id",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
},
|
||||
input: &iaas.AffinityGroup{},
|
||||
},
|
||||
&iaas.AffinityGroup{},
|
||||
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")
|
||||
}
|
||||
|
|
@ -64,7 +68,7 @@ func TestMapFields(t *testing.T) {
|
|||
t.Fatalf("Should not have failed")
|
||||
}
|
||||
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: %v", diff)
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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"`
|
||||
ImageId types.String `tfsdk:"image_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
DiskFormat types.String `tfsdk:"disk_format"`
|
||||
|
|
@ -49,7 +50,8 @@ func NewImageDataSource() datasource.DataSource {
|
|||
|
||||
// imageDataSource is the data source implementation.
|
||||
type imageDataSource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
|
|
@ -58,12 +60,13 @@ func (d *imageDataSource) Metadata(_ context.Context, req datasource.MetadataReq
|
|||
}
|
||||
|
||||
func (d *imageDataSource) 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
|
||||
}
|
||||
|
|
@ -72,14 +75,14 @@ func (d *imageDataSource) Configure(ctx context.Context, req datasource.Configur
|
|||
}
|
||||
|
||||
// Schema defines the schema for the datasource.
|
||||
func (r *imageDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
func (d *imageDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
description := "Image 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`,`image_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`image_id`\".",
|
||||
Computed: true,
|
||||
},
|
||||
"project_id": schema.StringAttribute{
|
||||
|
|
@ -90,6 +93,11 @@ func (r *imageDataSource) 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,
|
||||
},
|
||||
"image_id": schema.StringAttribute{
|
||||
Description: "The image ID.",
|
||||
Required: true,
|
||||
|
|
@ -203,23 +211,26 @@ func (r *imageDataSource) Schema(_ context.Context, _ datasource.SchemaRequest,
|
|||
}
|
||||
}
|
||||
|
||||
// // Read refreshes the Terraform state with the latest data.
|
||||
func (r *imageDataSource) 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 *imageDataSource) 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...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := d.providerData.GetRegionWithOverride(model.Region)
|
||||
imageId := model.ImageId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "image_id", imageId)
|
||||
|
||||
imageResp, err := r.client.GetImage(ctx, projectId, imageId).Execute()
|
||||
imageResp, err := d.client.GetImage(ctx, projectId, region, imageId).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
|
|
@ -238,7 +249,7 @@ func (r *imageDataSource) Read(ctx context.Context, req datasource.ReadRequest,
|
|||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapDataSourceFields(ctx, imageResp, &model)
|
||||
err = mapDataSourceFields(ctx, imageResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading image", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -252,7 +263,7 @@ func (r *imageDataSource) Read(ctx context.Context, req datasource.ReadRequest,
|
|||
tflog.Info(ctx, "image read")
|
||||
}
|
||||
|
||||
func mapDataSourceFields(ctx context.Context, imageResp *iaas.Image, model *DataSourceModel) error {
|
||||
func mapDataSourceFields(ctx context.Context, imageResp *iaas.Image, model *DataSourceModel, region string) error {
|
||||
if imageResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
|
|
@ -269,7 +280,8 @@ func mapDataSourceFields(ctx context.Context, imageResp *iaas.Image, model *Data
|
|||
return fmt.Errorf("image id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), imageId)
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, imageId)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
// Map config
|
||||
var configModel = &configModel{}
|
||||
|
|
|
|||
|
|
@ -12,69 +12,81 @@ import (
|
|||
)
|
||||
|
||||
func TestMapDataSourceFields(t *testing.T) {
|
||||
type args struct {
|
||||
state DataSourceModel
|
||||
input *iaas.Image
|
||||
region string
|
||||
}
|
||||
tests := []struct {
|
||||
description string
|
||||
state DataSourceModel
|
||||
input *iaas.Image
|
||||
args args
|
||||
expected DataSourceModel
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"default_values",
|
||||
DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
description: "default_values",
|
||||
args: args{
|
||||
state: DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
},
|
||||
input: &iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
},
|
||||
DataSourceModel{
|
||||
Id: types.StringValue("pid,iid"),
|
||||
expected: DataSourceModel{
|
||||
Id: types.StringValue("pid,eu01,iid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"simple_values",
|
||||
DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
description: "simple_values",
|
||||
args: args{
|
||||
state: DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
input: &iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
Name: utils.Ptr("name"),
|
||||
DiskFormat: utils.Ptr("format"),
|
||||
MinDiskSize: utils.Ptr(int64(1)),
|
||||
MinRam: utils.Ptr(int64(1)),
|
||||
Protected: utils.Ptr(true),
|
||||
Scope: utils.Ptr("scope"),
|
||||
Config: &iaas.ImageConfig{
|
||||
BootMenu: utils.Ptr(true),
|
||||
CdromBus: iaas.NewNullableString(utils.Ptr("cdrom_bus")),
|
||||
DiskBus: iaas.NewNullableString(utils.Ptr("disk_bus")),
|
||||
NicModel: iaas.NewNullableString(utils.Ptr("model")),
|
||||
OperatingSystem: utils.Ptr("os"),
|
||||
OperatingSystemDistro: iaas.NewNullableString(utils.Ptr("os_distro")),
|
||||
OperatingSystemVersion: iaas.NewNullableString(utils.Ptr("os_version")),
|
||||
RescueBus: iaas.NewNullableString(utils.Ptr("rescue_bus")),
|
||||
RescueDevice: iaas.NewNullableString(utils.Ptr("rescue_device")),
|
||||
SecureBoot: utils.Ptr(true),
|
||||
Uefi: utils.Ptr(true),
|
||||
VideoModel: iaas.NewNullableString(utils.Ptr("model")),
|
||||
VirtioScsi: utils.Ptr(true),
|
||||
},
|
||||
Checksum: &iaas.ImageChecksum{
|
||||
Algorithm: utils.Ptr("algorithm"),
|
||||
Digest: utils.Ptr("digest"),
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
region: "eu02",
|
||||
},
|
||||
&iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
Name: utils.Ptr("name"),
|
||||
DiskFormat: utils.Ptr("format"),
|
||||
MinDiskSize: utils.Ptr(int64(1)),
|
||||
MinRam: utils.Ptr(int64(1)),
|
||||
Protected: utils.Ptr(true),
|
||||
Scope: utils.Ptr("scope"),
|
||||
Config: &iaas.ImageConfig{
|
||||
BootMenu: utils.Ptr(true),
|
||||
CdromBus: iaas.NewNullableString(utils.Ptr("cdrom_bus")),
|
||||
DiskBus: iaas.NewNullableString(utils.Ptr("disk_bus")),
|
||||
NicModel: iaas.NewNullableString(utils.Ptr("model")),
|
||||
OperatingSystem: utils.Ptr("os"),
|
||||
OperatingSystemDistro: iaas.NewNullableString(utils.Ptr("os_distro")),
|
||||
OperatingSystemVersion: iaas.NewNullableString(utils.Ptr("os_version")),
|
||||
RescueBus: iaas.NewNullableString(utils.Ptr("rescue_bus")),
|
||||
RescueDevice: iaas.NewNullableString(utils.Ptr("rescue_device")),
|
||||
SecureBoot: utils.Ptr(true),
|
||||
Uefi: utils.Ptr(true),
|
||||
VideoModel: iaas.NewNullableString(utils.Ptr("model")),
|
||||
VirtioScsi: utils.Ptr(true),
|
||||
},
|
||||
Checksum: &iaas.ImageChecksum{
|
||||
Algorithm: utils.Ptr("algorithm"),
|
||||
Digest: utils.Ptr("digest"),
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
DataSourceModel{
|
||||
Id: types.StringValue("pid,iid"),
|
||||
expected: DataSourceModel{
|
||||
Id: types.StringValue("pid,eu02,iid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Name: types.StringValue("name"),
|
||||
|
|
@ -105,47 +117,50 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Region: types.StringValue("eu02"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"empty_labels",
|
||||
DataSourceModel{
|
||||
description: "empty_labels",
|
||||
args: args{
|
||||
state: DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
},
|
||||
input: &iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
expected: DataSourceModel{
|
||||
Id: types.StringValue("pid,eu01,iid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
&iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
},
|
||||
DataSourceModel{
|
||||
Id: types.StringValue("pid,iid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
},
|
||||
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.Image{},
|
||||
},
|
||||
&iaas.Image{},
|
||||
DataSourceModel{},
|
||||
false,
|
||||
expected: DataSourceModel{},
|
||||
isValid: 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")
|
||||
}
|
||||
|
|
@ -153,7 +168,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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
|
||||
|
|
@ -40,11 +39,13 @@ var (
|
|||
_ resource.Resource = &imageResource{}
|
||||
_ resource.ResourceWithConfigure = &imageResource{}
|
||||
_ resource.ResourceWithImportState = &imageResource{}
|
||||
_ resource.ResourceWithModifyPlan = &imageResource{}
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
ImageId types.String `tfsdk:"image_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
DiskFormat types.String `tfsdk:"disk_format"`
|
||||
|
|
@ -111,7 +112,8 @@ func NewImageResource() resource.Resource {
|
|||
|
||||
// imageResource is the resource implementation.
|
||||
type imageResource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
|
|
@ -119,14 +121,45 @@ func (r *imageResource) Metadata(_ context.Context, req resource.MetadataRequest
|
|||
resp.TypeName = req.ProviderTypeName + "_image"
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
func (r *imageResource) 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
|
||||
}
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *imageResource) 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
|
||||
}
|
||||
|
|
@ -140,7 +173,7 @@ func (r *imageResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
|
|||
Description: "Image 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`,`image_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`image_id`\".",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
|
|
@ -157,6 +190,15 @@ func (r *imageResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
|
|||
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(),
|
||||
},
|
||||
},
|
||||
"image_id": schema.StringAttribute{
|
||||
Description: "The image ID.",
|
||||
Computed: true,
|
||||
|
|
@ -378,11 +420,12 @@ func (r *imageResource) 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 {
|
||||
|
|
@ -391,7 +434,7 @@ func (r *imageResource) Create(ctx context.Context, req resource.CreateRequest,
|
|||
}
|
||||
|
||||
// Create new image
|
||||
imageCreateResp, err := r.client.CreateImage(ctx, projectId).CreateImagePayload(*payload).Execute()
|
||||
imageCreateResp, err := r.client.CreateImage(ctx, projectId, region).CreateImagePayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating image", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -401,15 +444,15 @@ func (r *imageResource) Create(ctx context.Context, req resource.CreateRequest,
|
|||
|
||||
ctx = tflog.SetField(ctx, "image_id", *imageCreateResp.Id)
|
||||
|
||||
// Get the image object, as the create response does not contain all fields
|
||||
image, err := r.client.GetImage(ctx, projectId, *imageCreateResp.Id).Execute()
|
||||
// Get the image object, as the creation response does not contain all fields
|
||||
image, err := r.client.GetImage(ctx, projectId, region, *imageCreateResp.Id).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating image", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, image, &model)
|
||||
err = mapFields(ctx, image, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating image", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -430,7 +473,7 @@ func (r *imageResource) Create(ctx context.Context, req resource.CreateRequest,
|
|||
}
|
||||
|
||||
// Wait for image to become available
|
||||
waiter := wait.UploadImageWaitHandler(ctx, r.client, projectId, *imageCreateResp.Id)
|
||||
waiter := wait.UploadImageWaitHandler(ctx, r.client, projectId, region, *imageCreateResp.Id)
|
||||
waiter = waiter.SetTimeout(7 * 24 * time.Hour) // Set timeout to one week, to make the timeout useless
|
||||
waitResp, err := waiter.WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -439,7 +482,7 @@ func (r *imageResource) Create(ctx context.Context, req resource.CreateRequest,
|
|||
}
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, waitResp, &model)
|
||||
err = mapFields(ctx, waitResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating image", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -454,7 +497,7 @@ func (r *imageResource) Create(ctx context.Context, req resource.CreateRequest,
|
|||
tflog.Info(ctx, "Image created")
|
||||
}
|
||||
|
||||
// // Read refreshes the Terraform state with the latest data.
|
||||
// Read refreshes the Terraform state with the latest data.
|
||||
func (r *imageResource) 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)
|
||||
|
|
@ -462,15 +505,18 @@ func (r *imageResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
|||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
imageId := model.ImageId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "image_id", imageId)
|
||||
|
||||
imageResp, err := r.client.GetImage(ctx, projectId, imageId).Execute()
|
||||
imageResp, err := r.client.GetImage(ctx, projectId, region, imageId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
|
|
@ -484,7 +530,7 @@ func (r *imageResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
|||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, imageResp, &model)
|
||||
err = mapFields(ctx, imageResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading image", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -507,12 +553,15 @@ func (r *imageResource) Update(ctx context.Context, req resource.UpdateRequest,
|
|||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
imageId := model.ImageId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "image_id", imageId)
|
||||
|
||||
// Retrieve values from state
|
||||
|
|
@ -530,7 +579,7 @@ func (r *imageResource) Update(ctx context.Context, req resource.UpdateRequest,
|
|||
return
|
||||
}
|
||||
// Update existing image
|
||||
updatedImage, err := r.client.UpdateImage(ctx, projectId, imageId).UpdateImagePayload(*payload).Execute()
|
||||
updatedImage, err := r.client.UpdateImage(ctx, projectId, region, imageId).UpdateImagePayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating image", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -538,7 +587,7 @@ func (r *imageResource) Update(ctx context.Context, req resource.UpdateRequest,
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapFields(ctx, updatedImage, &model)
|
||||
err = mapFields(ctx, updatedImage, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating image", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -563,14 +612,15 @@ func (r *imageResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
|||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
imageId := model.ImageId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "image_id", imageId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "image_id", imageId)
|
||||
|
||||
// Delete existing image
|
||||
err := r.client.DeleteImage(ctx, projectId, imageId).Execute()
|
||||
err := r.client.DeleteImage(ctx, projectId, region, imageId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting image", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -578,7 +628,7 @@ func (r *imageResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
_, err = wait.DeleteImageWaitHandler(ctx, r.client, projectId, imageId).WaitWithContext(ctx)
|
||||
_, err = wait.DeleteImageWaitHandler(ctx, r.client, projectId, region, imageId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting image", fmt.Sprintf("image deletion waiting: %v", err))
|
||||
return
|
||||
|
|
@ -588,29 +638,28 @@ func (r *imageResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
|||
}
|
||||
|
||||
// ImportState imports a resource into the Terraform state on success.
|
||||
// The expected format of the resource import identifier is: project_id,image_id
|
||||
// The expected format of the resource import identifier is: project_id,region,image_id
|
||||
func (r *imageResource) 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 image",
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[image_id] Got: %q", req.ID),
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[image_id] Got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
imageId := idParts[1]
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "image_id", imageId)
|
||||
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
|
||||
"project_id": idParts[0],
|
||||
"region": idParts[1],
|
||||
"image_id": idParts[2],
|
||||
})
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("image_id"), imageId)...)
|
||||
tflog.Info(ctx, "Image state imported")
|
||||
}
|
||||
|
||||
func mapFields(ctx context.Context, imageResp *iaas.Image, model *Model) error {
|
||||
func mapFields(ctx context.Context, imageResp *iaas.Image, model *Model, region string) error {
|
||||
if imageResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
|
|
@ -627,7 +676,8 @@ func mapFields(ctx context.Context, imageResp *iaas.Image, model *Model) error {
|
|||
return fmt.Errorf("image id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), imageId)
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, imageId)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
// Map config
|
||||
var configModel = &configModel{}
|
||||
|
|
|
|||
|
|
@ -17,69 +17,81 @@ import (
|
|||
)
|
||||
|
||||
func TestMapFields(t *testing.T) {
|
||||
type args struct {
|
||||
state Model
|
||||
input *iaas.Image
|
||||
region string
|
||||
}
|
||||
tests := []struct {
|
||||
description string
|
||||
state Model
|
||||
input *iaas.Image
|
||||
args args
|
||||
expected Model
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"default_values",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
description: "default_values",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
},
|
||||
input: &iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,iid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,iid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"simple_values",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
description: "simple_values",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
input: &iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
Name: utils.Ptr("name"),
|
||||
DiskFormat: utils.Ptr("format"),
|
||||
MinDiskSize: utils.Ptr(int64(1)),
|
||||
MinRam: utils.Ptr(int64(1)),
|
||||
Protected: utils.Ptr(true),
|
||||
Scope: utils.Ptr("scope"),
|
||||
Config: &iaas.ImageConfig{
|
||||
BootMenu: utils.Ptr(true),
|
||||
CdromBus: iaas.NewNullableString(utils.Ptr("cdrom_bus")),
|
||||
DiskBus: iaas.NewNullableString(utils.Ptr("disk_bus")),
|
||||
NicModel: iaas.NewNullableString(utils.Ptr("model")),
|
||||
OperatingSystem: utils.Ptr("os"),
|
||||
OperatingSystemDistro: iaas.NewNullableString(utils.Ptr("os_distro")),
|
||||
OperatingSystemVersion: iaas.NewNullableString(utils.Ptr("os_version")),
|
||||
RescueBus: iaas.NewNullableString(utils.Ptr("rescue_bus")),
|
||||
RescueDevice: iaas.NewNullableString(utils.Ptr("rescue_device")),
|
||||
SecureBoot: utils.Ptr(true),
|
||||
Uefi: utils.Ptr(true),
|
||||
VideoModel: iaas.NewNullableString(utils.Ptr("model")),
|
||||
VirtioScsi: utils.Ptr(true),
|
||||
},
|
||||
Checksum: &iaas.ImageChecksum{
|
||||
Algorithm: utils.Ptr("algorithm"),
|
||||
Digest: utils.Ptr("digest"),
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
region: "eu02",
|
||||
},
|
||||
&iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
Name: utils.Ptr("name"),
|
||||
DiskFormat: utils.Ptr("format"),
|
||||
MinDiskSize: utils.Ptr(int64(1)),
|
||||
MinRam: utils.Ptr(int64(1)),
|
||||
Protected: utils.Ptr(true),
|
||||
Scope: utils.Ptr("scope"),
|
||||
Config: &iaas.ImageConfig{
|
||||
BootMenu: utils.Ptr(true),
|
||||
CdromBus: iaas.NewNullableString(utils.Ptr("cdrom_bus")),
|
||||
DiskBus: iaas.NewNullableString(utils.Ptr("disk_bus")),
|
||||
NicModel: iaas.NewNullableString(utils.Ptr("model")),
|
||||
OperatingSystem: utils.Ptr("os"),
|
||||
OperatingSystemDistro: iaas.NewNullableString(utils.Ptr("os_distro")),
|
||||
OperatingSystemVersion: iaas.NewNullableString(utils.Ptr("os_version")),
|
||||
RescueBus: iaas.NewNullableString(utils.Ptr("rescue_bus")),
|
||||
RescueDevice: iaas.NewNullableString(utils.Ptr("rescue_device")),
|
||||
SecureBoot: utils.Ptr(true),
|
||||
Uefi: utils.Ptr(true),
|
||||
VideoModel: iaas.NewNullableString(utils.Ptr("model")),
|
||||
VirtioScsi: utils.Ptr(true),
|
||||
},
|
||||
Checksum: &iaas.ImageChecksum{
|
||||
Algorithm: utils.Ptr("algorithm"),
|
||||
Digest: utils.Ptr("digest"),
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,iid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu02,iid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Name: types.StringValue("name"),
|
||||
|
|
@ -110,47 +122,48 @@ func TestMapFields(t *testing.T) {
|
|||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Region: types.StringValue("eu02"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"empty_labels",
|
||||
Model{
|
||||
description: "empty_labels",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
},
|
||||
input: &iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,iid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
&iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,iid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
},
|
||||
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.Image{},
|
||||
},
|
||||
&iaas.Image{},
|
||||
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")
|
||||
}
|
||||
|
|
@ -158,7 +171,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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ var (
|
|||
type DataSourceModel struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
ImageId types.String `tfsdk:"image_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
NameRegex types.String `tfsdk:"name_regex"`
|
||||
|
|
@ -113,7 +114,8 @@ func NewImageV2DataSource() datasource.DataSource {
|
|||
|
||||
// imageDataV2Source is the data source implementation.
|
||||
type imageDataV2Source struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
|
|
@ -122,17 +124,18 @@ func (d *imageDataV2Source) Metadata(_ context.Context, req datasource.MetadataR
|
|||
}
|
||||
|
||||
func (d *imageDataV2Source) 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
|
||||
}
|
||||
|
||||
features.CheckBetaResourcesEnabled(ctx, &providerData, &resp.Diagnostics, "stackit_image_v2", "datasource")
|
||||
features.CheckBetaResourcesEnabled(ctx, &d.providerData, &resp.Diagnostics, "stackit_image_v2", "datasource")
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
apiClient := iaasUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics)
|
||||
apiClient := iaasUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
|
@ -189,7 +192,7 @@ func (d *imageDataV2Source) Schema(_ context.Context, _ datasource.SchemaRequest
|
|||
Description: description,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`image_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`image_id`\".",
|
||||
Computed: true,
|
||||
},
|
||||
"project_id": schema.StringAttribute{
|
||||
|
|
@ -200,6 +203,11 @@ func (d *imageDataV2Source) 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,
|
||||
},
|
||||
"image_id": schema.StringAttribute{
|
||||
Description: "Image ID to fetch directly",
|
||||
Optional: true,
|
||||
|
|
@ -357,6 +365,7 @@ func (d *imageDataV2Source) Read(ctx context.Context, req datasource.ReadRequest
|
|||
}
|
||||
|
||||
projectID := model.ProjectId.ValueString()
|
||||
region := d.providerData.GetRegionWithOverride(model.Region)
|
||||
imageID := model.ImageId.ValueString()
|
||||
name := model.Name.ValueString()
|
||||
nameRegex := model.NameRegex.ValueString()
|
||||
|
|
@ -373,6 +382,7 @@ func (d *imageDataV2Source) Read(ctx context.Context, req datasource.ReadRequest
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectID)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "image_id", imageID)
|
||||
ctx = tflog.SetField(ctx, "name", name)
|
||||
ctx = tflog.SetField(ctx, "name_regex", nameRegex)
|
||||
|
|
@ -383,7 +393,7 @@ func (d *imageDataV2Source) Read(ctx context.Context, req datasource.ReadRequest
|
|||
|
||||
// Case 1: Direct lookup by image ID
|
||||
if imageID != "" {
|
||||
imageResp, err = d.client.GetImage(ctx, projectID, imageID).Execute()
|
||||
imageResp, err = d.client.GetImage(ctx, projectID, region, imageID).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(ctx, &resp.Diagnostics, err, "Reading image",
|
||||
fmt.Sprintf("Image with ID %q does not exist in project %q.", imageID, projectID),
|
||||
|
|
@ -409,7 +419,7 @@ func (d *imageDataV2Source) Read(ctx context.Context, req datasource.ReadRequest
|
|||
}
|
||||
|
||||
// Fetch all available images
|
||||
imageList, err := d.client.ListImages(ctx, projectID).Execute()
|
||||
imageList, err := d.client.ListImages(ctx, projectID, region).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(ctx, &resp.Diagnostics, err, "List images", "Unable to fetch images", nil)
|
||||
return
|
||||
|
|
@ -457,7 +467,7 @@ func (d *imageDataV2Source) Read(ctx context.Context, req datasource.ReadRequest
|
|||
imageResp = filteredImages[0]
|
||||
}
|
||||
|
||||
err = mapDataSourceFields(ctx, imageResp, &model)
|
||||
err = mapDataSourceFields(ctx, imageResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading image", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -473,7 +483,7 @@ func (d *imageDataV2Source) Read(ctx context.Context, req datasource.ReadRequest
|
|||
tflog.Info(ctx, "image read")
|
||||
}
|
||||
|
||||
func mapDataSourceFields(ctx context.Context, imageResp *iaas.Image, model *DataSourceModel) error {
|
||||
func mapDataSourceFields(ctx context.Context, imageResp *iaas.Image, model *DataSourceModel, region string) error {
|
||||
if imageResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
|
|
@ -490,7 +500,8 @@ func mapDataSourceFields(ctx context.Context, imageResp *iaas.Image, model *Data
|
|||
return fmt.Errorf("image id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), imageId)
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, imageId)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
// Map config
|
||||
var configModel = &configModel{}
|
||||
|
|
|
|||
|
|
@ -12,69 +12,81 @@ import (
|
|||
)
|
||||
|
||||
func TestMapDataSourceFields(t *testing.T) {
|
||||
type args struct {
|
||||
state DataSourceModel
|
||||
input *iaas.Image
|
||||
region string
|
||||
}
|
||||
tests := []struct {
|
||||
description string
|
||||
state DataSourceModel
|
||||
input *iaas.Image
|
||||
args args
|
||||
expected DataSourceModel
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"default_values",
|
||||
DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
description: "default_values",
|
||||
args: args{
|
||||
state: DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
},
|
||||
input: &iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
},
|
||||
DataSourceModel{
|
||||
Id: types.StringValue("pid,iid"),
|
||||
expected: DataSourceModel{
|
||||
Id: types.StringValue("pid,eu01,iid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"simple_values",
|
||||
DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
description: "simple_values",
|
||||
args: args{
|
||||
state: DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
input: &iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
Name: utils.Ptr("name"),
|
||||
DiskFormat: utils.Ptr("format"),
|
||||
MinDiskSize: utils.Ptr(int64(1)),
|
||||
MinRam: utils.Ptr(int64(1)),
|
||||
Protected: utils.Ptr(true),
|
||||
Scope: utils.Ptr("scope"),
|
||||
Config: &iaas.ImageConfig{
|
||||
BootMenu: utils.Ptr(true),
|
||||
CdromBus: iaas.NewNullableString(utils.Ptr("cdrom_bus")),
|
||||
DiskBus: iaas.NewNullableString(utils.Ptr("disk_bus")),
|
||||
NicModel: iaas.NewNullableString(utils.Ptr("model")),
|
||||
OperatingSystem: utils.Ptr("os"),
|
||||
OperatingSystemDistro: iaas.NewNullableString(utils.Ptr("os_distro")),
|
||||
OperatingSystemVersion: iaas.NewNullableString(utils.Ptr("os_version")),
|
||||
RescueBus: iaas.NewNullableString(utils.Ptr("rescue_bus")),
|
||||
RescueDevice: iaas.NewNullableString(utils.Ptr("rescue_device")),
|
||||
SecureBoot: utils.Ptr(true),
|
||||
Uefi: utils.Ptr(true),
|
||||
VideoModel: iaas.NewNullableString(utils.Ptr("model")),
|
||||
VirtioScsi: utils.Ptr(true),
|
||||
},
|
||||
Checksum: &iaas.ImageChecksum{
|
||||
Algorithm: utils.Ptr("algorithm"),
|
||||
Digest: utils.Ptr("digest"),
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
region: "eu02",
|
||||
},
|
||||
&iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
Name: utils.Ptr("name"),
|
||||
DiskFormat: utils.Ptr("format"),
|
||||
MinDiskSize: utils.Ptr(int64(1)),
|
||||
MinRam: utils.Ptr(int64(1)),
|
||||
Protected: utils.Ptr(true),
|
||||
Scope: utils.Ptr("scope"),
|
||||
Config: &iaas.ImageConfig{
|
||||
BootMenu: utils.Ptr(true),
|
||||
CdromBus: iaas.NewNullableString(utils.Ptr("cdrom_bus")),
|
||||
DiskBus: iaas.NewNullableString(utils.Ptr("disk_bus")),
|
||||
NicModel: iaas.NewNullableString(utils.Ptr("model")),
|
||||
OperatingSystem: utils.Ptr("os"),
|
||||
OperatingSystemDistro: iaas.NewNullableString(utils.Ptr("os_distro")),
|
||||
OperatingSystemVersion: iaas.NewNullableString(utils.Ptr("os_version")),
|
||||
RescueBus: iaas.NewNullableString(utils.Ptr("rescue_bus")),
|
||||
RescueDevice: iaas.NewNullableString(utils.Ptr("rescue_device")),
|
||||
SecureBoot: utils.Ptr(true),
|
||||
Uefi: utils.Ptr(true),
|
||||
VideoModel: iaas.NewNullableString(utils.Ptr("model")),
|
||||
VirtioScsi: utils.Ptr(true),
|
||||
},
|
||||
Checksum: &iaas.ImageChecksum{
|
||||
Algorithm: utils.Ptr("algorithm"),
|
||||
Digest: utils.Ptr("digest"),
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
DataSourceModel{
|
||||
Id: types.StringValue("pid,iid"),
|
||||
expected: DataSourceModel{
|
||||
Id: types.StringValue("pid,eu02,iid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Name: types.StringValue("name"),
|
||||
|
|
@ -105,47 +117,48 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Region: types.StringValue("eu02"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"empty_labels",
|
||||
DataSourceModel{
|
||||
description: "empty_labels",
|
||||
args: args{
|
||||
state: DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
},
|
||||
input: &iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
expected: DataSourceModel{
|
||||
Id: types.StringValue("pid,eu01,iid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
&iaas.Image{
|
||||
Id: utils.Ptr("iid"),
|
||||
},
|
||||
DataSourceModel{
|
||||
Id: types.StringValue("pid,iid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ImageId: types.StringValue("iid"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
},
|
||||
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.Image{},
|
||||
},
|
||||
&iaas.Image{},
|
||||
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")
|
||||
}
|
||||
|
|
@ -153,7 +166,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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ var (
|
|||
_ datasource.DataSource = &keyPairDataSource{}
|
||||
)
|
||||
|
||||
// NewVolumeDataSource is a helper function to simplify the provider implementation.
|
||||
// NewKeyPairDataSource is a helper function to simplify the provider implementation.
|
||||
func NewKeyPairDataSource() datasource.DataSource {
|
||||
return &keyPairDataSource{}
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ func (d *keyPairDataSource) Configure(ctx context.Context, req datasource.Config
|
|||
}
|
||||
|
||||
// Schema defines the schema for the resource.
|
||||
func (r *keyPairDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
func (d *keyPairDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
description := "Key pair resource schema. Must have a `region` specified in the provider configuration."
|
||||
|
||||
resp.Schema = schema.Schema{
|
||||
|
|
@ -84,7 +84,7 @@ func (r *keyPairDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
|
|||
}
|
||||
|
||||
// Read refreshes the Terraform state with the latest data.
|
||||
func (r *keyPairDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
func (d *keyPairDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
var model Model
|
||||
diags := req.Config.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -97,7 +97,7 @@ func (r *keyPairDataSource) Read(ctx context.Context, req datasource.ReadRequest
|
|||
|
||||
ctx = tflog.SetField(ctx, "name", name)
|
||||
|
||||
keypairResp, err := r.client.GetKeyPair(ctx, name).Execute()
|
||||
keypairResp, err := d.client.GetKeyPair(ctx, name).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
|
||||
|
|
@ -19,7 +21,6 @@ import (
|
|||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
|
|
@ -28,6 +29,7 @@ var _ datasource.DataSource = &machineTypeDataSource{}
|
|||
type DataSourceModel struct {
|
||||
Id types.String `tfsdk:"id"` // required by Terraform to identify state
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
SortAscending types.Bool `tfsdk:"sort_ascending"`
|
||||
Filter types.String `tfsdk:"filter"`
|
||||
Description types.String `tfsdk:"description"`
|
||||
|
|
@ -44,7 +46,8 @@ func NewMachineTypeDataSource() datasource.DataSource {
|
|||
}
|
||||
|
||||
type machineTypeDataSource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
func (d *machineTypeDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
|
|
@ -52,17 +55,18 @@ func (d *machineTypeDataSource) Metadata(_ context.Context, req datasource.Metad
|
|||
}
|
||||
|
||||
func (d *machineTypeDataSource) 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
|
||||
}
|
||||
|
||||
features.CheckBetaResourcesEnabled(ctx, &providerData, &resp.Diagnostics, "stackit_machine_type", "datasource")
|
||||
features.CheckBetaResourcesEnabled(ctx, &d.providerData, &resp.Diagnostics, "stackit_machine_type", "datasource")
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
client := iaasUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics)
|
||||
client := iaasUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
|
@ -76,7 +80,7 @@ func (d *machineTypeDataSource) Schema(_ context.Context, _ datasource.SchemaReq
|
|||
MarkdownDescription: features.AddBetaDescription("Machine type data source.", core.Datasource),
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`image_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`image_id`\".",
|
||||
Computed: true,
|
||||
},
|
||||
"project_id": schema.StringAttribute{
|
||||
|
|
@ -87,6 +91,11 @@ func (d *machineTypeDataSource) Schema(_ context.Context, _ datasource.SchemaReq
|
|||
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,
|
||||
},
|
||||
"sort_ascending": schema.BoolAttribute{
|
||||
Description: "Sort machine types by name ascending (`true`) or descending (`false`). Defaults to `false`",
|
||||
Optional: true,
|
||||
|
|
@ -142,15 +151,17 @@ func (d *machineTypeDataSource) Read(ctx context.Context, req datasource.ReadReq
|
|||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := d.providerData.GetRegionWithOverride(model.Region)
|
||||
sortAscending := model.SortAscending.ValueBool()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "filter_is_null", model.Filter.IsNull())
|
||||
ctx = tflog.SetField(ctx, "filter_is_unknown", model.Filter.IsUnknown())
|
||||
|
||||
listMachineTypeReq := d.client.ListMachineTypes(ctx, projectId)
|
||||
listMachineTypeReq := d.client.ListMachineTypes(ctx, projectId, region)
|
||||
|
||||
if !model.Filter.IsNull() && !model.Filter.IsUnknown() && strings.TrimSpace(model.Filter.ValueString()) != "" {
|
||||
listMachineTypeReq = listMachineTypeReq.Filter(strings.TrimSpace(model.Filter.ValueString()))
|
||||
|
|
@ -187,7 +198,7 @@ func (d *machineTypeDataSource) Read(ctx context.Context, req datasource.ReadReq
|
|||
return
|
||||
}
|
||||
|
||||
if err := mapDataSourceFields(ctx, sorted[0], &model); err != nil {
|
||||
if err := mapDataSourceFields(ctx, sorted[0], &model, region); err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading machine type", fmt.Sprintf("Failed to translate API response: %v", err))
|
||||
return
|
||||
}
|
||||
|
|
@ -199,7 +210,7 @@ func (d *machineTypeDataSource) Read(ctx context.Context, req datasource.ReadReq
|
|||
tflog.Info(ctx, "Successfully read machine type")
|
||||
}
|
||||
|
||||
func mapDataSourceFields(ctx context.Context, machineType *iaas.MachineType, model *DataSourceModel) error {
|
||||
func mapDataSourceFields(ctx context.Context, machineType *iaas.MachineType, model *DataSourceModel, region string) error {
|
||||
if machineType == nil || model == nil {
|
||||
return fmt.Errorf("nil input provided")
|
||||
}
|
||||
|
|
@ -208,7 +219,8 @@ func mapDataSourceFields(ctx context.Context, machineType *iaas.MachineType, mod
|
|||
return fmt.Errorf("machine type name is missing")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), *machineType.Name)
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, *machineType.Name)
|
||||
model.Region = types.StringValue(region)
|
||||
model.Name = types.StringPointerValue(machineType.Name)
|
||||
model.Description = types.StringPointerValue(machineType.Description)
|
||||
model.Disk = types.Int64PointerValue(machineType.Disk)
|
||||
|
|
|
|||
|
|
@ -13,32 +13,39 @@ import (
|
|||
)
|
||||
|
||||
func TestMapDataSourceFields(t *testing.T) {
|
||||
type args struct {
|
||||
initial DataSourceModel
|
||||
input *iaas.MachineType
|
||||
region string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
initial DataSourceModel
|
||||
input *iaas.MachineType
|
||||
args args
|
||||
expected DataSourceModel
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "valid simple values",
|
||||
initial: DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
},
|
||||
input: &iaas.MachineType{
|
||||
Name: utils.Ptr("s1.2"),
|
||||
Description: utils.Ptr("general-purpose small"),
|
||||
Disk: utils.Ptr(int64(20)),
|
||||
Ram: utils.Ptr(int64(2048)),
|
||||
Vcpus: utils.Ptr(int64(2)),
|
||||
ExtraSpecs: &map[string]interface{}{
|
||||
"cpu": "amd-epycrome-7702",
|
||||
"overcommit": "1",
|
||||
"environment": "general",
|
||||
args: args{
|
||||
initial: DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
},
|
||||
input: &iaas.MachineType{
|
||||
Name: utils.Ptr("s1.2"),
|
||||
Description: utils.Ptr("general-purpose small"),
|
||||
Disk: utils.Ptr(int64(20)),
|
||||
Ram: utils.Ptr(int64(2048)),
|
||||
Vcpus: utils.Ptr(int64(2)),
|
||||
ExtraSpecs: &map[string]interface{}{
|
||||
"cpu": "amd-epycrome-7702",
|
||||
"overcommit": "1",
|
||||
"environment": "general",
|
||||
},
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
expected: DataSourceModel{
|
||||
Id: types.StringValue("pid,s1.2"),
|
||||
Id: types.StringValue("pid,eu01,s1.2"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
Name: types.StringValue("s1.2"),
|
||||
Description: types.StringValue("general-purpose small"),
|
||||
|
|
@ -50,42 +57,50 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
"overcommit": types.StringValue("1"),
|
||||
"environment": types.StringValue("general"),
|
||||
}),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "missing name should fail",
|
||||
initial: DataSourceModel{
|
||||
ProjectId: types.StringValue("pid-456"),
|
||||
},
|
||||
input: &iaas.MachineType{
|
||||
Description: utils.Ptr("gp-medium"),
|
||||
args: args{
|
||||
initial: DataSourceModel{
|
||||
ProjectId: types.StringValue("pid-456"),
|
||||
},
|
||||
input: &iaas.MachineType{
|
||||
Description: utils.Ptr("gp-medium"),
|
||||
},
|
||||
},
|
||||
expected: DataSourceModel{},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "nil machineType should fail",
|
||||
initial: DataSourceModel{},
|
||||
input: nil,
|
||||
name: "nil machineType should fail",
|
||||
args: args{
|
||||
initial: DataSourceModel{},
|
||||
input: nil,
|
||||
},
|
||||
expected: DataSourceModel{},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "empty extraSpecs should return null map",
|
||||
initial: DataSourceModel{
|
||||
ProjectId: types.StringValue("pid-789"),
|
||||
},
|
||||
input: &iaas.MachineType{
|
||||
Name: utils.Ptr("m1.noextras"),
|
||||
Description: utils.Ptr("no extras"),
|
||||
Disk: utils.Ptr(int64(10)),
|
||||
Ram: utils.Ptr(int64(1024)),
|
||||
Vcpus: utils.Ptr(int64(1)),
|
||||
ExtraSpecs: &map[string]interface{}{},
|
||||
args: args{
|
||||
initial: DataSourceModel{
|
||||
ProjectId: types.StringValue("pid-789"),
|
||||
},
|
||||
input: &iaas.MachineType{
|
||||
Name: utils.Ptr("m1.noextras"),
|
||||
Description: utils.Ptr("no extras"),
|
||||
Disk: utils.Ptr(int64(10)),
|
||||
Ram: utils.Ptr(int64(1024)),
|
||||
Vcpus: utils.Ptr(int64(1)),
|
||||
ExtraSpecs: &map[string]interface{}{},
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
expected: DataSourceModel{
|
||||
Id: types.StringValue("pid-789,m1.noextras"),
|
||||
Id: types.StringValue("pid-789,eu01,m1.noextras"),
|
||||
ProjectId: types.StringValue("pid-789"),
|
||||
Name: types.StringValue("m1.noextras"),
|
||||
Description: types.StringValue("no extras"),
|
||||
|
|
@ -93,24 +108,28 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
Ram: types.Int64Value(1024),
|
||||
Vcpus: types.Int64Value(1),
|
||||
ExtraSpecs: types.MapNull(types.StringType),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "nil extrasSpecs should return null map",
|
||||
initial: DataSourceModel{
|
||||
ProjectId: types.StringValue("pid-987"),
|
||||
},
|
||||
input: &iaas.MachineType{
|
||||
Name: utils.Ptr("g1.nil"),
|
||||
Description: utils.Ptr("missing extras"),
|
||||
Disk: utils.Ptr(int64(40)),
|
||||
Ram: utils.Ptr(int64(8096)),
|
||||
Vcpus: utils.Ptr(int64(4)),
|
||||
ExtraSpecs: nil,
|
||||
args: args{
|
||||
initial: DataSourceModel{
|
||||
ProjectId: types.StringValue("pid-987"),
|
||||
},
|
||||
input: &iaas.MachineType{
|
||||
Name: utils.Ptr("g1.nil"),
|
||||
Description: utils.Ptr("missing extras"),
|
||||
Disk: utils.Ptr(int64(40)),
|
||||
Ram: utils.Ptr(int64(8096)),
|
||||
Vcpus: utils.Ptr(int64(4)),
|
||||
ExtraSpecs: nil,
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
expected: DataSourceModel{
|
||||
Id: types.StringValue("pid-987,g1.nil"),
|
||||
Id: types.StringValue("pid-987,eu01,g1.nil"),
|
||||
ProjectId: types.StringValue("pid-987"),
|
||||
Name: types.StringValue("g1.nil"),
|
||||
Description: types.StringValue("missing extras"),
|
||||
|
|
@ -118,24 +137,27 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
Ram: types.Int64Value(8096),
|
||||
Vcpus: types.Int64Value(4),
|
||||
ExtraSpecs: types.MapNull(types.StringType),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid extraSpecs with non-string values",
|
||||
initial: DataSourceModel{
|
||||
ProjectId: types.StringValue("test-err"),
|
||||
},
|
||||
input: &iaas.MachineType{
|
||||
Name: utils.Ptr("invalid"),
|
||||
Description: utils.Ptr("bad map"),
|
||||
Disk: utils.Ptr(int64(10)),
|
||||
Ram: utils.Ptr(int64(4096)),
|
||||
Vcpus: utils.Ptr(int64(2)),
|
||||
ExtraSpecs: &map[string]interface{}{
|
||||
"cpu": "intel",
|
||||
"burst": true, // not a string
|
||||
"gen": 8, // not a string
|
||||
args: args{
|
||||
initial: DataSourceModel{
|
||||
ProjectId: types.StringValue("test-err"),
|
||||
},
|
||||
input: &iaas.MachineType{
|
||||
Name: utils.Ptr("invalid"),
|
||||
Description: utils.Ptr("bad map"),
|
||||
Disk: utils.Ptr(int64(10)),
|
||||
Ram: utils.Ptr(int64(4096)),
|
||||
Vcpus: utils.Ptr(int64(2)),
|
||||
ExtraSpecs: &map[string]interface{}{
|
||||
"cpu": "intel",
|
||||
"burst": true, // not a string
|
||||
"gen": 8, // not a string
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: DataSourceModel{},
|
||||
|
|
@ -145,7 +167,7 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := mapDataSourceFields(context.Background(), tt.input, &tt.initial)
|
||||
err := mapDataSourceFields(context.Background(), tt.args.input, &tt.args.initial, tt.args.region)
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("expected error but got none")
|
||||
|
|
@ -157,13 +179,13 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
diff := cmp.Diff(tt.expected, tt.initial)
|
||||
diff := cmp.Diff(tt.expected, tt.args.initial)
|
||||
if diff != "" {
|
||||
t.Errorf("unexpected diff (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
// Extra sanity check for proper ID format
|
||||
if id := tt.initial.Id.ValueString(); !strings.HasPrefix(id, tt.initial.ProjectId.ValueString()+",") {
|
||||
if id := tt.args.initial.Id.ValueString(); !strings.HasPrefix(id, tt.args.initial.ProjectId.ValueString()+",") {
|
||||
t.Errorf("unexpected ID format: got %q", id)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,14 +2,11 @@ package network
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/v1network"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/v2network"
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
iaasAlphaUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
|
|
@ -18,7 +15,9 @@ import (
|
|||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||
)
|
||||
|
||||
|
|
@ -27,6 +26,30 @@ var (
|
|||
_ datasource.DataSource = &networkDataSource{}
|
||||
)
|
||||
|
||||
type DataSourceModel struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
NetworkId types.String `tfsdk:"network_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
Nameservers types.List `tfsdk:"nameservers"`
|
||||
IPv4Gateway types.String `tfsdk:"ipv4_gateway"`
|
||||
IPv4Nameservers types.List `tfsdk:"ipv4_nameservers"`
|
||||
IPv4Prefix types.String `tfsdk:"ipv4_prefix"`
|
||||
IPv4PrefixLength types.Int64 `tfsdk:"ipv4_prefix_length"`
|
||||
Prefixes types.List `tfsdk:"prefixes"`
|
||||
IPv4Prefixes types.List `tfsdk:"ipv4_prefixes"`
|
||||
IPv6Gateway types.String `tfsdk:"ipv6_gateway"`
|
||||
IPv6Nameservers types.List `tfsdk:"ipv6_nameservers"`
|
||||
IPv6Prefix types.String `tfsdk:"ipv6_prefix"`
|
||||
IPv6PrefixLength types.Int64 `tfsdk:"ipv6_prefix_length"`
|
||||
IPv6Prefixes types.List `tfsdk:"ipv6_prefixes"`
|
||||
PublicIP types.String `tfsdk:"public_ip"`
|
||||
Labels types.Map `tfsdk:"labels"`
|
||||
Routed types.Bool `tfsdk:"routed"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
RoutingTableID types.String `tfsdk:"routing_table_id"`
|
||||
}
|
||||
|
||||
// NewNetworkDataSource is a helper function to simplify the provider implementation.
|
||||
func NewNetworkDataSource() datasource.DataSource {
|
||||
return &networkDataSource{}
|
||||
|
|
@ -34,11 +57,8 @@ func NewNetworkDataSource() datasource.DataSource {
|
|||
|
||||
// networkDataSource is the data source implementation.
|
||||
type networkDataSource struct {
|
||||
client *iaas.APIClient
|
||||
// alphaClient will be used in case the experimental flag "network" is set
|
||||
alphaClient *iaasalpha.APIClient
|
||||
isExperimental bool
|
||||
providerData core.ProviderData
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
|
|
@ -53,24 +73,11 @@ func (d *networkDataSource) Configure(ctx context.Context, req datasource.Config
|
|||
return
|
||||
}
|
||||
|
||||
d.isExperimental = features.CheckExperimentEnabledWithoutError(ctx, &d.providerData, features.NetworkExperiment, "stackit_network", core.Datasource, &resp.Diagnostics)
|
||||
apiClient := iaasUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if d.isExperimental {
|
||||
alphaApiClient := iaasAlphaUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
d.alphaClient = alphaApiClient
|
||||
} else {
|
||||
apiClient := iaasUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
d.client = apiClient
|
||||
}
|
||||
d.client = apiClient
|
||||
tflog.Info(ctx, "IaaS client configured")
|
||||
}
|
||||
|
||||
|
|
@ -197,9 +204,199 @@ func (d *networkDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
|
|||
|
||||
// Read refreshes the Terraform state with the latest data.
|
||||
func (d *networkDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
if !d.isExperimental {
|
||||
v1network.DatasourceRead(ctx, req, resp, d.client)
|
||||
} else {
|
||||
v2network.DatasourceRead(ctx, req, resp, d.alphaClient, d.providerData)
|
||||
var model DataSourceModel
|
||||
diags := req.Config.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
networkId := model.NetworkId.ValueString()
|
||||
region := d.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
|
||||
networkResp, err := d.client.GetNetwork(ctx, projectId, region, networkId).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
&resp.Diagnostics,
|
||||
err,
|
||||
"Reading network",
|
||||
fmt.Sprintf("Network with ID %q does not exist in project %q.", networkId, projectId),
|
||||
map[int]string{
|
||||
http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId),
|
||||
},
|
||||
)
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
err = mapDataSourceFields(ctx, networkResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Network read")
|
||||
}
|
||||
|
||||
func mapDataSourceFields(ctx context.Context, networkResp *iaas.Network, model *DataSourceModel, region string) error {
|
||||
if networkResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
if model == nil {
|
||||
return fmt.Errorf("model input is nil")
|
||||
}
|
||||
|
||||
var networkId string
|
||||
if model.NetworkId.ValueString() != "" {
|
||||
networkId = model.NetworkId.ValueString()
|
||||
} else if networkResp.Id != nil {
|
||||
networkId = *networkResp.Id
|
||||
} else {
|
||||
return fmt.Errorf("network id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, networkId)
|
||||
|
||||
labels, err := iaasUtils.MapLabels(ctx, networkResp.Labels, model.Labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// IPv4
|
||||
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.Nameservers == nil {
|
||||
model.Nameservers = types.ListNull(types.StringType)
|
||||
model.IPv4Nameservers = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respNameservers := *networkResp.Ipv4.Nameservers
|
||||
modelNameservers, err := utils.ListValuetoStringSlice(model.Nameservers)
|
||||
modelIPv4Nameservers, errIpv4 := utils.ListValuetoStringSlice(model.IPv4Nameservers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current network nameservers from model: %w", err)
|
||||
}
|
||||
if errIpv4 != nil {
|
||||
return fmt.Errorf("get current IPv4 network nameservers from model: %w", errIpv4)
|
||||
}
|
||||
|
||||
reconciledNameservers := utils.ReconcileStringSlices(modelNameservers, respNameservers)
|
||||
reconciledIPv4Nameservers := utils.ReconcileStringSlices(modelIPv4Nameservers, respNameservers)
|
||||
|
||||
nameserversTF, diags := types.ListValueFrom(ctx, types.StringType, reconciledNameservers)
|
||||
ipv4NameserversTF, ipv4Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv4Nameservers)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network nameservers: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if ipv4Diags.HasError() {
|
||||
return fmt.Errorf("map IPv4 network nameservers: %w", core.DiagsToError(ipv4Diags))
|
||||
}
|
||||
|
||||
model.Nameservers = nameserversTF
|
||||
model.IPv4Nameservers = ipv4NameserversTF
|
||||
}
|
||||
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.Prefixes == nil {
|
||||
model.Prefixes = types.ListNull(types.StringType)
|
||||
model.IPv4Prefixes = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respPrefixes := *networkResp.Ipv4.Prefixes
|
||||
prefixesTF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixes)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network prefixes: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if len(respPrefixes) > 0 {
|
||||
model.IPv4Prefix = types.StringValue(respPrefixes[0])
|
||||
_, netmask, err := net.ParseCIDR(respPrefixes[0])
|
||||
if err != nil {
|
||||
// silently ignore parsing error for the netmask
|
||||
model.IPv4PrefixLength = types.Int64Null()
|
||||
} else {
|
||||
ones, _ := netmask.Mask.Size()
|
||||
model.IPv4PrefixLength = types.Int64Value(int64(ones))
|
||||
}
|
||||
}
|
||||
|
||||
model.Prefixes = prefixesTF
|
||||
model.IPv4Prefixes = prefixesTF
|
||||
}
|
||||
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.Gateway == nil {
|
||||
model.IPv4Gateway = types.StringNull()
|
||||
} else {
|
||||
model.IPv4Gateway = types.StringPointerValue(networkResp.Ipv4.GetGateway())
|
||||
}
|
||||
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.PublicIp == nil {
|
||||
model.PublicIP = types.StringNull()
|
||||
} else {
|
||||
model.PublicIP = types.StringPointerValue(networkResp.Ipv4.PublicIp)
|
||||
}
|
||||
|
||||
// IPv6
|
||||
|
||||
if networkResp.Ipv6 == nil || networkResp.Ipv6.Nameservers == nil {
|
||||
model.IPv6Nameservers = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respIPv6Nameservers := *networkResp.Ipv6.Nameservers
|
||||
modelIPv6Nameservers, errIpv6 := utils.ListValuetoStringSlice(model.IPv6Nameservers)
|
||||
if errIpv6 != nil {
|
||||
return fmt.Errorf("get current IPv6 network nameservers from model: %w", errIpv6)
|
||||
}
|
||||
|
||||
reconciledIPv6Nameservers := utils.ReconcileStringSlices(modelIPv6Nameservers, respIPv6Nameservers)
|
||||
|
||||
ipv6NameserversTF, ipv6Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv6Nameservers)
|
||||
if ipv6Diags.HasError() {
|
||||
return fmt.Errorf("map IPv6 network nameservers: %w", core.DiagsToError(ipv6Diags))
|
||||
}
|
||||
|
||||
model.IPv6Nameservers = ipv6NameserversTF
|
||||
}
|
||||
|
||||
if networkResp.Ipv6 == nil || networkResp.Ipv6.Prefixes == nil {
|
||||
model.IPv6Prefixes = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respPrefixesV6 := *networkResp.Ipv6.Prefixes
|
||||
prefixesV6TF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixesV6)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network IPv6 prefixes: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if len(respPrefixesV6) > 0 {
|
||||
model.IPv6Prefix = types.StringValue(respPrefixesV6[0])
|
||||
_, netmask, err := net.ParseCIDR(respPrefixesV6[0])
|
||||
if err != nil {
|
||||
// silently ignore parsing error for the netmask
|
||||
model.IPv6PrefixLength = types.Int64Null()
|
||||
} else {
|
||||
ones, _ := netmask.Mask.Size()
|
||||
model.IPv6PrefixLength = types.Int64Value(int64(ones))
|
||||
}
|
||||
}
|
||||
model.IPv6Prefixes = prefixesV6TF
|
||||
}
|
||||
|
||||
if networkResp.Ipv6 == nil || networkResp.Ipv6.Gateway == nil {
|
||||
model.IPv6Gateway = types.StringNull()
|
||||
} else {
|
||||
model.IPv6Gateway = types.StringPointerValue(networkResp.Ipv6.GetGateway())
|
||||
}
|
||||
|
||||
model.RoutingTableID = types.StringNull()
|
||||
if networkResp.RoutingTableId != nil {
|
||||
model.RoutingTableID = types.StringValue(*networkResp.RoutingTableId)
|
||||
}
|
||||
|
||||
model.NetworkId = types.StringValue(networkId)
|
||||
model.Name = types.StringPointerValue(networkResp.Name)
|
||||
model.Labels = labels
|
||||
model.Routed = types.BoolPointerValue(networkResp.Routed)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
package v2network
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
|
||||
networkModel "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -19,26 +19,26 @@ const (
|
|||
func TestMapDataSourceFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
state networkModel.DataSourceModel
|
||||
input *iaasalpha.Network
|
||||
state DataSourceModel
|
||||
input *iaas.Network
|
||||
region string
|
||||
expected networkModel.DataSourceModel
|
||||
expected DataSourceModel
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"id_ok",
|
||||
networkModel.DataSourceModel{
|
||||
DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
},
|
||||
&iaasalpha.Network{
|
||||
&iaas.Network{
|
||||
Id: utils.Ptr("nid"),
|
||||
Ipv4: &iaasalpha.NetworkIPv4{
|
||||
Gateway: iaasalpha.NewNullableString(nil),
|
||||
Ipv4: &iaas.NetworkIPv4{
|
||||
Gateway: iaas.NewNullableString(nil),
|
||||
},
|
||||
},
|
||||
testRegion,
|
||||
networkModel.DataSourceModel{
|
||||
DataSourceModel{
|
||||
Id: types.StringValue("pid,region,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
|
|
@ -64,14 +64,14 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"values_ok",
|
||||
networkModel.DataSourceModel{
|
||||
DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
},
|
||||
&iaasalpha.Network{
|
||||
&iaas.Network{
|
||||
Id: utils.Ptr("nid"),
|
||||
Name: utils.Ptr("name"),
|
||||
Ipv4: &iaasalpha.NetworkIPv4{
|
||||
Ipv4: &iaas.NetworkIPv4{
|
||||
Nameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
|
|
@ -81,9 +81,9 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
"10.100.10.0/16",
|
||||
},
|
||||
PublicIp: utils.Ptr("publicIp"),
|
||||
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
},
|
||||
Ipv6: &iaasalpha.NetworkIPv6{
|
||||
Ipv6: &iaas.NetworkIPv6{
|
||||
Nameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
|
|
@ -92,7 +92,7 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
"fd12:3456:789a:1::/64",
|
||||
"fd12:3456:789a:2::/64",
|
||||
},
|
||||
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
|
|
@ -100,7 +100,7 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
Routed: utils.Ptr(true),
|
||||
},
|
||||
testRegion,
|
||||
networkModel.DataSourceModel{
|
||||
DataSourceModel{
|
||||
Id: types.StringValue("pid,region,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
|
|
@ -146,7 +146,7 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv4_nameservers_changed_outside_tf",
|
||||
networkModel.DataSourceModel{
|
||||
DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
|
|
@ -158,9 +158,9 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
types.StringValue("ns2"),
|
||||
}),
|
||||
},
|
||||
&iaasalpha.Network{
|
||||
&iaas.Network{
|
||||
Id: utils.Ptr("nid"),
|
||||
Ipv4: &iaasalpha.NetworkIPv4{
|
||||
Ipv4: &iaas.NetworkIPv4{
|
||||
Nameservers: &[]string{
|
||||
"ns2",
|
||||
"ns3",
|
||||
|
|
@ -168,7 +168,7 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
},
|
||||
},
|
||||
testRegion,
|
||||
networkModel.DataSourceModel{
|
||||
DataSourceModel{
|
||||
Id: types.StringValue("pid,region,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
|
|
@ -192,7 +192,7 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv6_nameservers_changed_outside_tf",
|
||||
networkModel.DataSourceModel{
|
||||
DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
|
|
@ -200,9 +200,9 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
types.StringValue("ns2"),
|
||||
}),
|
||||
},
|
||||
&iaasalpha.Network{
|
||||
&iaas.Network{
|
||||
Id: utils.Ptr("nid"),
|
||||
Ipv6: &iaasalpha.NetworkIPv6{
|
||||
Ipv6: &iaas.NetworkIPv6{
|
||||
Nameservers: &[]string{
|
||||
"ns2",
|
||||
"ns3",
|
||||
|
|
@ -210,7 +210,7 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
},
|
||||
},
|
||||
testRegion,
|
||||
networkModel.DataSourceModel{
|
||||
DataSourceModel{
|
||||
Id: types.StringValue("pid,region,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
|
|
@ -231,7 +231,7 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv4_prefixes_changed_outside_tf",
|
||||
networkModel.DataSourceModel{
|
||||
DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
|
|
@ -239,9 +239,9 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
types.StringValue("10.100.10.0/16"),
|
||||
}),
|
||||
},
|
||||
&iaasalpha.Network{
|
||||
&iaas.Network{
|
||||
Id: utils.Ptr("nid"),
|
||||
Ipv4: &iaasalpha.NetworkIPv4{
|
||||
Ipv4: &iaas.NetworkIPv4{
|
||||
Prefixes: &[]string{
|
||||
"10.100.20.0/16",
|
||||
"10.100.10.0/16",
|
||||
|
|
@ -249,7 +249,7 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
},
|
||||
},
|
||||
testRegion,
|
||||
networkModel.DataSourceModel{
|
||||
DataSourceModel{
|
||||
Id: types.StringValue("pid,region,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
|
|
@ -276,7 +276,7 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv6_prefixes_changed_outside_tf",
|
||||
networkModel.DataSourceModel{
|
||||
DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
|
|
@ -284,9 +284,9 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
types.StringValue("fd12:3456:789a:2::/64"),
|
||||
}),
|
||||
},
|
||||
&iaasalpha.Network{
|
||||
&iaas.Network{
|
||||
Id: utils.Ptr("nid"),
|
||||
Ipv6: &iaasalpha.NetworkIPv6{
|
||||
Ipv6: &iaas.NetworkIPv6{
|
||||
Prefixes: &[]string{
|
||||
"fd12:3456:789a:3::/64",
|
||||
"fd12:3456:789a:4::/64",
|
||||
|
|
@ -294,7 +294,7 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
},
|
||||
},
|
||||
testRegion,
|
||||
networkModel.DataSourceModel{
|
||||
DataSourceModel{
|
||||
Id: types.StringValue("pid,region,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
|
|
@ -318,15 +318,15 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv4_ipv6_gateway_nil",
|
||||
networkModel.DataSourceModel{
|
||||
DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
},
|
||||
&iaasalpha.Network{
|
||||
&iaas.Network{
|
||||
Id: utils.Ptr("nid"),
|
||||
},
|
||||
testRegion,
|
||||
networkModel.DataSourceModel{
|
||||
DataSourceModel{
|
||||
Id: types.StringValue("pid,region,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
|
|
@ -350,20 +350,20 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"response_nil_fail",
|
||||
networkModel.DataSourceModel{},
|
||||
DataSourceModel{},
|
||||
nil,
|
||||
testRegion,
|
||||
networkModel.DataSourceModel{},
|
||||
DataSourceModel{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"no_resource_id",
|
||||
networkModel.DataSourceModel{
|
||||
DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
},
|
||||
&iaasalpha.Network{},
|
||||
&iaas.Network{},
|
||||
testRegion,
|
||||
networkModel.DataSourceModel{},
|
||||
DataSourceModel{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
|
@ -3,9 +3,13 @@ package network
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
|
|
@ -18,16 +22,12 @@ import (
|
|||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas/wait"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/v1network"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/v2network"
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
iaasAlphaUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||
)
|
||||
|
|
@ -37,6 +37,7 @@ var (
|
|||
_ resource.Resource = &networkResource{}
|
||||
_ resource.ResourceWithConfigure = &networkResource{}
|
||||
_ resource.ResourceWithImportState = &networkResource{}
|
||||
_ resource.ResourceWithModifyPlan = &networkResource{}
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -46,6 +47,32 @@ const (
|
|||
"In cases where `ipv4_nameservers` are defined within the resource, the existing behavior will remain unchanged."
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
NetworkId types.String `tfsdk:"network_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
Nameservers types.List `tfsdk:"nameservers"`
|
||||
IPv4Gateway types.String `tfsdk:"ipv4_gateway"`
|
||||
IPv4Nameservers types.List `tfsdk:"ipv4_nameservers"`
|
||||
IPv4Prefix types.String `tfsdk:"ipv4_prefix"`
|
||||
IPv4PrefixLength types.Int64 `tfsdk:"ipv4_prefix_length"`
|
||||
Prefixes types.List `tfsdk:"prefixes"`
|
||||
IPv4Prefixes types.List `tfsdk:"ipv4_prefixes"`
|
||||
IPv6Gateway types.String `tfsdk:"ipv6_gateway"`
|
||||
IPv6Nameservers types.List `tfsdk:"ipv6_nameservers"`
|
||||
IPv6Prefix types.String `tfsdk:"ipv6_prefix"`
|
||||
IPv6PrefixLength types.Int64 `tfsdk:"ipv6_prefix_length"`
|
||||
IPv6Prefixes types.List `tfsdk:"ipv6_prefixes"`
|
||||
PublicIP types.String `tfsdk:"public_ip"`
|
||||
Labels types.Map `tfsdk:"labels"`
|
||||
Routed types.Bool `tfsdk:"routed"`
|
||||
NoIPv4Gateway types.Bool `tfsdk:"no_ipv4_gateway"`
|
||||
NoIPv6Gateway types.Bool `tfsdk:"no_ipv6_gateway"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
RoutingTableID types.String `tfsdk:"routing_table_id"`
|
||||
}
|
||||
|
||||
// NewNetworkResource is a helper function to simplify the provider implementation.
|
||||
func NewNetworkResource() resource.Resource {
|
||||
return &networkResource{}
|
||||
|
|
@ -53,11 +80,8 @@ func NewNetworkResource() resource.Resource {
|
|||
|
||||
// networkResource is the resource implementation.
|
||||
type networkResource struct {
|
||||
client *iaas.APIClient
|
||||
// alphaClient will be used in case the experimental flag "network" is set
|
||||
alphaClient *iaasalpha.APIClient
|
||||
isExperimental bool
|
||||
providerData core.ProviderData
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
|
|
@ -73,31 +97,18 @@ func (r *networkResource) Configure(ctx context.Context, req resource.ConfigureR
|
|||
return
|
||||
}
|
||||
|
||||
r.isExperimental = features.CheckExperimentEnabledWithoutError(ctx, &r.providerData, features.NetworkExperiment, "stackit_network", core.Resource, &resp.Diagnostics)
|
||||
apiClient := iaasUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if r.isExperimental {
|
||||
alphaApiClient := iaasAlphaUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
r.alphaClient = alphaApiClient
|
||||
} else {
|
||||
apiClient := iaasUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
r.client = apiClient
|
||||
}
|
||||
r.client = apiClient
|
||||
tflog.Info(ctx, "IaaS client configured")
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
func (r *networkResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
var configModel model.Model
|
||||
var configModel Model
|
||||
// skip initial empty configuration to avoid follow-up errors
|
||||
if req.Config.Raw.IsNull() {
|
||||
return
|
||||
|
|
@ -107,7 +118,7 @@ func (r *networkResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
|
|||
return
|
||||
}
|
||||
|
||||
var planModel model.Model
|
||||
var planModel Model
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
|
|
@ -118,10 +129,6 @@ func (r *networkResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
|
|||
addIPv4Warning(&resp.Diagnostics)
|
||||
}
|
||||
|
||||
// If the v1 api is used, it's not required to get the fallback region because it isn't used
|
||||
if !r.isExperimental {
|
||||
return
|
||||
}
|
||||
utils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
|
|
@ -134,7 +141,7 @@ func (r *networkResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
|
|||
}
|
||||
|
||||
func (r *networkResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
|
||||
var resourceModel model.Model
|
||||
var resourceModel Model
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &resourceModel)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
|
|
@ -143,14 +150,6 @@ func (r *networkResource) ValidateConfig(ctx context.Context, req resource.Valid
|
|||
if !resourceModel.Nameservers.IsUnknown() && !resourceModel.IPv4Nameservers.IsUnknown() && !resourceModel.Nameservers.IsNull() && !resourceModel.IPv4Nameservers.IsNull() {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring network", "You cannot provide both the `nameservers` and `ipv4_nameservers` fields simultaneously. Please remove the deprecated `nameservers` field, and use `ipv4_nameservers` to configure nameservers for IPv4.")
|
||||
}
|
||||
if !r.isExperimental {
|
||||
if !utils.IsUndefined(resourceModel.Region) {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring network", "Setting the `region` is not supported yet. This can only be configured when the experiments `network` is set.")
|
||||
}
|
||||
if !utils.IsUndefined(resourceModel.RoutingTableID) {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring network", "Setting the field `routing_table_id` is not supported yet. This can only be configured when the experiments `network` is set.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigValidators validates the resource configuration
|
||||
|
|
@ -192,7 +191,7 @@ func (r *networkResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
|||
Description: description,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`network_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`network_id`\".",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
|
|
@ -359,7 +358,7 @@ func (r *networkResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
|||
},
|
||||
},
|
||||
"routing_table_id": schema.StringAttribute{
|
||||
Description: "Can only be used when experimental \"network\" is set.\nThe ID of the routing table associated with the network.",
|
||||
Description: "The ID of the routing table associated with the network.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
|
|
@ -374,7 +373,7 @@ func (r *networkResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
|||
Optional: true,
|
||||
// must be computed to allow for storing the override value from the provider
|
||||
Computed: true,
|
||||
Description: "Can only be used when experimental \"network\" is set.\nThe resource region. If not defined, the provider region is used.",
|
||||
Description: "The resource region. If not defined, the provider region is used.",
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplaceIfConfigured(),
|
||||
},
|
||||
|
|
@ -386,59 +385,568 @@ func (r *networkResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
|||
// Create creates the resource and sets the initial Terraform state.
|
||||
func (r *networkResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var planModel model.Model
|
||||
diags := req.Plan.Get(ctx, &planModel)
|
||||
var model Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// When IPv4Nameserver is not set, print warning that the behavior of ipv4_nameservers will change
|
||||
if utils.IsUndefined(planModel.IPv4Nameservers) {
|
||||
if utils.IsUndefined(model.IPv4Nameservers) {
|
||||
addIPv4Warning(&resp.Diagnostics)
|
||||
}
|
||||
|
||||
if !r.isExperimental {
|
||||
v1network.Create(ctx, req, resp, r.client)
|
||||
} else {
|
||||
v2network.Create(ctx, req, resp, r.alphaClient)
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Generate API request body from model
|
||||
payload, err := toCreatePayload(ctx, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Creating API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Create new network
|
||||
|
||||
network, err := r.client.CreateNetwork(ctx, projectId, region).CreateNetworkPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
networkId := *network.Id
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
|
||||
network, err = wait.CreateNetworkWaitHandler(ctx, r.client, projectId, region, networkId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Network creation waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, network, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
// Set state to fully populated data
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Network created")
|
||||
}
|
||||
|
||||
// Read refreshes the Terraform state with the latest data.
|
||||
func (r *networkResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
if !r.isExperimental {
|
||||
v1network.Read(ctx, req, resp, r.client)
|
||||
} else {
|
||||
v2network.Read(ctx, req, resp, r.alphaClient, r.providerData)
|
||||
var model Model
|
||||
diags := req.State.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
networkId := model.NetworkId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
networkResp, err := r.client.GetNetwork(ctx, projectId, region, networkId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, networkResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
// Set refreshed state
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Network read")
|
||||
}
|
||||
|
||||
// Update updates the resource and sets the updated Terraform state on success.
|
||||
func (r *networkResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
if !r.isExperimental {
|
||||
v1network.Update(ctx, req, resp, r.client)
|
||||
} else {
|
||||
v2network.Update(ctx, req, resp, r.alphaClient)
|
||||
// Retrieve values from plan
|
||||
var model Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
networkId := model.NetworkId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Retrieve values from state
|
||||
var stateModel Model
|
||||
diags = req.State.Get(ctx, &stateModel)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Generate API request body from model
|
||||
payload, err := toUpdatePayload(ctx, &model, &stateModel)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Creating API payload: %v", err))
|
||||
return
|
||||
}
|
||||
// Update existing network
|
||||
err = r.client.PartialUpdateNetwork(ctx, projectId, region, networkId).PartialUpdateNetworkPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
waitResp, err := wait.UpdateNetworkWaitHandler(ctx, r.client, projectId, region, networkId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Network update waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = mapFields(ctx, waitResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Network updated")
|
||||
}
|
||||
|
||||
// Delete deletes the resource and removes the Terraform state on success.
|
||||
func (r *networkResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
if !r.isExperimental {
|
||||
v1network.Delete(ctx, req, resp, r.client)
|
||||
} else {
|
||||
v2network.Delete(ctx, req, resp, r.alphaClient)
|
||||
// Retrieve values from state
|
||||
var model Model
|
||||
diags := req.State.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
networkId := model.NetworkId.ValueString()
|
||||
region := model.Region.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Delete existing network
|
||||
err := r.client.DeleteNetwork(ctx, projectId, region, networkId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
_, err = wait.DeleteNetworkWaitHandler(ctx, r.client, projectId, region, networkId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network", fmt.Sprintf("Network deletion waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Network deleted")
|
||||
}
|
||||
|
||||
// ImportState imports a resource into the Terraform state on success.
|
||||
// The expected format of the resource import identifier is: project_id,network_id
|
||||
// The expected format of the resource import identifier is: project_id,region,network_id
|
||||
func (r *networkResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
if !r.isExperimental {
|
||||
v1network.ImportState(ctx, req, resp)
|
||||
} else {
|
||||
v2network.ImportState(ctx, req, resp)
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
|
||||
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics,
|
||||
"Error importing network",
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[network_id] Got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
region := idParts[1]
|
||||
networkId := idParts[2]
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), region)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_id"), networkId)...)
|
||||
tflog.Info(ctx, "Network state imported")
|
||||
}
|
||||
|
||||
func mapFields(ctx context.Context, networkResp *iaas.Network, model *Model, region string) error {
|
||||
if networkResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
if model == nil {
|
||||
return fmt.Errorf("model input is nil")
|
||||
}
|
||||
|
||||
var networkId string
|
||||
if model.NetworkId.ValueString() != "" {
|
||||
networkId = model.NetworkId.ValueString()
|
||||
} else if networkResp.Id != nil {
|
||||
networkId = *networkResp.Id
|
||||
} else {
|
||||
return fmt.Errorf("network id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, networkId)
|
||||
|
||||
labels, err := iaasUtils.MapLabels(ctx, networkResp.Labels, model.Labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// IPv4
|
||||
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.Nameservers == nil {
|
||||
model.Nameservers = types.ListNull(types.StringType)
|
||||
model.IPv4Nameservers = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respNameservers := *networkResp.Ipv4.Nameservers
|
||||
modelNameservers, err := utils.ListValuetoStringSlice(model.Nameservers)
|
||||
modelIPv4Nameservers, errIpv4 := utils.ListValuetoStringSlice(model.IPv4Nameservers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current network nameservers from model: %w", err)
|
||||
}
|
||||
if errIpv4 != nil {
|
||||
return fmt.Errorf("get current IPv4 network nameservers from model: %w", errIpv4)
|
||||
}
|
||||
|
||||
reconciledNameservers := utils.ReconcileStringSlices(modelNameservers, respNameservers)
|
||||
reconciledIPv4Nameservers := utils.ReconcileStringSlices(modelIPv4Nameservers, respNameservers)
|
||||
|
||||
nameserversTF, diags := types.ListValueFrom(ctx, types.StringType, reconciledNameservers)
|
||||
ipv4NameserversTF, ipv4Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv4Nameservers)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network nameservers: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if ipv4Diags.HasError() {
|
||||
return fmt.Errorf("map IPv4 network nameservers: %w", core.DiagsToError(ipv4Diags))
|
||||
}
|
||||
|
||||
model.Nameservers = nameserversTF
|
||||
model.IPv4Nameservers = ipv4NameserversTF
|
||||
}
|
||||
|
||||
model.IPv4PrefixLength = types.Int64Null()
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.Prefixes == nil {
|
||||
model.Prefixes = types.ListNull(types.StringType)
|
||||
model.IPv4Prefixes = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respPrefixes := *networkResp.Ipv4.Prefixes
|
||||
prefixesTF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixes)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network prefixes: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if len(respPrefixes) > 0 {
|
||||
model.IPv4Prefix = types.StringValue(respPrefixes[0])
|
||||
_, netmask, err := net.ParseCIDR(respPrefixes[0])
|
||||
if err != nil {
|
||||
tflog.Error(ctx, fmt.Sprintf("ipv4_prefix_length: %+v", err))
|
||||
// silently ignore parsing error for the netmask
|
||||
model.IPv4PrefixLength = types.Int64Null()
|
||||
} else {
|
||||
ones, _ := netmask.Mask.Size()
|
||||
model.IPv4PrefixLength = types.Int64Value(int64(ones))
|
||||
}
|
||||
}
|
||||
|
||||
model.Prefixes = prefixesTF
|
||||
model.IPv4Prefixes = prefixesTF
|
||||
}
|
||||
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.Gateway == nil {
|
||||
model.IPv4Gateway = types.StringNull()
|
||||
} else {
|
||||
model.IPv4Gateway = types.StringPointerValue(networkResp.Ipv4.GetGateway())
|
||||
}
|
||||
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.PublicIp == nil {
|
||||
model.PublicIP = types.StringNull()
|
||||
} else {
|
||||
model.PublicIP = types.StringPointerValue(networkResp.Ipv4.PublicIp)
|
||||
}
|
||||
|
||||
// IPv6
|
||||
|
||||
if networkResp.Ipv6 == nil || networkResp.Ipv6.Nameservers == nil {
|
||||
model.IPv6Nameservers = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respIPv6Nameservers := *networkResp.Ipv6.Nameservers
|
||||
modelIPv6Nameservers, errIpv6 := utils.ListValuetoStringSlice(model.IPv6Nameservers)
|
||||
if errIpv6 != nil {
|
||||
return fmt.Errorf("get current IPv6 network nameservers from model: %w", errIpv6)
|
||||
}
|
||||
|
||||
reconciledIPv6Nameservers := utils.ReconcileStringSlices(modelIPv6Nameservers, respIPv6Nameservers)
|
||||
|
||||
ipv6NameserversTF, ipv6Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv6Nameservers)
|
||||
if ipv6Diags.HasError() {
|
||||
return fmt.Errorf("map IPv6 network nameservers: %w", core.DiagsToError(ipv6Diags))
|
||||
}
|
||||
|
||||
model.IPv6Nameservers = ipv6NameserversTF
|
||||
}
|
||||
|
||||
model.IPv6PrefixLength = types.Int64Null()
|
||||
model.IPv6Prefix = types.StringNull()
|
||||
if networkResp.Ipv6 == nil || networkResp.Ipv6.Prefixes == nil {
|
||||
model.IPv6Prefixes = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respPrefixesV6 := *networkResp.Ipv6.Prefixes
|
||||
prefixesV6TF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixesV6)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network IPv6 prefixes: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if len(respPrefixesV6) > 0 {
|
||||
model.IPv6Prefix = types.StringValue(respPrefixesV6[0])
|
||||
_, netmask, err := net.ParseCIDR(respPrefixesV6[0])
|
||||
if err != nil {
|
||||
// silently ignore parsing error for the netmask
|
||||
model.IPv6PrefixLength = types.Int64Null()
|
||||
} else {
|
||||
ones, _ := netmask.Mask.Size()
|
||||
model.IPv6PrefixLength = types.Int64Value(int64(ones))
|
||||
}
|
||||
}
|
||||
model.IPv6Prefixes = prefixesV6TF
|
||||
}
|
||||
|
||||
if networkResp.Ipv6 == nil || networkResp.Ipv6.Gateway == nil {
|
||||
model.IPv6Gateway = types.StringNull()
|
||||
} else {
|
||||
model.IPv6Gateway = types.StringPointerValue(networkResp.Ipv6.GetGateway())
|
||||
}
|
||||
|
||||
model.RoutingTableID = types.StringPointerValue(networkResp.RoutingTableId)
|
||||
model.NetworkId = types.StringValue(networkId)
|
||||
model.Name = types.StringPointerValue(networkResp.Name)
|
||||
model.Labels = labels
|
||||
model.Routed = types.BoolPointerValue(networkResp.Routed)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateNetworkPayload, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
|
||||
var modelIPv6Nameservers []string
|
||||
// Is true when IPv6Nameservers is not null or unset
|
||||
if !utils.IsUndefined(model.IPv6Nameservers) {
|
||||
// If ipv6Nameservers is empty, modelIPv6Nameservers will be set to an empty slice.
|
||||
// empty slice != nil slice. Empty slice will result in an empty list in the payload []. Nil slice will result in a payload without the property set
|
||||
modelIPv6Nameservers = []string{}
|
||||
for _, ipv6ns := range model.IPv6Nameservers.Elements() {
|
||||
ipv6NameserverString, ok := ipv6ns.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
modelIPv6Nameservers = append(modelIPv6Nameservers, ipv6NameserverString.ValueString())
|
||||
}
|
||||
}
|
||||
|
||||
var ipv6Body *iaas.CreateNetworkIPv6
|
||||
if !utils.IsUndefined(model.IPv6PrefixLength) {
|
||||
ipv6Body = &iaas.CreateNetworkIPv6{
|
||||
CreateNetworkIPv6WithPrefixLength: &iaas.CreateNetworkIPv6WithPrefixLength{
|
||||
PrefixLength: conversion.Int64ValueToPointer(model.IPv6PrefixLength),
|
||||
},
|
||||
}
|
||||
|
||||
// IPv6 nameservers should only be set, if it contains any value. If the slice is nil, it should NOT be set.
|
||||
// Setting it to a nil slice would result in a payload, where nameservers is set to null in the json payload,
|
||||
// but it should actually be unset. Setting it to "null" will result in an error, because it's NOT nullable.
|
||||
if modelIPv6Nameservers != nil {
|
||||
ipv6Body.CreateNetworkIPv6WithPrefixLength.Nameservers = &modelIPv6Nameservers
|
||||
}
|
||||
} else if !utils.IsUndefined(model.IPv6Prefix) {
|
||||
var gateway *iaas.NullableString
|
||||
if model.NoIPv6Gateway.ValueBool() {
|
||||
gateway = iaas.NewNullableString(nil)
|
||||
} else if !(model.IPv6Gateway.IsUnknown() || model.IPv6Gateway.IsNull()) {
|
||||
gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv6Gateway))
|
||||
}
|
||||
|
||||
ipv6Body = &iaas.CreateNetworkIPv6{
|
||||
CreateNetworkIPv6WithPrefix: &iaas.CreateNetworkIPv6WithPrefix{
|
||||
Gateway: gateway,
|
||||
Prefix: conversion.StringValueToPointer(model.IPv6Prefix),
|
||||
},
|
||||
}
|
||||
|
||||
// IPv6 nameservers should only be set, if it contains any value. If the slice is nil, it should NOT be set.
|
||||
// Setting it to a nil slice would result in a payload, where nameservers is set to null in the json payload,
|
||||
// but it should actually be unset. Setting it to "null" will result in an error, because it's NOT nullable.
|
||||
if modelIPv6Nameservers != nil {
|
||||
ipv6Body.CreateNetworkIPv6WithPrefix.Nameservers = &modelIPv6Nameservers
|
||||
}
|
||||
}
|
||||
|
||||
modelIPv4Nameservers := []string{}
|
||||
var modelIPv4List []attr.Value
|
||||
|
||||
if !(model.IPv4Nameservers.IsNull() || model.IPv4Nameservers.IsUnknown()) {
|
||||
modelIPv4List = model.IPv4Nameservers.Elements()
|
||||
} else {
|
||||
modelIPv4List = model.Nameservers.Elements()
|
||||
}
|
||||
|
||||
for _, ipv4ns := range modelIPv4List {
|
||||
ipv4NameserverString, ok := ipv4ns.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
modelIPv4Nameservers = append(modelIPv4Nameservers, ipv4NameserverString.ValueString())
|
||||
}
|
||||
|
||||
var ipv4Body *iaas.CreateNetworkIPv4
|
||||
if !utils.IsUndefined(model.IPv4PrefixLength) {
|
||||
ipv4Body = &iaas.CreateNetworkIPv4{
|
||||
CreateNetworkIPv4WithPrefixLength: &iaas.CreateNetworkIPv4WithPrefixLength{
|
||||
Nameservers: &modelIPv4Nameservers,
|
||||
PrefixLength: conversion.Int64ValueToPointer(model.IPv4PrefixLength),
|
||||
},
|
||||
}
|
||||
} else if !utils.IsUndefined(model.IPv4Prefix) {
|
||||
var gateway *iaas.NullableString
|
||||
if model.NoIPv4Gateway.ValueBool() {
|
||||
gateway = iaas.NewNullableString(nil)
|
||||
} else if !(model.IPv4Gateway.IsUnknown() || model.IPv4Gateway.IsNull()) {
|
||||
gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv4Gateway))
|
||||
}
|
||||
|
||||
ipv4Body = &iaas.CreateNetworkIPv4{
|
||||
CreateNetworkIPv4WithPrefix: &iaas.CreateNetworkIPv4WithPrefix{
|
||||
Nameservers: &modelIPv4Nameservers,
|
||||
Prefix: conversion.StringValueToPointer(model.IPv4Prefix),
|
||||
Gateway: gateway,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
labels, err := conversion.ToStringInterfaceMap(ctx, model.Labels)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting to Go map: %w", err)
|
||||
}
|
||||
|
||||
payload := iaas.CreateNetworkPayload{
|
||||
Name: conversion.StringValueToPointer(model.Name),
|
||||
Labels: &labels,
|
||||
Routed: conversion.BoolValueToPointer(model.Routed),
|
||||
Ipv4: ipv4Body,
|
||||
Ipv6: ipv6Body,
|
||||
RoutingTableId: conversion.StringValueToPointer(model.RoutingTableID),
|
||||
}
|
||||
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func toUpdatePayload(ctx context.Context, model, stateModel *Model) (*iaas.PartialUpdateNetworkPayload, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
|
||||
var modelIPv6Nameservers []string
|
||||
// Is true when IPv6Nameservers is not null or unset
|
||||
if !utils.IsUndefined(model.IPv6Nameservers) {
|
||||
// If ipv6Nameservers is empty, modelIPv6Nameservers will be set to an empty slice.
|
||||
// empty slice != nil slice. Empty slice will result in an empty list in the payload []. Nil slice will result in a payload without the property set
|
||||
modelIPv6Nameservers = []string{}
|
||||
for _, ipv6ns := range model.IPv6Nameservers.Elements() {
|
||||
ipv6NameserverString, ok := ipv6ns.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
modelIPv6Nameservers = append(modelIPv6Nameservers, ipv6NameserverString.ValueString())
|
||||
}
|
||||
}
|
||||
|
||||
var ipv6Body *iaas.UpdateNetworkIPv6Body
|
||||
if modelIPv6Nameservers != nil || !utils.IsUndefined(model.NoIPv6Gateway) || !utils.IsUndefined(model.IPv6Gateway) {
|
||||
ipv6Body = &iaas.UpdateNetworkIPv6Body{}
|
||||
// IPv6 nameservers should only be set, if it contains any value. If the slice is nil, it should NOT be set.
|
||||
// Setting it to a nil slice would result in a payload, where nameservers is set to null in the json payload,
|
||||
// but it should actually be unset. Setting it to "null" will result in an error, because it's NOT nullable.
|
||||
if modelIPv6Nameservers != nil {
|
||||
ipv6Body.Nameservers = &modelIPv6Nameservers
|
||||
}
|
||||
|
||||
if model.NoIPv6Gateway.ValueBool() {
|
||||
ipv6Body.Gateway = iaas.NewNullableString(nil)
|
||||
} else if !(model.IPv6Gateway.IsUnknown() || model.IPv6Gateway.IsNull()) {
|
||||
ipv6Body.Gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv6Gateway))
|
||||
}
|
||||
}
|
||||
|
||||
modelIPv4Nameservers := []string{}
|
||||
var modelIPv4List []attr.Value
|
||||
|
||||
if !(model.IPv4Nameservers.IsNull() || model.IPv4Nameservers.IsUnknown()) {
|
||||
modelIPv4List = model.IPv4Nameservers.Elements()
|
||||
} else {
|
||||
modelIPv4List = model.Nameservers.Elements()
|
||||
}
|
||||
for _, ipv4ns := range modelIPv4List {
|
||||
ipv4NameserverString, ok := ipv4ns.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
modelIPv4Nameservers = append(modelIPv4Nameservers, ipv4NameserverString.ValueString())
|
||||
}
|
||||
|
||||
var ipv4Body *iaas.UpdateNetworkIPv4Body
|
||||
if !model.IPv4Nameservers.IsNull() || !model.Nameservers.IsNull() {
|
||||
ipv4Body = &iaas.UpdateNetworkIPv4Body{
|
||||
Nameservers: &modelIPv4Nameservers,
|
||||
}
|
||||
|
||||
if model.NoIPv4Gateway.ValueBool() {
|
||||
ipv4Body.Gateway = iaas.NewNullableString(nil)
|
||||
} else if !(model.IPv4Gateway.IsUnknown() || model.IPv4Gateway.IsNull()) {
|
||||
ipv4Body.Gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv4Gateway))
|
||||
}
|
||||
}
|
||||
currentLabels := stateModel.Labels
|
||||
labels, err := conversion.ToJSONMapPartialUpdatePayload(ctx, currentLabels, model.Labels)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting to Go map: %w", err)
|
||||
}
|
||||
|
||||
payload := iaas.PartialUpdateNetworkPayload{
|
||||
Name: conversion.StringValueToPointer(model.Name),
|
||||
Labels: &labels,
|
||||
Ipv4: ipv4Body,
|
||||
Ipv6: ipv6Body,
|
||||
RoutingTableId: conversion.StringValueToPointer(model.RoutingTableID),
|
||||
}
|
||||
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func addIPv4Warning(diags *diag.Diagnostics) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package v2network
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -8,34 +8,33 @@ import (
|
|||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
|
||||
)
|
||||
|
||||
func TestMapFields(t *testing.T) {
|
||||
const testRegion = "region"
|
||||
tests := []struct {
|
||||
description string
|
||||
state model.Model
|
||||
input *iaasalpha.Network
|
||||
state Model
|
||||
input *iaas.Network
|
||||
region string
|
||||
expected model.Model
|
||||
expected Model
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"id_ok",
|
||||
model.Model{
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
},
|
||||
&iaasalpha.Network{
|
||||
&iaas.Network{
|
||||
Id: utils.Ptr("nid"),
|
||||
Ipv4: &iaasalpha.NetworkIPv4{
|
||||
Gateway: iaasalpha.NewNullableString(nil),
|
||||
Ipv4: &iaas.NetworkIPv4{
|
||||
Gateway: iaas.NewNullableString(nil),
|
||||
},
|
||||
},
|
||||
testRegion,
|
||||
model.Model{
|
||||
Model{
|
||||
Id: types.StringValue("pid,region,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
|
|
@ -61,14 +60,14 @@ func TestMapFields(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"values_ok",
|
||||
model.Model{
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
},
|
||||
&iaasalpha.Network{
|
||||
&iaas.Network{
|
||||
Id: utils.Ptr("nid"),
|
||||
Name: utils.Ptr("name"),
|
||||
Ipv4: &iaasalpha.NetworkIPv4{
|
||||
Ipv4: &iaas.NetworkIPv4{
|
||||
Nameservers: utils.Ptr([]string{"ns1", "ns2"}),
|
||||
Prefixes: utils.Ptr(
|
||||
[]string{
|
||||
|
|
@ -77,15 +76,15 @@ func TestMapFields(t *testing.T) {
|
|||
},
|
||||
),
|
||||
PublicIp: utils.Ptr("publicIp"),
|
||||
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
},
|
||||
Ipv6: &iaasalpha.NetworkIPv6{
|
||||
Ipv6: &iaas.NetworkIPv6{
|
||||
Nameservers: utils.Ptr([]string{"ns1", "ns2"}),
|
||||
Prefixes: utils.Ptr([]string{
|
||||
"fd12:3456:789a:1::/64",
|
||||
"fd12:3456:789b:1::/64",
|
||||
}),
|
||||
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
|
|
@ -93,7 +92,7 @@ func TestMapFields(t *testing.T) {
|
|||
Routed: utils.Ptr(true),
|
||||
},
|
||||
testRegion,
|
||||
model.Model{
|
||||
Model{
|
||||
Id: types.StringValue("pid,region,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
|
|
@ -139,7 +138,7 @@ func TestMapFields(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv4_nameservers_changed_outside_tf",
|
||||
model.Model{
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
|
|
@ -151,9 +150,9 @@ func TestMapFields(t *testing.T) {
|
|||
types.StringValue("ns2"),
|
||||
}),
|
||||
},
|
||||
&iaasalpha.Network{
|
||||
&iaas.Network{
|
||||
Id: utils.Ptr("nid"),
|
||||
Ipv4: &iaasalpha.NetworkIPv4{
|
||||
Ipv4: &iaas.NetworkIPv4{
|
||||
Nameservers: utils.Ptr([]string{
|
||||
"ns2",
|
||||
"ns3",
|
||||
|
|
@ -161,7 +160,7 @@ func TestMapFields(t *testing.T) {
|
|||
},
|
||||
},
|
||||
testRegion,
|
||||
model.Model{
|
||||
Model{
|
||||
Id: types.StringValue("pid,region,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
|
|
@ -185,7 +184,7 @@ func TestMapFields(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv6_nameservers_changed_outside_tf",
|
||||
model.Model{
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
|
|
@ -193,9 +192,9 @@ func TestMapFields(t *testing.T) {
|
|||
types.StringValue("ns2"),
|
||||
}),
|
||||
},
|
||||
&iaasalpha.Network{
|
||||
&iaas.Network{
|
||||
Id: utils.Ptr("nid"),
|
||||
Ipv6: &iaasalpha.NetworkIPv6{
|
||||
Ipv6: &iaas.NetworkIPv6{
|
||||
Nameservers: utils.Ptr([]string{
|
||||
"ns2",
|
||||
"ns3",
|
||||
|
|
@ -203,7 +202,7 @@ func TestMapFields(t *testing.T) {
|
|||
},
|
||||
},
|
||||
testRegion,
|
||||
model.Model{
|
||||
Model{
|
||||
Id: types.StringValue("pid,region,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
|
|
@ -224,7 +223,7 @@ func TestMapFields(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv4_prefixes_changed_outside_tf",
|
||||
model.Model{
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
|
|
@ -232,9 +231,9 @@ func TestMapFields(t *testing.T) {
|
|||
types.StringValue("10.100.10.0/24"),
|
||||
}),
|
||||
},
|
||||
&iaasalpha.Network{
|
||||
&iaas.Network{
|
||||
Id: utils.Ptr("nid"),
|
||||
Ipv4: &iaasalpha.NetworkIPv4{
|
||||
Ipv4: &iaas.NetworkIPv4{
|
||||
Prefixes: utils.Ptr(
|
||||
[]string{
|
||||
"192.168.54.0/24",
|
||||
|
|
@ -244,7 +243,7 @@ func TestMapFields(t *testing.T) {
|
|||
},
|
||||
},
|
||||
testRegion,
|
||||
model.Model{
|
||||
Model{
|
||||
Id: types.StringValue("pid,region,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
|
|
@ -271,7 +270,7 @@ func TestMapFields(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv6_prefixes_changed_outside_tf",
|
||||
model.Model{
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
|
|
@ -279,9 +278,9 @@ func TestMapFields(t *testing.T) {
|
|||
types.StringValue("fd12:3456:789a:2::/64"),
|
||||
}),
|
||||
},
|
||||
&iaasalpha.Network{
|
||||
&iaas.Network{
|
||||
Id: utils.Ptr("nid"),
|
||||
Ipv6: &iaasalpha.NetworkIPv6{
|
||||
Ipv6: &iaas.NetworkIPv6{
|
||||
Prefixes: utils.Ptr(
|
||||
[]string{
|
||||
"fd12:3456:789a:1::/64",
|
||||
|
|
@ -291,7 +290,7 @@ func TestMapFields(t *testing.T) {
|
|||
},
|
||||
},
|
||||
testRegion,
|
||||
model.Model{
|
||||
Model{
|
||||
Id: types.StringValue("pid,region,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
|
|
@ -315,15 +314,15 @@ func TestMapFields(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv4_ipv6_gateway_nil",
|
||||
model.Model{
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
},
|
||||
&iaasalpha.Network{
|
||||
&iaas.Network{
|
||||
Id: utils.Ptr("nid"),
|
||||
},
|
||||
testRegion,
|
||||
model.Model{
|
||||
Model{
|
||||
Id: types.StringValue("pid,region,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
|
|
@ -347,20 +346,20 @@ func TestMapFields(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"response_nil_fail",
|
||||
model.Model{},
|
||||
Model{},
|
||||
nil,
|
||||
testRegion,
|
||||
model.Model{},
|
||||
Model{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"no_resource_id",
|
||||
model.Model{
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
},
|
||||
&iaasalpha.Network{},
|
||||
&iaas.Network{},
|
||||
testRegion,
|
||||
model.Model{},
|
||||
Model{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
|
@ -386,13 +385,13 @@ func TestMapFields(t *testing.T) {
|
|||
func TestToCreatePayload(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input *model.Model
|
||||
expected *iaasalpha.CreateNetworkPayload
|
||||
input *Model
|
||||
expected *iaas.CreateNetworkPayload
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"default_ok",
|
||||
&model.Model{
|
||||
&Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
|
|
@ -405,11 +404,11 @@ func TestToCreatePayload(t *testing.T) {
|
|||
IPv4Gateway: types.StringValue("gateway"),
|
||||
IPv4Prefix: types.StringValue("prefix"),
|
||||
},
|
||||
&iaasalpha.CreateNetworkPayload{
|
||||
&iaas.CreateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
Ipv4: &iaasalpha.CreateNetworkIPv4{
|
||||
CreateNetworkIPv4WithPrefix: &iaasalpha.CreateNetworkIPv4WithPrefix{
|
||||
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
|
||||
Ipv4: &iaas.CreateNetworkIPv4{
|
||||
CreateNetworkIPv4WithPrefix: &iaas.CreateNetworkIPv4WithPrefix{
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
Nameservers: utils.Ptr([]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
|
|
@ -426,7 +425,7 @@ func TestToCreatePayload(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv4_nameservers_okay",
|
||||
&model.Model{
|
||||
&Model{
|
||||
Name: types.StringValue("name"),
|
||||
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
|
|
@ -439,11 +438,11 @@ func TestToCreatePayload(t *testing.T) {
|
|||
IPv4Gateway: types.StringValue("gateway"),
|
||||
IPv4Prefix: types.StringValue("prefix"),
|
||||
},
|
||||
&iaasalpha.CreateNetworkPayload{
|
||||
&iaas.CreateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
Ipv4: &iaasalpha.CreateNetworkIPv4{
|
||||
CreateNetworkIPv4WithPrefix: &iaasalpha.CreateNetworkIPv4WithPrefix{
|
||||
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
|
||||
Ipv4: &iaas.CreateNetworkIPv4{
|
||||
CreateNetworkIPv4WithPrefix: &iaas.CreateNetworkIPv4WithPrefix{
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
Nameservers: utils.Ptr([]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
|
|
@ -460,7 +459,7 @@ func TestToCreatePayload(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv6_default_ok",
|
||||
&model.Model{
|
||||
&Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
|
|
@ -473,11 +472,11 @@ func TestToCreatePayload(t *testing.T) {
|
|||
IPv6Gateway: types.StringValue("gateway"),
|
||||
IPv6Prefix: types.StringValue("prefix"),
|
||||
},
|
||||
&iaasalpha.CreateNetworkPayload{
|
||||
&iaas.CreateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
Ipv6: &iaasalpha.CreateNetworkIPv6{
|
||||
CreateNetworkIPv6WithPrefix: &iaasalpha.CreateNetworkIPv6WithPrefix{
|
||||
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
|
||||
Ipv6: &iaas.CreateNetworkIPv6{
|
||||
CreateNetworkIPv6WithPrefix: &iaas.CreateNetworkIPv6WithPrefix{
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
Nameservers: utils.Ptr([]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
|
|
@ -494,7 +493,7 @@ func TestToCreatePayload(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv6_nameserver_null",
|
||||
&model.Model{
|
||||
&Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv6Nameservers: types.ListNull(types.StringType),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
|
|
@ -504,12 +503,12 @@ func TestToCreatePayload(t *testing.T) {
|
|||
IPv6Gateway: types.StringValue("gateway"),
|
||||
IPv6Prefix: types.StringValue("prefix"),
|
||||
},
|
||||
&iaasalpha.CreateNetworkPayload{
|
||||
&iaas.CreateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
Ipv6: &iaasalpha.CreateNetworkIPv6{
|
||||
CreateNetworkIPv6WithPrefix: &iaasalpha.CreateNetworkIPv6WithPrefix{
|
||||
Ipv6: &iaas.CreateNetworkIPv6{
|
||||
CreateNetworkIPv6WithPrefix: &iaas.CreateNetworkIPv6WithPrefix{
|
||||
Nameservers: nil,
|
||||
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
Prefix: utils.Ptr("prefix"),
|
||||
},
|
||||
},
|
||||
|
|
@ -522,7 +521,7 @@ func TestToCreatePayload(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv6_nameserver_empty_list",
|
||||
&model.Model{
|
||||
&Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{}),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
|
|
@ -532,12 +531,12 @@ func TestToCreatePayload(t *testing.T) {
|
|||
IPv6Gateway: types.StringValue("gateway"),
|
||||
IPv6Prefix: types.StringValue("prefix"),
|
||||
},
|
||||
&iaasalpha.CreateNetworkPayload{
|
||||
&iaas.CreateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
Ipv6: &iaasalpha.CreateNetworkIPv6{
|
||||
CreateNetworkIPv6WithPrefix: &iaasalpha.CreateNetworkIPv6WithPrefix{
|
||||
Ipv6: &iaas.CreateNetworkIPv6{
|
||||
CreateNetworkIPv6WithPrefix: &iaas.CreateNetworkIPv6WithPrefix{
|
||||
Nameservers: utils.Ptr([]string{}),
|
||||
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
Prefix: utils.Ptr("prefix"),
|
||||
},
|
||||
},
|
||||
|
|
@ -559,7 +558,7 @@ func TestToCreatePayload(t *testing.T) {
|
|||
t.Fatalf("Should not have failed: %v", err)
|
||||
}
|
||||
if tt.isValid {
|
||||
diff := cmp.Diff(output, tt.expected, cmp.AllowUnexported(iaasalpha.NullableString{}))
|
||||
diff := cmp.Diff(output, tt.expected, cmp.AllowUnexported(iaas.NullableString{}))
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
|
|
@ -571,14 +570,14 @@ func TestToCreatePayload(t *testing.T) {
|
|||
func TestToUpdatePayload(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input *model.Model
|
||||
state model.Model
|
||||
expected *iaasalpha.PartialUpdateNetworkPayload
|
||||
input *Model
|
||||
state Model
|
||||
expected *iaas.PartialUpdateNetworkPayload
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"default_ok",
|
||||
&model.Model{
|
||||
&Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
|
|
@ -590,15 +589,15 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
Routed: types.BoolValue(true),
|
||||
IPv4Gateway: types.StringValue("gateway"),
|
||||
},
|
||||
model.Model{
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
&iaasalpha.PartialUpdateNetworkPayload{
|
||||
&iaas.PartialUpdateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
Ipv4: &iaasalpha.UpdateNetworkIPv4Body{
|
||||
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
|
||||
Ipv4: &iaas.UpdateNetworkIPv4Body{
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
Nameservers: utils.Ptr([]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
|
|
@ -612,7 +611,7 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv4_nameservers_okay",
|
||||
&model.Model{
|
||||
&Model{
|
||||
Name: types.StringValue("name"),
|
||||
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
|
|
@ -624,15 +623,15 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
Routed: types.BoolValue(true),
|
||||
IPv4Gateway: types.StringValue("gateway"),
|
||||
},
|
||||
model.Model{
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
&iaasalpha.PartialUpdateNetworkPayload{
|
||||
&iaas.PartialUpdateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
Ipv4: &iaasalpha.UpdateNetworkIPv4Body{
|
||||
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
|
||||
Ipv4: &iaas.UpdateNetworkIPv4Body{
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
Nameservers: utils.Ptr([]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
|
|
@ -646,7 +645,7 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv4_gateway_nil",
|
||||
&model.Model{
|
||||
&Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
|
|
@ -657,14 +656,14 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
}),
|
||||
Routed: types.BoolValue(true),
|
||||
},
|
||||
model.Model{
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
&iaasalpha.PartialUpdateNetworkPayload{
|
||||
&iaas.PartialUpdateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
Ipv4: &iaasalpha.UpdateNetworkIPv4Body{
|
||||
Ipv4: &iaas.UpdateNetworkIPv4Body{
|
||||
Nameservers: utils.Ptr([]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
|
|
@ -678,7 +677,7 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv6_default_ok",
|
||||
&model.Model{
|
||||
&Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
|
|
@ -690,15 +689,15 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
Routed: types.BoolValue(true),
|
||||
IPv6Gateway: types.StringValue("gateway"),
|
||||
},
|
||||
model.Model{
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
&iaasalpha.PartialUpdateNetworkPayload{
|
||||
&iaas.PartialUpdateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
Ipv6: &iaasalpha.UpdateNetworkIPv6Body{
|
||||
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
|
||||
Ipv6: &iaas.UpdateNetworkIPv6Body{
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
Nameservers: utils.Ptr([]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
|
|
@ -712,7 +711,7 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv6_gateway_nil",
|
||||
&model.Model{
|
||||
&Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
|
|
@ -723,14 +722,14 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
}),
|
||||
Routed: types.BoolValue(true),
|
||||
},
|
||||
model.Model{
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
&iaasalpha.PartialUpdateNetworkPayload{
|
||||
&iaas.PartialUpdateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
Ipv6: &iaasalpha.UpdateNetworkIPv6Body{
|
||||
Ipv6: &iaas.UpdateNetworkIPv6Body{
|
||||
Nameservers: utils.Ptr([]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
|
|
@ -744,7 +743,7 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv6_nameserver_null",
|
||||
&model.Model{
|
||||
&Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv6Nameservers: types.ListNull(types.StringType),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
|
|
@ -753,16 +752,16 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
Routed: types.BoolValue(true),
|
||||
IPv6Gateway: types.StringValue("gateway"),
|
||||
},
|
||||
model.Model{
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
&iaasalpha.PartialUpdateNetworkPayload{
|
||||
&iaas.PartialUpdateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
Ipv6: &iaasalpha.UpdateNetworkIPv6Body{
|
||||
Ipv6: &iaas.UpdateNetworkIPv6Body{
|
||||
Nameservers: nil,
|
||||
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
|
|
@ -772,7 +771,7 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ipv6_nameserver_empty_list",
|
||||
&model.Model{
|
||||
&Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{}),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
|
|
@ -781,16 +780,16 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
Routed: types.BoolValue(true),
|
||||
IPv6Gateway: types.StringValue("gateway"),
|
||||
},
|
||||
model.Model{
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
&iaasalpha.PartialUpdateNetworkPayload{
|
||||
&iaas.PartialUpdateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
Ipv6: &iaasalpha.UpdateNetworkIPv6Body{
|
||||
Ipv6: &iaas.UpdateNetworkIPv6Body{
|
||||
Nameservers: utils.Ptr([]string{}),
|
||||
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
|
|
@ -809,7 +808,7 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
t.Fatalf("Should not have failed: %v", err)
|
||||
}
|
||||
if tt.isValid {
|
||||
diff := cmp.Diff(output, tt.expected, cmp.AllowUnexported(iaasalpha.NullableString{}))
|
||||
diff := cmp.Diff(output, tt.expected, cmp.AllowUnexported(iaas.NullableString{}))
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
package model
|
||||
|
||||
import "github.com/hashicorp/terraform-plugin-framework/types"
|
||||
|
||||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
NetworkId types.String `tfsdk:"network_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
Nameservers types.List `tfsdk:"nameservers"`
|
||||
IPv4Gateway types.String `tfsdk:"ipv4_gateway"`
|
||||
IPv4Nameservers types.List `tfsdk:"ipv4_nameservers"`
|
||||
IPv4Prefix types.String `tfsdk:"ipv4_prefix"`
|
||||
IPv4PrefixLength types.Int64 `tfsdk:"ipv4_prefix_length"`
|
||||
Prefixes types.List `tfsdk:"prefixes"`
|
||||
IPv4Prefixes types.List `tfsdk:"ipv4_prefixes"`
|
||||
IPv6Gateway types.String `tfsdk:"ipv6_gateway"`
|
||||
IPv6Nameservers types.List `tfsdk:"ipv6_nameservers"`
|
||||
IPv6Prefix types.String `tfsdk:"ipv6_prefix"`
|
||||
IPv6PrefixLength types.Int64 `tfsdk:"ipv6_prefix_length"`
|
||||
IPv6Prefixes types.List `tfsdk:"ipv6_prefixes"`
|
||||
PublicIP types.String `tfsdk:"public_ip"`
|
||||
Labels types.Map `tfsdk:"labels"`
|
||||
Routed types.Bool `tfsdk:"routed"`
|
||||
NoIPv4Gateway types.Bool `tfsdk:"no_ipv4_gateway"`
|
||||
NoIPv6Gateway types.Bool `tfsdk:"no_ipv6_gateway"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
RoutingTableID types.String `tfsdk:"routing_table_id"`
|
||||
}
|
||||
|
||||
type DataSourceModel struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
NetworkId types.String `tfsdk:"network_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
Nameservers types.List `tfsdk:"nameservers"`
|
||||
IPv4Gateway types.String `tfsdk:"ipv4_gateway"`
|
||||
IPv4Nameservers types.List `tfsdk:"ipv4_nameservers"`
|
||||
IPv4Prefix types.String `tfsdk:"ipv4_prefix"`
|
||||
IPv4PrefixLength types.Int64 `tfsdk:"ipv4_prefix_length"`
|
||||
Prefixes types.List `tfsdk:"prefixes"`
|
||||
IPv4Prefixes types.List `tfsdk:"ipv4_prefixes"`
|
||||
IPv6Gateway types.String `tfsdk:"ipv6_gateway"`
|
||||
IPv6Nameservers types.List `tfsdk:"ipv6_nameservers"`
|
||||
IPv6Prefix types.String `tfsdk:"ipv6_prefix"`
|
||||
IPv6PrefixLength types.Int64 `tfsdk:"ipv6_prefix_length"`
|
||||
IPv6Prefixes types.List `tfsdk:"ipv6_prefixes"`
|
||||
PublicIP types.String `tfsdk:"public_ip"`
|
||||
Labels types.Map `tfsdk:"labels"`
|
||||
Routed types.Bool `tfsdk:"routed"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
RoutingTableID types.String `tfsdk:"routing_table_id"`
|
||||
}
|
||||
|
|
@ -1,208 +0,0 @@
|
|||
package v1network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
networkModel "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
)
|
||||
|
||||
func DatasourceRead(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse, client *iaas.APIClient) { // nolint:gocritic // function signature required by Terraform
|
||||
var model networkModel.DataSourceModel
|
||||
diags := req.Config.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
networkId := model.NetworkId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
|
||||
networkResp, err := client.GetNetwork(ctx, projectId, networkId).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
&resp.Diagnostics,
|
||||
err,
|
||||
"Reading network",
|
||||
fmt.Sprintf("Network with ID %q does not exist in project %q.", networkId, projectId),
|
||||
map[int]string{
|
||||
http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId),
|
||||
},
|
||||
)
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapDataSourceFields(ctx, networkResp, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Network read")
|
||||
}
|
||||
|
||||
func mapDataSourceFields(ctx context.Context, networkResp *iaas.Network, model *networkModel.DataSourceModel) error {
|
||||
if networkResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
if model == nil {
|
||||
return fmt.Errorf("model input is nil")
|
||||
}
|
||||
|
||||
var networkId string
|
||||
if model.NetworkId.ValueString() != "" {
|
||||
networkId = model.NetworkId.ValueString()
|
||||
} else if networkResp.NetworkId != nil {
|
||||
networkId = *networkResp.NetworkId
|
||||
} else {
|
||||
return fmt.Errorf("network id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), networkId)
|
||||
|
||||
labels, err := iaasUtils.MapLabels(ctx, networkResp.Labels, model.Labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// IPv4
|
||||
|
||||
if networkResp.Nameservers == nil {
|
||||
model.Nameservers = types.ListNull(types.StringType)
|
||||
model.IPv4Nameservers = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respNameservers := *networkResp.Nameservers
|
||||
modelNameservers, err := utils.ListValuetoStringSlice(model.Nameservers)
|
||||
modelIPv4Nameservers, errIpv4 := utils.ListValuetoStringSlice(model.IPv4Nameservers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current network nameservers from model: %w", err)
|
||||
}
|
||||
if errIpv4 != nil {
|
||||
return fmt.Errorf("get current IPv4 network nameservers from model: %w", errIpv4)
|
||||
}
|
||||
|
||||
reconciledNameservers := utils.ReconcileStringSlices(modelNameservers, respNameservers)
|
||||
reconciledIPv4Nameservers := utils.ReconcileStringSlices(modelIPv4Nameservers, respNameservers)
|
||||
|
||||
nameserversTF, diags := types.ListValueFrom(ctx, types.StringType, reconciledNameservers)
|
||||
ipv4NameserversTF, ipv4Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv4Nameservers)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network nameservers: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if ipv4Diags.HasError() {
|
||||
return fmt.Errorf("map IPv4 network nameservers: %w", core.DiagsToError(ipv4Diags))
|
||||
}
|
||||
|
||||
model.Nameservers = nameserversTF
|
||||
model.IPv4Nameservers = ipv4NameserversTF
|
||||
}
|
||||
|
||||
if networkResp.Prefixes == nil {
|
||||
model.Prefixes = types.ListNull(types.StringType)
|
||||
model.IPv4Prefixes = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respPrefixes := *networkResp.Prefixes
|
||||
prefixesTF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixes)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network prefixes: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if len(respPrefixes) > 0 {
|
||||
model.IPv4Prefix = types.StringValue(respPrefixes[0])
|
||||
_, netmask, err := net.ParseCIDR(respPrefixes[0])
|
||||
if err != nil {
|
||||
// silently ignore parsing error for the netmask
|
||||
model.IPv4PrefixLength = types.Int64Null()
|
||||
} else {
|
||||
ones, _ := netmask.Mask.Size()
|
||||
model.IPv4PrefixLength = types.Int64Value(int64(ones))
|
||||
}
|
||||
}
|
||||
|
||||
model.Prefixes = prefixesTF
|
||||
model.IPv4Prefixes = prefixesTF
|
||||
}
|
||||
|
||||
model.IPv4Gateway = types.StringNull()
|
||||
if networkResp.Gateway != nil {
|
||||
model.IPv4Gateway = types.StringPointerValue(networkResp.GetGateway())
|
||||
}
|
||||
|
||||
// IPv6
|
||||
|
||||
if networkResp.NameserversV6 == nil {
|
||||
model.IPv6Nameservers = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respIPv6Nameservers := *networkResp.NameserversV6
|
||||
modelIPv6Nameservers, errIpv6 := utils.ListValuetoStringSlice(model.IPv6Nameservers)
|
||||
if errIpv6 != nil {
|
||||
return fmt.Errorf("get current IPv6 network nameservers from model: %w", errIpv6)
|
||||
}
|
||||
|
||||
reconciledIPv6Nameservers := utils.ReconcileStringSlices(modelIPv6Nameservers, respIPv6Nameservers)
|
||||
|
||||
ipv6NameserversTF, ipv6Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv6Nameservers)
|
||||
if ipv6Diags.HasError() {
|
||||
return fmt.Errorf("map IPv6 network nameservers: %w", core.DiagsToError(ipv6Diags))
|
||||
}
|
||||
|
||||
model.IPv6Nameservers = ipv6NameserversTF
|
||||
}
|
||||
|
||||
if networkResp.PrefixesV6 == nil {
|
||||
model.IPv6Prefixes = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respPrefixesV6 := *networkResp.PrefixesV6
|
||||
prefixesV6TF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixesV6)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network IPv6 prefixes: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if len(respPrefixesV6) > 0 {
|
||||
model.IPv6Prefix = types.StringValue(respPrefixesV6[0])
|
||||
_, netmask, err := net.ParseCIDR(respPrefixesV6[0])
|
||||
if err != nil {
|
||||
// silently ignore parsing error for the netmask
|
||||
model.IPv6PrefixLength = types.Int64Null()
|
||||
} else {
|
||||
ones, _ := netmask.Mask.Size()
|
||||
model.IPv6PrefixLength = types.Int64Value(int64(ones))
|
||||
}
|
||||
}
|
||||
model.IPv6Prefixes = prefixesV6TF
|
||||
}
|
||||
|
||||
model.IPv6Gateway = types.StringNull()
|
||||
if networkResp.Gatewayv6 != nil {
|
||||
model.IPv6Gateway = types.StringPointerValue(networkResp.GetGatewayv6())
|
||||
}
|
||||
|
||||
model.NetworkId = types.StringValue(networkId)
|
||||
model.Name = types.StringPointerValue(networkResp.Name)
|
||||
model.PublicIP = types.StringPointerValue(networkResp.PublicIp)
|
||||
model.Labels = labels
|
||||
model.Routed = types.BoolPointerValue(networkResp.Routed)
|
||||
model.RoutingTableID = types.StringNull()
|
||||
model.Region = types.StringNull()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,352 +0,0 @@
|
|||
package v1network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
|
||||
networkModel "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
|
||||
)
|
||||
|
||||
func TestMapDataSourceFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
state networkModel.DataSourceModel
|
||||
input *iaas.Network
|
||||
expected networkModel.DataSourceModel
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"id_ok",
|
||||
networkModel.DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
},
|
||||
&iaas.Network{
|
||||
NetworkId: utils.Ptr("nid"),
|
||||
Gateway: iaas.NewNullableString(nil),
|
||||
},
|
||||
networkModel.DataSourceModel{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Name: types.StringNull(),
|
||||
Nameservers: types.ListNull(types.StringType),
|
||||
IPv4Nameservers: types.ListNull(types.StringType),
|
||||
IPv4PrefixLength: types.Int64Null(),
|
||||
IPv4Gateway: types.StringNull(),
|
||||
IPv4Prefix: types.StringNull(),
|
||||
Prefixes: types.ListNull(types.StringType),
|
||||
IPv4Prefixes: types.ListNull(types.StringType),
|
||||
IPv6Nameservers: types.ListNull(types.StringType),
|
||||
IPv6PrefixLength: types.Int64Null(),
|
||||
IPv6Gateway: types.StringNull(),
|
||||
IPv6Prefix: types.StringNull(),
|
||||
IPv6Prefixes: types.ListNull(types.StringType),
|
||||
PublicIP: types.StringNull(),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Routed: types.BoolNull(),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"values_ok",
|
||||
networkModel.DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
},
|
||||
&iaas.Network{
|
||||
NetworkId: utils.Ptr("nid"),
|
||||
Name: utils.Ptr("name"),
|
||||
Nameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
Prefixes: &[]string{
|
||||
"192.168.42.0/24",
|
||||
"10.100.10.0/16",
|
||||
},
|
||||
NameserversV6: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
PrefixesV6: &[]string{
|
||||
"fd12:3456:789a:1::/64",
|
||||
"fd12:3456:789a:2::/64",
|
||||
},
|
||||
PublicIp: utils.Ptr("publicIp"),
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
Routed: utils.Ptr(true),
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
Gatewayv6: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
},
|
||||
networkModel.DataSourceModel{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Name: types.StringValue("name"),
|
||||
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
IPv4PrefixLength: types.Int64Value(24),
|
||||
Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("192.168.42.0/24"),
|
||||
types.StringValue("10.100.10.0/16"),
|
||||
}),
|
||||
IPv4Prefix: types.StringValue("192.168.42.0/24"),
|
||||
IPv4Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("192.168.42.0/24"),
|
||||
types.StringValue("10.100.10.0/16"),
|
||||
}),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
IPv6PrefixLength: types.Int64Value(64),
|
||||
IPv6Prefix: types.StringValue("fd12:3456:789a:1::/64"),
|
||||
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("fd12:3456:789a:1::/64"),
|
||||
types.StringValue("fd12:3456:789a:2::/64"),
|
||||
}),
|
||||
PublicIP: types.StringValue("publicIp"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Routed: types.BoolValue(true),
|
||||
IPv4Gateway: types.StringValue("gateway"),
|
||||
IPv6Gateway: types.StringValue("gateway"),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv4_nameservers_changed_outside_tf",
|
||||
networkModel.DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
},
|
||||
&iaas.Network{
|
||||
NetworkId: utils.Ptr("nid"),
|
||||
Nameservers: &[]string{
|
||||
"ns2",
|
||||
"ns3",
|
||||
},
|
||||
},
|
||||
networkModel.DataSourceModel{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Name: types.StringNull(),
|
||||
IPv6Prefixes: types.ListNull(types.StringType),
|
||||
IPv6Nameservers: types.ListNull(types.StringType),
|
||||
Prefixes: types.ListNull(types.StringType),
|
||||
IPv4Prefixes: types.ListNull(types.StringType),
|
||||
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns2"),
|
||||
types.StringValue("ns3"),
|
||||
}),
|
||||
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns2"),
|
||||
types.StringValue("ns3"),
|
||||
}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv6_nameservers_changed_outside_tf",
|
||||
networkModel.DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
},
|
||||
&iaas.Network{
|
||||
NetworkId: utils.Ptr("nid"),
|
||||
NameserversV6: &[]string{
|
||||
"ns2",
|
||||
"ns3",
|
||||
},
|
||||
},
|
||||
networkModel.DataSourceModel{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Name: types.StringNull(),
|
||||
IPv6Prefixes: types.ListNull(types.StringType),
|
||||
IPv4Nameservers: types.ListNull(types.StringType),
|
||||
Prefixes: types.ListNull(types.StringType),
|
||||
IPv4Prefixes: types.ListNull(types.StringType),
|
||||
Nameservers: types.ListNull(types.StringType),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns2"),
|
||||
types.StringValue("ns3"),
|
||||
}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv4_prefixes_changed_outside_tf",
|
||||
networkModel.DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("192.168.42.0/24"),
|
||||
types.StringValue("10.100.10.0/16"),
|
||||
}),
|
||||
},
|
||||
&iaas.Network{
|
||||
NetworkId: utils.Ptr("nid"),
|
||||
Prefixes: &[]string{
|
||||
"10.100.20.0/16",
|
||||
"10.100.10.0/16",
|
||||
},
|
||||
},
|
||||
networkModel.DataSourceModel{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Name: types.StringNull(),
|
||||
IPv6Nameservers: types.ListNull(types.StringType),
|
||||
IPv6PrefixLength: types.Int64Null(),
|
||||
IPv6Prefixes: types.ListNull(types.StringType),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Nameservers: types.ListNull(types.StringType),
|
||||
IPv4Nameservers: types.ListNull(types.StringType),
|
||||
IPv4PrefixLength: types.Int64Value(16),
|
||||
IPv4Prefix: types.StringValue("10.100.20.0/16"),
|
||||
Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("10.100.20.0/16"),
|
||||
types.StringValue("10.100.10.0/16"),
|
||||
}),
|
||||
IPv4Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("10.100.20.0/16"),
|
||||
types.StringValue("10.100.10.0/16"),
|
||||
}),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv6_prefixes_changed_outside_tf",
|
||||
networkModel.DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("fd12:3456:789a:1::/64"),
|
||||
types.StringValue("fd12:3456:789a:2::/64"),
|
||||
}),
|
||||
},
|
||||
&iaas.Network{
|
||||
NetworkId: utils.Ptr("nid"),
|
||||
PrefixesV6: &[]string{
|
||||
"fd12:3456:789a:3::/64",
|
||||
"fd12:3456:789a:4::/64",
|
||||
},
|
||||
},
|
||||
networkModel.DataSourceModel{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Name: types.StringNull(),
|
||||
IPv4Nameservers: types.ListNull(types.StringType),
|
||||
IPv4PrefixLength: types.Int64Null(),
|
||||
Prefixes: types.ListNull(types.StringType),
|
||||
IPv4Prefixes: types.ListNull(types.StringType),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Nameservers: types.ListNull(types.StringType),
|
||||
IPv6Nameservers: types.ListNull(types.StringType),
|
||||
IPv6PrefixLength: types.Int64Value(64),
|
||||
IPv6Prefix: types.StringValue("fd12:3456:789a:3::/64"),
|
||||
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("fd12:3456:789a:3::/64"),
|
||||
types.StringValue("fd12:3456:789a:4::/64"),
|
||||
}),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv4_ipv6_gateway_nil",
|
||||
networkModel.DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
},
|
||||
&iaas.Network{
|
||||
NetworkId: utils.Ptr("nid"),
|
||||
},
|
||||
networkModel.DataSourceModel{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Name: types.StringNull(),
|
||||
Nameservers: types.ListNull(types.StringType),
|
||||
IPv4Nameservers: types.ListNull(types.StringType),
|
||||
IPv4PrefixLength: types.Int64Null(),
|
||||
IPv4Gateway: types.StringNull(),
|
||||
Prefixes: types.ListNull(types.StringType),
|
||||
IPv4Prefixes: types.ListNull(types.StringType),
|
||||
IPv6Nameservers: types.ListNull(types.StringType),
|
||||
IPv6PrefixLength: types.Int64Null(),
|
||||
IPv6Gateway: types.StringNull(),
|
||||
IPv6Prefixes: types.ListNull(types.StringType),
|
||||
PublicIP: types.StringNull(),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Routed: types.BoolNull(),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"response_nil_fail",
|
||||
networkModel.DataSourceModel{},
|
||||
nil,
|
||||
networkModel.DataSourceModel{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"no_resource_id",
|
||||
networkModel.DataSourceModel{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
},
|
||||
&iaas.Network{},
|
||||
networkModel.DataSourceModel{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
err := mapDataSourceFields(context.Background(), tt.input, &tt.state)
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && err != nil {
|
||||
t.Fatalf("Should not have failed: %v", err)
|
||||
}
|
||||
if tt.isValid {
|
||||
diff := cmp.Diff(tt.state, tt.expected)
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,558 +0,0 @@
|
|||
package v1network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas/wait"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
networkModel "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
)
|
||||
|
||||
func Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, client *iaas.APIClient) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model networkModel.Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
|
||||
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 {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Creating API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Create new network
|
||||
|
||||
network, err := client.CreateNetwork(ctx, projectId).CreateNetworkPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
networkId := *network.NetworkId
|
||||
network, err = wait.CreateNetworkWaitHandler(ctx, client, projectId, networkId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Network creation waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, network, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
// Set state to fully populated data
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Network created")
|
||||
}
|
||||
|
||||
func Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, client *iaas.APIClient) { // nolint:gocritic // function signature required by Terraform
|
||||
var model networkModel.Model
|
||||
diags := req.State.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
networkId := model.NetworkId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
|
||||
networkResp, err := client.GetNetwork(ctx, projectId, networkId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, networkResp, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
// Set refreshed state
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Network read")
|
||||
}
|
||||
|
||||
func Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, client *iaas.APIClient) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model networkModel.Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
networkId := model.NetworkId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
|
||||
// Retrieve values from state
|
||||
var stateModel networkModel.Model
|
||||
diags = req.State.Get(ctx, &stateModel)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Generate API request body from model
|
||||
payload, err := toUpdatePayload(ctx, &model, &stateModel)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Creating API payload: %v", err))
|
||||
return
|
||||
}
|
||||
// Update existing network
|
||||
err = client.PartialUpdateNetwork(ctx, projectId, networkId).PartialUpdateNetworkPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
waitResp, err := wait.UpdateNetworkWaitHandler(ctx, client, projectId, networkId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Network update waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = mapFields(ctx, waitResp, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Network updated")
|
||||
}
|
||||
|
||||
func Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, client *iaas.APIClient) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from state
|
||||
var model networkModel.Model
|
||||
diags := req.State.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
networkId := model.NetworkId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
|
||||
// Delete existing network
|
||||
err := client.DeleteNetwork(ctx, projectId, networkId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
_, err = wait.DeleteNetworkWaitHandler(ctx, client, projectId, networkId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network", fmt.Sprintf("Network deletion waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Network deleted")
|
||||
}
|
||||
|
||||
// ImportState imports a resource into the Terraform state on success.
|
||||
// The expected format of the resource import identifier is: project_id,network_id
|
||||
func 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] == "" {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics,
|
||||
"Error importing network",
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[network_id] Got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
networkId := idParts[1]
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_id"), networkId)...)
|
||||
tflog.Info(ctx, "Network state imported")
|
||||
}
|
||||
|
||||
func mapFields(ctx context.Context, networkResp *iaas.Network, model *networkModel.Model) error {
|
||||
if networkResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
if model == nil {
|
||||
return fmt.Errorf("model input is nil")
|
||||
}
|
||||
|
||||
var networkId string
|
||||
if model.NetworkId.ValueString() != "" {
|
||||
networkId = model.NetworkId.ValueString()
|
||||
} else if networkResp.NetworkId != nil {
|
||||
networkId = *networkResp.NetworkId
|
||||
} else {
|
||||
return fmt.Errorf("network id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), networkId)
|
||||
|
||||
labels, err := iaasUtils.MapLabels(ctx, networkResp.Labels, model.Labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// IPv4
|
||||
if networkResp.Nameservers == nil {
|
||||
model.Nameservers = types.ListNull(types.StringType)
|
||||
model.IPv4Nameservers = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respNameservers := *networkResp.Nameservers
|
||||
modelNameservers, err := utils.ListValuetoStringSlice(model.Nameservers)
|
||||
modelIPv4Nameservers, errIpv4 := utils.ListValuetoStringSlice(model.IPv4Nameservers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current network nameservers from model: %w", err)
|
||||
}
|
||||
if errIpv4 != nil {
|
||||
return fmt.Errorf("get current IPv4 network nameservers from model: %w", errIpv4)
|
||||
}
|
||||
|
||||
reconciledNameservers := utils.ReconcileStringSlices(modelNameservers, respNameservers)
|
||||
reconciledIPv4Nameservers := utils.ReconcileStringSlices(modelIPv4Nameservers, respNameservers)
|
||||
|
||||
nameserversTF, diags := types.ListValueFrom(ctx, types.StringType, reconciledNameservers)
|
||||
ipv4NameserversTF, ipv4Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv4Nameservers)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network nameservers: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if ipv4Diags.HasError() {
|
||||
return fmt.Errorf("map IPv4 network nameservers: %w", core.DiagsToError(ipv4Diags))
|
||||
}
|
||||
|
||||
model.Nameservers = nameserversTF
|
||||
model.IPv4Nameservers = ipv4NameserversTF
|
||||
}
|
||||
|
||||
if networkResp.Prefixes == nil {
|
||||
model.Prefixes = types.ListNull(types.StringType)
|
||||
model.IPv4Prefixes = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respPrefixes := *networkResp.Prefixes
|
||||
prefixesTF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixes)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network prefixes: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if len(respPrefixes) > 0 {
|
||||
model.IPv4Prefix = types.StringValue(respPrefixes[0])
|
||||
_, netmask, err := net.ParseCIDR(respPrefixes[0])
|
||||
if err != nil {
|
||||
// silently ignore parsing error for the netmask
|
||||
model.IPv4PrefixLength = types.Int64Null()
|
||||
} else {
|
||||
ones, _ := netmask.Mask.Size()
|
||||
model.IPv4PrefixLength = types.Int64Value(int64(ones))
|
||||
}
|
||||
}
|
||||
|
||||
model.Prefixes = prefixesTF
|
||||
model.IPv4Prefixes = prefixesTF
|
||||
}
|
||||
|
||||
if networkResp.Gateway != nil {
|
||||
model.IPv4Gateway = types.StringPointerValue(networkResp.GetGateway())
|
||||
} else {
|
||||
model.IPv4Gateway = types.StringNull()
|
||||
}
|
||||
|
||||
// IPv6
|
||||
|
||||
if networkResp.NameserversV6 == nil {
|
||||
model.IPv6Nameservers = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respIPv6Nameservers := *networkResp.NameserversV6
|
||||
modelIPv6Nameservers, errIpv6 := utils.ListValuetoStringSlice(model.IPv6Nameservers)
|
||||
if errIpv6 != nil {
|
||||
return fmt.Errorf("get current IPv6 network nameservers from model: %w", errIpv6)
|
||||
}
|
||||
|
||||
reconciledIPv6Nameservers := utils.ReconcileStringSlices(modelIPv6Nameservers, respIPv6Nameservers)
|
||||
|
||||
ipv6NameserversTF, ipv6Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv6Nameservers)
|
||||
if ipv6Diags.HasError() {
|
||||
return fmt.Errorf("map IPv6 network nameservers: %w", core.DiagsToError(ipv6Diags))
|
||||
}
|
||||
|
||||
model.IPv6Nameservers = ipv6NameserversTF
|
||||
}
|
||||
|
||||
if networkResp.PrefixesV6 == nil || len(*networkResp.PrefixesV6) == 0 {
|
||||
model.IPv6Prefixes = types.ListNull(types.StringType)
|
||||
model.IPv6Prefix = types.StringNull()
|
||||
model.IPv6PrefixLength = types.Int64Null()
|
||||
} else {
|
||||
respPrefixesV6 := *networkResp.PrefixesV6
|
||||
prefixesV6TF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixesV6)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network IPv6 prefixes: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if len(respPrefixesV6) > 0 {
|
||||
model.IPv6Prefix = types.StringValue(respPrefixesV6[0])
|
||||
_, netmask, err := net.ParseCIDR(respPrefixesV6[0])
|
||||
if err != nil {
|
||||
// silently ignore parsing error for the netmask
|
||||
model.IPv6PrefixLength = types.Int64Null()
|
||||
} else {
|
||||
ones, _ := netmask.Mask.Size()
|
||||
model.IPv6PrefixLength = types.Int64Value(int64(ones))
|
||||
}
|
||||
}
|
||||
model.IPv6Prefixes = prefixesV6TF
|
||||
}
|
||||
|
||||
if networkResp.Gatewayv6 != nil {
|
||||
model.IPv6Gateway = types.StringPointerValue(networkResp.GetGatewayv6())
|
||||
} else {
|
||||
model.IPv6Gateway = types.StringNull()
|
||||
}
|
||||
|
||||
model.NetworkId = types.StringValue(networkId)
|
||||
model.Name = types.StringPointerValue(networkResp.Name)
|
||||
model.PublicIP = types.StringPointerValue(networkResp.PublicIp)
|
||||
model.Labels = labels
|
||||
model.Routed = types.BoolPointerValue(networkResp.Routed)
|
||||
model.Region = types.StringNull()
|
||||
model.RoutingTableID = types.StringNull()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toCreatePayload(ctx context.Context, model *networkModel.Model) (*iaas.CreateNetworkPayload, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
addressFamily := &iaas.CreateNetworkAddressFamily{}
|
||||
|
||||
var modelIPv6Nameservers []string
|
||||
// Is true when IPv6Nameservers is not null or unset
|
||||
if !utils.IsUndefined(model.IPv6Nameservers) {
|
||||
// If ipv6Nameservers is empty, modelIPv6Nameservers will be set to an empty slice.
|
||||
// empty slice != nil slice. Empty slice will result in an empty list in the payload []. Nil slice will result in a payload without the property set
|
||||
modelIPv6Nameservers = []string{}
|
||||
for _, ipv6ns := range model.IPv6Nameservers.Elements() {
|
||||
ipv6NameserverString, ok := ipv6ns.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
modelIPv6Nameservers = append(modelIPv6Nameservers, ipv6NameserverString.ValueString())
|
||||
}
|
||||
}
|
||||
|
||||
if !utils.IsUndefined(model.IPv6Prefix) || !utils.IsUndefined(model.IPv6PrefixLength) || (modelIPv6Nameservers != nil) {
|
||||
addressFamily.Ipv6 = &iaas.CreateNetworkIPv6Body{
|
||||
Prefix: conversion.StringValueToPointer(model.IPv6Prefix),
|
||||
PrefixLength: conversion.Int64ValueToPointer(model.IPv6PrefixLength),
|
||||
}
|
||||
// IPv6 nameservers should only be set, if it contains any value. If the slice is nil, it should NOT be set.
|
||||
// Setting it to a nil slice would result in a payload, where nameservers is set to null in the json payload,
|
||||
// but it should actually be unset. Setting it to "null" will result in an error, because it's NOT nullable.
|
||||
if modelIPv6Nameservers != nil {
|
||||
addressFamily.Ipv6.Nameservers = &modelIPv6Nameservers
|
||||
}
|
||||
|
||||
if model.NoIPv6Gateway.ValueBool() {
|
||||
addressFamily.Ipv6.Gateway = iaas.NewNullableString(nil)
|
||||
} else if !(model.IPv6Gateway.IsUnknown() || model.IPv6Gateway.IsNull()) {
|
||||
addressFamily.Ipv6.Gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv6Gateway))
|
||||
}
|
||||
}
|
||||
|
||||
modelIPv4Nameservers := []string{}
|
||||
var modelIPv4List []attr.Value
|
||||
|
||||
if !(model.IPv4Nameservers.IsNull() || model.IPv4Nameservers.IsUnknown()) {
|
||||
modelIPv4List = model.IPv4Nameservers.Elements()
|
||||
} else {
|
||||
modelIPv4List = model.Nameservers.Elements()
|
||||
}
|
||||
|
||||
for _, ipv4ns := range modelIPv4List {
|
||||
ipv4NameserverString, ok := ipv4ns.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
modelIPv4Nameservers = append(modelIPv4Nameservers, ipv4NameserverString.ValueString())
|
||||
}
|
||||
|
||||
if !model.IPv4Prefix.IsNull() || !model.IPv4PrefixLength.IsNull() || !model.IPv4Nameservers.IsNull() || !model.Nameservers.IsNull() {
|
||||
addressFamily.Ipv4 = &iaas.CreateNetworkIPv4Body{
|
||||
Nameservers: &modelIPv4Nameservers,
|
||||
Prefix: conversion.StringValueToPointer(model.IPv4Prefix),
|
||||
PrefixLength: conversion.Int64ValueToPointer(model.IPv4PrefixLength),
|
||||
}
|
||||
|
||||
if model.NoIPv4Gateway.ValueBool() {
|
||||
addressFamily.Ipv4.Gateway = iaas.NewNullableString(nil)
|
||||
} else if !(model.IPv4Gateway.IsUnknown() || model.IPv4Gateway.IsNull()) {
|
||||
addressFamily.Ipv4.Gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv4Gateway))
|
||||
}
|
||||
}
|
||||
|
||||
labels, err := conversion.ToStringInterfaceMap(ctx, model.Labels)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting to Go map: %w", err)
|
||||
}
|
||||
|
||||
payload := iaas.CreateNetworkPayload{
|
||||
Name: conversion.StringValueToPointer(model.Name),
|
||||
Labels: &labels,
|
||||
Routed: conversion.BoolValueToPointer(model.Routed),
|
||||
}
|
||||
|
||||
if addressFamily.Ipv6 != nil || addressFamily.Ipv4 != nil {
|
||||
payload.AddressFamily = addressFamily
|
||||
}
|
||||
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func toUpdatePayload(ctx context.Context, model, stateModel *networkModel.Model) (*iaas.PartialUpdateNetworkPayload, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
addressFamily := &iaas.UpdateNetworkAddressFamily{}
|
||||
|
||||
var modelIPv6Nameservers []string
|
||||
// Is true when IPv6Nameservers is not null or unset
|
||||
if !utils.IsUndefined(model.IPv6Nameservers) {
|
||||
// If ipv6Nameservers is empty, modelIPv6Nameservers will be set to an empty slice.
|
||||
// empty slice != nil slice. Empty slice will result in an empty list in the payload []. Nil slice will result in a payload without the property set
|
||||
modelIPv6Nameservers = []string{}
|
||||
for _, ipv6ns := range model.IPv6Nameservers.Elements() {
|
||||
ipv6NameserverString, ok := ipv6ns.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
modelIPv6Nameservers = append(modelIPv6Nameservers, ipv6NameserverString.ValueString())
|
||||
}
|
||||
}
|
||||
|
||||
if !utils.IsUndefined(model.NoIPv6Gateway) || !utils.IsUndefined(model.IPv6Gateway) || modelIPv6Nameservers != nil {
|
||||
addressFamily.Ipv6 = &iaas.UpdateNetworkIPv6Body{}
|
||||
|
||||
// IPv6 nameservers should only be set, if it contains any value. If the slice is nil, it should NOT be set.
|
||||
// Setting it to a nil slice would result in a payload, where nameservers is set to null in the json payload,
|
||||
// but it should actually be unset. Setting it to "null" will result in an error, because it's NOT nullable.
|
||||
if modelIPv6Nameservers != nil {
|
||||
addressFamily.Ipv6.Nameservers = &modelIPv6Nameservers
|
||||
}
|
||||
|
||||
if model.NoIPv6Gateway.ValueBool() {
|
||||
addressFamily.Ipv6.Gateway = iaas.NewNullableString(nil)
|
||||
} else if !utils.IsUndefined(model.IPv6Gateway) {
|
||||
addressFamily.Ipv6.Gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv6Gateway))
|
||||
}
|
||||
}
|
||||
|
||||
modelIPv4Nameservers := []string{}
|
||||
var modelIPv4List []attr.Value
|
||||
|
||||
if !(model.IPv4Nameservers.IsNull() || model.IPv4Nameservers.IsUnknown()) {
|
||||
modelIPv4List = model.IPv4Nameservers.Elements()
|
||||
} else {
|
||||
modelIPv4List = model.Nameservers.Elements()
|
||||
}
|
||||
for _, ipv4ns := range modelIPv4List {
|
||||
ipv4NameserverString, ok := ipv4ns.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
modelIPv4Nameservers = append(modelIPv4Nameservers, ipv4NameserverString.ValueString())
|
||||
}
|
||||
|
||||
if !model.IPv4Nameservers.IsNull() || !model.Nameservers.IsNull() {
|
||||
addressFamily.Ipv4 = &iaas.UpdateNetworkIPv4Body{
|
||||
Nameservers: &modelIPv4Nameservers,
|
||||
}
|
||||
|
||||
if model.NoIPv4Gateway.ValueBool() {
|
||||
addressFamily.Ipv4.Gateway = iaas.NewNullableString(nil)
|
||||
} else if !(model.IPv4Gateway.IsUnknown() || model.IPv4Gateway.IsNull()) {
|
||||
addressFamily.Ipv4.Gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv4Gateway))
|
||||
}
|
||||
}
|
||||
currentLabels := stateModel.Labels
|
||||
labels, err := conversion.ToJSONMapPartialUpdatePayload(ctx, currentLabels, model.Labels)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting to Go map: %w", err)
|
||||
}
|
||||
|
||||
payload := iaas.PartialUpdateNetworkPayload{
|
||||
Name: conversion.StringValueToPointer(model.Name),
|
||||
Labels: &labels,
|
||||
}
|
||||
|
||||
if addressFamily.Ipv6 != nil || addressFamily.Ipv4 != nil {
|
||||
payload.AddressFamily = addressFamily
|
||||
}
|
||||
|
||||
return &payload, nil
|
||||
}
|
||||
|
|
@ -1,811 +0,0 @@
|
|||
package v1network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
|
||||
)
|
||||
|
||||
func TestMapFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
state model.Model
|
||||
input *iaas.Network
|
||||
expected model.Model
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"id_ok",
|
||||
model.Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
},
|
||||
&iaas.Network{
|
||||
NetworkId: utils.Ptr("nid"),
|
||||
Gateway: iaas.NewNullableString(nil),
|
||||
},
|
||||
model.Model{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Name: types.StringNull(),
|
||||
Nameservers: types.ListNull(types.StringType),
|
||||
IPv4Nameservers: types.ListNull(types.StringType),
|
||||
IPv4PrefixLength: types.Int64Null(),
|
||||
IPv4Gateway: types.StringNull(),
|
||||
IPv4Prefix: types.StringNull(),
|
||||
Prefixes: types.ListNull(types.StringType),
|
||||
IPv4Prefixes: types.ListNull(types.StringType),
|
||||
IPv6Nameservers: types.ListNull(types.StringType),
|
||||
IPv6PrefixLength: types.Int64Null(),
|
||||
IPv6Gateway: types.StringNull(),
|
||||
IPv6Prefix: types.StringNull(),
|
||||
IPv6Prefixes: types.ListNull(types.StringType),
|
||||
PublicIP: types.StringNull(),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Routed: types.BoolNull(),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"values_ok",
|
||||
model.Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
},
|
||||
&iaas.Network{
|
||||
NetworkId: utils.Ptr("nid"),
|
||||
Name: utils.Ptr("name"),
|
||||
Nameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
Prefixes: &[]string{
|
||||
"192.168.42.0/24",
|
||||
"10.100.10.0/16",
|
||||
},
|
||||
NameserversV6: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
PrefixesV6: &[]string{
|
||||
"fd12:3456:789a:1::/64",
|
||||
"fd12:3456:789b:1::/64",
|
||||
},
|
||||
PublicIp: utils.Ptr("publicIp"),
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
Routed: utils.Ptr(true),
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
Gatewayv6: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
},
|
||||
model.Model{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Name: types.StringValue("name"),
|
||||
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
IPv4PrefixLength: types.Int64Value(24),
|
||||
Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("192.168.42.0/24"),
|
||||
types.StringValue("10.100.10.0/16"),
|
||||
}),
|
||||
IPv4Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("192.168.42.0/24"),
|
||||
types.StringValue("10.100.10.0/16"),
|
||||
}),
|
||||
IPv4Prefix: types.StringValue("192.168.42.0/24"),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
IPv6PrefixLength: types.Int64Value(64),
|
||||
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("fd12:3456:789a:1::/64"),
|
||||
types.StringValue("fd12:3456:789b:1::/64"),
|
||||
}),
|
||||
IPv6Prefix: types.StringValue("fd12:3456:789a:1::/64"),
|
||||
PublicIP: types.StringValue("publicIp"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Routed: types.BoolValue(true),
|
||||
IPv4Gateway: types.StringValue("gateway"),
|
||||
IPv6Gateway: types.StringValue("gateway"),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv4_nameservers_changed_outside_tf",
|
||||
model.Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
},
|
||||
&iaas.Network{
|
||||
NetworkId: utils.Ptr("nid"),
|
||||
Nameservers: &[]string{
|
||||
"ns2",
|
||||
"ns3",
|
||||
},
|
||||
},
|
||||
model.Model{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Name: types.StringNull(),
|
||||
IPv6Prefixes: types.ListNull(types.StringType),
|
||||
IPv6Nameservers: types.ListNull(types.StringType),
|
||||
Prefixes: types.ListNull(types.StringType),
|
||||
IPv4Prefixes: types.ListNull(types.StringType),
|
||||
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns2"),
|
||||
types.StringValue("ns3"),
|
||||
}),
|
||||
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns2"),
|
||||
types.StringValue("ns3"),
|
||||
}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv6_nameservers_changed_outside_tf",
|
||||
model.Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
},
|
||||
&iaas.Network{
|
||||
NetworkId: utils.Ptr("nid"),
|
||||
NameserversV6: &[]string{
|
||||
"ns2",
|
||||
"ns3",
|
||||
},
|
||||
},
|
||||
model.Model{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Name: types.StringNull(),
|
||||
IPv6Prefixes: types.ListNull(types.StringType),
|
||||
IPv4Nameservers: types.ListNull(types.StringType),
|
||||
Prefixes: types.ListNull(types.StringType),
|
||||
IPv4Prefixes: types.ListNull(types.StringType),
|
||||
Nameservers: types.ListNull(types.StringType),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns2"),
|
||||
types.StringValue("ns3"),
|
||||
}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv4_prefixes_changed_outside_tf",
|
||||
model.Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("192.168.42.0/24"),
|
||||
types.StringValue("10.100.10.0/24"),
|
||||
}),
|
||||
},
|
||||
&iaas.Network{
|
||||
NetworkId: utils.Ptr("nid"),
|
||||
Prefixes: &[]string{
|
||||
"192.168.54.0/24",
|
||||
"192.168.55.0/24",
|
||||
},
|
||||
},
|
||||
model.Model{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Name: types.StringNull(),
|
||||
IPv6Nameservers: types.ListNull(types.StringType),
|
||||
IPv6PrefixLength: types.Int64Null(),
|
||||
IPv6Prefixes: types.ListNull(types.StringType),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Nameservers: types.ListNull(types.StringType),
|
||||
IPv4Nameservers: types.ListNull(types.StringType),
|
||||
IPv4PrefixLength: types.Int64Value(24),
|
||||
IPv4Prefix: types.StringValue("192.168.54.0/24"),
|
||||
Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("192.168.54.0/24"),
|
||||
types.StringValue("192.168.55.0/24"),
|
||||
}),
|
||||
IPv4Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("192.168.54.0/24"),
|
||||
types.StringValue("192.168.55.0/24"),
|
||||
}),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv6_prefixes_changed_outside_tf",
|
||||
model.Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("fd12:3456:789a:1::/64"),
|
||||
types.StringValue("fd12:3456:789a:2::/64"),
|
||||
}),
|
||||
},
|
||||
&iaas.Network{
|
||||
NetworkId: utils.Ptr("nid"),
|
||||
PrefixesV6: &[]string{
|
||||
"fd12:3456:789a:1::/64",
|
||||
"fd12:3456:789a:2::/64",
|
||||
},
|
||||
},
|
||||
model.Model{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Name: types.StringNull(),
|
||||
IPv4Nameservers: types.ListNull(types.StringType),
|
||||
IPv4PrefixLength: types.Int64Null(),
|
||||
Prefixes: types.ListNull(types.StringType),
|
||||
IPv4Prefixes: types.ListNull(types.StringType),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Nameservers: types.ListNull(types.StringType),
|
||||
IPv6Nameservers: types.ListNull(types.StringType),
|
||||
IPv6PrefixLength: types.Int64Value(64),
|
||||
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("fd12:3456:789a:1::/64"),
|
||||
types.StringValue("fd12:3456:789a:2::/64"),
|
||||
}),
|
||||
IPv6Prefix: types.StringValue("fd12:3456:789a:1::/64"),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv4_ipv6_gateway_nil",
|
||||
model.Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
},
|
||||
&iaas.Network{
|
||||
NetworkId: utils.Ptr("nid"),
|
||||
},
|
||||
model.Model{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Name: types.StringNull(),
|
||||
Nameservers: types.ListNull(types.StringType),
|
||||
IPv4Nameservers: types.ListNull(types.StringType),
|
||||
IPv4PrefixLength: types.Int64Null(),
|
||||
IPv4Gateway: types.StringNull(),
|
||||
Prefixes: types.ListNull(types.StringType),
|
||||
IPv4Prefixes: types.ListNull(types.StringType),
|
||||
IPv6Nameservers: types.ListNull(types.StringType),
|
||||
IPv6PrefixLength: types.Int64Null(),
|
||||
IPv6Gateway: types.StringNull(),
|
||||
IPv6Prefixes: types.ListNull(types.StringType),
|
||||
PublicIP: types.StringNull(),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Routed: types.BoolNull(),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"response_nil_fail",
|
||||
model.Model{},
|
||||
nil,
|
||||
model.Model{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"no_resource_id",
|
||||
model.Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
},
|
||||
&iaas.Network{},
|
||||
model.Model{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
err := mapFields(context.Background(), tt.input, &tt.state)
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && err != nil {
|
||||
t.Fatalf("Should not have failed: %v", err)
|
||||
}
|
||||
if tt.isValid {
|
||||
diff := cmp.Diff(tt.state, tt.expected)
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToCreatePayload(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input *model.Model
|
||||
expected *iaas.CreateNetworkPayload
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"default_ok",
|
||||
&model.Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
IPv4PrefixLength: types.Int64Value(24),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Routed: types.BoolValue(false),
|
||||
IPv4Gateway: types.StringValue("gateway"),
|
||||
IPv4Prefix: types.StringValue("prefix"),
|
||||
},
|
||||
&iaas.CreateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
AddressFamily: &iaas.CreateNetworkAddressFamily{
|
||||
Ipv4: &iaas.CreateNetworkIPv4Body{
|
||||
Nameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
PrefixLength: utils.Ptr(int64(24)),
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
Prefix: utils.Ptr("prefix"),
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
Routed: utils.Ptr(false),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv4_nameservers_okay",
|
||||
&model.Model{
|
||||
Name: types.StringValue("name"),
|
||||
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
IPv4PrefixLength: types.Int64Value(24),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Routed: types.BoolValue(false),
|
||||
IPv4Gateway: types.StringValue("gateway"),
|
||||
IPv4Prefix: types.StringValue("prefix"),
|
||||
},
|
||||
&iaas.CreateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
AddressFamily: &iaas.CreateNetworkAddressFamily{
|
||||
Ipv4: &iaas.CreateNetworkIPv4Body{
|
||||
Nameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
PrefixLength: utils.Ptr(int64(24)),
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
Prefix: utils.Ptr("prefix"),
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
Routed: utils.Ptr(false),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv6_default_ok",
|
||||
&model.Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
IPv6PrefixLength: types.Int64Value(24),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Routed: types.BoolValue(false),
|
||||
IPv6Gateway: types.StringValue("gateway"),
|
||||
IPv6Prefix: types.StringValue("prefix"),
|
||||
},
|
||||
&iaas.CreateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
AddressFamily: &iaas.CreateNetworkAddressFamily{
|
||||
Ipv6: &iaas.CreateNetworkIPv6Body{
|
||||
Nameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
PrefixLength: utils.Ptr(int64(24)),
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
Prefix: utils.Ptr("prefix"),
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
Routed: utils.Ptr(false),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv6_nameserver_null",
|
||||
&model.Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv6Nameservers: types.ListNull(types.StringType),
|
||||
IPv6PrefixLength: types.Int64Value(24),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Routed: types.BoolValue(false),
|
||||
IPv6Gateway: types.StringValue("gateway"),
|
||||
IPv6Prefix: types.StringValue("prefix"),
|
||||
},
|
||||
&iaas.CreateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
AddressFamily: &iaas.CreateNetworkAddressFamily{
|
||||
Ipv6: &iaas.CreateNetworkIPv6Body{
|
||||
Nameservers: nil,
|
||||
PrefixLength: utils.Ptr(int64(24)),
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
Prefix: utils.Ptr("prefix"),
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
Routed: utils.Ptr(false),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv6_nameserver_empty_list",
|
||||
&model.Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{}),
|
||||
IPv6PrefixLength: types.Int64Value(24),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Routed: types.BoolValue(false),
|
||||
IPv6Gateway: types.StringValue("gateway"),
|
||||
IPv6Prefix: types.StringValue("prefix"),
|
||||
},
|
||||
&iaas.CreateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
AddressFamily: &iaas.CreateNetworkAddressFamily{
|
||||
Ipv6: &iaas.CreateNetworkIPv6Body{
|
||||
Nameservers: utils.Ptr([]string{}),
|
||||
PrefixLength: utils.Ptr(int64(24)),
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
Prefix: utils.Ptr("prefix"),
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
Routed: utils.Ptr(false),
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
output, err := toCreatePayload(context.Background(), tt.input)
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && err != nil {
|
||||
t.Fatalf("Should not have failed: %v", err)
|
||||
}
|
||||
if tt.isValid {
|
||||
diff := cmp.Diff(output, tt.expected, cmp.AllowUnexported(iaas.NullableString{}))
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToUpdatePayload(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input *model.Model
|
||||
state model.Model
|
||||
expected *iaas.PartialUpdateNetworkPayload
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"default_ok",
|
||||
&model.Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Routed: types.BoolValue(true),
|
||||
IPv4Gateway: types.StringValue("gateway"),
|
||||
},
|
||||
model.Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
&iaas.PartialUpdateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
AddressFamily: &iaas.UpdateNetworkAddressFamily{
|
||||
Ipv4: &iaas.UpdateNetworkIPv4Body{
|
||||
Nameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv4_nameservers_okay",
|
||||
&model.Model{
|
||||
Name: types.StringValue("name"),
|
||||
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Routed: types.BoolValue(true),
|
||||
IPv4Gateway: types.StringValue("gateway"),
|
||||
},
|
||||
model.Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
&iaas.PartialUpdateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
AddressFamily: &iaas.UpdateNetworkAddressFamily{
|
||||
Ipv4: &iaas.UpdateNetworkIPv4Body{
|
||||
Nameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv4_gateway_nil",
|
||||
&model.Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Routed: types.BoolValue(true),
|
||||
},
|
||||
model.Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
&iaas.PartialUpdateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
AddressFamily: &iaas.UpdateNetworkAddressFamily{
|
||||
Ipv4: &iaas.UpdateNetworkIPv4Body{
|
||||
Nameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv6_default_ok",
|
||||
&model.Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Routed: types.BoolValue(true),
|
||||
IPv6Gateway: types.StringValue("gateway"),
|
||||
},
|
||||
model.Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
&iaas.PartialUpdateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
AddressFamily: &iaas.UpdateNetworkAddressFamily{
|
||||
Ipv6: &iaas.UpdateNetworkIPv6Body{
|
||||
Nameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv6_gateway_nil",
|
||||
&model.Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Routed: types.BoolValue(true),
|
||||
},
|
||||
model.Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
&iaas.PartialUpdateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
AddressFamily: &iaas.UpdateNetworkAddressFamily{
|
||||
Ipv6: &iaas.UpdateNetworkIPv6Body{
|
||||
Nameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv6_nameserver_null",
|
||||
&model.Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv6Nameservers: types.ListNull(types.StringType),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Routed: types.BoolValue(true),
|
||||
IPv6Gateway: types.StringValue("gateway"),
|
||||
},
|
||||
model.Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
&iaas.PartialUpdateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
AddressFamily: &iaas.UpdateNetworkAddressFamily{
|
||||
Ipv6: &iaas.UpdateNetworkIPv6Body{
|
||||
Nameservers: nil,
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ipv6_nameserver_empty_list",
|
||||
&model.Model{
|
||||
Name: types.StringValue("name"),
|
||||
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{}),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Routed: types.BoolValue(true),
|
||||
IPv6Gateway: types.StringValue("gateway"),
|
||||
},
|
||||
model.Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
&iaas.PartialUpdateNetworkPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
AddressFamily: &iaas.UpdateNetworkAddressFamily{
|
||||
Ipv6: &iaas.UpdateNetworkIPv6Body{
|
||||
Nameservers: &[]string{},
|
||||
Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
output, err := toUpdatePayload(context.Background(), tt.input, &tt.state)
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && err != nil {
|
||||
t.Fatalf("Should not have failed: %v", err)
|
||||
}
|
||||
if tt.isValid {
|
||||
diff := cmp.Diff(output, tt.expected, cmp.AllowUnexported(iaas.NullableString{}))
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,220 +0,0 @@
|
|||
package v2network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
networkModel "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
)
|
||||
|
||||
func DatasourceRead(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse, client *iaasalpha.APIClient, providerData core.ProviderData) { // nolint:gocritic // function signature required by Terraform
|
||||
var model networkModel.DataSourceModel
|
||||
diags := req.Config.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
networkId := model.NetworkId.ValueString()
|
||||
region := providerData.GetRegionWithOverride(model.Region)
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
|
||||
networkResp, err := client.GetNetwork(ctx, projectId, region, networkId).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
&resp.Diagnostics,
|
||||
err,
|
||||
"Reading network",
|
||||
fmt.Sprintf("Network with ID %q does not exist in project %q.", networkId, projectId),
|
||||
map[int]string{
|
||||
http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId),
|
||||
},
|
||||
)
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapDataSourceFields(ctx, networkResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Network read")
|
||||
}
|
||||
|
||||
func mapDataSourceFields(ctx context.Context, networkResp *iaasalpha.Network, model *networkModel.DataSourceModel, region string) error {
|
||||
if networkResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
if model == nil {
|
||||
return fmt.Errorf("model input is nil")
|
||||
}
|
||||
|
||||
var networkId string
|
||||
if model.NetworkId.ValueString() != "" {
|
||||
networkId = model.NetworkId.ValueString()
|
||||
} else if networkResp.Id != nil {
|
||||
networkId = *networkResp.Id
|
||||
} else {
|
||||
return fmt.Errorf("network id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, networkId)
|
||||
|
||||
labels, err := iaasUtils.MapLabels(ctx, networkResp.Labels, model.Labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// IPv4
|
||||
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.Nameservers == nil {
|
||||
model.Nameservers = types.ListNull(types.StringType)
|
||||
model.IPv4Nameservers = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respNameservers := *networkResp.Ipv4.Nameservers
|
||||
modelNameservers, err := utils.ListValuetoStringSlice(model.Nameservers)
|
||||
modelIPv4Nameservers, errIpv4 := utils.ListValuetoStringSlice(model.IPv4Nameservers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current network nameservers from model: %w", err)
|
||||
}
|
||||
if errIpv4 != nil {
|
||||
return fmt.Errorf("get current IPv4 network nameservers from model: %w", errIpv4)
|
||||
}
|
||||
|
||||
reconciledNameservers := utils.ReconcileStringSlices(modelNameservers, respNameservers)
|
||||
reconciledIPv4Nameservers := utils.ReconcileStringSlices(modelIPv4Nameservers, respNameservers)
|
||||
|
||||
nameserversTF, diags := types.ListValueFrom(ctx, types.StringType, reconciledNameservers)
|
||||
ipv4NameserversTF, ipv4Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv4Nameservers)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network nameservers: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if ipv4Diags.HasError() {
|
||||
return fmt.Errorf("map IPv4 network nameservers: %w", core.DiagsToError(ipv4Diags))
|
||||
}
|
||||
|
||||
model.Nameservers = nameserversTF
|
||||
model.IPv4Nameservers = ipv4NameserversTF
|
||||
}
|
||||
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.Prefixes == nil {
|
||||
model.Prefixes = types.ListNull(types.StringType)
|
||||
model.IPv4Prefixes = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respPrefixes := *networkResp.Ipv4.Prefixes
|
||||
prefixesTF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixes)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network prefixes: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if len(respPrefixes) > 0 {
|
||||
model.IPv4Prefix = types.StringValue(respPrefixes[0])
|
||||
_, netmask, err := net.ParseCIDR(respPrefixes[0])
|
||||
if err != nil {
|
||||
// silently ignore parsing error for the netmask
|
||||
model.IPv4PrefixLength = types.Int64Null()
|
||||
} else {
|
||||
ones, _ := netmask.Mask.Size()
|
||||
model.IPv4PrefixLength = types.Int64Value(int64(ones))
|
||||
}
|
||||
}
|
||||
|
||||
model.Prefixes = prefixesTF
|
||||
model.IPv4Prefixes = prefixesTF
|
||||
}
|
||||
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.Gateway == nil {
|
||||
model.IPv4Gateway = types.StringNull()
|
||||
} else {
|
||||
model.IPv4Gateway = types.StringPointerValue(networkResp.Ipv4.GetGateway())
|
||||
}
|
||||
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.PublicIp == nil {
|
||||
model.PublicIP = types.StringNull()
|
||||
} else {
|
||||
model.PublicIP = types.StringPointerValue(networkResp.Ipv4.PublicIp)
|
||||
}
|
||||
|
||||
// IPv6
|
||||
|
||||
if networkResp.Ipv6 == nil || networkResp.Ipv6.Nameservers == nil {
|
||||
model.IPv6Nameservers = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respIPv6Nameservers := *networkResp.Ipv6.Nameservers
|
||||
modelIPv6Nameservers, errIpv6 := utils.ListValuetoStringSlice(model.IPv6Nameservers)
|
||||
if errIpv6 != nil {
|
||||
return fmt.Errorf("get current IPv6 network nameservers from model: %w", errIpv6)
|
||||
}
|
||||
|
||||
reconciledIPv6Nameservers := utils.ReconcileStringSlices(modelIPv6Nameservers, respIPv6Nameservers)
|
||||
|
||||
ipv6NameserversTF, ipv6Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv6Nameservers)
|
||||
if ipv6Diags.HasError() {
|
||||
return fmt.Errorf("map IPv6 network nameservers: %w", core.DiagsToError(ipv6Diags))
|
||||
}
|
||||
|
||||
model.IPv6Nameservers = ipv6NameserversTF
|
||||
}
|
||||
|
||||
if networkResp.Ipv6 == nil || networkResp.Ipv6.Prefixes == nil {
|
||||
model.IPv6Prefixes = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respPrefixesV6 := *networkResp.Ipv6.Prefixes
|
||||
prefixesV6TF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixesV6)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network IPv6 prefixes: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if len(respPrefixesV6) > 0 {
|
||||
model.IPv6Prefix = types.StringValue(respPrefixesV6[0])
|
||||
_, netmask, err := net.ParseCIDR(respPrefixesV6[0])
|
||||
if err != nil {
|
||||
// silently ignore parsing error for the netmask
|
||||
model.IPv6PrefixLength = types.Int64Null()
|
||||
} else {
|
||||
ones, _ := netmask.Mask.Size()
|
||||
model.IPv6PrefixLength = types.Int64Value(int64(ones))
|
||||
}
|
||||
}
|
||||
model.IPv6Prefixes = prefixesV6TF
|
||||
}
|
||||
|
||||
if networkResp.Ipv6 == nil || networkResp.Ipv6.Gateway == nil {
|
||||
model.IPv6Gateway = types.StringNull()
|
||||
} else {
|
||||
model.IPv6Gateway = types.StringPointerValue(networkResp.Ipv6.GetGateway())
|
||||
}
|
||||
|
||||
model.RoutingTableID = types.StringNull()
|
||||
if networkResp.RoutingTableId != nil {
|
||||
model.RoutingTableID = types.StringValue(*networkResp.RoutingTableId)
|
||||
}
|
||||
|
||||
model.NetworkId = types.StringValue(networkId)
|
||||
model.Name = types.StringPointerValue(networkResp.Name)
|
||||
model.Labels = labels
|
||||
model.Routed = types.BoolPointerValue(networkResp.Routed)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,603 +0,0 @@
|
|||
package v2network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha/wait"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
networkModel "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
)
|
||||
|
||||
func Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, client *iaasalpha.APIClient) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model networkModel.Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := model.Region.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Generate API request body from model
|
||||
payload, err := toCreatePayload(ctx, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Creating API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Create new network
|
||||
|
||||
network, err := client.CreateNetwork(ctx, projectId, region).CreateNetworkPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
networkId := *network.Id
|
||||
network, err = wait.CreateNetworkWaitHandler(ctx, client, projectId, region, networkId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Network creation waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, network, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
// Set state to fully populated data
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Network created")
|
||||
}
|
||||
|
||||
func Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, client *iaasalpha.APIClient, providerData core.ProviderData) { // nolint:gocritic // function signature required by Terraform
|
||||
var model networkModel.Model
|
||||
diags := req.State.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
networkId := model.NetworkId.ValueString()
|
||||
region := providerData.GetRegionWithOverride(model.Region)
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
networkResp, err := client.GetNetwork(ctx, projectId, region, networkId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, networkResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
// Set refreshed state
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Network read")
|
||||
}
|
||||
|
||||
func Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, client *iaasalpha.APIClient) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model networkModel.Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
networkId := model.NetworkId.ValueString()
|
||||
region := model.Region.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Retrieve values from state
|
||||
var stateModel networkModel.Model
|
||||
diags = req.State.Get(ctx, &stateModel)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Generate API request body from model
|
||||
payload, err := toUpdatePayload(ctx, &model, &stateModel)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Creating API payload: %v", err))
|
||||
return
|
||||
}
|
||||
// Update existing network
|
||||
err = client.PartialUpdateNetwork(ctx, projectId, region, networkId).PartialUpdateNetworkPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
waitResp, err := wait.UpdateNetworkWaitHandler(ctx, client, projectId, region, networkId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Network update waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = mapFields(ctx, waitResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Network updated")
|
||||
}
|
||||
|
||||
func Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, client *iaasalpha.APIClient) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from state
|
||||
var model networkModel.Model
|
||||
diags := req.State.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
networkId := model.NetworkId.ValueString()
|
||||
region := model.Region.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Delete existing network
|
||||
err := client.DeleteNetwork(ctx, projectId, region, networkId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
_, err = wait.DeleteNetworkWaitHandler(ctx, client, projectId, region, networkId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network", fmt.Sprintf("Network deletion waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Network deleted")
|
||||
}
|
||||
|
||||
// ImportState imports a resource into the Terraform state on success.
|
||||
// The expected format of the resource import identifier is: project_id,region,network_id
|
||||
func ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
|
||||
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics,
|
||||
"Error importing network",
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[network_id] Got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
region := idParts[1]
|
||||
networkId := idParts[2]
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), region)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_id"), networkId)...)
|
||||
tflog.Info(ctx, "Network state imported")
|
||||
}
|
||||
|
||||
func mapFields(ctx context.Context, networkResp *iaasalpha.Network, model *networkModel.Model, region string) error {
|
||||
if networkResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
if model == nil {
|
||||
return fmt.Errorf("model input is nil")
|
||||
}
|
||||
|
||||
var networkId string
|
||||
if model.NetworkId.ValueString() != "" {
|
||||
networkId = model.NetworkId.ValueString()
|
||||
} else if networkResp.Id != nil {
|
||||
networkId = *networkResp.Id
|
||||
} else {
|
||||
return fmt.Errorf("network id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, networkId)
|
||||
|
||||
labels, err := iaasUtils.MapLabels(ctx, networkResp.Labels, model.Labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// IPv4
|
||||
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.Nameservers == nil {
|
||||
model.Nameservers = types.ListNull(types.StringType)
|
||||
model.IPv4Nameservers = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respNameservers := *networkResp.Ipv4.Nameservers
|
||||
modelNameservers, err := utils.ListValuetoStringSlice(model.Nameservers)
|
||||
modelIPv4Nameservers, errIpv4 := utils.ListValuetoStringSlice(model.IPv4Nameservers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current network nameservers from model: %w", err)
|
||||
}
|
||||
if errIpv4 != nil {
|
||||
return fmt.Errorf("get current IPv4 network nameservers from model: %w", errIpv4)
|
||||
}
|
||||
|
||||
reconciledNameservers := utils.ReconcileStringSlices(modelNameservers, respNameservers)
|
||||
reconciledIPv4Nameservers := utils.ReconcileStringSlices(modelIPv4Nameservers, respNameservers)
|
||||
|
||||
nameserversTF, diags := types.ListValueFrom(ctx, types.StringType, reconciledNameservers)
|
||||
ipv4NameserversTF, ipv4Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv4Nameservers)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network nameservers: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if ipv4Diags.HasError() {
|
||||
return fmt.Errorf("map IPv4 network nameservers: %w", core.DiagsToError(ipv4Diags))
|
||||
}
|
||||
|
||||
model.Nameservers = nameserversTF
|
||||
model.IPv4Nameservers = ipv4NameserversTF
|
||||
}
|
||||
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.Prefixes == nil {
|
||||
model.Prefixes = types.ListNull(types.StringType)
|
||||
model.IPv4Prefixes = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respPrefixes := *networkResp.Ipv4.Prefixes
|
||||
prefixesTF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixes)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network prefixes: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if len(respPrefixes) > 0 {
|
||||
model.IPv4Prefix = types.StringValue(respPrefixes[0])
|
||||
_, netmask, err := net.ParseCIDR(respPrefixes[0])
|
||||
if err != nil {
|
||||
tflog.Error(ctx, fmt.Sprintf("ipv4_prefix_length: %+v", err))
|
||||
// silently ignore parsing error for the netmask
|
||||
model.IPv4PrefixLength = types.Int64Null()
|
||||
} else {
|
||||
ones, _ := netmask.Mask.Size()
|
||||
model.IPv4PrefixLength = types.Int64Value(int64(ones))
|
||||
}
|
||||
}
|
||||
|
||||
model.Prefixes = prefixesTF
|
||||
model.IPv4Prefixes = prefixesTF
|
||||
}
|
||||
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.Gateway == nil {
|
||||
model.IPv4Gateway = types.StringNull()
|
||||
} else {
|
||||
model.IPv4Gateway = types.StringPointerValue(networkResp.Ipv4.GetGateway())
|
||||
}
|
||||
|
||||
if networkResp.Ipv4 == nil || networkResp.Ipv4.PublicIp == nil {
|
||||
model.PublicIP = types.StringNull()
|
||||
} else {
|
||||
model.PublicIP = types.StringPointerValue(networkResp.Ipv4.PublicIp)
|
||||
}
|
||||
|
||||
// IPv6
|
||||
|
||||
if networkResp.Ipv6 == nil || networkResp.Ipv6.Nameservers == nil {
|
||||
model.IPv6Nameservers = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respIPv6Nameservers := *networkResp.Ipv6.Nameservers
|
||||
modelIPv6Nameservers, errIpv6 := utils.ListValuetoStringSlice(model.IPv6Nameservers)
|
||||
if errIpv6 != nil {
|
||||
return fmt.Errorf("get current IPv6 network nameservers from model: %w", errIpv6)
|
||||
}
|
||||
|
||||
reconciledIPv6Nameservers := utils.ReconcileStringSlices(modelIPv6Nameservers, respIPv6Nameservers)
|
||||
|
||||
ipv6NameserversTF, ipv6Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv6Nameservers)
|
||||
if ipv6Diags.HasError() {
|
||||
return fmt.Errorf("map IPv6 network nameservers: %w", core.DiagsToError(ipv6Diags))
|
||||
}
|
||||
|
||||
model.IPv6Nameservers = ipv6NameserversTF
|
||||
}
|
||||
|
||||
if networkResp.Ipv6 == nil || networkResp.Ipv6.Prefixes == nil {
|
||||
model.IPv6Prefixes = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respPrefixesV6 := *networkResp.Ipv6.Prefixes
|
||||
prefixesV6TF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixesV6)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network IPv6 prefixes: %w", core.DiagsToError(diags))
|
||||
}
|
||||
if len(respPrefixesV6) > 0 {
|
||||
model.IPv6Prefix = types.StringValue(respPrefixesV6[0])
|
||||
_, netmask, err := net.ParseCIDR(respPrefixesV6[0])
|
||||
if err != nil {
|
||||
// silently ignore parsing error for the netmask
|
||||
model.IPv6PrefixLength = types.Int64Null()
|
||||
} else {
|
||||
ones, _ := netmask.Mask.Size()
|
||||
model.IPv6PrefixLength = types.Int64Value(int64(ones))
|
||||
}
|
||||
}
|
||||
model.IPv6Prefixes = prefixesV6TF
|
||||
}
|
||||
|
||||
if networkResp.Ipv6 == nil || networkResp.Ipv6.Gateway == nil {
|
||||
model.IPv6Gateway = types.StringNull()
|
||||
} else {
|
||||
model.IPv6Gateway = types.StringPointerValue(networkResp.Ipv6.GetGateway())
|
||||
}
|
||||
|
||||
if networkResp.RoutingTableId != nil {
|
||||
model.RoutingTableID = types.StringPointerValue(networkResp.RoutingTableId)
|
||||
} else {
|
||||
model.RoutingTableID = types.StringNull()
|
||||
}
|
||||
|
||||
model.NetworkId = types.StringValue(networkId)
|
||||
model.Name = types.StringPointerValue(networkResp.Name)
|
||||
model.Labels = labels
|
||||
model.Routed = types.BoolPointerValue(networkResp.Routed)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toCreatePayload(ctx context.Context, model *networkModel.Model) (*iaasalpha.CreateNetworkPayload, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
|
||||
var modelIPv6Nameservers []string
|
||||
// Is true when IPv6Nameservers is not null or unset
|
||||
if !utils.IsUndefined(model.IPv6Nameservers) {
|
||||
// If ipv6Nameservers is empty, modelIPv6Nameservers will be set to an empty slice.
|
||||
// empty slice != nil slice. Empty slice will result in an empty list in the payload []. Nil slice will result in a payload without the property set
|
||||
modelIPv6Nameservers = []string{}
|
||||
for _, ipv6ns := range model.IPv6Nameservers.Elements() {
|
||||
ipv6NameserverString, ok := ipv6ns.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
modelIPv6Nameservers = append(modelIPv6Nameservers, ipv6NameserverString.ValueString())
|
||||
}
|
||||
}
|
||||
|
||||
var ipv6Body *iaasalpha.CreateNetworkIPv6
|
||||
if !utils.IsUndefined(model.IPv6PrefixLength) {
|
||||
ipv6Body = &iaasalpha.CreateNetworkIPv6{
|
||||
CreateNetworkIPv6WithPrefixLength: &iaasalpha.CreateNetworkIPv6WithPrefixLength{
|
||||
PrefixLength: conversion.Int64ValueToPointer(model.IPv6PrefixLength),
|
||||
},
|
||||
}
|
||||
// IPv6 nameservers should only be set, if it contains any value. If the slice is nil, it should NOT be set.
|
||||
// Setting it to a nil slice would result in a payload, where nameservers is set to null in the json payload,
|
||||
// but it should actually be unset. Setting it to "null" will result in an error, because it's NOT nullable.
|
||||
if modelIPv6Nameservers != nil {
|
||||
ipv6Body.CreateNetworkIPv6WithPrefixLength.Nameservers = &modelIPv6Nameservers
|
||||
}
|
||||
} else if !utils.IsUndefined(model.IPv6Prefix) {
|
||||
var gateway *iaasalpha.NullableString
|
||||
if model.NoIPv6Gateway.ValueBool() {
|
||||
gateway = iaasalpha.NewNullableString(nil)
|
||||
} else if !(model.IPv6Gateway.IsUnknown() || model.IPv6Gateway.IsNull()) {
|
||||
gateway = iaasalpha.NewNullableString(conversion.StringValueToPointer(model.IPv6Gateway))
|
||||
}
|
||||
|
||||
ipv6Body = &iaasalpha.CreateNetworkIPv6{
|
||||
CreateNetworkIPv6WithPrefix: &iaasalpha.CreateNetworkIPv6WithPrefix{
|
||||
Gateway: gateway,
|
||||
Prefix: conversion.StringValueToPointer(model.IPv6Prefix),
|
||||
},
|
||||
}
|
||||
// IPv6 nameservers should only be set, if it contains any value. If the slice is nil, it should NOT be set.
|
||||
// Setting it to a nil slice would result in a payload, where nameservers is set to null in the json payload,
|
||||
// but it should actually be unset. Setting it to "null" will result in an error, because it's NOT nullable.
|
||||
if modelIPv6Nameservers != nil {
|
||||
ipv6Body.CreateNetworkIPv6WithPrefix.Nameservers = &modelIPv6Nameservers
|
||||
}
|
||||
}
|
||||
|
||||
modelIPv4Nameservers := []string{}
|
||||
var modelIPv4List []attr.Value
|
||||
|
||||
if !(model.IPv4Nameservers.IsNull() || model.IPv4Nameservers.IsUnknown()) {
|
||||
modelIPv4List = model.IPv4Nameservers.Elements()
|
||||
} else {
|
||||
modelIPv4List = model.Nameservers.Elements()
|
||||
}
|
||||
|
||||
for _, ipv4ns := range modelIPv4List {
|
||||
ipv4NameserverString, ok := ipv4ns.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
modelIPv4Nameservers = append(modelIPv4Nameservers, ipv4NameserverString.ValueString())
|
||||
}
|
||||
|
||||
var ipv4Body *iaasalpha.CreateNetworkIPv4
|
||||
if !utils.IsUndefined(model.IPv4PrefixLength) {
|
||||
ipv4Body = &iaasalpha.CreateNetworkIPv4{
|
||||
CreateNetworkIPv4WithPrefixLength: &iaasalpha.CreateNetworkIPv4WithPrefixLength{
|
||||
Nameservers: &modelIPv4Nameservers,
|
||||
PrefixLength: conversion.Int64ValueToPointer(model.IPv4PrefixLength),
|
||||
},
|
||||
}
|
||||
} else if !utils.IsUndefined(model.IPv4Prefix) {
|
||||
var gateway *iaasalpha.NullableString
|
||||
if model.NoIPv4Gateway.ValueBool() {
|
||||
gateway = iaasalpha.NewNullableString(nil)
|
||||
} else if !(model.IPv4Gateway.IsUnknown() || model.IPv4Gateway.IsNull()) {
|
||||
gateway = iaasalpha.NewNullableString(conversion.StringValueToPointer(model.IPv4Gateway))
|
||||
}
|
||||
|
||||
ipv4Body = &iaasalpha.CreateNetworkIPv4{
|
||||
CreateNetworkIPv4WithPrefix: &iaasalpha.CreateNetworkIPv4WithPrefix{
|
||||
Nameservers: &modelIPv4Nameservers,
|
||||
Prefix: conversion.StringValueToPointer(model.IPv4Prefix),
|
||||
Gateway: gateway,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
labels, err := conversion.ToStringInterfaceMap(ctx, model.Labels)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting to Go map: %w", err)
|
||||
}
|
||||
|
||||
payload := iaasalpha.CreateNetworkPayload{
|
||||
Name: conversion.StringValueToPointer(model.Name),
|
||||
Labels: &labels,
|
||||
Routed: conversion.BoolValueToPointer(model.Routed),
|
||||
Ipv4: ipv4Body,
|
||||
Ipv6: ipv6Body,
|
||||
RoutingTableId: conversion.StringValueToPointer(model.RoutingTableID),
|
||||
}
|
||||
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func toUpdatePayload(ctx context.Context, model, stateModel *networkModel.Model) (*iaasalpha.PartialUpdateNetworkPayload, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
|
||||
var modelIPv6Nameservers []string
|
||||
// Is true when IPv6Nameservers is not null or unset
|
||||
if !utils.IsUndefined(model.IPv6Nameservers) {
|
||||
// If ipv6Nameservers is empty, modelIPv6Nameservers will be set to an empty slice.
|
||||
// empty slice != nil slice. Empty slice will result in an empty list in the payload []. Nil slice will result in a payload without the property set
|
||||
modelIPv6Nameservers = []string{}
|
||||
for _, ipv6ns := range model.IPv6Nameservers.Elements() {
|
||||
ipv6NameserverString, ok := ipv6ns.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
modelIPv6Nameservers = append(modelIPv6Nameservers, ipv6NameserverString.ValueString())
|
||||
}
|
||||
}
|
||||
|
||||
var ipv6Body *iaasalpha.UpdateNetworkIPv6Body
|
||||
if modelIPv6Nameservers != nil || !utils.IsUndefined(model.NoIPv6Gateway) || !utils.IsUndefined(model.IPv6Gateway) {
|
||||
ipv6Body = &iaasalpha.UpdateNetworkIPv6Body{}
|
||||
// IPv6 nameservers should only be set, if it contains any value. If the slice is nil, it should NOT be set.
|
||||
// Setting it to a nil slice would result in a payload, where nameservers is set to null in the json payload,
|
||||
// but it should actually be unset. Setting it to "null" will result in an error, because it's NOT nullable.
|
||||
if modelIPv6Nameservers != nil {
|
||||
ipv6Body.Nameservers = &modelIPv6Nameservers
|
||||
}
|
||||
|
||||
if model.NoIPv6Gateway.ValueBool() {
|
||||
ipv6Body.Gateway = iaasalpha.NewNullableString(nil)
|
||||
} else if !(model.IPv6Gateway.IsUnknown() || model.IPv6Gateway.IsNull()) {
|
||||
ipv6Body.Gateway = iaasalpha.NewNullableString(conversion.StringValueToPointer(model.IPv6Gateway))
|
||||
}
|
||||
}
|
||||
|
||||
modelIPv4Nameservers := []string{}
|
||||
var modelIPv4List []attr.Value
|
||||
|
||||
if !(model.IPv4Nameservers.IsNull() || model.IPv4Nameservers.IsUnknown()) {
|
||||
modelIPv4List = model.IPv4Nameservers.Elements()
|
||||
} else {
|
||||
modelIPv4List = model.Nameservers.Elements()
|
||||
}
|
||||
for _, ipv4ns := range modelIPv4List {
|
||||
ipv4NameserverString, ok := ipv4ns.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
modelIPv4Nameservers = append(modelIPv4Nameservers, ipv4NameserverString.ValueString())
|
||||
}
|
||||
|
||||
var ipv4Body *iaasalpha.UpdateNetworkIPv4Body
|
||||
if !model.IPv4Nameservers.IsNull() || !model.Nameservers.IsNull() {
|
||||
ipv4Body = &iaasalpha.UpdateNetworkIPv4Body{
|
||||
Nameservers: &modelIPv4Nameservers,
|
||||
}
|
||||
|
||||
if model.NoIPv4Gateway.ValueBool() {
|
||||
ipv4Body.Gateway = iaasalpha.NewNullableString(nil)
|
||||
} else if !(model.IPv4Gateway.IsUnknown() || model.IPv4Gateway.IsNull()) {
|
||||
ipv4Body.Gateway = iaasalpha.NewNullableString(conversion.StringValueToPointer(model.IPv4Gateway))
|
||||
}
|
||||
}
|
||||
currentLabels := stateModel.Labels
|
||||
labels, err := conversion.ToJSONMapPartialUpdatePayload(ctx, currentLabels, model.Labels)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting to Go map: %w", err)
|
||||
}
|
||||
|
||||
payload := iaasalpha.PartialUpdateNetworkPayload{
|
||||
Name: conversion.StringValueToPointer(model.Name),
|
||||
Labels: &labels,
|
||||
Ipv4: ipv4Body,
|
||||
Ipv6: ipv6Body,
|
||||
RoutingTableId: conversion.StringValueToPointer(model.RoutingTableID),
|
||||
}
|
||||
|
||||
return &payload, nil
|
||||
}
|
||||
|
|
@ -2,9 +2,15 @@ package networkarea
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
|
||||
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
|
||||
|
|
@ -17,8 +23,6 @@ import (
|
|||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||
)
|
||||
|
||||
|
|
@ -58,6 +62,7 @@ func (d *networkAreaDataSource) Configure(ctx context.Context, req datasource.Co
|
|||
|
||||
// Schema defines the schema for the data source.
|
||||
func (d *networkAreaDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
deprecationMsg := "Deprecated because of the IaaS API v1 -> v2 migration. Will be removed in May 2026."
|
||||
description := "Network area datasource schema. Must have a `region` specified in the provider configuration."
|
||||
resp.Schema = schema.Schema{
|
||||
Description: description,
|
||||
|
|
@ -99,13 +104,15 @@ func (d *networkAreaDataSource) Schema(_ context.Context, _ datasource.SchemaReq
|
|||
},
|
||||
},
|
||||
"default_nameservers": schema.ListAttribute{
|
||||
Description: "List of DNS Servers/Nameservers.",
|
||||
Computed: true,
|
||||
ElementType: types.StringType,
|
||||
DeprecationMessage: deprecationMsg,
|
||||
Description: "List of DNS Servers/Nameservers.",
|
||||
Computed: true,
|
||||
ElementType: types.StringType,
|
||||
},
|
||||
"network_ranges": schema.ListNestedAttribute{
|
||||
Description: "List of Network ranges.",
|
||||
Computed: true,
|
||||
DeprecationMessage: deprecationMsg,
|
||||
Description: "List of Network ranges.",
|
||||
Computed: true,
|
||||
Validators: []validator.List{
|
||||
listvalidator.SizeAtLeast(1),
|
||||
listvalidator.SizeAtMost(64),
|
||||
|
|
@ -126,28 +133,32 @@ func (d *networkAreaDataSource) Schema(_ context.Context, _ datasource.SchemaReq
|
|||
},
|
||||
},
|
||||
"transfer_network": schema.StringAttribute{
|
||||
Description: "Classless Inter-Domain Routing (CIDR).",
|
||||
Computed: true,
|
||||
DeprecationMessage: deprecationMsg,
|
||||
Description: "Classless Inter-Domain Routing (CIDR).",
|
||||
Computed: true,
|
||||
},
|
||||
"default_prefix_length": schema.Int64Attribute{
|
||||
Description: "The default prefix length for networks in the network area.",
|
||||
Computed: true,
|
||||
DeprecationMessage: deprecationMsg,
|
||||
Description: "The default prefix length for networks in the network area.",
|
||||
Computed: true,
|
||||
Validators: []validator.Int64{
|
||||
int64validator.AtLeast(24),
|
||||
int64validator.AtMost(29),
|
||||
},
|
||||
},
|
||||
"max_prefix_length": schema.Int64Attribute{
|
||||
Description: "The maximal prefix length for networks in the network area.",
|
||||
Computed: true,
|
||||
DeprecationMessage: deprecationMsg,
|
||||
Description: "The maximal prefix length for networks in the network area.",
|
||||
Computed: true,
|
||||
Validators: []validator.Int64{
|
||||
int64validator.AtLeast(24),
|
||||
int64validator.AtMost(29),
|
||||
},
|
||||
},
|
||||
"min_prefix_length": schema.Int64Attribute{
|
||||
Description: "The minimal prefix length for networks in the network area.",
|
||||
Computed: true,
|
||||
DeprecationMessage: deprecationMsg,
|
||||
Description: "The minimal prefix length for networks in the network area.",
|
||||
Computed: true,
|
||||
Validators: []validator.Int64{
|
||||
int64validator.AtLeast(22),
|
||||
int64validator.AtMost(29),
|
||||
|
|
@ -196,13 +207,32 @@ func (d *networkAreaDataSource) Read(ctx context.Context, req datasource.ReadReq
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
networkAreaRanges := networkAreaResp.Ipv4.NetworkRanges
|
||||
|
||||
err = mapFields(ctx, networkAreaResp, networkAreaRanges, &model)
|
||||
err = mapFields(ctx, networkAreaResp, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network area", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
networkAreaRegionResp, err := d.client.GetNetworkAreaRegion(ctx, organizationId, networkAreaId, "eu01").Execute()
|
||||
if err != nil {
|
||||
var oapiErr *oapierror.GenericOpenAPIError
|
||||
ok := errors.As(err, &oapiErr)
|
||||
if !(ok && (oapiErr.StatusCode == http.StatusNotFound || oapiErr.StatusCode == http.StatusBadRequest)) { // TODO: iaas api returns http 400 in case network area region is not found
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network area region", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
networkAreaRegionResp = &iaas.RegionalArea{}
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
err = mapNetworkAreaRegionFields(ctx, networkAreaRegionResp, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network area region", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package networkarea
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
|
@ -34,26 +35,55 @@ import (
|
|||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||
)
|
||||
|
||||
const (
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
defaultValueDefaultPrefixLength = 25
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
defaultValueMinPrefixLength = 24
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
defaultValueMaxPrefixLength = 29
|
||||
|
||||
// Deprecated: Will be removed in May 2026.
|
||||
deprecationWarningSummary = "Migration to new `stackit_network_area_region` resource needed"
|
||||
// Deprecated: Will be removed in May 2026.
|
||||
deprecationWarningDetails = "You're using deprecated features of the `stackit_network_area` resource. These will be removed in May 2026. Migrate to the new `stackit_network_area_region` resource instead."
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ resource.Resource = &networkAreaResource{}
|
||||
_ resource.ResourceWithConfigure = &networkAreaResource{}
|
||||
_ resource.ResourceWithImportState = &networkAreaResource{}
|
||||
_ resource.Resource = &networkAreaResource{}
|
||||
_ resource.ResourceWithConfigure = &networkAreaResource{}
|
||||
_ resource.ResourceWithImportState = &networkAreaResource{}
|
||||
_ resource.ResourceWithValidateConfig = &networkAreaResource{}
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
OrganizationId types.String `tfsdk:"organization_id"`
|
||||
NetworkAreaId types.String `tfsdk:"network_area_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
ProjectCount types.Int64 `tfsdk:"project_count"`
|
||||
DefaultNameservers types.List `tfsdk:"default_nameservers"`
|
||||
NetworkRanges types.List `tfsdk:"network_ranges"`
|
||||
TransferNetwork types.String `tfsdk:"transfer_network"`
|
||||
DefaultPrefixLength types.Int64 `tfsdk:"default_prefix_length"`
|
||||
MaxPrefixLength types.Int64 `tfsdk:"max_prefix_length"`
|
||||
MinPrefixLength types.Int64 `tfsdk:"min_prefix_length"`
|
||||
Labels types.Map `tfsdk:"labels"`
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
OrganizationId types.String `tfsdk:"organization_id"`
|
||||
NetworkAreaId types.String `tfsdk:"network_area_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
ProjectCount types.Int64 `tfsdk:"project_count"`
|
||||
Labels types.Map `tfsdk:"labels"`
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
DefaultNameservers types.List `tfsdk:"default_nameservers"`
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
MaxPrefixLength types.Int64 `tfsdk:"max_prefix_length"`
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
NetworkRanges types.List `tfsdk:"network_ranges"`
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
TransferNetwork types.String `tfsdk:"transfer_network"`
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
DefaultPrefixLength types.Int64 `tfsdk:"default_prefix_length"`
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
MinPrefixLength types.Int64 `tfsdk:"min_prefix_length"`
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider. LegacyMode checks if any of the deprecated fields are set which now relate to the network area region API resource.
|
||||
func (model *Model) LegacyMode() bool {
|
||||
return !model.NetworkRanges.IsNull() || model.NetworkRanges.IsUnknown() || !model.TransferNetwork.IsNull() || model.TransferNetwork.IsUnknown() || !model.DefaultNameservers.IsNull() || model.DefaultNameservers.IsUnknown() || model.DefaultPrefixLength != types.Int64Value(int64(defaultValueDefaultPrefixLength)) || model.MinPrefixLength != types.Int64Value(int64(defaultValueMinPrefixLength)) || model.MaxPrefixLength != types.Int64Value(int64(defaultValueMaxPrefixLength))
|
||||
}
|
||||
|
||||
// Struct corresponding to Model.NetworkRanges[i]
|
||||
|
|
@ -104,9 +134,27 @@ func (r *networkAreaResource) Configure(ctx context.Context, req resource.Config
|
|||
tflog.Info(ctx, "IaaS client configured")
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
func (r *networkAreaResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
|
||||
var resourceModel Model
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &resourceModel)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if resourceModel.NetworkRanges.IsNull() != resourceModel.TransferNetwork.IsNull() {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring network network area", "You have to either provide both the `network_ranges` and `transfer_network` fields simultaneously or none of them.")
|
||||
}
|
||||
|
||||
if (resourceModel.NetworkRanges.IsNull() || resourceModel.TransferNetwork.IsNull()) && (!resourceModel.DefaultNameservers.IsNull() || !resourceModel.DefaultPrefixLength.IsNull() || !resourceModel.MinPrefixLength.IsNull() || !resourceModel.MaxPrefixLength.IsNull()) {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring network network area", "You have to provide both the `network_ranges` and `transfer_network` fields when providing one of these fields: `default_nameservers`, `default_prefix_length`, `max_prefix_length`, `min_prefix_length`")
|
||||
}
|
||||
}
|
||||
|
||||
// Schema defines the schema for the resource.
|
||||
func (r *networkAreaResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
description := "Network area resource schema. Must have a `region` specified in the provider configuration."
|
||||
deprecationMsg := "Deprecated because of the IaaS API v1 -> v2 migration. Will be removed in May 2026. Use the new `stackit_network_area_region` resource instead."
|
||||
description := "Network area resource schema."
|
||||
resp.Schema = schema.Schema{
|
||||
Description: description,
|
||||
MarkdownDescription: description,
|
||||
|
|
@ -155,14 +203,18 @@ func (r *networkAreaResource) Schema(_ context.Context, _ resource.SchemaRequest
|
|||
int64validator.AtLeast(0),
|
||||
},
|
||||
},
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
"default_nameservers": schema.ListAttribute{
|
||||
Description: "List of DNS Servers/Nameservers.",
|
||||
Optional: true,
|
||||
ElementType: types.StringType,
|
||||
Description: "List of DNS Servers/Nameservers for configuration of network area for region `eu01`.",
|
||||
DeprecationMessage: deprecationMsg,
|
||||
Optional: true,
|
||||
ElementType: types.StringType,
|
||||
},
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
"network_ranges": schema.ListNestedAttribute{
|
||||
Description: "List of Network ranges.",
|
||||
Required: true,
|
||||
Description: "List of Network ranges for configuration of network area for region `eu01`.",
|
||||
DeprecationMessage: deprecationMsg,
|
||||
Optional: true,
|
||||
Validators: []validator.List{
|
||||
listvalidator.SizeAtLeast(1),
|
||||
listvalidator.SizeAtMost(64),
|
||||
|
|
@ -170,55 +222,65 @@ func (r *networkAreaResource) Schema(_ context.Context, _ resource.SchemaRequest
|
|||
NestedObject: schema.NestedAttributeObject{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"network_range_id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
DeprecationMessage: deprecationMsg,
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"prefix": schema.StringAttribute{
|
||||
Description: "Classless Inter-Domain Routing (CIDR).",
|
||||
Required: true,
|
||||
DeprecationMessage: deprecationMsg,
|
||||
Description: "Classless Inter-Domain Routing (CIDR).",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
"transfer_network": schema.StringAttribute{
|
||||
Description: "Classless Inter-Domain Routing (CIDR).",
|
||||
Required: true,
|
||||
DeprecationMessage: deprecationMsg,
|
||||
Description: "Classless Inter-Domain Routing (CIDR) for configuration of network area for region `eu01`.",
|
||||
Optional: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
"default_prefix_length": schema.Int64Attribute{
|
||||
Description: "The default prefix length for networks in the network area.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
DeprecationMessage: deprecationMsg,
|
||||
Description: "The default prefix length for networks in the network area for region `eu01`.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Validators: []validator.Int64{
|
||||
int64validator.AtLeast(24),
|
||||
int64validator.AtMost(29),
|
||||
},
|
||||
Default: int64default.StaticInt64(25),
|
||||
Default: int64default.StaticInt64(defaultValueDefaultPrefixLength),
|
||||
},
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
"max_prefix_length": schema.Int64Attribute{
|
||||
Description: "The maximal prefix length for networks in the network area.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
DeprecationMessage: deprecationMsg,
|
||||
Description: "The maximal prefix length for networks in the network area for region `eu01`.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Validators: []validator.Int64{
|
||||
int64validator.AtLeast(24),
|
||||
int64validator.AtMost(29),
|
||||
},
|
||||
Default: int64default.StaticInt64(29),
|
||||
Default: int64default.StaticInt64(defaultValueMaxPrefixLength),
|
||||
},
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
"min_prefix_length": schema.Int64Attribute{
|
||||
Description: "The minimal prefix length for networks in the network area.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
DeprecationMessage: deprecationMsg,
|
||||
Description: "The minimal prefix length for networks in the network area for region `eu01`.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Validators: []validator.Int64{
|
||||
int64validator.AtLeast(8),
|
||||
int64validator.AtMost(29),
|
||||
},
|
||||
Default: int64default.StaticInt64(24),
|
||||
Default: int64default.StaticInt64(defaultValueMinPrefixLength),
|
||||
},
|
||||
"labels": schema.MapAttribute{
|
||||
Description: "Labels are key-value string pairs which can be attached to a resource container",
|
||||
|
|
@ -233,8 +295,7 @@ func (r *networkAreaResource) Schema(_ context.Context, _ resource.SchemaRequest
|
|||
func (r *networkAreaResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
|
@ -253,7 +314,7 @@ func (r *networkAreaResource) Create(ctx context.Context, req resource.CreateReq
|
|||
}
|
||||
|
||||
// Create new network area
|
||||
area, err := r.client.CreateNetworkArea(ctx, organizationId).CreateNetworkAreaPayload(*payload).Execute()
|
||||
networkArea, err := r.client.CreateNetworkArea(ctx, organizationId).CreateNetworkAreaPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network area", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -261,25 +322,66 @@ func (r *networkAreaResource) Create(ctx context.Context, req resource.CreateReq
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
networkArea, err := wait.CreateNetworkAreaWaitHandler(ctx, r.client, organizationId, *area.AreaId).WaitWithContext(context.Background())
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network area", fmt.Sprintf("Network area creation waiting: %v", err))
|
||||
return
|
||||
}
|
||||
networkAreaId := *networkArea.AreaId
|
||||
networkAreaId := *networkArea.Id
|
||||
ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
|
||||
|
||||
networkAreaRanges := networkArea.Ipv4.NetworkRanges
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, networkArea, networkAreaRanges, &model)
|
||||
err = mapFields(ctx, networkArea, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network area", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
if model.LegacyMode() {
|
||||
core.LogAndAddWarning(ctx, &resp.Diagnostics, deprecationWarningSummary, deprecationWarningDetails)
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
regionCreatePayload, err := toRegionCreatePayload(ctx, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network area region", fmt.Sprintf("Creating API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
networkAreaRegionCreateResp, err := r.client.CreateNetworkAreaRegion(ctx, organizationId, networkAreaId, "eu01").CreateNetworkAreaRegionPayload(*regionCreatePayload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network area region", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
err = mapNetworkAreaRegionFields(ctx, networkAreaRegionCreateResp, &model) // map partial state - just in case anything goes wrong during the wait handler
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network area region", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
networkAreaRegionResp, err := wait.CreateNetworkAreaRegionWaitHandler(ctx, r.client, organizationId, networkAreaId, "eu01").WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error waiting for network area region creation", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
err = mapNetworkAreaRegionFields(ctx, networkAreaRegionResp, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network area region", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
model.NetworkRanges = types.ListNull(types.ObjectType{AttrTypes: networkRangeTypes})
|
||||
model.DefaultNameservers = types.ListNull(types.StringType)
|
||||
model.TransferNetwork = types.StringNull()
|
||||
model.DefaultPrefixLength = types.Int64Value(defaultValueDefaultPrefixLength)
|
||||
model.MinPrefixLength = types.Int64Value(defaultValueMinPrefixLength)
|
||||
model.MaxPrefixLength = types.Int64Value(defaultValueMaxPrefixLength)
|
||||
}
|
||||
|
||||
// Set state to fully populated data
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
|
@ -289,11 +391,11 @@ func (r *networkAreaResource) Create(ctx context.Context, req resource.CreateReq
|
|||
// Read refreshes the Terraform state with the latest data.
|
||||
func (r *networkAreaResource) 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)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
organizationId := model.OrganizationId.ValueString()
|
||||
networkAreaId := model.NetworkAreaId.ValueString()
|
||||
|
||||
|
|
@ -304,7 +406,8 @@ func (r *networkAreaResource) Read(ctx context.Context, req resource.ReadRequest
|
|||
|
||||
networkAreaResp, err := r.client.GetNetworkArea(ctx, organizationId, networkAreaId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
var oapiErr *oapierror.GenericOpenAPIError
|
||||
ok := errors.As(err, &oapiErr)
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
|
|
@ -315,17 +418,53 @@ func (r *networkAreaResource) Read(ctx context.Context, req resource.ReadRequest
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
networkAreaRanges := networkAreaResp.Ipv4.NetworkRanges
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, networkAreaResp, networkAreaRanges, &model)
|
||||
err = mapFields(ctx, networkAreaResp, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network area", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
if model.LegacyMode() {
|
||||
core.LogAndAddWarning(ctx, &resp.Diagnostics, deprecationWarningSummary, deprecationWarningDetails)
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
networkAreaRegionResp, err := r.client.GetNetworkAreaRegion(ctx, organizationId, networkAreaId, "eu01").Execute()
|
||||
if err != nil {
|
||||
var oapiErr *oapierror.GenericOpenAPIError
|
||||
ok := errors.As(err, &oapiErr)
|
||||
if !(ok && (oapiErr.StatusCode == http.StatusNotFound || oapiErr.StatusCode == http.StatusBadRequest)) { // TODO: iaas api returns http 400 in case network area region is not found
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network area region", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
model.NetworkRanges = types.ListNull(types.ObjectType{AttrTypes: networkRangeTypes})
|
||||
model.DefaultNameservers = types.ListNull(types.StringType)
|
||||
model.TransferNetwork = types.StringNull()
|
||||
model.DefaultPrefixLength = types.Int64Value(defaultValueDefaultPrefixLength)
|
||||
model.MinPrefixLength = types.Int64Value(defaultValueMinPrefixLength)
|
||||
model.MaxPrefixLength = types.Int64Value(defaultValueMaxPrefixLength)
|
||||
} else {
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
err = mapNetworkAreaRegionFields(ctx, networkAreaRegionResp, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network area region", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
model.NetworkRanges = types.ListNull(types.ObjectType{AttrTypes: networkRangeTypes})
|
||||
model.DefaultNameservers = types.ListNull(types.StringType)
|
||||
model.TransferNetwork = types.StringNull()
|
||||
model.DefaultPrefixLength = types.Int64Value(defaultValueDefaultPrefixLength)
|
||||
model.MinPrefixLength = types.Int64Value(defaultValueMinPrefixLength)
|
||||
model.MaxPrefixLength = types.Int64Value(defaultValueMaxPrefixLength)
|
||||
}
|
||||
|
||||
// Set refreshed state
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
|
@ -336,11 +475,11 @@ func (r *networkAreaResource) Read(ctx context.Context, req resource.ReadRequest
|
|||
func (r *networkAreaResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
organizationId := model.OrganizationId.ValueString()
|
||||
networkAreaId := model.NetworkAreaId.ValueString()
|
||||
|
||||
|
|
@ -351,8 +490,7 @@ func (r *networkAreaResource) Update(ctx context.Context, req resource.UpdateReq
|
|||
|
||||
ranges := []networkRange{}
|
||||
if !(model.NetworkRanges.IsNull() || model.NetworkRanges.IsUnknown()) {
|
||||
diags = model.NetworkRanges.ElementsAs(ctx, &ranges, false)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
resp.Diagnostics.Append(model.NetworkRanges.ElementsAs(ctx, &ranges, false)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
|
@ -360,8 +498,7 @@ func (r *networkAreaResource) Update(ctx context.Context, req resource.UpdateReq
|
|||
|
||||
// Retrieve values from state
|
||||
var stateModel Model
|
||||
diags = req.State.Get(ctx, &stateModel)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &stateModel)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
|
@ -373,7 +510,7 @@ func (r *networkAreaResource) Update(ctx context.Context, req resource.UpdateReq
|
|||
return
|
||||
}
|
||||
// Update existing network
|
||||
_, err = r.client.PartialUpdateNetworkArea(ctx, organizationId, networkAreaId).PartialUpdateNetworkAreaPayload(*payload).Execute()
|
||||
networkAreaUpdateResp, err := r.client.PartialUpdateNetworkArea(ctx, organizationId, networkAreaId).PartialUpdateNetworkAreaPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network area", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -381,39 +518,73 @@ func (r *networkAreaResource) Update(ctx context.Context, req resource.UpdateReq
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
waitResp, err := wait.UpdateNetworkAreaWaitHandler(ctx, r.client, organizationId, networkAreaId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network area", fmt.Sprintf("Network area update waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Update network ranges
|
||||
err = updateNetworkRanges(ctx, organizationId, networkAreaId, ranges, r.client)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network area", fmt.Sprintf("Updating Network ranges: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
networkAreaResp, err := r.client.GetNetworkArea(ctx, organizationId, networkAreaId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network area", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
networkAreaRanges := networkAreaResp.Ipv4.NetworkRanges
|
||||
|
||||
err = mapFields(ctx, waitResp, networkAreaRanges, &model)
|
||||
err = mapFields(ctx, networkAreaUpdateResp, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network area", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
if model.LegacyMode() {
|
||||
core.LogAndAddWarning(ctx, &resp.Diagnostics, deprecationWarningSummary, deprecationWarningDetails)
|
||||
|
||||
// Deprecated: Update network area region payload creation. Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
regionUpdatePayload, err := toRegionUpdatePayload(ctx, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network area region", fmt.Sprintf("Creating API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Deprecated: Update network area region. Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
networkAreaRegionUpdateResp, err := r.client.UpdateNetworkAreaRegion(ctx, organizationId, networkAreaId, "eu01").UpdateNetworkAreaRegionPayload(*regionUpdatePayload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network area region", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Deprecated: Update network area region. Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
err = mapNetworkAreaRegionFields(ctx, networkAreaRegionUpdateResp, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network area region", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Deprecated: Update network ranges. Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
err = updateNetworkRanges(ctx, organizationId, networkAreaId, ranges, r.client)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network area region", fmt.Sprintf("Updating Network ranges: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
networkAreaRegionResp, err := r.client.GetNetworkAreaRegion(ctx, organizationId, networkAreaId, "eu01").Execute()
|
||||
if err != nil {
|
||||
var oapiErr *oapierror.GenericOpenAPIError
|
||||
ok := errors.As(err, &oapiErr)
|
||||
if ok && (oapiErr.StatusCode == http.StatusNotFound || oapiErr.StatusCode == http.StatusBadRequest) { // TODO: iaas api returns http 400 in case network area region is not found
|
||||
return
|
||||
}
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network area region", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
err = mapNetworkAreaRegionFields(ctx, networkAreaRegionResp, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network area region", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Deprecated: Will be removed in May 2026. Only introduced to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
model.NetworkRanges = types.ListNull(types.ObjectType{AttrTypes: networkRangeTypes})
|
||||
model.DefaultNameservers = types.ListNull(types.StringType)
|
||||
model.TransferNetwork = types.StringNull()
|
||||
model.DefaultPrefixLength = types.Int64Value(defaultValueDefaultPrefixLength)
|
||||
model.MinPrefixLength = types.Int64Value(defaultValueMinPrefixLength)
|
||||
model.MaxPrefixLength = types.Int64Value(defaultValueMaxPrefixLength)
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
|
@ -444,7 +615,29 @@ func (r *networkAreaResource) Delete(ctx context.Context, req resource.DeleteReq
|
|||
return
|
||||
}
|
||||
|
||||
// Delete existing network
|
||||
// Get all configured regions so we can delete them one by one before deleting the network area
|
||||
regionsListResp, err := r.client.ListNetworkAreaRegions(ctx, organizationId, networkAreaId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network area region", fmt.Sprintf("Calling API to list configured regions: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Delete network region configurations
|
||||
for region := range *regionsListResp.Regions {
|
||||
err = r.client.DeleteNetworkAreaRegion(ctx, organizationId, networkAreaId, region).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network area region", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
_, err = wait.DeleteNetworkAreaRegionWaitHandler(ctx, r.client, organizationId, networkAreaId, region).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network area region", fmt.Sprintf("Waiting for networea deletion: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Delete existing network area
|
||||
err = r.client.DeleteNetworkArea(ctx, organizationId, networkAreaId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network area", fmt.Sprintf("Calling API: %v", err))
|
||||
|
|
@ -453,12 +646,6 @@ func (r *networkAreaResource) Delete(ctx context.Context, req resource.DeleteReq
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
_, err = wait.DeleteNetworkAreaWaitHandler(ctx, r.client, organizationId, networkAreaId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network area", fmt.Sprintf("Network area deletion waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Network area deleted")
|
||||
}
|
||||
|
||||
|
|
@ -485,7 +672,7 @@ func (r *networkAreaResource) ImportState(ctx context.Context, req resource.Impo
|
|||
tflog.Info(ctx, "Network state imported")
|
||||
}
|
||||
|
||||
func mapFields(ctx context.Context, networkAreaResp *iaas.NetworkArea, networkAreaRangesResp *[]iaas.NetworkRange, model *Model) error {
|
||||
func mapFields(ctx context.Context, networkAreaResp *iaas.NetworkArea, model *Model) error {
|
||||
if networkAreaResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
|
|
@ -496,18 +683,41 @@ func mapFields(ctx context.Context, networkAreaResp *iaas.NetworkArea, networkAr
|
|||
var networkAreaId string
|
||||
if model.NetworkAreaId.ValueString() != "" {
|
||||
networkAreaId = model.NetworkAreaId.ValueString()
|
||||
} else if networkAreaResp.AreaId != nil {
|
||||
networkAreaId = *networkAreaResp.AreaId
|
||||
} else if networkAreaResp.Id != nil {
|
||||
networkAreaId = *networkAreaResp.Id
|
||||
} else {
|
||||
return fmt.Errorf("network area id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.OrganizationId.ValueString(), networkAreaId)
|
||||
|
||||
if networkAreaResp.Ipv4 == nil || networkAreaResp.Ipv4.DefaultNameservers == nil {
|
||||
labels, err := iaasUtils.MapLabels(ctx, networkAreaResp.Labels, model.Labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
model.NetworkAreaId = types.StringValue(networkAreaId)
|
||||
model.Name = types.StringPointerValue(networkAreaResp.Name)
|
||||
model.ProjectCount = types.Int64PointerValue(networkAreaResp.ProjectCount)
|
||||
model.Labels = labels
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: mapRegionFields maps the region configuration for eu01 to avoid a breaking change in the Terraform provider during the IaaS v1 -> v2 API migration. Will be removed in May 2026.
|
||||
func mapNetworkAreaRegionFields(ctx context.Context, networkAreaRegionResp *iaas.RegionalArea, model *Model) error {
|
||||
if model == nil {
|
||||
return fmt.Errorf("model input is nil")
|
||||
}
|
||||
if networkAreaRegionResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
|
||||
// map default nameservers
|
||||
if networkAreaRegionResp.Ipv4 == nil || networkAreaRegionResp.Ipv4.DefaultNameservers == nil {
|
||||
model.DefaultNameservers = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respDefaultNameservers := *networkAreaResp.Ipv4.DefaultNameservers
|
||||
respDefaultNameservers := *networkAreaRegionResp.Ipv4.DefaultNameservers
|
||||
modelDefaultNameservers, err := utils.ListValuetoStringSlice(model.DefaultNameservers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current network area default nameservers from model: %w", err)
|
||||
|
|
@ -523,31 +733,28 @@ func mapFields(ctx context.Context, networkAreaResp *iaas.NetworkArea, networkAr
|
|||
model.DefaultNameservers = defaultNameserversTF
|
||||
}
|
||||
|
||||
err := mapNetworkRanges(ctx, networkAreaRangesResp, model)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mapping network ranges: %w", err)
|
||||
// map network ranges
|
||||
if networkAreaRegionResp.Ipv4 == nil || networkAreaRegionResp.Ipv4.NetworkRanges == nil {
|
||||
model.NetworkRanges = types.ListNull(types.ObjectType{AttrTypes: networkRangeTypes})
|
||||
} else {
|
||||
err := mapNetworkRanges(ctx, networkAreaRegionResp.Ipv4.NetworkRanges, model)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mapping network ranges: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
labels, err := iaasUtils.MapLabels(ctx, networkAreaResp.Labels, model.Labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
model.NetworkAreaId = types.StringValue(networkAreaId)
|
||||
model.Name = types.StringPointerValue(networkAreaResp.Name)
|
||||
model.ProjectCount = types.Int64PointerValue(networkAreaResp.ProjectCount)
|
||||
model.Labels = labels
|
||||
|
||||
if networkAreaResp.Ipv4 != nil {
|
||||
model.TransferNetwork = types.StringPointerValue(networkAreaResp.Ipv4.TransferNetwork)
|
||||
model.DefaultPrefixLength = types.Int64PointerValue(networkAreaResp.Ipv4.DefaultPrefixLen)
|
||||
model.MaxPrefixLength = types.Int64PointerValue(networkAreaResp.Ipv4.MaxPrefixLen)
|
||||
model.MinPrefixLength = types.Int64PointerValue(networkAreaResp.Ipv4.MinPrefixLen)
|
||||
// map remaining fields
|
||||
if networkAreaRegionResp.Ipv4 != nil {
|
||||
model.TransferNetwork = types.StringPointerValue(networkAreaRegionResp.Ipv4.TransferNetwork)
|
||||
model.DefaultPrefixLength = types.Int64PointerValue(networkAreaRegionResp.Ipv4.DefaultPrefixLen)
|
||||
model.MaxPrefixLength = types.Int64PointerValue(networkAreaRegionResp.Ipv4.MaxPrefixLen)
|
||||
model.MinPrefixLength = types.Int64PointerValue(networkAreaRegionResp.Ipv4.MinPrefixLen)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: mapNetworkRanges will be removed in May 2026. Implementation won't be needed anymore because of the IaaS API v1 -> v2 migration. Func was only kept to circumvent breaking changes.
|
||||
func mapNetworkRanges(ctx context.Context, networkAreaRangesList *[]iaas.NetworkRange, model *Model) error {
|
||||
var diags diag.Diagnostics
|
||||
|
||||
|
|
@ -584,7 +791,7 @@ func mapNetworkRanges(ctx context.Context, networkAreaRangesList *[]iaas.Network
|
|||
var networkRangeId string
|
||||
for _, networkRangeElement := range *networkAreaRangesList {
|
||||
if *networkRangeElement.Prefix == prefix {
|
||||
networkRangeId = *networkRangeElement.NetworkRangeId
|
||||
networkRangeId = *networkRangeElement.Id
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -618,13 +825,26 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateNetworkArea
|
|||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
|
||||
modelDefaultNameservers := []string{}
|
||||
for _, ns := range model.DefaultNameservers.Elements() {
|
||||
nameserverString, ok := ns.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
modelDefaultNameservers = append(modelDefaultNameservers, nameserverString.ValueString())
|
||||
labels, err := conversion.ToStringInterfaceMap(ctx, model.Labels)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting to Go map: %w", err)
|
||||
}
|
||||
|
||||
return &iaas.CreateNetworkAreaPayload{
|
||||
Name: conversion.StringValueToPointer(model.Name),
|
||||
Labels: &labels,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Deprecated: toRegionCreatePayload will be removed in May 2026. Implementation won't be needed anymore because of the IaaS API v1 -> v2 migration. Func was only introduced to circumvent breaking changes.
|
||||
func toRegionCreatePayload(ctx context.Context, model *Model) (*iaas.CreateNetworkAreaRegionPayload, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
|
||||
modelDefaultNameservers, err := toDefaultNameserversPayload(ctx, model)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting default nameservers: %w", err)
|
||||
}
|
||||
|
||||
networkRangesPayload, err := toNetworkRangesPayload(ctx, model)
|
||||
|
|
@ -632,24 +852,15 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateNetworkArea
|
|||
return nil, fmt.Errorf("converting network ranges: %w", err)
|
||||
}
|
||||
|
||||
labels, err := conversion.ToStringInterfaceMap(ctx, model.Labels)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting to Go map: %w", err)
|
||||
}
|
||||
|
||||
return &iaas.CreateNetworkAreaPayload{
|
||||
Name: conversion.StringValueToPointer(model.Name),
|
||||
AddressFamily: &iaas.CreateAreaAddressFamily{
|
||||
Ipv4: &iaas.CreateAreaIPv4{
|
||||
DefaultNameservers: &modelDefaultNameservers,
|
||||
NetworkRanges: networkRangesPayload,
|
||||
TransferNetwork: conversion.StringValueToPointer(model.TransferNetwork),
|
||||
DefaultPrefixLen: conversion.Int64ValueToPointer(model.DefaultPrefixLength),
|
||||
MaxPrefixLen: conversion.Int64ValueToPointer(model.MaxPrefixLength),
|
||||
MinPrefixLen: conversion.Int64ValueToPointer(model.MinPrefixLength),
|
||||
},
|
||||
return &iaas.CreateNetworkAreaRegionPayload{
|
||||
Ipv4: &iaas.RegionalAreaIPv4{
|
||||
DefaultNameservers: &modelDefaultNameservers,
|
||||
DefaultPrefixLen: conversion.Int64ValueToPointer(model.DefaultPrefixLength),
|
||||
MaxPrefixLen: conversion.Int64ValueToPointer(model.MaxPrefixLength),
|
||||
MinPrefixLen: conversion.Int64ValueToPointer(model.MinPrefixLength),
|
||||
TransferNetwork: conversion.StringValueToPointer(model.TransferNetwork),
|
||||
NetworkRanges: networkRangesPayload,
|
||||
},
|
||||
Labels: &labels,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -658,6 +869,40 @@ func toUpdatePayload(ctx context.Context, model *Model, currentLabels types.Map)
|
|||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
|
||||
labels, err := conversion.ToJSONMapPartialUpdatePayload(ctx, currentLabels, model.Labels)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting to Go map: %w", err)
|
||||
}
|
||||
|
||||
return &iaas.PartialUpdateNetworkAreaPayload{
|
||||
Name: conversion.StringValueToPointer(model.Name),
|
||||
Labels: &labels,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Deprecated: toRegionUpdatePayload will be removed in May 2026. Implementation won't be needed anymore because of the IaaS API v1 -> v2 migration. Func was only introduced to circumvent breaking changes.
|
||||
func toRegionUpdatePayload(ctx context.Context, model *Model) (*iaas.UpdateNetworkAreaRegionPayload, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
|
||||
modelDefaultNameservers, err := toDefaultNameserversPayload(ctx, model)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting default nameservers: %w", err)
|
||||
}
|
||||
|
||||
return &iaas.UpdateNetworkAreaRegionPayload{
|
||||
Ipv4: &iaas.UpdateRegionalAreaIPv4{
|
||||
DefaultNameservers: &modelDefaultNameservers,
|
||||
DefaultPrefixLen: conversion.Int64ValueToPointer(model.DefaultPrefixLength),
|
||||
MaxPrefixLen: conversion.Int64ValueToPointer(model.MaxPrefixLength),
|
||||
MinPrefixLen: conversion.Int64ValueToPointer(model.MinPrefixLength),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Deprecated: toDefaultNameserversPayload will be removed in May 2026. Implementation won't be needed anymore because of the IaaS API v1 -> v2 migration. Func was only introduced to circumvent breaking changes.
|
||||
func toDefaultNameserversPayload(_ context.Context, model *Model) ([]string, error) {
|
||||
modelDefaultNameservers := []string{}
|
||||
for _, ns := range model.DefaultNameservers.Elements() {
|
||||
nameserverString, ok := ns.(types.String)
|
||||
|
|
@ -667,25 +912,10 @@ func toUpdatePayload(ctx context.Context, model *Model, currentLabels types.Map)
|
|||
modelDefaultNameservers = append(modelDefaultNameservers, nameserverString.ValueString())
|
||||
}
|
||||
|
||||
labels, err := conversion.ToJSONMapPartialUpdatePayload(ctx, currentLabels, model.Labels)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting to Go map: %w", err)
|
||||
}
|
||||
|
||||
return &iaas.PartialUpdateNetworkAreaPayload{
|
||||
Name: conversion.StringValueToPointer(model.Name),
|
||||
AddressFamily: &iaas.UpdateAreaAddressFamily{
|
||||
Ipv4: &iaas.UpdateAreaIPv4{
|
||||
DefaultNameservers: &modelDefaultNameservers,
|
||||
DefaultPrefixLen: conversion.Int64ValueToPointer(model.DefaultPrefixLength),
|
||||
MaxPrefixLen: conversion.Int64ValueToPointer(model.MaxPrefixLength),
|
||||
MinPrefixLen: conversion.Int64ValueToPointer(model.MinPrefixLength),
|
||||
},
|
||||
},
|
||||
Labels: &labels,
|
||||
}, nil
|
||||
return modelDefaultNameservers, nil
|
||||
}
|
||||
|
||||
// Deprecated: toNetworkRangesPayload will be removed in May 2026. Implementation won't be needed anymore because of the IaaS API v1 -> v2 migration. Func was only introduced to circumvent breaking changes.
|
||||
func toNetworkRangesPayload(ctx context.Context, model *Model) (*[]iaas.NetworkRange, error) {
|
||||
if model.NetworkRanges.IsNull() || model.NetworkRanges.IsUnknown() {
|
||||
return nil, nil
|
||||
|
|
@ -712,10 +942,10 @@ func toNetworkRangesPayload(ctx context.Context, model *Model) (*[]iaas.NetworkR
|
|||
return &payload, nil
|
||||
}
|
||||
|
||||
// updateNetworkRanges creates and deletes network ranges so that network area ranges are the ones in the model
|
||||
// Deprecated: updateNetworkRanges creates and deletes network ranges so that network area ranges are the ones in the model. This was only kept to make the v1 -> v2 IaaS API migration non-breaking in the Terraform provider.
|
||||
func updateNetworkRanges(ctx context.Context, organizationId, networkAreaId string, ranges []networkRange, client *iaas.APIClient) error {
|
||||
// Get network ranges current state
|
||||
currentNetworkRangesResp, err := client.ListNetworkAreaRanges(ctx, organizationId, networkAreaId).Execute()
|
||||
currentNetworkRangesResp, err := client.ListNetworkAreaRanges(ctx, organizationId, networkAreaId, "eu01").Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading network area ranges: %w", err)
|
||||
}
|
||||
|
|
@ -739,13 +969,13 @@ func updateNetworkRanges(ctx context.Context, organizationId, networkAreaId stri
|
|||
networkRangesState[prefix] = &networkRangeState{}
|
||||
}
|
||||
networkRangesState[prefix].isCreated = true
|
||||
networkRangesState[prefix].id = *networkRange.NetworkRangeId
|
||||
networkRangesState[prefix].id = *networkRange.Id
|
||||
}
|
||||
|
||||
// Delete network ranges
|
||||
for prefix, state := range networkRangesState {
|
||||
if !state.isInModel && state.isCreated {
|
||||
err := client.DeleteNetworkAreaRange(ctx, organizationId, networkAreaId, state.id).Execute()
|
||||
err := client.DeleteNetworkAreaRange(ctx, organizationId, networkAreaId, "eu01", state.id).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting network area range '%v': %w", prefix, err)
|
||||
}
|
||||
|
|
@ -763,7 +993,7 @@ func updateNetworkRanges(ctx context.Context, organizationId, networkAreaId stri
|
|||
},
|
||||
}
|
||||
|
||||
_, err := client.CreateNetworkAreaRange(ctx, organizationId, networkAreaId).CreateNetworkAreaRangePayload(payload).Execute()
|
||||
_, err := client.CreateNetworkAreaRange(ctx, organizationId, networkAreaId, "eu01").CreateNetworkAreaRangePayload(payload).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating network range '%v': %w", prefix, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,16 +28,15 @@ var testRangeId2Repeated = uuid.NewString()
|
|||
|
||||
func TestMapFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
state Model
|
||||
input *iaas.NetworkArea
|
||||
ListNetworkRanges *[]iaas.NetworkRange
|
||||
expected Model
|
||||
isValid bool
|
||||
description string
|
||||
state Model
|
||||
input *iaas.NetworkArea
|
||||
expected Model
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"id_ok",
|
||||
Model{
|
||||
description: "id_ok",
|
||||
state: Model{
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
|
|
@ -50,32 +49,16 @@ func TestMapFields(t *testing.T) {
|
|||
"prefix": types.StringValue("prefix-2"),
|
||||
}),
|
||||
}),
|
||||
DefaultNameservers: types.ListNull(types.StringType),
|
||||
},
|
||||
&iaas.NetworkArea{
|
||||
AreaId: utils.Ptr("naid"),
|
||||
Ipv4: &iaas.NetworkAreaIPv4{},
|
||||
input: &iaas.NetworkArea{
|
||||
Id: utils.Ptr("naid"),
|
||||
},
|
||||
&[]iaas.NetworkRange{
|
||||
{
|
||||
NetworkRangeId: utils.Ptr(testRangeId1),
|
||||
Prefix: utils.Ptr("prefix-1"),
|
||||
},
|
||||
{
|
||||
NetworkRangeId: utils.Ptr(testRangeId2),
|
||||
Prefix: utils.Ptr("prefix-2"),
|
||||
},
|
||||
},
|
||||
|
||||
Model{
|
||||
Id: types.StringValue("oid,naid"),
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
Name: types.StringNull(),
|
||||
DefaultNameservers: types.ListNull(types.StringType),
|
||||
TransferNetwork: types.StringNull(),
|
||||
DefaultPrefixLength: types.Int64Null(),
|
||||
MaxPrefixLength: types.Int64Null(),
|
||||
MinPrefixLength: types.Int64Null(),
|
||||
expected: Model{
|
||||
Id: types.StringValue("oid,naid"),
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
Name: types.StringNull(),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId1),
|
||||
|
|
@ -86,13 +69,14 @@ func TestMapFields(t *testing.T) {
|
|||
"prefix": types.StringValue("prefix-2"),
|
||||
}),
|
||||
}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
DefaultNameservers: types.ListNull(types.StringType),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"values_ok",
|
||||
Model{
|
||||
description: "values_ok",
|
||||
state: Model{
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
|
|
@ -105,47 +89,20 @@ func TestMapFields(t *testing.T) {
|
|||
"prefix": types.StringValue("prefix-2"),
|
||||
}),
|
||||
}),
|
||||
DefaultNameservers: types.ListNull(types.StringType),
|
||||
},
|
||||
&iaas.NetworkArea{
|
||||
AreaId: utils.Ptr("naid"),
|
||||
Ipv4: &iaas.NetworkAreaIPv4{
|
||||
DefaultNameservers: &[]string{
|
||||
"nameserver1",
|
||||
"nameserver2",
|
||||
},
|
||||
TransferNetwork: utils.Ptr("network"),
|
||||
DefaultPrefixLen: utils.Ptr(int64(20)),
|
||||
MaxPrefixLen: utils.Ptr(int64(22)),
|
||||
MinPrefixLen: utils.Ptr(int64(18)),
|
||||
},
|
||||
input: &iaas.NetworkArea{
|
||||
Id: utils.Ptr("naid"),
|
||||
Name: utils.Ptr("name"),
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
&[]iaas.NetworkRange{
|
||||
{
|
||||
NetworkRangeId: utils.Ptr(testRangeId1),
|
||||
Prefix: utils.Ptr("prefix-1"),
|
||||
},
|
||||
{
|
||||
NetworkRangeId: utils.Ptr(testRangeId2),
|
||||
Prefix: utils.Ptr("prefix-2"),
|
||||
},
|
||||
},
|
||||
Model{
|
||||
expected: Model{
|
||||
Id: types.StringValue("oid,naid"),
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
Name: types.StringValue("name"),
|
||||
DefaultNameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("nameserver1"),
|
||||
types.StringValue("nameserver2"),
|
||||
}),
|
||||
TransferNetwork: types.StringValue("network"),
|
||||
DefaultPrefixLength: types.Int64Value(20),
|
||||
MaxPrefixLength: types.Int64Value(22),
|
||||
MinPrefixLength: types.Int64Value(18),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId1),
|
||||
|
|
@ -159,207 +116,53 @@ func TestMapFields(t *testing.T) {
|
|||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"model and response have ranges in different order",
|
||||
Model{
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId1),
|
||||
"prefix": types.StringValue("prefix-1"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId2),
|
||||
"prefix": types.StringValue("prefix-2"),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
&iaas.NetworkArea{
|
||||
AreaId: utils.Ptr("naid"),
|
||||
Ipv4: &iaas.NetworkAreaIPv4{
|
||||
DefaultNameservers: &[]string{
|
||||
"nameserver1",
|
||||
"nameserver2",
|
||||
},
|
||||
TransferNetwork: utils.Ptr("network"),
|
||||
DefaultPrefixLen: utils.Ptr(int64(20)),
|
||||
MaxPrefixLen: utils.Ptr(int64(22)),
|
||||
MinPrefixLen: utils.Ptr(int64(18)),
|
||||
},
|
||||
Name: utils.Ptr("name"),
|
||||
},
|
||||
&[]iaas.NetworkRange{
|
||||
{
|
||||
NetworkRangeId: utils.Ptr(testRangeId2),
|
||||
Prefix: utils.Ptr("prefix-2"),
|
||||
},
|
||||
{
|
||||
NetworkRangeId: utils.Ptr(testRangeId3),
|
||||
Prefix: utils.Ptr("prefix-3"),
|
||||
},
|
||||
{
|
||||
NetworkRangeId: utils.Ptr(testRangeId1),
|
||||
Prefix: utils.Ptr("prefix-1"),
|
||||
},
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("oid,naid"),
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
Name: types.StringValue("name"),
|
||||
DefaultNameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("nameserver1"),
|
||||
types.StringValue("nameserver2"),
|
||||
}),
|
||||
TransferNetwork: types.StringValue("network"),
|
||||
DefaultPrefixLength: types.Int64Value(20),
|
||||
MaxPrefixLength: types.Int64Value(22),
|
||||
MinPrefixLength: types.Int64Value(18),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId1),
|
||||
"prefix": types.StringValue("prefix-1"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId2),
|
||||
"prefix": types.StringValue("prefix-2"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId3),
|
||||
"prefix": types.StringValue("prefix-3"),
|
||||
}),
|
||||
}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"default_nameservers_changed_outside_tf",
|
||||
Model{
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
DefaultNameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId1),
|
||||
"prefix": types.StringValue("prefix-1"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId2),
|
||||
"prefix": types.StringValue("prefix-2"),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
&iaas.NetworkArea{
|
||||
AreaId: utils.Ptr("naid"),
|
||||
Ipv4: &iaas.NetworkAreaIPv4{
|
||||
DefaultNameservers: &[]string{
|
||||
"ns2",
|
||||
"ns3",
|
||||
},
|
||||
},
|
||||
},
|
||||
&[]iaas.NetworkRange{
|
||||
{
|
||||
NetworkRangeId: utils.Ptr(testRangeId1),
|
||||
Prefix: utils.Ptr("prefix-1"),
|
||||
},
|
||||
{
|
||||
NetworkRangeId: utils.Ptr(testRangeId2),
|
||||
Prefix: utils.Ptr("prefix-2"),
|
||||
},
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("oid,naid"),
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
DefaultNameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns2"),
|
||||
types.StringValue("ns3"),
|
||||
}),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId1),
|
||||
"prefix": types.StringValue("prefix-1"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId2),
|
||||
"prefix": types.StringValue("prefix-2"),
|
||||
}),
|
||||
}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"network_ranges_changed_outside_tf",
|
||||
Model{
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId1),
|
||||
"prefix": types.StringValue("prefix-1"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId2),
|
||||
"prefix": types.StringValue("prefix-2"),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
&iaas.NetworkArea{
|
||||
AreaId: utils.Ptr("naid"),
|
||||
Ipv4: &iaas.NetworkAreaIPv4{},
|
||||
},
|
||||
&[]iaas.NetworkRange{
|
||||
{
|
||||
NetworkRangeId: utils.Ptr(testRangeId2),
|
||||
Prefix: utils.Ptr("prefix-2"),
|
||||
},
|
||||
{
|
||||
NetworkRangeId: utils.Ptr(testRangeId3),
|
||||
Prefix: utils.Ptr("prefix-3"),
|
||||
},
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("oid,naid"),
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
DefaultNameservers: types.ListNull(types.StringType),
|
||||
},
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
description: "default_nameservers_changed_outside_tf",
|
||||
state: Model{
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId1),
|
||||
"prefix": types.StringValue("prefix-1"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId2),
|
||||
"prefix": types.StringValue("prefix-2"),
|
||||
}),
|
||||
}),
|
||||
DefaultNameservers: types.ListNull(types.StringType),
|
||||
},
|
||||
input: &iaas.NetworkArea{
|
||||
Id: utils.Ptr("naid"),
|
||||
},
|
||||
expected: Model{
|
||||
Id: types.StringValue("oid,naid"),
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId3),
|
||||
"prefix": types.StringValue("prefix-3"),
|
||||
"network_range_id": types.StringValue(testRangeId1),
|
||||
"prefix": types.StringValue("prefix-1"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId2),
|
||||
"prefix": types.StringValue("prefix-2"),
|
||||
}),
|
||||
}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
DefaultNameservers: types.ListNull(types.StringType),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"nil_network_ranges_list",
|
||||
Model{},
|
||||
&iaas.NetworkArea{},
|
||||
nil,
|
||||
Model{},
|
||||
false,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"response_nil_fail",
|
||||
Model{},
|
||||
nil,
|
||||
nil,
|
||||
Model{},
|
||||
false,
|
||||
},
|
||||
|
|
@ -369,14 +172,13 @@ func TestMapFields(t *testing.T) {
|
|||
OrganizationId: types.StringValue("oid"),
|
||||
},
|
||||
&iaas.NetworkArea{},
|
||||
&[]iaas.NetworkRange{},
|
||||
Model{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
err := mapFields(context.Background(), tt.input, tt.ListNetworkRanges, &tt.state)
|
||||
err := mapFields(context.Background(), tt.input, &tt.state)
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
|
|
@ -393,6 +195,243 @@ func TestMapFields(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026.
|
||||
func Test_MapNetworkRanges(t *testing.T) {
|
||||
type args struct {
|
||||
networkAreaRangesList *[]iaas.NetworkRange
|
||||
model *Model
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *Model
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "model and response have ranges in different order",
|
||||
args: args{
|
||||
model: &Model{
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
DefaultNameservers: types.ListNull(types.StringType),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId1),
|
||||
"prefix": types.StringValue("prefix-1"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId2),
|
||||
"prefix": types.StringValue("prefix-2"),
|
||||
}),
|
||||
}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
networkAreaRangesList: &[]iaas.NetworkRange{
|
||||
{
|
||||
Id: utils.Ptr(testRangeId2),
|
||||
Prefix: utils.Ptr("prefix-2"),
|
||||
},
|
||||
{
|
||||
Id: utils.Ptr(testRangeId3),
|
||||
Prefix: utils.Ptr("prefix-3"),
|
||||
},
|
||||
{
|
||||
Id: utils.Ptr(testRangeId1),
|
||||
Prefix: utils.Ptr("prefix-1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &Model{
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId1),
|
||||
"prefix": types.StringValue("prefix-1"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId2),
|
||||
"prefix": types.StringValue("prefix-2"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId3),
|
||||
"prefix": types.StringValue("prefix-3"),
|
||||
}),
|
||||
}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
DefaultNameservers: types.ListNull(types.StringType),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "network_ranges_changed_outside_tf",
|
||||
args: args{
|
||||
model: &Model{
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId1),
|
||||
"prefix": types.StringValue("prefix-1"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId2),
|
||||
"prefix": types.StringValue("prefix-2"),
|
||||
}),
|
||||
}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
DefaultNameservers: types.ListNull(types.StringType),
|
||||
},
|
||||
networkAreaRangesList: &[]iaas.NetworkRange{
|
||||
{
|
||||
Id: utils.Ptr(testRangeId2),
|
||||
Prefix: utils.Ptr("prefix-2"),
|
||||
},
|
||||
{
|
||||
Id: utils.Ptr(testRangeId3),
|
||||
Prefix: utils.Ptr("prefix-3"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &Model{
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId2),
|
||||
"prefix": types.StringValue("prefix-2"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId3),
|
||||
"prefix": types.StringValue("prefix-3"),
|
||||
}),
|
||||
}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
DefaultNameservers: types.ListNull(types.StringType),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := mapNetworkRanges(context.Background(), tt.args.networkAreaRangesList, tt.args.model); (err != nil) != tt.wantErr {
|
||||
t.Errorf("mapNetworkRanges() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
||||
diff := cmp.Diff(tt.args.model, tt.want)
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026.
|
||||
func TestMapNetworkAreaRegionFields(t *testing.T) {
|
||||
type args struct {
|
||||
networkAreaRegionResp *iaas.RegionalArea
|
||||
model *Model
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *Model
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "default",
|
||||
args: args{
|
||||
model: &Model{
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
networkAreaRegionResp: &iaas.RegionalArea{
|
||||
Ipv4: &iaas.RegionalAreaIPv4{
|
||||
DefaultNameservers: &[]string{
|
||||
"nameserver1",
|
||||
"nameserver2",
|
||||
},
|
||||
TransferNetwork: utils.Ptr("network"),
|
||||
DefaultPrefixLen: utils.Ptr(int64(20)),
|
||||
MaxPrefixLen: utils.Ptr(int64(22)),
|
||||
MinPrefixLen: utils.Ptr(int64(18)),
|
||||
NetworkRanges: &[]iaas.NetworkRange{
|
||||
{
|
||||
Id: utils.Ptr(testRangeId1),
|
||||
Prefix: utils.Ptr("prefix-1"),
|
||||
},
|
||||
{
|
||||
Id: utils.Ptr(testRangeId2),
|
||||
Prefix: utils.Ptr("prefix-2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &Model{
|
||||
DefaultNameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("nameserver1"),
|
||||
types.StringValue("nameserver2"),
|
||||
}),
|
||||
TransferNetwork: types.StringValue("network"),
|
||||
DefaultPrefixLength: types.Int64Value(20),
|
||||
MaxPrefixLength: types.Int64Value(22),
|
||||
MinPrefixLength: types.Int64Value(18),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId1),
|
||||
"prefix": types.StringValue("prefix-1"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringValue(testRangeId2),
|
||||
"prefix": types.StringValue("prefix-2"),
|
||||
}),
|
||||
}),
|
||||
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "model is nil",
|
||||
args: args{
|
||||
model: nil,
|
||||
networkAreaRegionResp: &iaas.RegionalArea{},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "network area region response is nil",
|
||||
args: args{
|
||||
model: &Model{
|
||||
DefaultNameservers: types.ListNull(types.StringType),
|
||||
NetworkRanges: types.ListNull(types.ObjectType{AttrTypes: networkRangeTypes}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
networkAreaRegionResp: nil,
|
||||
},
|
||||
want: &Model{
|
||||
DefaultNameservers: types.ListNull(types.StringType),
|
||||
NetworkRanges: types.ListNull(types.ObjectType{AttrTypes: networkRangeTypes}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := mapNetworkAreaRegionFields(context.Background(), tt.args.networkAreaRegionResp, tt.args.model); (err != nil) != tt.wantErr {
|
||||
t.Errorf("mapNetworkAreaRegionFields() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
||||
diff := cmp.Diff(tt.args.model, tt.want)
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToCreatePayload(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
|
|
@ -404,50 +443,12 @@ func TestToCreatePayload(t *testing.T) {
|
|||
"default_ok",
|
||||
&Model{
|
||||
Name: types.StringValue("name"),
|
||||
DefaultNameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringUnknown(),
|
||||
"prefix": types.StringValue("pr-1"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringUnknown(),
|
||||
"prefix": types.StringValue("pr-2"),
|
||||
}),
|
||||
}),
|
||||
TransferNetwork: types.StringValue("network"),
|
||||
DefaultPrefixLength: types.Int64Value(20),
|
||||
MaxPrefixLength: types.Int64Value(22),
|
||||
MinPrefixLength: types.Int64Value(18),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
},
|
||||
&iaas.CreateNetworkAreaPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
AddressFamily: &iaas.CreateAreaAddressFamily{
|
||||
Ipv4: &iaas.CreateAreaIPv4{
|
||||
DefaultNameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
NetworkRanges: &[]iaas.NetworkRange{
|
||||
{
|
||||
Prefix: utils.Ptr("pr-1"),
|
||||
},
|
||||
{
|
||||
Prefix: utils.Ptr("pr-2"),
|
||||
},
|
||||
},
|
||||
TransferNetwork: utils.Ptr("network"),
|
||||
DefaultPrefixLen: utils.Ptr(int64(20)),
|
||||
MaxPrefixLen: utils.Ptr(int64(22)),
|
||||
MinPrefixLen: utils.Ptr(int64(18)),
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
|
|
@ -474,6 +475,86 @@ func TestToCreatePayload(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026.
|
||||
func TestToRegionCreatePayload(t *testing.T) {
|
||||
type args struct {
|
||||
model *Model
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *iaas.CreateNetworkAreaRegionPayload
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "default_ok",
|
||||
args: args{
|
||||
model: &Model{
|
||||
DefaultNameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
NetworkRanges: types.ListValueMust(types.ObjectType{AttrTypes: networkRangeTypes}, []attr.Value{
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringUnknown(),
|
||||
"prefix": types.StringValue("pr-1"),
|
||||
}),
|
||||
types.ObjectValueMust(networkRangeTypes, map[string]attr.Value{
|
||||
"network_range_id": types.StringUnknown(),
|
||||
"prefix": types.StringValue("pr-2"),
|
||||
}),
|
||||
}),
|
||||
TransferNetwork: types.StringValue("network"),
|
||||
DefaultPrefixLength: types.Int64Value(20),
|
||||
MaxPrefixLength: types.Int64Value(22),
|
||||
MinPrefixLength: types.Int64Value(18),
|
||||
},
|
||||
},
|
||||
want: &iaas.CreateNetworkAreaRegionPayload{
|
||||
Ipv4: &iaas.RegionalAreaIPv4{
|
||||
DefaultNameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
NetworkRanges: &[]iaas.NetworkRange{
|
||||
{
|
||||
Prefix: utils.Ptr("pr-1"),
|
||||
},
|
||||
{
|
||||
Prefix: utils.Ptr("pr-2"),
|
||||
},
|
||||
},
|
||||
TransferNetwork: utils.Ptr("network"),
|
||||
DefaultPrefixLen: utils.Ptr(int64(20)),
|
||||
MaxPrefixLen: utils.Ptr(int64(22)),
|
||||
MinPrefixLen: utils.Ptr(int64(18)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "model is nil",
|
||||
args: args{
|
||||
model: nil,
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := toRegionCreatePayload(context.Background(), tt.args.model)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("toRegionCreatePayload() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
diff := cmp.Diff(got, tt.want)
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToUpdatePayload(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
|
|
@ -485,30 +566,12 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
"default_ok",
|
||||
&Model{
|
||||
Name: types.StringValue("name"),
|
||||
DefaultNameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
DefaultPrefixLength: types.Int64Value(22),
|
||||
MaxPrefixLength: types.Int64Value(24),
|
||||
MinPrefixLength: types.Int64Value(20),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
},
|
||||
&iaas.PartialUpdateNetworkAreaPayload{
|
||||
Name: utils.Ptr("name"),
|
||||
AddressFamily: &iaas.UpdateAreaAddressFamily{
|
||||
Ipv4: &iaas.UpdateAreaIPv4{
|
||||
DefaultNameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
DefaultPrefixLen: utils.Ptr(int64(22)),
|
||||
MaxPrefixLen: utils.Ptr(int64(24)),
|
||||
MinPrefixLen: utils.Ptr(int64(20)),
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
|
|
@ -535,24 +598,84 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed in May 2026.
|
||||
func TestToRegionUpdatePayload(t *testing.T) {
|
||||
type args struct {
|
||||
model *Model
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *iaas.UpdateNetworkAreaRegionPayload
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "default_ok",
|
||||
args: args{
|
||||
model: &Model{
|
||||
DefaultNameservers: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("ns1"),
|
||||
types.StringValue("ns2"),
|
||||
}),
|
||||
DefaultPrefixLength: types.Int64Value(22),
|
||||
MaxPrefixLength: types.Int64Value(24),
|
||||
MinPrefixLength: types.Int64Value(20),
|
||||
},
|
||||
},
|
||||
want: &iaas.UpdateNetworkAreaRegionPayload{
|
||||
Ipv4: &iaas.UpdateRegionalAreaIPv4{
|
||||
DefaultNameservers: &[]string{
|
||||
"ns1",
|
||||
"ns2",
|
||||
},
|
||||
DefaultPrefixLen: utils.Ptr(int64(22)),
|
||||
MaxPrefixLen: utils.Ptr(int64(24)),
|
||||
MinPrefixLen: utils.Ptr(int64(20)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "model is nil",
|
||||
args: args{
|
||||
model: nil,
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := toRegionUpdatePayload(context.Background(), tt.args.model)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("toRegionUpdatePayload() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
diff := cmp.Diff(got, tt.want)
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateNetworkRanges(t *testing.T) {
|
||||
getAllNetworkRangesResp := iaas.NetworkRangeListResponse{
|
||||
Items: &[]iaas.NetworkRange{
|
||||
{
|
||||
Prefix: utils.Ptr("pr-1"),
|
||||
NetworkRangeId: utils.Ptr(testRangeId1),
|
||||
Prefix: utils.Ptr("pr-1"),
|
||||
Id: utils.Ptr(testRangeId1),
|
||||
},
|
||||
{
|
||||
Prefix: utils.Ptr("pr-2"),
|
||||
NetworkRangeId: utils.Ptr(testRangeId2),
|
||||
Prefix: utils.Ptr("pr-2"),
|
||||
Id: utils.Ptr(testRangeId2),
|
||||
},
|
||||
{
|
||||
Prefix: utils.Ptr("pr-3"),
|
||||
NetworkRangeId: utils.Ptr(testRangeId3),
|
||||
Prefix: utils.Ptr("pr-3"),
|
||||
Id: utils.Ptr(testRangeId3),
|
||||
},
|
||||
{
|
||||
Prefix: utils.Ptr("pr-2"),
|
||||
NetworkRangeId: utils.Ptr(testRangeId2Repeated),
|
||||
Prefix: utils.Ptr("pr-2"),
|
||||
Id: utils.Ptr(testRangeId2Repeated),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -903,8 +1026,8 @@ func TestUpdateNetworkRanges(t *testing.T) {
|
|||
}
|
||||
|
||||
resp := iaas.NetworkRange{
|
||||
Prefix: utils.Ptr("prefix"),
|
||||
NetworkRangeId: utils.Ptr("id-range"),
|
||||
Prefix: utils.Ptr("prefix"),
|
||||
Id: utils.Ptr("id-range"),
|
||||
}
|
||||
respBytes, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
|
|
@ -930,7 +1053,7 @@ func TestUpdateNetworkRanges(t *testing.T) {
|
|||
|
||||
var prefix string
|
||||
for _, rangeItem := range *getAllNetworkRangesResp.Items {
|
||||
if *rangeItem.NetworkRangeId == networkRangeId {
|
||||
if *rangeItem.Id == networkRangeId {
|
||||
prefix = *rangeItem.Prefix
|
||||
}
|
||||
}
|
||||
|
|
@ -963,14 +1086,14 @@ func TestUpdateNetworkRanges(t *testing.T) {
|
|||
|
||||
// Setup server and client
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/v1/organizations/{organizationId}/network-areas/{areaId}/network-ranges", func(w http.ResponseWriter, r *http.Request) {
|
||||
router.HandleFunc("/v2/organizations/{organizationId}/network-areas/{areaId}/regions/{region}/network-ranges", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" {
|
||||
getAllNetworkRangesHandler(w, r)
|
||||
} else if r.Method == "POST" {
|
||||
createNetworkRangeHandler(w, r)
|
||||
}
|
||||
})
|
||||
router.HandleFunc("/v1/organizations/{organizationId}/network-areas/{areaId}/network-ranges/{networkRangeId}", deleteNetworkRangeHandler)
|
||||
router.HandleFunc("/v2/organizations/{organizationId}/network-areas/{areaId}/regions/{region}/network-ranges/{networkRangeId}", deleteNetworkRangeHandler)
|
||||
mockedServer := httptest.NewServer(router)
|
||||
defer mockedServer.Close()
|
||||
client, err := iaas.NewAPIClient(
|
||||
|
|
|
|||
181
stackit/internal/services/iaas/networkarearegion/datasource.go
Normal file
181
stackit/internal/services/iaas/networkarearegion/datasource.go
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
package networkarearegion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ datasource.DataSource = &networkAreaRegionDataSource{}
|
||||
)
|
||||
|
||||
// NewNetworkAreaRegionDataSource is a helper function to simplify the provider implementation.
|
||||
func NewNetworkAreaRegionDataSource() datasource.DataSource {
|
||||
return &networkAreaRegionDataSource{}
|
||||
}
|
||||
|
||||
// networkAreaRegionDataSource is the data source implementation.
|
||||
type networkAreaRegionDataSource struct {
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
func (d *networkAreaRegionDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_network_area_region"
|
||||
}
|
||||
|
||||
func (d *networkAreaRegionDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
||||
var ok bool
|
||||
d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
apiClient := iaasUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
d.client = apiClient
|
||||
tflog.Info(ctx, "iaas client configured")
|
||||
}
|
||||
|
||||
// Schema defines the schema for the resource.
|
||||
func (d *networkAreaRegionDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
description := "Network area region data source schema."
|
||||
|
||||
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 \"`organization_id`,`network_area_id`,`region`\".",
|
||||
Computed: true,
|
||||
},
|
||||
"organization_id": schema.StringAttribute{
|
||||
Description: "STACKIT organization ID to which the network area is associated.",
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"network_area_id": schema.StringAttribute{
|
||||
Description: "The network area ID.",
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
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,
|
||||
},
|
||||
"ipv4": schema.SingleNestedAttribute{
|
||||
Computed: true,
|
||||
Description: "The regional IPv4 config of a network area.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"default_nameservers": schema.ListAttribute{
|
||||
Description: "List of DNS Servers/Nameservers.",
|
||||
Computed: true,
|
||||
ElementType: types.StringType,
|
||||
},
|
||||
"network_ranges": schema.ListNestedAttribute{
|
||||
Description: "List of Network ranges.",
|
||||
Computed: true,
|
||||
Validators: []validator.List{
|
||||
listvalidator.SizeAtLeast(1),
|
||||
listvalidator.SizeAtMost(64),
|
||||
},
|
||||
NestedObject: schema.NestedAttributeObject{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"network_range_id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"prefix": schema.StringAttribute{
|
||||
Description: "Classless Inter-Domain Routing (CIDR).",
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"transfer_network": schema.StringAttribute{
|
||||
Description: "IPv4 Classless Inter-Domain Routing (CIDR).",
|
||||
Computed: true,
|
||||
},
|
||||
"default_prefix_length": schema.Int64Attribute{
|
||||
Description: "The default prefix length for networks in the network area.",
|
||||
Computed: true,
|
||||
},
|
||||
"max_prefix_length": schema.Int64Attribute{
|
||||
Description: "The maximal prefix length for networks in the network area.",
|
||||
Computed: true,
|
||||
},
|
||||
"min_prefix_length": schema.Int64Attribute{
|
||||
Description: "The minimal prefix length for networks in the network area.",
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Read refreshes the Terraform state with the latest data.
|
||||
func (d *networkAreaRegionDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
var model Model
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
organizationId := model.OrganizationId.ValueString()
|
||||
networkAreaId := model.NetworkAreaId.ValueString()
|
||||
region := d.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "organization_id", organizationId)
|
||||
ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
networkAreaRegionResp, err := d.client.GetNetworkAreaRegion(ctx, organizationId, networkAreaId, region).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(ctx, &resp.Diagnostics, err, "Reading network area region", fmt.Sprintf("Region configuration for %q for network area %q does not exist.", region, networkAreaId), nil)
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, networkAreaRegionResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network area region", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Set refreshed state
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Network area region read")
|
||||
}
|
||||
728
stackit/internal/services/iaas/networkarearegion/resource.go
Normal file
728
stackit/internal/services/iaas/networkarearegion/resource.go
Normal file
|
|
@ -0,0 +1,728 @@
|
|||
package networkarearegion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/resourcemanager"
|
||||
resourcemanagerUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/resourcemanager/utils"
|
||||
|
||||
sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas/wait"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ resource.Resource = &networkAreaRegionResource{}
|
||||
_ resource.ResourceWithConfigure = &networkAreaRegionResource{}
|
||||
_ resource.ResourceWithImportState = &networkAreaRegionResource{}
|
||||
_ resource.ResourceWithModifyPlan = &networkAreaRegionResource{}
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
OrganizationId types.String `tfsdk:"organization_id"`
|
||||
NetworkAreaId types.String `tfsdk:"network_area_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
Ipv4 *ipv4Model `tfsdk:"ipv4"`
|
||||
}
|
||||
|
||||
// Struct corresponding to Model.Ipv4
|
||||
type ipv4Model struct {
|
||||
DefaultNameservers types.List `tfsdk:"default_nameservers"`
|
||||
NetworkRanges []networkRangeModel `tfsdk:"network_ranges"`
|
||||
TransferNetwork types.String `tfsdk:"transfer_network"`
|
||||
DefaultPrefixLength types.Int64 `tfsdk:"default_prefix_length"`
|
||||
MaxPrefixLength types.Int64 `tfsdk:"max_prefix_length"`
|
||||
MinPrefixLength types.Int64 `tfsdk:"min_prefix_length"`
|
||||
}
|
||||
|
||||
// Struct corresponding to Model.NetworkRanges[i]
|
||||
type networkRangeModel struct {
|
||||
Prefix types.String `tfsdk:"prefix"`
|
||||
NetworkRangeId types.String `tfsdk:"network_range_id"`
|
||||
}
|
||||
|
||||
// NewNetworkAreaRegionResource is a helper function to simplify the provider implementation.
|
||||
func NewNetworkAreaRegionResource() resource.Resource {
|
||||
return &networkAreaRegionResource{}
|
||||
}
|
||||
|
||||
// networkAreaRegionResource is the resource implementation.
|
||||
type networkAreaRegionResource struct {
|
||||
client *iaas.APIClient
|
||||
resourceManagerClient *resourcemanager.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
func (r *networkAreaRegionResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_network_area_region"
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
func (r *networkAreaRegionResource) 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
|
||||
}
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *networkAreaRegionResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
var ok bool
|
||||
r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
r.client = iaasUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
r.resourceManagerClient = resourcemanagerUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "iaas client configured")
|
||||
}
|
||||
|
||||
// Schema defines the schema for the resource.
|
||||
func (r *networkAreaRegionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
description := "Network area region resource schema."
|
||||
|
||||
resp.Schema = schema.Schema{
|
||||
Description: description,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`organization_id`,`network_area_id`,`region`\".",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"organization_id": schema.StringAttribute{
|
||||
Description: "STACKIT organization ID to which the network area is associated.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"network_area_id": schema.StringAttribute{
|
||||
Description: "The network area ID.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
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(),
|
||||
},
|
||||
},
|
||||
"ipv4": schema.SingleNestedAttribute{
|
||||
Description: "The regional IPv4 config of a network area.",
|
||||
Required: true,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"default_nameservers": schema.ListAttribute{
|
||||
Description: "List of DNS Servers/Nameservers.",
|
||||
Optional: true,
|
||||
ElementType: types.StringType,
|
||||
},
|
||||
"network_ranges": schema.ListNestedAttribute{
|
||||
Description: "List of Network ranges.",
|
||||
Required: true,
|
||||
Validators: []validator.List{
|
||||
listvalidator.SizeAtLeast(1),
|
||||
listvalidator.SizeAtMost(64),
|
||||
},
|
||||
NestedObject: schema.NestedAttributeObject{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"network_range_id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"prefix": schema.StringAttribute{
|
||||
Description: "Classless Inter-Domain Routing (CIDR).",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"transfer_network": schema.StringAttribute{
|
||||
Description: "IPv4 Classless Inter-Domain Routing (CIDR).",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"default_prefix_length": schema.Int64Attribute{
|
||||
Description: "The default prefix length for networks in the network area.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Validators: []validator.Int64{
|
||||
int64validator.AtLeast(24),
|
||||
int64validator.AtMost(29),
|
||||
},
|
||||
Default: int64default.StaticInt64(25),
|
||||
},
|
||||
"max_prefix_length": schema.Int64Attribute{
|
||||
Description: "The maximal prefix length for networks in the network area.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Validators: []validator.Int64{
|
||||
int64validator.AtLeast(24),
|
||||
int64validator.AtMost(29),
|
||||
},
|
||||
Default: int64default.StaticInt64(29),
|
||||
},
|
||||
"min_prefix_length": schema.Int64Attribute{
|
||||
Description: "The minimal prefix length for networks in the network area.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Validators: []validator.Int64{
|
||||
int64validator.AtLeast(8),
|
||||
int64validator.AtMost(29),
|
||||
},
|
||||
Default: int64default.StaticInt64(24),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates the resource and sets the initial Terraform state.
|
||||
func (r *networkAreaRegionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model Model
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
organizationId := model.OrganizationId.ValueString()
|
||||
networkAreaId := model.NetworkAreaId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "organization_id", organizationId)
|
||||
ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
// Generate API request body from model
|
||||
payload, err := toCreatePayload(ctx, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network area region", fmt.Sprintf("Creating API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Create new network area region configuration
|
||||
networkAreaRegion, err := r.client.CreateNetworkAreaRegion(ctx, organizationId, networkAreaId, region).CreateNetworkAreaRegionPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network area region", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Write id attributes to state before polling via the wait handler - just in case anything goes wrong during the wait handler
|
||||
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
|
||||
"organization_id": organizationId,
|
||||
"network_area_id": networkAreaId,
|
||||
"region": region,
|
||||
})
|
||||
|
||||
// wait for creation of network area region to complete
|
||||
_, err = wait.CreateNetworkAreaRegionWaitHandler(ctx, r.client, organizationId, networkAreaId, region).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating server", fmt.Sprintf("server creation waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, networkAreaRegion, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network area region", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Set state to fully populated data
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Network area region created")
|
||||
}
|
||||
|
||||
// Read refreshes the Terraform state with the latest data.
|
||||
func (r *networkAreaRegionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
var model Model
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
organizationId := model.OrganizationId.ValueString()
|
||||
networkAreaId := model.NetworkAreaId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "organization_id", organizationId)
|
||||
ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
networkAreaRegionResp, err := r.client.GetNetworkAreaRegion(ctx, organizationId, networkAreaId, region).Execute()
|
||||
if err != nil {
|
||||
var oapiErr *oapierror.GenericOpenAPIError
|
||||
ok := errors.As(err, &oapiErr)
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, networkAreaRegionResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network area region", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Set refreshed state
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Network area region read")
|
||||
}
|
||||
|
||||
// Update updates the resource and sets the updated Terraform state on success.
|
||||
func (r *networkAreaRegionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model Model
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
organizationId := model.OrganizationId.ValueString()
|
||||
networkAreaId := model.NetworkAreaId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "organization_id", organizationId)
|
||||
ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
// Retrieve values from state
|
||||
var stateModel Model
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &stateModel)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Generate API request body from model
|
||||
payload, err := toUpdatePayload(ctx, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network area region", fmt.Sprintf("Creating API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Update existing network area region configuration
|
||||
_, err = r.client.UpdateNetworkAreaRegion(ctx, organizationId, networkAreaId, region).UpdateNetworkAreaRegionPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network area region", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = updateIpv4NetworkRanges(ctx, organizationId, networkAreaId, model.Ipv4.NetworkRanges, r.client, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network area region", fmt.Sprintf("Updating Network ranges: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
updatedNetworkAreaRegion, err := r.client.GetNetworkAreaRegion(ctx, organizationId, networkAreaId, region).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network area region", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = mapFields(ctx, updatedNetworkAreaRegion, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network area region", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "network area region updated")
|
||||
}
|
||||
|
||||
// Delete deletes the resource and removes the Terraform state on success.
|
||||
func (r *networkAreaRegionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from state
|
||||
var model Model
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
organizationId := model.OrganizationId.ValueString()
|
||||
networkAreaId := model.NetworkAreaId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "organization_id", organizationId)
|
||||
ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
_, err := wait.ReadyForNetworkAreaDeletionWaitHandler(ctx, r.client, r.resourceManagerClient, organizationId, networkAreaId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network area region", fmt.Sprintf("Network area ready for deletion waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
// Delete network area region configuration
|
||||
err = r.client.DeleteNetworkAreaRegion(ctx, organizationId, networkAreaId, region).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network area region", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
_, err = wait.DeleteNetworkAreaRegionWaitHandler(ctx, r.client, organizationId, networkAreaId, region).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network area region", fmt.Sprintf("network area deletion waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Network area region deleted")
|
||||
}
|
||||
|
||||
// ImportState imports a resource into the Terraform state on success.
|
||||
// The expected format of the resource import identifier is: organization_id,network_area_id,region
|
||||
func (r *networkAreaRegionResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
|
||||
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics,
|
||||
"Error importing network area region",
|
||||
fmt.Sprintf("Expected import identifier with format: [organization_id],[network_area_id],[region] Got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
|
||||
"organization_id": idParts[0],
|
||||
"network_area_id": idParts[1],
|
||||
"region": idParts[2],
|
||||
})
|
||||
|
||||
tflog.Info(ctx, "Network area region state imported")
|
||||
}
|
||||
|
||||
// mapFields maps the API response values to the Terraform resource model fields
|
||||
func mapFields(ctx context.Context, networkAreaRegion *iaas.RegionalArea, model *Model, region string) error {
|
||||
if networkAreaRegion == nil {
|
||||
return fmt.Errorf("network are region input is nil")
|
||||
}
|
||||
if model == nil {
|
||||
return fmt.Errorf("model input is nil")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.OrganizationId.ValueString(), model.NetworkAreaId.ValueString(), region)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
model.Ipv4 = &ipv4Model{}
|
||||
if networkAreaRegion.Ipv4 != nil {
|
||||
model.Ipv4.TransferNetwork = types.StringPointerValue(networkAreaRegion.Ipv4.TransferNetwork)
|
||||
model.Ipv4.DefaultPrefixLength = types.Int64PointerValue(networkAreaRegion.Ipv4.DefaultPrefixLen)
|
||||
model.Ipv4.MaxPrefixLength = types.Int64PointerValue(networkAreaRegion.Ipv4.MaxPrefixLen)
|
||||
model.Ipv4.MinPrefixLength = types.Int64PointerValue(networkAreaRegion.Ipv4.MinPrefixLen)
|
||||
}
|
||||
|
||||
// map default nameservers
|
||||
if networkAreaRegion.Ipv4 == nil || networkAreaRegion.Ipv4.DefaultNameservers == nil {
|
||||
model.Ipv4.DefaultNameservers = types.ListNull(types.StringType)
|
||||
} else {
|
||||
respDefaultNameservers := *networkAreaRegion.Ipv4.DefaultNameservers
|
||||
modelDefaultNameservers, err := utils.ListValuetoStringSlice(model.Ipv4.DefaultNameservers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current network area default nameservers from model: %w", err)
|
||||
}
|
||||
|
||||
reconciledDefaultNameservers := utils.ReconcileStringSlices(modelDefaultNameservers, respDefaultNameservers)
|
||||
|
||||
defaultNameserversTF, diags := types.ListValueFrom(ctx, types.StringType, reconciledDefaultNameservers)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("map network area default nameservers: %w", core.DiagsToError(diags))
|
||||
}
|
||||
|
||||
model.Ipv4.DefaultNameservers = defaultNameserversTF
|
||||
}
|
||||
|
||||
// map network ranges
|
||||
err := mapIpv4NetworkRanges(ctx, networkAreaRegion.Ipv4.NetworkRanges, model)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mapping network ranges: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// mapFields maps the API ipv4 network ranges response values to the Terraform resource model fields
|
||||
func mapIpv4NetworkRanges(_ context.Context, networkAreaRangesList *[]iaas.NetworkRange, model *Model) error {
|
||||
if networkAreaRangesList == nil {
|
||||
return fmt.Errorf("nil network area ranges list")
|
||||
}
|
||||
if len(*networkAreaRangesList) == 0 {
|
||||
model.Ipv4.NetworkRanges = []networkRangeModel{}
|
||||
return nil
|
||||
}
|
||||
|
||||
modelNetworkRangePrefixes := []string{}
|
||||
for _, m := range model.Ipv4.NetworkRanges {
|
||||
modelNetworkRangePrefixes = append(modelNetworkRangePrefixes, m.Prefix.ValueString())
|
||||
}
|
||||
|
||||
apiNetworkRangePrefixes := []string{}
|
||||
for _, n := range *networkAreaRangesList {
|
||||
apiNetworkRangePrefixes = append(apiNetworkRangePrefixes, *n.Prefix)
|
||||
}
|
||||
|
||||
reconciledRangePrefixes := utils.ReconcileStringSlices(modelNetworkRangePrefixes, apiNetworkRangePrefixes)
|
||||
|
||||
model.Ipv4.NetworkRanges = []networkRangeModel{}
|
||||
for _, prefix := range reconciledRangePrefixes {
|
||||
var networkRangeId string
|
||||
for _, networkRangeElement := range *networkAreaRangesList {
|
||||
if *networkRangeElement.Prefix == prefix {
|
||||
networkRangeId = *networkRangeElement.Id
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
model.Ipv4.NetworkRanges = append(model.Ipv4.NetworkRanges, networkRangeModel{
|
||||
Prefix: types.StringValue(prefix),
|
||||
NetworkRangeId: types.StringValue(networkRangeId),
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toDefaultNameserversPayload(_ context.Context, model *Model) ([]string, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("model is nil")
|
||||
}
|
||||
|
||||
modelDefaultNameservers := []string{}
|
||||
for _, ns := range model.Ipv4.DefaultNameservers.Elements() {
|
||||
nameserverString, ok := ns.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
modelDefaultNameservers = append(modelDefaultNameservers, nameserverString.ValueString())
|
||||
}
|
||||
|
||||
return modelDefaultNameservers, nil
|
||||
}
|
||||
|
||||
func toNetworkRangesPayload(_ context.Context, model *Model) (*[]iaas.NetworkRange, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("model is nil")
|
||||
}
|
||||
|
||||
if len(model.Ipv4.NetworkRanges) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
payload := []iaas.NetworkRange{}
|
||||
for _, networkRange := range model.Ipv4.NetworkRanges {
|
||||
payload = append(payload, iaas.NetworkRange{
|
||||
Prefix: conversion.StringValueToPointer(networkRange.Prefix),
|
||||
})
|
||||
}
|
||||
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateNetworkAreaRegionPayload, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
} else if model.Ipv4 == nil {
|
||||
return nil, fmt.Errorf("nil model.Ipv4")
|
||||
}
|
||||
|
||||
modelDefaultNameservers, err := toDefaultNameserversPayload(ctx, model)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting default nameservers: %w", err)
|
||||
}
|
||||
|
||||
networkRangesPayload, err := toNetworkRangesPayload(ctx, model)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting network ranges: %w", err)
|
||||
}
|
||||
|
||||
return &iaas.CreateNetworkAreaRegionPayload{
|
||||
Ipv4: &iaas.RegionalAreaIPv4{
|
||||
DefaultNameservers: &modelDefaultNameservers,
|
||||
DefaultPrefixLen: conversion.Int64ValueToPointer(model.Ipv4.DefaultPrefixLength),
|
||||
MaxPrefixLen: conversion.Int64ValueToPointer(model.Ipv4.MaxPrefixLength),
|
||||
MinPrefixLen: conversion.Int64ValueToPointer(model.Ipv4.MinPrefixLength),
|
||||
TransferNetwork: conversion.StringValueToPointer(model.Ipv4.TransferNetwork),
|
||||
NetworkRanges: networkRangesPayload,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toUpdatePayload(ctx context.Context, model *Model) (*iaas.UpdateNetworkAreaRegionPayload, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
|
||||
modelDefaultNameservers, err := toDefaultNameserversPayload(ctx, model)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting default nameservers: %w", err)
|
||||
}
|
||||
|
||||
return &iaas.UpdateNetworkAreaRegionPayload{
|
||||
Ipv4: &iaas.UpdateRegionalAreaIPv4{
|
||||
DefaultNameservers: &modelDefaultNameservers,
|
||||
DefaultPrefixLen: conversion.Int64ValueToPointer(model.Ipv4.DefaultPrefixLength),
|
||||
MaxPrefixLen: conversion.Int64ValueToPointer(model.Ipv4.MaxPrefixLength),
|
||||
MinPrefixLen: conversion.Int64ValueToPointer(model.Ipv4.MinPrefixLength),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// updateIpv4NetworkRanges creates and deletes network ranges so that network area ranges are the ones in the model.
|
||||
func updateIpv4NetworkRanges(ctx context.Context, organizationId, networkAreaId string, ranges []networkRangeModel, client *iaas.APIClient, region string) error {
|
||||
// Get network ranges current state
|
||||
currentNetworkRangesResp, err := client.ListNetworkAreaRanges(ctx, organizationId, networkAreaId, region).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading network area ranges: %w", err)
|
||||
}
|
||||
|
||||
type networkRangeState struct {
|
||||
isInModel bool
|
||||
isCreated bool
|
||||
id string
|
||||
}
|
||||
|
||||
networkRangesState := make(map[string]*networkRangeState)
|
||||
for _, nwRange := range ranges {
|
||||
networkRangesState[nwRange.Prefix.ValueString()] = &networkRangeState{
|
||||
isInModel: true,
|
||||
}
|
||||
}
|
||||
|
||||
for _, networkRange := range *currentNetworkRangesResp.Items {
|
||||
prefix := *networkRange.Prefix
|
||||
if _, ok := networkRangesState[prefix]; !ok {
|
||||
networkRangesState[prefix] = &networkRangeState{}
|
||||
}
|
||||
networkRangesState[prefix].isCreated = true
|
||||
networkRangesState[prefix].id = *networkRange.Id
|
||||
}
|
||||
|
||||
// Delete network ranges
|
||||
for prefix, state := range networkRangesState {
|
||||
if !state.isInModel && state.isCreated {
|
||||
err := client.DeleteNetworkAreaRange(ctx, organizationId, networkAreaId, region, state.id).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting network area range '%v': %w", prefix, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create network ranges
|
||||
for prefix, state := range networkRangesState {
|
||||
if state.isInModel && !state.isCreated {
|
||||
payload := iaas.CreateNetworkAreaRangePayload{
|
||||
Ipv4: &[]iaas.NetworkRange{
|
||||
{
|
||||
Prefix: sdkUtils.Ptr(prefix),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := client.CreateNetworkAreaRange(ctx, organizationId, networkAreaId, region).CreateNetworkAreaRangePayload(payload).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating network range '%v': %w", prefix, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
1052
stackit/internal/services/iaas/networkarearegion/resource_test.go
Normal file
1052
stackit/internal/services/iaas/networkarearegion/resource_test.go
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -31,7 +31,8 @@ func NewNetworkAreaRouteDataSource() datasource.DataSource {
|
|||
|
||||
// networkDataSource is the data source implementation.
|
||||
type networkAreaRouteDataSource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
|
|
@ -40,12 +41,13 @@ func (d *networkAreaRouteDataSource) Metadata(_ context.Context, req datasource.
|
|||
}
|
||||
|
||||
func (d *networkAreaRouteDataSource) 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
|
||||
}
|
||||
|
|
@ -61,7 +63,7 @@ func (d *networkAreaRouteDataSource) Schema(_ context.Context, _ datasource.Sche
|
|||
MarkdownDescription: description,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal data source ID. It is structured as \"`organization_id`,`network_area_id`,`network_area_route_id`\".",
|
||||
Description: "Terraform's internal data source ID. It is structured as \"`organization_id`,`region`,`network_area_id`,`network_area_route_id`\".",
|
||||
Computed: true,
|
||||
},
|
||||
"organization_id": schema.StringAttribute{
|
||||
|
|
@ -80,6 +82,11 @@ func (d *networkAreaRouteDataSource) Schema(_ context.Context, _ datasource.Sche
|
|||
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,
|
||||
},
|
||||
"network_area_route_id": schema.StringAttribute{
|
||||
Description: "The network area route ID.",
|
||||
Required: true,
|
||||
|
|
@ -88,13 +95,33 @@ func (d *networkAreaRouteDataSource) Schema(_ context.Context, _ datasource.Sche
|
|||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"next_hop": schema.StringAttribute{
|
||||
Description: "The IP address of the routing system, that will route the prefix configured. Should be a valid IPv4 address.",
|
||||
"destination": schema.SingleNestedAttribute{
|
||||
Description: "Destination of the route.",
|
||||
Computed: true,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"type": schema.StringAttribute{
|
||||
Description: fmt.Sprintf("CIDRV type. %s", utils.FormatPossibleValues("cidrv4", "cidrv6")),
|
||||
Computed: true,
|
||||
},
|
||||
"value": schema.StringAttribute{
|
||||
Description: "An CIDR string.",
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"prefix": schema.StringAttribute{
|
||||
Description: "The network, that is reachable though the Next Hop. Should use CIDR notation.",
|
||||
"next_hop": schema.SingleNestedAttribute{
|
||||
Description: "Next hop destination.",
|
||||
Computed: true,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"type": schema.StringAttribute{
|
||||
Description: "Type of the next hop. " + utils.FormatPossibleValues("blackhole", "internet", "ipv4", "ipv6"),
|
||||
Computed: true,
|
||||
},
|
||||
"value": schema.StringAttribute{
|
||||
Description: "Either IPv4 or IPv6 (not set for blackhole and internet).",
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"labels": schema.MapAttribute{
|
||||
Description: "Labels are key-value string pairs which can be attached to a resource container",
|
||||
|
|
@ -107,23 +134,26 @@ func (d *networkAreaRouteDataSource) Schema(_ context.Context, _ datasource.Sche
|
|||
|
||||
// Read refreshes the Terraform state with the latest data.
|
||||
func (d *networkAreaRouteDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
var model Model
|
||||
var model ModelV1
|
||||
diags := req.Config.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
organizationId := model.OrganizationId.ValueString()
|
||||
networkAreaId := model.NetworkAreaId.ValueString()
|
||||
region := d.providerData.GetRegionWithOverride(model.Region)
|
||||
networkAreaRouteId := model.NetworkAreaRouteId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "organization_id", organizationId)
|
||||
ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "network_area_route_id", networkAreaRouteId)
|
||||
|
||||
networkAreaRouteResp, err := d.client.GetNetworkAreaRoute(ctx, organizationId, networkAreaId, networkAreaRouteId).Execute()
|
||||
networkAreaRouteResp, err := d.client.GetNetworkAreaRoute(ctx, organizationId, networkAreaId, region, networkAreaRouteId).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
|
|
@ -141,11 +171,12 @@ func (d *networkAreaRouteDataSource) Read(ctx context.Context, req datasource.Re
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapFields(ctx, networkAreaRouteResp, &model)
|
||||
err = mapFields(ctx, networkAreaRouteResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network area route", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
|
|
@ -27,13 +28,28 @@ import (
|
|||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ resource.Resource = &networkAreaRouteResource{}
|
||||
_ resource.ResourceWithConfigure = &networkAreaRouteResource{}
|
||||
_ resource.ResourceWithImportState = &networkAreaRouteResource{}
|
||||
_ resource.Resource = &networkAreaRouteResource{}
|
||||
_ resource.ResourceWithConfigure = &networkAreaRouteResource{}
|
||||
_ resource.ResourceWithImportState = &networkAreaRouteResource{}
|
||||
_ resource.ResourceWithModifyPlan = &networkAreaRouteResource{}
|
||||
_ resource.ResourceWithUpgradeState = &networkAreaRouteResource{}
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
// ModelV1 is the currently used model
|
||||
type ModelV1 struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
OrganizationId types.String `tfsdk:"organization_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
NetworkAreaId types.String `tfsdk:"network_area_id"`
|
||||
NetworkAreaRouteId types.String `tfsdk:"network_area_route_id"`
|
||||
NextHop *NexthopModelV1 `tfsdk:"next_hop"`
|
||||
Destination *DestinationModelV1 `tfsdk:"destination"`
|
||||
Labels types.Map `tfsdk:"labels"`
|
||||
}
|
||||
|
||||
// ModelV0 is the old model (only needed for state upgrade)
|
||||
type ModelV0 struct {
|
||||
Id types.String `tfsdk:"id"`
|
||||
OrganizationId types.String `tfsdk:"organization_id"`
|
||||
NetworkAreaId types.String `tfsdk:"network_area_id"`
|
||||
NetworkAreaRouteId types.String `tfsdk:"network_area_route_id"`
|
||||
|
|
@ -42,6 +58,18 @@ type Model struct {
|
|||
Labels types.Map `tfsdk:"labels"`
|
||||
}
|
||||
|
||||
// DestinationModelV1 maps the route destination data
|
||||
type DestinationModelV1 struct {
|
||||
Type types.String `tfsdk:"type"`
|
||||
Value types.String `tfsdk:"value"`
|
||||
}
|
||||
|
||||
// NexthopModelV1 maps the route nexthop data
|
||||
type NexthopModelV1 struct {
|
||||
Type types.String `tfsdk:"type"`
|
||||
Value types.String `tfsdk:"value"`
|
||||
}
|
||||
|
||||
// NewNetworkAreaRouteResource is a helper function to simplify the provider implementation.
|
||||
func NewNetworkAreaRouteResource() resource.Resource {
|
||||
return &networkAreaRouteResource{}
|
||||
|
|
@ -49,7 +77,8 @@ func NewNetworkAreaRouteResource() resource.Resource {
|
|||
|
||||
// networkResource is the resource implementation.
|
||||
type networkAreaRouteResource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
|
|
@ -57,14 +86,45 @@ func (r *networkAreaRouteResource) Metadata(_ context.Context, req resource.Meta
|
|||
resp.TypeName = req.ProviderTypeName + "_network_area_route"
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
func (r *networkAreaRouteResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
var configModel ModelV1
|
||||
// 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 ModelV1
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *networkAreaRouteResource) 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
|
||||
}
|
||||
|
|
@ -78,9 +138,10 @@ func (r *networkAreaRouteResource) Schema(_ context.Context, _ resource.SchemaRe
|
|||
resp.Schema = schema.Schema{
|
||||
Description: description,
|
||||
MarkdownDescription: description,
|
||||
Version: 1,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`organization_id`,`network_area_id`,`network_area_route_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`organization_id`,`network_area_id`,`region`,`network_area_route_id`\".",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
|
|
@ -97,6 +158,15 @@ func (r *networkAreaRouteResource) Schema(_ context.Context, _ resource.SchemaRe
|
|||
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(),
|
||||
},
|
||||
},
|
||||
"network_area_id": schema.StringAttribute{
|
||||
Description: "The network area ID to which the network area route is associated.",
|
||||
Required: true,
|
||||
|
|
@ -121,24 +191,50 @@ func (r *networkAreaRouteResource) Schema(_ context.Context, _ resource.SchemaRe
|
|||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"next_hop": schema.StringAttribute{
|
||||
Description: "The IP address of the routing system, that will route the prefix configured. Should be a valid IPv4 address.",
|
||||
"next_hop": schema.SingleNestedAttribute{
|
||||
Description: "Next hop destination.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
Validators: []validator.String{
|
||||
validate.IP(false),
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"type": schema.StringAttribute{
|
||||
Description: fmt.Sprintf("Type of the next hop. %s %s", utils.FormatPossibleValues("blackhole", "internet", "ipv4", "ipv6"), "Only `ipv4` supported currently."),
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"value": schema.StringAttribute{
|
||||
Description: "Either IPv4 or IPv6 (not set for blackhole and internet). Only IPv4 supported currently.",
|
||||
Optional: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
Validators: []validator.String{
|
||||
validate.IP(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"prefix": schema.StringAttribute{
|
||||
Description: "The network, that is reachable though the Next Hop. Should use CIDR notation.",
|
||||
"destination": schema.SingleNestedAttribute{
|
||||
Description: "Destination of the route.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
Validators: []validator.String{
|
||||
validate.CIDR(),
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"type": schema.StringAttribute{
|
||||
Description: fmt.Sprintf("CIDRV type. %s %s", utils.FormatPossibleValues("cidrv4", "cidrv6"), "Only `cidrv4` is supported currently."),
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"value": schema.StringAttribute{
|
||||
Description: "An CIDR string.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
Validators: []validator.String{
|
||||
validate.CIDR(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"labels": schema.MapAttribute{
|
||||
|
|
@ -150,10 +246,91 @@ func (r *networkAreaRouteResource) Schema(_ context.Context, _ resource.SchemaRe
|
|||
}
|
||||
}
|
||||
|
||||
func (r *networkAreaRouteResource) UpgradeState(_ context.Context) map[int64]resource.StateUpgrader {
|
||||
return map[int64]resource.StateUpgrader{
|
||||
0: {
|
||||
// This handles moving from version 0 to 1
|
||||
PriorSchema: &schema.Schema{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"organization_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"network_area_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"network_area_route_id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"next_hop": schema.StringAttribute{
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.IP(false),
|
||||
},
|
||||
},
|
||||
"prefix": schema.StringAttribute{
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.CIDR(),
|
||||
},
|
||||
},
|
||||
"labels": schema.MapAttribute{
|
||||
ElementType: types.StringType,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
|
||||
var priorStateData ModelV0
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &priorStateData)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
nexthopValue := priorStateData.NextHop.ValueString()
|
||||
prefixValue := priorStateData.Prefix.ValueString()
|
||||
|
||||
newStateData := ModelV1{
|
||||
Id: priorStateData.Id,
|
||||
OrganizationId: priorStateData.OrganizationId,
|
||||
NetworkAreaId: priorStateData.NetworkAreaId,
|
||||
NetworkAreaRouteId: priorStateData.NetworkAreaRouteId,
|
||||
Labels: priorStateData.Labels,
|
||||
|
||||
NextHop: &NexthopModelV1{
|
||||
Type: types.StringValue("ipv4"),
|
||||
Value: types.StringValue(nexthopValue),
|
||||
},
|
||||
Destination: &DestinationModelV1{
|
||||
Type: types.StringValue("cidrv4"),
|
||||
Value: types.StringValue(prefixValue),
|
||||
},
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, newStateData)...)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates the resource and sets the initial Terraform state.
|
||||
func (r *networkAreaRouteResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model Model
|
||||
var model ModelV1
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
|
|
@ -163,8 +340,10 @@ func (r *networkAreaRouteResource) Create(ctx context.Context, req resource.Crea
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
organizationId := model.OrganizationId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "organization_id", organizationId)
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
networkAreaId := model.NetworkAreaId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "organization_id", organizationId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
|
||||
|
||||
// Generate API request body from model
|
||||
|
|
@ -175,7 +354,7 @@ func (r *networkAreaRouteResource) Create(ctx context.Context, req resource.Crea
|
|||
}
|
||||
|
||||
// Create new network area route
|
||||
routes, err := r.client.CreateNetworkAreaRoute(ctx, organizationId, networkAreaId).CreateNetworkAreaRoutePayload(*payload).Execute()
|
||||
routes, err := r.client.CreateNetworkAreaRoute(ctx, organizationId, networkAreaId, region).CreateNetworkAreaRoutePayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network area route", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -196,12 +375,12 @@ func (r *networkAreaRouteResource) Create(ctx context.Context, req resource.Crea
|
|||
// Gets the route ID from the first element, routes.Items[0]
|
||||
routeItems := *routes.Items
|
||||
route := routeItems[0]
|
||||
routeId := *route.RouteId
|
||||
routeId := *route.Id
|
||||
|
||||
ctx = tflog.SetField(ctx, "network_area_route_id", routeId)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, &route, &model)
|
||||
err = mapFields(ctx, &route, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network area route.", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -217,7 +396,7 @@ func (r *networkAreaRouteResource) Create(ctx context.Context, req resource.Crea
|
|||
|
||||
// Read refreshes the Terraform state with the latest data.
|
||||
func (r *networkAreaRouteResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
var model Model
|
||||
var model ModelV1
|
||||
diags := req.State.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
|
|
@ -225,15 +404,17 @@ func (r *networkAreaRouteResource) Read(ctx context.Context, req resource.ReadRe
|
|||
}
|
||||
organizationId := model.OrganizationId.ValueString()
|
||||
networkAreaId := model.NetworkAreaId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
networkAreaRouteId := model.NetworkAreaRouteId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "organization_id", organizationId)
|
||||
ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "network_area_route_id", networkAreaRouteId)
|
||||
|
||||
networkAreaRouteResp, err := r.client.GetNetworkAreaRoute(ctx, organizationId, networkAreaId, networkAreaRouteId).Execute()
|
||||
networkAreaRouteResp, err := r.client.GetNetworkAreaRoute(ctx, organizationId, networkAreaId, region, networkAreaRouteId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
|
|
@ -247,7 +428,7 @@ func (r *networkAreaRouteResource) Read(ctx context.Context, req resource.ReadRe
|
|||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, networkAreaRouteResp, &model)
|
||||
err = mapFields(ctx, networkAreaRouteResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network area route", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -264,7 +445,7 @@ func (r *networkAreaRouteResource) Read(ctx context.Context, req resource.ReadRe
|
|||
// Delete deletes the resource and removes the Terraform state on success.
|
||||
func (r *networkAreaRouteResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from state
|
||||
var model Model
|
||||
var model ModelV1
|
||||
diags := req.State.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
|
|
@ -273,16 +454,18 @@ func (r *networkAreaRouteResource) Delete(ctx context.Context, req resource.Dele
|
|||
|
||||
organizationId := model.OrganizationId.ValueString()
|
||||
networkAreaId := model.NetworkAreaId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
networkAreaRouteId := model.NetworkAreaRouteId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "organization_id", organizationId)
|
||||
ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "network_area_route_id", networkAreaRouteId)
|
||||
|
||||
// Delete existing network
|
||||
err := r.client.DeleteNetworkAreaRoute(ctx, organizationId, networkAreaId, networkAreaRouteId).Execute()
|
||||
err := r.client.DeleteNetworkAreaRoute(ctx, organizationId, networkAreaId, region, networkAreaRouteId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network area route", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -296,7 +479,7 @@ func (r *networkAreaRouteResource) Delete(ctx context.Context, req resource.Dele
|
|||
// Update updates the resource and sets the updated Terraform state on success.
|
||||
func (r *networkAreaRouteResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model Model
|
||||
var model ModelV1
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
|
|
@ -305,16 +488,18 @@ func (r *networkAreaRouteResource) Update(ctx context.Context, req resource.Upda
|
|||
|
||||
organizationId := model.OrganizationId.ValueString()
|
||||
networkAreaId := model.NetworkAreaId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
networkAreaRouteId := model.NetworkAreaRouteId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "organization_id", organizationId)
|
||||
ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "network_area_route_id", networkAreaRouteId)
|
||||
|
||||
// Retrieve values from state
|
||||
var stateModel Model
|
||||
var stateModel ModelV1
|
||||
diags = req.State.Get(ctx, &stateModel)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
|
|
@ -328,7 +513,7 @@ func (r *networkAreaRouteResource) Update(ctx context.Context, req resource.Upda
|
|||
return
|
||||
}
|
||||
// Update existing network area route
|
||||
networkAreaRouteResp, err := r.client.UpdateNetworkAreaRoute(ctx, organizationId, networkAreaId, networkAreaRouteId).UpdateNetworkAreaRoutePayload(*payload).Execute()
|
||||
networkAreaRouteResp, err := r.client.UpdateNetworkAreaRoute(ctx, organizationId, networkAreaId, region, networkAreaRouteId).UpdateNetworkAreaRoutePayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network area route", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -336,7 +521,7 @@ func (r *networkAreaRouteResource) Update(ctx context.Context, req resource.Upda
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapFields(ctx, networkAreaRouteResp, &model)
|
||||
err = mapFields(ctx, networkAreaRouteResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network area route", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -354,28 +539,25 @@ func (r *networkAreaRouteResource) Update(ctx context.Context, req resource.Upda
|
|||
func (r *networkAreaRouteResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
|
||||
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
|
||||
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics,
|
||||
"Error importing network area route",
|
||||
fmt.Sprintf("Expected import identifier with format: [organization_id],[network_area_id],[network_area_route_id] Got: %q", req.ID),
|
||||
fmt.Sprintf("Expected import identifier with format: [organization_id],[network_area_id],[region],[network_area_route_id] Got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
organizationId := idParts[0]
|
||||
networkAreaId := idParts[1]
|
||||
networkAreaRouteId := idParts[2]
|
||||
ctx = tflog.SetField(ctx, "organization_id", organizationId)
|
||||
ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
|
||||
ctx = tflog.SetField(ctx, "network_area_route_id", networkAreaRouteId)
|
||||
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
|
||||
"organization_id": idParts[0],
|
||||
"network_area_id": idParts[1],
|
||||
"region": idParts[2],
|
||||
"network_area_route_id": idParts[3],
|
||||
})
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("organization_id"), organizationId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_area_id"), networkAreaId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_area_route_id"), networkAreaRouteId)...)
|
||||
tflog.Info(ctx, "Network area route state imported")
|
||||
}
|
||||
|
||||
func mapFields(ctx context.Context, networkAreaRoute *iaas.Route, model *Model) error {
|
||||
func mapFields(ctx context.Context, networkAreaRoute *iaas.Route, model *ModelV1, region string) error {
|
||||
if networkAreaRoute == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
|
|
@ -386,13 +568,14 @@ func mapFields(ctx context.Context, networkAreaRoute *iaas.Route, model *Model)
|
|||
var networkAreaRouteId string
|
||||
if model.NetworkAreaRouteId.ValueString() != "" {
|
||||
networkAreaRouteId = model.NetworkAreaRouteId.ValueString()
|
||||
} else if networkAreaRoute.RouteId != nil {
|
||||
networkAreaRouteId = *networkAreaRoute.RouteId
|
||||
} else if networkAreaRoute.Id != nil {
|
||||
networkAreaRouteId = *networkAreaRoute.Id
|
||||
} else {
|
||||
return fmt.Errorf("network area route id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.OrganizationId.ValueString(), model.NetworkAreaId.ValueString(), networkAreaRouteId)
|
||||
model.Id = utils.BuildInternalTerraformId(model.OrganizationId.ValueString(), model.NetworkAreaId.ValueString(), region, networkAreaRouteId)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
labels, err := iaasUtils.MapLabels(ctx, networkAreaRoute.Labels, model.Labels)
|
||||
if err != nil {
|
||||
|
|
@ -400,13 +583,22 @@ func mapFields(ctx context.Context, networkAreaRoute *iaas.Route, model *Model)
|
|||
}
|
||||
|
||||
model.NetworkAreaRouteId = types.StringValue(networkAreaRouteId)
|
||||
model.NextHop = types.StringPointerValue(networkAreaRoute.Nexthop)
|
||||
model.Prefix = types.StringPointerValue(networkAreaRoute.Prefix)
|
||||
model.Labels = labels
|
||||
|
||||
model.NextHop, err = mapRouteNextHop(networkAreaRoute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
model.Destination, err = mapRouteDestination(networkAreaRoute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateNetworkAreaRoutePayload, error) {
|
||||
func toCreatePayload(ctx context.Context, model *ModelV1) (*iaas.CreateNetworkAreaRoutePayload, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
|
|
@ -416,18 +608,28 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateNetworkArea
|
|||
return nil, fmt.Errorf("converting to Go map: %w", err)
|
||||
}
|
||||
|
||||
nextHopPayload, err := toNextHopPayload(model)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
destinationPayload, err := toDestinationPayload(model)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &iaas.CreateNetworkAreaRoutePayload{
|
||||
Ipv4: &[]iaas.Route{
|
||||
Items: &[]iaas.Route{
|
||||
{
|
||||
Prefix: conversion.StringValueToPointer(model.Prefix),
|
||||
Nexthop: conversion.StringValueToPointer(model.NextHop),
|
||||
Labels: &labels,
|
||||
Destination: destinationPayload,
|
||||
Labels: &labels,
|
||||
Nexthop: nextHopPayload,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toUpdatePayload(ctx context.Context, model *Model, currentLabels types.Map) (*iaas.UpdateNetworkAreaRoutePayload, error) {
|
||||
func toUpdatePayload(ctx context.Context, model *ModelV1, currentLabels types.Map) (*iaas.UpdateNetworkAreaRoutePayload, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
|
|
@ -441,3 +643,97 @@ func toUpdatePayload(ctx context.Context, model *Model, currentLabels types.Map)
|
|||
Labels: &labels,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toNextHopPayload(model *ModelV1) (*iaas.RouteNexthop, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
} else if model.NextHop == nil {
|
||||
return nil, fmt.Errorf("nexthop is nil in model")
|
||||
}
|
||||
|
||||
switch model.NextHop.Type.ValueString() {
|
||||
case "blackhole":
|
||||
return sdkUtils.Ptr(iaas.NexthopBlackholeAsRouteNexthop(iaas.NewNexthopBlackhole("blackhole"))), nil
|
||||
case "internet":
|
||||
return sdkUtils.Ptr(iaas.NexthopInternetAsRouteNexthop(iaas.NewNexthopInternet("internet"))), nil
|
||||
case "ipv4":
|
||||
return sdkUtils.Ptr(iaas.NexthopIPv4AsRouteNexthop(iaas.NewNexthopIPv4("ipv4", model.NextHop.Value.ValueString()))), nil
|
||||
case "ipv6":
|
||||
return sdkUtils.Ptr(iaas.NexthopIPv6AsRouteNexthop(iaas.NewNexthopIPv6("ipv6", model.NextHop.Value.ValueString()))), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown nexthop type: %s", model.NextHop.Type.ValueString())
|
||||
}
|
||||
|
||||
func toDestinationPayload(model *ModelV1) (*iaas.RouteDestination, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
} else if model.Destination == nil {
|
||||
return nil, fmt.Errorf("destination is nil in model")
|
||||
}
|
||||
|
||||
switch model.Destination.Type.ValueString() {
|
||||
case "cidrv4":
|
||||
return sdkUtils.Ptr(iaas.DestinationCIDRv4AsRouteDestination(iaas.NewDestinationCIDRv4("cidrv4", model.Destination.Value.ValueString()))), nil
|
||||
case "cidrv6":
|
||||
return sdkUtils.Ptr(iaas.DestinationCIDRv6AsRouteDestination(iaas.NewDestinationCIDRv6("cidrv6", model.Destination.Value.ValueString()))), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown destination type: %s", model.Destination.Type.ValueString())
|
||||
}
|
||||
|
||||
func mapRouteNextHop(routeResp *iaas.Route) (*NexthopModelV1, error) {
|
||||
if routeResp.Nexthop == nil {
|
||||
return &NexthopModelV1{
|
||||
Type: types.StringNull(),
|
||||
Value: types.StringNull(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
switch i := routeResp.Nexthop.GetActualInstance().(type) {
|
||||
case *iaas.NexthopIPv4:
|
||||
return &NexthopModelV1{
|
||||
Type: types.StringPointerValue(i.Type),
|
||||
Value: types.StringPointerValue(i.Value),
|
||||
}, nil
|
||||
case *iaas.NexthopIPv6:
|
||||
return &NexthopModelV1{
|
||||
Type: types.StringPointerValue(i.Type),
|
||||
Value: types.StringPointerValue(i.Value),
|
||||
}, nil
|
||||
case *iaas.NexthopBlackhole:
|
||||
return &NexthopModelV1{
|
||||
Type: types.StringPointerValue(i.Type),
|
||||
Value: types.StringNull(),
|
||||
}, nil
|
||||
case *iaas.NexthopInternet:
|
||||
return &NexthopModelV1{
|
||||
Type: types.StringPointerValue(i.Type),
|
||||
Value: types.StringNull(),
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected nexthop type: %T", i)
|
||||
}
|
||||
}
|
||||
|
||||
func mapRouteDestination(routeResp *iaas.Route) (*DestinationModelV1, error) {
|
||||
if routeResp.Destination == nil {
|
||||
return &DestinationModelV1{
|
||||
Type: types.StringNull(),
|
||||
Value: types.StringNull(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
switch i := routeResp.Destination.GetActualInstance().(type) {
|
||||
case *iaas.DestinationCIDRv4:
|
||||
return &DestinationModelV1{
|
||||
Type: types.StringPointerValue(i.Type),
|
||||
Value: types.StringPointerValue(i.Value),
|
||||
}, nil
|
||||
case *iaas.DestinationCIDRv6:
|
||||
return &DestinationModelV1{
|
||||
Type: types.StringPointerValue(i.Type),
|
||||
Value: types.StringPointerValue(i.Value),
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected Destionation type: %T", i)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,100 +2,133 @@ package networkarearoute
|
|||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
|
||||
)
|
||||
|
||||
func TestMapFields(t *testing.T) {
|
||||
type args struct {
|
||||
state ModelV1
|
||||
input *iaas.Route
|
||||
region string
|
||||
}
|
||||
tests := []struct {
|
||||
description string
|
||||
state Model
|
||||
input *iaas.Route
|
||||
expected Model
|
||||
args args
|
||||
expected ModelV1
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"id_ok",
|
||||
Model{
|
||||
description: "id_ok",
|
||||
args: args{
|
||||
state: ModelV1{
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
NetworkAreaRouteId: types.StringValue("narid"),
|
||||
},
|
||||
input: &iaas.Route{},
|
||||
region: "eu01",
|
||||
},
|
||||
expected: ModelV1{
|
||||
Id: types.StringValue("oid,naid,eu01,narid"),
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
NetworkAreaRouteId: types.StringValue("narid"),
|
||||
Destination: &DestinationModelV1{
|
||||
Type: types.StringNull(),
|
||||
Value: types.StringNull(),
|
||||
},
|
||||
NextHop: &NexthopModelV1{
|
||||
Type: types.StringNull(),
|
||||
Value: types.StringNull(),
|
||||
},
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
&iaas.Route{},
|
||||
Model{
|
||||
Id: types.StringValue("oid,naid,narid"),
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
NetworkAreaRouteId: types.StringValue("narid"),
|
||||
Prefix: types.StringNull(),
|
||||
NextHop: types.StringNull(),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"values_ok",
|
||||
Model{
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
NetworkAreaRouteId: types.StringValue("narid"),
|
||||
},
|
||||
&iaas.Route{
|
||||
Prefix: utils.Ptr("prefix"),
|
||||
Nexthop: utils.Ptr("hop"),
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
description: "values_ok",
|
||||
args: args{
|
||||
state: ModelV1{
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
NetworkAreaRouteId: types.StringValue("narid"),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
input: &iaas.Route{
|
||||
Destination: &iaas.RouteDestination{
|
||||
DestinationCIDRv4: &iaas.DestinationCIDRv4{
|
||||
Type: utils.Ptr("cidrv4"),
|
||||
Value: utils.Ptr("prefix"),
|
||||
},
|
||||
DestinationCIDRv6: nil,
|
||||
},
|
||||
Nexthop: &iaas.RouteNexthop{
|
||||
NexthopIPv4: &iaas.NexthopIPv4{
|
||||
Type: utils.Ptr("ipv4"),
|
||||
Value: utils.Ptr("hop"),
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
region: "eu02",
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("oid,naid,narid"),
|
||||
expected: ModelV1{
|
||||
Id: types.StringValue("oid,naid,eu02,narid"),
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
NetworkAreaRouteId: types.StringValue("narid"),
|
||||
Prefix: types.StringValue("prefix"),
|
||||
NextHop: types.StringValue("hop"),
|
||||
Destination: &DestinationModelV1{
|
||||
Type: types.StringValue("cidrv4"),
|
||||
Value: types.StringValue("prefix"),
|
||||
},
|
||||
NextHop: &NexthopModelV1{
|
||||
Type: types.StringValue("ipv4"),
|
||||
Value: types.StringValue("hop"),
|
||||
},
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
Region: types.StringValue("eu02"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"response_fields_nil_fail",
|
||||
Model{},
|
||||
&iaas.Route{
|
||||
Prefix: nil,
|
||||
Nexthop: nil,
|
||||
description: "response_fields_nil_fail",
|
||||
args: args{
|
||||
input: &iaas.Route{
|
||||
Destination: nil,
|
||||
Nexthop: nil,
|
||||
},
|
||||
},
|
||||
Model{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"response_nil_fail",
|
||||
Model{},
|
||||
nil,
|
||||
Model{},
|
||||
false,
|
||||
description: "response_nil_fail",
|
||||
},
|
||||
{
|
||||
"no_resource_id",
|
||||
Model{
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
description: "no_resource_id",
|
||||
args: args{
|
||||
state: ModelV1{
|
||||
OrganizationId: types.StringValue("oid"),
|
||||
NetworkAreaId: types.StringValue("naid"),
|
||||
},
|
||||
input: &iaas.Route{},
|
||||
},
|
||||
&iaas.Route{},
|
||||
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")
|
||||
}
|
||||
|
|
@ -103,7 +136,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)
|
||||
}
|
||||
|
|
@ -115,24 +148,41 @@ func TestMapFields(t *testing.T) {
|
|||
func TestToCreatePayload(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input *Model
|
||||
input *ModelV1
|
||||
expected *iaas.CreateNetworkAreaRoutePayload
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
description: "default_ok",
|
||||
input: &Model{
|
||||
Prefix: types.StringValue("prefix"),
|
||||
NextHop: types.StringValue("hop"),
|
||||
input: &ModelV1{
|
||||
Destination: &DestinationModelV1{
|
||||
Type: types.StringValue("cidrv4"),
|
||||
Value: types.StringValue("prefix"),
|
||||
},
|
||||
NextHop: &NexthopModelV1{
|
||||
Type: types.StringValue("ipv4"),
|
||||
Value: types.StringValue("hop"),
|
||||
},
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
},
|
||||
expected: &iaas.CreateNetworkAreaRoutePayload{
|
||||
Ipv4: &[]iaas.Route{
|
||||
Items: &[]iaas.Route{
|
||||
{
|
||||
Prefix: utils.Ptr("prefix"),
|
||||
Nexthop: utils.Ptr("hop"),
|
||||
Destination: &iaas.RouteDestination{
|
||||
DestinationCIDRv4: &iaas.DestinationCIDRv4{
|
||||
Type: utils.Ptr("cidrv4"),
|
||||
Value: utils.Ptr("prefix"),
|
||||
},
|
||||
DestinationCIDRv6: nil,
|
||||
},
|
||||
Nexthop: &iaas.RouteNexthop{
|
||||
NexthopIPv4: &iaas.NexthopIPv4{
|
||||
Type: utils.Ptr("ipv4"),
|
||||
Value: utils.Ptr("hop"),
|
||||
},
|
||||
},
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
|
|
@ -164,13 +214,13 @@ func TestToCreatePayload(t *testing.T) {
|
|||
func TestToUpdatePayload(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input *Model
|
||||
input *ModelV1
|
||||
expected *iaas.UpdateNetworkAreaRoutePayload
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"default_ok",
|
||||
&Model{
|
||||
&ModelV1{
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key1": types.StringValue("value1"),
|
||||
"key2": types.StringValue("value2"),
|
||||
|
|
@ -203,3 +253,371 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToNextHopPayload(t *testing.T) {
|
||||
type args struct {
|
||||
model *ModelV1
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *iaas.RouteNexthop
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "ipv4",
|
||||
args: args{
|
||||
model: &ModelV1{
|
||||
NextHop: &NexthopModelV1{
|
||||
Type: types.StringValue("ipv4"),
|
||||
Value: types.StringValue("10.20.30.40"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &iaas.RouteNexthop{
|
||||
NexthopIPv4: &iaas.NexthopIPv4{
|
||||
Type: utils.Ptr("ipv4"),
|
||||
Value: utils.Ptr("10.20.30.40"),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ipv6",
|
||||
args: args{
|
||||
model: &ModelV1{
|
||||
NextHop: &NexthopModelV1{
|
||||
Type: types.StringValue("ipv6"),
|
||||
Value: types.StringValue("2001:db8:85a3:0:0:8a2e:370:7334"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &iaas.RouteNexthop{
|
||||
NexthopIPv6: &iaas.NexthopIPv6{
|
||||
Type: utils.Ptr("ipv6"),
|
||||
Value: utils.Ptr("2001:db8:85a3:0:0:8a2e:370:7334"),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "internet",
|
||||
args: args{
|
||||
model: &ModelV1{
|
||||
NextHop: &NexthopModelV1{
|
||||
Type: types.StringValue("internet"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &iaas.RouteNexthop{
|
||||
NexthopInternet: &iaas.NexthopInternet{
|
||||
Type: utils.Ptr("internet"),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "blackhole",
|
||||
args: args{
|
||||
model: &ModelV1{
|
||||
NextHop: &NexthopModelV1{
|
||||
Type: types.StringValue("blackhole"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &iaas.RouteNexthop{
|
||||
NexthopBlackhole: &iaas.NexthopBlackhole{
|
||||
Type: utils.Ptr("blackhole"),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid type",
|
||||
args: args{
|
||||
model: &ModelV1{
|
||||
NextHop: &NexthopModelV1{
|
||||
Type: types.StringValue("foobar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "model is nil",
|
||||
args: args{
|
||||
model: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "nexthop in model is nil",
|
||||
args: args{
|
||||
model: &ModelV1{
|
||||
NextHop: nil,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := toNextHopPayload(tt.args.model)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("toNextHopPayload() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("toNextHopPayload() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToDestinationPayload(t *testing.T) {
|
||||
type args struct {
|
||||
model *ModelV1
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *iaas.RouteDestination
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "cidrv4",
|
||||
args: args{
|
||||
model: &ModelV1{
|
||||
Destination: &DestinationModelV1{
|
||||
Type: types.StringValue("cidrv4"),
|
||||
Value: types.StringValue("192.168.1.0/24"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &iaas.RouteDestination{
|
||||
DestinationCIDRv4: &iaas.DestinationCIDRv4{
|
||||
Type: utils.Ptr("cidrv4"),
|
||||
Value: utils.Ptr("192.168.1.0/24"),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "cidrv6",
|
||||
args: args{
|
||||
model: &ModelV1{
|
||||
Destination: &DestinationModelV1{
|
||||
Type: types.StringValue("cidrv6"),
|
||||
Value: types.StringValue("2001:db8:1234::/48"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &iaas.RouteDestination{
|
||||
DestinationCIDRv6: &iaas.DestinationCIDRv6{
|
||||
Type: utils.Ptr("cidrv6"),
|
||||
Value: utils.Ptr("2001:db8:1234::/48"),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid type",
|
||||
args: args{
|
||||
model: &ModelV1{
|
||||
Destination: &DestinationModelV1{
|
||||
Type: types.StringValue("foobar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "model is nil",
|
||||
args: args{
|
||||
model: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "destination in model is nil",
|
||||
args: args{
|
||||
model: &ModelV1{
|
||||
Destination: nil,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := toDestinationPayload(tt.args.model)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("toDestinationPayload() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("toDestinationPayload() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapRouteNextHop(t *testing.T) {
|
||||
type args struct {
|
||||
routeResp *iaas.Route
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *NexthopModelV1
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "ipv4",
|
||||
args: args{
|
||||
routeResp: &iaas.Route{
|
||||
Nexthop: &iaas.RouteNexthop{
|
||||
NexthopIPv4: &iaas.NexthopIPv4{
|
||||
Type: utils.Ptr("ipv4"),
|
||||
Value: utils.Ptr("192.168.1.0/24"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &NexthopModelV1{
|
||||
Type: types.StringValue("ipv4"),
|
||||
Value: types.StringValue("192.168.1.0/24"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ipv6",
|
||||
args: args{
|
||||
routeResp: &iaas.Route{
|
||||
Nexthop: &iaas.RouteNexthop{
|
||||
NexthopIPv4: &iaas.NexthopIPv4{
|
||||
Type: utils.Ptr("ipv6"),
|
||||
Value: utils.Ptr("2001:db8:85a3:0:0:8a2e:370:7334"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &NexthopModelV1{
|
||||
Type: types.StringValue("ipv6"),
|
||||
Value: types.StringValue("2001:db8:85a3:0:0:8a2e:370:7334"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "blackhole",
|
||||
args: args{
|
||||
routeResp: &iaas.Route{
|
||||
Nexthop: &iaas.RouteNexthop{
|
||||
NexthopBlackhole: &iaas.NexthopBlackhole{
|
||||
Type: utils.Ptr("blackhole"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &NexthopModelV1{
|
||||
Type: types.StringValue("blackhole"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "internet",
|
||||
args: args{
|
||||
routeResp: &iaas.Route{
|
||||
Nexthop: &iaas.RouteNexthop{
|
||||
NexthopInternet: &iaas.NexthopInternet{
|
||||
Type: utils.Ptr("internet"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &NexthopModelV1{
|
||||
Type: types.StringValue("internet"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := mapRouteNextHop(tt.args.routeResp)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("mapRouteNextHop() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("mapRouteNextHop() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapRouteDestination(t *testing.T) {
|
||||
type args struct {
|
||||
routeResp *iaas.Route
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *DestinationModelV1
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "cidrv4",
|
||||
args: args{
|
||||
routeResp: &iaas.Route{
|
||||
Destination: &iaas.RouteDestination{
|
||||
DestinationCIDRv4: &iaas.DestinationCIDRv4{
|
||||
Type: utils.Ptr("cidrv4"),
|
||||
Value: utils.Ptr("192.168.1.0/24"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &DestinationModelV1{
|
||||
Type: types.StringValue("cidrv4"),
|
||||
Value: types.StringValue("192.168.1.0/24"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "cidrv6",
|
||||
args: args{
|
||||
routeResp: &iaas.Route{
|
||||
Destination: &iaas.RouteDestination{
|
||||
DestinationCIDRv4: &iaas.DestinationCIDRv4{
|
||||
Type: utils.Ptr("cidrv6"),
|
||||
Value: utils.Ptr("2001:db8:1234::/48"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &DestinationModelV1{
|
||||
Type: types.StringValue("cidrv6"),
|
||||
Value: types.StringValue("2001:db8:1234::/48"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "destination in API response is nil",
|
||||
args: args{
|
||||
routeResp: &iaas.Route{
|
||||
Destination: nil,
|
||||
},
|
||||
},
|
||||
want: &DestinationModelV1{
|
||||
Type: types.StringNull(),
|
||||
Value: types.StringNull(),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := mapRouteDestination(tt.args.routeResp)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("mapRouteDestination() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("mapRouteDestination() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,14 +24,15 @@ var (
|
|||
_ datasource.DataSource = &networkInterfaceDataSource{}
|
||||
)
|
||||
|
||||
// NewNetworkDataSource is a helper function to simplify the provider implementation.
|
||||
// NewNetworkInterfaceDataSource is a helper function to simplify the provider implementation.
|
||||
func NewNetworkInterfaceDataSource() datasource.DataSource {
|
||||
return &networkInterfaceDataSource{}
|
||||
}
|
||||
|
||||
// networkInterfaceDataSource is the data source implementation.
|
||||
type networkInterfaceDataSource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
|
|
@ -40,12 +41,13 @@ func (d *networkInterfaceDataSource) Metadata(_ context.Context, req datasource.
|
|||
}
|
||||
|
||||
func (d *networkInterfaceDataSource) 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
|
||||
}
|
||||
|
|
@ -63,7 +65,7 @@ func (d *networkInterfaceDataSource) Schema(_ context.Context, _ datasource.Sche
|
|||
Description: description,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal data source ID. It is structured as \"`project_id`,`network_id`,`network_interface_id`\".",
|
||||
Description: "Terraform's internal data source ID. It is structured as \"`project_id`,`region`,`network_id`,`network_interface_id`\".",
|
||||
Computed: true,
|
||||
},
|
||||
"project_id": schema.StringAttribute{
|
||||
|
|
@ -74,6 +76,11 @@ func (d *networkInterfaceDataSource) Schema(_ context.Context, _ datasource.Sche
|
|||
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,
|
||||
},
|
||||
"network_id": schema.StringAttribute{
|
||||
Description: "The network ID to which the network interface is associated.",
|
||||
Required: true,
|
||||
|
|
@ -141,17 +148,20 @@ func (d *networkInterfaceDataSource) Read(ctx context.Context, req datasource.Re
|
|||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := d.providerData.GetRegionWithOverride(model.Region)
|
||||
networkId := model.NetworkId.ValueString()
|
||||
networkInterfaceId := model.NetworkInterfaceId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
ctx = tflog.SetField(ctx, "network_interface_id", networkInterfaceId)
|
||||
|
||||
networkInterfaceResp, err := d.client.GetNic(ctx, projectId, networkId, networkInterfaceId).Execute()
|
||||
networkInterfaceResp, err := d.client.GetNic(ctx, projectId, region, networkId, networkInterfaceId).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
|
|
@ -169,7 +179,7 @@ func (d *networkInterfaceDataSource) Read(ctx context.Context, req datasource.Re
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapFields(ctx, networkInterfaceResp, &model)
|
||||
err = mapFields(ctx, networkInterfaceResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network interface", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ type Model struct {
|
|||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
NetworkId types.String `tfsdk:"network_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
NetworkInterfaceId types.String `tfsdk:"network_interface_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
AllowedAddresses types.List `tfsdk:"allowed_addresses"`
|
||||
|
|
@ -59,7 +60,8 @@ func NewNetworkInterfaceResource() resource.Resource {
|
|||
|
||||
// networkResource is the resource implementation.
|
||||
type networkInterfaceResource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
|
|
@ -92,6 +94,17 @@ func (r *networkInterfaceResource) ModifyPlan(ctx context.Context, req resource.
|
|||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
|
|
@ -101,12 +114,13 @@ func (r *networkInterfaceResource) Metadata(_ context.Context, req resource.Meta
|
|||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *networkInterfaceResource) 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
|
||||
}
|
||||
|
|
@ -124,7 +138,7 @@ func (r *networkInterfaceResource) Schema(_ context.Context, _ resource.SchemaRe
|
|||
Description: description,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`network_id`,`network_interface_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`network_id`,`network_interface_id`\".",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
|
|
@ -164,6 +178,15 @@ func (r *networkInterfaceResource) Schema(_ context.Context, _ resource.SchemaRe
|
|||
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(),
|
||||
},
|
||||
},
|
||||
"name": schema.StringAttribute{
|
||||
Description: "The name of the network interface.",
|
||||
Optional: true,
|
||||
|
|
@ -260,8 +283,10 @@ func (r *networkInterfaceResource) Create(ctx context.Context, req resource.Crea
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
networkId := model.NetworkId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
|
||||
// Generate API request body from model
|
||||
|
|
@ -272,7 +297,7 @@ func (r *networkInterfaceResource) Create(ctx context.Context, req resource.Crea
|
|||
}
|
||||
|
||||
// Create new network interface
|
||||
networkInterface, err := r.client.CreateNic(ctx, projectId, networkId).CreateNicPayload(*payload).Execute()
|
||||
networkInterface, err := r.client.CreateNic(ctx, projectId, region, networkId).CreateNicPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network interface", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -285,7 +310,7 @@ func (r *networkInterfaceResource) Create(ctx context.Context, req resource.Crea
|
|||
ctx = tflog.SetField(ctx, "network_interface_id", networkInterfaceId)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, networkInterface, &model)
|
||||
err = mapFields(ctx, networkInterface, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network interface", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -308,16 +333,18 @@ func (r *networkInterfaceResource) Read(ctx context.Context, req resource.ReadRe
|
|||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
networkId := model.NetworkId.ValueString()
|
||||
networkInterfaceId := model.NetworkInterfaceId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
ctx = tflog.SetField(ctx, "network_interface_id", networkInterfaceId)
|
||||
|
||||
networkInterfaceResp, err := r.client.GetNic(ctx, projectId, networkId, networkInterfaceId).Execute()
|
||||
networkInterfaceResp, err := r.client.GetNic(ctx, projectId, region, networkId, networkInterfaceId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
|
|
@ -331,7 +358,7 @@ func (r *networkInterfaceResource) Read(ctx context.Context, req resource.ReadRe
|
|||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, networkInterfaceResp, &model)
|
||||
err = mapFields(ctx, networkInterfaceResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network interface", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -355,12 +382,14 @@ func (r *networkInterfaceResource) Update(ctx context.Context, req resource.Upda
|
|||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
networkId := model.NetworkId.ValueString()
|
||||
networkInterfaceId := model.NetworkInterfaceId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
ctx = tflog.SetField(ctx, "network_interface_id", networkInterfaceId)
|
||||
|
||||
|
|
@ -379,7 +408,7 @@ func (r *networkInterfaceResource) Update(ctx context.Context, req resource.Upda
|
|||
return
|
||||
}
|
||||
// Update existing network
|
||||
nicResp, err := r.client.UpdateNic(ctx, projectId, networkId, networkInterfaceId).UpdateNicPayload(*payload).Execute()
|
||||
nicResp, err := r.client.UpdateNic(ctx, projectId, region, networkId, networkInterfaceId).UpdateNicPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network interface", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -387,7 +416,7 @@ func (r *networkInterfaceResource) Update(ctx context.Context, req resource.Upda
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapFields(ctx, nicResp, &model)
|
||||
err = mapFields(ctx, nicResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network interface", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -411,17 +440,19 @@ func (r *networkInterfaceResource) Delete(ctx context.Context, req resource.Dele
|
|||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
networkId := model.NetworkId.ValueString()
|
||||
networkInterfaceId := model.NetworkInterfaceId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
ctx = tflog.SetField(ctx, "network_interface_id", networkInterfaceId)
|
||||
|
||||
// Delete existing network interface
|
||||
err := r.client.DeleteNic(ctx, projectId, networkId, networkInterfaceId).Execute()
|
||||
err := r.client.DeleteNic(ctx, projectId, region, networkId, networkInterfaceId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network interface", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -437,28 +468,25 @@ func (r *networkInterfaceResource) Delete(ctx context.Context, req resource.Dele
|
|||
func (r *networkInterfaceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
|
||||
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
|
||||
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics,
|
||||
"Error importing network interface",
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[network_id],[network_interface_id] Got: %q", req.ID),
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[network_id],[network_interface_id] Got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
networkId := idParts[1]
|
||||
networkInterfaceId := idParts[2]
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "network_id", networkId)
|
||||
ctx = tflog.SetField(ctx, "network_interface_id", networkInterfaceId)
|
||||
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
|
||||
"project_id": idParts[0],
|
||||
"region": idParts[1],
|
||||
"network_id": idParts[2],
|
||||
"network_interface_id": idParts[3],
|
||||
})
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_id"), networkId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_interface_id"), networkInterfaceId)...)
|
||||
tflog.Info(ctx, "Network interface state imported")
|
||||
}
|
||||
|
||||
func mapFields(ctx context.Context, networkInterfaceResp *iaas.NIC, model *Model) error {
|
||||
func mapFields(ctx context.Context, networkInterfaceResp *iaas.NIC, model *Model, region string) error {
|
||||
if networkInterfaceResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
|
|
@ -475,7 +503,8 @@ func mapFields(ctx context.Context, networkInterfaceResp *iaas.NIC, model *Model
|
|||
return fmt.Errorf("network interface id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.NetworkId.ValueString(), networkInterfaceId)
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, model.NetworkId.ValueString(), networkInterfaceId)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
respAllowedAddresses := []string{}
|
||||
var diags diag.Diagnostics
|
||||
|
|
|
|||
|
|
@ -12,25 +12,32 @@ import (
|
|||
)
|
||||
|
||||
func TestMapFields(t *testing.T) {
|
||||
type args struct {
|
||||
state Model
|
||||
input *iaas.NIC
|
||||
region string
|
||||
}
|
||||
tests := []struct {
|
||||
description string
|
||||
state Model
|
||||
input *iaas.NIC
|
||||
args args
|
||||
expected Model
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"id_ok",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
description: "id_ok",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
},
|
||||
input: &iaas.NIC{
|
||||
Id: utils.Ptr("nicid"),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.NIC{
|
||||
Id: utils.Ptr("nicid"),
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,nid,nicid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,nid,nicid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
|
|
@ -43,41 +50,46 @@ func TestMapFields(t *testing.T) {
|
|||
Mac: types.StringNull(),
|
||||
Type: types.StringNull(),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"values_ok",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
},
|
||||
&iaas.NIC{
|
||||
Id: utils.Ptr("nicid"),
|
||||
Name: utils.Ptr("name"),
|
||||
AllowedAddresses: &[]iaas.AllowedAddressesInner{
|
||||
{
|
||||
String: utils.Ptr("aa1"),
|
||||
description: "values_ok",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
input: &iaas.NIC{
|
||||
Id: utils.Ptr("nicid"),
|
||||
Name: utils.Ptr("name"),
|
||||
AllowedAddresses: &[]iaas.AllowedAddressesInner{
|
||||
{
|
||||
String: utils.Ptr("aa1"),
|
||||
},
|
||||
},
|
||||
SecurityGroups: &[]string{
|
||||
"prefix1",
|
||||
"prefix2",
|
||||
},
|
||||
Ipv4: utils.Ptr("ipv4"),
|
||||
Ipv6: utils.Ptr("ipv6"),
|
||||
NicSecurity: utils.Ptr(true),
|
||||
Device: utils.Ptr("device"),
|
||||
Mac: utils.Ptr("mac"),
|
||||
Status: utils.Ptr("status"),
|
||||
Type: utils.Ptr("type"),
|
||||
Labels: &map[string]interface{}{
|
||||
"label1": "ref1",
|
||||
},
|
||||
},
|
||||
SecurityGroups: &[]string{
|
||||
"prefix1",
|
||||
"prefix2",
|
||||
},
|
||||
Ipv4: utils.Ptr("ipv4"),
|
||||
Ipv6: utils.Ptr("ipv6"),
|
||||
NicSecurity: utils.Ptr(true),
|
||||
Device: utils.Ptr("device"),
|
||||
Mac: utils.Ptr("mac"),
|
||||
Status: utils.Ptr("status"),
|
||||
Type: utils.Ptr("type"),
|
||||
Labels: &map[string]interface{}{
|
||||
"label1": "ref1",
|
||||
},
|
||||
region: "eu02",
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,nid,nicid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu02,nid,nicid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
|
|
@ -95,29 +107,33 @@ func TestMapFields(t *testing.T) {
|
|||
Mac: types.StringValue("mac"),
|
||||
Type: types.StringValue("type"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"label1": types.StringValue("ref1")}),
|
||||
Region: types.StringValue("eu02"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"allowed_addresses_changed_outside_tf",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
AllowedAddresses: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("aa1"),
|
||||
}),
|
||||
},
|
||||
&iaas.NIC{
|
||||
Id: utils.Ptr("nicid"),
|
||||
AllowedAddresses: &[]iaas.AllowedAddressesInner{
|
||||
{
|
||||
String: utils.Ptr("aa2"),
|
||||
description: "allowed_addresses_changed_outside_tf",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
AllowedAddresses: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("aa1"),
|
||||
}),
|
||||
},
|
||||
input: &iaas.NIC{
|
||||
Id: utils.Ptr("nicid"),
|
||||
AllowedAddresses: &[]iaas.AllowedAddressesInner{
|
||||
{
|
||||
String: utils.Ptr("aa2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,nid,nicid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,nid,nicid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
|
|
@ -127,23 +143,27 @@ func TestMapFields(t *testing.T) {
|
|||
types.StringValue("aa2"),
|
||||
}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"empty_list_allowed_addresses",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
AllowedAddresses: types.ListValueMust(types.StringType, []attr.Value{}),
|
||||
description: "empty_list_allowed_addresses",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
AllowedAddresses: types.ListValueMust(types.StringType, []attr.Value{}),
|
||||
},
|
||||
input: &iaas.NIC{
|
||||
Id: utils.Ptr("nicid"),
|
||||
AllowedAddresses: nil,
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.NIC{
|
||||
Id: utils.Ptr("nicid"),
|
||||
AllowedAddresses: nil,
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,nid,nicid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,nid,nicid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
NetworkId: types.StringValue("nid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
|
|
@ -151,29 +171,34 @@ func TestMapFields(t *testing.T) {
|
|||
SecurityGroupIds: types.ListNull(types.StringType),
|
||||
AllowedAddresses: types.ListValueMust(types.StringType, []attr.Value{}),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"response_nil_fail",
|
||||
Model{},
|
||||
nil,
|
||||
Model{},
|
||||
false,
|
||||
description: "response_nil_fail",
|
||||
args: args{
|
||||
state: Model{},
|
||||
input: nil,
|
||||
},
|
||||
expected: Model{},
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
"no_resource_id",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
description: "no_resource_id",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
},
|
||||
input: &iaas.NIC{},
|
||||
},
|
||||
&iaas.NIC{},
|
||||
Model{},
|
||||
false,
|
||||
expected: Model{},
|
||||
isValid: 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")
|
||||
}
|
||||
|
|
@ -181,7 +206,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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
|
|
@ -30,11 +29,13 @@ var (
|
|||
_ resource.Resource = &networkInterfaceAttachResource{}
|
||||
_ resource.ResourceWithConfigure = &networkInterfaceAttachResource{}
|
||||
_ resource.ResourceWithImportState = &networkInterfaceAttachResource{}
|
||||
_ resource.ResourceWithModifyPlan = &networkInterfaceAttachResource{}
|
||||
)
|
||||
|
||||
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"`
|
||||
NetworkInterfaceId types.String `tfsdk:"network_interface_id"`
|
||||
}
|
||||
|
|
@ -46,7 +47,8 @@ func NewNetworkInterfaceAttachResource() resource.Resource {
|
|||
|
||||
// networkInterfaceAttachResource is the resource implementation.
|
||||
type networkInterfaceAttachResource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
|
|
@ -54,14 +56,45 @@ func (r *networkInterfaceAttachResource) Metadata(_ context.Context, req resourc
|
|||
resp.TypeName = req.ProviderTypeName + "_server_network_interface_attach"
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
func (r *networkInterfaceAttachResource) 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
|
||||
}
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *networkInterfaceAttachResource) 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
|
||||
}
|
||||
|
|
@ -71,13 +104,13 @@ func (r *networkInterfaceAttachResource) Configure(ctx context.Context, req reso
|
|||
|
||||
// Schema defines the schema for the resource.
|
||||
func (r *networkInterfaceAttachResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
description := "Network interface attachment resource schema. Attaches a network interface to a server. Must have a `region` specified in the provider configuration. The attachment only takes full effect after server reboot."
|
||||
description := "Network interface attachment resource schema. Attaches a network interface to a server. The attachment only takes full effect after server reboot."
|
||||
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`,`network_interface_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`server_id`,`network_interface_id`\".",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
|
|
@ -94,6 +127,15 @@ func (r *networkInterfaceAttachResource) Schema(_ context.Context, _ resource.Sc
|
|||
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.",
|
||||
Required: true,
|
||||
|
|
@ -133,14 +175,16 @@ func (r *networkInterfaceAttachResource) Create(ctx context.Context, req resourc
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
serverId := model.ServerId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
networkInterfaceId := model.NetworkInterfaceId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
ctx = tflog.SetField(ctx, "network_interface_id", networkInterfaceId)
|
||||
|
||||
// Create new network interface attachment
|
||||
err := r.client.AddNicToServer(ctx, projectId, serverId, networkInterfaceId).Execute()
|
||||
err := r.client.AddNicToServer(ctx, projectId, region, serverId, networkInterfaceId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error attaching network interface to server", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -148,7 +192,8 @@ func (r *networkInterfaceAttachResource) Create(ctx context.Context, req resourc
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(projectId, serverId, networkInterfaceId)
|
||||
model.Id = utils.BuildInternalTerraformId(projectId, region, serverId, networkInterfaceId)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
// Set state to fully populated data
|
||||
diags = resp.State.Set(ctx, model)
|
||||
|
|
@ -171,13 +216,14 @@ func (r *networkInterfaceAttachResource) Read(ctx context.Context, req resource.
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
serverId := model.ServerId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
networkInterfaceId := model.NetworkInterfaceId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
ctx = tflog.SetField(ctx, "network_interface_id", networkInterfaceId)
|
||||
|
||||
nics, err := r.client.ListServerNics(ctx, projectId, serverId).Execute()
|
||||
nics, err := r.client.ListServerNICs(ctx, projectId, region, serverId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
|
|
@ -200,12 +246,17 @@ func (r *networkInterfaceAttachResource) Read(ctx context.Context, req resource.
|
|||
if nic.Id == nil || (nic.Id != nil && *nic.Id != networkInterfaceId) {
|
||||
continue
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(projectId, region, serverId, networkInterfaceId)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
// Set refreshed state
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Network interface attachment read")
|
||||
return
|
||||
}
|
||||
|
|
@ -233,14 +284,16 @@ func (r *networkInterfaceAttachResource) Delete(ctx context.Context, req resourc
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
serverId := model.ServerId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
network_interfaceId := model.NetworkInterfaceId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
ctx = tflog.SetField(ctx, "network_interface_id", network_interfaceId)
|
||||
|
||||
// Remove network_interface from server
|
||||
err := r.client.RemoveNicFromServer(ctx, projectId, serverId, network_interfaceId).Execute()
|
||||
err := r.client.RemoveNicFromServer(ctx, projectId, region, serverId, network_interfaceId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error removing network interface from server", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -256,23 +309,20 @@ func (r *networkInterfaceAttachResource) Delete(ctx context.Context, req resourc
|
|||
func (r *networkInterfaceAttachResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
|
||||
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
|
||||
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics,
|
||||
"Error importing network_interface attachment",
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[server_id],[network_interface_id] Got: %q", req.ID),
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[server_id],[network_interface_id] Got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
serverId := idParts[1]
|
||||
network_interfaceId := idParts[2]
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
ctx = tflog.SetField(ctx, "network_interface_id", network_interfaceId)
|
||||
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]interface{}{
|
||||
"project_id": idParts[0],
|
||||
"region": idParts[1],
|
||||
"server_id": idParts[2],
|
||||
"network_interface_id": idParts[3],
|
||||
})
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("server_id"), serverId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_interface_id"), network_interfaceId)...)
|
||||
tflog.Info(ctx, "Network interface attachment state imported")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,9 +28,12 @@ type DatasourceModel struct {
|
|||
ProjectId types.String `tfsdk:"project_id"`
|
||||
AreaId types.String `tfsdk:"area_id"`
|
||||
InternetAccess types.Bool `tfsdk:"internet_access"`
|
||||
State types.String `tfsdk:"state"`
|
||||
Status types.String `tfsdk:"status"`
|
||||
CreatedAt types.String `tfsdk:"created_at"`
|
||||
UpdatedAt types.String `tfsdk:"updated_at"`
|
||||
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
State types.String `tfsdk:"state"`
|
||||
}
|
||||
|
||||
// NewProjectDataSource is a helper function to simplify the provider implementation.
|
||||
|
|
@ -70,7 +73,7 @@ func (d *projectDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
|
|||
"project_id": "STACKIT project ID.",
|
||||
"area_id": "The area ID to which the project belongs to.",
|
||||
"internet_access": "Specifies if the project has internet_access",
|
||||
"state": "Specifies the state of the project.",
|
||||
"status": "Specifies the status of the project.",
|
||||
"created_at": "Date-time when the project was created.",
|
||||
"updated_at": "Date-time when the project was last updated.",
|
||||
}
|
||||
|
|
@ -98,8 +101,14 @@ func (d *projectDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
|
|||
Description: descriptions["internet_access"],
|
||||
Computed: true,
|
||||
},
|
||||
// Deprecated: Will be removed in May 2026. Only kept to make the IaaS v1 -> v2 API migration non-breaking in the Terraform provider.
|
||||
"state": schema.StringAttribute{
|
||||
Description: descriptions["state"],
|
||||
DeprecationMessage: "Deprecated: Will be removed in May 2026. Use the `status` field instead.",
|
||||
Description: descriptions["status"],
|
||||
Computed: true,
|
||||
},
|
||||
"status": schema.StringAttribute{
|
||||
Description: descriptions["status"],
|
||||
Computed: true,
|
||||
},
|
||||
"created_at": schema.StringAttribute{
|
||||
|
|
@ -170,8 +179,8 @@ func mapDataSourceFields(projectResp *iaas.Project, model *DatasourceModel) erro
|
|||
var projectId string
|
||||
if model.ProjectId.ValueString() != "" {
|
||||
projectId = model.ProjectId.ValueString()
|
||||
} else if projectResp.ProjectId != nil {
|
||||
projectId = *projectResp.ProjectId
|
||||
} else if projectResp.Id != nil {
|
||||
projectId = *projectResp.Id
|
||||
} else {
|
||||
return fmt.Errorf("project id is not present")
|
||||
}
|
||||
|
|
@ -202,7 +211,8 @@ func mapDataSourceFields(projectResp *iaas.Project, model *DatasourceModel) erro
|
|||
|
||||
model.AreaId = areaId
|
||||
model.InternetAccess = types.BoolPointerValue(projectResp.InternetAccess)
|
||||
model.State = types.StringPointerValue(projectResp.State)
|
||||
model.State = types.StringPointerValue(projectResp.Status)
|
||||
model.Status = types.StringPointerValue(projectResp.Status)
|
||||
model.CreatedAt = createdAt
|
||||
model.UpdatedAt = updatedAt
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
ProjectId: types.StringValue(projectId),
|
||||
},
|
||||
input: &iaas.Project{
|
||||
ProjectId: utils.Ptr(projectId),
|
||||
Id: utils.Ptr(projectId),
|
||||
},
|
||||
expected: &DatasourceModel{
|
||||
Id: types.StringValue(projectId),
|
||||
|
|
@ -48,13 +48,12 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
ProjectId: types.StringValue(projectId),
|
||||
},
|
||||
input: &iaas.Project{
|
||||
AreaId: utils.Ptr(iaas.AreaId{String: utils.Ptr("aid")}),
|
||||
CreatedAt: utils.Ptr(testTimestamp()),
|
||||
InternetAccess: utils.Ptr(true),
|
||||
OpenstackProjectId: utils.Ptr("oid"),
|
||||
ProjectId: utils.Ptr(projectId),
|
||||
State: utils.Ptr("CREATED"),
|
||||
UpdatedAt: utils.Ptr(testTimestamp()),
|
||||
AreaId: utils.Ptr(iaas.AreaId{String: utils.Ptr("aid")}),
|
||||
CreatedAt: utils.Ptr(testTimestamp()),
|
||||
InternetAccess: utils.Ptr(true),
|
||||
Id: utils.Ptr(projectId),
|
||||
Status: utils.Ptr("CREATED"),
|
||||
UpdatedAt: utils.Ptr(testTimestamp()),
|
||||
},
|
||||
expected: &DatasourceModel{
|
||||
Id: types.StringValue(projectId),
|
||||
|
|
@ -62,6 +61,7 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
AreaId: types.StringValue("aid"),
|
||||
InternetAccess: types.BoolValue(true),
|
||||
State: types.StringValue("CREATED"),
|
||||
Status: types.StringValue("CREATED"),
|
||||
CreatedAt: types.StringValue(testTimestampValue),
|
||||
UpdatedAt: types.StringValue(testTimestampValue),
|
||||
},
|
||||
|
|
@ -76,7 +76,7 @@ func TestMapDataSourceFields(t *testing.T) {
|
|||
AreaId: utils.Ptr(iaas.AreaId{
|
||||
StaticAreaID: iaas.STATICAREAID_PUBLIC.Ptr(),
|
||||
}),
|
||||
ProjectId: utils.Ptr(projectId),
|
||||
Id: utils.Ptr(projectId),
|
||||
},
|
||||
expected: &DatasourceModel{
|
||||
Id: types.StringValue(projectId),
|
||||
|
|
|
|||
|
|
@ -24,14 +24,15 @@ var (
|
|||
_ datasource.DataSource = &publicIpDataSource{}
|
||||
)
|
||||
|
||||
// NewVolumeDataSource is a helper function to simplify the provider implementation.
|
||||
// NewPublicIpDataSource is a helper function to simplify the provider implementation.
|
||||
func NewPublicIpDataSource() datasource.DataSource {
|
||||
return &publicIpDataSource{}
|
||||
}
|
||||
|
||||
// publicIpDataSource is the data source implementation.
|
||||
type publicIpDataSource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
|
|
@ -40,12 +41,13 @@ func (d *publicIpDataSource) Metadata(_ context.Context, req datasource.Metadata
|
|||
}
|
||||
|
||||
func (d *publicIpDataSource) 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
|
||||
}
|
||||
|
|
@ -54,14 +56,14 @@ func (d *publicIpDataSource) Configure(ctx context.Context, req datasource.Confi
|
|||
}
|
||||
|
||||
// Schema defines the schema for the resource.
|
||||
func (r *publicIpDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
func (d *publicIpDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
description := "Public IP resource 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 datasource ID. It is structured as \"`project_id`,`public_ip_id`\".",
|
||||
Description: "Terraform's internal datasource ID. It is structured as \"`project_id`,`region`,`public_ip_id`\".",
|
||||
Computed: true,
|
||||
},
|
||||
"project_id": schema.StringAttribute{
|
||||
|
|
@ -72,6 +74,11 @@ func (r *publicIpDataSource) Schema(_ context.Context, _ datasource.SchemaReques
|
|||
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,
|
||||
},
|
||||
"public_ip_id": schema.StringAttribute{
|
||||
Description: "The public IP ID.",
|
||||
Required: true,
|
||||
|
|
@ -110,14 +117,16 @@ func (d *publicIpDataSource) Read(ctx context.Context, req datasource.ReadReques
|
|||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := d.providerData.GetRegionWithOverride(model.Region)
|
||||
publicIpId := model.PublicIpId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "public_ip_id", publicIpId)
|
||||
|
||||
publicIpResp, err := d.client.GetPublicIP(ctx, projectId, publicIpId).Execute()
|
||||
publicIpResp, err := d.client.GetPublicIP(ctx, projectId, region, publicIpId).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
|
|
@ -135,7 +144,7 @@ func (d *publicIpDataSource) Read(ctx context.Context, req datasource.ReadReques
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapFields(ctx, publicIpResp, &model)
|
||||
err = mapFields(ctx, publicIpResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading public IP", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import (
|
|||
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
|
|
@ -30,11 +29,13 @@ var (
|
|||
_ resource.Resource = &publicIpResource{}
|
||||
_ resource.ResourceWithConfigure = &publicIpResource{}
|
||||
_ resource.ResourceWithImportState = &publicIpResource{}
|
||||
_ resource.ResourceWithModifyPlan = &publicIpResource{}
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
PublicIpId types.String `tfsdk:"public_ip_id"`
|
||||
Ip types.String `tfsdk:"ip"`
|
||||
NetworkInterfaceId types.String `tfsdk:"network_interface_id"`
|
||||
|
|
@ -48,7 +49,8 @@ func NewPublicIpResource() resource.Resource {
|
|||
|
||||
// publicIpResource is the resource implementation.
|
||||
type publicIpResource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
|
|
@ -56,14 +58,45 @@ func (r *publicIpResource) Metadata(_ context.Context, req resource.MetadataRequ
|
|||
resp.TypeName = req.ProviderTypeName + "_public_ip"
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
func (r *publicIpResource) 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
|
||||
}
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *publicIpResource) 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
|
||||
}
|
||||
|
|
@ -79,7 +112,7 @@ func (r *publicIpResource) Schema(_ context.Context, _ resource.SchemaRequest, r
|
|||
Description: description,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`public_ip_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`public_ip_id`\".",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
|
|
@ -96,6 +129,15 @@ func (r *publicIpResource) Schema(_ context.Context, _ resource.SchemaRequest, r
|
|||
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(),
|
||||
},
|
||||
},
|
||||
"public_ip_id": schema.StringAttribute{
|
||||
Description: "The public IP ID.",
|
||||
Computed: true,
|
||||
|
|
@ -148,7 +190,9 @@ func (r *publicIpResource) Create(ctx context.Context, req resource.CreateReques
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Generate API request body from model
|
||||
payload, err := toCreatePayload(ctx, &model)
|
||||
|
|
@ -159,7 +203,7 @@ func (r *publicIpResource) Create(ctx context.Context, req resource.CreateReques
|
|||
|
||||
// Create new public IP
|
||||
|
||||
publicIp, err := r.client.CreatePublicIP(ctx, projectId).CreatePublicIPPayload(*payload).Execute()
|
||||
publicIp, err := r.client.CreatePublicIP(ctx, projectId, region).CreatePublicIPPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating public IP", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -170,7 +214,7 @@ func (r *publicIpResource) Create(ctx context.Context, req resource.CreateReques
|
|||
ctx = tflog.SetField(ctx, "public_ip_id", *publicIp.Id)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, publicIp, &model)
|
||||
err = mapFields(ctx, publicIp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating public IP", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -193,14 +237,16 @@ func (r *publicIpResource) Read(ctx context.Context, req resource.ReadRequest, r
|
|||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
publicIpId := model.PublicIpId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "public_ip_id", publicIpId)
|
||||
|
||||
publicIpResp, err := r.client.GetPublicIP(ctx, projectId, publicIpId).Execute()
|
||||
publicIpResp, err := r.client.GetPublicIP(ctx, projectId, region, publicIpId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
|
|
@ -214,7 +260,7 @@ func (r *publicIpResource) Read(ctx context.Context, req resource.ReadRequest, r
|
|||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, publicIpResp, &model)
|
||||
err = mapFields(ctx, publicIpResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading public IP", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -238,11 +284,13 @@ func (r *publicIpResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
publicIpId := model.PublicIpId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "public_ip_id", publicIpId)
|
||||
|
||||
// Retrieve values from state
|
||||
|
|
@ -260,7 +308,7 @@ func (r *publicIpResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
return
|
||||
}
|
||||
// Update existing public IP
|
||||
updatedPublicIp, err := r.client.UpdatePublicIP(ctx, projectId, publicIpId).UpdatePublicIPPayload(*payload).Execute()
|
||||
updatedPublicIp, err := r.client.UpdatePublicIP(ctx, projectId, region, publicIpId).UpdatePublicIPPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating public IP", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -268,7 +316,7 @@ func (r *publicIpResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapFields(ctx, updatedPublicIp, &model)
|
||||
err = mapFields(ctx, updatedPublicIp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating public IP", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -292,15 +340,17 @@ func (r *publicIpResource) Delete(ctx context.Context, req resource.DeleteReques
|
|||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
publicIpId := model.PublicIpId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "public_ip_id", publicIpId)
|
||||
|
||||
// Delete existing publicIp
|
||||
err := r.client.DeletePublicIP(ctx, projectId, publicIpId).Execute()
|
||||
err := r.client.DeletePublicIP(ctx, projectId, region, publicIpId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting public IP", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -316,25 +366,24 @@ func (r *publicIpResource) Delete(ctx context.Context, req resource.DeleteReques
|
|||
func (r *publicIpResource) 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 public IP",
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[public_ip_id] Got: %q", req.ID),
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[public_ip_id] Got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
publicIpId := idParts[1]
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "public_ip_id", publicIpId)
|
||||
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
|
||||
"project_id": idParts[0],
|
||||
"region": idParts[1],
|
||||
"public_ip_id": idParts[2],
|
||||
})
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("public_ip_id"), publicIpId)...)
|
||||
tflog.Info(ctx, "public IP state imported")
|
||||
}
|
||||
|
||||
func mapFields(ctx context.Context, publicIpResp *iaas.PublicIp, model *Model) error {
|
||||
func mapFields(ctx context.Context, publicIpResp *iaas.PublicIp, model *Model, region string) error {
|
||||
if publicIpResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
|
|
@ -351,7 +400,8 @@ func mapFields(ctx context.Context, publicIpResp *iaas.PublicIp, model *Model) e
|
|||
return fmt.Errorf("public IP id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), publicIpId)
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, publicIpId)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
labels, err := iaasUtils.MapLabels(ctx, publicIpResp.Labels, model.Labels)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -12,49 +12,61 @@ import (
|
|||
)
|
||||
|
||||
func TestMapFields(t *testing.T) {
|
||||
type args struct {
|
||||
state Model
|
||||
input *iaas.PublicIp
|
||||
region string
|
||||
}
|
||||
tests := []struct {
|
||||
description string
|
||||
state Model
|
||||
input *iaas.PublicIp
|
||||
args args
|
||||
expected Model
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"default_values",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
description: "default_values",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
},
|
||||
input: &iaas.PublicIp{
|
||||
Id: utils.Ptr("pipid"),
|
||||
NetworkInterface: iaas.NewNullableString(nil),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.PublicIp{
|
||||
Id: utils.Ptr("pipid"),
|
||||
NetworkInterface: iaas.NewNullableString(nil),
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,pipid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,pipid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
Ip: types.StringNull(),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
NetworkInterfaceId: types.StringNull(),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"simple_values",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
},
|
||||
&iaas.PublicIp{
|
||||
Id: utils.Ptr("pipid"),
|
||||
Ip: utils.Ptr("ip"),
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
description: "simple_values",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
NetworkInterface: iaas.NewNullableString(utils.Ptr("interface")),
|
||||
input: &iaas.PublicIp{
|
||||
Id: utils.Ptr("pipid"),
|
||||
Ip: utils.Ptr("ip"),
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
NetworkInterface: iaas.NewNullableString(utils.Ptr("interface")),
|
||||
},
|
||||
region: "eu02",
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,pipid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu02,pipid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
Ip: types.StringValue("ip"),
|
||||
|
|
@ -62,69 +74,74 @@ func TestMapFields(t *testing.T) {
|
|||
"key": types.StringValue("value"),
|
||||
}),
|
||||
NetworkInterfaceId: types.StringValue("interface"),
|
||||
Region: types.StringValue("eu02"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"empty_labels",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
description: "empty_labels",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
},
|
||||
input: &iaas.PublicIp{
|
||||
Id: utils.Ptr("pipid"),
|
||||
NetworkInterface: iaas.NewNullableString(utils.Ptr("interface")),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.PublicIp{
|
||||
Id: utils.Ptr("pipid"),
|
||||
NetworkInterface: iaas.NewNullableString(utils.Ptr("interface")),
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,pipid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,pipid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
Ip: types.StringNull(),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
NetworkInterfaceId: types.StringValue("interface"),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"network_interface_id_nil",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
description: "network_interface_id_nil",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
},
|
||||
input: &iaas.PublicIp{
|
||||
Id: utils.Ptr("pipid"),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.PublicIp{
|
||||
Id: utils.Ptr("pipid"),
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,pipid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,pipid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
Ip: types.StringNull(),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
NetworkInterfaceId: 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.PublicIp{},
|
||||
},
|
||||
&iaas.PublicIp{},
|
||||
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")
|
||||
}
|
||||
|
|
@ -132,7 +149,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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import (
|
|||
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
|
|
@ -30,11 +29,13 @@ var (
|
|||
_ resource.Resource = &publicIpAssociateResource{}
|
||||
_ resource.ResourceWithConfigure = &publicIpAssociateResource{}
|
||||
_ resource.ResourceWithImportState = &publicIpAssociateResource{}
|
||||
_ resource.ResourceWithModifyPlan = &publicIpAssociateResource{}
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
PublicIpId types.String `tfsdk:"public_ip_id"`
|
||||
Ip types.String `tfsdk:"ip"`
|
||||
NetworkInterfaceId types.String `tfsdk:"network_interface_id"`
|
||||
|
|
@ -47,7 +48,8 @@ func NewPublicIpAssociateResource() resource.Resource {
|
|||
|
||||
// publicIpAssociateResource is the resource implementation.
|
||||
type publicIpAssociateResource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
|
|
@ -55,14 +57,45 @@ func (r *publicIpAssociateResource) Metadata(_ context.Context, req resource.Met
|
|||
resp.TypeName = req.ProviderTypeName + "_public_ip_associate"
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
func (r *publicIpAssociateResource) 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
|
||||
}
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *publicIpAssociateResource) 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
|
||||
}
|
||||
|
|
@ -88,7 +121,7 @@ func (r *publicIpAssociateResource) Schema(_ context.Context, _ resource.SchemaR
|
|||
Description: fmt.Sprintf("%s\n\n%s", descriptions["main"], descriptions["warning_message"]),
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`public_ip_id`,`network_interface_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`public_ip_id`,`network_interface_id`\".",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
|
|
@ -105,6 +138,15 @@ func (r *publicIpAssociateResource) Schema(_ context.Context, _ resource.SchemaR
|
|||
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(),
|
||||
},
|
||||
},
|
||||
"public_ip_id": schema.StringAttribute{
|
||||
Description: "The public IP ID.",
|
||||
Required: true,
|
||||
|
|
@ -151,12 +193,14 @@ func (r *publicIpAssociateResource) Create(ctx context.Context, req resource.Cre
|
|||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
publicIpId := model.PublicIpId.ValueString()
|
||||
networkInterfaceId := model.NetworkInterfaceId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "public_ip_id", publicIpId)
|
||||
ctx = tflog.SetField(ctx, "network_interface_id", networkInterfaceId)
|
||||
|
||||
|
|
@ -167,7 +211,7 @@ func (r *publicIpAssociateResource) Create(ctx context.Context, req resource.Cre
|
|||
return
|
||||
}
|
||||
// Update existing public IP
|
||||
updatedPublicIp, err := r.client.UpdatePublicIP(ctx, projectId, publicIpId).UpdatePublicIPPayload(*payload).Execute()
|
||||
updatedPublicIp, err := r.client.UpdatePublicIP(ctx, projectId, region, publicIpId).UpdatePublicIPPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error associating public IP to network interface", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -175,7 +219,7 @@ func (r *publicIpAssociateResource) Create(ctx context.Context, req resource.Cre
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapFields(updatedPublicIp, &model)
|
||||
err = mapFields(updatedPublicIp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error associating public IP to network interface", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -197,16 +241,18 @@ func (r *publicIpAssociateResource) Read(ctx context.Context, req resource.ReadR
|
|||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
publicIpId := model.PublicIpId.ValueString()
|
||||
networkInterfaceId := model.NetworkInterfaceId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "public_ip_id", publicIpId)
|
||||
ctx = tflog.SetField(ctx, "network_interface_id", networkInterfaceId)
|
||||
|
||||
publicIpResp, err := r.client.GetPublicIP(ctx, projectId, publicIpId).Execute()
|
||||
publicIpResp, err := r.client.GetPublicIP(ctx, projectId, region, publicIpId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
|
|
@ -220,7 +266,7 @@ func (r *publicIpAssociateResource) Read(ctx context.Context, req resource.ReadR
|
|||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(publicIpResp, &model)
|
||||
err = mapFields(publicIpResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading public IP association", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -250,12 +296,14 @@ func (r *publicIpAssociateResource) Delete(ctx context.Context, req resource.Del
|
|||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
publicIpId := model.PublicIpId.ValueString()
|
||||
networkInterfaceId := model.NetworkInterfaceId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "public_ip_id", publicIpId)
|
||||
ctx = tflog.SetField(ctx, "network_interface_id", networkInterfaceId)
|
||||
|
||||
|
|
@ -263,7 +311,7 @@ func (r *publicIpAssociateResource) Delete(ctx context.Context, req resource.Del
|
|||
NetworkInterface: iaas.NewNullableString(nil),
|
||||
}
|
||||
|
||||
_, err := r.client.UpdatePublicIP(ctx, projectId, publicIpId).UpdatePublicIPPayload(*payload).Execute()
|
||||
_, err := r.client.UpdatePublicIP(ctx, projectId, region, publicIpId).UpdatePublicIPPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting public IP association", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -279,28 +327,25 @@ func (r *publicIpAssociateResource) Delete(ctx context.Context, req resource.Del
|
|||
func (r *publicIpAssociateResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
|
||||
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
|
||||
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics,
|
||||
"Error importing public IP associate",
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[public_ip_id],[network_interface_id] Got: %q", req.ID),
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[public_ip_id],[network_interface_id] Got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
publicIpId := idParts[1]
|
||||
networkInterfaceId := idParts[2]
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "public_ip_id", publicIpId)
|
||||
ctx = tflog.SetField(ctx, "network_interface_id", networkInterfaceId)
|
||||
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
|
||||
"project_id": idParts[0],
|
||||
"region": idParts[1],
|
||||
"public_ip_id": idParts[2],
|
||||
"network_interface_id": idParts[3],
|
||||
})
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("public_ip_id"), publicIpId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_interface_id"), networkInterfaceId)...)
|
||||
tflog.Info(ctx, "public IP state imported")
|
||||
}
|
||||
|
||||
func mapFields(publicIpResp *iaas.PublicIp, model *Model) error {
|
||||
func mapFields(publicIpResp *iaas.PublicIp, model *Model, region string) error {
|
||||
if publicIpResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
|
|
@ -324,8 +369,9 @@ func mapFields(publicIpResp *iaas.PublicIp, model *Model) error {
|
|||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(
|
||||
model.ProjectId.ValueString(), publicIpId, model.NetworkInterfaceId.ValueString(),
|
||||
model.ProjectId.ValueString(), region, publicIpId, model.NetworkInterfaceId.ValueString(),
|
||||
)
|
||||
model.Region = types.StringValue(region)
|
||||
model.PublicIpId = types.StringValue(publicIpId)
|
||||
model.Ip = types.StringPointerValue(publicIpResp.Ip)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,74 +10,82 @@ import (
|
|||
)
|
||||
|
||||
func TestMapFields(t *testing.T) {
|
||||
type args struct {
|
||||
state Model
|
||||
input *iaas.PublicIp
|
||||
region string
|
||||
}
|
||||
tests := []struct {
|
||||
description string
|
||||
state Model
|
||||
input *iaas.PublicIp
|
||||
args args
|
||||
expected Model
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"default_values",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
description: "default_values",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
},
|
||||
input: &iaas.PublicIp{
|
||||
Id: utils.Ptr("pipid"),
|
||||
NetworkInterface: iaas.NewNullableString(utils.Ptr("nicid")),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.PublicIp{
|
||||
Id: utils.Ptr("pipid"),
|
||||
NetworkInterface: iaas.NewNullableString(utils.Ptr("nicid")),
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,pipid,nicid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,pipid,nicid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
Ip: types.StringNull(),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"simple_values",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
description: "simple_values",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
},
|
||||
input: &iaas.PublicIp{
|
||||
Id: utils.Ptr("pipid"),
|
||||
Ip: utils.Ptr("ip"),
|
||||
NetworkInterface: iaas.NewNullableString(utils.Ptr("nicid")),
|
||||
},
|
||||
region: "eu02",
|
||||
},
|
||||
&iaas.PublicIp{
|
||||
Id: utils.Ptr("pipid"),
|
||||
Ip: utils.Ptr("ip"),
|
||||
NetworkInterface: iaas.NewNullableString(utils.Ptr("nicid")),
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,pipid,nicid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu02,pipid,nicid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
PublicIpId: types.StringValue("pipid"),
|
||||
Ip: types.StringValue("ip"),
|
||||
NetworkInterfaceId: types.StringValue("nicid"),
|
||||
Region: types.StringValue("eu02"),
|
||||
},
|
||||
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.PublicIp{},
|
||||
},
|
||||
&iaas.PublicIp{},
|
||||
Model{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
err := mapFields(tt.input, &tt.state)
|
||||
err := mapFields(tt.args.input, &tt.args.state, tt.args.region)
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
|
|
@ -85,7 +93,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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ func NewSecurityGroupDataSource() datasource.DataSource {
|
|||
|
||||
// securityGroupDataSource is the data source implementation.
|
||||
type securityGroupDataSource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
|
|
@ -40,12 +41,13 @@ func (d *securityGroupDataSource) Metadata(_ context.Context, req datasource.Met
|
|||
}
|
||||
|
||||
func (d *securityGroupDataSource) 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
|
||||
}
|
||||
|
|
@ -54,14 +56,14 @@ func (d *securityGroupDataSource) Configure(ctx context.Context, req datasource.
|
|||
}
|
||||
|
||||
// Schema defines the schema for the resource.
|
||||
func (r *securityGroupDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
func (d *securityGroupDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
description := "Security group 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`,`security_group_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`security_group_id`\".",
|
||||
Computed: true,
|
||||
},
|
||||
"project_id": schema.StringAttribute{
|
||||
|
|
@ -72,6 +74,11 @@ func (r *securityGroupDataSource) Schema(_ context.Context, _ datasource.SchemaR
|
|||
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,
|
||||
},
|
||||
"security_group_id": schema.StringAttribute{
|
||||
Description: "The security group ID.",
|
||||
Required: true,
|
||||
|
|
@ -110,14 +117,16 @@ func (d *securityGroupDataSource) Read(ctx context.Context, req datasource.ReadR
|
|||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := d.providerData.GetRegionWithOverride(model.Region)
|
||||
securityGroupId := model.SecurityGroupId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "security_group_id", securityGroupId)
|
||||
|
||||
securityGroupResp, err := d.client.GetSecurityGroup(ctx, projectId, securityGroupId).Execute()
|
||||
securityGroupResp, err := d.client.GetSecurityGroup(ctx, projectId, region, securityGroupId).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
|
|
@ -135,7 +144,7 @@ func (d *securityGroupDataSource) Read(ctx context.Context, req datasource.ReadR
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapFields(ctx, securityGroupResp, &model)
|
||||
err = mapFields(ctx, securityGroupResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading security group", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
|
||||
|
|
@ -33,11 +32,13 @@ var (
|
|||
_ resource.Resource = &securityGroupResource{}
|
||||
_ resource.ResourceWithConfigure = &securityGroupResource{}
|
||||
_ resource.ResourceWithImportState = &securityGroupResource{}
|
||||
_ resource.ResourceWithModifyPlan = &securityGroupResource{}
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
SecurityGroupId types.String `tfsdk:"security_group_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
Description types.String `tfsdk:"description"`
|
||||
|
|
@ -52,7 +53,8 @@ func NewSecurityGroupResource() resource.Resource {
|
|||
|
||||
// securityGroupResource is the resource implementation.
|
||||
type securityGroupResource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
|
|
@ -60,14 +62,45 @@ func (r *securityGroupResource) Metadata(_ context.Context, req resource.Metadat
|
|||
resp.TypeName = req.ProviderTypeName + "_security_group"
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
func (r *securityGroupResource) 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
|
||||
}
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *securityGroupResource) 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
|
||||
}
|
||||
|
|
@ -83,7 +116,7 @@ func (r *securityGroupResource) Schema(_ context.Context, _ resource.SchemaReque
|
|||
Description: description,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`security_group_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`security_group_id`\".",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
|
|
@ -100,6 +133,15 @@ func (r *securityGroupResource) Schema(_ context.Context, _ resource.SchemaReque
|
|||
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(),
|
||||
},
|
||||
},
|
||||
"security_group_id": schema.StringAttribute{
|
||||
Description: "The security group ID.",
|
||||
Computed: true,
|
||||
|
|
@ -165,7 +207,9 @@ func (r *securityGroupResource) Create(ctx context.Context, req resource.CreateR
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Generate API request body from model
|
||||
payload, err := toCreatePayload(ctx, &model)
|
||||
|
|
@ -176,7 +220,7 @@ func (r *securityGroupResource) Create(ctx context.Context, req resource.CreateR
|
|||
|
||||
// Create new security group
|
||||
|
||||
securityGroup, err := r.client.CreateSecurityGroup(ctx, projectId).CreateSecurityGroupPayload(*payload).Execute()
|
||||
securityGroup, err := r.client.CreateSecurityGroup(ctx, projectId, region).CreateSecurityGroupPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating security group", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -189,7 +233,7 @@ func (r *securityGroupResource) Create(ctx context.Context, req resource.CreateR
|
|||
ctx = tflog.SetField(ctx, "security_group_id", securityGroupId)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, securityGroup, &model)
|
||||
err = mapFields(ctx, securityGroup, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating security group", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -212,14 +256,16 @@ func (r *securityGroupResource) Read(ctx context.Context, req resource.ReadReque
|
|||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
securityGroupId := model.SecurityGroupId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "security_id", securityGroupId)
|
||||
|
||||
securityGroupResp, err := r.client.GetSecurityGroup(ctx, projectId, securityGroupId).Execute()
|
||||
securityGroupResp, err := r.client.GetSecurityGroup(ctx, projectId, region, securityGroupId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
|
|
@ -233,7 +279,7 @@ func (r *securityGroupResource) Read(ctx context.Context, req resource.ReadReque
|
|||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, securityGroupResp, &model)
|
||||
err = mapFields(ctx, securityGroupResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading security group", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -257,11 +303,13 @@ func (r *securityGroupResource) Update(ctx context.Context, req resource.UpdateR
|
|||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
securityGroupId := model.SecurityGroupId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "security_group_id", securityGroupId)
|
||||
|
||||
// Retrieve values from state
|
||||
|
|
@ -279,7 +327,7 @@ func (r *securityGroupResource) Update(ctx context.Context, req resource.UpdateR
|
|||
return
|
||||
}
|
||||
// Update existing security group
|
||||
updatedSecurityGroup, err := r.client.UpdateSecurityGroup(ctx, projectId, securityGroupId).UpdateSecurityGroupPayload(*payload).Execute()
|
||||
updatedSecurityGroup, err := r.client.UpdateSecurityGroup(ctx, projectId, region, securityGroupId).UpdateSecurityGroupPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating security group", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -287,7 +335,7 @@ func (r *securityGroupResource) Update(ctx context.Context, req resource.UpdateR
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapFields(ctx, updatedSecurityGroup, &model)
|
||||
err = mapFields(ctx, updatedSecurityGroup, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating security group", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -311,15 +359,17 @@ func (r *securityGroupResource) Delete(ctx context.Context, req resource.DeleteR
|
|||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
securityGroupId := model.SecurityGroupId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "security_group_id", securityGroupId)
|
||||
|
||||
// Delete existing security group
|
||||
err := r.client.DeleteSecurityGroup(ctx, projectId, securityGroupId).Execute()
|
||||
err := r.client.DeleteSecurityGroup(ctx, projectId, region, securityGroupId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting security group", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -335,25 +385,24 @@ func (r *securityGroupResource) Delete(ctx context.Context, req resource.DeleteR
|
|||
func (r *securityGroupResource) 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 security group",
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[security_group_id] Got: %q", req.ID),
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[security_group_id] Got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
securityGroupId := idParts[1]
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "security_group_id", securityGroupId)
|
||||
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
|
||||
"project_id": idParts[0],
|
||||
"region": idParts[1],
|
||||
"security_group_id": idParts[2],
|
||||
})
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("security_group_id"), securityGroupId)...)
|
||||
tflog.Info(ctx, "security group state imported")
|
||||
}
|
||||
|
||||
func mapFields(ctx context.Context, securityGroupResp *iaas.SecurityGroup, model *Model) error {
|
||||
func mapFields(ctx context.Context, securityGroupResp *iaas.SecurityGroup, model *Model, region string) error {
|
||||
if securityGroupResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
|
|
@ -370,7 +419,8 @@ func mapFields(ctx context.Context, securityGroupResp *iaas.SecurityGroup, model
|
|||
return fmt.Errorf("security group id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), securityGroupId)
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, securityGroupId)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
labels, err := iaasUtils.MapLabels(ctx, securityGroupResp.Labels, model.Labels)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -12,51 +12,62 @@ import (
|
|||
)
|
||||
|
||||
func TestMapFields(t *testing.T) {
|
||||
type args struct {
|
||||
state Model
|
||||
input *iaas.SecurityGroup
|
||||
region string
|
||||
}
|
||||
tests := []struct {
|
||||
description string
|
||||
state Model
|
||||
input *iaas.SecurityGroup
|
||||
args args
|
||||
expected Model
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"default_values",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
description: "default_values",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
},
|
||||
input: &iaas.SecurityGroup{
|
||||
Id: utils.Ptr("sgid"),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.SecurityGroup{
|
||||
Id: utils.Ptr("sgid"),
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,sgid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,sgid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
Name: types.StringNull(),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Description: types.StringNull(),
|
||||
Stateful: types.BoolNull(),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"simple_values",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
},
|
||||
// &sourceModel{},
|
||||
&iaas.SecurityGroup{
|
||||
Id: utils.Ptr("sgid"),
|
||||
Name: utils.Ptr("name"),
|
||||
Stateful: utils.Ptr(true),
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
description: "simple_values",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
Description: utils.Ptr("desc"),
|
||||
input: &iaas.SecurityGroup{
|
||||
Id: utils.Ptr("sgid"),
|
||||
Name: utils.Ptr("name"),
|
||||
Stateful: utils.Ptr(true),
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
Description: utils.Ptr("desc"),
|
||||
},
|
||||
region: "eu02",
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,sgid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu02,sgid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
Name: types.StringValue("name"),
|
||||
|
|
@ -65,50 +76,51 @@ func TestMapFields(t *testing.T) {
|
|||
}),
|
||||
Description: types.StringValue("desc"),
|
||||
Stateful: types.BoolValue(true),
|
||||
Region: types.StringValue("eu02"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"empty_labels",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
description: "empty_labels",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
},
|
||||
input: &iaas.SecurityGroup{
|
||||
Id: utils.Ptr("sgid"),
|
||||
Labels: &map[string]interface{}{},
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.SecurityGroup{
|
||||
Id: utils.Ptr("sgid"),
|
||||
Labels: &map[string]interface{}{},
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,sgid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,sgid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
Name: types.StringNull(),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
Description: types.StringNull(),
|
||||
Stateful: types.BoolNull(),
|
||||
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.SecurityGroup{},
|
||||
},
|
||||
&iaas.SecurityGroup{},
|
||||
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")
|
||||
}
|
||||
|
|
@ -116,7 +128,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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ func NewSecurityGroupRuleDataSource() datasource.DataSource {
|
|||
|
||||
// securityGroupRuleDataSource is the data source implementation.
|
||||
type securityGroupRuleDataSource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
|
|
@ -39,12 +40,13 @@ func (d *securityGroupRuleDataSource) Metadata(_ context.Context, req datasource
|
|||
}
|
||||
|
||||
func (d *securityGroupRuleDataSource) 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
|
||||
}
|
||||
|
|
@ -53,7 +55,7 @@ func (d *securityGroupRuleDataSource) Configure(ctx context.Context, req datasou
|
|||
}
|
||||
|
||||
// Schema defines the schema for the resource.
|
||||
func (r *securityGroupRuleDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
func (d *securityGroupRuleDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
directionOptions := []string{"ingress", "egress"}
|
||||
description := "Security group datasource schema. Must have a `region` specified in the provider configuration."
|
||||
|
||||
|
|
@ -62,7 +64,7 @@ func (r *securityGroupRuleDataSource) Schema(_ context.Context, _ datasource.Sch
|
|||
Description: description,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal datasource ID. It is structured as \"`project_id`,`security_group_id`,`security_group_rule_id`\".",
|
||||
Description: "Terraform's internal datasource ID. It is structured as \"`project_id`,`region`,`security_group_id`,`security_group_rule_id`\".",
|
||||
Computed: true,
|
||||
},
|
||||
"project_id": schema.StringAttribute{
|
||||
|
|
@ -89,6 +91,11 @@ func (r *securityGroupRuleDataSource) Schema(_ context.Context, _ datasource.Sch
|
|||
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,
|
||||
},
|
||||
"direction": schema.StringAttribute{
|
||||
Description: "The direction of the traffic which the rule should match. Some of the possible values are: " + utils.FormatPossibleValues(directionOptions...),
|
||||
Computed: true,
|
||||
|
|
@ -164,16 +171,18 @@ func (d *securityGroupRuleDataSource) Read(ctx context.Context, req datasource.R
|
|||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := d.providerData.GetRegionWithOverride(model.Region)
|
||||
securityGroupId := model.SecurityGroupId.ValueString()
|
||||
securityGroupRuleId := model.SecurityGroupRuleId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "security_group_id", securityGroupId)
|
||||
ctx = tflog.SetField(ctx, "security_group_rule_id", securityGroupRuleId)
|
||||
|
||||
securityGroupRuleResp, err := d.client.GetSecurityGroupRule(ctx, projectId, securityGroupId, securityGroupRuleId).Execute()
|
||||
securityGroupRuleResp, err := d.client.GetSecurityGroupRule(ctx, projectId, region, securityGroupId, securityGroupRuleId).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
|
|
@ -191,7 +200,7 @@ func (d *securityGroupRuleDataSource) Read(ctx context.Context, req datasource.R
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapFields(securityGroupRuleResp, &model)
|
||||
err = mapFields(securityGroupRuleResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading security group rule", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
|
|||
|
|
@ -34,11 +34,13 @@ import (
|
|||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ resource.Resource = &securityGroupRuleResource{}
|
||||
_ resource.ResourceWithConfigure = &securityGroupRuleResource{}
|
||||
_ resource.ResourceWithImportState = &securityGroupRuleResource{}
|
||||
icmpProtocols = []string{"icmp", "ipv6-icmp"}
|
||||
protocolsPossibleValues = []string{
|
||||
_ resource.Resource = &securityGroupRuleResource{}
|
||||
_ resource.ResourceWithConfigure = &securityGroupRuleResource{}
|
||||
_ resource.ResourceWithImportState = &securityGroupRuleResource{}
|
||||
_ resource.ResourceWithModifyPlan = &securityGroupRuleResource{}
|
||||
|
||||
icmpProtocols = []string{"icmp", "ipv6-icmp"}
|
||||
protocolsPossibleValues = []string{
|
||||
"ah", "dccp", "egp", "esp", "gre", "icmp", "igmp", "ipip", "ipv6-encap", "ipv6-frag", "ipv6-icmp",
|
||||
"ipv6-nonxt", "ipv6-opts", "ipv6-route", "ospf", "pgm", "rsvp", "sctp", "tcp", "udp", "udplite", "vrrp",
|
||||
}
|
||||
|
|
@ -47,6 +49,7 @@ var (
|
|||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
SecurityGroupId types.String `tfsdk:"security_group_id"`
|
||||
SecurityGroupRuleId types.String `tfsdk:"security_group_rule_id"`
|
||||
Direction types.String `tfsdk:"direction"`
|
||||
|
|
@ -99,7 +102,8 @@ func NewSecurityGroupRuleResource() resource.Resource {
|
|||
|
||||
// securityGroupRuleResource is the resource implementation.
|
||||
type securityGroupRuleResource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
|
|
@ -107,14 +111,45 @@ func (r *securityGroupRuleResource) Metadata(_ context.Context, req resource.Met
|
|||
resp.TypeName = req.ProviderTypeName + "_security_group_rule"
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
func (r *securityGroupRuleResource) 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
|
||||
}
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *securityGroupRuleResource) 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
|
||||
}
|
||||
|
|
@ -122,7 +157,7 @@ func (r *securityGroupRuleResource) Configure(ctx context.Context, req resource.
|
|||
tflog.Info(ctx, "iaas client configured")
|
||||
}
|
||||
|
||||
func (r securityGroupRuleResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
|
||||
func (r *securityGroupRuleResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
|
||||
var model Model
|
||||
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &model)...)
|
||||
|
|
@ -178,7 +213,7 @@ func (r *securityGroupRuleResource) Schema(_ context.Context, _ resource.SchemaR
|
|||
Description: description,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`security_group_id`,`security_group_rule_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`security_group_id`,`security_group_rule_id`\".",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
|
|
@ -196,6 +231,15 @@ func (r *securityGroupRuleResource) Schema(_ context.Context, _ resource.SchemaR
|
|||
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(),
|
||||
},
|
||||
},
|
||||
"security_group_id": schema.StringAttribute{
|
||||
Description: "The security group ID.",
|
||||
Required: true,
|
||||
|
|
@ -392,8 +436,10 @@ func (r *securityGroupRuleResource) Create(ctx context.Context, req resource.Cre
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
securityGroupId := model.SecurityGroupId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "security_group_id", securityGroupId)
|
||||
|
||||
var icmpParameters *icmpParametersModel
|
||||
|
|
@ -434,7 +480,7 @@ func (r *securityGroupRuleResource) Create(ctx context.Context, req resource.Cre
|
|||
}
|
||||
|
||||
// Create new security group rule
|
||||
securityGroupRule, err := r.client.CreateSecurityGroupRule(ctx, projectId, securityGroupId).CreateSecurityGroupRulePayload(*payload).Execute()
|
||||
securityGroupRule, err := r.client.CreateSecurityGroupRule(ctx, projectId, region, securityGroupId).CreateSecurityGroupRulePayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating security group rule", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -445,7 +491,7 @@ func (r *securityGroupRuleResource) Create(ctx context.Context, req resource.Cre
|
|||
ctx = tflog.SetField(ctx, "security_group_rule_id", *securityGroupRule.Id)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(securityGroupRule, &model)
|
||||
err = mapFields(securityGroupRule, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating security group rule", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -468,16 +514,18 @@ func (r *securityGroupRuleResource) Read(ctx context.Context, req resource.ReadR
|
|||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
securityGroupId := model.SecurityGroupId.ValueString()
|
||||
securityGroupRuleId := model.SecurityGroupRuleId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "security_group_id", securityGroupId)
|
||||
ctx = tflog.SetField(ctx, "security_group_rule_id", securityGroupRuleId)
|
||||
|
||||
securityGroupRuleResp, err := r.client.GetSecurityGroupRule(ctx, projectId, securityGroupId, securityGroupRuleId).Execute()
|
||||
securityGroupRuleResp, err := r.client.GetSecurityGroupRule(ctx, projectId, region, securityGroupId, securityGroupRuleId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
|
|
@ -491,7 +539,7 @@ func (r *securityGroupRuleResource) Read(ctx context.Context, req resource.ReadR
|
|||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(securityGroupRuleResp, &model)
|
||||
err = mapFields(securityGroupRuleResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading security group rule", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -522,17 +570,19 @@ func (r *securityGroupRuleResource) Delete(ctx context.Context, req resource.Del
|
|||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
securityGroupId := model.SecurityGroupId.ValueString()
|
||||
securityGroupRuleId := model.SecurityGroupRuleId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "security_group_id", securityGroupId)
|
||||
ctx = tflog.SetField(ctx, "security_group_rule_id", securityGroupRuleId)
|
||||
|
||||
// Delete existing security group rule
|
||||
err := r.client.DeleteSecurityGroupRule(ctx, projectId, securityGroupId, securityGroupRuleId).Execute()
|
||||
err := r.client.DeleteSecurityGroupRule(ctx, projectId, region, securityGroupId, securityGroupRuleId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting security group rule", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -548,28 +598,25 @@ func (r *securityGroupRuleResource) Delete(ctx context.Context, req resource.Del
|
|||
func (r *securityGroupRuleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
|
||||
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
|
||||
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics,
|
||||
"Error importing security group rule",
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[security_group_id],[security_group_rule_id] Got: %q", req.ID),
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[security_group_id],[security_group_rule_id] Got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
securityGroupId := idParts[1]
|
||||
securityGroupRuleId := idParts[2]
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "security_group_id", securityGroupId)
|
||||
ctx = tflog.SetField(ctx, "security_group_rule_id", securityGroupRuleId)
|
||||
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
|
||||
"project_id": idParts[0],
|
||||
"region": idParts[1],
|
||||
"security_group_id": idParts[2],
|
||||
"security_group_rule_id": idParts[3],
|
||||
})
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("security_group_id"), securityGroupId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("security_group_rule_id"), securityGroupRuleId)...)
|
||||
tflog.Info(ctx, "security group rule state imported")
|
||||
}
|
||||
|
||||
func mapFields(securityGroupRuleResp *iaas.SecurityGroupRule, model *Model) error {
|
||||
func mapFields(securityGroupRuleResp *iaas.SecurityGroupRule, model *Model, region string) error {
|
||||
if securityGroupRuleResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
|
|
@ -586,7 +633,8 @@ func mapFields(securityGroupRuleResp *iaas.SecurityGroupRule, model *Model) erro
|
|||
return fmt.Errorf("security group rule id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.SecurityGroupId.ValueString(), securityGroupRuleId)
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, model.SecurityGroupId.ValueString(), securityGroupRuleId)
|
||||
model.Region = types.StringValue(region)
|
||||
model.SecurityGroupRuleId = types.StringValue(securityGroupRuleId)
|
||||
model.Direction = types.StringPointerValue(securityGroupRuleResp.Direction)
|
||||
model.Description = types.StringPointerValue(securityGroupRuleResp.Description)
|
||||
|
|
|
|||
|
|
@ -52,25 +52,32 @@ var fixtureCreateProtocol = iaas.CreateProtocol{
|
|||
}
|
||||
|
||||
func TestMapFields(t *testing.T) {
|
||||
type args struct {
|
||||
state Model
|
||||
input *iaas.SecurityGroupRule
|
||||
region string
|
||||
}
|
||||
tests := []struct {
|
||||
description string
|
||||
state Model
|
||||
input *iaas.SecurityGroupRule
|
||||
args args
|
||||
expected Model
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"default_values",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
SecurityGroupRuleId: types.StringValue("sgrid"),
|
||||
description: "default_values",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
SecurityGroupRuleId: types.StringValue("sgrid"),
|
||||
},
|
||||
input: &iaas.SecurityGroupRule{
|
||||
Id: utils.Ptr("sgrid"),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.SecurityGroupRule{
|
||||
Id: utils.Ptr("sgrid"),
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,sgid,sgrid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,sgid,sgrid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
SecurityGroupRuleId: types.StringValue("sgrid"),
|
||||
|
|
@ -82,29 +89,34 @@ func TestMapFields(t *testing.T) {
|
|||
IcmpParameters: types.ObjectNull(icmpParametersTypes),
|
||||
PortRange: types.ObjectNull(portRangeTypes),
|
||||
Protocol: types.ObjectNull(protocolTypes),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"simple_values",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
SecurityGroupRuleId: types.StringValue("sgrid"),
|
||||
description: "simple_values",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
SecurityGroupRuleId: types.StringValue("sgrid"),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
input: &iaas.SecurityGroupRule{
|
||||
Id: utils.Ptr("sgrid"),
|
||||
Description: utils.Ptr("desc"),
|
||||
Direction: utils.Ptr("ingress"),
|
||||
Ethertype: utils.Ptr("ether"),
|
||||
IpRange: utils.Ptr("iprange"),
|
||||
RemoteSecurityGroupId: utils.Ptr("remote"),
|
||||
IcmpParameters: &fixtureIcmpParameters,
|
||||
PortRange: &fixturePortRange,
|
||||
Protocol: &fixtureProtocol,
|
||||
},
|
||||
region: "eu02",
|
||||
},
|
||||
&iaas.SecurityGroupRule{
|
||||
Id: utils.Ptr("sgrid"),
|
||||
Description: utils.Ptr("desc"),
|
||||
Direction: utils.Ptr("ingress"),
|
||||
Ethertype: utils.Ptr("ether"),
|
||||
IpRange: utils.Ptr("iprange"),
|
||||
RemoteSecurityGroupId: utils.Ptr("remote"),
|
||||
IcmpParameters: &fixtureIcmpParameters,
|
||||
PortRange: &fixturePortRange,
|
||||
Protocol: &fixtureProtocol,
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,sgid,sgrid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu02,sgid,sgrid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
SecurityGroupRuleId: types.StringValue("sgrid"),
|
||||
|
|
@ -116,26 +128,30 @@ func TestMapFields(t *testing.T) {
|
|||
IcmpParameters: fixtureModelIcmpParameters,
|
||||
PortRange: fixtureModelPortRange,
|
||||
Protocol: fixtureModelProtocol,
|
||||
Region: types.StringValue("eu02"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"protocol_only_with_name",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
SecurityGroupRuleId: types.StringValue("sgrid"),
|
||||
Protocol: types.ObjectValueMust(protocolTypes, map[string]attr.Value{
|
||||
"name": types.StringValue("name"),
|
||||
"number": types.Int64Null(),
|
||||
}),
|
||||
description: "protocol_only_with_name",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
SecurityGroupRuleId: types.StringValue("sgrid"),
|
||||
Protocol: types.ObjectValueMust(protocolTypes, map[string]attr.Value{
|
||||
"name": types.StringValue("name"),
|
||||
"number": types.Int64Null(),
|
||||
}),
|
||||
},
|
||||
input: &iaas.SecurityGroupRule{
|
||||
Id: utils.Ptr("sgrid"),
|
||||
Protocol: &fixtureProtocol,
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.SecurityGroupRule{
|
||||
Id: utils.Ptr("sgrid"),
|
||||
Protocol: &fixtureProtocol,
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,sgid,sgrid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,sgid,sgrid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
SecurityGroupRuleId: types.StringValue("sgrid"),
|
||||
|
|
@ -147,26 +163,30 @@ func TestMapFields(t *testing.T) {
|
|||
IcmpParameters: types.ObjectNull(icmpParametersTypes),
|
||||
PortRange: types.ObjectNull(portRangeTypes),
|
||||
Protocol: fixtureModelProtocol,
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"protocol_only_with_number",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
SecurityGroupRuleId: types.StringValue("sgrid"),
|
||||
Protocol: types.ObjectValueMust(protocolTypes, map[string]attr.Value{
|
||||
"name": types.StringNull(),
|
||||
"number": types.Int64Value(1),
|
||||
}),
|
||||
description: "protocol_only_with_number",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
SecurityGroupRuleId: types.StringValue("sgrid"),
|
||||
Protocol: types.ObjectValueMust(protocolTypes, map[string]attr.Value{
|
||||
"name": types.StringNull(),
|
||||
"number": types.Int64Value(1),
|
||||
}),
|
||||
},
|
||||
input: &iaas.SecurityGroupRule{
|
||||
Id: utils.Ptr("sgrid"),
|
||||
Protocol: &fixtureProtocol,
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.SecurityGroupRule{
|
||||
Id: utils.Ptr("sgrid"),
|
||||
Protocol: &fixtureProtocol,
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,sgid,sgrid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,sgid,sgrid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
SecurityGroupRuleId: types.StringValue("sgrid"),
|
||||
|
|
@ -178,30 +198,27 @@ func TestMapFields(t *testing.T) {
|
|||
IcmpParameters: types.ObjectNull(icmpParametersTypes),
|
||||
PortRange: types.ObjectNull(portRangeTypes),
|
||||
Protocol: fixtureModelProtocol,
|
||||
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"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
description: "no_resource_id",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
SecurityGroupId: types.StringValue("sgid"),
|
||||
},
|
||||
input: &iaas.SecurityGroupRule{},
|
||||
},
|
||||
&iaas.SecurityGroupRule{},
|
||||
Model{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
err := mapFields(tt.input, &tt.state)
|
||||
err := mapFields(tt.args.input, &tt.args.state, tt.args.region)
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
|
|
@ -209,7 +226,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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
|
|
@ -27,41 +26,75 @@ import (
|
|||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ resource.Resource = &networkInterfaceAttachResource{}
|
||||
_ resource.ResourceWithConfigure = &networkInterfaceAttachResource{}
|
||||
_ resource.ResourceWithImportState = &networkInterfaceAttachResource{}
|
||||
_ resource.Resource = &serviceAccountAttachResource{}
|
||||
_ resource.ResourceWithConfigure = &serviceAccountAttachResource{}
|
||||
_ resource.ResourceWithImportState = &serviceAccountAttachResource{}
|
||||
_ resource.ResourceWithModifyPlan = &serviceAccountAttachResource{}
|
||||
)
|
||||
|
||||
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"`
|
||||
ServiceAccountEmail types.String `tfsdk:"service_account_email"`
|
||||
}
|
||||
|
||||
// NewServiceAccountAttachResource is a helper function to simplify the provider implementation.
|
||||
func NewServiceAccountAttachResource() resource.Resource {
|
||||
return &networkInterfaceAttachResource{}
|
||||
return &serviceAccountAttachResource{}
|
||||
}
|
||||
|
||||
// networkInterfaceAttachResource is the resource implementation.
|
||||
type networkInterfaceAttachResource struct {
|
||||
client *iaas.APIClient
|
||||
// serviceAccountAttachResource is the resource implementation.
|
||||
type serviceAccountAttachResource struct {
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
func (r *networkInterfaceAttachResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
func (r *serviceAccountAttachResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_server_service_account_attach"
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
func (r *serviceAccountAttachResource) 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
|
||||
}
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *networkInterfaceAttachResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
|
||||
func (r *serviceAccountAttachResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
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
|
||||
}
|
||||
|
|
@ -70,14 +103,14 @@ func (r *networkInterfaceAttachResource) Configure(ctx context.Context, req reso
|
|||
}
|
||||
|
||||
// Schema defines the schema for the resource.
|
||||
func (r *networkInterfaceAttachResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
func (r *serviceAccountAttachResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
description := "Service account attachment resource schema. Attaches a service account to a server. 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`,`service_account_email`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`server_id`,`service_account_email`\".",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
|
|
@ -94,6 +127,15 @@ func (r *networkInterfaceAttachResource) Schema(_ context.Context, _ resource.Sc
|
|||
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.",
|
||||
Required: true,
|
||||
|
|
@ -117,7 +159,7 @@ func (r *networkInterfaceAttachResource) Schema(_ context.Context, _ resource.Sc
|
|||
}
|
||||
|
||||
// Create creates the resource and sets the initial Terraform state.
|
||||
func (r *networkInterfaceAttachResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
func (r *serviceAccountAttachResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
|
|
@ -129,14 +171,16 @@ func (r *networkInterfaceAttachResource) Create(ctx context.Context, req resourc
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
serverId := model.ServerId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
serviceAccountEmail := model.ServiceAccountEmail.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
ctx = tflog.SetField(ctx, "service_account_email", serviceAccountEmail)
|
||||
|
||||
// Create new service account attachment
|
||||
_, err := r.client.AddServiceAccountToServer(ctx, projectId, serverId, serviceAccountEmail).Execute()
|
||||
_, err := r.client.AddServiceAccountToServer(ctx, projectId, region, serverId, serviceAccountEmail).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error attaching service account to server", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -144,7 +188,8 @@ func (r *networkInterfaceAttachResource) Create(ctx context.Context, req resourc
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(projectId, serverId, serviceAccountEmail)
|
||||
model.Id = utils.BuildInternalTerraformId(projectId, region, serverId, serviceAccountEmail)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
// Set state to fully populated data
|
||||
diags = resp.State.Set(ctx, model)
|
||||
|
|
@ -156,7 +201,7 @@ func (r *networkInterfaceAttachResource) Create(ctx context.Context, req resourc
|
|||
}
|
||||
|
||||
// Read refreshes the Terraform state with the latest data.
|
||||
func (r *networkInterfaceAttachResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
func (r *serviceAccountAttachResource) 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)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -167,13 +212,15 @@ func (r *networkInterfaceAttachResource) Read(ctx context.Context, req resource.
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
serverId := model.ServerId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
serviceAccountEmail := model.ServiceAccountEmail.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
ctx = tflog.SetField(ctx, "service_account_email", serviceAccountEmail)
|
||||
|
||||
serviceAccounts, err := r.client.ListServerServiceAccounts(ctx, projectId, serverId).Execute()
|
||||
serviceAccounts, err := r.client.ListServerServiceAccounts(ctx, projectId, region, serverId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
|
|
@ -196,6 +243,10 @@ func (r *networkInterfaceAttachResource) Read(ctx context.Context, req resource.
|
|||
if mail != serviceAccountEmail {
|
||||
continue
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(projectId, region, serverId, serviceAccountEmail)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
// Set refreshed state
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -212,12 +263,12 @@ func (r *networkInterfaceAttachResource) Read(ctx context.Context, req resource.
|
|||
}
|
||||
|
||||
// Update updates the resource and sets the updated Terraform state on success.
|
||||
func (r *networkInterfaceAttachResource) Update(_ context.Context, _ resource.UpdateRequest, _ *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
func (r *serviceAccountAttachResource) Update(_ context.Context, _ resource.UpdateRequest, _ *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Update is not supported, all fields require replace
|
||||
}
|
||||
|
||||
// Delete deletes the resource and removes the Terraform state on success.
|
||||
func (r *networkInterfaceAttachResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
func (r *serviceAccountAttachResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from state
|
||||
var model Model
|
||||
diags := req.State.Get(ctx, &model)
|
||||
|
|
@ -229,14 +280,15 @@ func (r *networkInterfaceAttachResource) Delete(ctx context.Context, req resourc
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
serverId := model.ServerId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
service_accountId := model.ServiceAccountEmail.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
ctx = tflog.SetField(ctx, "service_account_email", service_accountId)
|
||||
|
||||
// Remove service_account from server
|
||||
_, err := r.client.RemoveServiceAccountFromServer(ctx, projectId, serverId, service_accountId).Execute()
|
||||
_, err := r.client.RemoveServiceAccountFromServer(ctx, projectId, region, serverId, service_accountId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error removing service account from server", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -249,26 +301,23 @@ func (r *networkInterfaceAttachResource) Delete(ctx context.Context, req resourc
|
|||
|
||||
// ImportState imports a resource into the Terraform state on success.
|
||||
// The expected format of the resource import identifier is: project_id,server_id
|
||||
func (r *networkInterfaceAttachResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
func (r *serviceAccountAttachResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
|
||||
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
|
||||
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics,
|
||||
"Error importing service_account attachment",
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[server_id],[service_account_email] Got: %q", req.ID),
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[server_id],[service_account_email] Got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
serverId := idParts[1]
|
||||
service_accountId := idParts[2]
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
ctx = tflog.SetField(ctx, "service_account_email", service_accountId)
|
||||
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
|
||||
"project_id": idParts[0],
|
||||
"region": idParts[1],
|
||||
"server_id": idParts[2],
|
||||
"service_account_email": idParts[3],
|
||||
})
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("server_id"), serverId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("service_account_email"), service_accountId)...)
|
||||
tflog.Info(ctx, "Service account attachment state imported")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ variable "default_prefix_length" {}
|
|||
variable "max_prefix_length" {}
|
||||
variable "min_prefix_length" {}
|
||||
|
||||
variable "route_prefix" {}
|
||||
variable "route_next_hop" {}
|
||||
variable "route_destination_type" {}
|
||||
variable "route_destination_value" {}
|
||||
variable "route_next_hop_type" {}
|
||||
variable "route_next_hop_value" {}
|
||||
variable "label" {}
|
||||
|
||||
resource "stackit_network_area" "network_area" {
|
||||
|
|
@ -33,8 +35,14 @@ resource "stackit_network_area" "network_area" {
|
|||
resource "stackit_network_area_route" "network_area_route" {
|
||||
organization_id = stackit_network_area.network_area.organization_id
|
||||
network_area_id = stackit_network_area.network_area.network_area_id
|
||||
prefix = var.route_prefix
|
||||
next_hop = var.route_next_hop
|
||||
destination = {
|
||||
type = var.route_destination_type
|
||||
value = var.route_destination_value
|
||||
}
|
||||
next_hop = {
|
||||
type = var.route_next_hop_type
|
||||
value = var.route_next_hop_value
|
||||
}
|
||||
labels = {
|
||||
"acc-test" : var.label
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,8 @@
|
|||
variable "organization_id" {}
|
||||
|
||||
variable "name" {}
|
||||
variable "transfer_network" {}
|
||||
variable "network_ranges_prefix" {}
|
||||
|
||||
variable "route_prefix" {}
|
||||
variable "route_next_hop" {}
|
||||
|
||||
resource "stackit_network_area" "network_area" {
|
||||
organization_id = var.organization_id
|
||||
name = var.name
|
||||
transfer_network = var.transfer_network
|
||||
network_ranges = [
|
||||
{
|
||||
prefix = var.network_ranges_prefix
|
||||
}
|
||||
]
|
||||
organization_id = var.organization_id
|
||||
name = var.name
|
||||
}
|
||||
|
||||
resource "stackit_network_area_route" "network_area_route" {
|
||||
organization_id = stackit_network_area.network_area.organization_id
|
||||
network_area_id = stackit_network_area.network_area.network_area_id
|
||||
prefix = var.route_prefix
|
||||
next_hop = var.route_next_hop
|
||||
}
|
||||
33
stackit/internal/services/iaas/testdata/resource-network-area-region-max.tf
vendored
Normal file
33
stackit/internal/services/iaas/testdata/resource-network-area-region-max.tf
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
variable "organization_id" {}
|
||||
|
||||
variable "name" {}
|
||||
variable "transfer_network" {}
|
||||
variable "network_ranges_prefix" {}
|
||||
variable "default_prefix_length" {}
|
||||
variable "min_prefix_length" {}
|
||||
variable "max_prefix_length" {}
|
||||
variable "default_nameservers" {}
|
||||
|
||||
resource "stackit_network_area" "network_area" {
|
||||
organization_id = var.organization_id
|
||||
name = var.name
|
||||
}
|
||||
|
||||
resource "stackit_network_area_region" "network_area_region" {
|
||||
organization_id = var.organization_id
|
||||
network_area_id = stackit_network_area.network_area.network_area_id
|
||||
ipv4 = {
|
||||
transfer_network = var.transfer_network
|
||||
network_ranges = [
|
||||
{
|
||||
prefix = var.network_ranges_prefix
|
||||
}
|
||||
]
|
||||
default_prefix_length = var.default_prefix_length
|
||||
min_prefix_length = var.min_prefix_length
|
||||
max_prefix_length = var.max_prefix_length
|
||||
default_nameservers = [
|
||||
var.default_nameservers
|
||||
]
|
||||
}
|
||||
}
|
||||
23
stackit/internal/services/iaas/testdata/resource-network-area-region-min.tf
vendored
Normal file
23
stackit/internal/services/iaas/testdata/resource-network-area-region-min.tf
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
variable "organization_id" {}
|
||||
|
||||
variable "name" {}
|
||||
variable "transfer_network" {}
|
||||
variable "network_ranges_prefix" {}
|
||||
|
||||
resource "stackit_network_area" "network_area" {
|
||||
organization_id = var.organization_id
|
||||
name = var.name
|
||||
}
|
||||
|
||||
resource "stackit_network_area_region" "network_area_region" {
|
||||
organization_id = var.organization_id
|
||||
network_area_id = stackit_network_area.network_area.network_area_id
|
||||
ipv4 = {
|
||||
transfer_network = var.transfer_network
|
||||
network_ranges = [
|
||||
{
|
||||
prefix = var.network_ranges_prefix
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
85
stackit/internal/services/iaas/testdata/resource-network-max.tf
vendored
Normal file
85
stackit/internal/services/iaas/testdata/resource-network-max.tf
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
variable "organization_id" {}
|
||||
variable "name" {}
|
||||
variable "ipv4_gateway" {}
|
||||
variable "ipv4_nameserver_0" {}
|
||||
variable "ipv4_nameserver_1" {}
|
||||
variable "ipv4_prefix" {}
|
||||
variable "ipv4_prefix_length" {}
|
||||
variable "routed" {}
|
||||
variable "label" {}
|
||||
variable "service_account_mail" {}
|
||||
|
||||
# no test candidate, just needed for the testing setup
|
||||
resource "stackit_network_area" "network_area" {
|
||||
organization_id = var.organization_id
|
||||
name = var.name
|
||||
labels = {
|
||||
"preview/routingtables" = "true"
|
||||
}
|
||||
}
|
||||
|
||||
resource "stackit_network_area_region" "network_area_region" {
|
||||
organization_id = var.organization_id
|
||||
network_area_id = stackit_network_area.network_area.network_area_id
|
||||
ipv4 = {
|
||||
network_ranges = [
|
||||
{
|
||||
prefix = "10.0.0.0/16"
|
||||
},
|
||||
{
|
||||
prefix = "10.2.2.0/24"
|
||||
}
|
||||
]
|
||||
transfer_network = "10.1.2.0/24"
|
||||
}
|
||||
}
|
||||
|
||||
# no test candidate, just needed for the testing setup
|
||||
resource "stackit_resourcemanager_project" "project" {
|
||||
parent_container_id = stackit_network_area.network_area.organization_id
|
||||
name = var.name
|
||||
labels = {
|
||||
"networkArea" = stackit_network_area.network_area.network_area_id
|
||||
}
|
||||
owner_email = var.service_account_mail
|
||||
|
||||
depends_on = [stackit_network_area_region.network_area_region]
|
||||
}
|
||||
|
||||
resource "stackit_network" "network_prefix" {
|
||||
project_id = stackit_resourcemanager_project.project.project_id
|
||||
name = var.name
|
||||
# ipv4_gateway = var.ipv4_gateway != "" ? var.ipv4_gateway : null
|
||||
# no_ipv4_gateway = var.ipv4_gateway != "" ? null : true
|
||||
ipv4_nameservers = [var.ipv4_nameserver_0, var.ipv4_nameserver_1]
|
||||
ipv4_prefix = var.ipv4_prefix
|
||||
routed = var.routed
|
||||
labels = {
|
||||
"acc-test" : var.label
|
||||
}
|
||||
|
||||
depends_on = [stackit_network_area_region.network_area_region]
|
||||
}
|
||||
|
||||
resource "stackit_network" "network_prefix_length" {
|
||||
project_id = stackit_resourcemanager_project.project.project_id
|
||||
name = var.name
|
||||
# no_ipv4_gateway = true
|
||||
ipv4_nameservers = [var.ipv4_nameserver_0, var.ipv4_nameserver_1]
|
||||
ipv4_prefix_length = var.ipv4_prefix_length
|
||||
routed = var.routed
|
||||
labels = {
|
||||
"acc-test" : var.label
|
||||
}
|
||||
routing_table_id = stackit_routing_table.routing_table.routing_table_id
|
||||
|
||||
depends_on = [stackit_network.network_prefix, stackit_network_area_region.network_area_region]
|
||||
}
|
||||
|
||||
resource "stackit_routing_table" "routing_table" {
|
||||
organization_id = var.organization_id
|
||||
network_area_id = stackit_network_area.network_area.network_area_id
|
||||
name = var.name
|
||||
|
||||
depends_on = [stackit_network_area_region.network_area_region]
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
variable "project_id" {}
|
||||
variable "name" {}
|
||||
variable "ipv4_gateway" {}
|
||||
variable "ipv4_nameserver_0" {}
|
||||
variable "ipv4_nameserver_1" {}
|
||||
variable "ipv4_prefix" {}
|
||||
variable "ipv4_prefix_length" {}
|
||||
variable "routed" {}
|
||||
variable "label" {}
|
||||
|
||||
resource "stackit_network" "network_prefix" {
|
||||
project_id = var.project_id
|
||||
name = var.name
|
||||
ipv4_gateway = var.ipv4_gateway != "" ? var.ipv4_gateway : null
|
||||
no_ipv4_gateway = var.ipv4_gateway != "" ? null : true
|
||||
ipv4_nameservers = [var.ipv4_nameserver_0, var.ipv4_nameserver_1]
|
||||
ipv4_prefix = var.ipv4_prefix
|
||||
routed = var.routed
|
||||
labels = {
|
||||
"acc-test" : var.label
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
resource "stackit_network" "network_prefix_length" {
|
||||
project_id = var.project_id
|
||||
name = var.name
|
||||
no_ipv4_gateway = true
|
||||
ipv4_nameservers = [var.ipv4_nameserver_0, var.ipv4_nameserver_1]
|
||||
ipv4_prefix_length = var.ipv4_prefix_length
|
||||
routed = var.routed
|
||||
labels = {
|
||||
"acc-test" : var.label
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
variable "project_id" {}
|
||||
variable "name" {}
|
||||
variable "ipv4_gateway" {}
|
||||
variable "ipv4_nameserver_0" {}
|
||||
variable "ipv4_nameserver_1" {}
|
||||
variable "ipv4_prefix" {}
|
||||
variable "ipv4_prefix_length" {}
|
||||
variable "routed" {}
|
||||
variable "label" {}
|
||||
variable "organization_id" {}
|
||||
variable "network_area_id" {}
|
||||
|
||||
# resource "stackit_network" "network_prefix" {
|
||||
# project_id = var.project_id
|
||||
# name = var.name
|
||||
# # ipv4_gateway = var.ipv4_gateway != "" ? var.ipv4_gateway : null
|
||||
# # no_ipv4_gateway = var.ipv4_gateway != "" ? null : true
|
||||
# ipv4_nameservers = [var.ipv4_nameserver_0, var.ipv4_nameserver_1]
|
||||
# ipv4_prefix = var.ipv4_prefix
|
||||
# routed = var.routed
|
||||
# labels = {
|
||||
# "acc-test" : var.label
|
||||
# }
|
||||
# }
|
||||
|
||||
resource "stackit_network" "network_prefix_length" {
|
||||
project_id = var.project_id
|
||||
name = var.name
|
||||
# no_ipv4_gateway = true
|
||||
ipv4_nameservers = [var.ipv4_nameserver_0, var.ipv4_nameserver_1]
|
||||
ipv4_prefix_length = var.ipv4_prefix_length
|
||||
routed = var.routed
|
||||
labels = {
|
||||
"acc-test" : var.label
|
||||
}
|
||||
routing_table_id = stackit_routing_table.routing_table.routing_table_id
|
||||
}
|
||||
|
||||
resource "stackit_routing_table" "routing_table" {
|
||||
organization_id = var.organization_id
|
||||
network_area_id = var.network_area_id
|
||||
name = var.name
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
variable "project_id" {}
|
||||
variable "name" {}
|
||||
|
||||
resource "stackit_network" "network" {
|
||||
project_id = var.project_id
|
||||
name = var.name
|
||||
}
|
||||
|
|
@ -1,8 +1,18 @@
|
|||
variable "project_id" {}
|
||||
variable "name" {}
|
||||
variable "network_name" {}
|
||||
variable "machine_type" {}
|
||||
variable "image_id" {}
|
||||
|
||||
resource "stackit_network" "network" {
|
||||
project_id = var.project_id
|
||||
name = var.network_name
|
||||
}
|
||||
|
||||
resource "stackit_network_interface" "nic" {
|
||||
project_id = var.project_id
|
||||
network_id = stackit_network.network.network_id
|
||||
}
|
||||
|
||||
resource "stackit_server" "server" {
|
||||
project_id = var.project_id
|
||||
|
|
@ -14,4 +24,7 @@ resource "stackit_server" "server" {
|
|||
source_id = var.image_id
|
||||
delete_on_termination = true
|
||||
}
|
||||
network_interfaces = [
|
||||
stackit_network_interface.nic.network_interface_id
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,9 @@ resource "stackit_volume" "volume_source" {
|
|||
availability_zone = var.availability_zone
|
||||
name = var.name
|
||||
description = var.description
|
||||
performance_class = var.performance_class
|
||||
size = var.size
|
||||
# TODO: keep commented until IaaS API bug is resolved
|
||||
#performance_class = var.performance_class
|
||||
size = var.size
|
||||
source = {
|
||||
id = stackit_volume.volume_size.volume_id
|
||||
type = "volume"
|
||||
|
|
|
|||
|
|
@ -21,9 +21,8 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags
|
|||
}
|
||||
if providerData.IaaSCustomEndpoint != "" {
|
||||
apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.IaaSCustomEndpoint))
|
||||
} else {
|
||||
apiClientConfigOptions = append(apiClientConfigOptions, config.WithRegion(providerData.GetRegion()))
|
||||
}
|
||||
|
||||
apiClient, err := iaas.NewAPIClient(apiClientConfigOptions...)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, diags, "Error configuring API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err))
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ func TestConfigureClient(t *testing.T) {
|
|||
},
|
||||
expected: func() *iaas.APIClient {
|
||||
apiClient, err := iaas.NewAPIClient(
|
||||
config.WithRegion("eu01"),
|
||||
utils.UserAgentConfigOption(testVersion),
|
||||
)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ func NewVolumeDataSource() datasource.DataSource {
|
|||
|
||||
// volumeDataSource is the data source implementation.
|
||||
type volumeDataSource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
|
|
@ -40,12 +41,13 @@ func (d *volumeDataSource) Metadata(_ context.Context, req datasource.MetadataRe
|
|||
}
|
||||
|
||||
func (d *volumeDataSource) 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
|
||||
}
|
||||
|
|
@ -54,14 +56,14 @@ func (d *volumeDataSource) Configure(ctx context.Context, req datasource.Configu
|
|||
}
|
||||
|
||||
// Schema defines the schema for the resource.
|
||||
func (r *volumeDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
func (d *volumeDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
description := "Volume resource 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`,`volume_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`volume_id`\".",
|
||||
Computed: true,
|
||||
},
|
||||
"project_id": schema.StringAttribute{
|
||||
|
|
@ -72,6 +74,11 @@ func (r *volumeDataSource) 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,
|
||||
},
|
||||
"volume_id": schema.StringAttribute{
|
||||
Description: "The volume ID.",
|
||||
Required: true,
|
||||
|
|
@ -140,14 +147,16 @@ func (d *volumeDataSource) Read(ctx context.Context, req datasource.ReadRequest,
|
|||
return
|
||||
}
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := d.providerData.GetRegionWithOverride(model.Region)
|
||||
volumeId := model.VolumeId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "volume_id", volumeId)
|
||||
|
||||
volumeResp, err := d.client.GetVolume(ctx, projectId, volumeId).Execute()
|
||||
volumeResp, err := d.client.GetVolume(ctx, projectId, region, volumeId).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
|
|
@ -165,7 +174,7 @@ func (d *volumeDataSource) Read(ctx context.Context, req datasource.ReadRequest,
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
err = mapFields(ctx, volumeResp, &model)
|
||||
err = mapFields(ctx, volumeResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading volume", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ var (
|
|||
_ resource.Resource = &volumeResource{}
|
||||
_ resource.ResourceWithConfigure = &volumeResource{}
|
||||
_ resource.ResourceWithImportState = &volumeResource{}
|
||||
_ resource.ResourceWithModifyPlan = &volumeResource{}
|
||||
|
||||
SupportedSourceTypes = []string{"volume", "image", "snapshot", "backup"}
|
||||
)
|
||||
|
|
@ -44,6 +45,7 @@ var (
|
|||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
VolumeId types.String `tfsdk:"volume_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
AvailabilityZone types.String `tfsdk:"availability_zone"`
|
||||
|
|
@ -74,7 +76,8 @@ func NewVolumeResource() resource.Resource {
|
|||
|
||||
// volumeResource is the resource implementation.
|
||||
type volumeResource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
|
|
@ -82,6 +85,36 @@ func (r *volumeResource) Metadata(_ context.Context, req resource.MetadataReques
|
|||
resp.TypeName = req.ProviderTypeName + "_volume"
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
func (r *volumeResource) 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
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigValidators validates the resource configuration
|
||||
func (r *volumeResource) ConfigValidators(_ context.Context) []resource.ConfigValidator {
|
||||
return []resource.ConfigValidator{
|
||||
|
|
@ -94,12 +127,13 @@ func (r *volumeResource) ConfigValidators(_ context.Context) []resource.ConfigVa
|
|||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *volumeResource) 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
|
||||
}
|
||||
|
|
@ -115,7 +149,7 @@ func (r *volumeResource) Schema(_ context.Context, _ resource.SchemaRequest, res
|
|||
Description: description,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`volume_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`volume_id`\".",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
|
|
@ -132,6 +166,15 @@ func (r *volumeResource) 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(),
|
||||
},
|
||||
},
|
||||
"volume_id": schema.StringAttribute{
|
||||
Description: "The volume ID.",
|
||||
Computed: true,
|
||||
|
|
@ -290,7 +333,9 @@ func (r *volumeResource) Create(ctx context.Context, req resource.CreateRequest,
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
var source = &sourceModel{}
|
||||
if !(model.Source.IsNull() || model.Source.IsUnknown()) {
|
||||
|
|
@ -310,7 +355,7 @@ func (r *volumeResource) Create(ctx context.Context, req resource.CreateRequest,
|
|||
|
||||
// Create new volume
|
||||
|
||||
volume, err := r.client.CreateVolume(ctx, projectId).CreateVolumePayload(*payload).Execute()
|
||||
volume, err := r.client.CreateVolume(ctx, projectId, region).CreateVolumePayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating volume", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -319,7 +364,7 @@ func (r *volumeResource) Create(ctx context.Context, req resource.CreateRequest,
|
|||
ctx = core.LogResponse(ctx)
|
||||
|
||||
volumeId := *volume.Id
|
||||
volume, err = wait.CreateVolumeWaitHandler(ctx, r.client, projectId, volumeId).WaitWithContext(ctx)
|
||||
volume, err = wait.CreateVolumeWaitHandler(ctx, r.client, projectId, region, volumeId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating volume", fmt.Sprintf("volume creation waiting: %v", err))
|
||||
return
|
||||
|
|
@ -328,7 +373,7 @@ func (r *volumeResource) Create(ctx context.Context, req resource.CreateRequest,
|
|||
ctx = tflog.SetField(ctx, "volume_id", volumeId)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, volume, &model)
|
||||
err = mapFields(ctx, volume, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating volume", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -350,15 +395,18 @@ func (r *volumeResource) Read(ctx context.Context, req resource.ReadRequest, res
|
|||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
volumeId := model.VolumeId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "volume_id", volumeId)
|
||||
|
||||
volumeResp, err := r.client.GetVolume(ctx, projectId, volumeId).Execute()
|
||||
volumeResp, err := r.client.GetVolume(ctx, projectId, region, volumeId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
|
|
@ -372,7 +420,7 @@ func (r *volumeResource) Read(ctx context.Context, req resource.ReadRequest, res
|
|||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, volumeResp, &model)
|
||||
err = mapFields(ctx, volumeResp, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading volume", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -399,8 +447,10 @@ func (r *volumeResource) Update(ctx context.Context, req resource.UpdateRequest,
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
volumeId := model.VolumeId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "volume_id", volumeId)
|
||||
|
||||
// Retrieve values from state
|
||||
|
|
@ -418,7 +468,7 @@ func (r *volumeResource) Update(ctx context.Context, req resource.UpdateRequest,
|
|||
return
|
||||
}
|
||||
// Update existing volume
|
||||
updatedVolume, err := r.client.UpdateVolume(ctx, projectId, volumeId).UpdateVolumePayload(*payload).Execute()
|
||||
updatedVolume, err := r.client.UpdateVolume(ctx, projectId, region, volumeId).UpdateVolumePayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating volume", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -436,7 +486,7 @@ func (r *volumeResource) Update(ctx context.Context, req resource.UpdateRequest,
|
|||
payload := iaas.ResizeVolumePayload{
|
||||
Size: modelSize,
|
||||
}
|
||||
err := r.client.ResizeVolume(ctx, projectId, volumeId).ResizeVolumePayload(payload).Execute()
|
||||
err := r.client.ResizeVolume(ctx, projectId, region, volumeId).ResizeVolumePayload(payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating volume", fmt.Sprintf("Resizing the volume, calling API: %v", err))
|
||||
}
|
||||
|
|
@ -444,7 +494,7 @@ func (r *volumeResource) Update(ctx context.Context, req resource.UpdateRequest,
|
|||
updatedVolume.Size = modelSize
|
||||
}
|
||||
}
|
||||
err = mapFields(ctx, updatedVolume, &model)
|
||||
err = mapFields(ctx, updatedVolume, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating volume", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -468,15 +518,17 @@ func (r *volumeResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
|||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
volumeId := model.VolumeId.ValueString()
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "volume_id", volumeId)
|
||||
|
||||
// Delete existing volume
|
||||
err := r.client.DeleteVolume(ctx, projectId, volumeId).Execute()
|
||||
err := r.client.DeleteVolume(ctx, projectId, region, volumeId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting volume", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -484,7 +536,7 @@ func (r *volumeResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
_, err = wait.DeleteVolumeWaitHandler(ctx, r.client, projectId, volumeId).WaitWithContext(ctx)
|
||||
_, err = wait.DeleteVolumeWaitHandler(ctx, r.client, projectId, region, volumeId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting volume", fmt.Sprintf("volume deletion waiting: %v", err))
|
||||
return
|
||||
|
|
@ -498,25 +550,24 @@ func (r *volumeResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
|||
func (r *volumeResource) 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 volume",
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[volume_id] Got: %q", req.ID),
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[volume_id] Got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
volumeId := idParts[1]
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "volume_id", volumeId)
|
||||
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
|
||||
"project_id": idParts[0],
|
||||
"region": idParts[1],
|
||||
"volume_id": idParts[2],
|
||||
})
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("volume_id"), volumeId)...)
|
||||
tflog.Info(ctx, "volume state imported")
|
||||
}
|
||||
|
||||
func mapFields(ctx context.Context, volumeResp *iaas.Volume, model *Model) error {
|
||||
func mapFields(ctx context.Context, volumeResp *iaas.Volume, model *Model, region string) error {
|
||||
if volumeResp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
|
|
@ -533,7 +584,8 @@ func mapFields(ctx context.Context, volumeResp *iaas.Volume, model *Model) error
|
|||
return fmt.Errorf("Volume id not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), volumeId)
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, volumeId)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
labels, err := iaasUtils.MapLabels(ctx, volumeResp.Labels, model.Labels)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -12,24 +12,31 @@ import (
|
|||
)
|
||||
|
||||
func TestMapFields(t *testing.T) {
|
||||
type args struct {
|
||||
state Model
|
||||
input *iaas.Volume
|
||||
region string
|
||||
}
|
||||
tests := []struct {
|
||||
description string
|
||||
state Model
|
||||
input *iaas.Volume
|
||||
args args
|
||||
expected Model
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"default_values",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
VolumeId: types.StringValue("nid"),
|
||||
description: "default_values",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
VolumeId: types.StringValue("nid"),
|
||||
},
|
||||
input: &iaas.Volume{
|
||||
Id: utils.Ptr("nid"),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.Volume{
|
||||
Id: utils.Ptr("nid"),
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
VolumeId: types.StringValue("nid"),
|
||||
Name: types.StringNull(),
|
||||
|
|
@ -40,30 +47,35 @@ func TestMapFields(t *testing.T) {
|
|||
ServerId: types.StringNull(),
|
||||
Size: types.Int64Null(),
|
||||
Source: types.ObjectNull(sourceTypes),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"simple_values",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
VolumeId: types.StringValue("nid"),
|
||||
},
|
||||
&iaas.Volume{
|
||||
Id: utils.Ptr("nid"),
|
||||
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"),
|
||||
VolumeId: types.StringValue("nid"),
|
||||
Region: types.StringValue("eu01"),
|
||||
},
|
||||
Description: utils.Ptr("desc"),
|
||||
PerformanceClass: utils.Ptr("class"),
|
||||
ServerId: utils.Ptr("sid"),
|
||||
Size: utils.Ptr(int64(1)),
|
||||
Source: &iaas.VolumeSource{},
|
||||
input: &iaas.Volume{
|
||||
Id: utils.Ptr("nid"),
|
||||
Name: utils.Ptr("name"),
|
||||
AvailabilityZone: utils.Ptr("zone"),
|
||||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
Description: utils.Ptr("desc"),
|
||||
PerformanceClass: utils.Ptr("class"),
|
||||
ServerId: utils.Ptr("sid"),
|
||||
Size: utils.Ptr(int64(1)),
|
||||
Source: &iaas.VolumeSource{},
|
||||
},
|
||||
region: "eu02",
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu02,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
VolumeId: types.StringValue("nid"),
|
||||
Name: types.StringValue("name"),
|
||||
|
|
@ -79,21 +91,25 @@ func TestMapFields(t *testing.T) {
|
|||
"type": types.StringNull(),
|
||||
"id": types.StringNull(),
|
||||
}),
|
||||
Region: types.StringValue("eu02"),
|
||||
},
|
||||
true,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
"empty_labels",
|
||||
Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
VolumeId: types.StringValue("nid"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
description: "empty_labels",
|
||||
args: args{
|
||||
state: Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
VolumeId: types.StringValue("nid"),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
},
|
||||
input: &iaas.Volume{
|
||||
Id: utils.Ptr("nid"),
|
||||
},
|
||||
region: "eu01",
|
||||
},
|
||||
&iaas.Volume{
|
||||
Id: utils.Ptr("nid"),
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,nid"),
|
||||
expected: Model{
|
||||
Id: types.StringValue("pid,eu01,nid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
VolumeId: types.StringValue("nid"),
|
||||
Name: types.StringNull(),
|
||||
|
|
@ -104,29 +120,28 @@ func TestMapFields(t *testing.T) {
|
|||
ServerId: types.StringNull(),
|
||||
Size: types.Int64Null(),
|
||||
Source: types.ObjectNull(sourceTypes),
|
||||
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.Volume{},
|
||||
},
|
||||
&iaas.Volume{},
|
||||
Model{},
|
||||
false,
|
||||
expected: Model{},
|
||||
isValid: 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")
|
||||
}
|
||||
|
|
@ -134,7 +149,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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
|
|
@ -32,11 +31,13 @@ var (
|
|||
_ resource.Resource = &volumeAttachResource{}
|
||||
_ resource.ResourceWithConfigure = &volumeAttachResource{}
|
||||
_ resource.ResourceWithImportState = &volumeAttachResource{}
|
||||
_ resource.ResourceWithModifyPlan = &volumeAttachResource{}
|
||||
)
|
||||
|
||||
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"`
|
||||
VolumeId types.String `tfsdk:"volume_id"`
|
||||
}
|
||||
|
|
@ -48,7 +49,8 @@ func NewVolumeAttachResource() resource.Resource {
|
|||
|
||||
// volumeAttachResource is the resource implementation.
|
||||
type volumeAttachResource struct {
|
||||
client *iaas.APIClient
|
||||
client *iaas.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
|
|
@ -56,14 +58,45 @@ func (r *volumeAttachResource) Metadata(_ context.Context, req resource.Metadata
|
|||
resp.TypeName = req.ProviderTypeName + "_server_volume_attach"
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
func (r *volumeAttachResource) 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
|
||||
}
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *volumeAttachResource) 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
|
||||
}
|
||||
|
|
@ -79,7 +112,7 @@ func (r *volumeAttachResource) Schema(_ context.Context, _ resource.SchemaReques
|
|||
Description: description,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`server_id`,`volume_id`\".",
|
||||
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`server_id`,`volume_id`\".",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
|
|
@ -96,6 +129,15 @@ func (r *volumeAttachResource) Schema(_ context.Context, _ resource.SchemaReques
|
|||
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.",
|
||||
Required: true,
|
||||
|
|
@ -135,10 +177,12 @@ func (r *volumeAttachResource) Create(ctx context.Context, req resource.CreateRe
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
serverId := model.ServerId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
volumeId := model.VolumeId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
ctx = tflog.SetField(ctx, "volume_id", volumeId)
|
||||
|
||||
// Create new Volume attachment
|
||||
|
|
@ -146,7 +190,7 @@ func (r *volumeAttachResource) Create(ctx context.Context, req resource.CreateRe
|
|||
payload := iaas.AddVolumeToServerPayload{
|
||||
DeleteOnTermination: sdkUtils.Ptr(false),
|
||||
}
|
||||
_, err := r.client.AddVolumeToServer(ctx, projectId, serverId, volumeId).AddVolumeToServerPayload(payload).Execute()
|
||||
_, err := r.client.AddVolumeToServer(ctx, projectId, region, serverId, volumeId).AddVolumeToServerPayload(payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error attaching volume to server", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -154,13 +198,13 @@ func (r *volumeAttachResource) Create(ctx context.Context, req resource.CreateRe
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
_, err = wait.AddVolumeToServerWaitHandler(ctx, r.client, projectId, serverId, volumeId).WaitWithContext(ctx)
|
||||
_, err = wait.AddVolumeToServerWaitHandler(ctx, r.client, projectId, region, serverId, volumeId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error attaching volume to server", fmt.Sprintf("volume attachment waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(projectId, serverId, volumeId)
|
||||
model.Id = utils.BuildInternalTerraformId(projectId, region, serverId, volumeId)
|
||||
|
||||
// Set state to fully populated data
|
||||
diags = resp.State.Set(ctx, model)
|
||||
|
|
@ -183,13 +227,15 @@ func (r *volumeAttachResource) Read(ctx context.Context, req resource.ReadReques
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
serverId := model.ServerId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
volumeId := model.VolumeId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
ctx = tflog.SetField(ctx, "volume_id", volumeId)
|
||||
|
||||
_, err := r.client.GetAttachedVolume(ctx, projectId, serverId, volumeId).Execute()
|
||||
_, err := r.client.GetAttachedVolume(ctx, projectId, region, serverId, volumeId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
|
|
@ -229,14 +275,16 @@ func (r *volumeAttachResource) Delete(ctx context.Context, req resource.DeleteRe
|
|||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
serverId := model.ServerId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
volumeId := model.VolumeId.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
ctx = tflog.SetField(ctx, "volume_id", volumeId)
|
||||
|
||||
// Remove volume from server
|
||||
err := r.client.RemoveVolumeFromServer(ctx, projectId, serverId, volumeId).Execute()
|
||||
err := r.client.RemoveVolumeFromServer(ctx, projectId, region, serverId, volumeId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error removing volume from server", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -244,7 +292,7 @@ func (r *volumeAttachResource) Delete(ctx context.Context, req resource.DeleteRe
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
_, err = wait.RemoveVolumeFromServerWaitHandler(ctx, r.client, projectId, serverId, volumeId).WaitWithContext(ctx)
|
||||
_, err = wait.RemoveVolumeFromServerWaitHandler(ctx, r.client, projectId, region, serverId, volumeId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error removing volume from server", fmt.Sprintf("volume removal waiting: %v", err))
|
||||
return
|
||||
|
|
@ -258,23 +306,20 @@ func (r *volumeAttachResource) Delete(ctx context.Context, req resource.DeleteRe
|
|||
func (r *volumeAttachResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
|
||||
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
|
||||
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics,
|
||||
"Error importing volume attachment",
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[server_id],[volume_id] Got: %q", req.ID),
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[server_id],[volume_id] Got: %q", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
serverId := idParts[1]
|
||||
volumeId := idParts[2]
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
ctx = tflog.SetField(ctx, "volume_id", volumeId)
|
||||
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
|
||||
"project_id": idParts[0],
|
||||
"region": idParts[1],
|
||||
"server_id": idParts[2],
|
||||
"volume_id": idParts[3],
|
||||
})
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("server_id"), serverId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("volume_id"), volumeId)...)
|
||||
tflog.Info(ctx, "Volume attachment state imported")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,6 +176,9 @@ func (r *routeResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
|
|||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
Validators: []validator.String{
|
||||
validate.CIDR(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -213,6 +216,9 @@ func (r *routeResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
|
|||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
Validators: []validator.String{
|
||||
validate.IP(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ func TestAccLogMeMaxResource(t *testing.T) {
|
|||
resource.TestCheckResourceAttr("stackit_logme_instance.instance", "parameters.syslog.0", testutil.ConvertConfigVariable(testConfigVarsMax["params_syslog1"])),
|
||||
resource.TestCheckResourceAttr("stackit_logme_instance.instance", "parameters.syslog.1", testutil.ConvertConfigVariable(testConfigVarsMax["params_syslog2"])),
|
||||
|
||||
// // Credential data
|
||||
// Credential data
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_logme_credential.credential", "project_id",
|
||||
"stackit_logme_instance.instance", "project_id",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
|
||||
observabilityUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/observability/utils"
|
||||
|
|
@ -528,16 +529,25 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
|
|||
Description: "Specifies for how many days the raw metrics are kept. Default is set to `90`.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.Int64{
|
||||
int64planmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"metrics_retention_days_5m_downsampling": schema.Int64Attribute{
|
||||
Description: "Specifies for how many days the 5m downsampled metrics are kept. must be less than the value of the general retention. Default is set to `90`.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.Int64{
|
||||
int64planmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"metrics_retention_days_1h_downsampling": schema.Int64Attribute{
|
||||
Description: "Specifies for how many days the 1h downsampled metrics are kept. must be less than the value of the 5m downsampling retention. Default is set to `90`.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.Int64{
|
||||
int64planmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"metrics_url": schema.StringAttribute{
|
||||
Description: "Specifies metrics URL.",
|
||||
|
|
@ -659,6 +669,8 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
|
|||
"send_resolved": schema.BoolAttribute{
|
||||
Description: "Whether to notify about resolved alerts.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: booldefault.StaticBool(true),
|
||||
},
|
||||
"smart_host": schema.StringAttribute{
|
||||
Description: "The SMTP host through which emails are sent.",
|
||||
|
|
@ -698,6 +710,8 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
|
|||
"send_resolved": schema.BoolAttribute{
|
||||
Description: "Whether to notify about resolved alerts.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: booldefault.StaticBool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -733,6 +747,8 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
|
|||
"send_resolved": schema.BoolAttribute{
|
||||
Description: "Whether to notify about resolved alerts.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: booldefault.StaticBool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -789,10 +805,18 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
|
|||
Description: "The API key for OpsGenie.",
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"opsgenie_api_url": schema.StringAttribute{
|
||||
Description: "The host to send OpsGenie API requests to. Must be a valid URL",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"resolve_timeout": schema.StringAttribute{
|
||||
Description: "The default value used by alertmanager if the alert does not include EndsAt. After this time passes, it can declare the alert as resolved if it has not been updated. This has no impact on alerts from Prometheus, as they always include EndsAt.",
|
||||
|
|
@ -805,24 +829,43 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
|
|||
"smtp_auth_identity": schema.StringAttribute{
|
||||
Description: "SMTP authentication information. Must be a valid email address",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"smtp_auth_password": schema.StringAttribute{
|
||||
Description: "SMTP Auth using LOGIN and PLAIN.",
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"smtp_auth_username": schema.StringAttribute{
|
||||
Description: "SMTP Auth using CRAM-MD5, LOGIN and PLAIN. If empty, Alertmanager doesn't authenticate to the SMTP server.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"smtp_from": schema.StringAttribute{
|
||||
Description: "The default SMTP From header field. Must be a valid email address",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"smtp_smart_host": schema.StringAttribute{
|
||||
Description: "The default SMTP smarthost used for sending emails, including port number in format `host:port` (eg. `smtp.example.com:587`). Port number usually is 25, or 587 for SMTP over TLS (sometimes referred to as STARTTLS).",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -1798,6 +1841,8 @@ func mapGlobalConfigToAttributes(respGlobalConfigs *observability.Global, global
|
|||
smtpAuthIdentity := respGlobalConfigs.SmtpAuthIdentity
|
||||
smtpAuthPassword := respGlobalConfigs.SmtpAuthPassword
|
||||
smtpAuthUsername := respGlobalConfigs.SmtpAuthUsername
|
||||
opsgenieApiKey := respGlobalConfigs.OpsgenieApiKey
|
||||
opsgenieApiUrl := respGlobalConfigs.OpsgenieApiUrl
|
||||
if globalConfigsTF != nil {
|
||||
if respGlobalConfigs.SmtpSmarthost == nil &&
|
||||
!globalConfigsTF.SmtpSmartHost.IsNull() && !globalConfigsTF.SmtpSmartHost.IsUnknown() {
|
||||
|
|
@ -1815,11 +1860,17 @@ func mapGlobalConfigToAttributes(respGlobalConfigs *observability.Global, global
|
|||
!globalConfigsTF.SmtpAuthUsername.IsNull() && !globalConfigsTF.SmtpAuthUsername.IsUnknown() {
|
||||
smtpAuthUsername = sdkUtils.Ptr(globalConfigsTF.SmtpAuthUsername.ValueString())
|
||||
}
|
||||
if respGlobalConfigs.OpsgenieApiKey == nil {
|
||||
opsgenieApiKey = sdkUtils.Ptr(globalConfigsTF.OpsgenieApiKey.ValueString())
|
||||
}
|
||||
if respGlobalConfigs.OpsgenieApiUrl == nil {
|
||||
opsgenieApiUrl = sdkUtils.Ptr(globalConfigsTF.OpsgenieApiUrl.ValueString())
|
||||
}
|
||||
}
|
||||
|
||||
globalConfigObject, diags := types.ObjectValue(globalConfigurationTypes, map[string]attr.Value{
|
||||
"opsgenie_api_key": types.StringPointerValue(respGlobalConfigs.OpsgenieApiKey),
|
||||
"opsgenie_api_url": types.StringPointerValue(respGlobalConfigs.OpsgenieApiUrl),
|
||||
"opsgenie_api_key": types.StringPointerValue(opsgenieApiKey),
|
||||
"opsgenie_api_url": types.StringPointerValue(opsgenieApiUrl),
|
||||
"resolve_timeout": types.StringPointerValue(respGlobalConfigs.ResolveTimeout),
|
||||
"smtp_from": types.StringPointerValue(respGlobalConfigs.SmtpFrom),
|
||||
"smtp_auth_identity": types.StringPointerValue(smtpAuthIdentity),
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ func TestAccServerUpdateScheduleMinResource(t *testing.T) {
|
|||
resource.TestCheckResourceAttrSet("data.stackit_server_update_schedules.schedules_data_test", "id"),
|
||||
),
|
||||
},
|
||||
// // Import
|
||||
// Import
|
||||
{
|
||||
ConfigVariables: testConfigVarsMin,
|
||||
ResourceName: "stackit_server_update_schedule.test_schedule",
|
||||
|
|
@ -139,7 +139,7 @@ func TestAccServerUpdateScheduleMinResource(t *testing.T) {
|
|||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
// // Update
|
||||
// Update
|
||||
{
|
||||
ConfigVariables: configVarsMinUpdated(),
|
||||
Config: testutil.ServerUpdateProviderConfig() + "\n" + resourceMinConfig,
|
||||
|
|
@ -209,7 +209,7 @@ func TestAccServerUpdateScheduleMaxResource(t *testing.T) {
|
|||
resource.TestCheckResourceAttrSet("data.stackit_server_update_schedules.schedules_data_test", "id"),
|
||||
),
|
||||
},
|
||||
// // Import
|
||||
// Import
|
||||
{
|
||||
ConfigVariables: testConfigVarsMax,
|
||||
ResourceName: "stackit_server_update_schedule.test_schedule",
|
||||
|
|
@ -227,7 +227,7 @@ func TestAccServerUpdateScheduleMaxResource(t *testing.T) {
|
|||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
// // Update
|
||||
// Update
|
||||
{
|
||||
ConfigVariables: configVarsMaxUpdated(),
|
||||
Config: testutil.ServerUpdateProviderConfig() + "\n" + resourceMaxConfig,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import (
|
|||
machineType "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/machinetype"
|
||||
iaasNetwork "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network"
|
||||
iaasNetworkArea "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/networkarea"
|
||||
iaasNetworkAreaRegion "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/networkarearegion"
|
||||
iaasNetworkAreaRoute "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/networkarearoute"
|
||||
iaasNetworkInterface "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/networkinterface"
|
||||
iaasNetworkInterfaceAttach "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/networkinterfaceattach"
|
||||
|
|
@ -502,6 +503,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource
|
|||
iaasImageV2.NewImageV2DataSource,
|
||||
iaasNetwork.NewNetworkDataSource,
|
||||
iaasNetworkArea.NewNetworkAreaDataSource,
|
||||
iaasNetworkAreaRegion.NewNetworkAreaRegionDataSource,
|
||||
iaasNetworkAreaRoute.NewNetworkAreaRouteDataSource,
|
||||
iaasNetworkInterface.NewNetworkInterfaceDataSource,
|
||||
iaasVolume.NewVolumeDataSource,
|
||||
|
|
@ -573,6 +575,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource {
|
|||
iaasImage.NewImageResource,
|
||||
iaasNetwork.NewNetworkResource,
|
||||
iaasNetworkArea.NewNetworkAreaResource,
|
||||
iaasNetworkAreaRegion.NewNetworkAreaRegionResource,
|
||||
iaasNetworkAreaRoute.NewNetworkAreaRouteResource,
|
||||
iaasNetworkInterface.NewNetworkInterfaceResource,
|
||||
iaasVolume.NewVolumeResource,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue