Six new resources, all backed by endpoints that did not exist before v0.30.0
and verified end-to-end against a live v0.30.2 instance:
dokploy_network Docker networks, now first-class. Services attach
through network_ids, which is what deprecates
Compose's isolated_deployment upstream.
dokploy_dns_provider Cloudflare or Route53, so adding a domain creates
its DNS record.
dokploy_vault_provider Env values resolved from HashiCorp Vault, Infisical,
AWS, Doppler, Azure or Scaleway at deploy time, so
the secret never lands in Dokploy or in state.
dokploy_schedule Cron jobs in a container, a stack, or on a server.
dokploy_volume_backup Scheduled backups of a named volume — the companion
to a mount that persists.
dokploy_libsql The sixth managed database engine.
libsql.create is the strictest endpoint in the API: eleven keys required to
be present, several only meaningfully null, no generated service name, and it
returns `true` rather than the row. CreateDefaults and ListIDs absorb all
three so the resource behaves like every other database.
Also filled the gaps a field-by-field diff against the live schema turned up:
domain gains `enabled` (the v0.30.0 park-a-domain toggle), compose gains
create_env_file, icon and service_networks, application gains icon and
preview_require_collaborator_permissions, and mounts accept libsql.
DNS and vault credentials are masked by Dokploy on read, so `config` is
tagged noread and keeps the configured value, as the basic-auth password
already does.
241 lines
9.4 KiB
Go
241 lines
9.4 KiB
Go
package provider
|
|
|
|
import (
|
|
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
|
|
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
|
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
|
)
|
|
|
|
// Shorthand schema constructors. Dokploy assigns server-side defaults to most
|
|
// optional fields, so nearly everything optional is also Computed: that lets
|
|
// the API supply a value without Terraform reporting an inconsistent result.
|
|
|
|
func computedID(description string) schema.StringAttribute {
|
|
return schema.StringAttribute{
|
|
Computed: true,
|
|
MarkdownDescription: description,
|
|
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
|
|
}
|
|
}
|
|
|
|
func requiredString(description string) schema.StringAttribute {
|
|
return schema.StringAttribute{Required: true, MarkdownDescription: description}
|
|
}
|
|
|
|
// requiredReplaceString is a required attribute that cannot be changed in
|
|
// place; Dokploy has no API to move the resource, so Terraform recreates it.
|
|
func requiredReplaceString(description string) schema.StringAttribute {
|
|
return schema.StringAttribute{
|
|
Required: true,
|
|
MarkdownDescription: description,
|
|
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
|
|
}
|
|
}
|
|
|
|
func optionalReplaceString(description string) schema.StringAttribute {
|
|
return schema.StringAttribute{
|
|
Optional: true,
|
|
MarkdownDescription: description,
|
|
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
|
|
}
|
|
}
|
|
|
|
// requiresReplaceString is the plan-modifier list for an attribute that cannot
|
|
// be changed in place.
|
|
func requiresReplaceString() []planmodifier.String {
|
|
return []planmodifier.String{stringplanmodifier.RequiresReplace()}
|
|
}
|
|
|
|
func optionalString(description string) schema.StringAttribute {
|
|
return schema.StringAttribute{Optional: true, MarkdownDescription: description}
|
|
}
|
|
|
|
// computedString is a server-assigned value that Terraform never sends.
|
|
//
|
|
// UseStateForUnknown is essential here: without it every computed attribute is
|
|
// planned as unknown during an update, and any attribute referencing one --
|
|
// `environment_id = dokploy_project.x.default_environment_id`, say -- would
|
|
// then be unknown too, forcing a spurious replacement of the dependent
|
|
// resource. Read still refreshes these values, so genuine drift is detected.
|
|
func computedString(description string) schema.StringAttribute {
|
|
return schema.StringAttribute{
|
|
Computed: true,
|
|
MarkdownDescription: description,
|
|
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
|
|
}
|
|
}
|
|
|
|
// optionalComputedString is the workhorse: settable by the practitioner, and
|
|
// defaulted by Dokploy when omitted.
|
|
//
|
|
// UseStateForUnknown keeps plans readable. Without it the framework replans
|
|
// every unconfigured Optional+Computed attribute as unknown as soon as
|
|
// anything else on the resource changes, so a one-line edit renders as a dozen
|
|
// "(known after apply)" lines. Dokploy assigns these defaults once at create
|
|
// and then stores them, so reusing the prior value is accurate -- and Read
|
|
// still refreshes them, so real drift is still caught.
|
|
func optionalComputedString(description string) schema.StringAttribute {
|
|
return schema.StringAttribute{
|
|
Optional: true,
|
|
Computed: true,
|
|
MarkdownDescription: description,
|
|
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
|
|
}
|
|
}
|
|
|
|
// optionalComputedReplaceString is defaulted by Dokploy when omitted, but
|
|
// changing an explicitly configured value forces recreation.
|
|
func optionalComputedReplaceString(description string) schema.StringAttribute {
|
|
return schema.StringAttribute{
|
|
Optional: true,
|
|
Computed: true,
|
|
MarkdownDescription: description,
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.RequiresReplace(),
|
|
stringplanmodifier.UseStateForUnknown(),
|
|
},
|
|
}
|
|
}
|
|
|
|
func sensitiveString(description string, required bool) schema.StringAttribute {
|
|
return schema.StringAttribute{
|
|
Required: required,
|
|
Optional: !required,
|
|
Computed: !required,
|
|
Sensitive: true,
|
|
MarkdownDescription: description,
|
|
}
|
|
}
|
|
|
|
func enumString(description string, values []string, required bool) schema.StringAttribute {
|
|
return schema.StringAttribute{
|
|
Required: required,
|
|
Optional: !required,
|
|
Computed: !required,
|
|
MarkdownDescription: description + " Valid values: `" + joinBackticked(values) + "`.",
|
|
Validators: []validator.String{stringvalidator.OneOf(values...)},
|
|
}
|
|
}
|
|
|
|
// enumStringWithDefault is an enum whose value Dokploy insists on receiving
|
|
// even though only one value is currently valid.
|
|
func enumStringWithDefault(description string, values []string, def string) schema.StringAttribute {
|
|
return schema.StringAttribute{
|
|
Optional: true,
|
|
Computed: true,
|
|
MarkdownDescription: description + " Valid values: `" + joinBackticked(values) + "`. Defaults to `" + def + "`.",
|
|
Validators: []validator.String{stringvalidator.OneOf(values...)},
|
|
Default: stringdefault.StaticString(def),
|
|
}
|
|
}
|
|
|
|
// computedBool mirrors computedString for boolean server-assigned values.
|
|
func computedBool(description string) schema.BoolAttribute {
|
|
return schema.BoolAttribute{
|
|
Computed: true,
|
|
MarkdownDescription: description,
|
|
PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()},
|
|
}
|
|
}
|
|
|
|
func optionalComputedBool(description string) schema.BoolAttribute {
|
|
return schema.BoolAttribute{
|
|
Optional: true,
|
|
Computed: true,
|
|
MarkdownDescription: description,
|
|
PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()},
|
|
}
|
|
}
|
|
|
|
// optionalBool has no server-side counterpart; it only steers provider
|
|
// behaviour. It is deliberately not Computed, so an unset value stays null and
|
|
// an imported resource shows no phantom diff.
|
|
func optionalBool(description string) schema.BoolAttribute {
|
|
return schema.BoolAttribute{Optional: true, MarkdownDescription: description}
|
|
}
|
|
|
|
func optionalComputedInt(description string) schema.Int64Attribute {
|
|
return schema.Int64Attribute{
|
|
Optional: true,
|
|
Computed: true,
|
|
MarkdownDescription: description,
|
|
PlanModifiers: []planmodifier.Int64{int64planmodifier.UseStateForUnknown()},
|
|
}
|
|
}
|
|
|
|
func requiredInt(description string) schema.Int64Attribute {
|
|
return schema.Int64Attribute{Required: true, MarkdownDescription: description}
|
|
}
|
|
|
|
func optionalComputedStringList(description string) schema.ListAttribute {
|
|
return schema.ListAttribute{
|
|
Optional: true,
|
|
Computed: true,
|
|
ElementType: types.StringType,
|
|
MarkdownDescription: description,
|
|
PlanModifiers: []planmodifier.List{listplanmodifier.UseStateForUnknown()},
|
|
}
|
|
}
|
|
|
|
// optionalJSON exposes one of Dokploy's free-form JSON columns (the Docker
|
|
// Swarm service settings) as a normalized JSON string, so semantically equal
|
|
// documents do not produce a diff.
|
|
func optionalJSON(description string) schema.StringAttribute {
|
|
return schema.StringAttribute{
|
|
Optional: true,
|
|
CustomType: jsontypes.NormalizedType{},
|
|
MarkdownDescription: description,
|
|
}
|
|
}
|
|
|
|
// enumValidator is the validator list for a string constrained to a fixed set,
|
|
// for attributes assembled by hand rather than through enumString.
|
|
func enumValidator(values []string) []validator.String {
|
|
return []validator.String{stringvalidator.OneOf(values...)}
|
|
}
|
|
|
|
func joinBackticked(values []string) string {
|
|
out := ""
|
|
for i, v := range values {
|
|
if i > 0 {
|
|
out += "`, `"
|
|
}
|
|
out += v
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Enum value sets mirrored from Dokploy's Postgres enums.
|
|
var (
|
|
certificateTypes = []string{"letsencrypt", "none", "custom"}
|
|
sourceTypes = []string{"docker", "git", "github", "gitlab", "bitbucket", "gitea", "drop"}
|
|
buildTypes = []string{"dockerfile", "heroku_buildpacks", "paketo_buildpacks", "nixpacks", "static", "railpack"}
|
|
triggerTypes = []string{"push", "tag"}
|
|
composeTypes = []string{"docker-compose", "stack"}
|
|
composeSources = []string{"git", "github", "gitlab", "bitbucket", "gitea", "raw"}
|
|
domainTypes = []string{"compose", "application", "preview"}
|
|
mountTypes = []string{"bind", "volume", "file"}
|
|
serviceTypes = []string{"application", "postgres", "mysql", "mariadb", "mongo", "redis", "compose", "libsql"}
|
|
protocolTypes = []string{"tcp", "udp"}
|
|
publishModes = []string{"ingress", "host"}
|
|
|
|
// Added in Dokploy v0.30.0.
|
|
networkDrivers = []string{"bridge", "overlay"}
|
|
shellTypes = []string{"bash", "sh"}
|
|
scheduleTypes = []string{"application", "compose", "server", "dokploy-server"}
|
|
sqldNodes = []string{"primary", "replica"}
|
|
|
|
// volumeBackups accepts the service types plus libsql.
|
|
volumeBackupServiceTypes = []string{
|
|
"application", "postgres", "mysql", "mariadb", "mongo", "redis", "compose", "libsql",
|
|
}
|
|
)
|