From a60b1db1f4fd3e7228101e4bed5a24dec648355a Mon Sep 17 00:00:00 2001 From: "Marcel S. Henselin" Date: Tue, 27 Jan 2026 09:58:06 +0100 Subject: [PATCH] chore: work save --- .github/workflows/publish.yaml | 135 ++++++++++++++++++ sample/postgres/postresql.tf | 37 ----- sample/sqlserver/sqlserver.tf | 55 +++++-- .../postgresflexalpha/instance/resource.go | 68 +++++++-- .../instance/planModifiers.yaml | 120 ++++++++++++++++ .../sqlserverflexalpha/instance/resource.go | 2 +- .../resources_gen/instance_resource_gen.go | 8 +- stackit/provider.go | 2 + 8 files changed, 364 insertions(+), 63 deletions(-) create mode 100644 .github/workflows/publish.yaml create mode 100644 stackit/internal/services/sqlserverflexalpha/instance/planModifiers.yaml diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 00000000..151a5178 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,135 @@ +name: Publish + +on: + pull_request: + workflow_dispatch: + push: + tags: + - 'v0.*' + +env: + GO_VERSION: "1.25" + CODE_COVERAGE_FILE_NAME: "coverage.out" # must be the same as in Makefile + CODE_COVERAGE_ARTIFACT_NAME: "code-coverage" + +jobs: + main: + name: prepare + runs-on: ubuntu-latest + permissions: + actions: read # Required to identify workflow run. + checks: write # Required to add status summary. + contents: read # Required to checkout repository. + pull-requests: write # Required to add PR comment. + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v6 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_wrapper: false + # terraform_version: "1.1.7" + +# - run: terraform init + +# - id: plan +# run: terraform plan -no-color + +# - run: echo ${{ steps.plan.outputs.stdout }} +# - run: echo ${{ steps.plan.outputs.stderr }} +# - run: echo ${{ steps.plan.outputs.exitcode }} + + # Run plan by default, or apply on merge. + - uses: op5dev/tf-via-pr@v13 + with: + working-directory: path/to/directory + command: ${{ github.event_name == 'push' && 'apply' || 'plan' }} + arg-lock: ${{ github.event_name == 'push' }} + arg-backend-config: env/dev.tfbackend + arg-var-file: env/dev.tfvars + arg-workspace: dev-use1 + plan-encrypt: ${{ secrets.PASSPHRASE }} + +# - name: "Ensure docs are up-to-date" +# if: ${{ github.event_name == 'pull_request' }} +# run: ./scripts/check-docs.sh +# continue-on-error: true + +# - name: "Run go mod tidy" +# if: ${{ github.event_name == 'pull_request' }} +# run: go mod tidy + + - name: golangci-lint + uses: golangci/golangci-lint-action@v9 + with: + version: v2.7 + args: --config=golang-ci.yaml --allow-parallel-runners --timeout=5m + +# - name: Lint +# run: make lint + +# - name: Test +# run: make test + +# - name: Archive code coverage results +# uses: actions/upload-artifact@v4 +# with: +# name: ${{ env.CODE_COVERAGE_ARTIFACT_NAME }} +# path: "stackit/${{ env.CODE_COVERAGE_FILE_NAME }}" + + + config: + name: Check GoReleaser config + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Check GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + args: check + + code_coverage: + name: "Code coverage report" + if: github.event_name == 'pull_request' # Do not run when workflow is triggered by push to main branch + runs-on: ubuntu-latest + needs: main + permissions: + contents: read + actions: read # to download code coverage results from "main" job + pull-requests: write # write permission needed to comment on PR + steps: + - name: Check new code coverage + uses: fgrosse/go-coverage-report@v1.2.0 + continue-on-error: true # Add this line to prevent pipeline failures in forks + with: + coverage-artifact-name: ${{ env.CODE_COVERAGE_ARTIFACT_NAME }} + coverage-file-name: ${{ env.CODE_COVERAGE_FILE_NAME }} + root-package: 'tfregistry.sysops.stackit.rocks/mhenselin/stackitprivatepreview' + + publish: + name: "Publish artifact" + runs-on: ubuntu-latest + steps: + - name: Set up S3cmd cli tool + uses: s3-actions/s3cmd@v2.0.1 + with: + provider: aws # default is linode + region: 'eu01' + access_key: ${{ secrets.S3_ACCESS_KEY }} + secret_key: ${{ secrets.S3_SECRET_KEY }} + + - name: Interact with object storage + run: | + s3cmd ls + # s3cmd sync --recursive --acl-public dist s3://awesome.blog/ + # s3cmd put dist/style.css --mime-type 'text/css' --acl-public s3://awesome.blog/style.css + s3cmd info s3://awesome.blog diff --git a/sample/postgres/postresql.tf b/sample/postgres/postresql.tf index cbe34a39..44c2993e 100644 --- a/sample/postgres/postresql.tf +++ b/sample/postgres/postresql.tf @@ -35,43 +35,6 @@ resource "stackitprivatepreview_postgresflexalpha_instance" "msh-sna-pe-example" version = 14 } -resource "stackitprivatepreview_postgresflexalpha_instance" "import_for_deletion" { - project_id = var.project_id - name = "mshpetest2" - backup_schedule = "0 0 * * *" - retention_days = 45 - flavor_id = data.stackitprivatepreview_postgresflexalpha_flavor.pgsql_flavor.flavor_id - replicas = 1 - storage = { - # class = "premium-perf2-stackit" - performance_class = "premium-perf2-stackit" - size = 10 - } - encryption = { - # key_id = stackit_kms_key.key.key_id - # keyring_id = stackit_kms_keyring.keyring.keyring_id - kek_key_id = var.key_id - kek_key_ring_id = var.keyring_id - kek_key_version = var.key_version - service_account = var.sa_email - } - network = { - acl = ["0.0.0.0/0", "193.148.160.0/19", "170.85.2.177/32"] - access_scope = "PUBLIC" - } - version = 14 -} - -import { - to = stackitprivatepreview_postgresflexalpha_instance.import_for_deletion - identity = { - project_id = "9a6b8cca-7d45-41b1-b5e5-68123db347af" - region = "eu01" - instance_id = "d52b5d4c-be3f-4c14-a107-330dab99fd2e" - } -} - - resource "stackitprivatepreview_postgresflexalpha_instance" "msh-sna-pe-example2" { project_id = var.project_id name = "mshpetest2-1" diff --git a/sample/sqlserver/sqlserver.tf b/sample/sqlserver/sqlserver.tf index 8de3e627..004dab43 100644 --- a/sample/sqlserver/sqlserver.tf +++ b/sample/sqlserver/sqlserver.tf @@ -33,7 +33,9 @@ resource "stackitprivatepreview_sqlserverflexalpha_instance" "sqlsrv" { #key_id = stackit_kms_key.key.key_id #keyring_id = stackit_kms_keyring.keyring.keyring_id #key_version = 1 - key_id = var.key_id + # key with scope public + key_id = "fe039bcf-8d7b-431a-801d-9e81371a6b7b" + # key_id = var.key_id keyring_id = var.keyring_id key_version = var.key_version service_account = var.sa_email @@ -44,6 +46,33 @@ resource "stackitprivatepreview_sqlserverflexalpha_instance" "sqlsrv" { } } +resource "stackitprivatepreview_sqlserverflexalpha_instance" "sqlsrv-nosna" { + project_id = var.project_id + name = "msh-example-instance-nosna" + backup_schedule = "0 3 * * *" + retention_days = 31 + flavor_id = data.stackitprivatepreview_sqlserverflexalpha_flavor.sqlserver_flavor.flavor_id + storage = { + class = "premium-perf2-stackit" + size = 50 + } + version = 2022 + encryption = { + #key_id = stackit_kms_key.key.key_id + #keyring_id = stackit_kms_keyring.keyring.keyring_id + #key_version = 1 + #key_id = var.key_id + key_id = "fe039bcf-8d7b-431a-801d-9e81371a6b7b" + keyring_id = var.keyring_id + key_version = var.key_version + service_account = var.sa_email + } + network = { + acl = ["0.0.0.0/0", "193.148.160.0/19"] + access_scope = "PUBLIC" + } +} + # data "stackitprivatepreview_sqlserverflexalpha_instance" "test" { # project_id = var.project_id # instance_id = var.instance_id @@ -54,17 +83,17 @@ resource "stackitprivatepreview_sqlserverflexalpha_instance" "sqlsrv" { # value = data.stackitprivatepreview_sqlserverflexalpha_instance.test # } -resource "stackitprivatepreview_sqlserverflexalpha_user" "ptlsdbadminuser" { - project_id = var.project_id - instance_id = stackitprivatepreview_sqlserverflexalpha_instance.sqlsrv.instance_id - username = var.db_admin_username - roles = ["##STACKIT_LoginManager##", "##STACKIT_DatabaseManager##"] -} +# resource "stackitprivatepreview_sqlserverflexalpha_user" "ptlsdbadminuser" { +# project_id = var.project_id +# instance_id = stackitprivatepreview_sqlserverflexalpha_instance.sqlsrv.instance_id +# username = var.db_admin_username +# roles = ["##STACKIT_LoginManager##", "##STACKIT_DatabaseManager##"] +# } -resource "stackitprivatepreview_sqlserverflexalpha_user" "ptlsdbuser" { - project_id = var.project_id - instance_id = stackitprivatepreview_sqlserverflexalpha_instance.sqlsrv.instance_id - username = var.db_username - roles = ["##STACKIT_LoginManager##"] -} +# resource "stackitprivatepreview_sqlserverflexalpha_user" "ptlsdbuser" { +# project_id = var.project_id +# instance_id = stackitprivatepreview_sqlserverflexalpha_instance.sqlsrv.instance_id +# username = var.db_username +# roles = ["##STACKIT_LoginManager##"] +# } diff --git a/stackit/internal/services/postgresflexalpha/instance/resource.go b/stackit/internal/services/postgresflexalpha/instance/resource.go index 951d5cf1..8acd2ff9 100644 --- a/stackit/internal/services/postgresflexalpha/instance/resource.go +++ b/stackit/internal/services/postgresflexalpha/instance/resource.go @@ -283,7 +283,8 @@ func modelToCreateInstancePayload(netAcl []string, model postgresflexalpha.Insta // Read refreshes the Terraform state with the latest data. func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform - functionError := "read instance failed" + functionErrorSummary := "read instance failed" + var model postgresflexalpha.InstanceModel diags := req.State.Get(ctx, &model) resp.Diagnostics.Append(diags...) @@ -300,9 +301,47 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r ctx = core.InitProviderContext(ctx) - projectId := model.ProjectId.ValueString() - instanceId := model.InstanceId.ValueString() - region := r.providerData.GetRegionWithOverride(model.Region) + // region := r.providerData.GetRegionWithOverride(model.Region) + // instanceId := model.InstanceId.ValueString() + + var projectId string + if !model.ProjectId.IsNull() && !model.ProjectId.IsUnknown() { + projectId = model.ProjectId.ValueString() + } else { + if identityData.ProjectID.IsNull() || identityData.ProjectID.IsUnknown() { + core.LogAndAddError(ctx, &resp.Diagnostics, functionErrorSummary, "project_id not found in config") + return + } + projectId = identityData.ProjectID.ValueString() + } + + var region string + if !model.Region.IsNull() && !model.Region.IsUnknown() { + region = r.providerData.GetRegionWithOverride(model.Region) + } else { + if identityData.Region.IsNull() || identityData.Region.IsUnknown() { + core.LogAndAddError(ctx, &resp.Diagnostics, functionErrorSummary, "region not found in config") + return + } + region = r.providerData.GetRegionWithOverride(identityData.Region) + } + + var instanceId string + if !model.InstanceId.IsNull() && !model.InstanceId.IsUnknown() { + instanceId = model.InstanceId.ValueString() + } else { + if identityData.InstanceID.IsNull() || identityData.InstanceID.IsUnknown() { + core.LogAndAddError(ctx, &resp.Diagnostics, functionErrorSummary, "instance_id not found in config") + return + } + region = identityData.Region.ValueString() + } + + tflog.Info(ctx, "Reading instance model", map[string]interface{}{ + "projectId": projectId, + "region": region, + "instanceId": instanceId, + }) ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) @@ -314,7 +353,7 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r resp.State.RemoveResource(ctx) return } - core.LogAndAddError(ctx, &resp.Diagnostics, functionError, err.Error()) + core.LogAndAddError(ctx, &resp.Diagnostics, functionErrorSummary, err.Error()) return } @@ -322,7 +361,7 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r respInstanceID, ok := instanceResp.GetIdOk() if !ok { - core.LogAndAddError(ctx, &resp.Diagnostics, functionError, "response provided no ID") + core.LogAndAddError(ctx, &resp.Diagnostics, functionErrorSummary, "response provided no ID") return } if !model.InstanceId.IsUnknown() && !model.InstanceId.IsNull() { @@ -330,7 +369,7 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r core.LogAndAddError( ctx, &resp.Diagnostics, - functionError, + functionErrorSummary, "ID in response did not match ID in state", ) return @@ -339,7 +378,7 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r err = mapGetInstanceResponseToModel(ctx, &model, instanceResp) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, functionError, fmt.Sprintf("Processing API payload: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, functionErrorSummary, fmt.Sprintf("Processing API payload: %v", err)) return } @@ -511,6 +550,9 @@ func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteReques // The expected format of the resource import identifier is: project_id,region,instance_id func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { tflog.Debug(ctx, "ImportState called with id:", map[string]interface{}{"id": req.ID}) + + ctx = core.InitProviderContext(ctx) + if req.ID != "" { idParts := strings.Split(req.ID, core.Separator) @@ -534,6 +576,16 @@ func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportS return } + resp.Diagnostics.Append( + resp.State.SetAttribute( + ctx, + path.Root("id"), + utils.BuildInternalTerraformId( + identityData.ProjectID.ValueString(), + identityData.Region.ValueString(), + identityData.InstanceID.ValueString(), + ), + )...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), identityData.ProjectID.ValueString())...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), identityData.Region.ValueString())...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), identityData.InstanceID.ValueString())...) diff --git a/stackit/internal/services/sqlserverflexalpha/instance/planModifiers.yaml b/stackit/internal/services/sqlserverflexalpha/instance/planModifiers.yaml new file mode 100644 index 00000000..2e72ba16 --- /dev/null +++ b/stackit/internal/services/sqlserverflexalpha/instance/planModifiers.yaml @@ -0,0 +1,120 @@ +fields: + - name: 'id' + modifiers: + - 'UseStateForUnknown' + + - name: 'instance_id' + validators: + - validate.NoSeparator + - validate.UUID + modifiers: + - 'UseStateForUnknown' + + - name: 'project_id' + validators: + - validate.NoSeparator + - validate.UUID + modifiers: + - 'UseStateForUnknown' + - 'RequiresReplace' + + - name: 'name' + modifiers: + - 'UseStateForUnknown' + - 'RequiresReplace' + + - name: 'backup_schedule' + modifiers: + - 'UseStateForUnknown' + + - name: 'encryption.kek_key_id' + validators: + - validate.NoSeparator + modifiers: + - 'RequiresReplace' + + - name: 'encryption.kek_key_version' + validators: + - validate.NoSeparator + modifiers: + - 'RequiresReplace' + + - name: 'encryption.kek_key_ring_id' + validators: + - validate.NoSeparator + modifiers: + - 'RequiresReplace' + + - name: 'encryption.service_account' + validators: + - validate.NoSeparator + modifiers: + - 'RequiresReplace' + + - name: 'network.access_scope' + validators: + - validate.NoSeparator + modifiers: + - 'UseStateForUnknown' + - 'RequiresReplace' + + - name: 'network.acl' + modifiers: + - 'UseStateForUnknown' + + - name: 'network.instance_address' + modifiers: + - 'UseStateForUnknown' + + - name: 'network.router_address' + modifiers: + - 'UseStateForUnknown' + + - name: 'status' + modifiers: + - 'UseStateForUnknown' + + - name: 'region' + modifiers: + - 'RequiresReplace' + + - name: 'retention_days' + modifiers: + - 'UseStateForUnknown' + + - name: 'edition' + modifiers: + - 'UseStateForUnknown' + - 'RequiresReplace' + + - name: 'version' + modifiers: + - 'UseStateForUnknown' + - 'RequiresReplace' + + - name: 'replicas' + modifiers: + - 'UseStateForUnknown' + - 'RequiresReplace' + + - name: 'storage' + modifiers: + - 'UseStateForUnknown' + + - name: 'storage.class' + modifiers: + - 'UseStateForUnknown' + - 'RequiresReplace' + + - name: 'storage.size' + modifiers: + - 'UseStateForUnknown' + + - name: 'flavor_id' + modifiers: + - 'UseStateForUnknown' + - 'RequiresReplace' + + - name: 'is_deletable' + modifiers: + - 'UseStateForUnknown' diff --git a/stackit/internal/services/sqlserverflexalpha/instance/resource.go b/stackit/internal/services/sqlserverflexalpha/instance/resource.go index d201e1cc..8569af36 100644 --- a/stackit/internal/services/sqlserverflexalpha/instance/resource.go +++ b/stackit/internal/services/sqlserverflexalpha/instance/resource.go @@ -339,7 +339,7 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r Description: descriptions["status"], }, "encryption": schema.SingleNestedAttribute{ - Required: true, + Optional: true, PlanModifiers: []planmodifier.Object{ objectplanmodifier.RequiresReplace(), objectplanmodifier.UseStateForUnknown(), diff --git a/stackit/internal/services/sqlserverflexalpha/instance/resources_gen/instance_resource_gen.go b/stackit/internal/services/sqlserverflexalpha/instance/resources_gen/instance_resource_gen.go index 58cbf8d1..73293905 100644 --- a/stackit/internal/services/sqlserverflexalpha/instance/resources_gen/instance_resource_gen.go +++ b/stackit/internal/services/sqlserverflexalpha/instance/resources_gen/instance_resource_gen.go @@ -29,22 +29,22 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema { "encryption": schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ "kek_key_id": schema.StringAttribute{ - Required: true, + Optional: true, Description: "The key identifier", MarkdownDescription: "The key identifier", }, "kek_key_ring_id": schema.StringAttribute{ - Required: true, + Optional: true, Description: "The keyring identifier", MarkdownDescription: "The keyring identifier", }, "kek_key_version": schema.StringAttribute{ - Required: true, + Optional: true, Description: "The key version", MarkdownDescription: "The key version", }, "service_account": schema.StringAttribute{ - Required: true, + Optional: true, }, }, CustomType: EncryptionType{ diff --git a/stackit/provider.go b/stackit/provider.go index 34e7dc5c..9d37dda4 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/core" "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/features" postgresFlexAlphaDatabase "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/database" @@ -320,6 +321,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro // Configure prepares a stackit API client for data sources and resources. func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + tflog.Info(ctx, "Configuring provider client") // Retrieve provider data and configuration var providerConfig providerModel diags := req.Config.Get(ctx, &providerConfig)