Files
terraform-provider-unifi/internal/provider/portal/resource_portal_file.go
Mateusz Filipowicz a133383b43 feat: support customizing guest portal logo and background with unifi_portal_file and unifi_setting_guest_access resources (#74)
* feat: support customizing guest portal logo and background with `unifi_portal_file` and `unifi_setting_guest_access` resources

* ci: run acceptance tests on go.mod changes

* f
2025-03-22 17:31:30 +01:00

180 lines
6.2 KiB
Go

package portal
import (
"context"
"fmt"
"github.com/filipowm/go-unifi/unifi"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/diag"
"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/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"os"
)
var (
_ resource.Resource = &portalFileResource{}
_ resource.ResourceWithConfigure = &portalFileResource{}
_ resource.ResourceWithImportState = &portalFileResource{}
_ base.Resource = &portalFileResource{}
)
type portalFileResource struct {
*base.GenericResource[*portalFileModel]
}
type portalFileModel struct {
base.Model
Filename types.String `tfsdk:"filename"`
FilePath types.String `tfsdk:"file_path"`
ContentType types.String `tfsdk:"content_type"`
FileSize types.Int64 `tfsdk:"file_size"`
MD5 types.String `tfsdk:"md5"`
URL types.String `tfsdk:"url"`
LastModified types.Int64 `tfsdk:"last_modified"`
}
func (m *portalFileModel) Merge(_ context.Context, data interface{}) diag.Diagnostics {
var diags diag.Diagnostics
portalFile, ok := data.(*unifi.PortalFile)
if !ok {
diags.AddError("Invalid data type", fmt.Sprintf("Expected *unifi.PortalFile, got: %T", data))
return diags
}
m.ID = types.StringValue(portalFile.ID)
m.Filename = types.StringValue(portalFile.Filename)
m.ContentType = types.StringValue(portalFile.ContentType)
m.FileSize = types.Int64Value(int64(portalFile.FileSize))
m.MD5 = types.StringValue(portalFile.MD5)
m.URL = types.StringValue(portalFile.URL)
m.LastModified = types.Int64Value(int64(portalFile.LastModified))
return diags
}
func (m *portalFileModel) AsUnifiModel(_ context.Context) (interface{}, diag.Diagnostics) {
// Not used for upload - we don't convert the model to a UniFi model
// The file path is used directly for upload
return nil, diag.Diagnostics{}
}
func NewPortalFileResource() resource.Resource {
return &portalFileResource{
GenericResource: base.NewGenericResource(
"unifi_portal_file",
func() *portalFileModel { return &portalFileModel{} },
base.ResourceFunctions{
Read: func(ctx context.Context, client *base.Client, site, id string) (interface{}, error) {
return client.GetPortalFile(ctx, site, id)
},
Create: nil, // Custom implementation in CreateWithContext
Update: nil, // Portal files cannot be updated, only replaced
Delete: func(ctx context.Context, client *base.Client, site, id string) error {
return client.DeletePortalFile(ctx, site, id)
},
},
),
}
}
func (r *portalFileResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "The `unifi_portal_file` resource manages files uploaded to the UniFi guest portal. " +
"This resource allows you to upload images that can be used in customizing " +
"the UniFi guest portal interface.\n\n" +
"**Note:** This resource uploads files to the UniFi controller. The file must exist on the local filesystem " +
"where Terraform is executed.",
Attributes: map[string]schema.Attribute{
"id": ut.ID(),
"site": ut.SiteAttribute(),
"file_path": schema.StringAttribute{
MarkdownDescription: "Path to the file on the local filesystem to upload to the UniFi controller. " +
"The file must exist and be readable.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},
"filename": schema.StringAttribute{
MarkdownDescription: "Name of the file as stored in the UniFi controller.",
Computed: true,
},
"content_type": schema.StringAttribute{
MarkdownDescription: "MIME type of the file.",
Computed: true,
},
"file_size": schema.Int64Attribute{
MarkdownDescription: "Size of the file in bytes.",
Computed: true,
},
"md5": schema.StringAttribute{
MarkdownDescription: "MD5 hash of the file content.",
Computed: true,
},
"url": schema.StringAttribute{
MarkdownDescription: "URL where the file can be accessed on the UniFi controller.",
Computed: true,
},
"last_modified": schema.Int64Attribute{
MarkdownDescription: "Timestamp when the file was last modified.",
Computed: true,
},
},
}
}
func (r *portalFileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data portalFileModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Get file path
filePath := data.FilePath.ValueString()
if filePath == "" {
resp.Diagnostics.AddError("File path is required", "A valid file path must be provided")
return
}
// Check if file exists
_, err := os.Stat(filePath)
if err != nil {
resp.Diagnostics.AddError("Invalid file path", fmt.Sprintf("Error accessing file: %s", err))
return
}
site := r.GetClient().ResolveSite(&data)
portalFile, err := r.GetClient().UploadPortalFile(ctx, site, filePath)
if err != nil {
resp.Diagnostics.AddError("Error uploading file", fmt.Sprintf("Could not upload file: %s", err))
return
}
// Map response back to model
resp.Diagnostics.Append(data.Merge(ctx, portalFile)...)
if resp.Diagnostics.HasError() {
return
}
data.Site = types.StringValue(site)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *portalFileResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resp.Diagnostics.AddError("Import is not supported", "The `unifi_portal_file` resource does not support import")
}