Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a39fce3
feat: TelemetryLink resources and datasources
May 11, 2026
db12dc1
chore: lint fix
May 11, 2026
a5447dd
Merge remote-tracking branch 'origin/main' into feature/telemetrylink
May 14, 2026
a085795
chore: renamed stackit_telemetrylink link to stackit_telemetrylink
May 14, 2026
4213072
chore: rename "TelemetryLink Link" to "TelemetryLink"
May 14, 2026
6d6d309
chore: sorted schema by required then optional
May 14, 2026
e573f59
chore: sorted schema fields by required then optional
May 14, 2026
d38ccae
chore: review comments fixed
May 14, 2026
9db81c2
chore: moved error handling out of the switch statement
May 14, 2026
a5f01a5
chore: partial update
May 14, 2026
648da00
chore: LinkLink renamed to Link
May 19, 2026
3955797
chore: telemetryLinkInstanceResource to telemetryLinkResource
May 19, 2026
79e8ba0
Merge remote-tracking branch 'origin/main' into feature/telemetrylink
May 22, 2026
3b7f608
Merge remote-tracking branch 'origin/main' into feature/telemetrylink
May 25, 2026
b4903ef
feat: use waiter when deleting acceptance test resources
May 25, 2026
caccee4
feat: telemetry router resources deletion in acceptance tests
May 25, 2026
c61fcfe
feat: delete telemetry router resources in acceptance tests
May 25, 2026
b854951
chore: use real telemetry router for telemetry link in acceptance tests
May 25, 2026
5de9c3b
fix: fixed acceptance test
May 27, 2026
94b2a2b
chore: improvements
May 28, 2026
6413bd8
Merge branch 'main' into feature/telemetrylink
ozanichkovsky May 28, 2026
634d8e9
chore: lint fix
May 28, 2026
9b65448
chore; improvements
May 28, 2026
aa72ff9
chore: adjusted update and delete
May 29, 2026
5535910
Update stackit/internal/services/telemetrylink/link/datasource.go
ozanichkovsky May 29, 2026
f5e9a84
chore: code review fixes
May 29, 2026
afee88c
chore: fixed tests
May 29, 2026
97bb747
chore: Import state fields
May 29, 2026
85ccd64
Merge remote-tracking branch 'origin/main' into feature/telemetrylink
May 29, 2026
83c8279
chore: code review fixes
May 29, 2026
0975b3a
chore: acc test fix
May 29, 2026
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
5 changes: 5 additions & 0 deletions examples/data-sources/stackit_telemetrylink/data-source.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
data "stackit_telemetrylink" "link" {
resource_type = "project"
resource_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
region = "eu01"
}
24 changes: 24 additions & 0 deletions examples/resources/stackit_telemetrylink/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
resource "stackit_telemetrylink" "link" {
resource_type = "project"
resource_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
region = "eu01"
display_name = "telemetrylink-example"
access_token = "eyJxxx"
telemetry_router_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

resource "stackit_telemetrylink" "link2" {
resource_type = "project"
resource_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
region = "eu01"
display_name = "telemetrylink-example"
description = "telemetrylink description"
access_token = "eyJxxx"
telemetry_router_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

# Only use the import statement, if you want to import an existing TelemetryLink
import {
to = stackit_telemetrylink.import-example
id = "${var.resource_type},${var.resource_id},${var.region}"
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ require (
github.com/stackitcloud/stackit-sdk-go/services/sfs v0.10.1
github.com/stackitcloud/stackit-sdk-go/services/ske v1.15.0
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.11.0
github.com/stackitcloud/stackit-sdk-go/services/telemetrylink v0.2.0
github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.3.0
github.com/teambition/rrule-go v1.8.2
golang.org/x/mod v0.36.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,8 @@ github.com/stackitcloud/stackit-sdk-go/services/ske v1.15.0 h1:sSuwbqAvh89jUUX2R
github.com/stackitcloud/stackit-sdk-go/services/ske v1.15.0/go.mod h1:TbqmZhLMofmfl+HhVl6oHYcI3zvXTm1vRjN3A/fOkM4=
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.11.0 h1:PwjQeupEnXxhu+uWCUzO/hUfL4yqNblOcZbP2jvaQtU=
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.11.0/go.mod h1:AiUoMAqQcOlMgDtkVJlqI7P/VGD5xjN3dYjERGnwN/M=
github.com/stackitcloud/stackit-sdk-go/services/telemetrylink v0.2.0 h1:U1mQoCk0TXc8NsSc/Sl9PKMdEyJpWNU2zLnsqmx6wEc=
github.com/stackitcloud/stackit-sdk-go/services/telemetrylink v0.2.0/go.mod h1:hgw8janWmDfP2bnuZensxqcAePr49BX5ug8Rq85o+h8=
github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.3.0 h1:MEvzGItcbig+9A4JvK2E5W6/mqXDPafiGkDZ1BprBAI=
github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.3.0/go.mod h1:WUmgKtwpe90Yq3YbgNxc2clTTULVxCu0ha6lMTjUnII=
github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g=
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 @@ -72,6 +72,7 @@ type ProviderData struct {
ServiceEnablementCustomEndpoint string
SfsCustomEndpoint string
ServiceAccountCustomEndpoint string
TelemetryLinkCustomEndpoint string
TelemetryRouterCustomEndpoint string
EnableBetaResources bool
Experiments []string
Expand Down
217 changes: 217 additions & 0 deletions stackit/internal/services/telemetrylink/link/datasource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package link

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

var (
_ datasource.DataSource = &telemetryLinkDataSource{}
)

func NewTelemetryLinkDataSource() datasource.DataSource {
return &telemetryLinkDataSource{}
}

type DataSourceModel struct {
ID types.String `tfsdk:"id"` // Required by Terraform
LinkID types.String `tfsdk:"link_id"`
Region types.String `tfsdk:"region"`
ResourceType types.String `tfsdk:"resource_type"`
ResourceID types.String `tfsdk:"resource_id"`
DisplayName types.String `tfsdk:"display_name"`
Description types.String `tfsdk:"description"`
TelemetryRouterID types.String `tfsdk:"telemetry_router_id"`
CreateTime types.String `tfsdk:"create_time"`
Status types.String `tfsdk:"status"`
}

type telemetryLinkDataSource struct {
client *telemetrylink.APIClient
providerData core.ProviderData
}

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

func (d *telemetryLinkDataSource) 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, "TelemetryLink client configured")
}

func (d *telemetryLinkDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: fmt.Sprintf("TelemetryLink data source schema. %s", core.DatasourceRegionFallbackDocstring),
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: schemaDescriptions["id"],
Computed: true,
},
"link_id": schema.StringAttribute{
Description: schemaDescriptions["link_id"],
Computed: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"resource_type": schema.StringAttribute{
Description: schemaDescriptions["resource_type"],
Required: true,
Validators: []validator.String{
stringvalidator.OneOf(resourceTypes...),
validate.NoSeparator(),
},
},
"resource_id": schema.StringAttribute{
Comment thread
ozanichkovsky marked this conversation as resolved.
Description: schemaDescriptions["resource_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,
},
"display_name": schema.StringAttribute{
Description: schemaDescriptions["display_name"],
Computed: true,
},
"description": schema.StringAttribute{
Description: schemaDescriptions["description"],
Computed: true,
},
"telemetry_router_id": schema.StringAttribute{
Description: schemaDescriptions["telemetry_router_id"],
Computed: true,
},
"create_time": schema.StringAttribute{
Description: schemaDescriptions["create_time"],
Computed: true,
},
"status": schema.StringAttribute{
Description: schemaDescriptions["status"],
Computed: true,
},
},
}
}

func (d *telemetryLinkDataSource) 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)

resourceType := model.ResourceType.ValueString()
resourceID := model.ResourceID.ValueString()
region := d.providerData.GetRegionWithOverride(model.Region)

ctx = tflog.SetField(ctx, "resource_type", resourceType)
ctx = tflog.SetField(ctx, "resource_id", resourceID)
ctx = tflog.SetField(ctx, "region", region)

var response *telemetrylink.TelemetryLinkResponse
var err error
switch resourceType {
case resourceTypeOrganization:
response, err = d.client.DefaultAPI.GetOrganizationTelemetryLink(ctx, resourceID, region).Execute()
case resourceTypeFolder:
response, err = d.client.DefaultAPI.GetFolderTelemetryLink(ctx, resourceID, region).Execute()
case resourceTypeProject:
response, err = d.client.DefaultAPI.GetProjectTelemetryLink(ctx, resourceID, region).Execute()
default:
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink", fmt.Sprintf("Unsupported resource type: %s", resourceType))
return
}
if err != nil {
tfutils.LogError(
ctx,
&resp.Diagnostics,
err,
"Reading TelemetryLink",
fmt.Sprintf("TelemetryLink for resource type %q and resource ID %q does not exist.", resourceType, resourceID),
map[int]string{
http.StatusForbidden: fmt.Sprintf("Resource with type %q ID %q not found or forbidden access", resourceType, resourceID),
},
)
resp.State.RemoveResource(ctx)
return
}
ctx = core.LogResponse(ctx)

err = mapDataSourceFields(ctx, response, &model, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink", 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, "TelemetryLink read", map[string]interface{}{
"resource_type": resourceType,
"resource_id": resourceID,
})
}

func mapDataSourceFields(_ context.Context, link *telemetrylink.TelemetryLinkResponse, model *DataSourceModel, region string) error {
if link == nil {
return fmt.Errorf("link is nil")
}
if model == nil {
return fmt.Errorf("model is nil")
}
var linkID string
if model.LinkID.ValueString() != "" {
linkID = model.LinkID.ValueString()
} else {
linkID = link.Id
}

model.ID = tfutils.BuildInternalTerraformId(model.ResourceType.ValueString(), model.ResourceID.ValueString(), region)
model.LinkID = types.StringValue(linkID)
model.Region = types.StringValue(region)
model.DisplayName = types.StringValue(link.DisplayName)
model.Description = types.StringPointerValue(link.Description)
model.TelemetryRouterID = types.StringValue(link.TelemetryRouterId)
model.CreateTime = types.StringValue(link.CreateTime.Format(time.RFC3339))
model.Status = types.StringValue(string(link.Status))

return nil
}
86 changes: 86 additions & 0 deletions stackit/internal/services/telemetrylink/link/datasource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package link

import (
"context"
"testing"
"time"

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

func fixtureDataSourceModel(mods ...func(model *DataSourceModel)) *DataSourceModel {
model := &DataSourceModel{
ID: types.StringValue("rtp,rid,reg"),
LinkID: types.StringValue("lid"),
Region: types.StringValue("reg"),
ResourceType: types.StringValue("rtp"),
ResourceID: types.StringValue("rid"),
DisplayName: types.StringValue("name"),
Description: types.String{},
TelemetryRouterID: types.StringValue("tlmrid"),
CreateTime: types.StringValue(testTime.Format(time.RFC3339)),
Status: types.StringValue("active"),
}
for _, mod := range mods {
mod(model)
}
return model
}

func TestMapDataSourceFields(t *testing.T) {
tests := []struct {
description string
input *telemetrylink.TelemetryLinkResponse
expected *DataSourceModel
wantErr bool
}{
{
description: "min values",
input: fixtureLink(),
expected: fixtureDataSourceModel(),
},
{
description: "max values",
input: fixtureLink(func(link *telemetrylink.TelemetryLinkResponse) {
link.Description = new("description")
link.DisplayName = "display-name"
link.AccessToken = new("access-token")
link.TelemetryRouterId = "tlmr-id"
}),
expected: fixtureDataSourceModel(func(model *DataSourceModel) {
model.Description = types.StringValue("description")
model.DisplayName = types.StringValue("display-name")
model.TelemetryRouterID = types.StringValue("tlmr-id")
}),
},
{
description: "nil input",
wantErr: true,
expected: fixtureDataSourceModel(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
state := &DataSourceModel{
ResourceType: tt.expected.ResourceType,
ResourceID: tt.expected.ResourceID,
Region: tt.expected.Region,
}
err := mapDataSourceFields(context.Background(), tt.input, state, tt.expected.Region.ValueString())
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