0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-30 02:31:10 +00:00
terraform-provider-proxmox/fwprovider/nodes/apt/models.go
Pavel Boldyrev d23f7ab1c0
chore(code): fix new qodana warnings (#1951)
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
2025-05-05 22:11:19 -04:00

282 lines
10 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"
"regexp"
"slices"
"strings"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/nodes/apt"
api "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/apt/repositories"
)
// Note that most constants are exported to allow the usage in (acceptance) tests.
const (
// SchemaAttrNameComment is the name of the APT repository schema attribute for the associated comment.
SchemaAttrNameComment = "comment"
// SchemaAttrNameComponents is the name of the APT repository schema attribute for the list of components.
SchemaAttrNameComponents = "components"
// SchemaAttrNameEnabled is the name of the APT repository schema attribute that indicates the activation status.
SchemaAttrNameEnabled = "enabled"
// SchemaAttrNameFilePath is the name of the APT repository schema attribute for the path of the defining source list
// file.
SchemaAttrNameFilePath = "file_path"
// SchemaAttrNameFileType is the name of the APT repository schema attribute for the format of the defining source
// list file.
SchemaAttrNameFileType = "file_type"
// SchemaAttrNameIndex is the name of the APT repository schema attribute for the index within the defining source
// list file.
SchemaAttrNameIndex = "index"
// SchemaAttrNameNode is the name of the APT repository schema attribute for the name of the Proxmox VE node.
SchemaAttrNameNode = "node"
// SchemaAttrNamePackageTypes is the name of the APT repository schema attribute for the list of package types.
SchemaAttrNamePackageTypes = "package_types"
// SchemaAttrNameStandardDescription is the name of the APT repository schema attribute for the description.
SchemaAttrNameStandardDescription = "description"
// SchemaAttrNameStandardHandle is the name of the APT repository schema attribute for the standard repository
// handle.
SchemaAttrNameStandardHandle = "handle"
// SchemaAttrNameStandardName is the name of the APT repository schema attribute for the human-readable name.
SchemaAttrNameStandardName = "name"
// SchemaAttrNameStandardStatus is the name of the APT standard repository schema attribute that indicates the
// configuration and activation status.
SchemaAttrNameStandardStatus = "status"
// SchemaAttrNameSuites is the name of the APT repository schema attribute for the list of package distributions.
SchemaAttrNameSuites = "suites"
// SchemaAttrNameTerraformID is the name of the APT repository schema attribute for the Terraform ID.
SchemaAttrNameTerraformID = "id"
// SchemaAttrNameURIs is the name of the APT repository schema attribute for the list of repository URIs.
SchemaAttrNameURIs = "uris"
)
// RepoIDCharReplaceRegEx is a regular expression to replace characters in a Terraform resource/data source ID.
// The "^" at the beginning of the character group selects all characters not matching the group.
var RepoIDCharReplaceRegEx = regexp.MustCompile(`([^a-zA-Z1-9_])`)
// modelRepo maps the schema data for an APT repository from a parsed source list file.
type modelRepo struct {
// Comment is the comment of the APT repository.
Comment types.String `tfsdk:"comment"`
// Components is the list of repository components.
Components types.List `tfsdk:"components"`
// Enabled indicates whether the APT repository is enabled.
Enabled types.Bool `tfsdk:"enabled"`
// FilePath is the path of the source list file that contains the APT repository.
FilePath types.String `tfsdk:"file_path"`
// FileType is the format of the packages.
FileType types.String `tfsdk:"file_type"`
// ID is the Terraform identifier of the APT repository.
ID types.String `tfsdk:"id"`
// Index is the index of the APT repository within the defining source list.
Index types.Int64 `tfsdk:"index"`
// Node is the name of the Proxmox VE node for the APT repository.
Node types.String `tfsdk:"node"`
// PackageTypes is the list of package types.
PackageTypes types.List `tfsdk:"package_types"`
// Suites is the list of package distributions.
Suites types.List `tfsdk:"suites"`
// URIs is the list of repository URIs.
URIs types.List `tfsdk:"uris"`
}
// modelStandardRepo maps the schema data for an APT standard repository.
type modelStandardRepo struct {
// Description is the description of the APT standard repository.
Description types.String `tfsdk:"description"`
// FilePath is the path of the source list file that contains the APT standard repository.
FilePath types.String `tfsdk:"file_path"`
// ID is the Terraform identifier of the APT standard repository.
ID types.String `tfsdk:"id"`
// Index is the index of the APT standard repository within the defining source list file.
Index types.Int64 `tfsdk:"index"`
// Handle is the handle of the APT standard repository.
Handle customtypes.StandardRepoHandleValue `tfsdk:"handle"`
// Name is the name of the APT standard repository.
Name types.String `tfsdk:"name"`
// Node is the name of the Proxmox VE node for the APT standard repository.
Node types.String `tfsdk:"node"`
// Status is the configuration and activation status of the APT standard repository.
Status types.Int64 `tfsdk:"status"`
}
// importFromAPI imports the contents of an APT repository model from the Proxmox VE API's response data.
func (rp *modelRepo) importFromAPI(ctx context.Context, data *api.GetResponseData) diag.Diagnostics {
diags := diag.Diagnostics{}
// We can only ensure a unique ID by using the name of the Proxmox VE node and the absolute file path because custom
// source list files can be loaded by Proxmox VE from every path on a node.
rp.ID = types.StringValue(
fmt.Sprintf(
"%s_%s_%s_%d",
ResourceRepoIDPrefix,
strings.ToLower(rp.Node.ValueString()),
strings.ToLower(RepoIDCharReplaceRegEx.ReplaceAllString(strings.TrimPrefix(rp.FilePath.ValueString(), "/"), "_")),
rp.Index.ValueInt64(),
),
)
// We must ensure that the type definitions for lists and other attributes are set since Terraform must know these
// during the planning phase. This is important when the resource was imported where only the ID is known.
rp.Comment = types.StringNull()
rp.Enabled = types.BoolNull()
rp.FileType = types.StringNull()
rp.Components = types.ListNull(types.StringType)
rp.PackageTypes = types.ListNull(types.StringType)
rp.Suites = types.ListNull(types.StringType)
rp.URIs = types.ListNull(types.StringType)
// Iterate through all repository files…
for _, repoFile := range data.Files {
// …and the defined repositories when the file path matches.
if repoFile.Path == rp.FilePath.ValueString() {
// Handle situations where an APT repository might have been removed manually which is currently the only way to
// solve this with the capabilities of the Proxmox VE API.
if int64(len(repoFile.Repositories)) > rp.Index.ValueInt64() {
repo := repoFile.Repositories[rp.Index.ValueInt64()]
// Strip the unnecessary new line control character (\n) from the end of the comment that is, for whatever
// reason, returned this way by the Proxmox VE API.
if repo.Comment != nil {
rp.Comment = types.StringValue(strings.TrimSuffix(*repo.Comment, "\n"))
}
rp.Enabled = repo.Enabled.ToValue()
rp.FileType = types.StringValue(repo.FileType)
components, convDiags := types.ListValueFrom(ctx, types.StringType, repo.Components)
if convDiags.HasError() {
diags.AddError("Terraform list value conversion", "Convert list of APT repository components")
} else {
rp.Components = components
}
pkgTypes, convDiags := types.ListValueFrom(ctx, types.StringType, repo.PackageTypes)
if convDiags.HasError() {
diags.AddError("Terraform list value conversion", "Convert list of APT repository package types")
} else {
rp.PackageTypes = pkgTypes
}
suites, convDiags := types.ListValueFrom(ctx, types.StringType, repo.Suites)
if convDiags.HasError() {
diags.AddError("Terraform list value conversion", "Convert list of APT repository suites")
} else {
rp.Suites = suites
}
uris, convDiags := types.ListValueFrom(ctx, types.StringType, repo.URIs)
if convDiags.HasError() {
diags.AddError("Terraform list value conversion", "Convert list of APT repository URIs")
} else {
rp.URIs = uris
}
}
}
}
return diags
}
// importFromAPI imports the contents of an APT standard repository from the Proxmox VE API's response data.
func (srp *modelStandardRepo) importFromAPI(_ context.Context, data *api.GetResponseData) {
for _, repo := range data.StandardRepos {
if repo.Handle == srp.Handle.ValueString() {
srp.Description = types.StringPointerValue(repo.Description)
// We can only ensure a unique ID by using the name of the Proxmox VE node in combination with the unique standard
// handle.
srp.ID = types.StringValue(
fmt.Sprintf(
"%s_%s_%s",
ResourceStandardRepoIDPrefix,
strings.ToLower(srp.Node.ValueString()),
RepoIDCharReplaceRegEx.ReplaceAllString(srp.Handle.ValueString(), "_"),
),
)
srp.Name = types.StringValue(repo.Name)
srp.Status = types.Int64PointerValue(repo.Status)
}
}
// Set the index…
srp.setIndex(data)
// … and then the file path when the index is valid…
if !srp.Index.IsNull() {
// …by iterating through all repository files…
for _, repoFile := range data.Files {
// …and get the repository when the file path matches.
if srp.Handle.IsSupportedFilePath(repoFile.Path) {
srp.FilePath = types.StringValue(repoFile.Path)
}
}
}
}
// setIndex sets the index of the APT standard repository derived from the defining source list file.
func (srp *modelStandardRepo) setIndex(data *api.GetResponseData) {
for _, file := range data.Files {
for idx, repo := range file.Repositories {
if slices.Contains(repo.Components, srp.Handle.ComponentName()) {
// Return early for non-Ceph repositories…
if !srp.Handle.IsCephHandle() {
srp.Index = types.Int64Value(int64(idx))
return
}
// …and find the index for Ceph repositories based on the version name within the list of URIs.
for _, uri := range repo.URIs {
if strings.Contains(uri, srp.Handle.CephVersionName().String()) {
srp.Index = types.Int64Value(int64(idx))
return
}
}
}
}
}
srp.Index = types.Int64Null()
}