A Terraform provider for Dokploy

Plugin-framework provider covering projects, environments, applications,
Compose stacks, managed databases, domains, mounts, ports, redirects,
basic auth, registries, SSH keys, certificates and backup destinations,
over Dokploy's tRPC-over-REST API.

The shim package exposes the provider to other Go modules, which is how
pulumi-dokploy bridges it.
This commit is contained in:
2026-08-09 12:17:26 +03:00
commit a6d8aa8b52
160 changed files with 24260 additions and 0 deletions

View File

@@ -0,0 +1,231 @@
# State Checks and Plan Checks Reference
Detailed reference for `statecheck` and `plancheck` packages from
`terraform-plugin-testing`. Read this when writing assertions for test steps.
Source: [State Checks](https://developer.hashicorp.com/terraform/plugin/testing/acceptance-tests/state-checks/resource),
[Plan Checks](https://developer.hashicorp.com/terraform/plugin/testing/acceptance-tests/plan-checks)
---
## Table of Contents
1. [State Checks](#state-checks)
2. [Known Value Types](#known-value-types)
3. [tfjsonpath Navigation](#tfjsonpath-navigation)
4. [Value Comparers](#value-comparers)
5. [Plan Checks](#plan-checks)
---
## State Checks
Use via `ConfigStateChecks` field on `TestStep`. All assertion errors are
aggregated and reported together.
### ExpectKnownValue
Assert an attribute has a specific type and value:
```go
statecheck.ExpectKnownValue("example_widget.test",
tfjsonpath.New("name"),
knownvalue.StringExact("my-widget"))
```
### ExpectSensitiveValue
Assert an attribute is marked sensitive (requires Terraform 1.4.6+):
```go
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(tfversion.Version1_4_6),
},
// ...
statecheck.ExpectSensitiveValue("example_widget.test",
tfjsonpath.New("api_key"))
```
### CompareValue
Compare the same attribute across sequential test steps:
```go
compareValuesSame := statecheck.CompareValue(compare.ValuesSame())
Steps: []resource.TestStep{
{
Config: testAccConfig_v1(rName),
ConfigStateChecks: []statecheck.StateCheck{
compareValuesSame.AddStateValue("example_widget.test",
tfjsonpath.New("id")),
},
},
{
Config: testAccConfig_v2(rName),
ConfigStateChecks: []statecheck.StateCheck{
compareValuesSame.AddStateValue("example_widget.test",
tfjsonpath.New("id")),
},
},
},
```
### CompareValuePairs
Compare attributes between two resources:
```go
statecheck.CompareValuePairs(
"example_widget.test", tfjsonpath.New("vpc_id"),
"example_vpc.test", tfjsonpath.New("id"),
compare.ValuesSame())
```
### CompareValueCollection
Check if a value exists in a collection attribute:
```go
statecheck.CompareValueCollection(
"example_widget.test", tfjsonpath.New("tags"),
"example_widget.test", tfjsonpath.New("name"),
compare.ValuesSame())
```
---
## Known Value Types
Use with `ExpectKnownValue` to assert attribute values:
| Type | Example |
|------|---------|
| `knownvalue.StringExact("value")` | Exact string match |
| `knownvalue.StringRegexp(regexp.MustCompile(`^arn:`))` | Regex match |
| `knownvalue.Bool(true)` | Boolean value |
| `knownvalue.Int64Exact(42)` | Exact int64 |
| `knownvalue.Float64Exact(3.14)` | Exact float64 |
| `knownvalue.NotNull()` | Value is set (not null) |
| `knownvalue.Null()` | Value is null |
| `knownvalue.ListExact([]knownvalue.Check{...})` | Exact list match |
| `knownvalue.ListPartial(map[int]knownvalue.Check{0: ...})` | Partial list match |
| `knownvalue.ListSizeExact(3)` | List has N elements |
| `knownvalue.SetExact([]knownvalue.Check{...})` | Exact set match |
| `knownvalue.SetPartial([]knownvalue.Check{...})` | Set contains items |
| `knownvalue.SetSizeExact(2)` | Set has N elements |
| `knownvalue.MapExact(map[string]knownvalue.Check{...})` | Exact map match |
| `knownvalue.MapPartial(map[string]knownvalue.Check{...})` | Map contains keys |
| `knownvalue.MapSizeExact(1)` | Map has N keys |
| `knownvalue.ObjectExact(map[string]knownvalue.Check{...})` | Exact object match |
| `knownvalue.ObjectPartial(map[string]knownvalue.Check{...})` | Object has attributes |
| `knownvalue.Float32Exact(1.5)` | Exact float32 |
| `knownvalue.Int32Exact(42)` | Exact int32 |
| `knownvalue.NumberExact(big.NewFloat(42))` | Exact number (`*big.Float`) |
| `knownvalue.TupleExact([]knownvalue.Check{...})` | Exact tuple match |
| `knownvalue.TuplePartial(map[int]knownvalue.Check{0: ...})` | Partial tuple match |
| `knownvalue.TupleSizeExact(3)` | Tuple has N elements |
### Nested Value Example
```go
statecheck.ExpectKnownValue("example_widget.test",
tfjsonpath.New("settings"),
knownvalue.ObjectExact(map[string]knownvalue.Check{
"mode": knownvalue.StringExact("production"),
"enabled": knownvalue.Bool(true),
}))
```
---
## tfjsonpath Navigation
Navigate nested attributes in state:
```go
tfjsonpath.New("attribute") // top-level attribute
tfjsonpath.New("block").AtMapKey("key") // nested map/object key
tfjsonpath.New("list_attr").AtSliceIndex(0) // list element by index
tfjsonpath.New("block").AtMapKey("nested").AtMapKey("deep") // deep nesting
```
---
## Value Comparers
Use with `CompareValue`, `CompareValuePairs`, `CompareValueCollection`:
| Comparer | Purpose |
|----------|---------|
| `compare.ValuesSame()` | Values are identical |
| `compare.ValuesDiffer()` | Values are different |
---
## Plan Checks
Use via `ConfigPlanChecks` or `RefreshPlanChecks` on `TestStep`. Plan checks
inspect the plan file at specific phases.
### ConfigPlanChecks Phases
```go
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{...}, // after plan, before apply
PostApplyPreRefresh: []plancheck.PlanCheck{...}, // after apply, before refresh
PostApplyPostRefresh: []plancheck.PlanCheck{...}, // after refresh
},
```
### Built-in Plan Checks
```go
// Expect no changes in plan
plancheck.ExpectEmptyPlan()
// Expect changes in plan
plancheck.ExpectNonEmptyPlan()
// Expect specific resource action
plancheck.ExpectResourceAction("example_widget.test", plancheck.ResourceActionCreate)
plancheck.ExpectResourceAction("example_widget.test", plancheck.ResourceActionUpdate)
plancheck.ExpectResourceAction("example_widget.test", plancheck.ResourceActionDestroy)
plancheck.ExpectResourceAction("example_widget.test", plancheck.ResourceActionNoop)
// Expect known plan value
plancheck.ExpectKnownValue("example_widget.test",
tfjsonpath.New("name"),
knownvalue.StringExact("my-widget"))
// Expect unknown (computed) value in plan
plancheck.ExpectUnknownValue("example_widget.test",
tfjsonpath.New("computed_field"))
// Expect sensitive value in plan
plancheck.ExpectSensitiveValue("example_widget.test",
tfjsonpath.New("api_key"))
```
### No-Op After Update Example
Verify that updating a config back to original values produces no diff:
```go
Steps: []resource.TestStep{
{
Config: testAccConfig_basic(rName),
},
{
Config: testAccConfig_updated(rName),
},
{
Config: testAccConfig_basic(rName),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectEmptyPlan(),
},
},
},
},
```

View File

@@ -0,0 +1,208 @@
# Ephemeral Resource Testing Reference
Testing patterns for ephemeral resources using `terraform-plugin-testing`.
Ephemeral resources reference external data without persisting it to plan or
state artifacts, which means standard plan checks and state checks cannot
directly assert on ephemeral resource data.
Source: [Ephemeral Resource Acceptance Tests](https://developer.hashicorp.com/terraform/plugin/testing/acceptance-tests/ephemeral-resources)
**Requires Terraform >= 1.10.0** — gate all ephemeral tests with
`tfversion.SkipBelow(tfversion.Version1_10_0)`.
---
## Table of Contents
1. [Testing Approaches](#testing-approaches)
2. [Direct Integration Testing](#direct-integration-testing)
3. [Echo Provider Pattern](#echo-provider-pattern)
4. [Multi-Step Testing](#multi-step-testing)
---
## Testing Approaches
Two strategies for testing ephemeral resources:
| Approach | When to use |
|----------|-------------|
| **Direct integration** | Verify the ephemeral resource successfully provides data to a dependent resource or provider |
| **Echo provider** | Assert on specific attribute values using `ConfigStateChecks` via the `echoprovider` package |
---
## Direct Integration Testing
Test that an ephemeral resource successfully provides data to a dependent
resource. No direct assertions on ephemeral data — the test passes if the
dependent resource applies cleanly.
```go
func TestExampleCloudSecret_DnsKerberos(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(tfversion.Version1_10_0),
},
ExternalProviders: map[string]resource.ExternalProvider{
"dns": {
Source: "hashicorp/dns",
},
},
ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){
"examplecloud": providerserver.NewProtocol5WithError(New()),
},
Steps: []resource.TestStep{
{
Config: `
ephemeral "examplecloud_secret" "krb" {
name = "example_kerberos_user"
}
provider "dns" {
update {
server = "ns.example.com"
gssapi {
realm = ephemeral.examplecloud_secret.krb.secret_data.realm
username = ephemeral.examplecloud_secret.krb.secret_data.username
password = ephemeral.examplecloud_secret.krb.secret_data.password
}
}
}
resource "dns_a_record_set" "record_set" {
zone = "example.com."
addresses = ["192.168.0.1", "192.168.0.2", "192.168.0.3"]
}
`,
},
},
})
}
```
---
## Echo Provider Pattern
The `echoprovider` package (Protocol V6) captures ephemeral data into a
managed resource's state, making it assertable with standard
`ConfigStateChecks`.
### Setup
Register both your provider and the echo provider:
```go
import (
"github.com/hashicorp/terraform-plugin-testing/echoprovider"
)
func TestExampleCloudSecret(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(tfversion.Version1_10_0),
},
ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){
"examplecloud": providerserver.NewProtocol5WithError(New()),
},
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"echo": echoprovider.NewProviderServer(),
},
Steps: []resource.TestStep{
// test configurations
},
})
}
```
### Config Pattern
Pass ephemeral data to the echo provider's `data` attribute, then assert on
the `echo` managed resource:
```terraform
ephemeral "examplecloud_secret" "krb" {
name = "example_kerberos_user"
}
provider "echo" {
data = ephemeral.examplecloud_secret.krb.secret_data
}
resource "echo" "test_krb" {}
```
### State Assertions
Assert on the echo resource's `data` attribute using standard state checks:
```go
Steps: []resource.TestStep{
{
Config: `...`,
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("echo.test_krb",
tfjsonpath.New("data").AtMapKey("realm"),
knownvalue.StringExact("EXAMPLE.COM")),
statecheck.ExpectKnownValue("echo.test_krb",
tfjsonpath.New("data").AtMapKey("username"),
knownvalue.StringExact("john-doe")),
statecheck.ExpectKnownValue("echo.test_krb",
tfjsonpath.New("data").AtMapKey("password"),
knownvalue.StringRegexp(regexp.MustCompile(`^.{12}$`))),
},
},
},
```
---
## Multi-Step Testing
The echo resource has special behavior to accommodate ephemeral data
variability:
- During planning for new resources, the `data` attribute is marked unknown
- Existing echo resources preserve prior state regardless of config changes
- Refresh operations always return prior state
Because of this, **create new echo resource instances for each test step**
rather than reusing the same one:
```go
Steps: []resource.TestStep{
{
Config: `
ephemeral "examplecloud_secret" "krb" {
name = "user_one"
}
provider "echo" {
data = ephemeral.examplecloud_secret.krb
}
resource "echo" "test_krb_one" {}
`,
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("echo.test_krb_one",
tfjsonpath.New("data").AtMapKey("name"),
knownvalue.StringExact("user_one")),
},
},
{
Config: `
ephemeral "examplecloud_secret" "krb" {
name = "user_two"
}
provider "echo" {
data = ephemeral.examplecloud_secret.krb
}
resource "echo" "test_krb_two" {}
`,
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("echo.test_krb_two",
tfjsonpath.New("data").AtMapKey("name"),
knownvalue.StringExact("user_two")),
},
},
},
```

View File

@@ -0,0 +1,101 @@
# Test Sweepers Reference
Sweepers clean up infrastructure resources that leak during acceptance tests —
when test infrastructure fails to be destroyed due to API errors or test
failures.
Source: [Sweepers](https://developer.hashicorp.com/terraform/plugin/testing/acceptance-tests/sweepers)
---
## Setup
### TestMain (required)
Add to a dedicated file (e.g., `sweep_test.go`):
```go
func TestMain(m *testing.M) {
resource.TestMain(m)
}
```
This parses the `-sweep` flag and invokes registered sweepers.
### Register a Sweeper
Register in the test file for the resource being swept, using `init()`:
```go
func init() {
resource.AddTestSweepers("example_widget", &resource.Sweeper{
Name: "example_widget",
F: sweepWidgets,
})
}
func sweepWidgets(region string) error {
client, err := sharedClientForRegion(region)
if err != nil {
return fmt.Errorf("getting client: %w", err)
}
conn := client.(*Client)
widgets, err := conn.ListWidgets()
if err != nil {
return fmt.Errorf("listing widgets: %w", err)
}
for _, w := range widgets {
if !strings.HasPrefix(w.Name, "test-acc") {
continue
}
if err := conn.DeleteWidget(w.ID); err != nil {
log.Printf("[WARN] Failed to delete widget %s: %s", w.ID, err)
}
}
return nil
}
```
Use a consistent test name prefix (e.g., `"test-acc"`) to identify
test-created resources.
### Dependencies
When resources have ordering requirements (e.g., child resources must be
deleted before parents), the **parent** sweeper declares children as
dependencies so they run first:
```go
resource.AddTestSweepers("example_widget", &resource.Sweeper{
Name: "example_widget",
Dependencies: []string{"example_widget_child"},
F: sweepWidgets,
})
```
Dependencies run **before** the sweeper that declares them. In this example,
`example_widget_child` is swept first, then `example_widget`.
### Shared Client
Create a helper to build an API client for the sweep region:
```go
func sharedClientForRegion(region string) (any, error) {
// Build and return a configured API client
return NewClient(region)
}
```
## Running Sweepers
```bash
# Run all sweepers for a region
TF_ACC=1 go test ./internal/service/example -sweep=us-east-1 -v
# Makefile target (common convention)
make sweep
```