package services import ( "context" "database/sql" "encoding/base64" "encoding/json" "errors" "fmt" "strings" "sync" "time" goext "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1" helmApi "github.com/fluxcd/helm-controller/api/v2beta1" fluxmeta "github.com/fluxcd/pkg/apis/meta" sourceApi "github.com/fluxcd/source-controller/api/v1beta2" "github.com/go-resty/resty/v2" "github.com/qri-io/jsonschema" "github.com/rs/zerolog/log" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/repo" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" "edge-infra.dev/pkg/edge/api/apierror" sqlerr "edge-infra.dev/pkg/edge/api/apierror/sql" "edge-infra.dev/pkg/edge/api/clients" "edge-infra.dev/pkg/edge/api/graph/mapper" "edge-infra.dev/pkg/edge/api/graph/model" "edge-infra.dev/pkg/edge/api/middleware" sqlquery "edge-infra.dev/pkg/edge/api/sql" "edge-infra.dev/pkg/edge/api/status" "edge-infra.dev/pkg/edge/api/types" "edge-infra.dev/pkg/edge/api/utils" "edge-infra.dev/pkg/edge/bsl" chariotClientApi "edge-infra.dev/pkg/edge/chariot/client" "edge-infra.dev/pkg/edge/constants" "edge-infra.dev/pkg/edge/constants/api/fleet" "edge-infra.dev/pkg/edge/externalsecrets" "edge-infra.dev/pkg/lib/runtime/version" ) const ( helmRepoSecretType = "helm-repository" helmReleaseKind = "HelmRelease" chariotPath = "chariot/" EdgeInjectableConfigmapAnnotation = "injector.edge.ncr.com/configmap" unknownStatus = "Unknown" maxHelmWorkloadConcurrency = 20 ) var ( ErrorDeleteHelmRepo = errors.New("helm repository cannot be deleted it has associated helm releases") ) //go:generate mockgen -destination=../mocks/mock_helm_service.go -package=mocks edge-infra.dev/pkg/edge/api/services HelmService type HelmService interface { UpdateHelmReleaseSQL(ctx context.Context, helmEdgeID string, version, configValues *string, injectConfigmaps []model.InjectableConfigmaps) error CreateHelmReleaseSQL(ctx context.Context, payload model.HelmReleasePayload, cluster *model.Cluster) error ValidateHelmConfig(config string, schema string) (bool, error) GetHelmCharts(ctx context.Context, secretName, projectID string) ([]*model.HelmChart, error) GetHelmChartVersion(ctx context.Context, name, secretName, projectID string) (*model.HelmChartResponse, error) DeleteHelmReleaseSQL(ctx context.Context, clusterEdgeID string) ([]string, error) SoftDeleteHelmReleaseSQL(ctx context.Context, helmEdgeID, name, clusterEdgeID *string) error GetDefaultConfigSchema(ctx context.Context, chartName string, secret string, chartVersion string, projectID string) (*model.HelmConfig, error) CreateBannerHelmRepository(ctx context.Context, name string, url string, secret *string, projectID string) error DeleteHelmRepo(ctx context.Context, name string, projectID string, bannerEdgeID string) error GetHelmRepositoryInfo(ctx context.Context, params model.HelmConfigSchemaParams, projectID string) (*model.HelmRepositoryInfo, error) GenerateHelmReleaseExternalSecrets(ctx context.Context, projectID string, namespace string, secrets []string) ([]*externalsecrets.ExternalSecret, error) SendExternalSecretToChariot(ctx context.Context, projectID, id string, externalSecrets []*externalsecrets.ExternalSecret) error GetHelmReleasesStatus(ctx context.Context, cluster *model.Cluster) ([]*model.HelmReleaseStatus, error) GetHelmWorkloadsByName(ctx context.Context, clusterEdgeID string, workloadName string) ([]*model.HelmWorkload, error) GetHelmWorkloads(ctx context.Context, clusterEdgeID *string, bannerEdgeID *string, all bool) ([]*model.HelmWorkload, error) GetHelmRepositoryIndex(repoURL string, username string, password string) (*repo.IndexFile, error) GetHelmWorkload(ctx context.Context, helmEdgeID, clusterEdgeID string) (*model.HelmWorkload, error) GetAttachedHelmSecrets(ctx context.Context, helmEdgeID *string) ([]*model.HelmSecrets, error) DeleteHelmSecrets(ctx context.Context, helmEdgeID string, deleteSecrets []string) (bool, error) GetSecretsByHelmEdgeID(ctx context.Context, helmEdgeID string) ([]string, error) AddHelmSecrets(ctx context.Context, helmEdgeID string, secretName []string) (bool, error) GetHelmStatus(ctx context.Context, clusterEdgeID, workloadName, workloadNamespace, helmChart, helmRepo, createdAt, updatedAt string, deleted *bool) (*model.HelmStatus, error) GetLabelsByHelmEdgeID(ctx context.Context, helmEdgeID string) ([]*model.Label, error) AddWorkloadLabel(ctx context.Context, helmEdgeID, newLabel string) (bool, error) AddWorkloadLabels(ctx context.Context, helmEdgeID string, newLabels []string) (bool, error) DeleteWorkloadLabel(ctx context.Context, helmEdgeID, deleteLabel string) (bool, error) DeleteWorkloadLabels(ctx context.Context, helmEdgeID string) (bool, error) DeleteWorkloadLabelByLabelEdgeID(ctx context.Context, deleteLabel string) (bool, error) GetHelmWorkloadConfigmaps(ctx context.Context, helmEdgeID string) ([]*model.HelmWorkloadConfigmaps, error) GetBannerByHelmEdgeID(ctx context.Context, helmEdgeID string) (*model.Banner, error) GetHelmRepoConnectionInfo(ctx context.Context, secretName string, projectID string) (string, string, string, error) GetHelmChartFromIndex(index *repo.IndexFile, chartName string, chartVersion string, username string, password string) (*chart.Chart, error) GetSoftDeletedHelmWorkloads(ctx context.Context, clusterEdgeID string) ([]*model.HelmWorkload, error) DeleteHelmReleaseByHelmEdgeID(ctx context.Context, helmEdgeID string) error } type helmService struct { Config *types.Config ChariotService ChariotService GCPService GCPService SQLDB *sql.DB BQClient clients.BQClient CompatibilityService CompatibilityService } func (s *helmService) GenerateHelmReleaseExternalSecrets(ctx context.Context, projectID string, namespace string, secrets []string) ([]*externalsecrets.ExternalSecret, error) { externalSecrets := make([]*externalsecrets.ExternalSecret, 0) secretManagerRes := make([]*model.SecretManagerResponse, 0) for _, name := range secrets { nameRef := name //implicit memory aliasing gotcha secretResponse, err := s.GCPService.GetSecrets(ctx, &nameRef, nil, nil, true, projectID) if err != nil { return nil, err } secretManagerRes = append(secretManagerRes, secretResponse...) } for _, secret := range secretManagerRes { externalSecret := createExternalSecretByType(projectID, namespace, secret) if externalSecret == nil { return nil, errors.New("secret type not supported") } externalSecrets = append(externalSecrets, externalSecret) } return externalSecrets, nil } func (s *helmService) SendExternalSecretToChariot(ctx context.Context, projectID, id string, externalSecrets []*externalsecrets.ExternalSecret) error { chariotMessage := chariotClientApi. NewChariotMessage(). SetBanner(projectID). SetCluster(id). SetOperation(chariotClientApi.Create). SetOwner(ComponentOwner) for _, externalSecret := range externalSecrets { externalSecretData, err := externalSecret.Build() if err != nil { return err } externalSecretByte, err := json.Marshal(externalSecretData) if err != nil { return err } externalSecretString := base64.StdEncoding.EncodeToString(externalSecretByte) chariotMessage.AddObject(externalSecretString) } if err := s.ChariotService.InvokeChariotPubsub(ctx, chariotMessage, nil); err != nil { err := fmt.Errorf("error calling chariot v2: %w", err) log.Ctx(ctx).Err(err).Msg("chariot invocation failed") return err } return nil } func createExternalSecretByType(projectID string, namespace string, secret *model.SecretManagerResponse) *externalsecrets.ExternalSecret { externalSecret := externalsecrets.DefaultExternalSecret(). Name(secret.Name). Namespace(namespace). Path(chariotPath). ProjectID(projectID). K8sSecretName(secret.Name) switch *secret.Type { case externalsecrets.DockerPullSecretType: for _, kv := range secret.Values { if kv.Key == "dockerconfigjson" { return externalSecret.DockerConfig(secret.Name, kv.Key) } } default: for _, val := range secret.Values { externalSecret.MapSecretFieldToK8sSecretKey(secret.Name, val.Key, val.Key) } } return externalSecret } func (s *helmService) DeleteHelmRepo(ctx context.Context, name string, projectID string, bannerEdgeID string) error { if err := s.checkDependentWorkloads(ctx, name, bannerEdgeID); err != nil { return err } helmRepo := &sourceApi.HelmRepository{ TypeMeta: metav1.TypeMeta{ Kind: sourceApi.HelmRepositoryKind, APIVersion: sourceApi.GroupVersion.String(), }, ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: constants.FluxSystem, }, } helmRepositoryBase64, err := mapper.ToBase64StringFromHelmRepository(helmRepo) if err != nil { return err } err = s.sendChariotMessage(ctx, projectID, nil, chariotClientApi.Delete, helmRepositoryBase64) //delete accompanying helm repo secret if err != nil { return err } extSec := &goext.ExternalSecret{TypeMeta: metav1.TypeMeta{ APIVersion: goext.ExtSecretGroupVersionKind.GroupVersion().String(), Kind: goext.ExtSecretGroupVersionKind.Kind, }, ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: mapper.HelmReleaseNamespace}, } extSecString, err := mapper.ToBase64StringFromExternalSecret(extSec) if err != nil { return err } err = s.sendChariotMessage(ctx, projectID, nil, chariotClientApi.Delete, extSecString) if err != nil { return err } return nil } func (s *helmService) checkDependentWorkloads(ctx context.Context, name string, bannerEdgeID string) error { helmWorkloads, err := s.GetHelmWorkloadsData(ctx, nil, &bannerEdgeID, true) if err != nil { return fmt.Errorf("Error getting helm workloads data for banner %s: %s", bannerEdgeID, err) } for _, workload := range helmWorkloads { if workload.HelmRepository == name { return ErrorDeleteHelmRepo } } return nil } func (s *helmService) CreateBannerHelmRepository(ctx context.Context, name string, url string, secret *string, projectID string) error { var secretRef *fluxmeta.LocalObjectReference if secret != nil { secretRef = &fluxmeta.LocalObjectReference{ Name: *secret, } } helmRepo := &sourceApi.HelmRepository{ TypeMeta: metav1.TypeMeta{ Kind: sourceApi.HelmRepositoryKind, APIVersion: sourceApi.GroupVersion.String(), }, ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: constants.FluxSystem, Labels: map[string]string{ constants.Tenant: projectID, }, }, Spec: sourceApi.HelmRepositorySpec{ Interval: metav1.Duration{ Duration: 2 * time.Minute, }, Timeout: &metav1.Duration{ Duration: 60 * time.Second, }, URL: url, SecretRef: secretRef, }, } helmRepositoryBase64, err := mapper.ToBase64StringFromHelmRepository(helmRepo) if err != nil { return err } err = s.sendChariotMessage(ctx, projectID, nil, chariotClientApi.Create, helmRepositoryBase64) if err != nil { return err } return nil } func (s *helmService) GetHelmReleasesStatus(ctx context.Context, cluster *model.Cluster) ([]*model.HelmReleaseStatus, error) { res, err := s.BQClient.GetKubeResource(ctx, cluster.ProjectID, cluster, mapper.GetHelmReleases()) if err != nil { return nil, err } helmReleases, err := ConvertToHelmReleases(ctx, res) if err != nil { return nil, err } return mapper.ToHelmReleasesStatusModels(helmReleases), nil } func ConvertToHelmReleases(ctx context.Context, res []string) ([]helmApi.HelmRelease, error) { var helmReleases []helmApi.HelmRelease for i := range res { helmRelease, err := convertToHelmRelease(ctx, res[i]) if err != nil { return nil, err } helmReleases = append(helmReleases, *helmRelease) } return helmReleases, nil } func convertToHelmRelease(ctx context.Context, res string) (*helmApi.HelmRelease, error) { helmRelease := &helmApi.HelmRelease{} err := json.Unmarshal([]byte(res), helmRelease) if err != nil { log.Ctx(ctx).Err(err).Msg("Unable to unmarshal helm release resource") } return helmRelease, err } func (s *helmService) GetHelmWorkloadConfigmaps(ctx context.Context, helmEdgeID string) ([]*model.HelmWorkloadConfigmaps, error) { configmaps := make([]*model.HelmWorkloadConfigmaps, 0) rows, err := s.SQLDB.QueryContext(ctx, sqlquery.GetHelmWorkloadConfigmaps, helmEdgeID) if err != nil { return configmaps, err } for rows.Next() { configmap := &model.HelmWorkloadConfigmaps{ ClusterEdgeID: "", // empty for backwards compatibility } if err := rows.Scan(&configmap.HelmWorkloadConfigmapEdgeID, &configmap.HelmEdgeID, &configmap.Namespace, &configmap.ConfigMap, &configmap.CreatedAt, &configmap.UpdatedAt); err != nil { return configmaps, err } configmaps = append(configmaps, configmap) } return configmaps, nil } func (s *helmService) ValidateHelmConfig(config string, schema string) (bool, error) { configValues, err := utils.YAMLToJSON(&config) if err != nil { return false, err } if configValues == nil { return true, nil } rs := &jsonschema.Schema{} if err = json.Unmarshal([]byte(schema), rs); err != nil { return false, apierror.New("Invalid json schema in helm chart.") } errs, err := rs.ValidateBytes(context.Background(), configValues) if err != nil { return false, apierror.New(apierror.HelmChartError).AddGenericErrorExtension(apierror.DetailedHelmError, err) } if len(errs) > 0 { return false, utils.GetSchemaError(errs) } return true, nil } func (s *helmService) CreateHelmReleaseSQL(ctx context.Context, payload model.HelmReleasePayload, cluster *model.Cluster) error { if utils.IsNullOrEmpty(payload.BannerEdgeID) && cluster == nil { return apierror.New("please provide bannerEdgeId or clusterEdgeId") } transaction, err := s.SQLDB.BeginTx(ctx, nil) if err != nil { return sqlerr.Wrap(err) } u := middleware.ForContext(ctx) installationType := model.WorkloadInstallationTypeServerPreferred if payload.InstallationType != nil { installationType = *payload.InstallationType } workloadBannerID := utils.CheckString(payload.BannerEdgeID) if cluster != nil { if payload.HelmChart == constants.UpgradeChartName && payload.Version != "0.0.1" { if err := s.validateEdgeOSUpgrade(ctx, payload, cluster.FleetVersion); err != nil { return err } } workloadBannerID = cluster.BannerEdgeID } var helmWorkloadID string result := transaction.QueryRowContext(ctx, sqlquery.CreateHelmWorkload, workloadBannerID, payload.Name, payload.Namespace, payload.HelmChart, payload.HelmRepository, payload.Version, payload.ConfigValues, u.Username, installationType, payload.Secret) err = result.Scan(&helmWorkloadID) if err != nil { if rollbackErr := transaction.Rollback(); rollbackErr != nil { return sqlerr.Wrap(rollbackErr) } return sqlerr.Wrap(err) } if cluster != nil { _, err := transaction.ExecContext(ctx, sqlquery.CreateWorkloadClusterMapper, helmWorkloadID, cluster.ClusterEdgeID) if err != nil { if rollbackErr := transaction.Rollback(); rollbackErr != nil { return sqlerr.Wrap(rollbackErr) } return sqlerr.Wrap(err) } } for _, secret := range payload.Secrets { _, err = transaction.ExecContext(ctx, sqlquery.CreateHelmSecret, helmWorkloadID, secret) if err != nil { if rollbackErr := transaction.Rollback(); rollbackErr != nil { return sqlerr.Wrap(rollbackErr) } return sqlerr.Wrap(err) } } if installationType == model.WorkloadInstallationTypeAdvanced { err = s.AddTerminalLabels(ctx, transaction, helmWorkloadID, payload) if err != nil { if rollbackErr := transaction.Rollback(); rollbackErr != nil { return sqlerr.Wrap(rollbackErr) } return sqlerr.Wrap(err) } } for _, configmap := range payload.InjectConfigmaps { _, err = transaction.ExecContext(ctx, sqlquery.CreateHelmConfigmaps, helmWorkloadID, payload.Namespace, configmap.String()) if err != nil { if rollbackErr := transaction.Rollback(); rollbackErr != nil { return sqlerr.Wrap(rollbackErr) } return sqlerr.Wrap(err) } } if err = transaction.Commit(); err != nil { return sqlerr.Wrap(err) } return nil } func (s *helmService) AddTerminalLabels(ctx context.Context, tx *sql.Tx, helmWorkloadID string, payload model.HelmReleasePayload) error { for _, labelID := range payload.Labels { _, err := tx.ExecContext(ctx, sqlquery.CreateHelmWorkloadLabels, helmWorkloadID, labelID) if err != nil { return err } } return nil } func (s *helmService) UpdateHelmReleaseSQL(ctx context.Context, helmEdgeID string, version, configValues *string, injectConfigmaps []model.InjectableConfigmaps) error { u := middleware.ForContext(ctx) if version == nil || configValues == nil { var currVersion, currConfigValues *string row := s.SQLDB.QueryRowContext(ctx, sqlquery.GetHelmWorkloadInfoByHelmEdgeID, helmEdgeID) if err := row.Scan(&currVersion, &currConfigValues); err != nil { return sqlerr.Wrap(err) } if version == nil { version = currVersion } if configValues == nil { configValues = currConfigValues } } var ns string result := s.SQLDB.QueryRowContext(ctx, sqlquery.UpdateHelmWorkload, version, configValues, u.Username, helmEdgeID) if err := result.Scan(&ns); err != nil { return sqlerr.Wrap(err) } if len(injectConfigmaps) == 0 { _, err := s.SQLDB.ExecContext(ctx, sqlquery.DeleteHelmConfigmaps, helmEdgeID) if err != nil { return sqlerr.Wrap(err) } return nil } cfgs, err := s.GetHelmWorkloadConfigmaps(ctx, helmEdgeID) if err != nil { return sqlerr.Wrap(err) } dbconfigmaps := make(map[string]bool) for _, cfg := range cfgs { dbconfigmaps[cfg.ConfigMap] = true } tx, err := s.SQLDB.BeginTx(ctx, &sql.TxOptions{}) if err != nil { return sqlerr.Wrap(err) } //1: handling case where a configmap is deleted. for cfg := range dbconfigmaps { found := true for _, injectedConfigmap := range injectConfigmaps { if cfg == injectedConfigmap.String() { found = true break } found = false } if !found { _, err = tx.ExecContext(ctx, sqlquery.DeleteHelmConfigmap, helmEdgeID, cfg) if err != nil { if err := tx.Rollback(); err != nil { return err } } } } //2: handling case where a new configmap is added. for _, injectedConfigmap := range injectConfigmaps { if _, exists := dbconfigmaps[injectedConfigmap.String()]; !exists { _, err := tx.ExecContext(ctx, sqlquery.CreateHelmConfigmaps, helmEdgeID, ns, injectedConfigmap.String()) if err != nil { if err := tx.Rollback(); err != nil { return err } } } } if err := tx.Commit(); err != nil { return sqlerr.Wrap(err) } return nil } func (s *helmService) GetHelmCharts(ctx context.Context, secretName, projectID string) ([]*model.HelmChart, error) { username, password, repoURL, err := s.GetHelmRepoConnectionInfo(ctx, secretName, projectID) if err != nil { return nil, err } index, err := s.GetHelmRepositoryIndex(repoURL, username, password) if err != nil { return nil, err } return mapper.ToHelmChartsFromIndex(index), nil } // TODO(pa250194): bottleneck here. Implement a memory cache // Can be extended to be a redis cache in future. func (s *helmService) GetHelmChartVersion(ctx context.Context, name, secretName, projectID string) (*model.HelmChartResponse, error) { username, password, repoURL, err := s.GetHelmRepoConnectionInfo(ctx, secretName, projectID) if err != nil { return nil, err } index, err := s.GetHelmRepositoryIndex(repoURL, username, password) if err != nil { return nil, err } chartVersions := index.Entries[name] versions := []*string{} for _, cv := range chartVersions { versions = append(versions, &cv.Version) } return mapper.ToHelmChartResponse(name, versions), nil } func parseRepoURLYaml(response []byte) (*types.HelmManifestResponse, error) { parsedYaml := &types.HelmManifestResponse{} if err := yaml.Unmarshal(response, parsedYaml); err != nil { return nil, err } return parsedYaml, nil } func (s *helmService) SoftDeleteHelmReleaseSQL(ctx context.Context, helmEdgeID, _, _ *string) error { if utils.IsNullOrEmpty(helmEdgeID) { return apierror.New("please provide a helmEdgeId") } workLabelInUse, err := s.workloadLabelsInUseByCluster(ctx, helmEdgeID) if err != nil { return sqlerr.Wrap(err) } if workLabelInUse { return apierror.New("error deleting workload, being used by a cluster using labels") } result, err := s.SQLDB.ExecContext(ctx, sqlquery.SoftDeleteHelmWorkload, helmEdgeID) if err != nil { return sqlerr.Wrap(err) } affectedRows, err := result.RowsAffected() if err != nil { return sqlerr.Wrap(err) } if affectedRows == 0 { log.Ctx(ctx).Err(err).Msg(fmt.Sprintf("Error Soft delete HelmRelease, helm_edge_id %s not found", *helmEdgeID)) return apierror.New("error deleting workload, not found") } return nil } func (s *helmService) DeleteHelmReleaseSQL(ctx context.Context, clusterEdgeID string) ([]string, error) { rows, err := s.SQLDB.QueryContext(ctx, sqlquery.DeleteHelmWorkloadsByClusterEdgeID, clusterEdgeID) if err != nil { return nil, sqlerr.Wrap(err) } defer rows.Close() var deletedWorkloads []string for rows.Next() { var name string if err = rows.Scan(&name); err != nil { return nil, sqlerr.Wrap(err) } deletedWorkloads = append(deletedWorkloads, name) } return deletedWorkloads, nil } func (s *helmService) workloadLabelsInUseByCluster(ctx context.Context, helmEdgeID *string) (bool, error) { clustersFound := 0 rows, err := s.SQLDB.QueryContext(ctx, sqlquery.GetClusterEdgeIDsUsingHelmWorkloadLabels, helmEdgeID) if err != nil { // if there was an error from sql // dont assume there is no matching error // returning true to block delete return true, sqlerr.Wrap(err) } for rows.Next() { clustersFound++ if clustersFound > 1 { // if rows can be iterated // then there is matching clusters // more than 1 matching cluster then in use = true return true, nil } } return false, nil } func (s *helmService) checkNamespaceToDelete(ctx context.Context, ClusterEdgeID *string, name string) (*model.HelmWorkload, bool, error) { var dummyVal *string helmWorkloads, err := s.GetHelmWorkloadsData(ctx, ClusterEdgeID, dummyVal, true) if err != nil { return nil, false, err } currentHelmWorkload := model.HelmWorkload{} nameSpacesMap := make(map[string]int) for _, helmWorkload := range helmWorkloads { if helmWorkload.Name == name { currentHelmWorkload = *mapper.ToHelmWorkload(helmWorkload) } key := helmWorkload.Namespace count, ok := nameSpacesMap[key] if !ok { nameSpacesMap[key] = 1 } else { nameSpacesMap[key] = count + 1 } } isDeleteNamespace := nameSpacesMap[currentHelmWorkload.Namespace] == 1 return ¤tHelmWorkload, isDeleteNamespace, nil } func (s *helmService) addSecretsToDeleteToChariotMessage(ctx context.Context, helmWorkload *model.HelmWorkload, clusterEdgeID string, message []string) ([]string, error) { var rows *sql.Rows var err error var secretsOnWorkload []string var secretsCounts = make(map[string]int, 0) rows, err = s.SQLDB.QueryContext(ctx, sqlquery.GetNamespacesAndSecretsNamesByClusterEdgeID, &clusterEdgeID, helmWorkload.Namespace) if err != nil { return nil, sqlerr.Wrap(err) } defer rows.Close() for rows.Next() { var helmEdgeID string var workloadName string var workloadNamespace string var secretName string if err = rows.Scan(&helmEdgeID, &workloadName, &workloadNamespace, &secretName); err != nil { return nil, sqlerr.Wrap(err) } if helmEdgeID == helmWorkload.HelmEdgeID { secretsOnWorkload = append(secretsOnWorkload, secretName) } if _, ok := secretsCounts[secretName]; !ok { secretsCounts[secretName] = 1 } else { secretsCounts[secretName]++ } } if err := rows.Err(); err != nil { return nil, sqlerr.Wrap(err) } extSecretsToDelete, err := getExternalSecretsToDelete(ctx, helmWorkload.Namespace, secretsOnWorkload, secretsCounts) if err != nil { return nil, err } message = append(message, extSecretsToDelete...) return message, nil } func getExternalSecretsToDelete(ctx context.Context, namespace string, secretsOnWorkload []string, secretsCounts map[string]int) ([]string, error) { var extSecretsToDelete []string for _, secret := range secretsOnWorkload { if secretsCounts[secret] == 1 { //append the external secret to list to be deleted extSec := &goext.ExternalSecret{TypeMeta: metav1.TypeMeta{ APIVersion: goext.ExtSecretGroupVersionKind.GroupVersion().String(), Kind: goext.ExtSecretGroupVersionKind.Kind, }, ObjectMeta: metav1.ObjectMeta{Name: secret, Namespace: namespace}, } extSecString, err := mapper.ToBase64StringFromExternalSecret(extSec) if err != nil { //handle error log.Ctx(ctx).Err(err).Msg("Unable to get base64 string from external secret") return nil, err } extSecretsToDelete = append(extSecretsToDelete, extSecString) } } return extSecretsToDelete, nil } func (s *helmService) GetDefaultConfigSchema(ctx context.Context, chartName string, secret string, chartVersion string, projectID string) (*model.HelmConfig, error) { username, password, repoURL, err := s.GetHelmRepoConnectionInfo(ctx, secret, projectID) if err != nil { return nil, err } index, err := s.GetHelmRepositoryIndex(repoURL, username, password) if err != nil { return nil, err } selectedChart, err := s.GetHelmChartFromIndex(index, chartName, chartVersion, username, password) if err != nil { return nil, err } sch := string(selectedChart.Schema) vals, err := yaml.Marshal(selectedChart.Values) if err != nil { return nil, err } valsString := string(vals) //get the original values file for readability if available for _, f := range selectedChart.Raw { if f.Name == "values.yaml" { valsString = string(f.Data) break } } config := &model.HelmConfig{ ConfigVals: &valsString, ConfigSchema: &sch, } return config, nil } func (s *helmService) GetHelmChartFromIndex(index *repo.IndexFile, chartName string, chartVersion string, username string, password string) (*chart.Chart, error) { cv, err := index.Get(chartName, chartVersion) if err != nil { return nil, err } response, err := GetContents(cv.URLs[0], username, password, "", true) if err != nil { return nil, err } return loader.LoadArchive(response.RawBody()) } func (s *helmService) GetHelmRepositoryIndex(repoURL string, username string, password string) (*repo.IndexFile, error) { response, err := GetContents(repoURL, username, password, "/index.yaml", false) if err != nil { return nil, err } index := &repo.IndexFile{} err = yaml.Unmarshal(response.Body(), index) return index, err } func (s *helmService) GetHelmRepoConnectionInfo(ctx context.Context, secretName string, projectID string) (string, string, string, error) { secretType := helmRepoSecretType helmSecrets, err := s.GCPService.GetSecrets(ctx, &secretName, nil, &secretType, true, projectID) if err != nil { return "", "", "", err } if len(helmSecrets) != 1 { return "", "", "", errors.New("helm repository secret not found") } _, username, password, repoURL := mapper.GetHelmSecretData(helmSecrets[0]) return username, password, repoURL, nil } func (s *helmService) GetHelmRepositoryInfo(ctx context.Context, params model.HelmConfigSchemaParams, projectID string) (*model.HelmRepositoryInfo, error) { username, password, repoURL, err := s.GetHelmRepoConnectionInfo(ctx, params.SecretName, projectID) if err != nil { return nil, err } index, err := s.GetHelmRepositoryIndex(repoURL, username, password) if err != nil { return nil, err } selectedChart, err := s.GetHelmChartFromIndex(index, params.ChartName, params.ChartVersion, username, password) if err != nil { return nil, err } for _, f := range selectedChart.Files { if strings.ToLower(f.Name) == "readme.md" { readmeContents := string(f.Data) return &model.HelmRepositoryInfo{ Readme: &readmeContents, Metadata: nil, }, nil } } return nil, errors.New("readme not found") } func GetContents(url string, username string, password string, file string, doNotParse bool) (*resty.Response, error) { client := resty.New().R().SetDoNotParseResponse(doNotParse) if username != "" && password != "" { client.SetBasicAuth(username, password) } resp, err := client.Get(url + file) return resp, bsl.ValidateResponse(client, resp, err) } func (s *helmService) sendChariotMessage(ctx context.Context, projectID string, cluster *model.Cluster, operation chariotClientApi.Operation, object ...string) error { chariotMessage := chariotClientApi. NewChariotMessage(). SetBanner(projectID). SetOperation(operation). SetOwner(ComponentOwner). AddObject(object...) if cluster != nil { chariotMessage = chariotMessage.SetCluster(cluster.ClusterEdgeID) } if err := s.ChariotService.InvokeChariotPubsub(ctx, chariotMessage, nil); err != nil { return fmt.Errorf("error calling chariot v2: %w", err) } return nil } func (s *helmService) GetAttachedHelmSecrets(ctx context.Context, helmEdgeID *string) ([]*model.HelmSecrets, error) { var rows *sql.Rows var err error rows, err = s.SQLDB.QueryContext(ctx, sqlquery.GetAttachedHelmSecretByHelmEdgeID, helmEdgeID) if err != nil { return nil, sqlerr.Wrap(err) } helmSecrets := []*model.HelmSecrets{} defer rows.Close() for rows.Next() { var helmSecret model.HelmSecrets if err = rows.Scan(&helmSecret.SecretEdgeID, &helmSecret.Name, &helmSecret.CreatedAt, &helmSecret.UpdatedAt); err != nil { return nil, sqlerr.Wrap(err) } helmSecrets = append(helmSecrets, &helmSecret) } if err := rows.Err(); err != nil { return nil, sqlerr.Wrap(err) } return helmSecrets, nil } func (s *helmService) GetHelmWorkloadsData(ctx context.Context, clusterEdgeID *string, bannerEdgeID *string, includeDeleted bool) ([]types.HelmWorkloadQuery, error) { var rows *sql.Rows var err error if !utils.IsNullOrEmpty(clusterEdgeID) { clusterWorkloadsQuery := sqlquery.GetNonDeletedHelmWorkloadsByClusterEdgeID if includeDeleted { clusterWorkloadsQuery = sqlquery.GetHelmWorkloadsByClusterEdgeID } rows, err = s.SQLDB.QueryContext(ctx, clusterWorkloadsQuery, clusterEdgeID) } else { bannerWorkloadsQuery := sqlquery.GetNonDeletedHelmWorkloadsByBannerEdgeID if includeDeleted { bannerWorkloadsQuery = sqlquery.GetHelmWorkloadsByBannerEdgeID } rows, err = s.SQLDB.QueryContext(ctx, bannerWorkloadsQuery, bannerEdgeID) } if err != nil { return nil, sqlerr.Wrap(err) } helmWorkloadsData := make([]types.HelmWorkloadQuery, 0) for rows.Next() { helmWorkloadData := types.HelmWorkloadQuery{} var clusterEdgeID sql.NullString err = rows.Scan(&clusterEdgeID, &helmWorkloadData.BannerEdgeID, &helmWorkloadData.HelmEdgeID, &helmWorkloadData.Name, &helmWorkloadData.Namespace, &helmWorkloadData.HelmChart, &helmWorkloadData.HelmRepository, &helmWorkloadData.HelmChartVersion, &helmWorkloadData.HelmConfig, &helmWorkloadData.InstalledBy, &helmWorkloadData.WorkloadInstallationType, &helmWorkloadData.CreatedAt, &helmWorkloadData.UpdatedAt, &helmWorkloadData.HelmRepoSecret, &helmWorkloadData.ProjectID, &helmWorkloadData.Deleted) if err != nil { return nil, sqlerr.Wrap(err) } if clusterEdgeID.Valid { helmWorkloadData.ClusterEdgeID = clusterEdgeID.String } helmWorkloadsData = append(helmWorkloadsData, helmWorkloadData) } if err := rows.Err(); err != nil { return nil, sqlerr.Wrap(err) } return helmWorkloadsData, nil } func (s *helmService) getHelmWorkloadData(ctx context.Context, helmEdgeID, clusterEdgeID string) (*types.HelmWorkloadQuery, error) { helmWorkloadData := types.HelmWorkloadQuery{} row := s.SQLDB.QueryRowContext(ctx, sqlquery.GetHelmWorkload, helmEdgeID, clusterEdgeID) if err := row.Scan(&helmWorkloadData.ClusterEdgeID, &helmWorkloadData.BannerEdgeID, &helmWorkloadData.HelmEdgeID, &helmWorkloadData.Name, &helmWorkloadData.Namespace, &helmWorkloadData.HelmChart, &helmWorkloadData.HelmRepository, &helmWorkloadData.HelmChartVersion, &helmWorkloadData.HelmConfig, &helmWorkloadData.InstalledBy, &helmWorkloadData.WorkloadInstallationType, &helmWorkloadData.CreatedAt, &helmWorkloadData.UpdatedAt, &helmWorkloadData.HelmRepoSecret, &helmWorkloadData.ProjectID, &helmWorkloadData.Deleted); err != nil { return nil, sqlerr.Wrap(err) } return &helmWorkloadData, nil } func (s *helmService) GetHelmWorkloads(ctx context.Context, clusterEdgeID *string, bannerEdgeID *string, all bool) ([]*model.HelmWorkload, error) { helmWorkloads := make([]*model.HelmWorkload, 0) var err error if utils.IsNullOrEmpty(clusterEdgeID) && utils.IsNullOrEmpty(bannerEdgeID) { return nil, apierror.New("please provide clusterEdgeId or bannerEdgeId") } queriedHelmWorkloads, err := s.GetHelmWorkloadsData(ctx, clusterEdgeID, bannerEdgeID, all) if err != nil { return nil, err } helmChartVersionMap := make(map[string]*model.HelmChartResponse) mu := sync.Mutex{} wg := sync.WaitGroup{} errorChan := make(chan error, len(queriedHelmWorkloads)) semaphore := make(chan struct{}, maxHelmWorkloadConcurrency) for i := range queriedHelmWorkloads { semaphore <- struct{}{} wg.Add(1) go func(gctx context.Context) { defer wg.Done() defer func() { <-semaphore }() helmWorkload, err := s.getHelmWorkloadFromQueryData(gctx, queriedHelmWorkloads[i], helmChartVersionMap) if err != nil { errorChan <- err return } mu.Lock() helmWorkloads = append(helmWorkloads, helmWorkload) mu.Unlock() }(ctx) } wg.Wait() close(errorChan) var aggregatedErr error for err := range errorChan { if err != nil { aggregatedErr = errors.Join(aggregatedErr, err) } } return helmWorkloads, aggregatedErr } func (s *helmService) GetHelmWorkloadsByName(ctx context.Context, clusterEdgeID, workloadName string) ([]*model.HelmWorkload, error) { var ( rows *sql.Rows err error ) rows, err = s.SQLDB.QueryContext(ctx, sqlquery.GetWorkloadsByNameAndID, clusterEdgeID, workloadName) if err != nil { return nil, sqlerr.Wrap(err) } helmWorkloadsList := make([]*model.HelmWorkload, 0) for rows.Next() { var helmWorkloadData model.HelmWorkload var clusterEdgeID sql.NullString err = rows.Scan(&helmWorkloadData.Name, &helmWorkloadData.HelmEdgeID, &clusterEdgeID) if err != nil { return nil, sqlerr.Wrap(err) } if clusterEdgeID.Valid { helmWorkloadData.ClusterEdgeID = clusterEdgeID.String } helmWorkloadsList = append(helmWorkloadsList, &helmWorkloadData) } if err := rows.Err(); err != nil { return nil, sqlerr.Wrap(err) } return helmWorkloadsList, nil } func (s *helmService) GetHelmStatus(ctx context.Context, clusterEdgeID, workloadName, workloadNamespace, helmChart, helmRepo, createdAt, updatedAt string, deleted *bool) (*model.HelmStatus, error) { if clusterEdgeID == "" { return &model.HelmStatus{Status: status.NotAvailable, Message: status.NotDeployedStatusMessage}, nil } if deleted != nil && *deleted { return &model.HelmStatus{Status: status.Deleting, Message: status.DeletingStatusMessage}, nil } pods, err := s.getHelmPodsStatus(ctx, workloadNamespace, clusterEdgeID) if err != nil { return nil, err } createdTime, err := time.Parse(time.RFC3339, createdAt) if err != nil { return nil, err } updatedTime, err := time.Parse(time.RFC3339, updatedAt) if err != nil { return nil, err } watchedTime, err := s.getHelmWorkloadWatchedTimeFromSQL(ctx, workloadName, clusterEdgeID) // cases when there's no watched data for the helm workload if err != nil { if !errors.Is(err, sql.ErrNoRows) { return nil, err } if createdTime.Equal(updatedTime) { return &model.HelmStatus{Status: status.Installing, Message: status.InstallingStatusMessage, Pods: pods}, nil } return &model.HelmStatus{Status: status.Updating, Message: status.UpdatingStatusMessage, Pods: pods}, nil } // check if the workload is updating if watchedTime.Before(updatedTime) { return &model.HelmStatus{Status: status.Updating, Message: status.UpdatingStatusMessage, Pods: pods}, nil } helmStatus, reasonHelmRelease, err := s.getHelmStatusValueFromSQL(ctx, "HelmRelease", workloadName, clusterEdgeID) if err != nil { return nil, err } if helmStatus == status.IsReady { return &model.HelmStatus{Status: status.Ready, Message: status.WorkloadReadyStatusMessage, Pods: pods}, nil } message := fmt.Sprintf("HelmRelease: %s", reasonHelmRelease) // check if the workload is installing if helmStatus == unknownStatus && createdTime.Equal(updatedTime) { return &model.HelmStatus{Status: status.Installing, Message: status.InstallingStatusMessage, Pods: pods}, nil } // If the helm workload status is not ready, get the helm chart/helm repo statuses _, reasonHelmChart, err := s.getHelmStatusValueFromSQL(ctx, "HelmChart", helmChart, clusterEdgeID) if err != nil { return nil, err } message = fmt.Sprintf("%s, helmChart: %s", message, reasonHelmChart) _, reasonHelmRepo, err := s.getHelmStatusValueFromSQL(ctx, "HelmRepo", helmRepo, clusterEdgeID) if err != nil { return nil, err } message = fmt.Sprintf("%s, helmRepo: %s", message, reasonHelmRepo) return &model.HelmStatus{Status: status.Error, Message: message, Pods: pods}, nil } func (s *helmService) getHelmWorkloadWatchedTimeFromSQL(ctx context.Context, name, clusterEdgeID string) (time.Time, error) { var watchedTime time.Time row := s.SQLDB.QueryRowContext(ctx, sqlquery.GetHelmWorkloadWatchedTime, name, clusterEdgeID) err := row.Scan(&watchedTime) return watchedTime, err } func (s *helmService) getHelmStatusValueFromSQL(ctx context.Context, kind, name, clusterEdgeID string) (string, string, error) { status := unknownStatus var reason string var missing bool row := s.SQLDB.QueryRowContext(ctx, sqlquery.GetHelmConditionStatus, kind, name, clusterEdgeID) if err := row.Scan(&status, &missing); err != nil { if errors.Is(err, sql.ErrNoRows) { return status, "No Status Found", nil } return "", "", err } status = mapper.ToTrimedValue(status) if missing { return unknownStatus, fmt.Sprintf("%s %s value missing", kind, name), nil } row = s.SQLDB.QueryRowContext(ctx, sqlquery.GetHelmConditionReason, kind, name, clusterEdgeID) if err := row.Scan(&reason, &missing); err != nil { if errors.Is(err, sql.ErrNoRows) { return status, "No Message Found", nil } return "", "", err } return status, mapper.ToTrimedValue(reason), nil } func (s *helmService) getHelmPodsStatus(ctx context.Context, namespace, clusterEdgeID string) ([]*model.Pods, error) { var ( unknownstatus = unknownStatus reason, name string missing bool podsStatusMap = make(map[string]string) podsReasonMap = make(map[string]string) pods []*model.Pods ) rows, err := s.SQLDB.QueryContext(ctx, sqlquery.GetHelmWorkloadPodsStatus, clusterEdgeID, namespace) if err != nil { return pods, sqlerr.Wrap(err) } for rows.Next() { if err := rows.Scan(&name, &unknownstatus, &missing); err != nil { if errors.Is(err, sql.ErrNoRows) { podsStatusMap[name] = "No Status Found" } return pods, sqlerr.Wrap(err) } if missing { podsStatusMap[name] = fmt.Sprintf("Pod %s status value missing", name) } else { podsStatusMap[name] = mapper.ToTrimedValue(unknownstatus) } } rows, err = s.SQLDB.QueryContext(ctx, sqlquery.GetHelmWorkloadPodsReason, clusterEdgeID, namespace) if err != nil { return pods, sqlerr.Wrap(err) } for rows.Next() { if err := rows.Scan(&name, &reason, &missing); err != nil { if errors.Is(err, sql.ErrNoRows) { podsReasonMap[name] = "No Message Found" } return pods, sqlerr.Wrap(err) } stats, statusExists := podsStatusMap[name] if missing && statusExists && stats == status.IsReady { podsReasonMap[name] = "Pod is ready" } else if missing { podsReasonMap[name] = fmt.Sprintf("Pod %s message value missing", name) } else { podsReasonMap[name] = mapper.ToTrimedValue(reason) } } for k, v := range podsStatusMap { pods = append(pods, &model.Pods{Name: k, Status: mapper.ToPodStatus(v), Message: podsReasonMap[k]}) } return pods, nil } func (s *helmService) GetHelmWorkload(ctx context.Context, helmEdgeID, clusterEdgeID string) (*model.HelmWorkload, error) { queriedHelmWorkload, err := s.getHelmWorkloadData(ctx, helmEdgeID, clusterEdgeID) if err != nil { return nil, err } helmChartVersionMap := make(map[string]*model.HelmChartResponse) return s.getHelmWorkloadFromQueryData(ctx, *queriedHelmWorkload, helmChartVersionMap) } func (s *helmService) DeleteHelmSecrets(ctx context.Context, helmEdgeID string, deleteSecrets []string) (bool, error) { for _, secret := range deleteSecrets { _, err := s.SQLDB.ExecContext(ctx, sqlquery.DeleteHelmSecret, helmEdgeID, secret) if err != nil { return false, sqlerr.Wrap(err) } } return true, nil } func (s *helmService) AddHelmSecrets(ctx context.Context, helmEdgeID string, secretName []string) (bool, error) { for _, secret := range secretName { _, err := s.SQLDB.ExecContext(ctx, sqlquery.CreateHelmSecret, helmEdgeID, secret) if err != nil { return false, sqlerr.Wrap(err) } } return true, nil } func (s *helmService) GetBannerByHelmEdgeID(ctx context.Context, helmEdgeID string) (*model.Banner, error) { row := s.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerByHelmEdgeID, helmEdgeID) banner := &model.Banner{} if err := row.Scan(&banner.ProjectID, &banner.BannerEdgeID); err != nil { return nil, sqlerr.Wrap(err) } return banner, nil } func (s *helmService) GetSecretsByHelmEdgeID(ctx context.Context, helmEdgeID string) ([]string, error) { rows, err := s.SQLDB.QueryContext(ctx, sqlquery.GetHelmWorkloadSecretNames, helmEdgeID) if err != nil { return nil, sqlerr.Wrap(err) } currentSecretNames := []string{} var secret string for rows.Next() { err = rows.Scan(&secret) if err != nil { return nil, sqlerr.Wrap(err) } currentSecretNames = append(currentSecretNames, secret) } if err := rows.Err(); err != nil { return nil, sqlerr.Wrap(err) } return currentSecretNames, nil } func (s *helmService) GetLabelsByHelmEdgeID(ctx context.Context, helmEdgeID string) ([]*model.Label, error) { rows, err := s.SQLDB.QueryContext(ctx, sqlquery.SelectEdgeLabelsForHelmEdgeID, helmEdgeID) if err != nil { return nil, sqlerr.Wrap(err) } var labelList []*model.Label defer rows.Close() for rows.Next() { var label model.Label if err = rows.Scan(&label.LabelEdgeID, &label.Key, &label.Color, &label.Visible, &label.Editable, &label.BannerEdgeID, &label.Unique, &label.Description, &label.Type); err != nil { return nil, sqlerr.Wrap(err) } labelList = append(labelList, &label) } if err := rows.Err(); err != nil { return nil, sqlerr.Wrap(err) } return labelList, nil } func (s *helmService) AddWorkloadLabel(ctx context.Context, helmEdgeID string, newLabel string) (bool, error) { _, errAdd := s.SQLDB.ExecContext(ctx, sqlquery.CreateHelmWorkloadLabels, helmEdgeID, newLabel) if errAdd != nil { return false, sqlerr.Wrap(errAdd) } return true, nil } func (s *helmService) AddWorkloadLabels(ctx context.Context, helmEdgeID string, newLabels []string) (bool, error) { for _, newLabel := range newLabels { _, err := s.AddWorkloadLabel(ctx, helmEdgeID, newLabel) if err != nil { return false, sqlerr.Wrap(err) } } return true, nil } func (s *helmService) DeleteWorkloadLabel(ctx context.Context, helmEdgeID, deleteLabel string) (bool, error) { _, errDelete := s.SQLDB.ExecContext(ctx, sqlquery.DeleteLabelFromHelmWorkload, deleteLabel, helmEdgeID) if errDelete != nil { return false, sqlerr.Wrap(errDelete) } return true, nil } func (s *helmService) DeleteWorkloadLabels(ctx context.Context, helmEdgeID string) (bool, error) { _, errDelete := s.SQLDB.ExecContext(ctx, sqlquery.DeleteLabelEdgeIDWithHelmEdgeID, helmEdgeID) if errDelete != nil { return false, sqlerr.Wrap(errDelete) } return true, nil } func (s *helmService) DeleteWorkloadLabelByLabelEdgeID(ctx context.Context, deleteLabel string) (bool, error) { _, errDelete := s.SQLDB.ExecContext(ctx, sqlquery.DeleteLabelEdgeIDWithLabelEdgeID, deleteLabel) if errDelete != nil { return false, fmt.Errorf("failed to delete label from workload") } return true, nil } func (s *helmService) DeleteHelmReleaseByHelmEdgeID(ctx context.Context, helmEdgeID string) error { _, err := s.SQLDB.ExecContext(ctx, sqlquery.DeleteHelmWorkload, helmEdgeID) if err != nil { return sqlerr.Wrap(err) } return nil } func (s *helmService) GetSoftDeletedHelmWorkloads(ctx context.Context, clusterEdgeID string) ([]*model.HelmWorkload, error) { rows, err := s.SQLDB.QueryContext(ctx, sqlquery.GetSoftDeletedHelmWorkloadsByClusterEdgeID, clusterEdgeID) if err != nil { return nil, sqlerr.Wrap(err) } defer rows.Close() var workloads = []*model.HelmWorkload{} for rows.Next() { var workload = &model.HelmWorkload{} err = rows.Scan(&workload.HelmEdgeID, &workload.Name) if err != nil { return nil, sqlerr.Wrap(err) } workloads = append(workloads, workload) } return workloads, nil } func (s *helmService) getHelmWorkloadFromQueryData(ctx context.Context, queriedHelmWorkload types.HelmWorkloadQuery, helmChartVersionMap map[string]*model.HelmChartResponse) (*model.HelmWorkload, error) { secrets, err := s.GetAttachedHelmSecrets(ctx, &queriedHelmWorkload.HelmEdgeID) if err != nil { return nil, err } key := queriedHelmWorkload.HelmChart + queriedHelmWorkload.HelmRepoSecret helmChartVersion, ok := helmChartVersionMap[key] if !ok { chartVersions, err := s.GetHelmChartVersion(ctx, queriedHelmWorkload.HelmChart, queriedHelmWorkload.HelmRepoSecret, queriedHelmWorkload.ProjectID) if err != nil { return nil, err } helmChartVersion = chartVersions helmChartVersionMap[key] = chartVersions } configMaps, err := s.GetHelmWorkloadConfigmaps(ctx, queriedHelmWorkload.HelmEdgeID) if err != nil { return nil, err } labels, err := s.GetLabelsByHelmEdgeID(ctx, queriedHelmWorkload.HelmEdgeID) if err != nil { return nil, err } helmWorkload := mapper.UpdateHelmWorkload(mapper.ToHelmWorkload(queriedHelmWorkload), helmChartVersion.Versions, secrets, configMaps, labels) return helmWorkload, nil } func (s *helmService) validateEdgeOSUpgrade(ctx context.Context, payload model.HelmReleasePayload, clusterInfraVersion string) error { upgradeVersion := payload.Version if clusterInfraVersion == "latest" || clusterInfraVersion == "" { clusterInfraVersion = version.New().SemVer } infraBaseVersion, edgeOSMinorVersion := utils.SetVersionToBaseAndMinorVersion(&clusterInfraVersion, &upgradeVersion) compatible, err := s.CompatibilityService.IsCompatible(ctx, model.ArtifactVersion{Name: fleet.Store, Version: infraBaseVersion}, model.ArtifactVersion{Name: constants.EdgeOSArtifact, Version: edgeOSMinorVersion}) if err != nil { return err } if !compatible { return fmt.Errorf("the requested upgrade ien version %s is not compatible with the current store infra version %s", upgradeVersion, clusterInfraVersion) } return nil } func NewHelmService(cfg *types.Config, chariotService ChariotService, gcpService GCPService, sqlDB *sql.DB, bqClient clients.BQClient, compatibilityService CompatibilityService) *helmService { //nolint stupid return &helmService{ Config: cfg, ChariotService: chariotService, GCPService: gcpService, SQLDB: sqlDB, BQClient: bqClient, CompatibilityService: compatibilityService, } }