Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .github/workflows/acceptance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ jobs:
- name: Project
package: ./internal/resources/project
project_id_required: false
- name: Telemetry destination
package: ./internal/resources/telemetrydestination
project_id_required: false
- name: Project data source
package: ./internal/datasources/project
project_id_required: true
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Resources:

- `kernel_browser_pool`
- `kernel_project`
- `kernel_telemetry_destination`

Data sources:

Expand All @@ -28,6 +29,7 @@ Import:

- `kernel_browser_pool` by canonical browser pool ID
- `kernel_project` by canonical project ID
- `kernel_telemetry_destination` by canonical telemetry destination ID

## Not Supported

Expand Down Expand Up @@ -84,8 +86,13 @@ use the project-qualified form to import from a different project:
terraform import kernel_browser_pool.example <browser-pool-id>
terraform import kernel_browser_pool.example <project-id>/<browser-pool-id>
terraform import kernel_project.example <project-id>
terraform import kernel_telemetry_destination.example <telemetry-destination-id>
```

Kernel never returns telemetry destination header values, so an imported
destination has empty header values in state and the next apply sends the
configured values.

See the [first public release guide](docs/first-release.md) for the exact public
surface and for adopting existing Kernel projects or browser pools.

Expand Down
10 changes: 7 additions & 3 deletions docs/acceptance.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This document is the live-API release gate for the provider's selected public
surface. Unit tests remain the fast default. Acceptance tests run after changes
reach `main`, through explicit local opt-in, or by manual workflow dispatch.

The selected surface contains two managed resources and four read-only data
The selected surface contains three managed resources and four read-only data
sources. It does not claim coverage for future or unregistered Kernel objects.

## Gate Rules
Expand Down Expand Up @@ -32,14 +32,17 @@ export KERNEL_BASE_URL=... # optional non-production API
```

`KERNEL_PROJECT_ID` is required for the browser-pool resource and all four data
sources. The project resource is organization-scoped and does not require it.
sources. The project and telemetry destination resources are organization-scoped
and do not require it; the telemetry destination test needs an API key that is
not bound to a project.

## Matrix

| Surface | Package | Live scenario |
| --- | --- | --- |
| `kernel_project` resource | `./internal/resources/project` | Create, rename with stable ID, no-drift plan, canonical-ID import, post-import no drift, delete, and HTTP 404 verification. |
| `kernel_browser_pool` resource | `./internal/resources/browserpool` | Create, durable update with stable ID, opt into rebuilding and acquire an idle browser after a configuration change, no-drift plan, provider-default and explicit project scope, bare and project-qualified import, non-force delete, and HTTP 404 verification. |
| `kernel_telemetry_destination` resource | `./internal/resources/telemetrydestination` | Create with headers, update name, endpoint, description, and headers with stable ID, remove all headers, verify header names through the API, no-drift plan, canonical-ID import, delete, and HTTP 404 verification. |
| `kernel_project` data source | `./internal/datasources/project` | Create a unique project fixture, read it by ID and exact name, read the provider-default project, verify durable metadata and no drift, then delete and require coded `not_found`. |
| `kernel_profile` data source | `./internal/datasources/profile` | Create a durable profile fixture through the SDK, read it by ID and exact name with explicit and default project scope, verify durable metadata and no drift, then delete and require coded `not_found`. |
| `kernel_proxy` data source | `./internal/datasources/proxy` | Create a managed datacenter proxy fixture through the SDK, read it by ID and exact name with explicit and default project scope, verify durable masked metadata and no drift, then delete and require coded `not_found`. |
Expand All @@ -61,13 +64,14 @@ Run packages independently for fast failure isolation:
```sh
go test -count=1 -timeout=30m -v ./internal/resources/project -run TestAcc
go test -count=1 -timeout=30m -v ./internal/resources/browserpool -run TestAcc
go test -count=1 -timeout=30m -v ./internal/resources/telemetrydestination -run TestAcc
go test -count=1 -timeout=30m -v ./internal/datasources/project -run TestAcc
go test -count=1 -timeout=30m -v ./internal/datasources/profile -run TestAcc
go test -count=1 -timeout=30m -v ./internal/datasources/proxy -run TestAcc
go test -count=1 -timeout=30m -v ./internal/datasources/extension -run TestAcc
```

The `Acceptance` workflow runs the same six packages as separate matrix jobs
The `Acceptance` workflow runs the same seven packages as separate matrix jobs
with `fail-fast: false` after changes reach `main` and on manual dispatch.

## Outside The Selected Surface
Expand Down
4 changes: 2 additions & 2 deletions docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ Use this checklist before publishing a Kernel Terraform provider version.
checks each archive, and verifies checksum coverage. A snapshot skips signing
and is not a publishable release.
- Run the complete [selected-surface acceptance matrix](acceptance.md) with real credentials against the release commit.
- The `Acceptance` workflow runs all six packages as independent matrix jobs after changes reach `main` and supports manual dispatch. Keep live tests out of pull-request CI.
- The `Acceptance` workflow runs all seven packages as independent matrix jobs after changes reach `main` and supports manual dispatch. Keep live tests out of pull-request CI.
- Process-level timeouts can bypass Go test cleanup. After an interrupted or hard-timeout run:
1. In the Kernel dashboard or durable API, find projects, browser pools, profiles, proxies, and extensions named `kernel-tf-*` that were created during the failed workflow run.
1. In the Kernel dashboard or durable API, find projects, browser pools, profiles, proxies, extensions, and telemetry destinations named `kernel-tf-*` that were created during the failed workflow run.
2. Delete leaked browser pools with `force=false`. If deletion conflicts with a lease, wait for the lease to end; do not force-release or recover the browser from Terraform cleanup.
3. Delete other leaked project-scoped fixtures, then delete a leaked project only after its child resources are gone and the organization still has another active project.
4. Read each canonical ID and require the expected not-found response before considering cleanup complete.
Expand Down
40 changes: 40 additions & 0 deletions docs/resources/telemetry_destination.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "kernel_telemetry_destination Resource - Kernel"
subcategory: ""
description: |-
Kernel OTLP telemetry destination. Browser sessions reference a destination by ID or name in telemetry.export.otlp.destination to export their telemetry to it. Destinations are organization-scoped and require an organization-scoped API key.
---

# kernel_telemetry_destination (Resource)

Kernel OTLP telemetry destination. Browser sessions reference a destination by ID or name in `telemetry.export.otlp.destination` to export their telemetry to it. Destinations are organization-scoped and require an organization-scoped API key.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `endpoint` (String) Base OTLP/HTTP collector endpoint, without a signal path such as `/v1/logs`. Must be a public http or https URL with no query string or fragment.
- `name` (String) Destination name. Must be unique within the organization, match `^[a-zA-Z0-9._-]{1,255}$`, and not look like an ID.

### Optional

- `description` (String) Destination description.
- `headers` (Map of String, Sensitive) Headers sent with each export request, typically an ingestion key. Header names are matched case-insensitively. Kernel never returns header values, so Terraform detects added or removed header names but not values changed outside Terraform. After import, header values are empty in state and the next apply sends the configured values.

### Read-Only

- `id` (String) Unique telemetry destination identifier.

## Import

Import is supported using the following syntax:

The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:

```shell
terraform import kernel_telemetry_destination.example <telemetry-destination-id>
```
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ override setup.
- [lookups](lookups) shows read-only project, profile, proxy, and extension data sources.
- [project](project) creates a durable Kernel project with an explicit unique name.
- [project-scoped-browser-pool](project-scoped-browser-pool) places a browser pool in an explicit project, overriding the provider-level `project_id` default.
- [telemetry-destination](telemetry-destination) creates an OTLP destination that browser sessions can export telemetry to.
1 change: 1 addition & 0 deletions examples/resources/kernel_telemetry_destination/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import kernel_telemetry_destination.example <telemetry-destination-id>
33 changes: 33 additions & 0 deletions examples/telemetry-destination/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
terraform {
required_providers {
kernel = {
source = "kernel/kernel"
}
}
}

provider "kernel" {}

variable "otlp_endpoint" {
type = string
description = "Base OTLP/HTTP collector endpoint, without a signal path such as /v1/logs."
}

variable "otlp_token" {
type = string
sensitive = true
description = "Collector credential sent with each telemetry export."
}

resource "kernel_telemetry_destination" "example" {
name = "otlp-collector"
endpoint = var.otlp_endpoint
description = "Browser session telemetry"
headers = {
"Authorization" = "Bearer ${var.otlp_token}"
}
}

output "telemetry_destination_id" {
value = kernel_telemetry_destination.example.id
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0
github.com/hashicorp/terraform-plugin-go v0.31.0
github.com/hashicorp/terraform-plugin-testing v1.16.0
github.com/kernel/kernel-go-sdk v0.76.0
github.com/kernel/kernel-go-sdk v0.99.0
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
github.com/kernel/kernel-go-sdk v0.76.0 h1:bnGTPq69M6+Qfr0Bkkl6EGqvr0dfGrCwdiEYAx3pHOc=
github.com/kernel/kernel-go-sdk v0.76.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ=
github.com/kernel/kernel-go-sdk v0.99.0 h1:L8P3JNwF46/oWmwhrc8BNbgK8vTUh5HlYnPXqXT4qlU=
github.com/kernel/kernel-go-sdk v0.99.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
Expand Down
35 changes: 35 additions & 0 deletions internal/acctest/acctest.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,41 @@ func cleanupProject(t testing.TB, client projectCleaner, id string) {
})
}

func CleanupTelemetryDestination(t testing.TB, id string) {
t.Helper()

cleanupTelemetryDestination(t, ClientFromEnv(), id)
}

type telemetryDestinationCleaner interface {
DeleteTelemetryDestination(context.Context, string) error
}

func cleanupTelemetryDestination(t testing.TB, client telemetryDestinationCleaner, id string) {
t.Helper()

if id == "" {
return
}
if !AcceptanceEnabled() {
t.Fatalf("%s must be set to clean up Kernel acceptance test resources", EnvAcceptance)
return
}
if os.Getenv(EnvAPIKey) == "" {
t.Fatalf("%s must be set to clean up Kernel acceptance test resources", EnvAPIKey)
return
}

t.Cleanup(func() {
ctx, cancel := context.WithTimeout(context.Background(), cleanupTimeout)
defer cancel()

if err := client.DeleteTelemetryDestination(ctx, id); err != nil && !IsNotFound(err) {
t.Errorf("cleanup Kernel telemetry destination %s: %v", id, err)
}
})
}

// CleanupProfile registers a cleanup that deletes the profile from projectID;
// empty means the env-configured default project.
func CleanupProfile(t testing.TB, projectID, id string) {
Expand Down
44 changes: 32 additions & 12 deletions internal/kernelclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@ type Config struct {
}

type Clients struct {
defaultProjectID string
projects kernel.ProjectService
profiles kernel.ProfileService
proxies kernel.ProxyService
extensions kernel.ExtensionService
browserPools kernel.BrowserPoolService
defaultProjectID string
projects kernel.ProjectService
profiles kernel.ProfileService
proxies kernel.ProxyService
extensions kernel.ExtensionService
browserPools kernel.BrowserPoolService
telemetryDestinations kernel.TelemetryDestinationService
}

type Option func(*clientOptions)
Expand All @@ -63,12 +64,13 @@ func New(config Config, opts ...Option) Clients {
requestOpts := requestOptions(config, clientOpts)

return Clients{
defaultProjectID: config.ProjectID,
projects: kernel.NewProjectService(requestOpts...),
profiles: kernel.NewProfileService(requestOpts...),
proxies: kernel.NewProxyService(requestOpts...),
extensions: kernel.NewExtensionService(requestOpts...),
browserPools: kernel.NewBrowserPoolService(requestOpts...),
defaultProjectID: config.ProjectID,
projects: kernel.NewProjectService(requestOpts...),
profiles: kernel.NewProfileService(requestOpts...),
proxies: kernel.NewProxyService(requestOpts...),
extensions: kernel.NewExtensionService(requestOpts...),
browserPools: kernel.NewBrowserPoolService(requestOpts...),
telemetryDestinations: kernel.NewTelemetryDestinationService(requestOpts...),
}
}

Expand Down Expand Up @@ -96,6 +98,24 @@ func (c Clients) DeleteProject(ctx context.Context, id string) error {
return c.projects.Delete(ctx, id, noMutationRetries())
}

// Telemetry destinations are org-scoped too.

func (c Clients) CreateTelemetryDestination(ctx context.Context, params kernel.TelemetryDestinationNewParams) (*kernel.OtlpDestination, error) {
return c.telemetryDestinations.New(ctx, params, noMutationRetries())
}

func (c Clients) GetTelemetryDestination(ctx context.Context, id string) (*kernel.OtlpDestination, error) {
return c.telemetryDestinations.Get(ctx, id)
}

func (c Clients) UpdateTelemetryDestination(ctx context.Context, id string, params kernel.TelemetryDestinationUpdateParams) (*kernel.OtlpDestination, error) {
return c.telemetryDestinations.Update(ctx, id, params, noMutationRetries())
}

func (c Clients) DeleteTelemetryDestination(ctx context.Context, id string) error {
return c.telemetryDestinations.Delete(ctx, id, noMutationRetries())
}

// The remaining methods are project-scoped and take the resolved project
// for each call.

Expand Down
28 changes: 28 additions & 0 deletions internal/kernelclient/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,34 @@ func TestMutationsDisableSDKRetriesAndUseExpectedScope(t *testing.T) {
return clients.DeleteProject(ctx, "project_123")
},
},
"telemetry destination create": {
method: http.MethodPost,
path: "/telemetry/destinations",
call: func(ctx context.Context, clients Clients) error {
_, err := clients.CreateTelemetryDestination(ctx, kernel.TelemetryDestinationNewParams{
Name: "destination",
Endpoint: "https://otlp.example",
})
return err
},
},
"telemetry destination update": {
method: http.MethodPatch,
path: "/telemetry/destinations/dest_123",
call: func(ctx context.Context, clients Clients) error {
_, err := clients.UpdateTelemetryDestination(ctx, "dest_123", kernel.TelemetryDestinationUpdateParams{
Name: kernel.String("renamed"),
})
return err
},
},
"telemetry destination delete": {
method: http.MethodDelete,
path: "/telemetry/destinations/dest_123",
call: func(ctx context.Context, clients Clients) error {
return clients.DeleteTelemetryDestination(ctx, "dest_123")
},
},
"profile create": {
method: http.MethodPost,
path: "/profiles",
Expand Down
2 changes: 2 additions & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/kernel/terraform-provider-kernel/internal/kernelclient"
"github.com/kernel/terraform-provider-kernel/internal/resources/browserpool"
projectresource "github.com/kernel/terraform-provider-kernel/internal/resources/project"
"github.com/kernel/terraform-provider-kernel/internal/resources/telemetrydestination"
)

var _ provider.Provider = (*kernelProvider)(nil)
Expand Down Expand Up @@ -83,6 +84,7 @@ func (p *kernelProvider) Resources(ctx context.Context) []func() resource.Resour
return []func() resource.Resource{
browserpool.NewResource,
projectresource.NewResource,
telemetrydestination.NewResource,
}
}

Expand Down
6 changes: 3 additions & 3 deletions internal/provider/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ func TestProviderRegistersResources(t *testing.T) {
p := provider.New("test")()

resources := p.Resources(context.Background())
if len(resources) != 2 {
t.Fatalf("Resources length = %d, want 2", len(resources))
if len(resources) != 3 {
t.Fatalf("Resources length = %d, want 3", len(resources))
}

got := make(map[string]bool, len(resources))
Expand All @@ -49,7 +49,7 @@ func TestProviderRegistersResources(t *testing.T) {
got[resp.TypeName] = true
}

for _, want := range []string{"kernel_browser_pool", "kernel_project"} {
for _, want := range []string{"kernel_browser_pool", "kernel_project", "kernel_telemetry_destination"} {
if !got[want] {
t.Fatalf("missing resource %s; got %v", want, got)
}
Expand Down
Loading
Loading