diff --git a/api/v1alpha1/helm_cluster_addon_chart.go b/api/v1alpha1/helm_cluster_addon_chart.go index a623fb8..ef3500e 100644 --- a/api/v1alpha1/helm_cluster_addon_chart.go +++ b/api/v1alpha1/helm_cluster_addon_chart.go @@ -64,6 +64,8 @@ func (r *HelmClusterAddonChart) GetConditionTypesForUpdate() []string { } type HelmClusterAddonChartStatus struct { + // IconURL is the URL to the Helm chart icon (applicable to Helm Chart repository charts only). + IconURL string `json:"iconURL,omitempty"` // Conditions represent the latest available observations of the addon chart state. // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` diff --git a/crds/doc-ru-helmclusteraddoncharts.yaml b/crds/doc-ru-helmclusteraddoncharts.yaml index a7e7948..4788076 100644 --- a/crds/doc-ru-helmclusteraddoncharts.yaml +++ b/crds/doc-ru-helmclusteraddoncharts.yaml @@ -12,6 +12,8 @@ spec: properties: status: properties: + iconURL: + description: URL-адрес иконки Helm-чарта. Применимо только для чартов из Helm-репозиториев. conditions: description: Условия отражают последние наблюдения за состоянием репозитория. observedGeneration: diff --git a/crds/helmclusteraddoncharts.yaml b/crds/helmclusteraddoncharts.yaml index b9cc38e..70f01a0 100644 --- a/crds/helmclusteraddoncharts.yaml +++ b/crds/helmclusteraddoncharts.yaml @@ -103,6 +103,10 @@ spec: - type type: object type: array + iconURL: + description: IconURL is the URL to the Helm chart icon (applicable + to Helm Chart repository charts only). + type: string observedGeneration: description: Generation represents resource generation that was last processed by the controller. diff --git a/images/operator-helm-controller/go.mod b/images/operator-helm-controller/go.mod index c04c6b6..f819642 100644 --- a/images/operator-helm-controller/go.mod +++ b/images/operator-helm-controller/go.mod @@ -61,6 +61,7 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect + github.com/samber/lo v1.53.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/pflag v1.0.10 // indirect diff --git a/images/operator-helm-controller/go.sum b/images/operator-helm-controller/go.sum index 22e4e8c..560d15e 100644 --- a/images/operator-helm-controller/go.sum +++ b/images/operator-helm-controller/go.sum @@ -111,6 +111,8 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM= +github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= diff --git a/images/operator-helm-controller/internal/client/repository/client.go b/images/operator-helm-controller/internal/client/repository/client.go index 99a1ee7..69cd889 100644 --- a/images/operator-helm-controller/internal/client/repository/client.go +++ b/images/operator-helm-controller/internal/client/repository/client.go @@ -23,12 +23,22 @@ import ( "fmt" "net/http" - helmv1alpha1 "github.com/deckhouse/operator-helm/api/v1alpha1" + "github.com/Masterminds/semver/v3" "github.com/deckhouse/operator-helm/internal/utils" ) +type Chart struct { + Name string + Versions []ChartVersion +} + +type ChartVersion struct { + Version *semver.Version + IconURL string +} + type ClientInterface interface { - FetchCharts(ctx context.Context, url string, config *RepoConfig) (map[string][]helmv1alpha1.HelmClusterAddonChartVersion, error) + FetchCharts(ctx context.Context, url string, config *RepoConfig) ([]Chart, error) } func NewClient(repoType utils.InternalRepositoryType) (ClientInterface, error) { diff --git a/images/operator-helm-controller/internal/client/repository/helm.go b/images/operator-helm-controller/internal/client/repository/helm.go index ae6ce07..0e1dc34 100644 --- a/images/operator-helm-controller/internal/client/repository/helm.go +++ b/images/operator-helm-controller/internal/client/repository/helm.go @@ -20,20 +20,33 @@ import ( "context" "fmt" "net/http" + "sort" "strings" "time" "go.yaml.in/yaml/v3" "k8s.io/apimachinery/pkg/util/wait" - helmv1alpha1 "github.com/deckhouse/operator-helm/api/v1alpha1" + "github.com/Masterminds/semver/v3" ) var HelmRepositoryDefaultClient ClientInterface = &helmRepositoryClient{} type helmRepositoryClient struct{} -func (c *helmRepositoryClient) FetchCharts(ctx context.Context, url string, config *RepoConfig) (map[string][]helmv1alpha1.HelmClusterAddonChartVersion, error) { +type HelmRepositoryIndex struct { + APIVersion string `json:"apiVersion"` + Entries map[string][]HelmRepositoryChartVersion `json:"entries"` +} + +type HelmRepositoryChartVersion struct { + Icon string `json:"icon,omitempty"` + Version string `json:"version"` + Digest string `json:"digest"` + Removed bool `json:"removed,omitempty"` +} + +func (c *helmRepositoryClient) FetchCharts(ctx context.Context, url string, config *RepoConfig) ([]Chart, error) { if !strings.HasSuffix(url, "/index.yaml") { url += "/index.yaml" } @@ -89,32 +102,30 @@ func (c *helmRepositoryClient) FetchCharts(ctx context.Context, url string, conf return nil, fmt.Errorf("helm repository index.yaml request failed: %w", err) } - charts := make(map[string][]helmv1alpha1.HelmClusterAddonChartVersion) + charts := make([]Chart, 0, len(indexFile.Entries)) for chartName, chartInfo := range indexFile.Entries { - charts[chartName] = make([]helmv1alpha1.HelmClusterAddonChartVersion, 0) + chart := Chart{Name: chartName, Versions: make([]ChartVersion, 0, len(chartInfo))} for _, chartVersion := range chartInfo { if chartVersion.Removed { continue } - charts[chartName] = append(charts[chartName], helmv1alpha1.HelmClusterAddonChartVersion{ - Version: chartVersion.Version, - }) + semVersion, err := semver.NewVersion(chartVersion.Version) + if err != nil { + return nil, fmt.Errorf("failed to parse chart %q version %q: %w", chartName, chartVersion.Version, err) + } + + chart.Versions = append(chart.Versions, ChartVersion{Version: semVersion, IconURL: chartVersion.Icon}) } - } - return charts, nil -} + sort.Slice(chart.Versions, func(i, j int) bool { + return chart.Versions[i].Version.GreaterThan(chart.Versions[j].Version) + }) -type HelmRepositoryIndex struct { - APIVersion string `json:"apiVersion"` - Entries map[string][]HelmRepositoryChartVersion `json:"entries"` -} + charts = append(charts, chart) + } -type HelmRepositoryChartVersion struct { - Version string `json:"version"` - Digest string `json:"digest"` - Removed bool `json:"removed,omitempty"` + return charts, nil } diff --git a/images/operator-helm-controller/internal/client/repository/oci.go b/images/operator-helm-controller/internal/client/repository/oci.go index 0a07a1b..4e02b01 100644 --- a/images/operator-helm-controller/internal/client/repository/oci.go +++ b/images/operator-helm-controller/internal/client/repository/oci.go @@ -27,15 +27,13 @@ import ( "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" - - helmv1alpha1 "github.com/deckhouse/operator-helm/api/v1alpha1" ) var OCIRepositoryDefaultClient ClientInterface = &ociRepositoryClient{} type ociRepositoryClient struct{} -func (c *ociRepositoryClient) FetchCharts(ctx context.Context, url string, config *RepoConfig) (map[string][]helmv1alpha1.HelmClusterAddonChartVersion, error) { +func (c *ociRepositoryClient) FetchCharts(ctx context.Context, url string, config *RepoConfig) ([]Chart, error) { url = trimSchemaPrefixes(url) url = strings.TrimSuffix(url, "/") @@ -82,21 +80,26 @@ func (c *ociRepositoryClient) FetchCharts(ctx context.Context, url string, confi return nil, fmt.Errorf("listing image tags: %w", err) } - var chartVersions []helmv1alpha1.HelmClusterAddonChartVersion + var chartVersions []ChartVersion for _, tag := range tags { - if isCosignTag(tag) || !isSemverCompliantTag(tag) { + if isCosignTag(tag) { + continue + } + + semVersion, err := semver.NewVersion(tag) + if err != nil { continue } - // Do not obtain digests as they are currently not used and require a HEAD request per tag. - chartVersions = append(chartVersions, helmv1alpha1.HelmClusterAddonChartVersion{ - Version: tag, - }) + chartVersions = append(chartVersions, ChartVersion{Version: semVersion}) } - return map[string][]helmv1alpha1.HelmClusterAddonChartVersion{ - chartName: chartVersions, + return []Chart{ + { + Name: chartName, + Versions: chartVersions, + }, }, nil } diff --git a/images/operator-helm-controller/internal/services/repo_sync_service.go b/images/operator-helm-controller/internal/services/repo_sync_service.go index 9b8dcdf..2940d8b 100644 --- a/images/operator-helm-controller/internal/services/repo_sync_service.go +++ b/images/operator-helm-controller/internal/services/repo_sync_service.go @@ -34,6 +34,7 @@ import ( repoclient "github.com/deckhouse/operator-helm/internal/client/repository" "github.com/deckhouse/operator-helm/internal/manager/status" "github.com/deckhouse/operator-helm/internal/utils" + "github.com/samber/lo" ) const ( @@ -125,8 +126,12 @@ func (s *RepoSyncService) EnsureAddonCharts(ctx context.Context, repo *helmv1alp desiredCharts := make(map[string]struct{}, len(charts)) - for chart, versions := range charts { - addonChartName := utils.GetHelmClusterAddonChartName(repo.Name, chart) + for _, chart := range charts { + if len(chart.Versions) == 0 { + continue + } + + addonChartName := utils.GetHelmClusterAddonChartName(repo.Name, chart.Name) existing := &helmv1alpha1.HelmClusterAddonChart{ ObjectMeta: metav1.ObjectMeta{ Name: addonChartName, @@ -149,7 +154,7 @@ func (s *RepoSyncService) EnsureAddonCharts(ctx context.Context, repo *helmv1alp existing.Labels = map[string]string{ helmv1alpha1.LabelDeckhouseHeritage: helmv1alpha1.LabelDeckhouseHeritageValue, LabelRepositoryName: repo.Name, - LabelChartName: chart, + LabelChartName: chart.Name, } return nil }) @@ -169,7 +174,11 @@ func (s *RepoSyncService) EnsureAddonCharts(ctx context.Context, repo *helmv1alp } base := existing.DeepCopy() - existing.Status.Versions = versions + + existing.Status.IconURL = chart.Versions[0].IconURL + existing.Status.Versions = lo.Map(chart.Versions, func(v repoclient.ChartVersion, _ int) helmv1alpha1.HelmClusterAddonChartVersion { + return helmv1alpha1.HelmClusterAddonChartVersion{Version: v.Version.Original()} + }) if err := s.Client.Status().Patch(ctx, existing, client.MergeFrom(base)); err != nil { return RepoSyncResult{