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:
467
internal/provider/data_sources.go
Normal file
467
internal/provider/data_sources.go
Normal file
@@ -0,0 +1,467 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user