Skip to content

Commit 85281fa

Browse files
committed
review changes 3
Signed-off-by: Mauritz Uphoff <[email protected]>
1 parent 6134249 commit 85281fa

File tree

8 files changed

+375
-48
lines changed

8 files changed

+375
-48
lines changed

docs/ephemeral-resources/access_token.md

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,36 @@ page_title: "stackit_access_token Ephemeral Resource - stackit"
44
subcategory: ""
55
description: |-
66
Ephemeral resource that generates a short-lived STACKIT access token (JWT) using a service account key. A new token is generated each time the resource is evaluated, and it remains consistent for the duration of a Terraform operation. If a private key is not explicitly provided, the provider attempts to extract it from the service account key instead. Token generation logic prioritizes environment variables first, followed by provider configuration. Access tokens generated from service account keys expire after 60 minutes.
7+
~> This ephemeral-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.
78
---
89

910
# stackit_access_token (Ephemeral Resource)
1011

1112
Ephemeral resource that generates a short-lived STACKIT access token (JWT) using a service account key. A new token is generated each time the resource is evaluated, and it remains consistent for the duration of a Terraform operation. If a private key is not explicitly provided, the provider attempts to extract it from the service account key instead. Token generation logic prioritizes environment variables first, followed by provider configuration. Access tokens generated from service account keys expire after 60 minutes.
1213

14+
~> This ephemeral-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.
15+
1316
## Example Usage
1417

1518
```terraform
19+
provider "stackit" {
20+
default_region = "eu01"
21+
service_account_key_path = "/path/to/sa_key.json"
22+
}
23+
1624
ephemeral "stackit_access_token" "example" {}
1725
26+
locals {
27+
publicIpBody = {
28+
labels = {
29+
key = "value"
30+
}
31+
}
32+
}
33+
1834
// https://registry.terraform.io/providers/Mastercard/restapi/latest/docs
1935
provider "restapi" {
20-
uri = "https://iaas.api.eu01.stackit.cloud/"
36+
uri = "https://iaas.api.stackit.cloud"
2137
write_returns_object = true
2238
2339
headers = {
@@ -30,23 +46,40 @@ provider "restapi" {
3046
destroy_method = "DELETE"
3147
}
3248
33-
resource "restapi_object" "iaas_keypair" {
34-
path = "/v2/keypairs"
49+
resource "restapi_object" "public_ip" {
50+
path = "/v2/projects/${var.project_id}/regions/${var.region}/public-ips"
3551
36-
data = jsonencode({
37-
labels = {
38-
key = "testvalue"
39-
}
40-
name = "test-keypair-123"
41-
publicKey = file(chomp("~/.ssh/id_rsa.pub"))
42-
})
52+
data = jsonencode(local.publicIpBody)
4353
44-
id_attribute = "name"
54+
id_attribute = "id"
4555
read_method = "GET"
4656
create_method = "POST"
4757
update_method = "PATCH"
4858
destroy_method = "DELETE"
4959
}
60+
61+
// https://registry.terraform.io/providers/magodo/restful/latest/docs
62+
provider "restful" {
63+
base_url = "https://iaas.api.stackit.cloud"
64+
security = {
65+
http = {
66+
token = {
67+
token = ephemeral.stackit_access_token.example.access_token
68+
}
69+
}
70+
}
71+
}
72+
73+
resource "restful_resource" "public_ip" {
74+
path = "/v2/projects/${var.project_id}/regions/${var.region}/public-ips"
75+
data = jsonencode(local.publicIpBody)
76+
77+
read_path = "$(path)/$(body.id)"
78+
update_path = "$(path)/$(body.id)"
79+
update_method = "PATCH"
80+
delete_path = "$(path)/$(body.id)"
81+
delete_method = "DELETE"
82+
}
5083
```
5184

5285
<!-- schema generated by tfplugindocs -->
Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
1+
provider "stackit" {
2+
default_region = "eu01"
3+
service_account_key_path = "/path/to/sa_key.json"
4+
enable_beta_resources = true
5+
}
6+
17
ephemeral "stackit_access_token" "example" {}
28

3-
// https://registry.terraform.io/providers/Mastercard/restapi/latest/docs
9+
locals {
10+
stackit_api_base_url = "https://iaas.api.stackit.cloud"
11+
public_ip_path = "/v2/projects/${var.project_id}/regions/${var.region}/public-ips"
12+
13+
public_ip_payload = {
14+
labels = {
15+
key = "value"
16+
}
17+
}
18+
}
19+
20+
# Docs: https://registry.terraform.io/providers/Mastercard/restapi/latest
421
provider "restapi" {
5-
uri = "https://iaas.api.eu01.stackit.cloud/"
22+
uri = local.stackit_api_base_url
623
write_returns_object = true
724

825
headers = {
@@ -11,24 +28,41 @@ provider "restapi" {
1128
}
1229

1330
create_method = "POST"
14-
update_method = "PUT"
31+
update_method = "PATCH" # more likely in RESTful APIs than PUT
1532
destroy_method = "DELETE"
1633
}
1734

18-
resource "restapi_object" "iaas_keypair" {
19-
path = "/v2/keypairs"
20-
21-
data = jsonencode({
22-
labels = {
23-
key = "testvalue"
24-
}
25-
name = "test-keypair-123"
26-
publicKey = file(chomp("~/.ssh/id_rsa.pub"))
27-
})
35+
resource "restapi_object" "public_ip_restapi" {
36+
path = local.public_ip_path
37+
data = jsonencode(local.public_ip_payload)
2838

29-
id_attribute = "name"
39+
id_attribute = "id"
3040
read_method = "GET"
3141
create_method = "POST"
3242
update_method = "PATCH"
3343
destroy_method = "DELETE"
3444
}
45+
46+
# Docs: https://registry.terraform.io/providers/magodo/restful/latest
47+
provider "restful" {
48+
base_url = local.stackit_api_base_url
49+
50+
security = {
51+
http = {
52+
token = {
53+
token = ephemeral.stackit_access_token.example.access_token
54+
}
55+
}
56+
}
57+
}
58+
59+
resource "restful_resource" "public_ip_restful" {
60+
path = local.public_ip_path
61+
data = jsonencode(local.public_ip_payload)
62+
63+
read_path = "$(path)/$(body.id)"
64+
update_path = "$(path)/$(body.id)"
65+
update_method = "PATCH"
66+
delete_path = "$(path)/$(body.id)"
67+
delete_method = "DELETE"
68+
}

stackit/internal/core/core.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ import (
1515
type ResourceType string
1616

1717
const (
18-
Resource ResourceType = "resource"
19-
Datasource ResourceType = "datasource"
18+
Resource ResourceType = "resource"
19+
Datasource ResourceType = "datasource"
20+
EphemeralResource ResourceType = "ephemeral-resource"
2021

2122
// Separator used for concatenation of TF-internal resource ID
2223
Separator = ","
@@ -31,6 +32,7 @@ type EphemeralProviderData struct {
3132
ServiceAccountKey string
3233
ServiceAccountKeyPath string
3334
TokenCustomEndpoint string
35+
EnableBetaResources bool
3436
}
3537

3638
type ProviderData struct {

stackit/internal/services/access_token/ephemeral_resource.go

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/stackitcloud/stackit-sdk-go/core/config"
1313
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
1414
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
15+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
1516
)
1617

1718
var (
@@ -28,17 +29,27 @@ type accessTokenEphemeralResource struct {
2829
}
2930

3031
func (e *accessTokenEphemeralResource) Configure(ctx context.Context, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse) {
31-
providerData, ok := conversion.ParseEphemeralProviderData(ctx, req.ProviderData, &resp.Diagnostics)
32+
ephemeralProviderData, ok := conversion.ParseEphemeralProviderData(ctx, req.ProviderData, &resp.Diagnostics)
3233
if !ok {
3334
return
3435
}
3536

37+
features.CheckBetaResourcesEnabled(
38+
ctx,
39+
&core.ProviderData{EnableBetaResources: ephemeralProviderData.EnableBetaResources},
40+
&resp.Diagnostics,
41+
"stackit_access_token", "ephemeral_resource",
42+
)
43+
if resp.Diagnostics.HasError() {
44+
return
45+
}
46+
3647
e.keyAuthConfig = config.Configuration{
37-
ServiceAccountKey: providerData.ServiceAccountKey,
38-
ServiceAccountKeyPath: providerData.ServiceAccountKeyPath,
39-
PrivateKeyPath: providerData.PrivateKey,
40-
PrivateKey: providerData.PrivateKeyPath,
41-
TokenCustomUrl: providerData.TokenCustomEndpoint,
48+
ServiceAccountKey: ephemeralProviderData.ServiceAccountKey,
49+
ServiceAccountKeyPath: ephemeralProviderData.ServiceAccountKeyPath,
50+
PrivateKeyPath: ephemeralProviderData.PrivateKey,
51+
PrivateKey: ephemeralProviderData.PrivateKeyPath,
52+
TokenCustomUrl: ephemeralProviderData.TokenCustomEndpoint,
4253
}
4354
}
4455

@@ -52,11 +63,11 @@ func (e *accessTokenEphemeralResource) Metadata(_ context.Context, req ephemeral
5263

5364
func (e *accessTokenEphemeralResource) Schema(_ context.Context, _ ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) {
5465
resp.Schema = schema.Schema{
55-
Description: "Ephemeral resource that generates a short-lived STACKIT access token (JWT) using a service account key. " +
56-
"A new token is generated each time the resource is evaluated, and it remains consistent for the duration of a Terraform operation. " +
57-
"If a private key is not explicitly provided, the provider attempts to extract it from the service account key instead. " +
58-
"Token generation logic prioritizes environment variables first, followed by provider configuration. " +
59-
"Access tokens generated from service account keys expire after 60 minutes.",
66+
Description: features.AddBetaDescription("Ephemeral resource that generates a short-lived STACKIT access token (JWT) using a service account key. "+
67+
"A new token is generated each time the resource is evaluated, and it remains consistent for the duration of a Terraform operation. "+
68+
"If a private key is not explicitly provided, the provider attempts to extract it from the service account key instead. "+
69+
"Token generation logic prioritizes environment variables first, followed by provider configuration. "+
70+
"Access tokens generated from service account keys expire after 60 minutes.", core.EphemeralResource),
6071
Attributes: map[string]schema.Attribute{
6172
"access_token": schema.StringAttribute{
6273
Description: "JWT access token for STACKIT API authentication.",
@@ -75,26 +86,34 @@ func (e *accessTokenEphemeralResource) Open(ctx context.Context, req ephemeral.O
7586
return
7687
}
7788

78-
rt, err := auth.KeyAuth(&e.keyAuthConfig)
89+
accessToken, err := getAccessToken(&e.keyAuthConfig)
7990
if err != nil {
80-
core.LogAndAddError(ctx, &resp.Diagnostics, "Access token generation failed", fmt.Sprintf("Failed to initialize authentication: %v", err))
91+
core.LogAndAddError(ctx, &resp.Diagnostics, "Access token generation failed", err.Error())
8192
return
8293
}
8394

95+
model.AccessToken = types.StringValue(accessToken)
96+
resp.Diagnostics.Append(resp.Result.Set(ctx, model)...)
97+
}
98+
99+
// getAccessToken initializes authentication using the provided config and returns an access token via the KeyFlow mechanism.
100+
func getAccessToken(keyAuthConfig *config.Configuration) (string, error) {
101+
roundTripper, err := auth.KeyAuth(keyAuthConfig)
102+
if err != nil {
103+
return "", fmt.Errorf("failed to initialize authentication: %v", err)
104+
}
105+
84106
// Type assert to access token functionality
85-
client, ok := rt.(*clients.KeyFlow)
107+
client, ok := roundTripper.(*clients.KeyFlow)
86108
if !ok {
87-
core.LogAndAddError(ctx, &resp.Diagnostics, "Access token generation failed", "Internal error: expected *clients.KeyFlow, but received a different implementation of http.RoundTripper")
88-
return
109+
return "", fmt.Errorf("internal error: expected *clients.KeyFlow, but received a different implementation of http.RoundTripper")
89110
}
90111

91112
// Retrieve the access token
92113
accessToken, err := client.GetAccessToken()
93114
if err != nil {
94-
core.LogAndAddError(ctx, &resp.Diagnostics, "Access token retrieval failed", fmt.Sprintf("Error obtaining access token: %v", err))
95-
return
115+
return "", fmt.Errorf("error obtaining access token: %v", err)
96116
}
97117

98-
model.AccessToken = types.StringValue(accessToken)
99-
resp.Diagnostics.Append(resp.Result.Set(ctx, model)...)
118+
return accessToken, nil
100119
}

0 commit comments

Comments
 (0)