mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-30 02:31:10 +00:00
361 lines
11 KiB
Go
361 lines
11 KiB
Go
/*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
package apt
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
|
|
"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/booldefault"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
|
|
"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/tfsdk"
|
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
|
|
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
|
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/validators"
|
|
"github.com/bpg/terraform-provider-proxmox/proxmox"
|
|
api "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/apt/repositories"
|
|
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
|
)
|
|
|
|
const (
|
|
// ResourceRepoIDPrefix is the prefix for the resource ID of repositoryResource.
|
|
ResourceRepoIDPrefix = "apt_repository"
|
|
|
|
// ResourceRepoActivationStatus is the default activation status for newly created or imported APT repositories.
|
|
// This reflects the same default value used by the Proxmox VE API when the "enabled" parameter is not set.
|
|
ResourceRepoActivationStatus = true
|
|
)
|
|
|
|
// Ensure the resource implements the required interfaces.
|
|
var (
|
|
_ resource.Resource = &repositoryResource{}
|
|
_ resource.ResourceWithConfigure = &repositoryResource{}
|
|
_ resource.ResourceWithImportState = &repositoryResource{}
|
|
)
|
|
|
|
// repositoryResource contains the APT repository resource's internal data.
|
|
type repositoryResource struct {
|
|
// client is the Proxmox VE API client.
|
|
client proxmox.Client
|
|
}
|
|
|
|
// read reads information about an APT repository from the Proxmox VE API.
|
|
// Note that the name of the node must be set before this method is called!
|
|
func (r *repositoryResource) read(ctx context.Context, rp *modelRepo) (bool, diag.Diagnostics) {
|
|
diags := diag.Diagnostics{}
|
|
|
|
data, err := r.client.Node(rp.Node.ValueString()).APT().Repositories().Get(ctx)
|
|
if err != nil {
|
|
diags.AddError(fmt.Sprintf("Could not read APT repositories on node %v", rp.Node), err.Error())
|
|
|
|
return false, diags
|
|
}
|
|
|
|
diags.Append(rp.importFromAPI(ctx, data)...)
|
|
|
|
if diags.HasError() {
|
|
return false, diags
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// readBack reads information about an APT repository from the Proxmox VE API and then updates the response state
|
|
// accordingly.
|
|
// Note that the Terraform resource identifier must be set in the state before this method is called!
|
|
func (r *repositoryResource) readBack(ctx context.Context, rp *modelRepo, diags *diag.Diagnostics, state *tfsdk.State) {
|
|
found, readDiags := r.read(ctx, rp)
|
|
|
|
diags.Append(readDiags...)
|
|
|
|
if !found {
|
|
diags.AddError(
|
|
"APT repository resource not found after update",
|
|
"Failed to find the resource when trying to read back the updated APT repository's data.",
|
|
)
|
|
}
|
|
|
|
if !diags.HasError() {
|
|
diags.Append(state.Set(ctx, *rp)...)
|
|
}
|
|
}
|
|
|
|
// Configure adds the provider-configured client to the resource.
|
|
func (r *repositoryResource) Configure(
|
|
_ context.Context,
|
|
req resource.ConfigureRequest,
|
|
resp *resource.ConfigureResponse,
|
|
) {
|
|
if req.ProviderData == nil {
|
|
return
|
|
}
|
|
|
|
cfg, ok := req.ProviderData.(config.Resource)
|
|
if !ok {
|
|
resp.Diagnostics.AddError(
|
|
"Unexpected Resource Configure Type",
|
|
fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
r.client = cfg.Client
|
|
}
|
|
|
|
// Create modifies the activation state of an existing APT repository, including the addition of standard repositories
|
|
// to the repository lists.
|
|
// The name of this method might be a bit confusing for this resource, but this is due to the way how the Proxmox VE API
|
|
// works for APT repositories.
|
|
func (r *repositoryResource) Create(
|
|
ctx context.Context,
|
|
req resource.CreateRequest,
|
|
resp *resource.CreateResponse,
|
|
) {
|
|
var rp modelRepo
|
|
|
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &rp)...)
|
|
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
body := &api.ModifyRequestBody{
|
|
Enabled: proxmoxtypes.CustomBool(rp.Enabled.ValueBool()),
|
|
Index: rp.Index.ValueInt64(),
|
|
Path: rp.FilePath.ValueString(),
|
|
}
|
|
|
|
if err := r.client.Node(rp.Node.ValueString()).APT().Repositories().Modify(ctx, body); err != nil {
|
|
resp.Diagnostics.AddError(
|
|
fmt.Sprintf("Could not modify APT repository in file %v at index %v on node %v", rp.FilePath, rp.Index, rp.Node),
|
|
err.Error(),
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
r.readBack(ctx, &rp, &resp.Diagnostics, &resp.State)
|
|
}
|
|
|
|
// Delete is currently a no-op for APT repositories due to the non-existing capability of the Proxmox VE API of deleting
|
|
// a configured APT repository.
|
|
// Also see Terraform's "Delete" framework documentation about [recommendations] and [caveats].
|
|
//
|
|
// [caveats]: https://developer.hashicorp.com/terraform/plugin/framework/resources/delete#caveats
|
|
// [recommendations]: https://developer.hashicorp.com/terraform/plugin/framework/resources/delete#recommendations
|
|
func (r *repositoryResource) Delete(_ context.Context, _ resource.DeleteRequest, _ *resource.DeleteResponse) {
|
|
}
|
|
|
|
// ImportState imports an APT repository from the Proxmox VE API.
|
|
func (r *repositoryResource) ImportState(
|
|
ctx context.Context,
|
|
req resource.ImportStateRequest,
|
|
resp *resource.ImportStateResponse,
|
|
) {
|
|
rp := modelRepo{
|
|
Enabled: types.BoolValue(ResourceRepoActivationStatus),
|
|
ID: types.StringValue(req.ID),
|
|
}
|
|
|
|
idFormatErrMsg := "expected import ID as comma-separated list in format " +
|
|
"PROXMOX_VE_NODE_NAME,SOURCE_LIST_FILE_PATH,INDEX (e.g. pve,/etc/apt/sources.list,0)"
|
|
|
|
parts := strings.Split(rp.ID.ValueString(), ",")
|
|
if len(parts) != 3 {
|
|
resp.Diagnostics.AddError("Invalid resource ID", fmt.Sprintf("%s, but got %v", idFormatErrMsg, rp.ID))
|
|
|
|
return
|
|
}
|
|
|
|
rp.Node = types.StringValue(parts[0])
|
|
|
|
if !strings.HasPrefix(parts[1], "/") {
|
|
resp.Diagnostics.AddError(
|
|
"Invalid resource ID",
|
|
fmt.Sprintf("given source list file path %q is not an absolute path: %s", parts[1], idFormatErrMsg),
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
rp.FilePath = types.StringValue(parts[1])
|
|
|
|
index, err := strconv.Atoi(parts[2])
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Parse resource ID",
|
|
fmt.Sprintf("Failed to parse given import ID index parameter %q as number: %s", parts[2], idFormatErrMsg),
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
rp.Index = types.Int64Value(int64(index))
|
|
|
|
resource.ImportStatePassthroughID(ctx, path.Root(SchemaAttrNameTerraformID), req, resp)
|
|
r.readBack(ctx, &rp, &resp.Diagnostics, &resp.State)
|
|
}
|
|
|
|
// Metadata defines the name of the APT repository resource.
|
|
func (r *repositoryResource) Metadata(
|
|
_ context.Context,
|
|
req resource.MetadataRequest,
|
|
resp *resource.MetadataResponse,
|
|
) {
|
|
resp.TypeName = req.ProviderTypeName + "_" + ResourceRepoIDPrefix
|
|
}
|
|
|
|
// Read reads the APT repository.
|
|
func (r *repositoryResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
|
var rp modelRepo
|
|
|
|
resp.Diagnostics.Append(req.State.Get(ctx, &rp)...)
|
|
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
found, diags := r.read(ctx, &rp)
|
|
resp.Diagnostics.Append(diags...)
|
|
|
|
if !resp.Diagnostics.HasError() {
|
|
if found {
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, rp)...)
|
|
} else {
|
|
resp.State.RemoveResource(ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Schema defines the schema for the APT repository.
|
|
func (r *repositoryResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
Description: "Manages an APT repository of a Proxmox VE node.",
|
|
Attributes: map[string]schema.Attribute{
|
|
SchemaAttrNameComment: schema.StringAttribute{
|
|
Computed: true,
|
|
Description: "The associated comment.",
|
|
},
|
|
SchemaAttrNameComponents: schema.ListAttribute{
|
|
Computed: true,
|
|
Description: "The list of components.",
|
|
ElementType: types.StringType,
|
|
},
|
|
SchemaAttrNameEnabled: schema.BoolAttribute{
|
|
Computed: true,
|
|
Default: booldefault.StaticBool(ResourceRepoActivationStatus),
|
|
Description: "Indicates the activation status.",
|
|
Optional: true,
|
|
},
|
|
SchemaAttrNameFilePath: schema.StringAttribute{
|
|
Description: "The absolute path of the source list file that contains this repository.",
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.RequiresReplace(),
|
|
},
|
|
Required: true,
|
|
Validators: []validator.String{
|
|
validators.AbsoluteFilePathValidator(),
|
|
validators.NonEmptyString(),
|
|
},
|
|
},
|
|
SchemaAttrNameFileType: schema.StringAttribute{
|
|
Computed: true,
|
|
Description: "The format of the defining source list file.",
|
|
},
|
|
SchemaAttrNameIndex: schema.Int64Attribute{
|
|
Description: "The index within the defining source list file.",
|
|
PlanModifiers: []planmodifier.Int64{
|
|
int64planmodifier.RequiresReplace(),
|
|
},
|
|
Required: true,
|
|
Validators: []validator.Int64{
|
|
int64validator.AtLeast(0),
|
|
},
|
|
},
|
|
SchemaAttrNameNode: schema.StringAttribute{
|
|
Description: "The name of the target Proxmox VE node.",
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.RequiresReplace(),
|
|
},
|
|
Required: true,
|
|
Validators: []validator.String{
|
|
validators.NonEmptyString(),
|
|
},
|
|
},
|
|
SchemaAttrNamePackageTypes: schema.ListAttribute{
|
|
Computed: true,
|
|
Description: "The list of package types.",
|
|
ElementType: types.StringType,
|
|
},
|
|
SchemaAttrNameSuites: schema.ListAttribute{
|
|
Computed: true,
|
|
Description: "The list of package distributions.",
|
|
ElementType: types.StringType,
|
|
},
|
|
SchemaAttrNameTerraformID: attribute.ResourceID("The unique identifier of this APT repository resource."),
|
|
SchemaAttrNameURIs: schema.ListAttribute{
|
|
Computed: true,
|
|
Description: "The list of repository URIs.",
|
|
ElementType: types.StringType,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Update updates an existing APT repository.
|
|
func (r *repositoryResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
|
var rpPlan modelRepo
|
|
|
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &rpPlan)...)
|
|
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
body := &api.ModifyRequestBody{
|
|
Enabled: proxmoxtypes.CustomBool(rpPlan.Enabled.ValueBool()),
|
|
Index: rpPlan.Index.ValueInt64(),
|
|
Path: rpPlan.FilePath.ValueString(),
|
|
}
|
|
|
|
err := r.client.Node(rpPlan.Node.ValueString()).APT().Repositories().Modify(ctx, body)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
fmt.Sprintf(
|
|
"Could not modify APT repository in file %v at index %v on node %v",
|
|
rpPlan.FilePath,
|
|
rpPlan.Index,
|
|
rpPlan.Node,
|
|
),
|
|
err.Error(),
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
r.readBack(ctx, &rpPlan, &resp.Diagnostics, &resp.State)
|
|
}
|
|
|
|
// NewRepositoryResource returns a new resource for managing an APT repository.
|
|
// This is a helper function to simplify the provider implementation.
|
|
func NewRepositoryResource() resource.Resource {
|
|
return &repositoryResource{}
|
|
}
|