Files
terraform-provider-dokploy/internal/provider/schema_helpers.go
T
max-voitcov 2d1caf6e73
build / build (push) Successful in 3m56s
release / release (push) Successful in 15m2s
Cover what Dokploy v0.30 added
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.
2026-08-26 00:40:27 +03:00

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",
}
)