Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ require (
github.com/stackitcloud/stackit-sdk-go/services/sfs v0.9.0
github.com/stackitcloud/stackit-sdk-go/services/ske v1.14.0
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.10.0
github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.2.1
github.com/teambition/rrule-go v1.8.2
golang.org/x/mod v0.35.0
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,8 @@ github.com/stackitcloud/stackit-sdk-go/services/ske v1.14.0 h1:Zy3yxmHzW+ydu1nae
github.com/stackitcloud/stackit-sdk-go/services/ske v1.14.0/go.mod h1:TbqmZhLMofmfl+HhVl6oHYcI3zvXTm1vRjN3A/fOkM4=
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.10.0 h1:angvO3z0TGqZtdwTDsG/tgTw9hxB76A6leUsiUXQtME=
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.10.0/go.mod h1:AiUoMAqQcOlMgDtkVJlqI7P/VGD5xjN3dYjERGnwN/M=
github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.2.1 h1:Tc5gEuHRXbd+r6OlVh9LZFSDKXftD0ChF529FiGG7Wg=
github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.2.1/go.mod h1:WUmgKtwpe90Yq3YbgNxc2clTTULVxCu0ha6lMTjUnII=
github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g=
github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
1 change: 1 addition & 0 deletions stackit/internal/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type ProviderData struct {
ServiceEnablementCustomEndpoint string
SfsCustomEndpoint string
ServiceAccountCustomEndpoint string
TelemetryRouterCustomEndpoint string
EnableBetaResources bool
Experiments []string

Expand Down
217 changes: 217 additions & 0 deletions stackit/internal/services/telemetryrouter/accesstoken/datasource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package accesstoken

import (
"context"
"fmt"
"net/http"
"time"

"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"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"
telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi"

"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/services/telemetryrouter/utils"
tfutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
)

var (
_ datasource.DataSource = &telemetryRouterAccessTokenDataSource{}
_ datasource.DataSourceWithConfigure = &telemetryRouterAccessTokenDataSource{}
)

type DataSourceModel struct {
ID types.String `tfsdk:"id"` // Required by Terraform
AccessTokenID types.String `tfsdk:"access_token_id"`
InstanceID types.String `tfsdk:"instance_id"`
Region types.String `tfsdk:"region"`
ProjectID types.String `tfsdk:"project_id"`
CreatorID types.String `tfsdk:"creator_id"`
Description types.String `tfsdk:"description"`
DisplayName types.String `tfsdk:"display_name"`
ExpirationTime types.String `tfsdk:"expiration_time"`
Status types.String `tfsdk:"status"`
}

func NewTelemetryRouterAccessTokenDataSource() datasource.DataSource {
return &telemetryRouterAccessTokenDataSource{}
}

type telemetryRouterAccessTokenDataSource struct {
client *telemetryrouter.APIClient
providerData core.ProviderData
}

func (d *telemetryRouterAccessTokenDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_telemetryrouter_access_token"
}

func (d *telemetryRouterAccessTokenDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
if !ok {
return
}
d.providerData = providerData

apiClient := utils.ConfigureClient(ctx, &providerData, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
d.client = apiClient
tflog.Info(ctx, "TelemetryRouter client configured")
}

func (d *telemetryRouterAccessTokenDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: fmt.Sprintf("TelemetryRouter access token data source schema. %s", core.DatasourceRegionFallbackDocstring),
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: schemaDescriptions["id"],
Computed: true,
},
"access_token_id": schema.StringAttribute{
Description: schemaDescriptions["access_token_id"],
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"instance_id": schema.StringAttribute{
Description: schemaDescriptions["instance_id"],
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"region": schema.StringAttribute{
Description: schemaDescriptions["region"],
// the region cannot be found, so it has to be passed
Optional: true,
},
"project_id": schema.StringAttribute{
Description: schemaDescriptions["project_id"],
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"creator_id": schema.StringAttribute{
Description: schemaDescriptions["creator_id"],
Computed: true,
},
"description": schema.StringAttribute{
Description: schemaDescriptions["description"],
Computed: true,
},
"display_name": schema.StringAttribute{
Description: schemaDescriptions["display_name"],
Computed: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
"expiration_time": schema.StringAttribute{
Description: schemaDescriptions["expiration_time"],
Computed: true,
},
"status": schema.StringAttribute{
Description: schemaDescriptions["status"],
Computed: true,
},
},
}
}

func (d *telemetryRouterAccessTokenDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model DataSourceModel
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

ctx = core.InitProviderContext(ctx)

projectID := model.ProjectID.ValueString()
region := d.providerData.GetRegionWithOverride(model.Region)
instanceID := model.InstanceID.ValueString()
accessTokenID := model.AccessTokenID.ValueString()

ctx = tflog.SetField(ctx, "project_id", projectID)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "instance_id", instanceID)
ctx = tflog.SetField(ctx, "access_token_id", accessTokenID)

accessTokenResponse, err := d.client.DefaultAPI.GetAccessToken(ctx, projectID, region, instanceID, accessTokenID).Execute()
if err != nil {
tfutils.LogError(
ctx,
&resp.Diagnostics,
err,
"Reading TelemetryRouter access token",
fmt.Sprintf("Access token with ID %q does not exist in project %q.", accessTokenID, projectID),
map[int]string{
http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectID),
},
)
resp.State.RemoveResource(ctx)
return
}
ctx = core.LogResponse(ctx)

err = mapDataSourceFields(ctx, accessTokenResponse, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryRouter access token", fmt.Sprintf("Processing response: %v", err))
return
}
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "TelemetryRouter access token read", map[string]interface{}{
"access_token_id": accessTokenID,
})
}

func mapDataSourceFields(_ context.Context, accessToken *telemetryrouter.GetAccessTokenResponse, model *DataSourceModel) error {
if accessToken == nil {
return fmt.Errorf("access token is nil")
}
if model == nil {
return fmt.Errorf("model is nil")
}

var accessTokenID string
if model.AccessTokenID.ValueString() != "" {
accessTokenID = model.AccessTokenID.ValueString()
} else if accessToken.Id != "" {
accessTokenID = accessToken.Id
} else {
return fmt.Errorf("access token id not present")
}

model.ID = tfutils.BuildInternalTerraformId(model.ProjectID.ValueString(), model.Region.ValueString(), model.InstanceID.ValueString(), accessTokenID)
model.AccessTokenID = types.StringValue(accessTokenID)
model.Region = types.StringValue(model.Region.ValueString())
model.CreatorID = types.StringValue(accessToken.CreatorId)
if accessToken.Description != nil && *accessToken.Description != "" {
model.Description = types.StringPointerValue(accessToken.Description)
}
model.DisplayName = types.StringValue(accessToken.DisplayName)
model.Status = types.StringValue(accessToken.Status)

model.ExpirationTime = types.StringNull()
if accessToken.HasExpirationTime() && accessToken.ExpirationTime.Get() != nil {
model.ExpirationTime = types.StringValue(accessToken.ExpirationTime.Get().Format(time.RFC3339))
}

return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package accesstoken

import (
"context"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi"
)

func fixtureDataSourceModel(mods ...func(model *DataSourceModel)) *DataSourceModel {
model := &DataSourceModel{
ID: types.StringValue("pid,rid,iid,atid"),
AccessTokenID: types.StringValue("atid"),
InstanceID: types.StringValue("iid"),
Region: types.StringValue("rid"),
ProjectID: types.StringValue("pid"),
CreatorID: types.StringValue(""),
Description: types.String{},
DisplayName: types.StringValue(""),
ExpirationTime: types.String{},
Status: types.StringValue("active"),
}
for _, mod := range mods {
mod(model)
}
return model
}

func TestMapDataSourceFields(t *testing.T) {
tests := []struct {
description string
input *telemetryrouter.GetAccessTokenResponse
expected *DataSourceModel
wantErr bool
}{
{
description: "min values",
input: fixtureGetAccessToken(),
expected: fixtureDataSourceModel(),
},
{
description: "max values",
input: fixtureGetAccessToken(func(accessToken *telemetryrouter.GetAccessTokenResponse) {
accessToken.Description = utils.Ptr("description")
Comment thread
ozanichkovsky marked this conversation as resolved.
Outdated
accessToken.DisplayName = "display-name"
accessToken.CreatorId = "testUser"
accessToken.ExpirationTime = *telemetryrouter.NewNullableTime(&testTime)
}),
expected: fixtureDataSourceModel(func(model *DataSourceModel) {
model.Description = types.StringValue("description")
model.DisplayName = types.StringValue("display-name")
model.CreatorID = types.StringValue("testUser")
model.ExpirationTime = types.StringValue(testTime.Format(time.RFC3339))
}),
},
{
description: "nil input",
wantErr: true,
expected: fixtureDataSourceModel(),
},
{
description: "nil access token id",
input: &telemetryrouter.GetAccessTokenResponse{},
wantErr: true,
expected: fixtureDataSourceModel(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
state := &DataSourceModel{
ProjectID: tt.expected.ProjectID,
Region: tt.expected.Region,
InstanceID: tt.expected.InstanceID,
}
err := mapDataSourceFields(context.Background(), tt.input, state)
if tt.wantErr && err == nil {
t.Fatalf("Should have failed")
}
if !tt.wantErr && err != nil {
t.Fatalf("Should not have failed: %v", err)
}
if !tt.wantErr {
diff := cmp.Diff(state, tt.expected)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
}
})
}
}
Loading