package mapper import ( "encoding/json" "strconv" "time" "helm.sh/helm/v3/pkg/repo" "sigs.k8s.io/controller-runtime/pkg/client" helmApiV2 "github.com/fluxcd/helm-controller/api/v2" helmApiV2beta1 "github.com/fluxcd/helm-controller/api/v2beta1" "github.com/fluxcd/pkg/apis/kustomize" goVersion "github.com/hashicorp/go-version" corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "edge-infra.dev/pkg/edge/constants" "edge-infra.dev/pkg/edge/flux/bootstrap" "edge-infra.dev/pkg/edge/api/graph/model" "edge-infra.dev/pkg/edge/api/status" "edge-infra.dev/pkg/edge/api/types" "edge-infra.dev/pkg/edge/api/utils" ) func ToHelmRepositoryKeyValues(name string, url string, username *string, password *string) []*model.KeyValues { kv := []*model.KeyValues{ {Key: constants.HelmURL, Value: url}, {Key: constants.HelmRepoName, Value: name}, } if username != nil { kv = append(kv, &model.KeyValues{Key: constants.HelmUsername, Value: *username}) } if password != nil { kv = append(kv, &model.KeyValues{Key: constants.HelmPassword, Value: *password}) } return kv } func ToHelmRepositoriesModels(helmRepos []*model.SecretManagerResponse) []*model.HelmRepository { helmRepositoryModels := make([]*model.HelmRepository, 0) for _, helmRepo := range helmRepos { helmRepositoryModels = append(helmRepositoryModels, ToHelmRepositoriesModel(helmRepo)) //nolint } return helmRepositoryModels } func ToHelmRepositoriesModel(hs *model.SecretManagerResponse) *model.HelmRepository { repoName, _, _, repoURL := GetHelmSecretData(hs) hr := &model.HelmRepository{ Name: repoName, URL: repoURL, Secret: hs.Name, } if hs.Workload != nil { hr.Workload = *hs.Workload } if hs.Created != nil { hr.CreatedOn = *hs.Created } return hr } func GetHelmSecretData(smr *model.SecretManagerResponse) (name string, username string, password string, repoURL string) { for _, keyValues := range smr.Values { if keyValues.Key == constants.HelmUsername { username = keyValues.Value } else if keyValues.Key == constants.HelmPassword { password = keyValues.Value } else if keyValues.Key == constants.HelmURL { repoURL = keyValues.Value } else if keyValues.Key == constants.HelmRepoName { name = keyValues.Value } } return } func ToHelmReleasesModels(helmReleases []helmApiV2beta1.HelmRelease, helmReleasesData map[string][]*string) []*model.HelmRelease { helmReleaseModels := make([]*model.HelmRelease, 0) for _, helmRelease := range helmReleases { versions := helmReleasesData[helmRelease.Spec.Chart.Spec.Chart] helmReleaseModels = append(helmReleaseModels, ToHelmReleasesModel(&helmRelease, versions)) //nolint } return helmReleaseModels } func ToHelmReleasesStatusModels(helmReleases []helmApiV2beta1.HelmRelease) []*model.HelmReleaseStatus { helmReleaseModels := make([]*model.HelmReleaseStatus, 0) for _, helmRelease := range helmReleases { helmReleaseModels = append(helmReleaseModels, ToHelmReleasesStatusModel(&helmRelease)) //nolint } return helmReleaseModels } func ToHelmReleasesStatusModel(h *helmApiV2beta1.HelmRelease) *model.HelmReleaseStatus { conditions := h.Status.Conditions lastUpdateTime := "" helmStatus := "" installedCondition := &model.HelmCondition{} readyCondition := &model.HelmCondition{} var config string if h.Spec.Values != nil { config = convertConfigJSONtoYAML(h) } if len(conditions) > 0 { lastUpdateTime = conditions[len(conditions)-1].LastTransitionTime.Format(TimeFormat) helmStatus = ToHelmStatus(string(conditions[len(conditions)-1].Status)) installedCondition = ToHelmConditionModel(conditions, helmApiV2.ReleasedCondition) readyCondition = ToHelmConditionModel(conditions, "Ready") } var versionInstalled string if h.Status.History.Latest() != nil { versionInstalled = h.Status.History.Latest().ChartVersion } helmReleaseStatusModel := &model.HelmReleaseStatus{ Name: h.Name, LastActionTime: lastUpdateTime, StatusType: helmStatus, VersionInstalled: versionInstalled, VersionRequested: h.Spec.Chart.Spec.Version, InstallCondition: installedCondition, ReadyCondition: readyCondition, ConfigValues: &config, } if h.Spec.Values != nil { jsonStr := string(h.Spec.Values.Raw) if jsonStr != "" { configMapYaml, _ := utils.JSONToYAML(&jsonStr) yamlStr := string(configMapYaml) helmReleaseStatusModel.ConfigValues = &yamlStr } } return helmReleaseStatusModel } func ToHelmReleasesModel(h *helmApiV2beta1.HelmRelease, versions []*string) *model.HelmRelease { conditions := h.Status.Conditions lastUpdateTime := "" helmStatus := "" installedCondition := &model.HelmCondition{} readyCondition := &model.HelmCondition{} var config string if h.Spec.Values != nil { config = convertConfigJSONtoYAML(h) } if len(conditions) > 0 { lastUpdateTime = conditions[len(conditions)-1].LastTransitionTime.Format(TimeFormat) helmStatus = ToHelmStatus(string(conditions[len(conditions)-1].Status)) installedCondition = ToHelmConditionModel(conditions, helmApiV2.ReleasedCondition) readyCondition = ToHelmConditionModel(conditions, "Ready") } upgradeableVersions, downgradeableVersions, updateAvailable := ToHelmVersion(versions, h.Spec.Chart.Spec.Version) var versionInstalled string if h.Status.History.Latest() != nil { versionInstalled = h.Status.History.Latest().ChartVersion } helmReleaseModel := &model.HelmRelease{ Name: h.Name, HelmChart: h.Spec.Chart.Spec.Chart, LastActionTime: lastUpdateTime, StatusType: helmStatus, VersionInstalled: versionInstalled, VersionRequested: h.Spec.Chart.Spec.Version, InstallCondition: installedCondition, ReadyCondition: readyCondition, Namespace: h.Spec.TargetNamespace, UpgradeableVersions: upgradeableVersions, DowngradeableVersions: downgradeableVersions, UpdateAvailable: &updateAvailable, ConfigValues: &config, } if h.Spec.Values != nil { jsonStr := string(h.Spec.Values.Raw) if jsonStr != "" { configMapYaml, _ := utils.JSONToYAML(&jsonStr) yamlStr := string(configMapYaml) helmReleaseModel.ConfigValues = &yamlStr } } return helmReleaseModel } func ToHelmStatus(statusType string) string { status := "Unknown" if statusType == "True" { status = "Succeeded" } else if statusType == "False" { status = "Failed" } return status } // trim quotation marks from database result (e.g. "\"True\"", "\"UpgradeSucceeded\"") func ToTrimedValue(value string) string { if len(value) > 0 && value[0] == '"' { return value[1 : len(value)-1] } return value } func ToPodStatus(value string) string { switch value { case "True": return status.Running case "False": return status.Error default: return value } } func ToHelmConditionModel(conditions []metav1.Condition, s string) *model.HelmCondition { for i, item := range conditions { if item.Type == s { status := string(item.Status) statusBool, _ := strconv.ParseBool(status) ltt := item.LastTransitionTime.Format(TimeFormat) return &model.HelmCondition{ LastTransitionTime: <t, Message: &conditions[i].Message, Reason: &conditions[i].Reason, Status: &status, Type: &conditions[i].Type, Installed: &statusBool, Ready: &statusBool, } } } return nil } func ToHelmVersion(versions []*string, installedVersion string) ([]*model.HelmVersion, []*model.HelmVersion, bool) { if len(versions) > 0 { upgradeableVersions := make([]*model.HelmVersion, 0) downgradeableVersions := make([]*model.HelmVersion, 0) var updateAvailable = false for _, version := range versions { if compareHelmVersions(*version, installedVersion) { updateAvailable = true upgradeableVersions = append(upgradeableVersions, &model.HelmVersion{ Version: *version, }) } else if *version != installedVersion { downgradeableVersions = append(downgradeableVersions, &model.HelmVersion{ Version: *version, }) } } return upgradeableVersions, downgradeableVersions, updateAvailable } return nil, nil, false } func compareHelmVersions(versionOne string, versionTwo string) bool { v1, err := goVersion.NewVersion(versionOne) if err != nil { return false } v2, err := goVersion.NewVersion(versionTwo) if err != nil { return false } return v1.GreaterThan(v2) } func getHelmReleasePostRenderer(installationType model.WorkloadInstallationType, customLabels []*model.Label, helmEdgeID string) (interface{}, error) { helmReleasePostRenderer := &HelmReleasePostRenderer{ installationType: installationType, customLabels: customLabels, helmEdgeID: helmEdgeID, } return helmReleasePostRenderer.CreatePostrender() } func ToCreateHelmRelease(name, helmRepository, helmChart, version, secretManagerSecret string, namespace string, configMap []byte, installationType model.WorkloadInstallationType, customLabels []*model.Label, clusterVersion string, helmEdgeID string) (client.Object, error) { var ( labels = map[string]string{ "parent-cluster": Region, SecretManagerSecretLabel: secretManagerSecret, } helmRelease client.Object ) supportsFluxV2 := bootstrap.SupportsFluxV024(clusterVersion) if supportsFluxV2 { //nolint: nestif hr := &helmApiV2.HelmRelease{ TypeMeta: metav1.TypeMeta{ APIVersion: helmApiV2.GroupVersion.String(), Kind: "HelmRelease", }, ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: HelmReleaseNamespace, Labels: labels, }, Spec: helmApiV2.HelmReleaseSpec{ Interval: metav1.Duration{ Duration: time.Duration(2) * time.Minute, }, ReleaseName: name, TargetNamespace: namespace, Chart: &helmApiV2.HelmChartTemplate{ Spec: helmApiV2.HelmChartTemplateSpec{ Chart: helmChart, Version: version, SourceRef: helmApiV2.CrossNamespaceObjectReference{ Kind: "HelmRepository", Name: helmRepository, }, }, }, Timeout: &metav1.Duration{ Duration: time.Duration(HelmReleaseTimeout) * time.Minute, }, DriftDetection: &helmApiV2.DriftDetection{ Mode: helmApiV2.DriftDetectionEnabled, }, Install: &helmApiV2.Install{ Remediation: &helmApiV2.InstallRemediation{ Retries: 3, }, }, Upgrade: &helmApiV2.Upgrade{ Remediation: &helmApiV2.UpgradeRemediation{ Retries: 3, }, }, }, } // TODO(pa250194): doesn't look pretty will have to be refactored in future postRenderers := []helmApiV2.PostRenderer{ { Kustomize: &helmApiV2.Kustomize{ Patches: disableDriftDetectionForProblemKinds(), }, }, } if installationType == model.WorkloadInstallationTypeAdvanced { if len(customLabels) > 0 { patches, err := getHelmReleasePostRenderer(installationType, customLabels, helmEdgeID) if err != nil { return nil, err } postRenderers[0].Kustomize.Patches = append(postRenderers[0].Kustomize.Patches, patches.([]kustomize.Patch)...) } } else if installationType != model.WorkloadInstallationTypeNoMod { patches, err := getHelmReleasePostRenderer(installationType, nil, helmEdgeID) if err != nil { return nil, err } postRenderers[0].Kustomize.Patches = append(postRenderers[0].Kustomize.Patches, patches.([]kustomize.Patch)...) } hr.Spec.PostRenderers = postRenderers if configMap != nil { configMapRaw := &apiextensionsv1.JSON{ Raw: configMap, } hr.Spec.Values = configMapRaw } helmRelease = hr } else { hr := &helmApiV2beta1.HelmRelease{ TypeMeta: metav1.TypeMeta{ APIVersion: helmApiV2beta1.GroupVersion.String(), Kind: "HelmRelease", }, ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: HelmReleaseNamespace, Labels: labels, }, Spec: helmApiV2beta1.HelmReleaseSpec{ Interval: metav1.Duration{ Duration: time.Duration(2) * time.Minute, }, ReleaseName: name, TargetNamespace: namespace, Chart: &helmApiV2beta1.HelmChartTemplate{ Spec: helmApiV2beta1.HelmChartTemplateSpec{ Chart: helmChart, Version: version, SourceRef: helmApiV2beta1.CrossNamespaceObjectReference{ Kind: "HelmRepository", Name: helmRepository, }, }, }, Timeout: &metav1.Duration{ Duration: time.Duration(HelmReleaseTimeout) * time.Minute, }, Install: &helmApiV2beta1.Install{ Remediation: &helmApiV2beta1.InstallRemediation{ Retries: 3, }, }, Upgrade: &helmApiV2beta1.Upgrade{ Remediation: &helmApiV2beta1.UpgradeRemediation{ Retries: 3, }, }, }, } postRenderers := []helmApiV2beta1.PostRenderer{ { Kustomize: &helmApiV2beta1.Kustomize{ Patches: make([]kustomize.Patch, 0), }, }, } if installationType == model.WorkloadInstallationTypeAdvanced { if len(customLabels) > 0 { patches, err := getHelmReleasePostRenderer(installationType, customLabels, helmEdgeID) if err != nil { return nil, err } postRenderers[0].Kustomize.Patches = append(postRenderers[0].Kustomize.Patches, patches.([]kustomize.Patch)...) } } else if installationType != model.WorkloadInstallationTypeNoMod { patches, err := getHelmReleasePostRenderer(installationType, nil, helmEdgeID) if err != nil { return nil, err } postRenderers[0].Kustomize.Patches = append(postRenderers[0].Kustomize.Patches, patches.([]kustomize.Patch)...) } hr.Spec.PostRenderers = postRenderers if configMap != nil { configMapRaw := &apiextensionsv1.JSON{ Raw: configMap, } hr.Spec.Values = configMapRaw } helmRelease = hr } return helmRelease, nil } func ToBase64StringFromHelmRelease(helmRelease client.Object) (string, error) { helmReleaseByte, err := json.Marshal(helmRelease) if err != nil { return "", err } helmReleaseBase64 := utils.ToBase64(helmReleaseByte) return helmReleaseBase64, nil } func ToBase64StringFromHelmRepository(helmRepository client.Object) (string, error) { helmRepositoryByte, err := json.Marshal(helmRepository) if err != nil { return "", err } helmRepositoryBase64 := utils.ToBase64(helmRepositoryByte) return helmRepositoryBase64, nil } func ToCreateHelmReleaseBase64String(name, helmRepository, helmChart, version, secretManagerSecret, namespace string, configMap []byte, installationType model.WorkloadInstallationType, customLabels []*model.Label, clusterVersion string, helmEdgeID string) (string, error) { helmRelease, err := ToCreateHelmRelease(name, helmRepository, helmChart, version, secretManagerSecret, namespace, configMap, installationType, customLabels, clusterVersion, helmEdgeID) if err != nil { return "", err } return ToBase64StringFromHelmRelease(helmRelease) } func ToHelmChartResponse(name string, versions []*string) *model.HelmChartResponse { return &model.HelmChartResponse{ Name: name, Versions: versions, } } func ToHelmChartsFromIndex(index *repo.IndexFile) []*model.HelmChart { helmChartModels := make([]*model.HelmChart, 0) if index == nil || index.Entries == nil { return helmChartModels } for _, entries := range index.Entries { helmChart := model.HelmChart{} if !utils.IsNullOrEmpty(&entries[0].Name) { helmChart.Name = entries[0].Name } if !utils.IsNullOrEmpty(&entries[0].Description) { helmChart.Description = entries[0].Description } if !utils.IsNullOrEmpty(&entries[0].Version) { helmChart.Version = entries[0].Version } if !utils.IsNullOrEmpty(&entries[0].AppVersion) { helmChart.AppVersion = entries[0].AppVersion } if !utils.IsNullOrEmpty(&entries[0].Icon) { helmChart.Icon = entries[0].Icon } if entries[0].Keywords != nil { helmChart.Keywords = entries[0].Keywords } if entries[0].Sources != nil { helmChart.Sources = entries[0].Sources } if entries[0].URLs != nil { helmChart.Urls = entries[0].URLs } helmChart.Created = entries[0].Created.String() helmChartModels = append(helmChartModels, &helmChart) } return helmChartModels } func convertConfigJSONtoYAML(h *helmApiV2beta1.HelmRelease) string { configValuesJSON := h.Spec.Values configValues := configValuesJSON.Raw config := string(configValues) configYaml, _ := utils.JSONToYAML(&config) config = string(configYaml) return config } func ConvertToConfigMapData(res []string) ([]*model.ConfigmapData, error) { var configmaps = make([]*model.ConfigmapData, 0) for i := range res { configmap := &corev1.ConfigMap{} err := json.Unmarshal([]byte(res[i]), configmap) if err != nil { return nil, err } for key, value := range configmap.Data { key := key value := value configmaps = append(configmaps, &model.ConfigmapData{ Key: &key, Value: &value, }) } } return configmaps, nil } func ConvertToConfigMap(res []string) (*corev1.ConfigMap, error) { configmap := &corev1.ConfigMap{} err := json.Unmarshal([]byte(res[0]), configmap) return configmap, err } func UpdateHelmWorkload(helmWorkload *model.HelmWorkload, versions []*string, secrets []*model.HelmSecrets, maps []*model.HelmWorkloadConfigmaps, labels []*model.Label) *model.HelmWorkload { upgradeableVersions, downgradeableVersions, updateAvailable := ToHelmVersion(versions, helmWorkload.HelmChartVersion) helmWorkload.UpdateAvailable = &updateAvailable helmWorkload.UpgradeableVersions = upgradeableVersions helmWorkload.DowngradeableVersions = downgradeableVersions helmWorkload.Secrets = secrets helmWorkload.Configmaps = maps helmWorkload.Labels = labels return helmWorkload } func ToHelmWorkload(helmWorkloadsData types.HelmWorkloadQuery) *model.HelmWorkload { return &model.HelmWorkload{ HelmEdgeID: helmWorkloadsData.HelmEdgeID, HelmChart: helmWorkloadsData.HelmChart, Name: helmWorkloadsData.Name, HelmRepository: helmWorkloadsData.HelmRepository, CreatedAt: helmWorkloadsData.CreatedAt, UpdatedAt: helmWorkloadsData.UpdatedAt, HelmChartVersion: helmWorkloadsData.HelmChartVersion, ConfigValues: helmWorkloadsData.HelmConfig, Namespace: helmWorkloadsData.Namespace, ClusterEdgeID: helmWorkloadsData.ClusterEdgeID, BannerEdgeID: helmWorkloadsData.BannerEdgeID, HelmRepoSecret: helmWorkloadsData.HelmRepoSecret, InstalledBy: helmWorkloadsData.InstalledBy, InstallationType: &helmWorkloadsData.WorkloadInstallationType, Deleted: helmWorkloadsData.Deleted, } }