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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/data-sources/sfs_export_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ data "stackit_sfs_export_policy" "example" {
### Read-Only

- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`policy_id`".
- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource pool
- `name` (String) Name of the export policy.
- `rules` (Attributes List) (see [below for nested schema](#nestedatt--rules))

Expand Down
1 change: 1 addition & 0 deletions docs/data-sources/sfs_resource_pool.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ data "stackit_sfs_resource_pool" "resourcepool" {
- `availability_zone` (String) Availability zone.
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`resource_pool_id`".
- `ip_acl` (List of String) List of IPs that can mount the resource pool in read-only; IPs must have a subnet mask (e.g. "172.16.0.0/24" for a range of IPs, or "172.16.0.250/32" for a specific IP).
- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource pool
- `name` (String) Name of the resource pool.
- `performance_class` (String) Name of the performance class.
- `performance_class_downgradable_at` (String) Time when the performance class can be downgraded again.
Expand Down
1 change: 1 addition & 0 deletions docs/data-sources/sfs_share.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Note that if this is not set, the Share can only be mounted in read only by
clients with IPs matching the IP ACL of the Resource Pool hosting this Share.
You can also assign a Share Export Policy after creating the Share
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`share_id`".
- `labels` (Map of String) Labels are key-value string pairs which can be attached to a share
- `mount_path` (String) Mount path of the Share, used to mount the Share
- `name` (String) Name of the Share
- `space_hard_limit_gigabytes` (Number) Space hard limit for the Share.
Expand Down
1 change: 1 addition & 0 deletions docs/resources/sfs_export_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {

### Optional

- `labels` (Map of String) Labels are key-value string pairs which can be attached to the resource.
- `region` (String) The resource region. If not defined, the provider region is used.
- `rules` (Attributes List) (see [below for nested schema](#nestedatt--rules))

Expand Down
1 change: 1 addition & 0 deletions docs/resources/sfs_resource_pool.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {

### Optional

- `labels` (Map of String) Labels are key-value string pairs which can be attached to the resource.
- `region` (String) The resource region. If not defined, the provider region is used.
- `snapshots_are_visible` (Boolean) If set to true, snapshots are visible and accessible to users. (default: false)

Expand Down
1 change: 1 addition & 0 deletions docs/resources/sfs_share.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
Note that if this is set to an empty string, the Share can only be mounted in read only by
clients with IPs matching the IP ACL of the Resource Pool hosting this Share.
You can also assign a Share Export Policy after creating the Share
- `labels` (Map of String) Labels are key-value string pairs which can be attached to the resource.
- `region` (String) The resource region. If not defined, the provider region is used.

### Read-Only
Expand Down
5 changes: 5 additions & 0 deletions stackit/internal/services/sfs/export-policy/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ func (d *exportPolicyDataSource) Schema(_ context.Context, _ datasource.SchemaRe
Description: "Name of the export policy.",
Computed: true,
},
"labels": schema.MapAttribute{
Description: "Labels are key-value string pairs which can be attached to a resource pool",
ElementType: types.StringType,
Computed: true,
},
"rules": schema.ListNestedAttribute{
Computed: true,
NestedObject: schema.NestedAttributeObject{
Expand Down
37 changes: 31 additions & 6 deletions stackit/internal/services/sfs/export-policy/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type Model struct {
ProjectId types.String `tfsdk:"project_id"`
ExportPolicyId types.String `tfsdk:"policy_id"`
Name types.String `tfsdk:"name"`
Labels types.Map `tfsdk:"labels"`
Rules types.List `tfsdk:"rules"`
Region types.String `tfsdk:"region"`
}
Expand Down Expand Up @@ -188,6 +189,12 @@ func (r *exportPolicyResource) Schema(_ context.Context, _ resource.SchemaReques
stringvalidator.LengthAtLeast(1),
},
},
"labels": schema.MapAttribute{
Description: "Labels are key-value string pairs which can be attached to the resource.",
ElementType: types.StringType,
Optional: true,
Validators: validate.LabelValidators(),
},
"rules": schema.ListNestedAttribute{
Computed: true,
Optional: true,
Expand Down Expand Up @@ -269,7 +276,7 @@ func (r *exportPolicyResource) Create(ctx context.Context, req resource.CreateRe
}
}

payload, err := toCreatePayload(&model, rules)
payload, err := toCreatePayload(ctx, &model, rules)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating export policy", fmt.Sprintf("Creating API payload: %v", err))
return
Expand Down Expand Up @@ -400,7 +407,7 @@ func (r *exportPolicyResource) Update(ctx context.Context, req resource.UpdateRe
}
}

payload, err := toUpdatePayload(&model, rules)
payload, err := toUpdatePayload(ctx, &model, rules)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating export policy", fmt.Sprintf("Creating API payload: %v", err))
return
Expand Down Expand Up @@ -511,6 +518,12 @@ func mapFields(ctx context.Context, resp *sfs.GetShareExportPolicyResponse, mode
return fmt.Errorf("export policy id not present")
}

labels, err := utils.MapLabels(ctx, resp.ShareExportPolicy.Labels, model.Labels)
if err != nil {
return err
}
model.Labels = labels

// iterate over Rules from response
if resp.ShareExportPolicy.Rules != nil {
rulesList := []attr.Value{}
Expand Down Expand Up @@ -564,14 +577,19 @@ func mapFields(ctx context.Context, resp *sfs.GetShareExportPolicyResponse, mode
}

// Build a CreateShareExportPolicyPayload from provider's model
func toCreatePayload(model *Model, rules []rulesModel) (*sfs.CreateShareExportPolicyPayload, error) {
func toCreatePayload(ctx context.Context, model *Model, rules []rulesModel) (*sfs.CreateShareExportPolicyPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
if rules == nil {
return nil, fmt.Errorf("nil rules")
}

labels, err := utils.LabelsToPayload(ctx, model.Labels)
if err != nil {
return nil, err
}

// iterate over rules
var tempRules []sfs.CreateShareExportPolicyRequestRule
for _, rule := range rules {
Expand All @@ -593,7 +611,8 @@ func toCreatePayload(model *Model, rules []rulesModel) (*sfs.CreateShareExportPo

// name and rules
result := &sfs.CreateShareExportPolicyPayload{
Name: model.Name.ValueString(),
Name: model.Name.ValueString(),
Labels: &labels,
}

// Rules should only be set if tempRules has value. Otherwise, the payload would contain `{ "rules": null }` what should be prevented
Expand All @@ -604,14 +623,19 @@ func toCreatePayload(model *Model, rules []rulesModel) (*sfs.CreateShareExportPo
return result, nil
}

func toUpdatePayload(model *Model, rules []rulesModel) (*sfs.UpdateShareExportPolicyPayload, error) {
func toUpdatePayload(ctx context.Context, model *Model, rules []rulesModel) (*sfs.UpdateShareExportPolicyPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
if rules == nil {
return nil, fmt.Errorf("nil rules")
}

labels, err := utils.LabelsToPayload(ctx, model.Labels)
if err != nil {
return nil, err
}

// iterate over rules
tempRules := make([]sfs.UpdateShareExportPolicyBodyRule, len(rules))
for i, rule := range rules {
Expand All @@ -635,7 +659,8 @@ func toUpdatePayload(model *Model, rules []rulesModel) (*sfs.UpdateShareExportPo
result := &sfs.UpdateShareExportPolicyPayload{
// Rules should *+never** result in a payload where they are defined as null, e.g. `{ "rules": null }`. Instead,
// they should either be set to an array (with values or empty) or they shouldn't be present in the payload.
Rules: tempRules,
Rules: tempRules,
Labels: &labels,
}
return result, nil
}
128 changes: 123 additions & 5 deletions stackit/internal/services/sfs/export-policy/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func fixtureResponseModel(rulesModel basetypes.ListValue) *Model {
Id: types.StringValue(project_id + ",region,uuid1"),
ExportPolicyId: types.StringValue("uuid1"),
Rules: rulesModel,
Labels: types.MapNull(types.StringType),
Region: types.StringValue("region"),
}
}
Expand Down Expand Up @@ -152,14 +153,16 @@ func fixtureRulesPayloadModel() []rulesModel {

func fixtureExportPolicyCreatePayload(rules []sfs.CreateShareExportPolicyRequestRule) *sfs.CreateShareExportPolicyPayload {
return &sfs.CreateShareExportPolicyPayload{
Name: "createPayloadName",
Rules: rules,
Name: "createPayloadName",
Rules: rules,
Labels: &map[string]string{},
}
}

func fixtureExportPolicyUpdatePayload(rules []sfs.UpdateShareExportPolicyBodyRule) *sfs.UpdateShareExportPolicyPayload {
return &sfs.UpdateShareExportPolicyPayload{
Rules: rules,
Rules: rules,
Labels: &map[string]string{},
}
}

Expand Down Expand Up @@ -221,6 +224,84 @@ func TestMapFields(t *testing.T) {
region: testRegion,
isValid: true,
},
{
name: "Add Labels",
state: &Model{
ProjectId: types.StringValue(project_id),
},
input: &sfs.GetShareExportPolicyResponse{
ShareExportPolicy: &sfs.ShareExportPolicy{
Id: new("uuid1"),
Rules: fixtureRulesResponse(),
Labels: &map[string]string{
"foo": "bar",
},
},
},
expectedModel: &Model{
ProjectId: types.StringValue(project_id),
Id: types.StringValue(project_id + ",region,uuid1"),
ExportPolicyId: types.StringValue("uuid1"),
Rules: fixtureRulesModel(),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"foo": types.StringValue("bar"),
}),
Region: types.StringValue("region"),
},
region: testRegion,
isValid: true,
},
{
name: "Remove Labels through empty map",
state: &Model{
ProjectId: types.StringValue(project_id),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"foo": types.StringValue("bar"),
}),
},
input: &sfs.GetShareExportPolicyResponse{
ShareExportPolicy: &sfs.ShareExportPolicy{
Id: new("uuid1"),
Rules: fixtureRulesResponse(),
Labels: &map[string]string{},
},
},
expectedModel: &Model{
ProjectId: types.StringValue(project_id),
Id: types.StringValue(project_id + ",region,uuid1"),
ExportPolicyId: types.StringValue("uuid1"),
Rules: fixtureRulesModel(),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
Region: types.StringValue("region"),
},
region: testRegion,
isValid: true,
},
{
name: "Remove Labels through missing parameter",
state: &Model{
ProjectId: types.StringValue(project_id),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"foo": types.StringValue("bar"),
}),
},
input: &sfs.GetShareExportPolicyResponse{
ShareExportPolicy: &sfs.ShareExportPolicy{
Id: new("uuid1"),
Rules: fixtureRulesResponse(),
},
},
expectedModel: &Model{
ProjectId: types.StringValue(project_id),
Id: types.StringValue(project_id + ",region,uuid1"),
ExportPolicyId: types.StringValue("uuid1"),
Rules: fixtureRulesModel(),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
Region: types.StringValue("region"),
},
region: testRegion,
isValid: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -284,10 +365,29 @@ func TestToCreatePayload(t *testing.T) {
expected: fixtureExportPolicyCreatePayload(fixtureRulesCreatePayload()),
wantErr: false,
},
{
name: "valid label payload",
model: &Model{
ProjectId: types.StringValue(project_id),
Name: types.StringValue("createPayloadName"),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"foo": types.StringValue("bar"),
}),
},
rules: fixtureRulesPayloadModel(),
expected: &sfs.CreateShareExportPolicyPayload{
Name: "createPayloadName",
Rules: fixtureRulesCreatePayload(),
Labels: &map[string]string{
"foo": "bar",
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := toCreatePayload(tt.model, tt.rules)
got, err := toCreatePayload(context.Background(), tt.model, tt.rules)
if (err != nil) != tt.wantErr {
t.Errorf("toCreatePayload() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down Expand Up @@ -342,10 +442,28 @@ func TestToUpdatePayload(t *testing.T) {
expected: fixtureExportPolicyUpdatePayload(fixtureRulesUpdatePayload()),
wantErr: false,
},
{
name: "valid label payload",
model: &Model{
ProjectId: types.StringValue(project_id),
Name: types.StringValue("createPayloadName"),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"foo": types.StringValue("bar"),
}),
},
rules: fixtureRulesPayloadModel(),
expected: &sfs.UpdateShareExportPolicyPayload{
Rules: fixtureRulesUpdatePayload(),
Labels: &map[string]string{
"foo": "bar",
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := toUpdatePayload(tt.model, tt.rules)
got, err := toUpdatePayload(context.Background(), tt.model, tt.rules)
if (err != nil) != tt.wantErr {
t.Errorf("toUpdatePayload() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down
15 changes: 14 additions & 1 deletion stackit/internal/services/sfs/resourcepool/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type dataSourceModel struct {
PerformanceClassDowngradableAt types.String `tfsdk:"performance_class_downgradable_at"`
Region types.String `tfsdk:"region"`
SnapshotsAreVisible types.Bool `tfsdk:"snapshots_are_visible"`
Labels types.Map `tfsdk:"labels"`
}

type resourcePoolDataSource struct {
Expand Down Expand Up @@ -195,7 +196,13 @@ func (r *resourcePoolDataSource) Schema(_ context.Context, _ datasource.SchemaRe
// the region cannot be found automatically, so it has to be passed
Optional: true,
Description: "The resource region. Read-only attribute that reflects the provider region.",
}},
},
"labels": schema.MapAttribute{
Description: "Labels are key-value string pairs which can be attached to a resource pool",
ElementType: types.StringType,
Computed: true,
},
},
}
}

Expand Down Expand Up @@ -247,5 +254,11 @@ func mapDataSourceFields(ctx context.Context, region string, resourcePool *sfs.R
model.SizeReducibleAt = types.StringValue(t.Format(time.RFC3339))
}

labels, err := utils.MapLabels(ctx, resourcePool.Labels, model.Labels)
if err != nil {
return err
}
model.Labels = labels

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func TestMapDatasourceFields(t *testing.T) {
AvailabilityZone: types.StringNull(),
IpAcl: types.ListNull(types.StringType),
Name: types.StringNull(),
Labels: types.MapNull(types.StringType),
PerformanceClass: types.StringNull(),
SizeGigabytes: types.Int32Null(),
Region: testRegion,
Expand Down Expand Up @@ -87,6 +88,7 @@ func TestMapDatasourceFields(t *testing.T) {
types.StringValue("baz"),
}),
Name: types.StringValue("testname"),
Labels: types.MapNull(types.StringType),
PerformanceClass: types.StringValue("performance"),
SizeGigabytes: types.Int32Value(42),
Region: testRegion,
Expand Down
Loading
Loading