diff --git a/docs/data-sources/sfs_snapshot_policies.md b/docs/data-sources/sfs_snapshot_policies.md
new file mode 100644
index 000000000..545b8970c
--- /dev/null
+++ b/docs/data-sources/sfs_snapshot_policies.md
@@ -0,0 +1,65 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "stackit_sfs_snapshot_policies Data Source - stackit"
+subcategory: ""
+description: |-
+ SFS snapshot policies datasource schema
+---
+
+# stackit_sfs_snapshot_policies (Data Source)
+
+SFS snapshot policies datasource schema
+
+## Example Usage
+
+```terraform
+data "stackit_sfs_snapshot_policies" "all" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+}
+
+data "stackit_sfs_snapshot_policies" "immutable_only" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ immutable = true
+}
+```
+
+
+## Schema
+
+### Required
+
+- `project_id` (String) STACKIT project ID to which the snapshot policy is associated.
+
+### Optional
+
+- `immutable` (Boolean) List only immutable snapshot policies.
+
+### Read-Only
+
+- `id` (String) Terraform's internal data source ID. It is structured as "`project_id`".
+- `items` (Attributes List) (see [below for nested schema](#nestedatt--items))
+
+
+### Nested Schema for `items`
+
+Read-Only:
+
+- `comment` (String) Comment of the Snapshot Policy.
+- `created_at` (String) Created At timestamp.
+- `enabled` (Boolean) Wether the Snapshot Policy is enabled.
+- `id` (String) ID of the Snapshot Policy.
+- `name` (String) Name of the Snapshot Policy.
+- `snapshot_schedules` (Attributes List) (see [below for nested schema](#nestedatt--items--snapshot_schedules))
+
+
+### Nested Schema for `items.snapshot_schedules`
+
+Read-Only:
+
+- `created_at` (String) Created At timestamp.
+- `id` (String) ID of the Snapshot Schedule.
+- `interval` (String) Interval of the Snapshot Schedule (follows the cron schedule xpression in Unix-like systems).
+- `name` (String) Name of the Snapshot Schedule.
+- `prefix` (String) Prefix used for snapshots created by this policy.
+- `retention_count` (Number) Retention Count.
+- `retention_period` (String) Retention Period (ISO 8601 format or 'infinite').
diff --git a/examples/data-sources/stackit_sfs_snapshot_policies/data-source.tf b/examples/data-sources/stackit_sfs_snapshot_policies/data-source.tf
new file mode 100644
index 000000000..0e9bdee84
--- /dev/null
+++ b/examples/data-sources/stackit_sfs_snapshot_policies/data-source.tf
@@ -0,0 +1,8 @@
+data "stackit_sfs_snapshot_policies" "all" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+}
+
+data "stackit_sfs_snapshot_policies" "immutable_only" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ immutable = true
+}
\ No newline at end of file
diff --git a/stackit/internal/services/sfs/sfs_acc_test.go b/stackit/internal/services/sfs/sfs_acc_test.go
index 546df69ea..88688f36c 100644
--- a/stackit/internal/services/sfs/sfs_acc_test.go
+++ b/stackit/internal/services/sfs/sfs_acc_test.go
@@ -44,6 +44,9 @@ var (
//go:embed testdata/project-lock-min.tf
resourceProjectLockConfig string
+
+ //go:embed testdata/snapshot-policies-datasource.tf
+ snapshotPoliciesDataSourceConfig string
)
// EXPORT POLICY - MIN
@@ -904,6 +907,51 @@ func TestAccProjectLockMin(t *testing.T) {
})
}
+func TestAccSnapshotPolicies(t *testing.T) {
+ projectId := config.StringVariable(testutil.ProjectId)
+ cfg := fmt.Sprintf(`
+%s
+
+%s`, testutil.NewConfigBuilder().BuildProviderConfig(), snapshotPoliciesDataSourceConfig)
+ resource.Test(t, resource.TestCase{
+ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // all
+ {
+ Config: cfg,
+ ConfigVariables: config.Variables{
+ "project_id": projectId,
+ },
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("data.stackit_sfs_snapshot_policies.snapshot_policies", "project_id", testutil.ProjectId),
+ ),
+ },
+ // immutable
+ {
+ Config: cfg,
+ ConfigVariables: config.Variables{
+ "project_id": projectId,
+ "immutable": config.BoolVariable(true),
+ },
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("data.stackit_sfs_snapshot_policies.snapshot_policies", "project_id", testutil.ProjectId),
+ ),
+ },
+ // mutable
+ {
+ Config: cfg,
+ ConfigVariables: config.Variables{
+ "project_id": projectId,
+ "immutable": config.BoolVariable(false),
+ },
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("data.stackit_sfs_snapshot_policies.snapshot_policies", "project_id", testutil.ProjectId),
+ ),
+ },
+ },
+ })
+}
+
func createClient() (*sfs.APIClient, error) {
client, err := sfs.NewAPIClient(testutil.NewConfigBuilder().BuildClientOptions(testutil.SFSCustomEndpoint, false)...)
if err != nil {
diff --git a/stackit/internal/services/sfs/snapshot-policy/list_resource.go b/stackit/internal/services/sfs/snapshot-policy/list_resource.go
new file mode 100644
index 000000000..9cd6a28dc
--- /dev/null
+++ b/stackit/internal/services/sfs/snapshot-policy/list_resource.go
@@ -0,0 +1 @@
+package snapshot_policy
diff --git a/stackit/internal/services/sfs/snapshot-policy/policies_datasource.go b/stackit/internal/services/sfs/snapshot-policy/policies_datasource.go
new file mode 100644
index 000000000..a695fc149
--- /dev/null
+++ b/stackit/internal/services/sfs/snapshot-policy/policies_datasource.go
@@ -0,0 +1,257 @@
+package snapshot_policy
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ sfs "github.com/stackitcloud/stackit-sdk-go/services/sfs/v1api"
+
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
+ sfsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/sfs/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
+)
+
+var (
+ _ datasource.DataSource = &policiesDataSource{}
+)
+
+func NewSnapshotPoliciesDataSource() datasource.DataSource {
+ return &policiesDataSource{}
+}
+
+type policiesDataSource struct {
+ client *sfs.APIClient
+ providerData core.ProviderData
+}
+
+func (r *policiesDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_sfs_snapshot_policies"
+}
+
+func (r *policiesDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ var ok bool
+ r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
+ if !ok {
+ return
+ }
+
+ apiClient := sfsUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ r.client = apiClient
+ tflog.Info(ctx, "SFS client configured.")
+}
+
+func (r *policiesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: "SFS snapshot policies datasource schema",
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "Terraform's internal data source ID. It is structured as \"`project_id`\".",
+ Computed: true,
+ },
+ "project_id": schema.StringAttribute{
+ Description: "STACKIT project ID to which the snapshot policy is associated.",
+ Required: true,
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "immutable": schema.BoolAttribute{
+ Description: "List only immutable snapshot policies.",
+ Optional: true,
+ },
+ "items": schema.ListNestedAttribute{
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ Description: "ID of the Snapshot Policy.",
+ },
+ "name": schema.StringAttribute{
+ Computed: true,
+ Description: "Name of the Snapshot Policy.",
+ },
+ "comment": schema.StringAttribute{
+ Computed: true,
+ Description: "Comment of the Snapshot Policy.",
+ },
+ "enabled": schema.BoolAttribute{
+ Computed: true,
+ Description: "Wether the Snapshot Policy is enabled.",
+ },
+ "created_at": schema.StringAttribute{
+ Computed: true,
+ Description: "Created At timestamp.",
+ },
+ "snapshot_schedules": schema.ListNestedAttribute{
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ Description: "ID of the Snapshot Schedule.",
+ },
+ "name": schema.StringAttribute{
+ Computed: true,
+ Description: "Name of the Snapshot Schedule.",
+ },
+ "created_at": schema.StringAttribute{
+ Computed: true,
+ Description: "Created At timestamp.",
+ },
+ "interval": schema.StringAttribute{
+ Computed: true,
+ Description: "Interval of the Snapshot Schedule (follows the cron schedule xpression in Unix-like systems).",
+ },
+ "prefix": schema.StringAttribute{
+ Computed: true,
+ Description: "Prefix used for snapshots created by this policy.",
+ },
+ "retention_count": schema.Int64Attribute{
+ Computed: true,
+ Description: "Retention Count.",
+ },
+ "retention_period": schema.StringAttribute{
+ Computed: true,
+ Description: "Retention Period (ISO 8601 format or 'infinite').",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+type model struct {
+ ID types.String `tfsdk:"id"`
+ ProjectId types.String `tfsdk:"project_id"`
+ Immutable types.Bool `tfsdk:"immutable"`
+ Items []policy `tfsdk:"items"`
+}
+
+type policy struct {
+ ID types.String `tfsdk:"id"`
+ Name types.String `tfsdk:"name"`
+ Comment types.String `tfsdk:"comment"`
+ Enabled types.Bool `tfsdk:"enabled"`
+ CreatedAt types.String `tfsdk:"created_at"`
+ SnapshotSchedules []schedule `tfsdk:"snapshot_schedules"`
+}
+
+type schedule struct {
+ ID types.String `tfsdk:"id"`
+ Name types.String `tfsdk:"name"`
+ CreatedAt types.String `tfsdk:"created_at"`
+ Interval types.String `tfsdk:"interval"`
+ Prefix types.String `tfsdk:"prefix"`
+ RetentionCount types.Int64 `tfsdk:"retention_count"`
+ RetentionPeriod types.String `tfsdk:"retention_period"`
+}
+
+func (r *policiesDataSource) 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...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ ctx = core.InitProviderContext(ctx)
+
+ projectId := model.ProjectId.ValueString()
+
+ ctx = tflog.SetField(ctx, "project_id", projectId)
+
+ listRequest := r.client.DefaultAPI.ListSnapshotPolicies(ctx, projectId)
+ if !utils.IsUndefined(model.Immutable) {
+ listRequest = listRequest.Immutable(model.Immutable.ValueBool())
+
+ title := `The "immutable" attribute of the "stackit_sfs_snapshot_policies" data source is in beta`
+ content := `This attribute may be subject to breaking changes in the future. Use with caution.`
+ tflog.Warn(ctx, fmt.Sprintf(`%s | %s`, title, content))
+ diags.AddWarning(title, content)
+ }
+ policies, err := listRequest.Execute()
+ if err != nil {
+ core.LogAndAddError(ctx, &diags, "Error listing snapshot policies", fmt.Sprintf("Calling API: %v", err))
+ return
+ }
+
+ ctx = core.LogResponse(ctx)
+
+ err = mapFields(ctx, policies, &model)
+ if err != nil {
+ core.LogAndAddError(ctx, &diags, "Error reading snapshot policies", 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, "SFS snapshot policies read")
+}
+
+func mapFields(_ context.Context, resp *sfs.ListSnapshotPoliciesResponse, model *model) error {
+ if resp == nil || resp.SnapshotPolicies == nil {
+ return fmt.Errorf("response input is nil")
+ }
+ if model == nil {
+ return fmt.Errorf("model input is nil")
+ }
+
+ projectID := model.ProjectId.ValueString()
+
+ model.ID = utils.BuildInternalTerraformId(projectID)
+
+ for _, respPolicy := range resp.SnapshotPolicies {
+ var createdAt types.String
+ if respPolicy.CreatedAt != nil {
+ createdAt = types.StringValue(respPolicy.CreatedAt.String())
+ }
+ modelPolicy := policy{
+ ID: types.StringPointerValue(respPolicy.Id),
+ Name: types.StringPointerValue(respPolicy.Name),
+ Comment: types.StringPointerValue(respPolicy.Comment),
+ Enabled: types.BoolPointerValue(respPolicy.Enabled),
+ CreatedAt: createdAt,
+ }
+ for _, respSchedule := range respPolicy.SnapshotSchedules {
+ var scheduleCreatedAt types.String
+ if respPolicy.CreatedAt != nil {
+ scheduleCreatedAt = types.StringValue(respSchedule.CreatedAt.String())
+ }
+ var retentionCount *int64
+ if respSchedule.RetentionCount != nil {
+ retentionCount = new(int64(*respSchedule.RetentionCount))
+ }
+ modelSchedule := schedule{
+ ID: types.StringPointerValue(respSchedule.Id),
+ Name: types.StringPointerValue(respSchedule.Name),
+ CreatedAt: scheduleCreatedAt,
+ Interval: types.StringPointerValue(respSchedule.Interval),
+ Prefix: types.StringPointerValue(respSchedule.Prefix),
+ RetentionCount: types.Int64PointerValue(retentionCount),
+ RetentionPeriod: types.StringPointerValue(respSchedule.RetentionPeriod),
+ }
+ modelPolicy.SnapshotSchedules = append(modelPolicy.SnapshotSchedules, modelSchedule)
+ }
+ model.Items = append(model.Items, modelPolicy)
+ }
+ return nil
+}
diff --git a/stackit/internal/services/sfs/snapshot-policy/policies_datasource_test.go b/stackit/internal/services/sfs/snapshot-policy/policies_datasource_test.go
new file mode 100644
index 000000000..79bc44782
--- /dev/null
+++ b/stackit/internal/services/sfs/snapshot-policy/policies_datasource_test.go
@@ -0,0 +1,137 @@
+package snapshot_policy
+
+import (
+ "testing"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ sfs "github.com/stackitcloud/stackit-sdk-go/services/sfs/v1api"
+)
+
+func TestMapFields(t *testing.T) {
+ now := time.Now()
+ tests := []struct {
+ name string
+ resp *sfs.ListSnapshotPoliciesResponse
+ want *model
+ wantErr bool
+ }{
+ {
+ name: "nil response",
+ resp: nil,
+ want: &model{},
+ wantErr: true,
+ },
+ {
+ name: "some values",
+ resp: &sfs.ListSnapshotPoliciesResponse{
+ SnapshotPolicies: []sfs.SnapshotPolicy{
+ {
+ Comment: new("comment"),
+ CreatedAt: new(now),
+ Enabled: new(true),
+ Id: new("id"),
+ Name: new("name"),
+ SnapshotSchedules: []sfs.SnapshotPolicySnapshotPolicySchedule{
+ {
+ CreatedAt: new(now),
+ Id: new("id"),
+ Interval: new("interval"),
+ Name: new("name"),
+ Prefix: new("prefix"),
+ RetentionCount: new(int32(123)),
+ RetentionPeriod: new("period"),
+ },
+ },
+ },
+ },
+ },
+ want: &model{
+ ID: types.StringValue(""),
+ Items: []policy{
+ {
+ ID: types.StringValue("id"),
+ Name: types.StringValue("name"),
+ Comment: types.StringValue("comment"),
+ Enabled: types.BoolValue(true),
+ CreatedAt: types.StringValue(now.String()),
+ SnapshotSchedules: []schedule{
+ {
+ ID: types.StringValue("id"),
+ Name: types.StringValue("name"),
+ CreatedAt: types.StringValue(now.String()),
+ Interval: types.StringValue("interval"),
+ Prefix: types.StringValue("prefix"),
+ RetentionCount: types.Int64Value(123),
+ RetentionPeriod: types.StringValue("period"),
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "nil values policy",
+ resp: &sfs.ListSnapshotPoliciesResponse{
+ SnapshotPolicies: []sfs.SnapshotPolicy{
+ {},
+ },
+ },
+ want: &model{
+ ID: types.StringValue(""),
+ Items: []policy{
+ {
+ ID: types.String{},
+ Name: types.String{},
+ Comment: types.String{},
+ Enabled: types.Bool{},
+ CreatedAt: types.String{},
+ SnapshotSchedules: nil,
+ },
+ },
+ },
+ },
+ {
+ name: "nil values schedule",
+ resp: &sfs.ListSnapshotPoliciesResponse{
+ SnapshotPolicies: []sfs.SnapshotPolicy{
+ {
+ SnapshotSchedules: []sfs.SnapshotPolicySnapshotPolicySchedule{
+ {},
+ },
+ },
+ },
+ },
+ want: &model{
+ ID: types.StringValue(""),
+ Items: []policy{
+ {
+ SnapshotSchedules: []schedule{
+ {},
+ },
+ },
+ },
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ m := &model{}
+ err := mapFields(t.Context(), tt.resp, m)
+ if err != nil && !tt.wantErr {
+ t.Errorf("unexpected error: %v", err)
+ }
+ if err == nil && tt.wantErr {
+ t.Errorf("unexpected error: %v", err)
+ }
+ if err != nil {
+ return
+ }
+ if diff := cmp.Diff(tt.want, m); diff != "" {
+ t.Errorf("Data does not match: %s", diff)
+ }
+ })
+ }
+}
diff --git a/stackit/internal/services/sfs/testdata/snapshot-policies-datasource.tf b/stackit/internal/services/sfs/testdata/snapshot-policies-datasource.tf
new file mode 100644
index 000000000..3c7b63362
--- /dev/null
+++ b/stackit/internal/services/sfs/testdata/snapshot-policies-datasource.tf
@@ -0,0 +1,9 @@
+variable "project_id" {}
+variable "immutable" {
+ default = null
+}
+
+data "stackit_sfs_snapshot_policies" "snapshot_policies" {
+ project_id = var.project_id
+ immutable = var.immutable
+}
\ No newline at end of file
diff --git a/stackit/provider.go b/stackit/provider.go
index 01b56db9c..216cbce00 100644
--- a/stackit/provider.go
+++ b/stackit/provider.go
@@ -112,6 +112,7 @@ import (
projectLock "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/sfs/project-lock"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/sfs/resourcepool"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/sfs/share"
+ snapshotPolicy "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/sfs/snapshot-policy"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/sfs/snapshots"
skeCluster "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/ske/cluster"
skeKubeconfig "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/ske/kubeconfig"
@@ -697,6 +698,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource
share.NewShareDataSource,
exportpolicy.NewExportPolicyDataSource,
snapshots.NewResourcePoolSnapshotDataSource,
+ snapshotPolicy.NewSnapshotPoliciesDataSource,
projectLock.NewProjectLockDatasource,
compliancelock.NewComplianceLockDataSource,
serverBackupEnable.NewServerBackupEnableDataSource,