|  | 
|  | 1 | +package folder | 
|  | 2 | + | 
|  | 3 | +import ( | 
|  | 4 | +	"context" | 
|  | 5 | +	"fmt" | 
|  | 6 | +	"net/http" | 
|  | 7 | +	"regexp" | 
|  | 8 | + | 
|  | 9 | +	"github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator" | 
|  | 10 | +	"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" | 
|  | 11 | +	"github.com/hashicorp/terraform-plugin-framework/datasource" | 
|  | 12 | +	"github.com/hashicorp/terraform-plugin-framework/datasource/schema" | 
|  | 13 | +	"github.com/hashicorp/terraform-plugin-framework/schema/validator" | 
|  | 14 | +	"github.com/hashicorp/terraform-plugin-framework/types" | 
|  | 15 | +	"github.com/hashicorp/terraform-plugin-log/tflog" | 
|  | 16 | +	"github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" | 
|  | 17 | +	"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" | 
|  | 18 | +	"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" | 
|  | 19 | +	"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features" | 
|  | 20 | +	resourcemanagerUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/resourcemanager/utils" | 
|  | 21 | +	"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" | 
|  | 22 | +	"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" | 
|  | 23 | +) | 
|  | 24 | + | 
|  | 25 | +// Ensure the implementation satisfies the expected interfaces. | 
|  | 26 | +var ( | 
|  | 27 | +	_ datasource.DataSource              = &folderDataSource{} | 
|  | 28 | +	_ datasource.DataSourceWithConfigure = &folderDataSource{} | 
|  | 29 | +) | 
|  | 30 | + | 
|  | 31 | +// NewFolderDataSource is a helper function to simplify the provider implementation. | 
|  | 32 | +func NewFolderDataSource() datasource.DataSource { | 
|  | 33 | +	return &folderDataSource{} | 
|  | 34 | +} | 
|  | 35 | + | 
|  | 36 | +// folderDataSource is the data source implementation. | 
|  | 37 | +type folderDataSource struct { | 
|  | 38 | +	client *resourcemanager.APIClient | 
|  | 39 | +} | 
|  | 40 | + | 
|  | 41 | +// Metadata returns the data source type name. | 
|  | 42 | +func (d *folderDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { | 
|  | 43 | +	resp.TypeName = req.ProviderTypeName + "_resourcemanager_folder" | 
|  | 44 | +} | 
|  | 45 | + | 
|  | 46 | +func (d *folderDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { | 
|  | 47 | +	providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) | 
|  | 48 | +	if !ok { | 
|  | 49 | +		return | 
|  | 50 | +	} | 
|  | 51 | + | 
|  | 52 | +	features.CheckBetaResourcesEnabled(ctx, &providerData, &resp.Diagnostics, "stackit_resourcemanager_folder", "datasource") | 
|  | 53 | +	if resp.Diagnostics.HasError() { | 
|  | 54 | +		return | 
|  | 55 | +	} | 
|  | 56 | + | 
|  | 57 | +	apiClient := resourcemanagerUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) | 
|  | 58 | +	if resp.Diagnostics.HasError() { | 
|  | 59 | +		return | 
|  | 60 | +	} | 
|  | 61 | +	d.client = apiClient | 
|  | 62 | +	tflog.Info(ctx, "Resource Manager client configured") | 
|  | 63 | +} | 
|  | 64 | + | 
|  | 65 | +// Schema defines the schema for the data source. | 
|  | 66 | +func (d *folderDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { | 
|  | 67 | +	descriptions := map[string]string{ | 
|  | 68 | +		"main":                "Resource Manager folder data source schema. To identify the folder, you need to provide the container_id.", | 
|  | 69 | +		"id":                  "Terraform's internal resource ID. It is structured as \"`container_id`\".", | 
|  | 70 | +		"container_id":        "Folder container ID. Globally unique, user-friendly identifier.", | 
|  | 71 | +		"folder_id":           "Folder UUID identifier. Globally unique folder identifier", | 
|  | 72 | +		"parent_container_id": "Parent resource identifier. Both container ID (user-friendly) and UUID are supported.", | 
|  | 73 | +		"name":                "The name of the folder.", | 
|  | 74 | +		"labels":              "Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}.", | 
|  | 75 | +		"owner_email":         "Email address of the owner of the folder. This value is only considered during creation. Changing it afterwards will have no effect.", | 
|  | 76 | +		"creation_time":       "Date-time at which the folder was created.", | 
|  | 77 | +		"update_time":         "Date-time at which the folder was last modified.", | 
|  | 78 | +	} | 
|  | 79 | + | 
|  | 80 | +	resp.Schema = schema.Schema{ | 
|  | 81 | +		Description: features.AddBetaDescription(descriptions["main"], core.Datasource), | 
|  | 82 | +		Attributes: map[string]schema.Attribute{ | 
|  | 83 | +			"id": schema.StringAttribute{ | 
|  | 84 | +				Description: descriptions["id"], | 
|  | 85 | +				Computed:    true, | 
|  | 86 | +			}, | 
|  | 87 | +			"container_id": schema.StringAttribute{ | 
|  | 88 | +				Description: descriptions["container_id"], | 
|  | 89 | +				Validators: []validator.String{ | 
|  | 90 | +					validate.NoSeparator(), | 
|  | 91 | +				}, | 
|  | 92 | +				Required: true, | 
|  | 93 | +			}, | 
|  | 94 | +			"folder_id": schema.StringAttribute{ | 
|  | 95 | +				Description: descriptions["folder_id"], | 
|  | 96 | +				Computed:    true, | 
|  | 97 | +				Validators: []validator.String{ | 
|  | 98 | +					validate.UUID(), | 
|  | 99 | +				}, | 
|  | 100 | +			}, | 
|  | 101 | +			"parent_container_id": schema.StringAttribute{ | 
|  | 102 | +				Description: descriptions["parent_container_id"], | 
|  | 103 | +				Computed:    true, | 
|  | 104 | +				Validators: []validator.String{ | 
|  | 105 | +					validate.NoSeparator(), | 
|  | 106 | +				}, | 
|  | 107 | +			}, | 
|  | 108 | +			"name": schema.StringAttribute{ | 
|  | 109 | +				Description: descriptions["name"], | 
|  | 110 | +				Computed:    true, | 
|  | 111 | +				Validators: []validator.String{ | 
|  | 112 | +					stringvalidator.LengthAtLeast(1), | 
|  | 113 | +					stringvalidator.LengthAtMost(63), | 
|  | 114 | +				}, | 
|  | 115 | +			}, | 
|  | 116 | +			"labels": schema.MapAttribute{ | 
|  | 117 | +				Description: descriptions["labels"], | 
|  | 118 | +				ElementType: types.StringType, | 
|  | 119 | +				Computed:    true, | 
|  | 120 | +				Validators: []validator.Map{ | 
|  | 121 | +					mapvalidator.KeysAre( | 
|  | 122 | +						stringvalidator.RegexMatches( | 
|  | 123 | +							regexp.MustCompile(`[A-ZÄÜÖa-zäüöß0-9_-]{1,64}`), | 
|  | 124 | +							"must match expression"), | 
|  | 125 | +					), | 
|  | 126 | +					mapvalidator.ValueStringsAre( | 
|  | 127 | +						stringvalidator.RegexMatches( | 
|  | 128 | +							regexp.MustCompile(`[A-ZÄÜÖa-zäüöß0-9_-]{1,64}`), | 
|  | 129 | +							"must match expression"), | 
|  | 130 | +					), | 
|  | 131 | +				}, | 
|  | 132 | +			}, | 
|  | 133 | +			"creation_time": schema.StringAttribute{ | 
|  | 134 | +				Description: descriptions["creation_time"], | 
|  | 135 | +				Computed:    true, | 
|  | 136 | +			}, | 
|  | 137 | +			"update_time": schema.StringAttribute{ | 
|  | 138 | +				Description: descriptions["update_time"], | 
|  | 139 | +				Computed:    true, | 
|  | 140 | +			}, | 
|  | 141 | +		}, | 
|  | 142 | +	} | 
|  | 143 | +} | 
|  | 144 | + | 
|  | 145 | +// Read refreshes the Terraform state with the latest data. | 
|  | 146 | +func (d *folderDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform | 
|  | 147 | +	var model Model | 
|  | 148 | +	diags := req.Config.Get(ctx, &model) | 
|  | 149 | +	resp.Diagnostics.Append(diags...) | 
|  | 150 | +	if resp.Diagnostics.HasError() { | 
|  | 151 | +		return | 
|  | 152 | +	} | 
|  | 153 | + | 
|  | 154 | +	containerId := model.ContainerId.ValueString() | 
|  | 155 | +	ctx = tflog.SetField(ctx, "container_id", containerId) | 
|  | 156 | + | 
|  | 157 | +	folderResp, err := d.client.GetFolderDetails(ctx, containerId).Execute() | 
|  | 158 | +	if err != nil { | 
|  | 159 | +		utils.LogError( | 
|  | 160 | +			ctx, | 
|  | 161 | +			&resp.Diagnostics, | 
|  | 162 | +			err, | 
|  | 163 | +			"Reading folder", | 
|  | 164 | +			fmt.Sprintf("folder with ID %q does not exist.", containerId), | 
|  | 165 | +			map[int]string{ | 
|  | 166 | +				http.StatusForbidden: fmt.Sprintf("folder with ID %q not found or forbidden access", containerId), | 
|  | 167 | +			}, | 
|  | 168 | +		) | 
|  | 169 | +		resp.State.RemoveResource(ctx) | 
|  | 170 | +		return | 
|  | 171 | +	} | 
|  | 172 | + | 
|  | 173 | +	err = mapFolderFields(ctx, folderResp, &model, &resp.State) | 
|  | 174 | +	if err != nil { | 
|  | 175 | +		core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading folder", fmt.Sprintf("Processing API response: %v", err)) | 
|  | 176 | +		return | 
|  | 177 | +	} | 
|  | 178 | + | 
|  | 179 | +	diags = resp.State.Set(ctx, &model) | 
|  | 180 | +	resp.Diagnostics.Append(diags...) | 
|  | 181 | +	if resp.Diagnostics.HasError() { | 
|  | 182 | +		return | 
|  | 183 | +	} | 
|  | 184 | +	tflog.Info(ctx, "Resource Manager folder read") | 
|  | 185 | +} | 
0 commit comments