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.
468 lines
17 KiB
Go
468 lines
17 KiB
Go
package provider
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/terraform-plugin-framework/attr"
|
|
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
|
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
|
|
|
"github.com/maxvojtkov/terraform-provider-dokploy/internal/client"
|
|
)
|
|
|
|
// baseDataSource wires the shared API client into each data source.
|
|
type baseDataSource struct {
|
|
api *client.Client
|
|
}
|
|
|
|
func (d *baseDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
|
if req.ProviderData == nil {
|
|
return
|
|
}
|
|
api, ok := req.ProviderData.(*client.Client)
|
|
if !ok {
|
|
resp.Diagnostics.AddError(
|
|
"Unexpected provider data",
|
|
fmt.Sprintf("Expected *client.Client, got %T. This is a bug in the provider.", req.ProviderData),
|
|
)
|
|
return
|
|
}
|
|
d.api = api
|
|
}
|
|
|
|
// ------------------------------------------------------------ dokploy_project
|
|
|
|
type projectDataSourceModel struct {
|
|
ID types.String `tfsdk:"id"`
|
|
Name types.String `tfsdk:"name"`
|
|
Description types.String `tfsdk:"description"`
|
|
Env types.String `tfsdk:"env"`
|
|
CreatedAt types.String `tfsdk:"created_at"`
|
|
OrganizationID types.String `tfsdk:"organization_id"`
|
|
DefaultEnvironmentID types.String `tfsdk:"default_environment_id"`
|
|
Environments types.List `tfsdk:"environments"`
|
|
}
|
|
|
|
type projectDataSource struct{ baseDataSource }
|
|
|
|
func newProjectDataSource() datasource.DataSource { return &projectDataSource{} }
|
|
|
|
func (d *projectDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
|
resp.TypeName = req.ProviderTypeName + "_project"
|
|
}
|
|
|
|
var environmentObjectType = types.ObjectType{AttrTypes: map[string]attr.Type{
|
|
"id": types.StringType,
|
|
"name": types.StringType,
|
|
"is_default": types.BoolType,
|
|
}}
|
|
|
|
func (d *projectDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
MarkdownDescription: "Look up an existing Dokploy project by ID, including its environments.",
|
|
Attributes: map[string]schema.Attribute{
|
|
"id": schema.StringAttribute{Required: true, MarkdownDescription: "Project identifier."},
|
|
"name": schema.StringAttribute{Computed: true, MarkdownDescription: "Project name."},
|
|
"description": schema.StringAttribute{Computed: true, MarkdownDescription: "Project description."},
|
|
"env": schema.StringAttribute{Computed: true, MarkdownDescription: "Project-wide environment variables."},
|
|
"created_at": schema.StringAttribute{Computed: true, MarkdownDescription: "Creation timestamp."},
|
|
"organization_id": schema.StringAttribute{Computed: true, MarkdownDescription: "Owning organization."},
|
|
"default_environment_id": schema.StringAttribute{
|
|
Computed: true,
|
|
MarkdownDescription: "ID of the project's default environment.",
|
|
},
|
|
"environments": schema.ListAttribute{
|
|
Computed: true,
|
|
ElementType: environmentObjectType,
|
|
MarkdownDescription: "Every environment in the project.",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (d *projectDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
|
var config projectDataSourceModel
|
|
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
raw, err := d.api.Query(ctx, "project.one", map[string]any{"projectId": config.ID.ValueString()})
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Failed to read project", err.Error())
|
|
return
|
|
}
|
|
|
|
var payload struct {
|
|
ProjectID string `json:"projectId"`
|
|
Name string `json:"name"`
|
|
Description *string `json:"description"`
|
|
Env string `json:"env"`
|
|
CreatedAt string `json:"createdAt"`
|
|
OrganizationID string `json:"organizationId"`
|
|
Environments []struct {
|
|
EnvironmentID string `json:"environmentId"`
|
|
Name string `json:"name"`
|
|
IsDefault bool `json:"isDefault"`
|
|
} `json:"environments"`
|
|
}
|
|
if err := json.Unmarshal(raw, &payload); err != nil {
|
|
resp.Diagnostics.AddError("Failed to decode project", err.Error())
|
|
return
|
|
}
|
|
|
|
config.Name = types.StringValue(payload.Name)
|
|
config.Description = optionalStringValue(payload.Description)
|
|
config.Env = types.StringValue(payload.Env)
|
|
config.CreatedAt = types.StringValue(payload.CreatedAt)
|
|
config.OrganizationID = types.StringValue(payload.OrganizationID)
|
|
config.DefaultEnvironmentID = types.StringNull()
|
|
|
|
elements := make([]attr.Value, 0, len(payload.Environments))
|
|
for _, env := range payload.Environments {
|
|
if env.IsDefault {
|
|
config.DefaultEnvironmentID = types.StringValue(env.EnvironmentID)
|
|
}
|
|
object, diags := types.ObjectValue(environmentObjectType.AttrTypes, map[string]attr.Value{
|
|
"id": types.StringValue(env.EnvironmentID),
|
|
"name": types.StringValue(env.Name),
|
|
"is_default": types.BoolValue(env.IsDefault),
|
|
})
|
|
resp.Diagnostics.Append(diags...)
|
|
elements = append(elements, object)
|
|
}
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
list, diags := types.ListValue(environmentObjectType, elements)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
config.Environments = list
|
|
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, &config)...)
|
|
}
|
|
|
|
// ----------------------------------------------------------- dokploy_projects
|
|
|
|
type projectsDataSourceModel struct {
|
|
Projects types.List `tfsdk:"projects"`
|
|
}
|
|
|
|
type projectsDataSource struct{ baseDataSource }
|
|
|
|
func newProjectsDataSource() datasource.DataSource { return &projectsDataSource{} }
|
|
|
|
func (d *projectsDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
|
resp.TypeName = req.ProviderTypeName + "_projects"
|
|
}
|
|
|
|
var projectSummaryType = types.ObjectType{AttrTypes: map[string]attr.Type{
|
|
"id": types.StringType,
|
|
"name": types.StringType,
|
|
"description": types.StringType,
|
|
"default_environment_id": types.StringType,
|
|
}}
|
|
|
|
func (d *projectsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
MarkdownDescription: "List every Dokploy project the API token can see.",
|
|
Attributes: map[string]schema.Attribute{
|
|
"projects": schema.ListAttribute{
|
|
Computed: true,
|
|
ElementType: projectSummaryType,
|
|
MarkdownDescription: "All visible projects.",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (d *projectsDataSource) Read(ctx context.Context, _ datasource.ReadRequest, resp *datasource.ReadResponse) {
|
|
raw, err := d.api.Query(ctx, "project.all", nil)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Failed to list projects", err.Error())
|
|
return
|
|
}
|
|
|
|
var payload []struct {
|
|
ProjectID string `json:"projectId"`
|
|
Name string `json:"name"`
|
|
Description *string `json:"description"`
|
|
Environments []struct {
|
|
EnvironmentID string `json:"environmentId"`
|
|
IsDefault bool `json:"isDefault"`
|
|
} `json:"environments"`
|
|
}
|
|
if err := json.Unmarshal(raw, &payload); err != nil {
|
|
resp.Diagnostics.AddError("Failed to decode projects", err.Error())
|
|
return
|
|
}
|
|
|
|
elements := make([]attr.Value, 0, len(payload))
|
|
for _, project := range payload {
|
|
defaultEnv := types.StringNull()
|
|
for _, env := range project.Environments {
|
|
if env.IsDefault {
|
|
defaultEnv = types.StringValue(env.EnvironmentID)
|
|
break
|
|
}
|
|
}
|
|
object, diags := types.ObjectValue(projectSummaryType.AttrTypes, map[string]attr.Value{
|
|
"id": types.StringValue(project.ProjectID),
|
|
"name": types.StringValue(project.Name),
|
|
"description": optionalStringValue(project.Description),
|
|
"default_environment_id": defaultEnv,
|
|
})
|
|
resp.Diagnostics.Append(diags...)
|
|
elements = append(elements, object)
|
|
}
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
list, diags := types.ListValue(projectSummaryType, elements)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, &projectsDataSourceModel{Projects: list})...)
|
|
}
|
|
|
|
// -------------------------------------------------------- dokploy_environment
|
|
|
|
type environmentDataSourceModel struct {
|
|
ID types.String `tfsdk:"id"`
|
|
Name types.String `tfsdk:"name"`
|
|
Description types.String `tfsdk:"description"`
|
|
ProjectID types.String `tfsdk:"project_id"`
|
|
Env types.String `tfsdk:"env"`
|
|
IsDefault types.Bool `tfsdk:"is_default"`
|
|
CreatedAt types.String `tfsdk:"created_at"`
|
|
}
|
|
|
|
type environmentDataSource struct{ baseDataSource }
|
|
|
|
func newEnvironmentDataSource() datasource.DataSource { return &environmentDataSource{} }
|
|
|
|
func (d *environmentDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
|
resp.TypeName = req.ProviderTypeName + "_environment"
|
|
}
|
|
|
|
func (d *environmentDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
MarkdownDescription: "Look up an existing environment by ID.",
|
|
Attributes: map[string]schema.Attribute{
|
|
"id": schema.StringAttribute{Required: true, MarkdownDescription: "Environment identifier."},
|
|
"name": schema.StringAttribute{Computed: true, MarkdownDescription: "Environment name."},
|
|
"description": schema.StringAttribute{Computed: true, MarkdownDescription: "Environment description."},
|
|
"project_id": schema.StringAttribute{Computed: true, MarkdownDescription: "Owning project."},
|
|
"env": schema.StringAttribute{Computed: true, MarkdownDescription: "Environment-wide variables."},
|
|
"is_default": schema.BoolAttribute{Computed: true, MarkdownDescription: "Whether this is the project's default environment."},
|
|
"created_at": schema.StringAttribute{Computed: true, MarkdownDescription: "Creation timestamp."},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (d *environmentDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
|
var config environmentDataSourceModel
|
|
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
raw, err := d.api.Query(ctx, "environment.one", map[string]any{"environmentId": config.ID.ValueString()})
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Failed to read environment", err.Error())
|
|
return
|
|
}
|
|
|
|
var payload struct {
|
|
Name string `json:"name"`
|
|
Description *string `json:"description"`
|
|
ProjectID string `json:"projectId"`
|
|
Env string `json:"env"`
|
|
IsDefault bool `json:"isDefault"`
|
|
CreatedAt string `json:"createdAt"`
|
|
}
|
|
if err := json.Unmarshal(raw, &payload); err != nil {
|
|
resp.Diagnostics.AddError("Failed to decode environment", err.Error())
|
|
return
|
|
}
|
|
|
|
config.Name = types.StringValue(payload.Name)
|
|
config.Description = optionalStringValue(payload.Description)
|
|
config.ProjectID = types.StringValue(payload.ProjectID)
|
|
config.Env = types.StringValue(payload.Env)
|
|
config.IsDefault = types.BoolValue(payload.IsDefault)
|
|
config.CreatedAt = types.StringValue(payload.CreatedAt)
|
|
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, &config)...)
|
|
}
|
|
|
|
// -------------------------------------------------------- dokploy_application
|
|
|
|
type applicationDataSourceModel struct {
|
|
ID types.String `tfsdk:"id"`
|
|
Name types.String `tfsdk:"name"`
|
|
AppName types.String `tfsdk:"app_name"`
|
|
Description types.String `tfsdk:"description"`
|
|
EnvironmentID types.String `tfsdk:"environment_id"`
|
|
ApplicationStatus types.String `tfsdk:"application_status"`
|
|
SourceType types.String `tfsdk:"source_type"`
|
|
BuildType types.String `tfsdk:"build_type"`
|
|
CreatedAt types.String `tfsdk:"created_at"`
|
|
}
|
|
|
|
type applicationDataSource struct{ baseDataSource }
|
|
|
|
func newApplicationDataSource() datasource.DataSource { return &applicationDataSource{} }
|
|
|
|
func (d *applicationDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
|
resp.TypeName = req.ProviderTypeName + "_application"
|
|
}
|
|
|
|
func (d *applicationDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
MarkdownDescription: "Look up an existing application by ID.",
|
|
Attributes: map[string]schema.Attribute{
|
|
"id": schema.StringAttribute{Required: true, MarkdownDescription: "Application identifier."},
|
|
"name": schema.StringAttribute{Computed: true, MarkdownDescription: "Application name."},
|
|
"app_name": schema.StringAttribute{Computed: true, MarkdownDescription: "Docker service name."},
|
|
"description": schema.StringAttribute{Computed: true, MarkdownDescription: "Application description."},
|
|
"environment_id": schema.StringAttribute{Computed: true, MarkdownDescription: "Owning environment."},
|
|
"application_status": schema.StringAttribute{Computed: true, MarkdownDescription: "Current status."},
|
|
"source_type": schema.StringAttribute{Computed: true, MarkdownDescription: "Source of the code or image."},
|
|
"build_type": schema.StringAttribute{Computed: true, MarkdownDescription: "Build strategy."},
|
|
"created_at": schema.StringAttribute{Computed: true, MarkdownDescription: "Creation timestamp."},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (d *applicationDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
|
var config applicationDataSourceModel
|
|
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
raw, err := d.api.Query(ctx, "application.one", map[string]any{"applicationId": config.ID.ValueString()})
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Failed to read application", err.Error())
|
|
return
|
|
}
|
|
|
|
var payload struct {
|
|
Name string `json:"name"`
|
|
AppName string `json:"appName"`
|
|
Description *string `json:"description"`
|
|
EnvironmentID string `json:"environmentId"`
|
|
ApplicationStatus string `json:"applicationStatus"`
|
|
SourceType string `json:"sourceType"`
|
|
BuildType string `json:"buildType"`
|
|
CreatedAt string `json:"createdAt"`
|
|
}
|
|
if err := json.Unmarshal(raw, &payload); err != nil {
|
|
resp.Diagnostics.AddError("Failed to decode application", err.Error())
|
|
return
|
|
}
|
|
|
|
config.Name = types.StringValue(payload.Name)
|
|
config.AppName = types.StringValue(payload.AppName)
|
|
config.Description = optionalStringValue(payload.Description)
|
|
config.EnvironmentID = types.StringValue(payload.EnvironmentID)
|
|
config.ApplicationStatus = types.StringValue(payload.ApplicationStatus)
|
|
config.SourceType = types.StringValue(payload.SourceType)
|
|
config.BuildType = types.StringValue(payload.BuildType)
|
|
config.CreatedAt = types.StringValue(payload.CreatedAt)
|
|
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, &config)...)
|
|
}
|
|
|
|
// ------------------------------------------------------------ dokploy_servers
|
|
|
|
type serversDataSourceModel struct {
|
|
Servers types.List `tfsdk:"servers"`
|
|
}
|
|
|
|
type serversDataSource struct{ baseDataSource }
|
|
|
|
func newServersDataSource() datasource.DataSource { return &serversDataSource{} }
|
|
|
|
func (d *serversDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
|
resp.TypeName = req.ProviderTypeName + "_servers"
|
|
}
|
|
|
|
var serverSummaryType = types.ObjectType{AttrTypes: map[string]attr.Type{
|
|
"id": types.StringType,
|
|
"name": types.StringType,
|
|
"ip_address": types.StringType,
|
|
"server_status": types.StringType,
|
|
}}
|
|
|
|
func (d *serversDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
MarkdownDescription: "List the remote servers registered with Dokploy. Use a server's `id` as the " +
|
|
"`server_id` of an application, stack, or database to deploy it away from the Dokploy host.",
|
|
Attributes: map[string]schema.Attribute{
|
|
"servers": schema.ListAttribute{
|
|
Computed: true,
|
|
ElementType: serverSummaryType,
|
|
MarkdownDescription: "All registered servers.",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (d *serversDataSource) Read(ctx context.Context, _ datasource.ReadRequest, resp *datasource.ReadResponse) {
|
|
raw, err := d.api.Query(ctx, "server.all", nil)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Failed to list servers", err.Error())
|
|
return
|
|
}
|
|
|
|
var payload []struct {
|
|
ServerID string `json:"serverId"`
|
|
Name string `json:"name"`
|
|
IPAddress *string `json:"ipAddress"`
|
|
ServerStatus *string `json:"serverStatus"`
|
|
}
|
|
if err := json.Unmarshal(raw, &payload); err != nil {
|
|
resp.Diagnostics.AddError("Failed to decode servers", err.Error())
|
|
return
|
|
}
|
|
|
|
elements := make([]attr.Value, 0, len(payload))
|
|
for _, server := range payload {
|
|
object, diags := types.ObjectValue(serverSummaryType.AttrTypes, map[string]attr.Value{
|
|
"id": types.StringValue(server.ServerID),
|
|
"name": types.StringValue(server.Name),
|
|
"ip_address": optionalStringValue(server.IPAddress),
|
|
"server_status": optionalStringValue(server.ServerStatus),
|
|
})
|
|
resp.Diagnostics.Append(diags...)
|
|
elements = append(elements, object)
|
|
}
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
list, diags := types.ListValue(serverSummaryType, elements)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, &serversDataSourceModel{Servers: list})...)
|
|
}
|
|
|
|
func optionalStringValue(value *string) types.String {
|
|
if value == nil {
|
|
return types.StringNull()
|
|
}
|
|
return types.StringValue(*value)
|
|
}
|