feat: add new attributes to git resource and datasource (#890)

* feat: add new attributes to git resource and datasource

Signed-off-by: Mauritz Uphoff <mauritz.uphoff@stackit.cloud>

* review changes

Signed-off-by: Mauritz Uphoff <mauritz.uphoff@stackit.cloud>

* review changes 2

Signed-off-by: Mauritz Uphoff <mauritz.uphoff@stackit.cloud>

---------

Signed-off-by: Mauritz Uphoff <mauritz.uphoff@stackit.cloud>
This commit is contained in:
Mauritz Uphoff 2025-07-03 09:26:18 +02:00 committed by GitHub
parent 3c5c8e0a6c
commit 2dda93bb76
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 570 additions and 95 deletions

View file

@ -32,6 +32,11 @@ data "stackit_git" "git" {
### Read-Only
- `acl` (List of String) Restricted ACL for instance access.
- `consumed_disk` (String) How many bytes of disk space is consumed.
- `consumed_object_storage` (String) How many bytes of Object Storage is consumed.
- `created` (String) Instance creation timestamp in RFC3339 format.
- `flavor` (String) Instance flavor. If not provided, defaults to git-100. For a list of available flavors, refer to our API documentation: `https://docs.api.stackit.cloud/documentation/git/version/v1beta`
- `id` (String) Terraform's internal resource ID, structured as "`project_id`,`instance_id`".
- `name` (String) Unique name linked to the git instance.
- `url` (String) Url linked to the git instance.

View file

@ -4,14 +4,14 @@ page_title: "stackit_git Resource - stackit"
subcategory: ""
description: |-
Git Instance resource schema.
~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources. This resource currently does not support updates. Changing the ACLs, flavor, or name will trigger resource recreation. Update functionality will be added soon. In the meantime, please proceed with caution. To update these attributes, please open a support ticket.
---
# stackit_git (Resource)
Git Instance resource schema.
~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources. This resource currently does not support updates. Changing the ACLs, flavor, or name will trigger resource recreation. Update functionality will be added soon. In the meantime, please proceed with caution. To update these attributes, please open a support ticket.
## Example Usage
@ -20,6 +20,15 @@ resource "stackit_git" "git" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "git-example-instance"
}
resource "stackit_git" "git" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "git-example-instance"
acl = [
"0.0.0.0/0"
]
flavor = "git-100"
}
```
<!-- schema generated by tfplugindocs -->
@ -30,8 +39,16 @@ resource "stackit_git" "git" {
- `name` (String) Unique name linked to the git instance.
- `project_id` (String) STACKIT project ID to which the git instance is associated.
### Optional
- `acl` (List of String) Restricted ACL for instance access.
- `flavor` (String) Instance flavor. If not provided, defaults to git-100. For a list of available flavors, refer to our API documentation: `https://docs.api.stackit.cloud/documentation/git/version/v1beta`
### Read-Only
- `consumed_disk` (String) How many bytes of disk space is consumed.
- `consumed_object_storage` (String) How many bytes of Object Storage is consumed.
- `created` (String) Instance creation timestamp in RFC3339 format.
- `id` (String) Terraform's internal resource ID, structured as "`project_id`,`instance_id`".
- `instance_id` (String) ID linked to the git instance.
- `url` (String) Url linked to the git instance.

View file

@ -1,4 +1,13 @@
resource "stackit_git" "git" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "git-example-instance"
}
resource "stackit_git" "git" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "git-example-instance"
acl = [
"0.0.0.0/0"
]
flavor = "git-100"
}

View file

@ -19,46 +19,74 @@ import (
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
)
//go:embed testdata/resource.tf
var resourceConfig string
//go:embed testdata/resource-min.tf
var resourceMin string
var name = fmt.Sprintf("git-%s-instance", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))
var nameUpdated = fmt.Sprintf("git-%s-instance", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))
//go:embed testdata/resource-max.tf
var resourceMax string
var testConfigVars = config.Variables{
var nameMin = fmt.Sprintf("git-min-%s-instance", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))
var nameMinUpdated = fmt.Sprintf("git-min-%s-instance", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))
var nameMax = fmt.Sprintf("git-max-%s-instance", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))
var nameMaxUpdated = fmt.Sprintf("git-max-%s-instance", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))
var aclUpdated = "192.168.1.0/32"
var testConfigVarsMin = config.Variables{
"project_id": config.StringVariable(testutil.ProjectId),
"name": config.StringVariable(name),
"name": config.StringVariable(nameMin),
}
func testConfigVarsUpdated() config.Variables {
tempConfig := make(config.Variables, len(testConfigVars))
maps.Copy(tempConfig, testConfigVars)
var testConfigVarsMax = config.Variables{
"project_id": config.StringVariable(testutil.ProjectId),
"name": config.StringVariable(nameMax),
"acl": config.StringVariable("192.168.0.0/16"),
"flavor": config.StringVariable("git-100"),
}
func testConfigVarsMinUpdated() config.Variables {
tempConfig := make(config.Variables, len(testConfigVarsMin))
maps.Copy(tempConfig, testConfigVarsMin)
// update git instance to a new name
// should trigger creating a new instance
tempConfig["name"] = config.StringVariable(nameUpdated)
tempConfig["name"] = config.StringVariable(nameMinUpdated)
return tempConfig
}
func TestGitInstance(t *testing.T) {
func testConfigVarsMaxUpdated() config.Variables {
tempConfig := make(config.Variables, len(testConfigVarsMax))
maps.Copy(tempConfig, testConfigVarsMax)
// update git instance to a new name
// should trigger creating a new instance
tempConfig["name"] = config.StringVariable(nameMaxUpdated)
tempConfig["acl"] = config.StringVariable(aclUpdated)
return tempConfig
}
func TestAccGitMin(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckGitInstanceDestroy,
Steps: []resource.TestStep{
// Creation
{
ConfigVariables: testConfigVars,
Config: testutil.GitProviderConfig() + resourceConfig,
ConfigVariables: testConfigVarsMin,
Config: testutil.GitProviderConfig() + resourceMin,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("stackit_git.git", "project_id", testutil.ConvertConfigVariable(testConfigVars["project_id"])),
resource.TestCheckResourceAttr("stackit_git.git", "name", testutil.ConvertConfigVariable(testConfigVars["name"])),
resource.TestCheckResourceAttr("stackit_git.git", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])),
resource.TestCheckResourceAttr("stackit_git.git", "name", testutil.ConvertConfigVariable(testConfigVarsMin["name"])),
resource.TestCheckResourceAttrSet("stackit_git.git", "url"),
resource.TestCheckResourceAttrSet("stackit_git.git", "version"),
resource.TestCheckResourceAttrSet("stackit_git.git", "instance_id"),
resource.TestCheckResourceAttrSet("stackit_git.git", "created"),
resource.TestCheckResourceAttrSet("stackit_git.git", "consumed_object_storage"),
resource.TestCheckResourceAttrSet("stackit_git.git", "consumed_disk"),
resource.TestCheckResourceAttrSet("stackit_git.git", "flavor"),
),
},
// Data source
{
ConfigVariables: testConfigVars,
ConfigVariables: testConfigVarsMin,
Config: fmt.Sprintf(`
%s
@ -66,11 +94,11 @@ func TestGitInstance(t *testing.T) {
project_id = stackit_git.git.project_id
instance_id = stackit_git.git.instance_id
}
`, testutil.GitProviderConfig()+resourceConfig,
`, testutil.GitProviderConfig()+resourceMin,
),
Check: resource.ComposeAggregateTestCheckFunc(
// Instance
resource.TestCheckResourceAttr("data.stackit_git.git", "project_id", testutil.ConvertConfigVariable(testConfigVars["project_id"])),
resource.TestCheckResourceAttr("data.stackit_git.git", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])),
resource.TestCheckResourceAttrPair(
"stackit_git.git", "project_id",
"data.stackit_git.git", "project_id",
@ -91,11 +119,27 @@ func TestGitInstance(t *testing.T) {
"stackit_git.git", "version",
"data.stackit_git.git", "version",
),
resource.TestCheckResourceAttrPair(
"stackit_git.git", "created",
"data.stackit_git.git", "created",
),
resource.TestCheckResourceAttrPair(
"stackit_git.git", "consumed_object_storage",
"data.stackit_git.git", "consumed_object_storage",
),
resource.TestCheckResourceAttrPair(
"stackit_git.git", "consumed_disk",
"data.stackit_git.git", "consumed_disk",
),
resource.TestCheckResourceAttrPair(
"stackit_git.git", "flavor",
"data.stackit_git.git", "flavor",
),
),
},
// Import
{
ConfigVariables: testConfigVars,
ConfigVariables: testConfigVarsMin,
ResourceName: "stackit_git.git",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
r, ok := s.RootModule().Resources["stackit_git.git"]
@ -113,14 +157,137 @@ func TestGitInstance(t *testing.T) {
},
// Update
{
ConfigVariables: testConfigVarsUpdated(),
Config: testutil.GitProviderConfig() + resourceConfig,
ConfigVariables: testConfigVarsMinUpdated(),
Config: testutil.GitProviderConfig() + resourceMin,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("stackit_git.git", "project_id", testutil.ConvertConfigVariable(testConfigVars["project_id"])),
resource.TestCheckResourceAttr("stackit_git.git", "name", testutil.ConvertConfigVariable(testConfigVarsUpdated()["name"])),
resource.TestCheckResourceAttr("stackit_git.git", "project_id", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["project_id"])),
resource.TestCheckResourceAttr("stackit_git.git", "name", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["name"])),
resource.TestCheckResourceAttrSet("stackit_git.git", "url"),
resource.TestCheckResourceAttrSet("stackit_git.git", "version"),
resource.TestCheckResourceAttrSet("stackit_git.git", "instance_id"),
resource.TestCheckResourceAttrSet("stackit_git.git", "created"),
resource.TestCheckResourceAttrSet("stackit_git.git", "consumed_object_storage"),
resource.TestCheckResourceAttrSet("stackit_git.git", "consumed_disk"),
resource.TestCheckResourceAttrSet("stackit_git.git", "flavor"),
),
},
// Deletion is done by the framework implicitly
},
})
}
func TestAccGitMax(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckGitInstanceDestroy,
Steps: []resource.TestStep{
// Creation
{
ConfigVariables: testConfigVarsMax,
Config: testutil.GitProviderConfig() + resourceMax,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("stackit_git.git", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])),
resource.TestCheckResourceAttr("stackit_git.git", "name", testutil.ConvertConfigVariable(testConfigVarsMax["name"])),
resource.TestCheckResourceAttr("stackit_git.git", "flavor", testutil.ConvertConfigVariable(testConfigVarsMax["flavor"])),
resource.TestCheckResourceAttr("stackit_git.git", "acl.0", testutil.ConvertConfigVariable(testConfigVarsMax["acl"])),
resource.TestCheckResourceAttrSet("stackit_git.git", "url"),
resource.TestCheckResourceAttrSet("stackit_git.git", "version"),
resource.TestCheckResourceAttrSet("stackit_git.git", "instance_id"),
resource.TestCheckResourceAttrSet("stackit_git.git", "created"),
resource.TestCheckResourceAttrSet("stackit_git.git", "consumed_object_storage"),
resource.TestCheckResourceAttrSet("stackit_git.git", "consumed_disk"),
),
},
// Data source
{
ConfigVariables: testConfigVarsMax,
Config: fmt.Sprintf(`
%s
data "stackit_git" "git" {
project_id = stackit_git.git.project_id
instance_id = stackit_git.git.instance_id
}
`, testutil.GitProviderConfig()+resourceMax,
),
Check: resource.ComposeAggregateTestCheckFunc(
// Instance
resource.TestCheckResourceAttr("data.stackit_git.git", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])),
resource.TestCheckResourceAttrPair(
"stackit_git.git", "project_id",
"data.stackit_git.git", "project_id",
),
resource.TestCheckResourceAttrPair(
"stackit_git.git", "instance_id",
"data.stackit_git.git", "instance_id",
),
resource.TestCheckResourceAttrPair(
"stackit_git.git", "name",
"data.stackit_git.git", "name",
),
resource.TestCheckResourceAttrPair(
"stackit_git.git", "url",
"data.stackit_git.git", "url",
),
resource.TestCheckResourceAttrPair(
"stackit_git.git", "version",
"data.stackit_git.git", "version",
),
resource.TestCheckResourceAttrPair(
"stackit_git.git", "created",
"data.stackit_git.git", "created",
),
resource.TestCheckResourceAttrPair(
"stackit_git.git", "consumed_object_storage",
"data.stackit_git.git", "consumed_object_storage",
),
resource.TestCheckResourceAttrPair(
"stackit_git.git", "consumed_disk",
"data.stackit_git.git", "consumed_disk",
),
resource.TestCheckResourceAttrPair(
"stackit_git.git", "flavor",
"data.stackit_git.git", "flavor",
),
resource.TestCheckResourceAttrPair(
"stackit_git.git", "acl",
"data.stackit_git.git", "acl",
),
),
},
// Import
{
ConfigVariables: testConfigVarsMax,
ResourceName: "stackit_git.git",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
r, ok := s.RootModule().Resources["stackit_git.git"]
if !ok {
return "", fmt.Errorf("couldn't find resource stackit_git.git")
}
instanceId, ok := r.Primary.Attributes["instance_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute instance_id")
}
return fmt.Sprintf("%s,%s", testutil.ProjectId, instanceId), nil
},
ImportState: true,
ImportStateVerify: true,
},
// Update
{
ConfigVariables: testConfigVarsMaxUpdated(),
Config: testutil.GitProviderConfig() + resourceMax,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("stackit_git.git", "project_id", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["project_id"])),
resource.TestCheckResourceAttr("stackit_git.git", "name", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["name"])),
resource.TestCheckResourceAttr("stackit_git.git", "flavor", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["flavor"])),
resource.TestCheckResourceAttr("stackit_git.git", "acl.0", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["acl"])),
resource.TestCheckResourceAttrSet("stackit_git.git", "url"),
resource.TestCheckResourceAttrSet("stackit_git.git", "version"),
resource.TestCheckResourceAttrSet("stackit_git.git", "instance_id"),
resource.TestCheckResourceAttrSet("stackit_git.git", "created"),
resource.TestCheckResourceAttrSet("stackit_git.git", "consumed_object_storage"),
resource.TestCheckResourceAttrSet("stackit_git.git", "consumed_disk"),
),
},
// Deletion is done by the framework implicitly

View file

@ -12,6 +12,7 @@ import (
"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"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/services/git"
@ -86,6 +87,27 @@ func (g *gitDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, re
validate.NoSeparator(),
},
},
"acl": schema.ListAttribute{
Description: descriptions["acl"],
Computed: true,
ElementType: types.StringType,
},
"consumed_disk": schema.StringAttribute{
Description: descriptions["consumed_disk"],
Computed: true,
},
"consumed_object_storage": schema.StringAttribute{
Description: descriptions["consumed_object_storage"],
Computed: true,
},
"created": schema.StringAttribute{
Description: descriptions["created"],
Computed: true,
},
"flavor": schema.StringAttribute{
Description: descriptions["flavor"],
Computed: true,
},
"name": schema.StringAttribute{
Description: descriptions["name"],
Computed: true,
@ -127,7 +149,7 @@ func (g *gitDataSource) Read(ctx context.Context, req datasource.ReadRequest, re
return
}
err = mapFields(gitInstanceResp, &model)
err = mapFields(ctx, gitInstanceResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading git instance", fmt.Sprintf("Processing API response: %v", err))
return

View file

@ -7,15 +7,12 @@ import (
"net/http"
"strings"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
gitUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/git/utils"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"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/listplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
@ -24,8 +21,11 @@ import (
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/services/git"
"github.com/stackitcloud/stackit-sdk-go/services/git/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"
gitUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/git/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
)
@ -38,12 +38,17 @@ var (
// Model represents the schema for the git resource.
type Model struct {
Id types.String `tfsdk:"id"` // Required by Terraform
ProjectId types.String `tfsdk:"project_id"` // ProjectId associated with the git instance
InstanceId types.String `tfsdk:"instance_id"` // InstanceId associated with the git instance
Name types.String `tfsdk:"name"` // Name linked to the git instance
Url types.String `tfsdk:"url"` // Url linked to the git instance
Version types.String `tfsdk:"version"` // Version linked to the git instance
Id types.String `tfsdk:"id"` // Required by Terraform
ACL types.List `tfsdk:"acl"`
ConsumedDisk types.String `tfsdk:"consumed_disk"`
ConsumedObjectStorage types.String `tfsdk:"consumed_object_storage"`
Created types.String `tfsdk:"created"`
Flavor types.String `tfsdk:"flavor"`
InstanceId types.String `tfsdk:"instance_id"`
Name types.String `tfsdk:"name"`
ProjectId types.String `tfsdk:"project_id"`
Url types.String `tfsdk:"url"`
Version types.String `tfsdk:"version"`
}
// NewGitResource is a helper function to create a new git resource instance.
@ -58,12 +63,17 @@ type gitResource struct {
// descriptions for the attributes in the Schema
var descriptions = map[string]string{
"id": "Terraform's internal resource ID, structured as \"`project_id`,`instance_id`\".",
"project_id": "STACKIT project ID to which the git instance is associated.",
"instance_id": "ID linked to the git instance.",
"name": "Unique name linked to the git instance.",
"url": "Url linked to the git instance.",
"version": "Version linked to the git instance.",
"id": "Terraform's internal resource ID, structured as \"`project_id`,`instance_id`\".",
"acl": "Restricted ACL for instance access.",
"consumed_disk": "How many bytes of disk space is consumed.",
"consumed_object_storage": "How many bytes of Object Storage is consumed.",
"created": "Instance creation timestamp in RFC3339 format.",
"flavor": "Instance flavor. If not provided, defaults to git-100. For a list of available flavors, refer to our API documentation: `https://docs.api.stackit.cloud/documentation/git/version/v1beta`",
"instance_id": "ID linked to the git instance.",
"name": "Unique name linked to the git instance.",
"project_id": "STACKIT project ID to which the git instance is associated.",
"url": "Url linked to the git instance.",
"version": "Version linked to the git instance.",
}
// Configure sets up the API client for the git instance resource.
@ -94,8 +104,12 @@ func (g *gitResource) Metadata(_ context.Context, req resource.MetadataRequest,
// Schema defines the schema for the resource.
func (g *gitResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: features.AddBetaDescription("Git Instance resource schema.", core.Resource),
Description: "Git Instance resource schema.",
MarkdownDescription: fmt.Sprintf(
"%s %s",
features.AddBetaDescription("Git Instance resource schema.", core.Resource),
"This resource currently does not support updates. Changing the ACLs, flavor, or name will trigger resource recreation. Update functionality will be added soon. In the meantime, please proceed with caution. To update these attributes, please open a support ticket.",
),
Description: "Git Instance resource schema.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: descriptions["id"],
@ -120,6 +134,35 @@ func (g *gitResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *
validate.NoSeparator(),
},
},
"acl": schema.ListAttribute{
Description: descriptions["acl"],
PlanModifiers: []planmodifier.List{
listplanmodifier.RequiresReplace(),
},
ElementType: types.StringType,
Optional: true,
Computed: true,
},
"consumed_disk": schema.StringAttribute{
Description: descriptions["consumed_disk"],
Computed: true,
},
"consumed_object_storage": schema.StringAttribute{
Description: descriptions["consumed_object_storage"],
Computed: true,
},
"created": schema.StringAttribute{
Description: descriptions["created"],
Computed: true,
},
"flavor": schema.StringAttribute{
Description: descriptions["flavor"],
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Optional: true,
Computed: true,
},
"name": schema.StringAttribute{
Description: descriptions["name"],
Required: true,
@ -158,9 +201,15 @@ func (g *gitResource) Create(ctx context.Context, req resource.CreateRequest, re
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_name", instanceName)
payload, diags := toCreatePayload(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Create the new git instance via the API client.
gitInstanceResp, err := g.client.CreateInstance(ctx, projectId).
CreateInstancePayload(git.CreateInstancePayload{Name: &instanceName}).
CreateInstancePayload(payload).
Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating git instance", fmt.Sprintf("Calling API: %v", err))
@ -174,7 +223,7 @@ func (g *gitResource) Create(ctx context.Context, req resource.CreateRequest, re
return
}
err = mapFields(gitInstanceResp, &model)
err = mapFields(ctx, gitInstanceResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating git instance", fmt.Sprintf("Mapping fields: %v", err))
return
@ -216,7 +265,7 @@ func (g *gitResource) Read(ctx context.Context, req resource.ReadRequest, resp *
return
}
err = mapFields(gitInstanceResp, &model)
err = mapFields(ctx, gitInstanceResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading git instance", fmt.Sprintf("Processing API response: %v", err))
return
@ -295,7 +344,7 @@ func (g *gitResource) ImportState(ctx context.Context, req resource.ImportStateR
}
// mapFields maps a Git response to the model.
func mapFields(resp *git.Instance, model *Model) error {
func mapFields(ctx context.Context, resp *git.Instance, model *Model) error {
if resp == nil {
return fmt.Errorf("response input is nil")
}
@ -307,12 +356,58 @@ func mapFields(resp *git.Instance, model *Model) error {
return fmt.Errorf("git instance id not present")
}
aclList := types.ListNull(types.StringType)
var diags diag.Diagnostics
if resp.Acl != nil && len(*resp.Acl) > 0 {
aclList, diags = types.ListValueFrom(ctx, types.StringType, resp.Acl)
if diags.HasError() {
return fmt.Errorf("mapping ACL: %w", core.DiagsToError(diags))
}
}
model.Created = types.StringNull()
if resp.Created != nil && resp.Created.String() != "" {
model.Created = types.StringValue(resp.Created.String())
}
// Build the ID by combining the project ID and instance id and assign the model's fields.
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), *resp.Id)
model.Url = types.StringPointerValue(resp.Url)
model.Name = types.StringPointerValue(resp.Name)
model.ACL = aclList
model.ConsumedDisk = types.StringPointerValue(resp.ConsumedDisk)
model.ConsumedObjectStorage = types.StringPointerValue(resp.ConsumedObjectStorage)
model.Flavor = types.StringPointerValue(resp.Flavor)
model.InstanceId = types.StringPointerValue(resp.Id)
model.Name = types.StringPointerValue(resp.Name)
model.Url = types.StringPointerValue(resp.Url)
model.Version = types.StringPointerValue(resp.Version)
return nil
}
// toCreatePayload creates the payload to create a git instance
func toCreatePayload(ctx context.Context, model *Model) (git.CreateInstancePayload, diag.Diagnostics) {
diags := diag.Diagnostics{}
if model == nil {
return git.CreateInstancePayload{}, diags
}
payload := git.CreateInstancePayload{
Name: model.Name.ValueStringPointer(),
}
if !(model.ACL.IsNull() || model.ACL.IsUnknown()) {
var acl []string
aclDiags := model.ACL.ElementsAs(ctx, &acl, false)
diags.Append(aclDiags...)
if !aclDiags.HasError() {
payload.Acl = &acl
}
}
if !(model.Flavor.IsNull() || model.Flavor.IsUnknown()) {
payload.Flavor = model.Flavor.ValueStringPointer()
}
return payload, diags
}

View file

@ -1,79 +1,225 @@
package instance
import (
"context"
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/uuid"
"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/git"
)
var (
testInstanceId = uuid.New().String()
testProjectId = uuid.New().String()
)
func TestMapFields(t *testing.T) {
createdTime, err := time.Parse("2006-01-02 15:04:05 -0700 MST", "2025-01-01 00:00:00 +0000 UTC")
if err != nil {
t.Fatalf("failed to parse test time: %v", err)
}
tests := []struct {
description string
input *git.Instance
expected Model
expected *Model
isValid bool
}{
{
"default_values",
&git.Instance{
Created: nil,
Id: utils.Ptr("id"),
Name: utils.Ptr("foo"),
Url: utils.Ptr("https://foo.com"),
Version: utils.Ptr("v0.0.1"),
description: "minimal_input_name_only",
input: &git.Instance{
Id: utils.Ptr(testInstanceId),
Name: utils.Ptr("git-min-instance"),
},
Model{
Id: types.StringValue("pid,id"),
ProjectId: types.StringValue("pid"),
InstanceId: types.StringValue("id"),
Name: types.StringValue("foo"),
Url: types.StringValue("https://foo.com"),
Version: types.StringValue("v0.0.1"),
expected: &Model{
Id: types.StringValue(fmt.Sprintf("%s,%s", testProjectId, testInstanceId)),
ProjectId: types.StringValue(testProjectId),
InstanceId: types.StringValue(testInstanceId),
Name: types.StringValue("git-min-instance"),
ACL: types.ListNull(types.StringType),
Flavor: types.StringNull(),
Url: types.StringNull(),
Version: types.StringNull(),
Created: types.StringNull(),
ConsumedDisk: types.StringNull(),
ConsumedObjectStorage: types.StringNull(),
},
true,
isValid: true,
},
{
"nil_response",
nil,
Model{},
false,
},
{
"nil_response_2",
&git.Instance{},
Model{},
false,
},
{
"no_id",
&git.Instance{
Name: utils.Ptr("foo"),
description: "full_input_with_acl_and_flavor",
input: &git.Instance{
Acl: &[]string{"192.168.0.0/24"},
ConsumedDisk: utils.Ptr("1.00 GB"),
ConsumedObjectStorage: utils.Ptr("2.00 GB"),
Created: &createdTime,
Flavor: utils.Ptr("git-100"),
Id: utils.Ptr(testInstanceId),
Name: utils.Ptr("git-full-instance"),
Url: utils.Ptr("https://git-full-instance.git.onstackit.cloud"),
Version: utils.Ptr("v1.9.1"),
},
Model{},
false,
expected: &Model{
Id: types.StringValue(fmt.Sprintf("%s,%s", testProjectId, testInstanceId)),
ProjectId: types.StringValue(testProjectId),
InstanceId: types.StringValue(testInstanceId),
Name: types.StringValue("git-full-instance"),
ACL: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("192.168.0.0/24")}),
Flavor: types.StringValue("git-100"),
Url: types.StringValue("https://git-full-instance.git.onstackit.cloud"),
Version: types.StringValue("v1.9.1"),
Created: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
ConsumedDisk: types.StringValue("1.00 GB"),
ConsumedObjectStorage: types.StringValue("2.00 GB"),
},
isValid: true,
},
{
description: "empty_acls",
input: &git.Instance{
Id: utils.Ptr(testInstanceId),
Name: utils.Ptr("git-empty-acl"),
Acl: &[]string{},
},
expected: &Model{
Id: types.StringValue(fmt.Sprintf("%s,%s", testProjectId, testInstanceId)),
ProjectId: types.StringValue(testProjectId),
InstanceId: types.StringValue(testInstanceId),
Name: types.StringValue("git-empty-acl"),
ACL: types.ListNull(types.StringType),
Flavor: types.StringNull(),
Url: types.StringNull(),
Version: types.StringNull(),
Created: types.StringNull(),
ConsumedDisk: types.StringNull(),
ConsumedObjectStorage: types.StringNull(),
},
isValid: true,
},
{
description: "nil_instance",
input: nil,
expected: nil,
isValid: false,
},
{
description: "empty_instance",
input: &git.Instance{},
expected: nil,
isValid: false,
},
{
description: "missing_id",
input: &git.Instance{
Name: utils.Ptr("git-missing-id"),
},
expected: nil,
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
state := &Model{
ProjectId: tt.expected.ProjectId,
}
err := mapFields(tt.input, state)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
state := &Model{}
if tt.expected != nil {
state.ProjectId = tt.expected.ProjectId
}
err := mapFields(context.Background(), tt.input, state)
if tt.isValid && err != nil {
t.Fatalf("Should not have failed: %v", err)
t.Fatalf("expected success, got error: %v", err)
}
if !tt.isValid && err == nil {
t.Fatalf("expected error, got nil")
}
if tt.isValid {
diff := cmp.Diff(state, &tt.expected)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
if diff := cmp.Diff(tt.expected, state); diff != "" {
t.Errorf("unexpected diff (-want +got):\n%s", diff)
}
}
})
}
}
func TestToCreatePayload(t *testing.T) {
tests := []struct {
description string
input *Model
expected git.CreateInstancePayload
expectError bool
}{
{
description: "default values",
input: &Model{
Name: types.StringValue("example-instance"),
Flavor: types.StringNull(),
ACL: types.ListNull(types.StringType),
},
expected: git.CreateInstancePayload{
Name: utils.Ptr("example-instance"),
},
expectError: false,
},
{
description: "simple values with ACL and Flavor",
input: &Model{
Name: types.StringValue("my-instance"),
Flavor: types.StringValue("git-100"),
ACL: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("10.0.0.1"),
types.StringValue("10.0.0.2"),
}),
},
expected: git.CreateInstancePayload{
Name: utils.Ptr("my-instance"),
Flavor: utils.Ptr("git-100"),
Acl: &[]string{"10.0.0.1", "10.0.0.2"},
},
expectError: false,
},
{
description: "empty ACL still valid",
input: &Model{
Name: types.StringValue("my-instance"),
Flavor: types.StringValue("git-101"),
ACL: types.ListValueMust(types.StringType, []attr.Value{}),
},
expected: git.CreateInstancePayload{
Name: utils.Ptr("my-instance"),
Flavor: utils.Ptr("git-101"),
Acl: &[]string{},
},
expectError: false,
},
{
description: "nil input model",
input: nil,
expected: git.CreateInstancePayload{},
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
output, diags := toCreatePayload(context.Background(), tt.input)
if tt.expectError && !diags.HasError() {
t.Fatalf("expected diagnostics error but got none")
}
if !tt.expectError && diags.HasError() {
t.Fatalf("unexpected diagnostics error: %v", diags)
}
if diff := cmp.Diff(tt.expected, output); diff != "" {
t.Fatalf("unexpected payload (-want +got):\n%s", diff)
}
})
}
}

View file

@ -0,0 +1,14 @@
variable "project_id" {}
variable "name" {}
variable "acl" {}
variable "flavor" {}
resource "stackit_git" "git" {
project_id = var.project_id
name = var.name
acl = [
var.acl
]
flavor = var.flavor
}

View file

@ -4,5 +4,5 @@ variable "name" {}
resource "stackit_git" "git" {
project_id = var.project_id
name = var.name
name = var.name
}