Skip to content
Draft
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
103 changes: 103 additions & 0 deletions .github/workflows/tmp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Ignore this file, it is a temporary workflow for testing purposes.

name: Build and Validate Provider

on:
push:
branches:
- '**'
workflow_dispatch:

jobs:
build-and-test:
name: Build Provider & Test Plan
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write

steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'

- name: Build Provider
run: |
go build -o terraform-provider-stackit
chmod +x terraform-provider-stackit
echo "BINARY_PATH=${GITHUB_WORKSPACE}" >> $GITHUB_ENV

- name: Configure Terraform Dev Overrides
run: |
cat <<EOF > ~/.terraformrc
provider_installation {
dev_overrides {
"stackitcloud/stackit" = "${{ env.BINARY_PATH }}"
}
# Para otros providers (random, null, etc), usa el registro normal
direct {}
}
EOF

echo "Terraform RC content:"
cat ~/.terraformrc

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_wrapper: false

- name: Create Test Configuration
run: |
cat <<EOF > main.tf
terraform {
required_providers {
stackit = {
source = "stackitcloud/stackit"
}
}
}

provider "stackit" {
default_region = "eu01"
enable_beta_resources = true
service_account_custom_endpoint = "https://service-account.api.qa.stackit.cloud"
token_custom_endpoint = "https://accounts.qa.stackit.cloud/oauth/v2/token"
service_account_email = "[email protected]"
}

resource "stackit_service_account" "sa" {
project_id = "62675838-7758-4b99-a4eb-84b20ac8626b"
name = "terraform-wif-ci"
}
EOF

- name: Terraform Plan
run: |
# Inicializamos sin backend para pruebas rápidas
terraform init -backend=false

# Ejecutamos plan. Si el provider carga y la API responde, esto pasará.
terraform plan -out=tfplan
env:
STACKIT_USE_OIDC: "1"

- name: Terraform Apply
run: terraform apply -auto-approve tfplan
env:
STACKIT_USE_OIDC: "1"

- name: Terraform Destroy
if: always()
run: |
if [ -f terraform.tfstate ]; then
terraform destroy -auto-approve
else
echo "No state file found, skipping destroy."
fi
env:
STACKIT_USE_OIDC: "1"
7 changes: 6 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de
- `mongodbflex_custom_endpoint` (String) Custom endpoint for the MongoDB Flex service
- `objectstorage_custom_endpoint` (String) Custom endpoint for the Object Storage service
- `observability_custom_endpoint` (String) Custom endpoint for the Observability service
- `oidc_request_token` (String) The bearer token for the request to the OIDC provider. For use when authenticating as a Service Account using OpenID Connect.
- `oidc_request_url` (String) The URL for the OIDC provider from which to request an ID token. For use when authenticating as a Service Account using OpenID Connect.
- `opensearch_custom_endpoint` (String) Custom endpoint for the OpenSearch service
- `postgresflex_custom_endpoint` (String) Custom endpoint for the PostgresFlex service
- `private_key` (String) Private RSA key used for authentication, relevant for the key flow. It takes precedence over the private key that is included in the service account key.
Expand All @@ -183,7 +185,9 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de
- `server_backup_custom_endpoint` (String) Custom endpoint for the Server Backup service
- `server_update_custom_endpoint` (String) Custom endpoint for the Server Update service
- `service_account_custom_endpoint` (String) Custom endpoint for the Service Account service
- `service_account_email` (String, Deprecated) Service account email. It can also be set using the environment variable STACKIT_SERVICE_ACCOUNT_EMAIL. It is required if you want to use the resource manager project resource.
- `service_account_email` (String) Service account email. It can also be set using the environment variable STACKIT_SERVICE_ACCOUNT_EMAIL. It is required if you want to use the resource manager project resource.
- `service_account_federated_token` (String) The OIDC ID token for use when authenticating as a Service Account using OpenID Connect.
- `service_account_federated_token_path` (String) Path for workload identity assertion. It can also be set using the environment variable STACKIT_FEDERATED_TOKEN_FILE.
- `service_account_key` (String) Service account key used for authentication. If set, the key flow will be used to authenticate all operations.
- `service_account_key_path` (String) Path for the service account key used for authentication. If set, the key flow will be used to authenticate all operations.
- `service_account_token` (String, Deprecated) Token used for authentication. If set, the token flow will be used to authenticate all operations.
Expand All @@ -192,3 +196,4 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de
- `ske_custom_endpoint` (String) Custom endpoint for the Kubernetes Engine (SKE) service
- `sqlserverflex_custom_endpoint` (String) Custom endpoint for the SQL Server Flex service
- `token_custom_endpoint` (String) Custom endpoint for the token API, which is used to request access tokens when using the key flow
- `use_oidc` (Boolean) Should OIDC be used for Authentication? This can also be sourced from the `STACKIT_USE_OIDC` Environment Variable. Defaults to `false`.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ require (
golang.org/x/mod v0.31.0
)

replace github.com/stackitcloud/stackit-sdk-go/core => github.com/JorTurFer/stackit-sdk-go/core v0.0.0-20260107172957-5e41dc32d226

require (
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/kr/text v0.2.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/JorTurFer/stackit-sdk-go/core v0.0.0-20260107172957-5e41dc32d226 h1:AgqbJ2DrS+Be2Qrgzu2cQYdCFpktG6oo3cFK6XHpBrA=
github.com/JorTurFer/stackit-sdk-go/core v0.0.0-20260107172957-5e41dc32d226/go.mod h1:fqto7M82ynGhEnpZU6VkQKYWYoFG5goC076JWXTUPRQ=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
Expand Down Expand Up @@ -149,8 +151,6 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/stackitcloud/stackit-sdk-go/core v0.20.1 h1:odiuhhRXmxvEvnVTeZSN9u98edvw2Cd3DcnkepncP3M=
github.com/stackitcloud/stackit-sdk-go/core v0.20.1/go.mod h1:fqto7M82ynGhEnpZU6VkQKYWYoFG5goC076JWXTUPRQ=
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.9.0 h1:7ZKd3b+E/R4TEVShLTXxx5FrsuDuJBOyuVOuKTMa4mo=
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.9.0/go.mod h1:/FoXa6hF77Gv8brrvLBCKa5ie1Xy9xn39yfHwaln9Tw=
github.com/stackitcloud/stackit-sdk-go/services/cdn v1.6.0 h1:Q+qIdejeMsYMkbtVoI9BpGlKGdSVFRBhH/zj44SP8TM=
Expand Down
21 changes: 11 additions & 10 deletions stackit/internal/conversion/conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package conversion

import (
"context"
"crypto/tls"
"net/http"
"reflect"
"testing"

Expand Down Expand Up @@ -306,6 +308,9 @@ func TestParseProviderData(t *testing.T) {
}

func TestParseEphemeralProviderData(t *testing.T) {
var randomRoundTripper http.RoundTripper = &http.Transport{
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS13},
}
type args struct {
providerData any
}
Expand Down Expand Up @@ -354,21 +359,17 @@ func TestParseEphemeralProviderData(t *testing.T) {
name: "valid provider data 2",
args: args{
providerData: core.EphemeralProviderData{
PrivateKey: "",
PrivateKeyPath: "/home/dev/foo/private-key.json",
ServiceAccountKey: "",
ServiceAccountKeyPath: "/home/dev/foo/key.json",
TokenCustomEndpoint: "",
ProviderData: core.ProviderData{
RoundTripper: randomRoundTripper,
},
},
},
want: want{
ok: true,
providerData: core.EphemeralProviderData{
PrivateKey: "",
PrivateKeyPath: "/home/dev/foo/private-key.json",
ServiceAccountKey: "",
ServiceAccountKeyPath: "/home/dev/foo/key.json",
TokenCustomEndpoint: "",
ProviderData: core.ProviderData{
RoundTripper: randomRoundTripper,
},
},
},
wantErr: false,
Expand Down
8 changes: 1 addition & 7 deletions stackit/internal/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,11 @@ const (

type EphemeralProviderData struct {
ProviderData

PrivateKey string
PrivateKeyPath string
ServiceAccountKey string
ServiceAccountKeyPath string
TokenCustomEndpoint string
}

type ProviderData struct {
RoundTripper http.RoundTripper
ServiceAccountEmail string // Deprecated: ServiceAccountEmail is not required and will be removed after 12th June 2025.
ServiceAccountEmail string
// Deprecated: Use DefaultRegion instead
Region string
DefaultRegion string
Expand Down
34 changes: 8 additions & 26 deletions stackit/internal/services/access_token/ephemeral_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ package access_token
import (
"context"
"fmt"
"net/http"

"github.com/hashicorp/terraform-plugin-framework/ephemeral"
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/auth"
"github.com/stackitcloud/stackit-sdk-go/core/clients"
"github.com/stackitcloud/stackit-sdk-go/core/config"
"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"
Expand All @@ -25,7 +24,7 @@ func NewAccessTokenEphemeralResource() ephemeral.EphemeralResource {
}

type accessTokenEphemeralResource struct {
keyAuthConfig config.Configuration
roundTripper http.RoundTripper
}

func (e *accessTokenEphemeralResource) Configure(ctx context.Context, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse) {
Expand All @@ -44,13 +43,7 @@ func (e *accessTokenEphemeralResource) Configure(ctx context.Context, req epheme
return
}

e.keyAuthConfig = config.Configuration{
ServiceAccountKey: ephemeralProviderData.ServiceAccountKey,
ServiceAccountKeyPath: ephemeralProviderData.ServiceAccountKeyPath,
PrivateKeyPath: ephemeralProviderData.PrivateKey,
PrivateKey: ephemeralProviderData.PrivateKeyPath,
TokenCustomUrl: ephemeralProviderData.TokenCustomEndpoint,
}
e.roundTripper = ephemeralProviderData.RoundTripper
}

type ephemeralTokenModel struct {
Expand Down Expand Up @@ -95,7 +88,7 @@ func (e *accessTokenEphemeralResource) Open(ctx context.Context, req ephemeral.O
return
}

accessToken, err := getAccessToken(&e.keyAuthConfig)
accessToken, err := getAccessToken(e.roundTripper)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Access token generation failed", err.Error())
return
Expand All @@ -105,28 +98,17 @@ func (e *accessTokenEphemeralResource) Open(ctx context.Context, req ephemeral.O
resp.Diagnostics.Append(resp.Result.Set(ctx, model)...)
}

// getAccessToken initializes authentication using the provided config and returns an access token via the KeyFlow mechanism.
func getAccessToken(keyAuthConfig *config.Configuration) (string, error) {
roundTripper, err := auth.KeyAuth(keyAuthConfig)
if err != nil {
return "", fmt.Errorf(
"failed to initialize authentication: %w. "+
"Make sure service account credentials are configured either in the provider configuration or via environment variables",
err,
)
}

// getAccessToken initializes authentication using the provided config
func getAccessToken(roundTripper http.RoundTripper) (string, error) {
// Type assert to access token functionality
client, ok := roundTripper.(*clients.KeyFlow)
client, ok := roundTripper.(clients.AuthFlow)
if !ok {
return "", fmt.Errorf("internal error: expected *clients.KeyFlow, but received a different implementation of http.RoundTripper")
return "", fmt.Errorf("internal error: expected *clients.AuthFlow, but received a different implementation of http.RoundTripper")
}

// Retrieve the access token
accessToken, err := client.GetAccessToken()
if err != nil {
return "", fmt.Errorf("error obtaining access token: %w", err)
}

return accessToken, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"testing"
"time"

"github.com/stackitcloud/stackit-sdk-go/core/auth"
"github.com/stackitcloud/stackit-sdk-go/core/clients"
"github.com/stackitcloud/stackit-sdk-go/core/config"
)
Expand All @@ -23,11 +24,10 @@ var testServiceAccountKey string
func startMockTokenServer() *httptest.Server {
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
resp := clients.TokenResponseBody{
AccessToken: "mock_access_token",
RefreshToken: "mock_refresh_token",
TokenType: "Bearer",
ExpiresIn: int(time.Now().Add(time.Hour).Unix()),
Scope: "mock_scope",
AccessToken: "mock_access_token",
TokenType: "Bearer",
ExpiresIn: int(time.Now().Add(time.Hour).Unix()),
Scope: "mock_scope",
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(resp)
Expand Down Expand Up @@ -235,7 +235,13 @@ func TestGetAccessToken(t *testing.T) {

cfg := tt.cfgFactory()

token, err := getAccessToken(cfg)
roundTripper, err := auth.SetupAuth(cfg)
if tt.expectError {
if err == nil {
t.Errorf("expected error generating round tripper for test case '%s'", tt.description)
}
}
token, err := getAccessToken(roundTripper)
if tt.expectError {
if err == nil {
t.Errorf("expected error but got none for test case '%s'", tt.description)
Expand Down
Loading
Loading