From 1ac9019b5e850f61f7368da5eb15999e4bc26ffe Mon Sep 17 00:00:00 2001 From: Martin Helmich Date: Fri, 5 Jan 2024 13:10:15 +0100 Subject: [PATCH 1/8] Refactor generation of common schema attributes --- internal/provider/provider.go | 2 + .../provider/resource/common/attrbuilder.go | 55 +++++++++++++++++++ .../resource/cronjobresource/resource.go | 33 ++--------- .../mysqldatabaseresource/resource.go | 23 ++------ .../resource/projectresource/resource.go | 16 ++---- 5 files changed, 72 insertions(+), 57 deletions(-) create mode 100644 internal/provider/resource/common/attrbuilder.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 7cad4a6..25e0bac 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -10,6 +10,7 @@ import ( "github.com/mittwald/terraform-provider-mittwald/internal/provider/resource/cronjobresource" "github.com/mittwald/terraform-provider-mittwald/internal/provider/resource/mysqldatabaseresource" "github.com/mittwald/terraform-provider-mittwald/internal/provider/resource/projectresource" + "github.com/mittwald/terraform-provider-mittwald/internal/provider/resource/virtualhostresource" "os" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -106,6 +107,7 @@ func (p *MittwaldProvider) Resources(_ context.Context) []func() resource.Resour appresource.New, mysqldatabaseresource.New, cronjobresource.New, + virtualhostresource.New, } } diff --git a/internal/provider/resource/common/attrbuilder.go b/internal/provider/resource/common/attrbuilder.go new file mode 100644 index 0000000..177bb46 --- /dev/null +++ b/internal/provider/resource/common/attrbuilder.go @@ -0,0 +1,55 @@ +package common + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" +) + +type AttributeBuilder struct { + resourceName string +} + +func AttributeBuilderFor(name string) *AttributeBuilder { + return &AttributeBuilder{ + resourceName: name, + } +} + +func (b *AttributeBuilder) Id() schema.Attribute { + return schema.StringAttribute{ + Computed: true, + MarkdownDescription: fmt.Sprintf("The generated %s ID", b.resourceName), + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + } +} + +func (b *AttributeBuilder) ProjectId() schema.Attribute { + return schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("The ID of the project the %s belongs to", b.resourceName), + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + } +} + +func (b *AttributeBuilder) AppId() schema.Attribute { + return schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("The ID of the app the %s belongs to", b.resourceName), + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + } +} + +func (b *AttributeBuilder) Description() schema.Attribute { + return schema.StringAttribute{ + Required: true, + MarkdownDescription: fmt.Sprintf("Description for your %s", b.resourceName), + } +} diff --git a/internal/provider/resource/cronjobresource/resource.go b/internal/provider/resource/cronjobresource/resource.go index a45c958..51ca9c9 100644 --- a/internal/provider/resource/cronjobresource/resource.go +++ b/internal/provider/resource/cronjobresource/resource.go @@ -6,11 +6,10 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/mittwald/terraform-provider-mittwald/api/mittwaldv2" "github.com/mittwald/terraform-provider-mittwald/internal/provider/providerutil" + "github.com/mittwald/terraform-provider-mittwald/internal/provider/resource/common" ) // Ensure provider defined types fully satisfy framework interfaces. @@ -30,35 +29,15 @@ func (r *Resource) Metadata(_ context.Context, req resource.MetadataRequest, res } func (r *Resource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + builder := common.AttributeBuilderFor("cronjob") resp.Schema = schema.Schema{ MarkdownDescription: "This resource models a cron job.", Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - MarkdownDescription: "The generated cron job ID", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "project_id": schema.StringAttribute{ - MarkdownDescription: "The ID of the project the cron job belongs to", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "app_id": schema.StringAttribute{ - MarkdownDescription: "The ID of the app the cron job belongs to", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "description": schema.StringAttribute{ - Required: true, - MarkdownDescription: "Description for your cron job", - }, + "id": builder.Id(), + "project_id": builder.ProjectId(), + "app_id": builder.AppId(), + "description": builder.Description(), "destination": modelDestinationSchema, "interval": schema.StringAttribute{ Required: true, diff --git a/internal/provider/resource/mysqldatabaseresource/resource.go b/internal/provider/resource/mysqldatabaseresource/resource.go index 034e3df..ec6240f 100644 --- a/internal/provider/resource/mysqldatabaseresource/resource.go +++ b/internal/provider/resource/mysqldatabaseresource/resource.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/mittwald/terraform-provider-mittwald/api/mittwaldv2" "github.com/mittwald/terraform-provider-mittwald/internal/provider/providerutil" + "github.com/mittwald/terraform-provider-mittwald/internal/provider/resource/common" "time" ) @@ -34,17 +35,12 @@ func (d *Resource) Metadata(_ context.Context, request resource.MetadataRequest, } func (d *Resource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { + builder := common.AttributeBuilderFor("database") response.Schema = schema.Schema{ MarkdownDescription: "Models a MySQL database on the mittwald plattform", Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - MarkdownDescription: "ID of this database", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, + "id": builder.Id(), "version": schema.StringAttribute{ Required: true, MarkdownDescription: "Version of the database, e.g. `5.7`", @@ -59,17 +55,8 @@ func (d *Resource) Schema(_ context.Context, _ resource.SchemaRequest, response stringplanmodifier.UseStateForUnknown(), }, }, - "project_id": schema.StringAttribute{ - MarkdownDescription: "ID of the project this database belongs to", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "description": schema.StringAttribute{ - Required: true, - MarkdownDescription: "Description for your database", - }, + "project_id": builder.ProjectId(), + "description": builder.Description(), "hostname": schema.StringAttribute{ Computed: true, MarkdownDescription: "Hostname of the database; this is the hostname that you should use within the platform to connect to the database.", diff --git a/internal/provider/resource/projectresource/resource.go b/internal/provider/resource/projectresource/resource.go index d153d56..2c44f4c 100644 --- a/internal/provider/resource/projectresource/resource.go +++ b/internal/provider/resource/projectresource/resource.go @@ -9,10 +9,10 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/mittwald/terraform-provider-mittwald/api/mittwaldv2" "github.com/mittwald/terraform-provider-mittwald/internal/provider/providerutil" + "github.com/mittwald/terraform-provider-mittwald/internal/provider/resource/common" "time" ) @@ -34,6 +34,7 @@ func (r *Resource) Metadata(_ context.Context, req resource.MetadataRequest, res } func (r *Resource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + builder := common.AttributeBuilderFor("project") resp.Schema = schema.Schema{ MarkdownDescription: "This resource models a project on the mittwald cloud platform; a project is either provisioned on a server (in which case a `server_id` is required), or as a stand-alone project (currently not supported).", @@ -42,17 +43,8 @@ func (r *Resource) Schema(_ context.Context, _ resource.SchemaRequest, resp *res MarkdownDescription: "ID of the server this project belongs to", Optional: true, }, - "id": schema.StringAttribute{ - Computed: true, - MarkdownDescription: "The generated project ID", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "description": schema.StringAttribute{ - Required: true, - MarkdownDescription: "Description for your project", - }, + "id": builder.Id(), + "description": builder.Description(), "directories": schema.MapAttribute{ Computed: true, MarkdownDescription: "Contains a map of data directories within the project", From 9692c11626ce4fe0b9a7a3a699eec1e6e636c375 Mon Sep 17 00:00:00 2001 From: Martin Helmich Date: Tue, 9 Jan 2024 12:53:06 +0100 Subject: [PATCH 2/8] Add client for domains --- api/mittwaldv2/client_builder.go | 7 ++++ api/mittwaldv2/client_domain.go | 71 ++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 api/mittwaldv2/client_domain.go diff --git a/api/mittwaldv2/client_builder.go b/api/mittwaldv2/client_builder.go index 41da6fa..8b1621b 100644 --- a/api/mittwaldv2/client_builder.go +++ b/api/mittwaldv2/client_builder.go @@ -5,6 +5,7 @@ type ClientBuilder interface { App() AppClient Database() DatabaseClient Cronjob() CronjobClient + Domain() DomainClient } type clientBuilder struct { @@ -34,3 +35,9 @@ func (b *clientBuilder) Cronjob() CronjobClient { client: b.internalClient, } } + +func (b *clientBuilder) Domain() DomainClient { + return &domainClient{ + client: b.internalClient, + } +} diff --git a/api/mittwaldv2/client_domain.go b/api/mittwaldv2/client_domain.go new file mode 100644 index 0000000..75031a5 --- /dev/null +++ b/api/mittwaldv2/client_domain.go @@ -0,0 +1,71 @@ +package mittwaldv2 + +import ( + "context" + "github.com/google/uuid" +) + +type DomainClient interface { + GetIngress(ctx context.Context, ingressID string) (*DeMittwaldV1IngressIngress, error) + CreateIngress(ctx context.Context, projectID string, body IngressCreateJSONRequestBody) (string, error) + UpdateIngressPaths(ctx context.Context, ingressID string, body IngressPathsJSONRequestBody) error + DeleteIngress(ctx context.Context, ingressID string) error +} + +type domainClient struct { + client ClientWithResponsesInterface +} + +func (c *domainClient) GetIngress(ctx context.Context, ingressID string) (*DeMittwaldV1IngressIngress, error) { + resp, err := c.client.IngressGetSpecificWithResponse(ctx, uuid.MustParse(ingressID)) + if err != nil { + return nil, err + } + + if resp.JSON200 == nil { + return nil, errUnexpectedStatus(resp.StatusCode(), resp.Body) + } + + return resp.JSON200, nil +} + +func (c *domainClient) CreateIngress(ctx context.Context, projectID string, body IngressCreateJSONRequestBody) (string, error) { + resp, err := c.client.IngressCreateWithResponse(ctx, body) + if err != nil { + return "", err + } + + body.ProjectId = uuid.MustParse(projectID) + + if resp.JSON201 == nil { + return "", errUnexpectedStatus(resp.StatusCode(), resp.Body) + } + + return resp.JSON201.Id.String(), nil +} + +func (c *domainClient) UpdateIngressPaths(ctx context.Context, ingressID string, body IngressPathsJSONRequestBody) error { + resp, err := c.client.IngressPathsWithResponse(ctx, uuid.MustParse(ingressID), body) + if err != nil { + return err + } + + if resp.StatusCode() != 204 { + return errUnexpectedStatus(resp.StatusCode(), resp.Body) + } + + return nil +} + +func (c *domainClient) DeleteIngress(ctx context.Context, ingressID string) error { + resp, err := c.client.IngressDeleteWithResponse(ctx, uuid.MustParse(ingressID)) + if err != nil { + return err + } + + if resp.StatusCode() != 204 { + return errUnexpectedStatus(resp.StatusCode(), resp.Body) + } + + return nil +} From f930186bf1aac1c7ca861739186cc6b5ea1b6ed6 Mon Sep 17 00:00:00 2001 From: Martin Helmich Date: Tue, 9 Jan 2024 12:53:35 +0100 Subject: [PATCH 3/8] Re-generate documentation --- docs/resources/cronjob.md | 8 ++++---- docs/resources/mysql_database.md | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/resources/cronjob.md b/docs/resources/cronjob.md index 4c70ee9..ecaa449 100644 --- a/docs/resources/cronjob.md +++ b/docs/resources/cronjob.md @@ -35,11 +35,11 @@ resource "mittwald_cronjob" "demo" { ### Required -- `app_id` (String) The ID of the app the cron job belongs to -- `description` (String) Description for your cron job +- `app_id` (String) The ID of the app the cronjob belongs to +- `description` (String) Description for your cronjob - `destination` (Attributes) Models the action to be executed by the cron job. Exactly one of `url` or `command` must be set. (see [below for nested schema](#nestedatt--destination)) - `interval` (String) The interval of the cron job; this should be a cron expression -- `project_id` (String) The ID of the project the cron job belongs to +- `project_id` (String) The ID of the project the cronjob belongs to ### Optional @@ -47,7 +47,7 @@ resource "mittwald_cronjob" "demo" { ### Read-Only -- `id` (String) The generated cron job ID +- `id` (String) The generated cronjob ID ### Nested Schema for `destination` diff --git a/docs/resources/mysql_database.md b/docs/resources/mysql_database.md index cec0980..56d553f 100644 --- a/docs/resources/mysql_database.md +++ b/docs/resources/mysql_database.md @@ -42,7 +42,7 @@ resource "mittwald_mysql_database" "foobar_database" { ### Required - `description` (String) Description for your database -- `project_id` (String) ID of the project this database belongs to +- `project_id` (String) The ID of the project the database belongs to - `user` (Attributes) (see [below for nested schema](#nestedatt--user)) - `version` (String) Version of the database, e.g. `5.7` @@ -53,7 +53,7 @@ resource "mittwald_mysql_database" "foobar_database" { ### Read-Only - `hostname` (String) Hostname of the database; this is the hostname that you should use within the platform to connect to the database. -- `id` (String) ID of this database +- `id` (String) The generated database ID - `name` (String) Name of the database, e.g. `db-XXXXX` From 971514194534293c61047bbc64085cc96c55a75e Mon Sep 17 00:00:00 2001 From: Martin Helmich Date: Tue, 9 Jan 2024 12:53:54 +0100 Subject: [PATCH 4/8] Add virtualhost resource --- docs/resources/virtualhost.md | 24 +++ .../resource/virtualhostresource/model.go | 25 +++ .../resource/virtualhostresource/model_api.go | 104 ++++++++++++ .../resource/virtualhostresource/resource.go | 151 ++++++++++++++++++ 4 files changed, 304 insertions(+) create mode 100644 docs/resources/virtualhost.md create mode 100644 internal/provider/resource/virtualhostresource/model.go create mode 100644 internal/provider/resource/virtualhostresource/model_api.go create mode 100644 internal/provider/resource/virtualhostresource/resource.go diff --git a/docs/resources/virtualhost.md b/docs/resources/virtualhost.md new file mode 100644 index 0000000..d3f3da8 --- /dev/null +++ b/docs/resources/virtualhost.md @@ -0,0 +1,24 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "mittwald_virtualhost Resource - terraform-provider-mittwald" +subcategory: "" +description: |- + This resource models a virtualhost. +--- + +# mittwald_virtualhost (Resource) + +This resource models a virtualhost. + + + + +## Schema + +### Required + +- `project_id` (String) The ID of the project the virtualhost belongs to + +### Read-Only + +- `id` (String) The generated virtualhost ID diff --git a/internal/provider/resource/virtualhostresource/model.go b/internal/provider/resource/virtualhostresource/model.go new file mode 100644 index 0000000..c78bfb6 --- /dev/null +++ b/internal/provider/resource/virtualhostresource/model.go @@ -0,0 +1,25 @@ +package virtualhostresource + +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type ResourceModel struct { + ID types.String `tfsdk:"id"` + ProjectID types.String `tfsdk:"project_id"` + Hostname types.String `tfsdk:"hostname"` + Paths types.Map `tfsdk:"paths"` +} + +type PathModel struct { + App types.String `tfsdk:"app"` + Redirect types.String `tfsdk:"redirect"` +} + +var pathType = types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "app": types.StringType, + "redirect": types.StringType, + }, +} diff --git a/internal/provider/resource/virtualhostresource/model_api.go b/internal/provider/resource/virtualhostresource/model_api.go new file mode 100644 index 0000000..298884a --- /dev/null +++ b/internal/provider/resource/virtualhostresource/model_api.go @@ -0,0 +1,104 @@ +package virtualhostresource + +import ( + "context" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/mittwald/terraform-provider-mittwald/api/mittwaldv2" + "github.com/mittwald/terraform-provider-mittwald/internal/valueutil" +) + +func (m *ResourceModel) FromAPIModel(ctx context.Context, apiModel *mittwaldv2.DeMittwaldV1IngressIngress) (res diag.Diagnostics) { + m.ID = valueutil.StringerOrNull(apiModel.Id) + m.ProjectID = valueutil.StringerOrNull(apiModel.ProjectId) + m.Hostname = types.StringValue(apiModel.Hostname) + + pathObjs := make(map[string]attr.Value) + for _, ingressPath := range apiModel.Paths { + attrs := map[string]attr.Value{ + "app": types.StringNull(), + "redirect": types.StringNull(), + } + + if inst, err := ingressPath.Target.AsDeMittwaldV1IngressTargetInstallation(); err == nil && inst.InstallationId != uuid.Nil { + attrs["app"] = types.StringValue(inst.InstallationId.String()) + } + + if url, err := ingressPath.Target.AsDeMittwaldV1IngressTargetUrl(); err == nil && url.Url != "" { + attrs["redirect"] = types.StringValue(url.Url) + } + + obj, d := types.ObjectValue(pathType.AttrTypes, attrs) + res.Append(d...) + + pathObjs[ingressPath.Path] = obj + } + + p, d := types.MapValue(pathType, pathObjs) + res.Append(d...) + + m.Paths = p + + return +} + +func (m *ResourceModel) ToCreateRequest(ctx context.Context, d *diag.Diagnostics) mittwaldv2.IngressCreateJSONRequestBody { + return mittwaldv2.IngressCreateJSONRequestBody{ + Hostname: m.Hostname.ValueString(), + Paths: m.pathsAsAPIModel(ctx, d), + } +} + +func (m *ResourceModel) ToUpdateRequest(ctx context.Context, d *diag.Diagnostics, current *ResourceModel) mittwaldv2.IngressPathsJSONRequestBody { + return m.pathsAsAPIModel(ctx, d) +} + +func (m *PathModel) toAPIModel(p path.Path, urlPathPrefix string, d *diag.Diagnostics) mittwaldv2.DeMittwaldV1IngressPath { + model := mittwaldv2.DeMittwaldV1IngressPath{ + Path: urlPathPrefix, + } + + _ = model.Target.FromDeMittwaldV1IngressTargetUseDefaultPage(mittwaldv2.DeMittwaldV1IngressTargetUseDefaultPage{ + UseDefaultPage: true, + }) + + if !m.App.IsNull() { + err := model.Target.FromDeMittwaldV1IngressTargetInstallation(mittwaldv2.DeMittwaldV1IngressTargetInstallation{ + InstallationId: uuid.MustParse(m.App.ValueString()), + }) + + if err != nil { + d.AddAttributeError(p.AtName("app"), "error while build app installation path target", err.Error()) + } + } + + if !m.Redirect.IsNull() { + err := model.Target.FromDeMittwaldV1IngressTargetUrl(mittwaldv2.DeMittwaldV1IngressTargetUrl{ + Url: m.Redirect.ValueString(), + }) + + if err != nil { + d.AddAttributeError(p.AtName("redirect"), "error while build redirect path target", err.Error()) + } + } + + return model +} + +func (m *ResourceModel) pathsAsAPIModel(ctx context.Context, res *diag.Diagnostics) []mittwaldv2.DeMittwaldV1IngressPath { + out := make([]mittwaldv2.DeMittwaldV1IngressPath, 0, 0) + intermediate := map[string]PathModel{} + + res.Append(m.Paths.ElementsAs(ctx, &intermediate, false)...) + + attrPath := path.Root("paths") + + for p, model := range intermediate { + out = append(out, model.toAPIModel(attrPath.AtMapKey(p), p, res)) + } + + return out +} diff --git a/internal/provider/resource/virtualhostresource/resource.go b/internal/provider/resource/virtualhostresource/resource.go new file mode 100644 index 0000000..1cc3958 --- /dev/null +++ b/internal/provider/resource/virtualhostresource/resource.go @@ -0,0 +1,151 @@ +package virtualhostresource + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/mittwald/terraform-provider-mittwald/api/mittwaldv2" + "github.com/mittwald/terraform-provider-mittwald/internal/provider/providerutil" + "github.com/mittwald/terraform-provider-mittwald/internal/provider/resource/common" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &Resource{} +var _ resource.ResourceWithImportState = &Resource{} + +func New() resource.Resource { + return &Resource{} +} + +type Resource struct { + client mittwaldv2.ClientBuilder +} + +func (r *Resource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_virtualhost" +} + +func (r *Resource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + builder := common.AttributeBuilderFor("virtualhost") + resp.Schema = schema.Schema{ + MarkdownDescription: "This resource models a virtualhost.", + + Attributes: map[string]schema.Attribute{ + "id": builder.Id(), + "project_id": builder.ProjectId(), + "hostname": schema.StringAttribute{ + Description: "The desired hostname for the virtualhost.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "paths": schema.MapNestedAttribute{ + Description: "The desired paths for the virtualhost.", + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "app": schema.StringAttribute{ + MarkdownDescription: "The ID of an app installation that this path should point to.", + Optional: true, + }, + "redirect": schema.StringAttribute{ + MarkdownDescription: "The URL to redirect to.", + Optional: true, + }, + }, + }, + }, + }, + } +} + +func (r *Resource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + r.client = providerutil.ClientFromProviderData(req.ProviderData, &resp.Diagnostics) +} + +func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data ResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + id := providerutil. + Try[string](&resp.Diagnostics, "API error while creating virtual host"). + DoVal(r.client.Domain().CreateIngress(ctx, data.ProjectID.ValueString(), data.ToCreateRequest(ctx, &resp.Diagnostics))) + + if resp.Diagnostics.HasError() { + return + } + + data.ID = types.StringValue(id) + + resp.Diagnostics.Append(r.read(ctx, &data)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + data := ResourceModel{} + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(r.read(ctx, &data)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *Resource) read(ctx context.Context, data *ResourceModel) (res diag.Diagnostics) { + ingress := providerutil. + Try[*mittwaldv2.DeMittwaldV1IngressIngress](&res, "API error while fetching ingress"). + DoVal(r.client.Domain().GetIngress(ctx, data.ID.ValueString())) + + if res.HasError() { + return + } + + res.Append(data.FromAPIModel(ctx, ingress)...) + + return +} + +func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var planData, stateData ResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &stateData)...) + resp.Diagnostics.Append(req.Plan.Get(ctx, &planData)...) + + body := planData.ToUpdateRequest(ctx, &resp.Diagnostics, &stateData) + if resp.Diagnostics.HasError() { + return + } + + providerutil. + Try[any](&resp.Diagnostics, "API error while updating virtual host"). + Do(r.client.Domain().UpdateIngressPaths(ctx, planData.ID.ValueString(), body)) + + resp.Diagnostics.Append(r.read(ctx, &stateData)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &stateData)...) +} + +func (r *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data ResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + providerutil. + Try[any](&resp.Diagnostics, "API error while deleting virtual host"). + Do(r.client.Domain().DeleteIngress(ctx, data.ID.ValueString())) +} + +func (r *Resource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} From 4c258dfc047836c072c03142cbc0d5bf6044917e Mon Sep 17 00:00:00 2001 From: Martin Helmich Date: Fri, 12 Apr 2024 15:00:59 +0200 Subject: [PATCH 5/8] Re-generate documentation --- docs/resources/virtualhost.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/resources/virtualhost.md b/docs/resources/virtualhost.md index d3f3da8..13c961f 100644 --- a/docs/resources/virtualhost.md +++ b/docs/resources/virtualhost.md @@ -17,8 +17,18 @@ This resource models a virtualhost. ### Required +- `hostname` (String) The desired hostname for the virtualhost. +- `paths` (Attributes Map) The desired paths for the virtualhost. (see [below for nested schema](#nestedatt--paths)) - `project_id` (String) The ID of the project the virtualhost belongs to ### Read-Only - `id` (String) The generated virtualhost ID + + +### Nested Schema for `paths` + +Optional: + +- `app` (String) The ID of an app installation that this path should point to. +- `redirect` (String) The URL to redirect to. From 1414f4a06c5a471125cce51b55cb8d4f1c1c689e Mon Sep 17 00:00:00 2001 From: Martin Helmich Date: Fri, 12 Apr 2024 15:02:24 +0200 Subject: [PATCH 6/8] Mention virtualhost resource in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 71e08e5..ff62d5a 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ This provider offers the following resources: - [`mittwald_app`](https://registry.terraform.io/providers/mittwald/mittwald/latest/docs/resources/app) - [`mittwald_mysql_database`](https://registry.terraform.io/providers/mittwald/mittwald/latest/docs/resources/mysql_database) - [`mittwald_cronjob`](https://registry.terraform.io/providers/mittwald/mittwald/latest/docs/resources/cronjob) +- [`mittwald_virtualhost`](https://registry.terraform.io/providers/mittwald/mittwald/latest/docs/resources/virtualhost) and the following data sources: From 194a628e4492f04e2c0596c78760bbacfa202278 Mon Sep 17 00:00:00 2001 From: Martin Helmich Date: Fri, 12 Apr 2024 15:10:42 +0200 Subject: [PATCH 7/8] Adjust to renamed API operations --- api/mittwaldv2/client_domain.go | 16 ++++++++-------- .../resource/virtualhostresource/model_api.go | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/api/mittwaldv2/client_domain.go b/api/mittwaldv2/client_domain.go index 75031a5..93b8c6a 100644 --- a/api/mittwaldv2/client_domain.go +++ b/api/mittwaldv2/client_domain.go @@ -7,8 +7,8 @@ import ( type DomainClient interface { GetIngress(ctx context.Context, ingressID string) (*DeMittwaldV1IngressIngress, error) - CreateIngress(ctx context.Context, projectID string, body IngressCreateJSONRequestBody) (string, error) - UpdateIngressPaths(ctx context.Context, ingressID string, body IngressPathsJSONRequestBody) error + CreateIngress(ctx context.Context, projectID string, body IngressCreateIngressJSONRequestBody) (string, error) + UpdateIngressPaths(ctx context.Context, ingressID string, body IngressUpdateIngressPathsJSONRequestBody) error DeleteIngress(ctx context.Context, ingressID string) error } @@ -17,7 +17,7 @@ type domainClient struct { } func (c *domainClient) GetIngress(ctx context.Context, ingressID string) (*DeMittwaldV1IngressIngress, error) { - resp, err := c.client.IngressGetSpecificWithResponse(ctx, uuid.MustParse(ingressID)) + resp, err := c.client.IngressGetIngressWithResponse(ctx, uuid.MustParse(ingressID)) if err != nil { return nil, err } @@ -29,8 +29,8 @@ func (c *domainClient) GetIngress(ctx context.Context, ingressID string) (*DeMit return resp.JSON200, nil } -func (c *domainClient) CreateIngress(ctx context.Context, projectID string, body IngressCreateJSONRequestBody) (string, error) { - resp, err := c.client.IngressCreateWithResponse(ctx, body) +func (c *domainClient) CreateIngress(ctx context.Context, projectID string, body IngressCreateIngressJSONRequestBody) (string, error) { + resp, err := c.client.IngressCreateIngressWithResponse(ctx, body) if err != nil { return "", err } @@ -44,8 +44,8 @@ func (c *domainClient) CreateIngress(ctx context.Context, projectID string, body return resp.JSON201.Id.String(), nil } -func (c *domainClient) UpdateIngressPaths(ctx context.Context, ingressID string, body IngressPathsJSONRequestBody) error { - resp, err := c.client.IngressPathsWithResponse(ctx, uuid.MustParse(ingressID), body) +func (c *domainClient) UpdateIngressPaths(ctx context.Context, ingressID string, body IngressUpdateIngressPathsJSONRequestBody) error { + resp, err := c.client.IngressUpdateIngressPathsWithResponse(ctx, uuid.MustParse(ingressID), body) if err != nil { return err } @@ -58,7 +58,7 @@ func (c *domainClient) UpdateIngressPaths(ctx context.Context, ingressID string, } func (c *domainClient) DeleteIngress(ctx context.Context, ingressID string) error { - resp, err := c.client.IngressDeleteWithResponse(ctx, uuid.MustParse(ingressID)) + resp, err := c.client.IngressDeleteIngressWithResponse(ctx, uuid.MustParse(ingressID)) if err != nil { return err } diff --git a/internal/provider/resource/virtualhostresource/model_api.go b/internal/provider/resource/virtualhostresource/model_api.go index 298884a..39274ed 100644 --- a/internal/provider/resource/virtualhostresource/model_api.go +++ b/internal/provider/resource/virtualhostresource/model_api.go @@ -45,14 +45,14 @@ func (m *ResourceModel) FromAPIModel(ctx context.Context, apiModel *mittwaldv2.D return } -func (m *ResourceModel) ToCreateRequest(ctx context.Context, d *diag.Diagnostics) mittwaldv2.IngressCreateJSONRequestBody { - return mittwaldv2.IngressCreateJSONRequestBody{ +func (m *ResourceModel) ToCreateRequest(ctx context.Context, d *diag.Diagnostics) mittwaldv2.IngressCreateIngressJSONRequestBody { + return mittwaldv2.IngressCreateIngressJSONRequestBody{ Hostname: m.Hostname.ValueString(), Paths: m.pathsAsAPIModel(ctx, d), } } -func (m *ResourceModel) ToUpdateRequest(ctx context.Context, d *diag.Diagnostics, current *ResourceModel) mittwaldv2.IngressPathsJSONRequestBody { +func (m *ResourceModel) ToUpdateRequest(ctx context.Context, d *diag.Diagnostics, current *ResourceModel) mittwaldv2.IngressUpdateIngressPathsJSONRequestBody { return m.pathsAsAPIModel(ctx, d) } From d84b9bbfaff01b4c573d886acb78e77360f03fdb Mon Sep 17 00:00:00 2001 From: Martin Helmich Date: Fri, 12 Apr 2024 15:25:25 +0200 Subject: [PATCH 8/8] Fix linter issues --- internal/provider/resource/virtualhostresource/model_api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/resource/virtualhostresource/model_api.go b/internal/provider/resource/virtualhostresource/model_api.go index 39274ed..c8fc7ac 100644 --- a/internal/provider/resource/virtualhostresource/model_api.go +++ b/internal/provider/resource/virtualhostresource/model_api.go @@ -89,7 +89,7 @@ func (m *PathModel) toAPIModel(p path.Path, urlPathPrefix string, d *diag.Diagno } func (m *ResourceModel) pathsAsAPIModel(ctx context.Context, res *diag.Diagnostics) []mittwaldv2.DeMittwaldV1IngressPath { - out := make([]mittwaldv2.DeMittwaldV1IngressPath, 0, 0) + out := make([]mittwaldv2.DeMittwaldV1IngressPath, 0) intermediate := map[string]PathModel{} res.Append(m.Paths.ElementsAs(ctx, &intermediate, false)...)