package provider import ( "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" "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/planmodifier" "github.com/hashicorp/terraform-plugin-framework/types" ) // Docker networks became first-class in Dokploy v0.30.0. Services attach to // them through `network_ids` on the application, compose and database // resources. type networkModel struct { ID types.String `tfsdk:"id" dokploy:"networkId,id"` Name types.String `tfsdk:"name" dokploy:"name"` Driver types.String `tfsdk:"driver" dokploy:"driver"` Internal types.Bool `tfsdk:"internal" dokploy:"internal"` Attachable types.Bool `tfsdk:"attachable" dokploy:"attachable"` EnableIPv4 types.Bool `tfsdk:"enable_ipv4" dokploy:"enableIPv4"` EnableIPv6 types.Bool `tfsdk:"enable_ipv6" dokploy:"enableIPv6"` MTU types.Int64 `tfsdk:"mtu" dokploy:"mtu,nullable"` // IPAM is Docker's address-management block: {"subnet","gateway","ipRange"}. IPAM jsontypes.Normalized `tfsdk:"ipam" dokploy:"ipam,nullable"` ServerID types.String `tfsdk:"server_id" dokploy:"serverId,create"` OrganizationID types.String `tfsdk:"organization_id" dokploy:"organizationId,ro"` CreatedAt types.String `tfsdk:"created_at" dokploy:"createdAt,ro"` } func networkResource() ResourceSpec { // Dokploy exposes no `network.update`: a Docker network's driver, subnet // and flags are fixed once it exists. Every configurable attribute // therefore forces replacement. return ResourceSpec{ Name: "network", CreateProc: "network.create", ReadProc: "network.one", DeleteProc: "network.remove", NewModel: func() any { return &networkModel{} }, Schema: schema.Schema{ MarkdownDescription: "A Docker network managed by Dokploy.\n\n" + "Attach services to it with `network_ids` on `dokploy_application`, `dokploy_compose` and the " + "database resources. Every service also joins the shared `dokploy-network` unless " + "`detach_dokploy_network` is set.\n\n" + "~> Docker networks are immutable. Dokploy has no update endpoint, so changing any attribute " + "replaces the network — which detaches the services currently using it until they redeploy.", Attributes: map[string]schema.Attribute{ "id": computedID("Unique network identifier."), "name": requiredReplaceString("Name of the Docker network."), "driver": schema.StringAttribute{ Optional: true, Computed: true, MarkdownDescription: "Network driver. Use `overlay` for multi-node Swarm clusters and `bridge` for a single host. Valid values: `bridge`, `overlay`.", Validators: enumValidator(networkDrivers), PlanModifiers: requiresReplaceString(), }, "internal": replaceBool("Isolate the network from external access.", false), "attachable": replaceBool("Allow standalone containers to attach to an overlay network.", false), "enable_ipv4": replaceBool("Enable IPv4 address allocation.", true), "enable_ipv6": replaceBool("Enable IPv6 address allocation.", false), "mtu": schema.Int64Attribute{ Optional: true, MarkdownDescription: "Maximum transmission unit for the network. Leave unset to use Docker's default.", PlanModifiers: []planmodifier.Int64{int64planmodifier.RequiresReplace()}, }, "ipam": schema.StringAttribute{ Optional: true, CustomType: jsontypes.NormalizedType{}, MarkdownDescription: "Custom IP address management, as a JSON object with `subnet`, `gateway` " + "and `ipRange` keys. Leave unset to let Docker choose a subnet.", PlanModifiers: requiresReplaceString(), }, "server_id": optionalReplaceString("Remote server to create the network on. Omit to use the Dokploy host itself."), "organization_id": computedString("Organization that owns the network."), "created_at": computedString("RFC 3339 timestamp of when the network was created."), }, }, } } // replaceBool is an optional boolean with a fixed default that cannot be // changed in place. func replaceBool(description string, def bool) schema.BoolAttribute { return schema.BoolAttribute{ Optional: true, Computed: true, MarkdownDescription: description, Default: booldefault.StaticBool(def), PlanModifiers: []planmodifier.Bool{boolplanmodifier.RequiresReplace()}, } }