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 }