mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-07-04 12:32:59 +00:00
* feat(nodes): Initial support to manage APT repositories > Summary This commit implements initial support for managing APT repositories which is (currently) limited to… - …adding "standard" repositories to allow to configure it. - toggling the activation status (enabled/disabled) of any configured repository. + !WARNING! + Note that deleting or modifying a repository in any other way is + (sadly) not possible (yet?)! + The limited functionality is due to the (current) capabilities of + the Proxmox VE APT repository API [1] itself. >> Why are there two resources for one API entity? Even though an APT repository should be seen as a single API entity, it was required to implement standard repositories as dedicated `proxmox_virtual_environment_apt_standard_repository`. This is because standard repositories must be configured (added) first to the default source list files because their activation status can be toggled. This is handled by the HTTP `PUT` request, but the modifying request is `POST` which would require two calls within the same Terraform execution cycle. I tried to implement it in a single resource and it worked out mostly after some handling some edges cases, but in the end there were still too many situations an edge cases where it might break due to Terraform state drifts between states. In the end the dedicated resources are way cleaner and easier to use without no complexity and conditional attribute juggling for practitioners. >> Other "specialties" Unfortunately the Proxmox VE API responses to HTTP `GET` requests with four larger arrays which are, more or less, kind of connected to each other, but they also somehow stand on their own. This means that there is a `files` array that contains the `repositories` again which again contains all repositories with their metadata of every source file. On the other hand available standard repositories are listed in the `standard-repos` array, but their activation status is only stored when they have already been added through a `PUT` request. The `infos` array is more less useless. So in order to get the required data and store them in the state the `importFromAPI` methods of the models must loop through all the deep-nested arrays and act based on specific attributes like a matching file path, comparing it to the activation status and so on. In the end the implementation is really stable after testing it with all possible conditions and state combinations. @bpg if you'd like me to create a small data logic flow chart to make it easier to understand some parts of the code let me know. I can make my local notes "shareable" which I created to not loose track of the logic. >> What is the way to manage the activation status of a "standard" repository? Because the two resources are modular and scoped they can be simply combined to manage an APT "standard" repository, e.g. toggling its activation status. The following examples are also included in the documentations. ```hcl // This resource ensure that the "no-subscription" standard repository // is added to the source list. // It represents the `PUT` API request. resource "proxmox_virtual_environment_apt_standard_repository" "example" { handle = "no-subscription" node = "pve" } // This resource allows to actually modify the activation status of the // standard repository as it represents the `POST`. // Using the values from the dedicated standard repository resource // makes sure that Terraform correctly resolves dependency order. resource "proxmox_virtual_environment_apt_repository" "example" { enabled = true file_path = proxmox_virtual_environment_apt_standard_repository.example.file_path index = proxmox_virtual_environment_apt_standard_repository.example.index node = proxmox_virtual_environment_apt_standard_repository.example.node } ``` [1]: https://pve.proxmox.com/pve-docs/api-viewer/#/nodes/{node}/apt/repositories --------- Signed-off-by: Sven Greb <development@svengreb.de> Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
287 lines
9.8 KiB
Go
287 lines
9.8 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"
|
|
"strings"
|
|
|
|
"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/schema/validator"
|
|
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
|
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
|
|
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
|
|
customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/nodes/apt"
|
|
"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"
|
|
)
|
|
|
|
const (
|
|
// ResourceStandardRepoIDPrefix is the prefix for the resource ID of resourceStandardRepo.
|
|
ResourceStandardRepoIDPrefix = "apt_standard_repository"
|
|
)
|
|
|
|
// Ensure the resource implements the required interfaces.
|
|
var (
|
|
_ resource.Resource = &resourceStandardRepo{}
|
|
_ resource.ResourceWithConfigure = &resourceStandardRepo{}
|
|
_ resource.ResourceWithImportState = &resourceStandardRepo{}
|
|
)
|
|
|
|
// resourceStandardRepo contains the APT standard repository resource's internal data.
|
|
type resourceStandardRepo struct {
|
|
// client is the Proxmox VE API client.
|
|
client proxmox.Client
|
|
}
|
|
|
|
// read reads information about an APT standard repository from the Proxmox VE API.
|
|
// Note that the name of the node must be set before this method is called!
|
|
func (r *resourceStandardRepo) read(ctx context.Context, srp *modelStandardRepo) (bool, diag.Diagnostics) {
|
|
var diags diag.Diagnostics
|
|
|
|
data, err := r.client.Node(srp.Node.ValueString()).APT().Repositories().Get(ctx)
|
|
if err != nil {
|
|
diags.AddError("Could not read APT repositories", err.Error())
|
|
|
|
return false, diags
|
|
}
|
|
|
|
for _, stdRepo := range data.StandardRepos {
|
|
// Check if the APT standard repository is configured…
|
|
if stdRepo.Handle == srp.Handle.ValueString() && stdRepo.Status == nil {
|
|
// …handle the situation gracefully if not to signal that the repository has been removed outside of Terraform and
|
|
// must be added back again.
|
|
return false, diags
|
|
}
|
|
}
|
|
|
|
srp.importFromAPI(ctx, data)
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// readBack reads information about an APT standard repository from the Proxmox VE API and then updates the response
|
|
// state accordingly.
|
|
func (r *resourceStandardRepo) readBack(
|
|
ctx context.Context,
|
|
srp *modelStandardRepo,
|
|
diags *diag.Diagnostics,
|
|
state *tfsdk.State,
|
|
) {
|
|
found, readDiags := r.read(ctx, srp)
|
|
|
|
diags.Append(readDiags...)
|
|
|
|
if !found {
|
|
diags.AddError(
|
|
"APT standard repository resource not found after update",
|
|
"Failed to find the resource when trying to read back the updated APT standard repository's data.",
|
|
)
|
|
}
|
|
|
|
if !diags.HasError() {
|
|
diags.Append(state.Set(ctx, *srp)...)
|
|
}
|
|
}
|
|
|
|
// Configure adds the provider-configured client to the resource.
|
|
func (r *resourceStandardRepo) Configure(
|
|
_ context.Context,
|
|
req resource.ConfigureRequest,
|
|
resp *resource.ConfigureResponse,
|
|
) {
|
|
if req.ProviderData == nil {
|
|
return
|
|
}
|
|
|
|
client, ok := req.ProviderData.(proxmox.Client)
|
|
|
|
if !ok {
|
|
resp.Diagnostics.AddError(
|
|
"Unexpected resource configuration type",
|
|
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData),
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
r.client = client
|
|
}
|
|
|
|
// Create adds an APT standard repository to the repository source 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 standard repositories.
|
|
func (r *resourceStandardRepo) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
|
var srp modelStandardRepo
|
|
|
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &srp)...)
|
|
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
body := &api.AddRequestBody{
|
|
Handle: srp.Handle.ValueString(),
|
|
Node: srp.Node.ValueString(),
|
|
}
|
|
|
|
if err := r.client.Node(srp.Node.ValueString()).APT().Repositories().Add(ctx, body); err != nil {
|
|
resp.Diagnostics.AddError(
|
|
fmt.Sprintf("Could not add APT standard repository with handle %v on node %v", srp.Handle, srp.Node),
|
|
err.Error(),
|
|
)
|
|
}
|
|
|
|
r.readBack(ctx, &srp, &resp.Diagnostics, &resp.State)
|
|
}
|
|
|
|
// Delete is currently a no-op for APT standard repositories due to the non-existing capability of the Proxmox VE API
|
|
// of deleting a configured APT standard 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 *resourceStandardRepo) Delete(_ context.Context, _ resource.DeleteRequest, _ *resource.DeleteResponse) {
|
|
}
|
|
|
|
// ImportState imports an APT standard repository from the Proxmox VE API.
|
|
func (r *resourceStandardRepo) ImportState(
|
|
ctx context.Context,
|
|
req resource.ImportStateRequest,
|
|
resp *resource.ImportStateResponse,
|
|
) {
|
|
srp := modelStandardRepo{
|
|
ID: types.StringValue(req.ID),
|
|
}
|
|
|
|
idFormatErrMsg := "expected import ID as comma-separated list in format " +
|
|
"PROXMOX_VE_NODE_NAME,STANDARD_REPOSITORY_HANDLE (e.g. pve,no-subscription)"
|
|
|
|
parts := strings.Split(srp.ID.ValueString(), ",")
|
|
if len(parts) != 2 {
|
|
resp.Diagnostics.AddError("Invalid resource ID", fmt.Sprintf("%s, but got %v", idFormatErrMsg, srp.ID))
|
|
|
|
return
|
|
}
|
|
|
|
srp.Node = types.StringValue(parts[0])
|
|
srp.Handle = customtypes.StandardRepoHandleValue{StringValue: types.StringValue(parts[1])}
|
|
|
|
resource.ImportStatePassthroughID(ctx, path.Root(SchemaAttrNameTerraformID), req, resp)
|
|
r.readBack(ctx, &srp, &resp.Diagnostics, &resp.State)
|
|
}
|
|
|
|
// Metadata defines the name of the APT standard repository resource.
|
|
func (r *resourceStandardRepo) Metadata(
|
|
_ context.Context,
|
|
req resource.MetadataRequest,
|
|
resp *resource.MetadataResponse,
|
|
) {
|
|
resp.TypeName = req.ProviderTypeName + "_" + ResourceStandardRepoIDPrefix
|
|
}
|
|
|
|
// Read reads the APT standard repository.
|
|
func (r *resourceStandardRepo) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
|
var srp modelStandardRepo
|
|
|
|
resp.Diagnostics.Append(req.State.Get(ctx, &srp)...)
|
|
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
found, diags := r.read(ctx, &srp)
|
|
resp.Diagnostics.Append(diags...)
|
|
|
|
if !resp.Diagnostics.HasError() {
|
|
if found {
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, srp)...)
|
|
} else {
|
|
resp.State.RemoveResource(ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Schema defines the schema for the APT standard repository.
|
|
func (r *resourceStandardRepo) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
Description: "Manages an APT standard repository of a Proxmox VE node.",
|
|
Attributes: map[string]schema.Attribute{
|
|
SchemaAttrNameStandardDescription: schema.StringAttribute{
|
|
Computed: true,
|
|
Description: "The description of the APT standard repository.",
|
|
},
|
|
SchemaAttrNameFilePath: schema.StringAttribute{
|
|
Computed: true,
|
|
Description: "The absolute path of the source list file that contains this standard repository.",
|
|
},
|
|
SchemaAttrNameStandardHandle: schema.StringAttribute{
|
|
CustomType: customtypes.StandardRepoHandleType{},
|
|
Description: "The handle of the APT standard repository.",
|
|
MarkdownDescription: "The handle of the APT standard repository. Must be `ceph-quincy-enterprise` | " +
|
|
"`ceph-quincy-no-subscription` | `ceph-quincy-test` | `ceph-reef-enterprise` | `ceph-reef-no-subscription` " +
|
|
"| `ceph-reef-test` | `enterprise` | `no-subscription` | `test`.",
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.RequiresReplace(),
|
|
},
|
|
Required: true,
|
|
Validators: []validator.String{
|
|
validators.NonEmptyString(),
|
|
},
|
|
},
|
|
SchemaAttrNameIndex: schema.Int64Attribute{
|
|
Computed: true,
|
|
Description: "The index within the defining source list file.",
|
|
},
|
|
SchemaAttrNameStandardName: schema.StringAttribute{
|
|
Computed: true,
|
|
Description: "The name of the APT standard repository.",
|
|
},
|
|
SchemaAttrNameNode: schema.StringAttribute{
|
|
Description: "The name of the target Proxmox VE node.",
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.RequiresReplace(),
|
|
},
|
|
Required: true,
|
|
Validators: []validator.String{
|
|
validators.NonEmptyString(),
|
|
},
|
|
},
|
|
SchemaAttrNameStandardStatus: schema.Int64Attribute{
|
|
Computed: true,
|
|
Description: "Indicates the activation status.",
|
|
},
|
|
SchemaAttrNameTerraformID: attribute.ID(
|
|
"The unique identifier of this APT standard repository resource.",
|
|
),
|
|
},
|
|
}
|
|
}
|
|
|
|
// Update is currently a no-op for APT repositories due to the non-existing capability of the Proxmox VE API of updating
|
|
// a configured APT standard 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 *resourceStandardRepo) Update(_ context.Context, _ resource.UpdateRequest, _ *resource.UpdateResponse) {
|
|
}
|
|
|
|
// NewResourceStandardRepo returns a new resource for managing an APT standard repository.
|
|
// This is a helper function to simplify the provider implementation.
|
|
func NewResourceStandardRepo() resource.Resource {
|
|
return &resourceStandardRepo{}
|
|
}
|