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,87 @@
package provider
import (
"encoding/json"
"fmt"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
)
type projectModel struct {
ID types.String `tfsdk:"id" dokploy:"projectId,id"`
Name types.String `tfsdk:"name" dokploy:"name"`
Description types.String `tfsdk:"description" dokploy:"description,nullable"`
Env types.String `tfsdk:"env" dokploy:"env"`
CreatedAt types.String `tfsdk:"created_at" dokploy:"createdAt,ro"`
OrgID types.String `tfsdk:"organization_id" dokploy:"organizationId,ro"`
// Derived in PostRead from the nested `environments` array.
DefaultEnvironmentID types.String `tfsdk:"default_environment_id"`
}
func projectResource() ResourceSpec {
return ResourceSpec{
Name: "project",
CreateProc: "project.create",
ReadProc: "project.one",
UpdateProc: "project.update",
DeleteProc: "project.remove",
CreateResponseKey: "project",
NewModel: func() any { return &projectModel{} },
PostRead: projectPostRead,
Schema: schema.Schema{
MarkdownDescription: "A Dokploy project: the top-level container for environments and services.\n\n" +
"Creating a project automatically creates a default `production` environment. Its ID is exposed as " +
"`default_environment_id`, so services can be attached without declaring a separate " +
"`dokploy_environment` resource.",
Attributes: map[string]schema.Attribute{
"id": computedID("Unique project identifier."),
"name": requiredString("Display name of the project."),
"description": optionalString("Free-form description."),
"env": optionalComputedString("Project-wide environment variables in `KEY=value` format, one per " +
"line. These are shared with every service in the project."),
"created_at": computedString("RFC 3339 timestamp of when the project was created."),
"organization_id": computedString("Organization that owns the project."),
"default_environment_id": computedString("ID of the `production` environment that Dokploy creates " +
"automatically with the project."),
},
},
}
}
// projectPostRead extracts the default environment from a `project.one`
// response so it can be referenced directly.
func projectPostRead(raw json.RawMessage, model any) error {
project, ok := model.(*projectModel)
if !ok {
return fmt.Errorf("expected *projectModel, got %T", model)
}
var payload struct {
Environments []struct {
EnvironmentID string `json:"environmentId"`
Name string `json:"name"`
IsDefault bool `json:"isDefault"`
} `json:"environments"`
}
if err := json.Unmarshal(raw, &payload); err != nil {
return fmt.Errorf("decoding project environments: %w", err)
}
project.DefaultEnvironmentID = types.StringNull()
for _, env := range payload.Environments {
if env.IsDefault {
project.DefaultEnvironmentID = types.StringValue(env.EnvironmentID)
return nil
}
}
// Fall back to a conventionally named environment if none is flagged.
for _, env := range payload.Environments {
if env.Name == "production" {
project.DefaultEnvironmentID = types.StringValue(env.EnvironmentID)
return nil
}
}
return nil
}