diff --git a/fwprovider/nodes/apt/datasource_standard_repo.go b/fwprovider/nodes/apt/datasource_standard_repo.go index 3387ce6a0..7e3d0a010 100644 --- a/fwprovider/nodes/apt/datasource_standard_repo.go +++ b/fwprovider/nodes/apt/datasource_standard_repo.go @@ -86,7 +86,8 @@ func (d *standardRepositoryDataSource) Read( return } - srp.importFromAPI(ctx, data) + ver := getProxmoxVersion(ctx, d.client) + srp.importFromAPI(ctx, data, ver) resp.Diagnostics.Append(resp.State.Set(ctx, &srp)...) } diff --git a/fwprovider/nodes/apt/models.go b/fwprovider/nodes/apt/models.go index 3b81c1e07..fc99007eb 100644 --- a/fwprovider/nodes/apt/models.go +++ b/fwprovider/nodes/apt/models.go @@ -17,7 +17,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/nodes/apt" + "github.com/hashicorp/terraform-plugin-log/tflog" + + "github.com/bpg/terraform-provider-proxmox/proxmox" api "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/apt/repositories" + "github.com/bpg/terraform-provider-proxmox/proxmox/version" ) // Note that most constants are exported to allow the usage in (acceptance) tests. @@ -219,7 +223,7 @@ func (rp *modelRepo) importFromAPI(ctx context.Context, data *api.GetResponseDat } // 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) { +func (srp *modelStandardRepo) importFromAPI(_ context.Context, data *api.GetResponseData, proxmoxVersion *version.ProxmoxVersion) { for _, repo := range data.StandardRepos { if repo.Handle == srp.Handle.ValueString() { srp.Description = types.StringPointerValue(repo.Description) @@ -240,13 +244,13 @@ func (srp *modelStandardRepo) importFromAPI(_ context.Context, data *api.GetResp } // Set the index… - srp.setIndex(data) + srp.setIndex(data, proxmoxVersion) // … 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) { + if srp.Handle.IsSupportedFilePath(repoFile.Path, proxmoxVersion) { srp.FilePath = types.StringValue(repoFile.Path) } } @@ -254,10 +258,10 @@ func (srp *modelStandardRepo) importFromAPI(_ context.Context, data *api.GetResp } // setIndex sets the index of the APT standard repository derived from the defining source list file. -func (srp *modelStandardRepo) setIndex(data *api.GetResponseData) { +func (srp *modelStandardRepo) setIndex(data *api.GetResponseData, proxmoxVersion *version.ProxmoxVersion) { for _, file := range data.Files { for idx, repo := range file.Repositories { - if slices.Contains(repo.Components, srp.Handle.ComponentName()) { + if slices.Contains(repo.Components, srp.Handle.ComponentName(proxmoxVersion)) { // Return early for non-Ceph repositories… if !srp.Handle.IsCephHandle() { srp.Index = types.Int64Value(int64(idx)) @@ -279,3 +283,19 @@ func (srp *modelStandardRepo) setIndex(data *api.GetResponseData) { srp.Index = types.Int64Null() } + +// getProxmoxVersion retrieves the Proxmox VE version from the API client, falling back to the minimum supported version +// if the API call fails. +func getProxmoxVersion(ctx context.Context, client proxmox.Client) *version.ProxmoxVersion { + ver := version.MinimumProxmoxVersion + if versionResp, err := client.Version().Version(ctx); err == nil { + ver = versionResp.Version + } else { + tflog.Warn(ctx, "Failed to determine Proxmox VE version, assuming minimum supported version.", map[string]any{ + "error": err, + "assumed_version": ver.String(), + }) + } + + return &ver +} diff --git a/fwprovider/nodes/apt/repo_test.go b/fwprovider/nodes/apt/repo_test.go index 1717c6183..e63432e07 100644 --- a/fwprovider/nodes/apt/repo_test.go +++ b/fwprovider/nodes/apt/repo_test.go @@ -22,7 +22,6 @@ import ( "github.com/bpg/terraform-provider-proxmox/fwprovider/nodes/apt" "github.com/bpg/terraform-provider-proxmox/fwprovider/test" - apitypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/nodes/apt/repositories" ) // Note that some "hard-coded" values must be used because of the way how the Proxmox VE API for APT repositories works. @@ -51,7 +50,7 @@ func TestAccDataSourceRepo(t *testing.T) { { Config: te.RenderConfig(` data "proxmox_virtual_environment_apt_repository" "test" { - file_path = "/etc/apt/sources.list" + file_path = "/etc/apt/sources.list.d/proxmox.sources" index = 0 node = "{{.NodeName}}" }`), @@ -66,7 +65,7 @@ func TestAccDataSourceRepo(t *testing.T) { resource.TestCheckResourceAttr( "data.proxmox_virtual_environment_apt_repository.test", apt.SchemaAttrNameTerraformID, - "apt_repository_"+strings.ToLower(te.NodeName)+"_etc_apt_sources_list_0", + "apt_repository_"+strings.ToLower(te.NodeName)+"_etc_apt_sources_list_d_proxmox_sources_0", ), test.ResourceAttributesSet("data.proxmox_virtual_environment_apt_repository.test", []string{ "components.#", @@ -180,7 +179,7 @@ func TestAccResourceRepoValidInput(t *testing.T) { Config: te.RenderConfig(` resource "proxmox_virtual_environment_apt_repository" "test" { enabled = true - file_path = "/etc/apt/sources.list" + file_path = "/etc/apt/sources.list.d/debian.sources" index = 0 node = "{{.NodeName}}" }`), @@ -213,11 +212,11 @@ func TestAccResourceRepoValidInput(t *testing.T) { map[int]knownvalue.Check{ // The possible Debian version is based on the official table of the Proxmox VE FAQ page: // - https://pve.proxmox.com/wiki/FAQ#faq-support-table - // - https://www.thomas-krenn.com/en/wiki/Proxmox_VE#Proxmox_VE_8.x + // - https://www.thomas-krenn.com/en/wiki/Proxmox_VE#Proxmox_VE_9.x // // The required Proxmox VE version for this provider is of course also taken into account: // - https://github.com/bpg/terraform-provider-proxmox?tab=readme-ov-file#requirements - 0: knownvalue.StringRegexp(regexp.MustCompile(`(bookworm)`)), + 0: knownvalue.StringRegexp(regexp.MustCompile(`(trixie)`)), }, ), ), @@ -226,7 +225,7 @@ func TestAccResourceRepoValidInput(t *testing.T) { tfjsonpath.New(apt.SchemaAttrNameURIs), knownvalue.ListPartial( map[int]knownvalue.Check{ - 0: knownvalue.StringRegexp(regexp.MustCompile(`https?://ftp\.([a-z]+\.)?debian\.org/debian`)), + 0: knownvalue.StringRegexp(regexp.MustCompile(`https?://([a-z]+\.)?debian\.org/debian/`)), }, ), ), @@ -235,14 +234,14 @@ func TestAccResourceRepoValidInput(t *testing.T) { Check: resource.ComposeTestCheckFunc( test.ResourceAttributes("proxmox_virtual_environment_apt_repository.test", map[string]string{ "enabled": strconv.FormatBool(true), - "file_path": "/etc/apt/sources.list", + "file_path": "/etc/apt/sources.list.d/debian.sources", "index": strconv.FormatInt(0, 10), "node": te.NodeName, "id": fmt.Sprintf( "apt_repository_%s_%s_%d", strings.ToLower(te.NodeName), apt.RepoIDCharReplaceRegEx.ReplaceAllString( - strings.TrimPrefix("/etc/apt/sources.list", "/"), + strings.TrimPrefix("/etc/apt/sources.list.d/debian.sources", "/"), "_", ), 0, @@ -259,7 +258,7 @@ func TestAccResourceRepoValidInput(t *testing.T) { ImportStateId: fmt.Sprintf( "%s,%s,%d", strings.ToLower(te.NodeName), - apitypes.StandardRepoFilePathMain, + "/etc/apt/sources.list.d/debian.sources", testAccResourceRepoIndex, ), ImportStateVerify: true, @@ -271,7 +270,7 @@ func TestAccResourceRepoValidInput(t *testing.T) { Config: te.RenderConfig(` resource "proxmox_virtual_environment_apt_repository" "test" { enabled = false - file_path = "/etc/apt/sources.list" + file_path = "/etc/apt/sources.list.d/debian.sources" index = 0 node = "{{.NodeName}}" }`), @@ -309,7 +308,7 @@ func TestAccResourceStandardRepoValidInput(t *testing.T) { // Test the "Create" and "Read" implementations. { // PUT /api2/json/nodes/{node}/apt/repositories with handle = "no-subscription" will create a new - // entry in /etc/apt/sources.list on each call :/ + // entry in /etc/apt/sources.list.d/proxmox.sources on each call :/ SkipFunc: func() (bool, error) { return true, nil }, @@ -321,7 +320,7 @@ func TestAccResourceStandardRepoValidInput(t *testing.T) { // The provided attributes and computed attributes should be set. Check: resource.ComposeTestCheckFunc( test.ResourceAttributes("proxmox_virtual_environment_apt_standard_repository.test", map[string]string{ - "file_path": "/etc/apt/sources.list", + "file_path": "/etc/apt/sources.list.d/proxmox.sources", "handle": "no-subscription", "node": te.NodeName, "status": "1", diff --git a/fwprovider/nodes/apt/resource_standard_repo.go b/fwprovider/nodes/apt/resource_standard_repo.go index 058195459..9a9cf4820 100644 --- a/fwprovider/nodes/apt/resource_standard_repo.go +++ b/fwprovider/nodes/apt/resource_standard_repo.go @@ -68,7 +68,8 @@ func (r *standardRepositoryResource) read(ctx context.Context, srp *modelStandar } } - srp.importFromAPI(ctx, data) + ver := getProxmoxVersion(ctx, r.client) + srp.importFromAPI(ctx, data, ver) return true, nil } diff --git a/fwprovider/types/nodes/apt/standard_repo_handle.go b/fwprovider/types/nodes/apt/standard_repo_handle.go index e260face7..9e3ea4900 100644 --- a/fwprovider/types/nodes/apt/standard_repo_handle.go +++ b/fwprovider/types/nodes/apt/standard_repo_handle.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/terraform-plugin-go/tftypes" apitypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/nodes/apt/repositories" + "github.com/bpg/terraform-provider-proxmox/proxmox/version" ) // Ensure the implementations satisfy the required interfaces. @@ -139,10 +140,10 @@ func (v StandardRepoHandleValue) CephVersionName() apitypes.CephVersionName { } // ComponentName returns the corresponding component name. -func (v StandardRepoHandleValue) ComponentName() string { +func (v StandardRepoHandleValue) ComponentName(proxmoxVersion *version.ProxmoxVersion) string { if v.cvn == apitypes.CephVersionNameUnknown && v.kind != apitypes.StandardRepoHandleKindUnknown { - // For whatever reason the non-Ceph handle "test" kind does not use a dash in between the "pve" prefix. - if v.kind == apitypes.StandardRepoHandleKindTest { + // On PVE8, for whatever reason the non-Ceph handle "test" kind does not use a dash in between the "pve" prefix. + if proxmoxVersion != nil && !proxmoxVersion.SupportModernAptSources() && v.kind == apitypes.StandardRepoHandleKindTest { return fmt.Sprintf("pve%s", v.kind) } @@ -169,17 +170,37 @@ func (v StandardRepoHandleValue) IsCephHandle() bool { } // IsSupportedFilePath returns whether the handle is supported for the given source list file path. -func (v StandardRepoHandleValue) IsSupportedFilePath(filePath string) bool { - switch filePath { - case apitypes.StandardRepoFilePathCeph: - return v.IsCephHandle() - case apitypes.StandardRepoFilePathEnterprise: - return !v.IsCephHandle() && v.kind == apitypes.StandardRepoHandleKindEnterprise - case apitypes.StandardRepoFilePathMain: - return !v.IsCephHandle() && v.kind != apitypes.StandardRepoHandleKindEnterprise - default: - return false +// The proxmoxVersion parameter is used to determine whether to use old (.list) or new (.sources) format paths. +// For versions below PVE 9: only old .list paths are supported. +// For PVE 9 and above: both old and new paths are supported for compatibility. +func (v StandardRepoHandleValue) IsSupportedFilePath(filePath string, proxmoxVersion *version.ProxmoxVersion) bool { + supportsModernPaths := proxmoxVersion == nil || proxmoxVersion.SupportModernAptSources() + + if v.IsCephHandle() { + if supportsModernPaths { + return filePath == apitypes.StandardRepoFilePathCeph || filePath == apitypes.OldStandardRepoFilePathCeph + } + + return filePath == apitypes.OldStandardRepoFilePathCeph + } + + if v.kind == apitypes.StandardRepoHandleKindEnterprise { + if supportsModernPaths { + return filePath == apitypes.StandardRepoFilePathEnterprise || filePath == apitypes.OldStandardRepoFilePathEnterprise + } + + return filePath == apitypes.OldStandardRepoFilePathEnterprise + } + + if v.kind == apitypes.StandardRepoHandleKindNoSubscription || v.kind == apitypes.StandardRepoHandleKindTest { + if supportsModernPaths { + return filePath == apitypes.StandardRepoFilePathMain || filePath == apitypes.OldStandardRepoFilePathMain + } + + return filePath == apitypes.OldStandardRepoFilePathMain } + + return false } // Type returns the type of the value. diff --git a/fwprovider/types/nodes/apt/standard_repo_handle_test.go b/fwprovider/types/nodes/apt/standard_repo_handle_test.go index 39692724c..dae340533 100644 --- a/fwprovider/types/nodes/apt/standard_repo_handle_test.go +++ b/fwprovider/types/nodes/apt/standard_repo_handle_test.go @@ -9,14 +9,255 @@ package apt import ( "testing" + goversion "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/stretchr/testify/require" apitypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/nodes/apt/repositories" + "github.com/bpg/terraform-provider-proxmox/proxmox/version" ) +func TestStandardRepoHandleValueIsSupportedFilePath(t *testing.T) { + t.Parallel() + + pve8Version := &version.ProxmoxVersion{Version: *goversion.Must(goversion.NewVersion("8.4.0"))} + pve9Version := &version.ProxmoxVersion{Version: *goversion.Must(goversion.NewVersion("9.0.0"))} + pve10Version := &version.ProxmoxVersion{Version: *goversion.Must(goversion.NewVersion("10.0.0"))} + + tests := []struct { + name string + handle StandardRepoHandleValue + filePath string + proxmoxVersion *version.ProxmoxVersion + expected bool + }{ + // Ceph handle tests with PVE 8.4 (old paths only) + { + name: "Ceph handle with new path and PVE 8.4", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindEnterprise, cvn: apitypes.CephVersionNameQuincy}, + filePath: apitypes.StandardRepoFilePathCeph, + proxmoxVersion: pve8Version, + expected: false, + }, + { + name: "Ceph handle with old path and PVE 8.4", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindEnterprise, cvn: apitypes.CephVersionNameQuincy}, + filePath: apitypes.OldStandardRepoFilePathCeph, + proxmoxVersion: pve8Version, + expected: true, + }, + // Ceph handle tests with PVE 9.0 (both paths accepted) + { + name: "Ceph handle with new path and PVE 9.0", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindEnterprise, cvn: apitypes.CephVersionNameQuincy}, + filePath: apitypes.StandardRepoFilePathCeph, + proxmoxVersion: pve9Version, + expected: true, + }, + { + name: "Ceph handle with old path and PVE 9.0", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindEnterprise, cvn: apitypes.CephVersionNameQuincy}, + filePath: apitypes.OldStandardRepoFilePathCeph, + proxmoxVersion: pve9Version, + expected: true, + }, + // Ceph handle tests with PVE 10.0 (both paths accepted) + { + name: "Ceph handle with new path and PVE 10.0", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindTest, cvn: apitypes.CephVersionNameSquid}, + filePath: apitypes.StandardRepoFilePathCeph, + proxmoxVersion: pve10Version, + expected: true, + }, + { + name: "Ceph handle with old path and PVE 10.0", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindTest, cvn: apitypes.CephVersionNameSquid}, + filePath: apitypes.OldStandardRepoFilePathCeph, + proxmoxVersion: pve10Version, + expected: true, + }, + // Ceph handle tests with nil version (both paths accepted) + { + name: "Ceph handle with new path and nil version", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindNoSubscription, cvn: apitypes.CephVersionNameReef}, + filePath: apitypes.StandardRepoFilePathCeph, + proxmoxVersion: nil, + expected: true, + }, + { + name: "Ceph handle with old path and nil version", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindNoSubscription, cvn: apitypes.CephVersionNameReef}, + filePath: apitypes.OldStandardRepoFilePathCeph, + proxmoxVersion: nil, + expected: true, + }, + // Enterprise handle tests with PVE 8.4 (old paths only) + { + name: "Enterprise handle with new path and PVE 8.4", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindEnterprise, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.StandardRepoFilePathEnterprise, + proxmoxVersion: pve8Version, + expected: false, + }, + { + name: "Enterprise handle with old path and PVE 8.4", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindEnterprise, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.OldStandardRepoFilePathEnterprise, + proxmoxVersion: pve8Version, + expected: true, + }, + // Enterprise handle tests with PVE 9.0 (both paths accepted) + { + name: "Enterprise handle with new path and PVE 9.0", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindEnterprise, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.StandardRepoFilePathEnterprise, + proxmoxVersion: pve9Version, + expected: true, + }, + { + name: "Enterprise handle with old path and PVE 9.0", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindEnterprise, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.OldStandardRepoFilePathEnterprise, + proxmoxVersion: pve9Version, + expected: true, + }, + // Enterprise handle tests with nil version (both paths accepted) + { + name: "Enterprise handle with new path and nil version", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindEnterprise, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.StandardRepoFilePathEnterprise, + proxmoxVersion: nil, + expected: true, + }, + { + name: "Enterprise handle with old path and nil version", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindEnterprise, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.OldStandardRepoFilePathEnterprise, + proxmoxVersion: nil, + expected: true, + }, + // NoSubscription handle tests with PVE 8.4 (old paths only) + { + name: "NoSubscription handle with new path and PVE 8.4", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindNoSubscription, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.StandardRepoFilePathMain, + proxmoxVersion: pve8Version, + expected: false, + }, + { + name: "NoSubscription handle with old path and PVE 8.4", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindNoSubscription, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.OldStandardRepoFilePathMain, + proxmoxVersion: pve8Version, + expected: true, + }, + // NoSubscription handle tests with PVE 9.0 (both paths accepted) + { + name: "NoSubscription handle with new path and PVE 9.0", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindNoSubscription, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.StandardRepoFilePathMain, + proxmoxVersion: pve9Version, + expected: true, + }, + { + name: "NoSubscription handle with old path and PVE 9.0", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindNoSubscription, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.OldStandardRepoFilePathMain, + proxmoxVersion: pve9Version, + expected: true, + }, + // NoSubscription handle tests with nil version (both paths accepted) + { + name: "NoSubscription handle with new path and nil version", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindNoSubscription, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.StandardRepoFilePathMain, + proxmoxVersion: nil, + expected: true, + }, + { + name: "NoSubscription handle with old path and nil version", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindNoSubscription, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.OldStandardRepoFilePathMain, + proxmoxVersion: nil, + expected: true, + }, + // Test handle tests with PVE 8.4 (old paths only) + { + name: "Test handle with new path and PVE 8.4", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindTest, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.StandardRepoFilePathMain, + proxmoxVersion: pve8Version, + expected: false, + }, + { + name: "Test handle with old path and PVE 8.4", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindTest, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.OldStandardRepoFilePathMain, + proxmoxVersion: pve8Version, + expected: true, + }, + // Test handle tests with PVE 9.0 (both paths accepted) + { + name: "Test handle with new path and PVE 9.0", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindTest, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.StandardRepoFilePathMain, + proxmoxVersion: pve9Version, + expected: true, + }, + { + name: "Test handle with old path and PVE 9.0", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindTest, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.OldStandardRepoFilePathMain, + proxmoxVersion: pve9Version, + expected: true, + }, + // Invalid path tests + { + name: "Ceph handle with invalid path and PVE 9.0", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindEnterprise, cvn: apitypes.CephVersionNameQuincy}, + filePath: "/invalid/path", + proxmoxVersion: pve9Version, + expected: false, + }, + { + name: "Enterprise handle with Ceph path and PVE 9.0", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindEnterprise, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.StandardRepoFilePathCeph, + proxmoxVersion: pve9Version, + expected: false, + }, + { + name: "NoSubscription handle with Enterprise path and PVE 9.0", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindNoSubscription, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.StandardRepoFilePathEnterprise, + proxmoxVersion: pve9Version, + expected: false, + }, + { + name: "Unknown handle with any path and PVE 9.0", + handle: StandardRepoHandleValue{kind: apitypes.StandardRepoHandleKindUnknown, cvn: apitypes.CephVersionNameUnknown}, + filePath: apitypes.StandardRepoFilePathMain, + proxmoxVersion: pve9Version, + expected: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + result := test.handle.IsSupportedFilePath(test.filePath, test.proxmoxVersion) + require.Equal(t, test.expected, result) + }) + } +} + func TestStandardRepoHandleValueFromTerraform(t *testing.T) { t.Parallel() + pve8Version := &version.ProxmoxVersion{Version: *goversion.Must(goversion.NewVersion("8.4.0"))} + pve9Version := &version.ProxmoxVersion{Version: *goversion.Must(goversion.NewVersion("9.0.0"))} + tests := map[string]struct { val tftypes.Value expected func(val StandardRepoHandleValue) bool @@ -39,8 +280,8 @@ func TestStandardRepoHandleValueFromTerraform(t *testing.T) { expected: func(val StandardRepoHandleValue) bool { return val.kind == apitypes.StandardRepoHandleKindUnknown && !val.IsCephHandle() && - !val.IsSupportedFilePath(apitypes.StandardRepoFilePathCeph) && - val.ComponentName() == "unknown" && + !val.IsSupportedFilePath(apitypes.StandardRepoFilePathCeph, nil) && + val.ComponentName(pve9Version) == "unknown" && val.ValueString() == "ceph-foo-enterprise" }, }, @@ -50,8 +291,8 @@ func TestStandardRepoHandleValueFromTerraform(t *testing.T) { return val.kind == apitypes.StandardRepoHandleKindEnterprise && val.CephVersionName() == apitypes.CephVersionNameQuincy && val.IsCephHandle() && - val.IsSupportedFilePath(apitypes.StandardRepoFilePathCeph) && - val.ComponentName() == "enterprise" && + val.IsSupportedFilePath(apitypes.StandardRepoFilePathCeph, nil) && + val.ComponentName(pve9Version) == "enterprise" && val.ValueString() == "ceph-quincy-enterprise" }, }, @@ -61,8 +302,8 @@ func TestStandardRepoHandleValueFromTerraform(t *testing.T) { return val.kind == apitypes.StandardRepoHandleKindNoSubscription && val.CephVersionName() == apitypes.CephVersionNameReef && val.IsCephHandle() && - val.IsSupportedFilePath(apitypes.StandardRepoFilePathCeph) && - val.ComponentName() == "no-subscription" && + val.IsSupportedFilePath(apitypes.StandardRepoFilePathCeph, nil) && + val.ComponentName(pve9Version) == "no-subscription" && val.ValueString() == "ceph-reef-no-subscription" }, }, @@ -72,8 +313,8 @@ func TestStandardRepoHandleValueFromTerraform(t *testing.T) { return val.kind == apitypes.StandardRepoHandleKindTest && val.CephVersionName() == apitypes.CephVersionNameSquid && val.IsCephHandle() && - val.IsSupportedFilePath(apitypes.StandardRepoFilePathCeph) && - val.ComponentName() == "test" && + val.IsSupportedFilePath(apitypes.StandardRepoFilePathCeph, nil) && + val.ComponentName(pve9Version) == "test" && val.ValueString() == "ceph-squid-test" }, }, @@ -82,7 +323,7 @@ func TestStandardRepoHandleValueFromTerraform(t *testing.T) { expected: func(val StandardRepoHandleValue) bool { return val.kind == apitypes.StandardRepoHandleKindUnknown && !val.IsCephHandle() && - !val.IsSupportedFilePath(apitypes.StandardRepoFilePathCeph) && + !val.IsSupportedFilePath(apitypes.StandardRepoFilePathCeph, nil) && val.ValueString() == "foo-bar" }, }, @@ -91,8 +332,8 @@ func TestStandardRepoHandleValueFromTerraform(t *testing.T) { expected: func(val StandardRepoHandleValue) bool { return val.kind == apitypes.StandardRepoHandleKindNoSubscription && !val.IsCephHandle() && - val.IsSupportedFilePath(apitypes.StandardRepoFilePathMain) && - val.ComponentName() == "pve-no-subscription" && + val.IsSupportedFilePath(apitypes.StandardRepoFilePathMain, nil) && + val.ComponentName(pve9Version) == "pve-no-subscription" && val.ValueString() == "no-subscription" }, }, @@ -101,8 +342,9 @@ func TestStandardRepoHandleValueFromTerraform(t *testing.T) { expected: func(val StandardRepoHandleValue) bool { return val.kind == apitypes.StandardRepoHandleKindTest && !val.IsCephHandle() && - val.IsSupportedFilePath(apitypes.StandardRepoFilePathMain) && - val.ComponentName() == "pvetest" && + val.IsSupportedFilePath(apitypes.StandardRepoFilePathMain, nil) && + val.ComponentName(pve8Version) == "pvetest" && + val.ComponentName(pve9Version) == "pve-test" && val.ValueString() == "test" }, }, diff --git a/proxmox/types/nodes/apt/repositories/standard_repo_file_path.go b/proxmox/types/nodes/apt/repositories/standard_repo_file_path.go index 549929c3c..805ac711e 100644 --- a/proxmox/types/nodes/apt/repositories/standard_repo_file_path.go +++ b/proxmox/types/nodes/apt/repositories/standard_repo_file_path.go @@ -9,14 +9,26 @@ package repositories // Note that "hard-coded" slashes are used since Proxmox VE is built on top of Linux (Debian). const ( // StandardRepoFilePathCeph is the default Proxmox VE pre-defined (absolute) file path for the APT source list of Ceph - // repositories. - StandardRepoFilePathCeph = "/etc/apt/sources.list.d/ceph.list" + // repositories (for PVE 9.0 and above using the modern DEB822 .sources format). + StandardRepoFilePathCeph = "/etc/apt/sources.list.d/ceph.sources" + + // OldStandardRepoFilePathCeph is the legacy Proxmox VE pre-defined (absolute) file path for the APT source list of + // Ceph repositories (for PVE versions before 9.0 using the legacy .list format). + OldStandardRepoFilePathCeph = "/etc/apt/sources.list.d/ceph.list" // StandardRepoFilePathEnterprise is the default Proxmox VE pre-defined (absolute) file path for the APT source list - // of enterprise repositories. - StandardRepoFilePathEnterprise = "/etc/apt/sources.list.d/pve-enterprise.list" + // of enterprise repositories (for PVE 9.0 and above using the modern DEB822 .sources format). + StandardRepoFilePathEnterprise = "/etc/apt/sources.list.d/pve-enterprise.sources" + + // OldStandardRepoFilePathEnterprise is the legacy Proxmox VE pre-defined (absolute) file path for the APT source + // list of enterprise repositories (for PVE versions before 9.0 using the legacy .list format). + OldStandardRepoFilePathEnterprise = "/etc/apt/sources.list.d/pve-enterprise.list" // StandardRepoFilePathMain is the default Proxmox VE pre-defined (absolute) file path for the APT source list of main - // OS (Debian) repositories. - StandardRepoFilePathMain = "/etc/apt/sources.list" + // OS (Debian) repositories (for PVE 9.0 and above using the modern DEB822 .sources format). + StandardRepoFilePathMain = "/etc/apt/sources.list.d/proxmox.sources" + + // OldStandardRepoFilePathMain is the legacy Proxmox VE pre-defined (absolute) file path for the APT source list of + // main OS (Debian) repositories (for PVE versions before 9.0 using the legacy .list format). + OldStandardRepoFilePathMain = "/etc/apt/sources.list" ) diff --git a/proxmox/version/capabilities.go b/proxmox/version/capabilities.go index 5608f9cea..b46f91df8 100644 --- a/proxmox/version/capabilities.go +++ b/proxmox/version/capabilities.go @@ -18,3 +18,9 @@ var MinimumProxmoxVersion = ProxmoxVersion{*version.Must(version.NewVersion("8.0 func (v *ProxmoxVersion) SupportImportContentType() bool { return v.GreaterThanOrEqual(version.Must(version.NewVersion("8.4.0"))) } + +// SupportModernAptSources checks if the Proxmox version uses the modern DEB822 format (.sources) for APT repositories. +// PVE 9.0 and above use the new .sources format instead of the legacy .list format. +func (v *ProxmoxVersion) SupportModernAptSources() bool { + return v.GreaterThanOrEqual(version.Must(version.NewVersion("9.0.0"))) +}