diff --git a/docs/data-sources/pack.md b/docs/data-sources/pack.md index eb4a38980..8244c8a44 100644 --- a/docs/data-sources/pack.md +++ b/docs/data-sources/pack.md @@ -61,7 +61,7 @@ data "spectrocloud_pack" "cni" { - `name` (String) The name of the pack to search for. - `registry_uid` (String) The unique identifier (UID) of the registry where the pack is located. Specify `registry_uid` to search within a specific registry. - `type` (String) The type of pack to search for. Supported values are `helm`, `manifest`, `container`, `operator-instance`. -- `version` (String) The version of the pack to search for. +- `version` (String) Specify the version of the pack to search for. If not set, the latest available version from the specified registry will be used. ### Read-Only diff --git a/go.mod b/go.mod index 8bfb6fceb..c8dad5a00 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/spectrocloud/terraform-provider-spectrocloud go 1.22.5 require ( + github.com/Masterminds/semver/v3 v3.1.1 github.com/go-openapi/strfmt v0.23.0 github.com/google/go-cmp v0.6.0 github.com/gorilla/mux v1.8.0 @@ -12,7 +13,7 @@ require ( github.com/robfig/cron v1.2.0 github.com/spectrocloud/gomi v1.14.1-0.20240214074114-c19394812368 github.com/spectrocloud/hapi v1.14.1-0.20240214071352-81f589b1d86d - github.com/spectrocloud/palette-sdk-go v0.0.0-20241227233030-cd7c351b4722 + github.com/spectrocloud/palette-sdk-go v0.0.0-20250129091228-61bad491e5e8 github.com/stretchr/testify v1.10.0 gotest.tools v2.2.0+incompatible k8s.io/api v0.23.5 @@ -23,7 +24,6 @@ require ( require ( github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.1.1 // indirect github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect github.com/agext/levenshtein v1.2.2 // indirect diff --git a/go.sum b/go.sum index 5ba9ea2d6..f9b6286a1 100644 --- a/go.sum +++ b/go.sum @@ -601,8 +601,8 @@ github.com/spectrocloud/gomi v1.14.1-0.20240214074114-c19394812368 h1:eY0BOyEbGu github.com/spectrocloud/gomi v1.14.1-0.20240214074114-c19394812368/go.mod h1:LlZ9We4kDaELYi7Is0SVmnySuDhwphJLS6ZT4wXxFIk= github.com/spectrocloud/hapi v1.14.1-0.20240214071352-81f589b1d86d h1:OMRbHxMJ1a+G1BYzvUYuMM0wLkYJPdnEOFx16faQ/UY= github.com/spectrocloud/hapi v1.14.1-0.20240214071352-81f589b1d86d/go.mod h1:MktpRPnSXDTHsQrFSD+daJFQ1zMLSR+1gWOL31jVvWE= -github.com/spectrocloud/palette-sdk-go v0.0.0-20241227233030-cd7c351b4722 h1:Z574padnTtuCGPZfxhtbRGl3CtsAF8Zx+rc5aCo30zU= -github.com/spectrocloud/palette-sdk-go v0.0.0-20241227233030-cd7c351b4722/go.mod h1:Zv1+/Imw/lIOPAa+q9TzdyKiXmIzfLSwVTj11WemIZc= +github.com/spectrocloud/palette-sdk-go v0.0.0-20250129091228-61bad491e5e8 h1:VjJvMdivBh0TLPKJhtbZa1OYcGmbvoVIAHNN8v1cnvc= +github.com/spectrocloud/palette-sdk-go v0.0.0-20250129091228-61bad491e5e8/go.mod h1:Zv1+/Imw/lIOPAa+q9TzdyKiXmIzfLSwVTj11WemIZc= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= diff --git a/spectrocloud/data_source_pack.go b/spectrocloud/data_source_pack.go index fc22f48cb..472768a2f 100644 --- a/spectrocloud/data_source_pack.go +++ b/spectrocloud/data_source_pack.go @@ -3,6 +3,11 @@ package spectrocloud import ( "context" "fmt" + "github.com/Masterminds/semver/v3" + "github.com/spectrocloud/gomi/pkg/ptr" + "github.com/spectrocloud/palette-sdk-go/api/models" + "github.com/spectrocloud/palette-sdk-go/client" + "sort" "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -46,7 +51,7 @@ func dataSourcePack() *schema.Resource { }, "version": { Type: schema.TypeString, - Description: "The version of the pack to search for.", + Description: "Specify the version of the pack to search for. If not set, the latest available version from the specified registry will be used.", Computed: true, Optional: true, }, @@ -73,6 +78,7 @@ func dataSourcePack() *schema.Resource { func dataSourcePackRead(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := getV1ClientWithResourceContext(m, "") + var packName = "" // Warning or errors can be collected in a slice type var diags diag.Diagnostics @@ -115,17 +121,24 @@ func dataSourcePackRead(_ context.Context, d *schema.ResourceData, m interface{} and first part would be any random name to make overall pack name unique and 2nd part is actual pack name. Thus, splitting pack name with '--' to get the correct pack name to find pack uuid */ + if strings.Contains(v.(string), "--") { v = strings.Split(v.(string), "--")[1] } + packName = v.(string) filters = append(filters, fmt.Sprintf("spec.name=%s", v.(string))) } - if v, ok := d.GetOk("version"); ok { - filters = append(filters, fmt.Sprintf("spec.version=%s", v.(string))) - } if v, ok := d.GetOk("registry_uid"); ok { registryUID = v.(string) } + if v, ok := d.GetOk("version"); ok { + filters = append(filters, fmt.Sprintf("spec.version=%s", v.(string))) + } else { + latestVersion := setLatestPackVersionToFilters(packName, registryUID, c) + if latestVersion != "" { + filters = append(filters, fmt.Sprintf("spec.version=%s", latestVersion)) + } + } if v, ok := d.GetOk("cloud"); ok { clouds := expandStringList(v.(*schema.Set).List()) if !stringContains(clouds, "all") { @@ -140,7 +153,7 @@ func dataSourcePackRead(_ context.Context, d *schema.ResourceData, m interface{} return diag.FromErr(err) } - packName := "unknown" + packName = "unknown" if v, ok := d.GetOk("name"); ok { packName = v.(string) } @@ -196,3 +209,49 @@ func dataSourcePackRead(_ context.Context, d *schema.ResourceData, m interface{} return diags } + +func setLatestPackVersionToFilters(packName string, registryUID string, c *client.V1Client) string { + var packLayers = []models.V1PackLayer{"addon", "csi", "cni", "os", "kernel"} + var packTypes = []models.V1PackType{"spectro", "helm", "manifest", "oci"} + var packAddOnTypes = []string{"load balancer", "ingress", "logging", "monitoring", "security", "authentication", + "servicemesh", "system app", "app services", "registry", "csi", "cni", "integration", ""} + + newFilter := &models.V1PackFilterSpec{ + Name: &models.V1FilterString{ + Eq: ptr.StringPtr(packName), + }, + Type: packTypes, + Layer: packLayers, + Environment: []string{"all"}, + AddOnType: packAddOnTypes, + } + if registryUID != "" { + newFilter.RegistryUID = []string{registryUID} + } + var newSort []*models.V1PackSortSpec + latestVersion := "" + packsResults, _ := c.SearchPacks(newFilter, newSort) + if len(packsResults) == 1 { + latestVersion, _ = getLatestVersion(packsResults[0].Spec.Registries) + return latestVersion + } + return "" +} + +// getLatestVersion returns the latest version from a list of version strings. +func getLatestVersion(versions []*models.V1RegistryPackMetadata) (string, error) { + if len(versions) == 0 { + return "", fmt.Errorf("no versions provided") + } + semverVersions := make([]*semver.Version, len(versions)) + for i, v := range versions { + ver, err := semver.NewVersion(v.LatestVersion) + if err != nil { + return "", fmt.Errorf("invalid version %q: %w", v, err) + } + semverVersions[i] = ver + } + sort.Sort(semver.Collection(semverVersions)) + + return semverVersions[len(semverVersions)-1].String(), nil +} diff --git a/spectrocloud/data_source_pack_test.go b/spectrocloud/data_source_pack_test.go index 544615aec..afa147f51 100644 --- a/spectrocloud/data_source_pack_test.go +++ b/spectrocloud/data_source_pack_test.go @@ -3,6 +3,7 @@ package spectrocloud import ( "context" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/spectrocloud/palette-sdk-go/api/models" "github.com/stretchr/testify/assert" "testing" ) @@ -48,3 +49,61 @@ func TestDataSourcePacksReadHelmMultiPacks(t *testing.T) { assertFirstDiagMessage(t, diags, "Multiple packs returned") } + +func TestGetLatestVersion(t *testing.T) { + t.Run("valid versions", func(t *testing.T) { + versions := []*models.V1RegistryPackMetadata{ + {LatestVersion: "v1.0.0"}, + {LatestVersion: "v1.2.0"}, + {LatestVersion: "v1.1.0"}, + } + latest, err := getLatestVersion(versions) + + assert.NoError(t, err, "Expected no error") + assert.Equal(t, "1.2.0", latest, "The latest version should be returned") + }) + + t.Run("empty versions list", func(t *testing.T) { + versions := []*models.V1RegistryPackMetadata{} + latest, err := getLatestVersion(versions) + + assert.Error(t, err, "Expected an error for empty versions list") + assert.Equal(t, "", latest, "No version should be returned") + assert.Equal(t, "no versions provided", err.Error(), "Expected specific error message") + }) + + t.Run("invalid version string", func(t *testing.T) { + versions := []*models.V1RegistryPackMetadata{ + {LatestVersion: "1.0.0"}, + {LatestVersion: "invalid-version"}, + {LatestVersion: "1.1.0"}, + } + latest, err := getLatestVersion(versions) + + assert.Error(t, err, "Expected an error for invalid version string") + assert.Equal(t, "", latest, "No version should be returned for invalid input") + assert.Contains(t, err.Error(), "invalid version", "Error message should indicate invalid version") + }) + + t.Run("single version", func(t *testing.T) { + versions := []*models.V1RegistryPackMetadata{ + {LatestVersion: "2.0.0"}, + } + latest, err := getLatestVersion(versions) + + assert.NoError(t, err, "Expected no error") + assert.Equal(t, "2.0.0", latest, "The single version should be returned") + }) + + t.Run("pre-release versions", func(t *testing.T) { + versions := []*models.V1RegistryPackMetadata{ + {LatestVersion: "1.0.0-alpha"}, + {LatestVersion: "1.0.0-beta"}, + {LatestVersion: "1.0.0"}, + } + latest, err := getLatestVersion(versions) + + assert.NoError(t, err, "Expected no error") + assert.Equal(t, "1.0.0", latest, "The stable version should be returned as the latest") + }) +}