...

Source file src/edge-infra.dev/pkg/edge/api/services/helm_service.go

Documentation: edge-infra.dev/pkg/edge/api/services

     1  package services
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"encoding/base64"
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  
    14  	goext "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
    15  	helmApi "github.com/fluxcd/helm-controller/api/v2beta1"
    16  	fluxmeta "github.com/fluxcd/pkg/apis/meta"
    17  	sourceApi "github.com/fluxcd/source-controller/api/v1beta2"
    18  	"github.com/go-resty/resty/v2"
    19  	"github.com/qri-io/jsonschema"
    20  	"github.com/rs/zerolog/log"
    21  	"helm.sh/helm/v3/pkg/chart"
    22  	"helm.sh/helm/v3/pkg/chart/loader"
    23  	"helm.sh/helm/v3/pkg/repo"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"sigs.k8s.io/yaml"
    26  
    27  	"edge-infra.dev/pkg/edge/api/apierror"
    28  	sqlerr "edge-infra.dev/pkg/edge/api/apierror/sql"
    29  	"edge-infra.dev/pkg/edge/api/clients"
    30  	"edge-infra.dev/pkg/edge/api/graph/mapper"
    31  	"edge-infra.dev/pkg/edge/api/graph/model"
    32  	"edge-infra.dev/pkg/edge/api/middleware"
    33  	sqlquery "edge-infra.dev/pkg/edge/api/sql"
    34  	"edge-infra.dev/pkg/edge/api/status"
    35  	"edge-infra.dev/pkg/edge/api/types"
    36  	"edge-infra.dev/pkg/edge/api/utils"
    37  	"edge-infra.dev/pkg/edge/bsl"
    38  	chariotClientApi "edge-infra.dev/pkg/edge/chariot/client"
    39  	"edge-infra.dev/pkg/edge/constants"
    40  	"edge-infra.dev/pkg/edge/constants/api/fleet"
    41  	"edge-infra.dev/pkg/edge/externalsecrets"
    42  	"edge-infra.dev/pkg/lib/runtime/version"
    43  )
    44  
    45  const (
    46  	helmRepoSecretType                = "helm-repository"
    47  	helmReleaseKind                   = "HelmRelease"
    48  	chariotPath                       = "chariot/"
    49  	EdgeInjectableConfigmapAnnotation = "injector.edge.ncr.com/configmap"
    50  	unknownStatus                     = "Unknown"
    51  	maxHelmWorkloadConcurrency        = 20
    52  )
    53  
    54  var (
    55  	ErrorDeleteHelmRepo = errors.New("helm repository cannot be deleted it has associated helm releases")
    56  )
    57  
    58  //go:generate mockgen -destination=../mocks/mock_helm_service.go -package=mocks edge-infra.dev/pkg/edge/api/services HelmService
    59  type HelmService interface {
    60  	UpdateHelmReleaseSQL(ctx context.Context, helmEdgeID string, version, configValues *string, injectConfigmaps []model.InjectableConfigmaps) error
    61  	CreateHelmReleaseSQL(ctx context.Context, payload model.HelmReleasePayload, cluster *model.Cluster) error
    62  	ValidateHelmConfig(config string, schema string) (bool, error)
    63  	GetHelmCharts(ctx context.Context, secretName, projectID string) ([]*model.HelmChart, error)
    64  	GetHelmChartVersion(ctx context.Context, name, secretName, projectID string) (*model.HelmChartResponse, error)
    65  	DeleteHelmReleaseSQL(ctx context.Context, clusterEdgeID string) ([]string, error)
    66  	SoftDeleteHelmReleaseSQL(ctx context.Context, helmEdgeID, name, clusterEdgeID *string) error
    67  	GetDefaultConfigSchema(ctx context.Context, chartName string, secret string, chartVersion string, projectID string) (*model.HelmConfig, error)
    68  	CreateBannerHelmRepository(ctx context.Context, name string, url string, secret *string, projectID string) error
    69  	DeleteHelmRepo(ctx context.Context, name string, projectID string, bannerEdgeID string) error
    70  	GetHelmRepositoryInfo(ctx context.Context, params model.HelmConfigSchemaParams, projectID string) (*model.HelmRepositoryInfo, error)
    71  	GenerateHelmReleaseExternalSecrets(ctx context.Context, projectID string, namespace string, secrets []string) ([]*externalsecrets.ExternalSecret, error)
    72  	SendExternalSecretToChariot(ctx context.Context, projectID, id string, externalSecrets []*externalsecrets.ExternalSecret) error
    73  	GetHelmReleasesStatus(ctx context.Context, cluster *model.Cluster) ([]*model.HelmReleaseStatus, error)
    74  	GetHelmWorkloadsByName(ctx context.Context, clusterEdgeID string, workloadName string) ([]*model.HelmWorkload, error)
    75  	GetHelmWorkloads(ctx context.Context, clusterEdgeID *string, bannerEdgeID *string, all bool) ([]*model.HelmWorkload, error)
    76  	GetHelmRepositoryIndex(repoURL string, username string, password string) (*repo.IndexFile, error)
    77  	GetHelmWorkload(ctx context.Context, helmEdgeID, clusterEdgeID string) (*model.HelmWorkload, error)
    78  	GetAttachedHelmSecrets(ctx context.Context, helmEdgeID *string) ([]*model.HelmSecrets, error)
    79  	DeleteHelmSecrets(ctx context.Context, helmEdgeID string, deleteSecrets []string) (bool, error)
    80  	GetSecretsByHelmEdgeID(ctx context.Context, helmEdgeID string) ([]string, error)
    81  	AddHelmSecrets(ctx context.Context, helmEdgeID string, secretName []string) (bool, error)
    82  	GetHelmStatus(ctx context.Context, clusterEdgeID, workloadName, workloadNamespace, helmChart, helmRepo, createdAt, updatedAt string, deleted *bool) (*model.HelmStatus, error)
    83  	GetLabelsByHelmEdgeID(ctx context.Context, helmEdgeID string) ([]*model.Label, error)
    84  	AddWorkloadLabel(ctx context.Context, helmEdgeID, newLabel string) (bool, error)
    85  	AddWorkloadLabels(ctx context.Context, helmEdgeID string, newLabels []string) (bool, error)
    86  	DeleteWorkloadLabel(ctx context.Context, helmEdgeID, deleteLabel string) (bool, error)
    87  	DeleteWorkloadLabels(ctx context.Context, helmEdgeID string) (bool, error)
    88  	DeleteWorkloadLabelByLabelEdgeID(ctx context.Context, deleteLabel string) (bool, error)
    89  	GetHelmWorkloadConfigmaps(ctx context.Context, helmEdgeID string) ([]*model.HelmWorkloadConfigmaps, error)
    90  	GetBannerByHelmEdgeID(ctx context.Context, helmEdgeID string) (*model.Banner, error)
    91  	GetHelmRepoConnectionInfo(ctx context.Context, secretName string, projectID string) (string, string, string, error)
    92  	GetHelmChartFromIndex(index *repo.IndexFile, chartName string, chartVersion string, username string, password string) (*chart.Chart, error)
    93  	GetSoftDeletedHelmWorkloads(ctx context.Context, clusterEdgeID string) ([]*model.HelmWorkload, error)
    94  	DeleteHelmReleaseByHelmEdgeID(ctx context.Context, helmEdgeID string) error
    95  }
    96  
    97  type helmService struct {
    98  	Config               *types.Config
    99  	ChariotService       ChariotService
   100  	GCPService           GCPService
   101  	SQLDB                *sql.DB
   102  	BQClient             clients.BQClient
   103  	CompatibilityService CompatibilityService
   104  }
   105  
   106  func (s *helmService) GenerateHelmReleaseExternalSecrets(ctx context.Context, projectID string, namespace string, secrets []string) ([]*externalsecrets.ExternalSecret, error) {
   107  	externalSecrets := make([]*externalsecrets.ExternalSecret, 0)
   108  	secretManagerRes := make([]*model.SecretManagerResponse, 0)
   109  	for _, name := range secrets {
   110  		nameRef := name //implicit memory aliasing gotcha
   111  		secretResponse, err := s.GCPService.GetSecrets(ctx, &nameRef, nil, nil, true, projectID)
   112  		if err != nil {
   113  			return nil, err
   114  		}
   115  		secretManagerRes = append(secretManagerRes, secretResponse...)
   116  	}
   117  	for _, secret := range secretManagerRes {
   118  		externalSecret := createExternalSecretByType(projectID, namespace, secret)
   119  		if externalSecret == nil {
   120  			return nil, errors.New("secret type not supported")
   121  		}
   122  		externalSecrets = append(externalSecrets, externalSecret)
   123  	}
   124  	return externalSecrets, nil
   125  }
   126  
   127  func (s *helmService) SendExternalSecretToChariot(ctx context.Context, projectID, id string, externalSecrets []*externalsecrets.ExternalSecret) error {
   128  	chariotMessage := chariotClientApi.
   129  		NewChariotMessage().
   130  		SetBanner(projectID).
   131  		SetCluster(id).
   132  		SetOperation(chariotClientApi.Create).
   133  		SetOwner(ComponentOwner)
   134  
   135  	for _, externalSecret := range externalSecrets {
   136  		externalSecretData, err := externalSecret.Build()
   137  		if err != nil {
   138  			return err
   139  		}
   140  		externalSecretByte, err := json.Marshal(externalSecretData)
   141  		if err != nil {
   142  			return err
   143  		}
   144  		externalSecretString := base64.StdEncoding.EncodeToString(externalSecretByte)
   145  		chariotMessage.AddObject(externalSecretString)
   146  	}
   147  	if err := s.ChariotService.InvokeChariotPubsub(ctx, chariotMessage, nil); err != nil {
   148  		err := fmt.Errorf("error calling chariot v2: %w", err)
   149  		log.Ctx(ctx).Err(err).Msg("chariot invocation failed")
   150  		return err
   151  	}
   152  	return nil
   153  }
   154  
   155  func createExternalSecretByType(projectID string, namespace string, secret *model.SecretManagerResponse) *externalsecrets.ExternalSecret {
   156  	externalSecret := externalsecrets.DefaultExternalSecret().
   157  		Name(secret.Name).
   158  		Namespace(namespace).
   159  		Path(chariotPath).
   160  		ProjectID(projectID).
   161  		K8sSecretName(secret.Name)
   162  
   163  	switch *secret.Type {
   164  	case externalsecrets.DockerPullSecretType:
   165  		for _, kv := range secret.Values {
   166  			if kv.Key == "dockerconfigjson" {
   167  				return externalSecret.DockerConfig(secret.Name, kv.Key)
   168  			}
   169  		}
   170  	default:
   171  		for _, val := range secret.Values {
   172  			externalSecret.MapSecretFieldToK8sSecretKey(secret.Name, val.Key, val.Key)
   173  		}
   174  	}
   175  	return externalSecret
   176  }
   177  
   178  func (s *helmService) DeleteHelmRepo(ctx context.Context, name string, projectID string, bannerEdgeID string) error {
   179  	if err := s.checkDependentWorkloads(ctx, name, bannerEdgeID); err != nil {
   180  		return err
   181  	}
   182  
   183  	helmRepo := &sourceApi.HelmRepository{
   184  		TypeMeta: metav1.TypeMeta{
   185  			Kind:       sourceApi.HelmRepositoryKind,
   186  			APIVersion: sourceApi.GroupVersion.String(),
   187  		},
   188  		ObjectMeta: metav1.ObjectMeta{
   189  			Name:      name,
   190  			Namespace: constants.FluxSystem,
   191  		},
   192  	}
   193  	helmRepositoryBase64, err := mapper.ToBase64StringFromHelmRepository(helmRepo)
   194  	if err != nil {
   195  		return err
   196  	}
   197  	err = s.sendChariotMessage(ctx, projectID, nil, chariotClientApi.Delete, helmRepositoryBase64)
   198  
   199  	//delete accompanying helm repo secret
   200  	if err != nil {
   201  		return err
   202  	}
   203  	extSec := &goext.ExternalSecret{TypeMeta: metav1.TypeMeta{
   204  		APIVersion: goext.ExtSecretGroupVersionKind.GroupVersion().String(),
   205  		Kind:       goext.ExtSecretGroupVersionKind.Kind,
   206  	},
   207  		ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: mapper.HelmReleaseNamespace},
   208  	}
   209  	extSecString, err := mapper.ToBase64StringFromExternalSecret(extSec)
   210  	if err != nil {
   211  		return err
   212  	}
   213  	err = s.sendChariotMessage(ctx, projectID, nil, chariotClientApi.Delete, extSecString)
   214  	if err != nil {
   215  		return err
   216  	}
   217  	return nil
   218  }
   219  
   220  func (s *helmService) checkDependentWorkloads(ctx context.Context, name string, bannerEdgeID string) error {
   221  	helmWorkloads, err := s.GetHelmWorkloadsData(ctx, nil, &bannerEdgeID, true)
   222  	if err != nil {
   223  		return fmt.Errorf("Error getting helm workloads data for banner %s: %s", bannerEdgeID, err)
   224  	}
   225  	for _, workload := range helmWorkloads {
   226  		if workload.HelmRepository == name {
   227  			return ErrorDeleteHelmRepo
   228  		}
   229  	}
   230  	return nil
   231  }
   232  
   233  func (s *helmService) CreateBannerHelmRepository(ctx context.Context, name string, url string, secret *string, projectID string) error {
   234  	var secretRef *fluxmeta.LocalObjectReference
   235  	if secret != nil {
   236  		secretRef = &fluxmeta.LocalObjectReference{
   237  			Name: *secret,
   238  		}
   239  	}
   240  
   241  	helmRepo := &sourceApi.HelmRepository{
   242  		TypeMeta: metav1.TypeMeta{
   243  			Kind:       sourceApi.HelmRepositoryKind,
   244  			APIVersion: sourceApi.GroupVersion.String(),
   245  		},
   246  		ObjectMeta: metav1.ObjectMeta{
   247  			Name:      name,
   248  			Namespace: constants.FluxSystem,
   249  			Labels: map[string]string{
   250  				constants.Tenant: projectID,
   251  			},
   252  		},
   253  		Spec: sourceApi.HelmRepositorySpec{
   254  			Interval: metav1.Duration{
   255  				Duration: 2 * time.Minute,
   256  			},
   257  			Timeout: &metav1.Duration{
   258  				Duration: 60 * time.Second,
   259  			},
   260  			URL:       url,
   261  			SecretRef: secretRef,
   262  		},
   263  	}
   264  
   265  	helmRepositoryBase64, err := mapper.ToBase64StringFromHelmRepository(helmRepo)
   266  	if err != nil {
   267  		return err
   268  	}
   269  	err = s.sendChariotMessage(ctx, projectID, nil, chariotClientApi.Create, helmRepositoryBase64)
   270  	if err != nil {
   271  		return err
   272  	}
   273  
   274  	return nil
   275  }
   276  
   277  func (s *helmService) GetHelmReleasesStatus(ctx context.Context, cluster *model.Cluster) ([]*model.HelmReleaseStatus, error) {
   278  	res, err := s.BQClient.GetKubeResource(ctx, cluster.ProjectID, cluster, mapper.GetHelmReleases())
   279  	if err != nil {
   280  		return nil, err
   281  	}
   282  
   283  	helmReleases, err := ConvertToHelmReleases(ctx, res)
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  
   288  	return mapper.ToHelmReleasesStatusModels(helmReleases), nil
   289  }
   290  
   291  func ConvertToHelmReleases(ctx context.Context, res []string) ([]helmApi.HelmRelease, error) {
   292  	var helmReleases []helmApi.HelmRelease
   293  	for i := range res {
   294  		helmRelease, err := convertToHelmRelease(ctx, res[i])
   295  		if err != nil {
   296  			return nil, err
   297  		}
   298  		helmReleases = append(helmReleases, *helmRelease)
   299  	}
   300  	return helmReleases, nil
   301  }
   302  
   303  func convertToHelmRelease(ctx context.Context, res string) (*helmApi.HelmRelease, error) {
   304  	helmRelease := &helmApi.HelmRelease{}
   305  	err := json.Unmarshal([]byte(res), helmRelease)
   306  	if err != nil {
   307  		log.Ctx(ctx).Err(err).Msg("Unable to unmarshal helm release resource")
   308  	}
   309  	return helmRelease, err
   310  }
   311  
   312  func (s *helmService) GetHelmWorkloadConfigmaps(ctx context.Context, helmEdgeID string) ([]*model.HelmWorkloadConfigmaps, error) {
   313  	configmaps := make([]*model.HelmWorkloadConfigmaps, 0)
   314  	rows, err := s.SQLDB.QueryContext(ctx, sqlquery.GetHelmWorkloadConfigmaps, helmEdgeID)
   315  	if err != nil {
   316  		return configmaps, err
   317  	}
   318  	for rows.Next() {
   319  		configmap := &model.HelmWorkloadConfigmaps{
   320  			ClusterEdgeID: "", // empty for backwards compatibility
   321  		}
   322  		if err := rows.Scan(&configmap.HelmWorkloadConfigmapEdgeID, &configmap.HelmEdgeID, &configmap.Namespace, &configmap.ConfigMap, &configmap.CreatedAt, &configmap.UpdatedAt); err != nil {
   323  			return configmaps, err
   324  		}
   325  		configmaps = append(configmaps, configmap)
   326  	}
   327  	return configmaps, nil
   328  }
   329  
   330  func (s *helmService) ValidateHelmConfig(config string, schema string) (bool, error) {
   331  	configValues, err := utils.YAMLToJSON(&config)
   332  	if err != nil {
   333  		return false, err
   334  	}
   335  
   336  	if configValues == nil {
   337  		return true, nil
   338  	}
   339  
   340  	rs := &jsonschema.Schema{}
   341  	if err = json.Unmarshal([]byte(schema), rs); err != nil {
   342  		return false, apierror.New("Invalid json schema in helm chart.")
   343  	}
   344  
   345  	errs, err := rs.ValidateBytes(context.Background(), configValues)
   346  	if err != nil {
   347  		return false, apierror.New(apierror.HelmChartError).AddGenericErrorExtension(apierror.DetailedHelmError, err)
   348  	}
   349  	if len(errs) > 0 {
   350  		return false, utils.GetSchemaError(errs)
   351  	}
   352  	return true, nil
   353  }
   354  
   355  func (s *helmService) CreateHelmReleaseSQL(ctx context.Context, payload model.HelmReleasePayload, cluster *model.Cluster) error {
   356  	if utils.IsNullOrEmpty(payload.BannerEdgeID) && cluster == nil {
   357  		return apierror.New("please provide bannerEdgeId or clusterEdgeId")
   358  	}
   359  	transaction, err := s.SQLDB.BeginTx(ctx, nil)
   360  	if err != nil {
   361  		return sqlerr.Wrap(err)
   362  	}
   363  	u := middleware.ForContext(ctx)
   364  
   365  	installationType := model.WorkloadInstallationTypeServerPreferred
   366  	if payload.InstallationType != nil {
   367  		installationType = *payload.InstallationType
   368  	}
   369  
   370  	workloadBannerID := utils.CheckString(payload.BannerEdgeID)
   371  	if cluster != nil {
   372  		if payload.HelmChart == constants.UpgradeChartName && payload.Version != "0.0.1" {
   373  			if err := s.validateEdgeOSUpgrade(ctx, payload, cluster.FleetVersion); err != nil {
   374  				return err
   375  			}
   376  		}
   377  		workloadBannerID = cluster.BannerEdgeID
   378  	}
   379  
   380  	var helmWorkloadID string
   381  	result := transaction.QueryRowContext(ctx, sqlquery.CreateHelmWorkload, workloadBannerID, payload.Name, payload.Namespace, payload.HelmChart, payload.HelmRepository, payload.Version, payload.ConfigValues, u.Username, installationType, payload.Secret)
   382  	err = result.Scan(&helmWorkloadID)
   383  	if err != nil {
   384  		if rollbackErr := transaction.Rollback(); rollbackErr != nil {
   385  			return sqlerr.Wrap(rollbackErr)
   386  		}
   387  		return sqlerr.Wrap(err)
   388  	}
   389  
   390  	if cluster != nil {
   391  		_, err := transaction.ExecContext(ctx, sqlquery.CreateWorkloadClusterMapper, helmWorkloadID, cluster.ClusterEdgeID)
   392  		if err != nil {
   393  			if rollbackErr := transaction.Rollback(); rollbackErr != nil {
   394  				return sqlerr.Wrap(rollbackErr)
   395  			}
   396  			return sqlerr.Wrap(err)
   397  		}
   398  	}
   399  
   400  	for _, secret := range payload.Secrets {
   401  		_, err = transaction.ExecContext(ctx, sqlquery.CreateHelmSecret, helmWorkloadID, secret)
   402  		if err != nil {
   403  			if rollbackErr := transaction.Rollback(); rollbackErr != nil {
   404  				return sqlerr.Wrap(rollbackErr)
   405  			}
   406  			return sqlerr.Wrap(err)
   407  		}
   408  	}
   409  
   410  	if installationType == model.WorkloadInstallationTypeAdvanced {
   411  		err = s.AddTerminalLabels(ctx, transaction, helmWorkloadID, payload)
   412  		if err != nil {
   413  			if rollbackErr := transaction.Rollback(); rollbackErr != nil {
   414  				return sqlerr.Wrap(rollbackErr)
   415  			}
   416  			return sqlerr.Wrap(err)
   417  		}
   418  	}
   419  
   420  	for _, configmap := range payload.InjectConfigmaps {
   421  		_, err = transaction.ExecContext(ctx, sqlquery.CreateHelmConfigmaps, helmWorkloadID, payload.Namespace, configmap.String())
   422  		if err != nil {
   423  			if rollbackErr := transaction.Rollback(); rollbackErr != nil {
   424  				return sqlerr.Wrap(rollbackErr)
   425  			}
   426  			return sqlerr.Wrap(err)
   427  		}
   428  	}
   429  
   430  	if err = transaction.Commit(); err != nil {
   431  		return sqlerr.Wrap(err)
   432  	}
   433  
   434  	return nil
   435  }
   436  
   437  func (s *helmService) AddTerminalLabels(ctx context.Context, tx *sql.Tx, helmWorkloadID string, payload model.HelmReleasePayload) error {
   438  	for _, labelID := range payload.Labels {
   439  		_, err := tx.ExecContext(ctx, sqlquery.CreateHelmWorkloadLabels, helmWorkloadID, labelID)
   440  		if err != nil {
   441  			return err
   442  		}
   443  	}
   444  	return nil
   445  }
   446  
   447  func (s *helmService) UpdateHelmReleaseSQL(ctx context.Context, helmEdgeID string, version, configValues *string, injectConfigmaps []model.InjectableConfigmaps) error {
   448  	u := middleware.ForContext(ctx)
   449  	if version == nil || configValues == nil {
   450  		var currVersion, currConfigValues *string
   451  		row := s.SQLDB.QueryRowContext(ctx, sqlquery.GetHelmWorkloadInfoByHelmEdgeID, helmEdgeID)
   452  		if err := row.Scan(&currVersion, &currConfigValues); err != nil {
   453  			return sqlerr.Wrap(err)
   454  		}
   455  		if version == nil {
   456  			version = currVersion
   457  		}
   458  		if configValues == nil {
   459  			configValues = currConfigValues
   460  		}
   461  	}
   462  
   463  	var ns string
   464  	result := s.SQLDB.QueryRowContext(ctx, sqlquery.UpdateHelmWorkload, version, configValues, u.Username, helmEdgeID)
   465  	if err := result.Scan(&ns); err != nil {
   466  		return sqlerr.Wrap(err)
   467  	}
   468  	if len(injectConfigmaps) == 0 {
   469  		_, err := s.SQLDB.ExecContext(ctx, sqlquery.DeleteHelmConfigmaps, helmEdgeID)
   470  		if err != nil {
   471  			return sqlerr.Wrap(err)
   472  		}
   473  		return nil
   474  	}
   475  	cfgs, err := s.GetHelmWorkloadConfigmaps(ctx, helmEdgeID)
   476  	if err != nil {
   477  		return sqlerr.Wrap(err)
   478  	}
   479  	dbconfigmaps := make(map[string]bool)
   480  	for _, cfg := range cfgs {
   481  		dbconfigmaps[cfg.ConfigMap] = true
   482  	}
   483  
   484  	tx, err := s.SQLDB.BeginTx(ctx, &sql.TxOptions{})
   485  	if err != nil {
   486  		return sqlerr.Wrap(err)
   487  	}
   488  
   489  	//1: handling case where a configmap is deleted.
   490  	for cfg := range dbconfigmaps {
   491  		found := true
   492  		for _, injectedConfigmap := range injectConfigmaps {
   493  			if cfg == injectedConfigmap.String() {
   494  				found = true
   495  				break
   496  			}
   497  			found = false
   498  		}
   499  		if !found {
   500  			_, err = tx.ExecContext(ctx, sqlquery.DeleteHelmConfigmap, helmEdgeID, cfg)
   501  			if err != nil {
   502  				if err := tx.Rollback(); err != nil {
   503  					return err
   504  				}
   505  			}
   506  		}
   507  	}
   508  
   509  	//2: handling case where a new configmap is added.
   510  	for _, injectedConfigmap := range injectConfigmaps {
   511  		if _, exists := dbconfigmaps[injectedConfigmap.String()]; !exists {
   512  			_, err := tx.ExecContext(ctx, sqlquery.CreateHelmConfigmaps, helmEdgeID, ns, injectedConfigmap.String())
   513  			if err != nil {
   514  				if err := tx.Rollback(); err != nil {
   515  					return err
   516  				}
   517  			}
   518  		}
   519  	}
   520  	if err := tx.Commit(); err != nil {
   521  		return sqlerr.Wrap(err)
   522  	}
   523  	return nil
   524  }
   525  
   526  func (s *helmService) GetHelmCharts(ctx context.Context, secretName, projectID string) ([]*model.HelmChart, error) {
   527  	username, password, repoURL, err := s.GetHelmRepoConnectionInfo(ctx, secretName, projectID)
   528  	if err != nil {
   529  		return nil, err
   530  	}
   531  	index, err := s.GetHelmRepositoryIndex(repoURL, username, password)
   532  	if err != nil {
   533  		return nil, err
   534  	}
   535  	return mapper.ToHelmChartsFromIndex(index), nil
   536  }
   537  
   538  // TODO(pa250194): bottleneck here. Implement a memory cache
   539  // Can be extended to be a redis cache in future.
   540  func (s *helmService) GetHelmChartVersion(ctx context.Context, name, secretName, projectID string) (*model.HelmChartResponse, error) {
   541  	username, password, repoURL, err := s.GetHelmRepoConnectionInfo(ctx, secretName, projectID)
   542  	if err != nil {
   543  		return nil, err
   544  	}
   545  	index, err := s.GetHelmRepositoryIndex(repoURL, username, password)
   546  	if err != nil {
   547  		return nil, err
   548  	}
   549  	chartVersions := index.Entries[name]
   550  	versions := []*string{}
   551  	for _, cv := range chartVersions {
   552  		versions = append(versions, &cv.Version)
   553  	}
   554  	return mapper.ToHelmChartResponse(name, versions), nil
   555  }
   556  
   557  func parseRepoURLYaml(response []byte) (*types.HelmManifestResponse, error) {
   558  	parsedYaml := &types.HelmManifestResponse{}
   559  	if err := yaml.Unmarshal(response, parsedYaml); err != nil {
   560  		return nil, err
   561  	}
   562  
   563  	return parsedYaml, nil
   564  }
   565  
   566  func (s *helmService) SoftDeleteHelmReleaseSQL(ctx context.Context, helmEdgeID, _, _ *string) error {
   567  	if utils.IsNullOrEmpty(helmEdgeID) {
   568  		return apierror.New("please provide a helmEdgeId")
   569  	}
   570  
   571  	workLabelInUse, err := s.workloadLabelsInUseByCluster(ctx, helmEdgeID)
   572  
   573  	if err != nil {
   574  		return sqlerr.Wrap(err)
   575  	}
   576  
   577  	if workLabelInUse {
   578  		return apierror.New("error deleting workload, being used by a cluster using labels")
   579  	}
   580  
   581  	result, err := s.SQLDB.ExecContext(ctx, sqlquery.SoftDeleteHelmWorkload, helmEdgeID)
   582  	if err != nil {
   583  		return sqlerr.Wrap(err)
   584  	}
   585  	affectedRows, err := result.RowsAffected()
   586  	if err != nil {
   587  		return sqlerr.Wrap(err)
   588  	}
   589  	if affectedRows == 0 {
   590  		log.Ctx(ctx).Err(err).Msg(fmt.Sprintf("Error Soft delete HelmRelease, helm_edge_id %s not found", *helmEdgeID))
   591  		return apierror.New("error deleting workload, not found")
   592  	}
   593  	return nil
   594  }
   595  
   596  func (s *helmService) DeleteHelmReleaseSQL(ctx context.Context, clusterEdgeID string) ([]string, error) {
   597  	rows, err := s.SQLDB.QueryContext(ctx, sqlquery.DeleteHelmWorkloadsByClusterEdgeID, clusterEdgeID)
   598  	if err != nil {
   599  		return nil, sqlerr.Wrap(err)
   600  	}
   601  	defer rows.Close()
   602  
   603  	var deletedWorkloads []string
   604  	for rows.Next() {
   605  		var name string
   606  		if err = rows.Scan(&name); err != nil {
   607  			return nil, sqlerr.Wrap(err)
   608  		}
   609  		deletedWorkloads = append(deletedWorkloads, name)
   610  	}
   611  
   612  	return deletedWorkloads, nil
   613  }
   614  
   615  func (s *helmService) workloadLabelsInUseByCluster(ctx context.Context, helmEdgeID *string) (bool, error) {
   616  	clustersFound := 0
   617  
   618  	rows, err := s.SQLDB.QueryContext(ctx, sqlquery.GetClusterEdgeIDsUsingHelmWorkloadLabels, helmEdgeID)
   619  
   620  	if err != nil {
   621  		// if there was an error from sql
   622  		// dont assume there is no matching error
   623  		// returning true to block delete
   624  		return true, sqlerr.Wrap(err)
   625  	}
   626  
   627  	for rows.Next() {
   628  		clustersFound++
   629  
   630  		if clustersFound > 1 {
   631  			// if rows can be iterated
   632  			// then there is matching clusters
   633  			// more than 1 matching cluster then in use = true
   634  			return true, nil
   635  		}
   636  	}
   637  
   638  	return false, nil
   639  }
   640  
   641  func (s *helmService) checkNamespaceToDelete(ctx context.Context, ClusterEdgeID *string, name string) (*model.HelmWorkload, bool, error) {
   642  	var dummyVal *string
   643  	helmWorkloads, err := s.GetHelmWorkloadsData(ctx, ClusterEdgeID, dummyVal, true)
   644  	if err != nil {
   645  		return nil, false, err
   646  	}
   647  
   648  	currentHelmWorkload := model.HelmWorkload{}
   649  	nameSpacesMap := make(map[string]int)
   650  
   651  	for _, helmWorkload := range helmWorkloads {
   652  		if helmWorkload.Name == name {
   653  			currentHelmWorkload = *mapper.ToHelmWorkload(helmWorkload)
   654  		}
   655  		key := helmWorkload.Namespace
   656  		count, ok := nameSpacesMap[key]
   657  		if !ok {
   658  			nameSpacesMap[key] = 1
   659  		} else {
   660  			nameSpacesMap[key] = count + 1
   661  		}
   662  	}
   663  
   664  	isDeleteNamespace := nameSpacesMap[currentHelmWorkload.Namespace] == 1
   665  	return &currentHelmWorkload, isDeleteNamespace, nil
   666  }
   667  
   668  func (s *helmService) addSecretsToDeleteToChariotMessage(ctx context.Context, helmWorkload *model.HelmWorkload, clusterEdgeID string, message []string) ([]string, error) {
   669  	var rows *sql.Rows
   670  	var err error
   671  	var secretsOnWorkload []string
   672  	var secretsCounts = make(map[string]int, 0)
   673  	rows, err = s.SQLDB.QueryContext(ctx, sqlquery.GetNamespacesAndSecretsNamesByClusterEdgeID, &clusterEdgeID, helmWorkload.Namespace)
   674  	if err != nil {
   675  		return nil, sqlerr.Wrap(err)
   676  	}
   677  	defer rows.Close()
   678  	for rows.Next() {
   679  		var helmEdgeID string
   680  		var workloadName string
   681  		var workloadNamespace string
   682  		var secretName string
   683  		if err = rows.Scan(&helmEdgeID, &workloadName, &workloadNamespace, &secretName); err != nil {
   684  			return nil, sqlerr.Wrap(err)
   685  		}
   686  
   687  		if helmEdgeID == helmWorkload.HelmEdgeID {
   688  			secretsOnWorkload = append(secretsOnWorkload, secretName)
   689  		}
   690  
   691  		if _, ok := secretsCounts[secretName]; !ok {
   692  			secretsCounts[secretName] = 1
   693  		} else {
   694  			secretsCounts[secretName]++
   695  		}
   696  	}
   697  	if err := rows.Err(); err != nil {
   698  		return nil, sqlerr.Wrap(err)
   699  	}
   700  
   701  	extSecretsToDelete, err := getExternalSecretsToDelete(ctx, helmWorkload.Namespace, secretsOnWorkload, secretsCounts)
   702  	if err != nil {
   703  		return nil, err
   704  	}
   705  
   706  	message = append(message, extSecretsToDelete...)
   707  	return message, nil
   708  }
   709  
   710  func getExternalSecretsToDelete(ctx context.Context, namespace string, secretsOnWorkload []string, secretsCounts map[string]int) ([]string, error) {
   711  	var extSecretsToDelete []string
   712  	for _, secret := range secretsOnWorkload {
   713  		if secretsCounts[secret] == 1 {
   714  			//append the external secret to list to be deleted
   715  			extSec := &goext.ExternalSecret{TypeMeta: metav1.TypeMeta{
   716  				APIVersion: goext.ExtSecretGroupVersionKind.GroupVersion().String(),
   717  				Kind:       goext.ExtSecretGroupVersionKind.Kind,
   718  			},
   719  				ObjectMeta: metav1.ObjectMeta{Name: secret, Namespace: namespace},
   720  			}
   721  			extSecString, err := mapper.ToBase64StringFromExternalSecret(extSec)
   722  			if err != nil {
   723  				//handle error
   724  				log.Ctx(ctx).Err(err).Msg("Unable to get base64 string from external secret")
   725  				return nil, err
   726  			}
   727  			extSecretsToDelete = append(extSecretsToDelete, extSecString)
   728  		}
   729  	}
   730  	return extSecretsToDelete, nil
   731  }
   732  
   733  func (s *helmService) GetDefaultConfigSchema(ctx context.Context, chartName string, secret string, chartVersion string, projectID string) (*model.HelmConfig, error) {
   734  	username, password, repoURL, err := s.GetHelmRepoConnectionInfo(ctx, secret, projectID)
   735  	if err != nil {
   736  		return nil, err
   737  	}
   738  	index, err := s.GetHelmRepositoryIndex(repoURL, username, password)
   739  	if err != nil {
   740  		return nil, err
   741  	}
   742  	selectedChart, err := s.GetHelmChartFromIndex(index, chartName, chartVersion, username, password)
   743  	if err != nil {
   744  		return nil, err
   745  	}
   746  	sch := string(selectedChart.Schema)
   747  	vals, err := yaml.Marshal(selectedChart.Values)
   748  	if err != nil {
   749  		return nil, err
   750  	}
   751  	valsString := string(vals)
   752  	//get the original values file for readability if available
   753  	for _, f := range selectedChart.Raw {
   754  		if f.Name == "values.yaml" {
   755  			valsString = string(f.Data)
   756  			break
   757  		}
   758  	}
   759  	config := &model.HelmConfig{
   760  		ConfigVals:   &valsString,
   761  		ConfigSchema: &sch,
   762  	}
   763  	return config, nil
   764  }
   765  
   766  func (s *helmService) GetHelmChartFromIndex(index *repo.IndexFile, chartName string, chartVersion string, username string, password string) (*chart.Chart, error) {
   767  	cv, err := index.Get(chartName, chartVersion)
   768  	if err != nil {
   769  		return nil, err
   770  	}
   771  	response, err := GetContents(cv.URLs[0], username, password, "", true)
   772  	if err != nil {
   773  		return nil, err
   774  	}
   775  	return loader.LoadArchive(response.RawBody())
   776  }
   777  
   778  func (s *helmService) GetHelmRepositoryIndex(repoURL string, username string, password string) (*repo.IndexFile, error) {
   779  	response, err := GetContents(repoURL, username, password, "/index.yaml", false)
   780  	if err != nil {
   781  		return nil, err
   782  	}
   783  	index := &repo.IndexFile{}
   784  	err = yaml.Unmarshal(response.Body(), index)
   785  	return index, err
   786  }
   787  
   788  func (s *helmService) GetHelmRepoConnectionInfo(ctx context.Context, secretName string, projectID string) (string, string, string, error) {
   789  	secretType := helmRepoSecretType
   790  	helmSecrets, err := s.GCPService.GetSecrets(ctx, &secretName, nil, &secretType, true, projectID)
   791  	if err != nil {
   792  		return "", "", "", err
   793  	}
   794  	if len(helmSecrets) != 1 {
   795  		return "", "", "", errors.New("helm repository secret not found")
   796  	}
   797  	_, username, password, repoURL := mapper.GetHelmSecretData(helmSecrets[0])
   798  	return username, password, repoURL, nil
   799  }
   800  
   801  func (s *helmService) GetHelmRepositoryInfo(ctx context.Context, params model.HelmConfigSchemaParams, projectID string) (*model.HelmRepositoryInfo, error) {
   802  	username, password, repoURL, err := s.GetHelmRepoConnectionInfo(ctx, params.SecretName, projectID)
   803  	if err != nil {
   804  		return nil, err
   805  	}
   806  	index, err := s.GetHelmRepositoryIndex(repoURL, username, password)
   807  	if err != nil {
   808  		return nil, err
   809  	}
   810  
   811  	selectedChart, err := s.GetHelmChartFromIndex(index, params.ChartName, params.ChartVersion, username, password)
   812  	if err != nil {
   813  		return nil, err
   814  	}
   815  
   816  	for _, f := range selectedChart.Files {
   817  		if strings.ToLower(f.Name) == "readme.md" {
   818  			readmeContents := string(f.Data)
   819  			return &model.HelmRepositoryInfo{
   820  				Readme:   &readmeContents,
   821  				Metadata: nil,
   822  			}, nil
   823  		}
   824  	}
   825  	return nil, errors.New("readme not found")
   826  }
   827  
   828  func GetContents(url string, username string, password string, file string, doNotParse bool) (*resty.Response, error) {
   829  	client := resty.New().R().SetDoNotParseResponse(doNotParse)
   830  	if username != "" && password != "" {
   831  		client.SetBasicAuth(username, password)
   832  	}
   833  	resp, err := client.Get(url + file)
   834  	return resp, bsl.ValidateResponse(client, resp, err)
   835  }
   836  
   837  func (s *helmService) sendChariotMessage(ctx context.Context, projectID string, cluster *model.Cluster, operation chariotClientApi.Operation, object ...string) error {
   838  	chariotMessage := chariotClientApi.
   839  		NewChariotMessage().
   840  		SetBanner(projectID).
   841  		SetOperation(operation).
   842  		SetOwner(ComponentOwner).
   843  		AddObject(object...)
   844  
   845  	if cluster != nil {
   846  		chariotMessage = chariotMessage.SetCluster(cluster.ClusterEdgeID)
   847  	}
   848  	if err := s.ChariotService.InvokeChariotPubsub(ctx, chariotMessage, nil); err != nil {
   849  		return fmt.Errorf("error calling chariot v2: %w", err)
   850  	}
   851  	return nil
   852  }
   853  
   854  func (s *helmService) GetAttachedHelmSecrets(ctx context.Context, helmEdgeID *string) ([]*model.HelmSecrets, error) {
   855  	var rows *sql.Rows
   856  	var err error
   857  
   858  	rows, err = s.SQLDB.QueryContext(ctx, sqlquery.GetAttachedHelmSecretByHelmEdgeID, helmEdgeID)
   859  	if err != nil {
   860  		return nil, sqlerr.Wrap(err)
   861  	}
   862  
   863  	helmSecrets := []*model.HelmSecrets{}
   864  	defer rows.Close()
   865  	for rows.Next() {
   866  		var helmSecret model.HelmSecrets
   867  		if err = rows.Scan(&helmSecret.SecretEdgeID, &helmSecret.Name, &helmSecret.CreatedAt, &helmSecret.UpdatedAt); err != nil {
   868  			return nil, sqlerr.Wrap(err)
   869  		}
   870  
   871  		helmSecrets = append(helmSecrets, &helmSecret)
   872  	}
   873  	if err := rows.Err(); err != nil {
   874  		return nil, sqlerr.Wrap(err)
   875  	}
   876  
   877  	return helmSecrets, nil
   878  }
   879  
   880  func (s *helmService) GetHelmWorkloadsData(ctx context.Context, clusterEdgeID *string, bannerEdgeID *string, includeDeleted bool) ([]types.HelmWorkloadQuery, error) {
   881  	var rows *sql.Rows
   882  	var err error
   883  
   884  	if !utils.IsNullOrEmpty(clusterEdgeID) {
   885  		clusterWorkloadsQuery := sqlquery.GetNonDeletedHelmWorkloadsByClusterEdgeID
   886  		if includeDeleted {
   887  			clusterWorkloadsQuery = sqlquery.GetHelmWorkloadsByClusterEdgeID
   888  		}
   889  		rows, err = s.SQLDB.QueryContext(ctx, clusterWorkloadsQuery, clusterEdgeID)
   890  	} else {
   891  		bannerWorkloadsQuery := sqlquery.GetNonDeletedHelmWorkloadsByBannerEdgeID
   892  		if includeDeleted {
   893  			bannerWorkloadsQuery = sqlquery.GetHelmWorkloadsByBannerEdgeID
   894  		}
   895  		rows, err = s.SQLDB.QueryContext(ctx, bannerWorkloadsQuery, bannerEdgeID)
   896  	}
   897  	if err != nil {
   898  		return nil, sqlerr.Wrap(err)
   899  	}
   900  
   901  	helmWorkloadsData := make([]types.HelmWorkloadQuery, 0)
   902  	for rows.Next() {
   903  		helmWorkloadData := types.HelmWorkloadQuery{}
   904  		var clusterEdgeID sql.NullString
   905  
   906  		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)
   907  		if err != nil {
   908  			return nil, sqlerr.Wrap(err)
   909  		}
   910  		if clusterEdgeID.Valid {
   911  			helmWorkloadData.ClusterEdgeID = clusterEdgeID.String
   912  		}
   913  
   914  		helmWorkloadsData = append(helmWorkloadsData, helmWorkloadData)
   915  	}
   916  
   917  	if err := rows.Err(); err != nil {
   918  		return nil, sqlerr.Wrap(err)
   919  	}
   920  	return helmWorkloadsData, nil
   921  }
   922  
   923  func (s *helmService) getHelmWorkloadData(ctx context.Context, helmEdgeID, clusterEdgeID string) (*types.HelmWorkloadQuery, error) {
   924  	helmWorkloadData := types.HelmWorkloadQuery{}
   925  
   926  	row := s.SQLDB.QueryRowContext(ctx, sqlquery.GetHelmWorkload, helmEdgeID, clusterEdgeID)
   927  	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 {
   928  		return nil, sqlerr.Wrap(err)
   929  	}
   930  
   931  	return &helmWorkloadData, nil
   932  }
   933  
   934  func (s *helmService) GetHelmWorkloads(ctx context.Context, clusterEdgeID *string, bannerEdgeID *string, all bool) ([]*model.HelmWorkload, error) {
   935  	helmWorkloads := make([]*model.HelmWorkload, 0)
   936  	var err error
   937  
   938  	if utils.IsNullOrEmpty(clusterEdgeID) && utils.IsNullOrEmpty(bannerEdgeID) {
   939  		return nil, apierror.New("please provide clusterEdgeId or bannerEdgeId")
   940  	}
   941  
   942  	queriedHelmWorkloads, err := s.GetHelmWorkloadsData(ctx, clusterEdgeID, bannerEdgeID, all)
   943  	if err != nil {
   944  		return nil, err
   945  	}
   946  
   947  	helmChartVersionMap := make(map[string]*model.HelmChartResponse)
   948  	mu := sync.Mutex{}
   949  	wg := sync.WaitGroup{}
   950  	errorChan := make(chan error, len(queriedHelmWorkloads))
   951  	semaphore := make(chan struct{}, maxHelmWorkloadConcurrency)
   952  	for i := range queriedHelmWorkloads {
   953  		semaphore <- struct{}{}
   954  		wg.Add(1)
   955  		go func(gctx context.Context) {
   956  			defer wg.Done()
   957  			defer func() { <-semaphore }()
   958  			helmWorkload, err := s.getHelmWorkloadFromQueryData(gctx, queriedHelmWorkloads[i], helmChartVersionMap)
   959  			if err != nil {
   960  				errorChan <- err
   961  				return
   962  			}
   963  			mu.Lock()
   964  			helmWorkloads = append(helmWorkloads, helmWorkload)
   965  			mu.Unlock()
   966  		}(ctx)
   967  	}
   968  	wg.Wait()
   969  	close(errorChan)
   970  	var aggregatedErr error
   971  	for err := range errorChan {
   972  		if err != nil {
   973  			aggregatedErr = errors.Join(aggregatedErr, err)
   974  		}
   975  	}
   976  	return helmWorkloads, aggregatedErr
   977  }
   978  
   979  func (s *helmService) GetHelmWorkloadsByName(ctx context.Context, clusterEdgeID, workloadName string) ([]*model.HelmWorkload, error) {
   980  	var (
   981  		rows *sql.Rows
   982  		err  error
   983  	)
   984  
   985  	rows, err = s.SQLDB.QueryContext(ctx, sqlquery.GetWorkloadsByNameAndID, clusterEdgeID, workloadName)
   986  
   987  	if err != nil {
   988  		return nil, sqlerr.Wrap(err)
   989  	}
   990  
   991  	helmWorkloadsList := make([]*model.HelmWorkload, 0)
   992  	for rows.Next() {
   993  		var helmWorkloadData model.HelmWorkload
   994  		var clusterEdgeID sql.NullString
   995  
   996  		err = rows.Scan(&helmWorkloadData.Name, &helmWorkloadData.HelmEdgeID, &clusterEdgeID)
   997  		if err != nil {
   998  			return nil, sqlerr.Wrap(err)
   999  		}
  1000  		if clusterEdgeID.Valid {
  1001  			helmWorkloadData.ClusterEdgeID = clusterEdgeID.String
  1002  		}
  1003  
  1004  		helmWorkloadsList = append(helmWorkloadsList, &helmWorkloadData)
  1005  	}
  1006  
  1007  	if err := rows.Err(); err != nil {
  1008  		return nil, sqlerr.Wrap(err)
  1009  	}
  1010  	return helmWorkloadsList, nil
  1011  }
  1012  
  1013  func (s *helmService) GetHelmStatus(ctx context.Context, clusterEdgeID, workloadName, workloadNamespace, helmChart, helmRepo, createdAt, updatedAt string, deleted *bool) (*model.HelmStatus, error) {
  1014  	if clusterEdgeID == "" {
  1015  		return &model.HelmStatus{Status: status.NotAvailable, Message: status.NotDeployedStatusMessage}, nil
  1016  	}
  1017  
  1018  	if deleted != nil && *deleted {
  1019  		return &model.HelmStatus{Status: status.Deleting, Message: status.DeletingStatusMessage}, nil
  1020  	}
  1021  	pods, err := s.getHelmPodsStatus(ctx, workloadNamespace, clusterEdgeID)
  1022  	if err != nil {
  1023  		return nil, err
  1024  	}
  1025  	createdTime, err := time.Parse(time.RFC3339, createdAt)
  1026  	if err != nil {
  1027  		return nil, err
  1028  	}
  1029  	updatedTime, err := time.Parse(time.RFC3339, updatedAt)
  1030  	if err != nil {
  1031  		return nil, err
  1032  	}
  1033  	watchedTime, err := s.getHelmWorkloadWatchedTimeFromSQL(ctx, workloadName, clusterEdgeID)
  1034  	// cases when there's no watched data for the helm workload
  1035  	if err != nil {
  1036  		if !errors.Is(err, sql.ErrNoRows) {
  1037  			return nil, err
  1038  		}
  1039  		if createdTime.Equal(updatedTime) {
  1040  			return &model.HelmStatus{Status: status.Installing, Message: status.InstallingStatusMessage, Pods: pods}, nil
  1041  		}
  1042  		return &model.HelmStatus{Status: status.Updating, Message: status.UpdatingStatusMessage, Pods: pods}, nil
  1043  	}
  1044  	// check if the workload is updating
  1045  	if watchedTime.Before(updatedTime) {
  1046  		return &model.HelmStatus{Status: status.Updating, Message: status.UpdatingStatusMessage, Pods: pods}, nil
  1047  	}
  1048  
  1049  	helmStatus, reasonHelmRelease, err := s.getHelmStatusValueFromSQL(ctx, "HelmRelease", workloadName, clusterEdgeID)
  1050  	if err != nil {
  1051  		return nil, err
  1052  	}
  1053  	if helmStatus == status.IsReady {
  1054  		return &model.HelmStatus{Status: status.Ready, Message: status.WorkloadReadyStatusMessage, Pods: pods}, nil
  1055  	}
  1056  	message := fmt.Sprintf("HelmRelease: %s", reasonHelmRelease)
  1057  	// check if the workload is installing
  1058  	if helmStatus == unknownStatus && createdTime.Equal(updatedTime) {
  1059  		return &model.HelmStatus{Status: status.Installing, Message: status.InstallingStatusMessage, Pods: pods}, nil
  1060  	}
  1061  
  1062  	// If the helm workload status is not ready, get the helm chart/helm repo statuses
  1063  	_, reasonHelmChart, err := s.getHelmStatusValueFromSQL(ctx, "HelmChart", helmChart, clusterEdgeID)
  1064  	if err != nil {
  1065  		return nil, err
  1066  	}
  1067  	message = fmt.Sprintf("%s, helmChart: %s", message, reasonHelmChart)
  1068  
  1069  	_, reasonHelmRepo, err := s.getHelmStatusValueFromSQL(ctx, "HelmRepo", helmRepo, clusterEdgeID)
  1070  	if err != nil {
  1071  		return nil, err
  1072  	}
  1073  	message = fmt.Sprintf("%s, helmRepo: %s", message, reasonHelmRepo)
  1074  
  1075  	return &model.HelmStatus{Status: status.Error, Message: message, Pods: pods}, nil
  1076  }
  1077  
  1078  func (s *helmService) getHelmWorkloadWatchedTimeFromSQL(ctx context.Context, name, clusterEdgeID string) (time.Time, error) {
  1079  	var watchedTime time.Time
  1080  	row := s.SQLDB.QueryRowContext(ctx, sqlquery.GetHelmWorkloadWatchedTime, name, clusterEdgeID)
  1081  	err := row.Scan(&watchedTime)
  1082  	return watchedTime, err
  1083  }
  1084  
  1085  func (s *helmService) getHelmStatusValueFromSQL(ctx context.Context, kind, name, clusterEdgeID string) (string, string, error) {
  1086  	status := unknownStatus
  1087  	var reason string
  1088  	var missing bool
  1089  	row := s.SQLDB.QueryRowContext(ctx, sqlquery.GetHelmConditionStatus, kind, name, clusterEdgeID)
  1090  	if err := row.Scan(&status, &missing); err != nil {
  1091  		if errors.Is(err, sql.ErrNoRows) {
  1092  			return status, "No Status Found", nil
  1093  		}
  1094  		return "", "", err
  1095  	}
  1096  	status = mapper.ToTrimedValue(status)
  1097  
  1098  	if missing {
  1099  		return unknownStatus, fmt.Sprintf("%s %s value missing", kind, name), nil
  1100  	}
  1101  	row = s.SQLDB.QueryRowContext(ctx, sqlquery.GetHelmConditionReason, kind, name, clusterEdgeID)
  1102  	if err := row.Scan(&reason, &missing); err != nil {
  1103  		if errors.Is(err, sql.ErrNoRows) {
  1104  			return status, "No Message Found", nil
  1105  		}
  1106  		return "", "", err
  1107  	}
  1108  	return status, mapper.ToTrimedValue(reason), nil
  1109  }
  1110  
  1111  func (s *helmService) getHelmPodsStatus(ctx context.Context, namespace, clusterEdgeID string) ([]*model.Pods, error) {
  1112  	var (
  1113  		unknownstatus = unknownStatus
  1114  		reason, name  string
  1115  		missing       bool
  1116  		podsStatusMap = make(map[string]string)
  1117  		podsReasonMap = make(map[string]string)
  1118  		pods          []*model.Pods
  1119  	)
  1120  	rows, err := s.SQLDB.QueryContext(ctx, sqlquery.GetHelmWorkloadPodsStatus, clusterEdgeID, namespace)
  1121  	if err != nil {
  1122  		return pods, sqlerr.Wrap(err)
  1123  	}
  1124  	for rows.Next() {
  1125  		if err := rows.Scan(&name, &unknownstatus, &missing); err != nil {
  1126  			if errors.Is(err, sql.ErrNoRows) {
  1127  				podsStatusMap[name] = "No Status Found"
  1128  			}
  1129  			return pods, sqlerr.Wrap(err)
  1130  		}
  1131  		if missing {
  1132  			podsStatusMap[name] = fmt.Sprintf("Pod %s status value missing", name)
  1133  		} else {
  1134  			podsStatusMap[name] = mapper.ToTrimedValue(unknownstatus)
  1135  		}
  1136  	}
  1137  
  1138  	rows, err = s.SQLDB.QueryContext(ctx, sqlquery.GetHelmWorkloadPodsReason, clusterEdgeID, namespace)
  1139  	if err != nil {
  1140  		return pods, sqlerr.Wrap(err)
  1141  	}
  1142  	for rows.Next() {
  1143  		if err := rows.Scan(&name, &reason, &missing); err != nil {
  1144  			if errors.Is(err, sql.ErrNoRows) {
  1145  				podsReasonMap[name] = "No Message Found"
  1146  			}
  1147  			return pods, sqlerr.Wrap(err)
  1148  		}
  1149  		stats, statusExists := podsStatusMap[name]
  1150  		if missing && statusExists && stats == status.IsReady {
  1151  			podsReasonMap[name] = "Pod is ready"
  1152  		} else if missing {
  1153  			podsReasonMap[name] = fmt.Sprintf("Pod %s message value missing", name)
  1154  		} else {
  1155  			podsReasonMap[name] = mapper.ToTrimedValue(reason)
  1156  		}
  1157  	}
  1158  
  1159  	for k, v := range podsStatusMap {
  1160  		pods = append(pods, &model.Pods{Name: k, Status: mapper.ToPodStatus(v), Message: podsReasonMap[k]})
  1161  	}
  1162  	return pods, nil
  1163  }
  1164  
  1165  func (s *helmService) GetHelmWorkload(ctx context.Context, helmEdgeID, clusterEdgeID string) (*model.HelmWorkload, error) {
  1166  	queriedHelmWorkload, err := s.getHelmWorkloadData(ctx, helmEdgeID, clusterEdgeID)
  1167  	if err != nil {
  1168  		return nil, err
  1169  	}
  1170  	helmChartVersionMap := make(map[string]*model.HelmChartResponse)
  1171  	return s.getHelmWorkloadFromQueryData(ctx, *queriedHelmWorkload, helmChartVersionMap)
  1172  }
  1173  
  1174  func (s *helmService) DeleteHelmSecrets(ctx context.Context, helmEdgeID string, deleteSecrets []string) (bool, error) {
  1175  	for _, secret := range deleteSecrets {
  1176  		_, err := s.SQLDB.ExecContext(ctx, sqlquery.DeleteHelmSecret, helmEdgeID, secret)
  1177  		if err != nil {
  1178  			return false, sqlerr.Wrap(err)
  1179  		}
  1180  	}
  1181  	return true, nil
  1182  }
  1183  
  1184  func (s *helmService) AddHelmSecrets(ctx context.Context, helmEdgeID string, secretName []string) (bool, error) {
  1185  	for _, secret := range secretName {
  1186  		_, err := s.SQLDB.ExecContext(ctx, sqlquery.CreateHelmSecret, helmEdgeID, secret)
  1187  		if err != nil {
  1188  			return false, sqlerr.Wrap(err)
  1189  		}
  1190  	}
  1191  	return true, nil
  1192  }
  1193  
  1194  func (s *helmService) GetBannerByHelmEdgeID(ctx context.Context, helmEdgeID string) (*model.Banner, error) {
  1195  	row := s.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerByHelmEdgeID, helmEdgeID)
  1196  
  1197  	banner := &model.Banner{}
  1198  
  1199  	if err := row.Scan(&banner.ProjectID, &banner.BannerEdgeID); err != nil {
  1200  		return nil, sqlerr.Wrap(err)
  1201  	}
  1202  	return banner, nil
  1203  }
  1204  
  1205  func (s *helmService) GetSecretsByHelmEdgeID(ctx context.Context, helmEdgeID string) ([]string, error) {
  1206  	rows, err := s.SQLDB.QueryContext(ctx, sqlquery.GetHelmWorkloadSecretNames, helmEdgeID)
  1207  	if err != nil {
  1208  		return nil, sqlerr.Wrap(err)
  1209  	}
  1210  	currentSecretNames := []string{}
  1211  	var secret string
  1212  	for rows.Next() {
  1213  		err = rows.Scan(&secret)
  1214  		if err != nil {
  1215  			return nil, sqlerr.Wrap(err)
  1216  		}
  1217  		currentSecretNames = append(currentSecretNames, secret)
  1218  	}
  1219  	if err := rows.Err(); err != nil {
  1220  		return nil, sqlerr.Wrap(err)
  1221  	}
  1222  	return currentSecretNames, nil
  1223  }
  1224  
  1225  func (s *helmService) GetLabelsByHelmEdgeID(ctx context.Context, helmEdgeID string) ([]*model.Label, error) {
  1226  	rows, err := s.SQLDB.QueryContext(ctx, sqlquery.SelectEdgeLabelsForHelmEdgeID, helmEdgeID)
  1227  	if err != nil {
  1228  		return nil, sqlerr.Wrap(err)
  1229  	}
  1230  
  1231  	var labelList []*model.Label
  1232  	defer rows.Close()
  1233  	for rows.Next() {
  1234  		var label model.Label
  1235  		if err = rows.Scan(&label.LabelEdgeID, &label.Key, &label.Color, &label.Visible, &label.Editable, &label.BannerEdgeID, &label.Unique, &label.Description, &label.Type); err != nil {
  1236  			return nil, sqlerr.Wrap(err)
  1237  		}
  1238  		labelList = append(labelList, &label)
  1239  	}
  1240  	if err := rows.Err(); err != nil {
  1241  		return nil, sqlerr.Wrap(err)
  1242  	}
  1243  	return labelList, nil
  1244  }
  1245  
  1246  func (s *helmService) AddWorkloadLabel(ctx context.Context, helmEdgeID string, newLabel string) (bool, error) {
  1247  	_, errAdd := s.SQLDB.ExecContext(ctx, sqlquery.CreateHelmWorkloadLabels, helmEdgeID, newLabel)
  1248  	if errAdd != nil {
  1249  		return false, sqlerr.Wrap(errAdd)
  1250  	}
  1251  	return true, nil
  1252  }
  1253  
  1254  func (s *helmService) AddWorkloadLabels(ctx context.Context, helmEdgeID string, newLabels []string) (bool, error) {
  1255  	for _, newLabel := range newLabels {
  1256  		_, err := s.AddWorkloadLabel(ctx, helmEdgeID, newLabel)
  1257  		if err != nil {
  1258  			return false, sqlerr.Wrap(err)
  1259  		}
  1260  	}
  1261  	return true, nil
  1262  }
  1263  
  1264  func (s *helmService) DeleteWorkloadLabel(ctx context.Context, helmEdgeID, deleteLabel string) (bool, error) {
  1265  	_, errDelete := s.SQLDB.ExecContext(ctx, sqlquery.DeleteLabelFromHelmWorkload, deleteLabel, helmEdgeID)
  1266  	if errDelete != nil {
  1267  		return false, sqlerr.Wrap(errDelete)
  1268  	}
  1269  	return true, nil
  1270  }
  1271  
  1272  func (s *helmService) DeleteWorkloadLabels(ctx context.Context, helmEdgeID string) (bool, error) {
  1273  	_, errDelete := s.SQLDB.ExecContext(ctx, sqlquery.DeleteLabelEdgeIDWithHelmEdgeID, helmEdgeID)
  1274  	if errDelete != nil {
  1275  		return false, sqlerr.Wrap(errDelete)
  1276  	}
  1277  	return true, nil
  1278  }
  1279  
  1280  func (s *helmService) DeleteWorkloadLabelByLabelEdgeID(ctx context.Context, deleteLabel string) (bool, error) {
  1281  	_, errDelete := s.SQLDB.ExecContext(ctx, sqlquery.DeleteLabelEdgeIDWithLabelEdgeID, deleteLabel)
  1282  	if errDelete != nil {
  1283  		return false, fmt.Errorf("failed to delete label from workload")
  1284  	}
  1285  	return true, nil
  1286  }
  1287  
  1288  func (s *helmService) DeleteHelmReleaseByHelmEdgeID(ctx context.Context, helmEdgeID string) error {
  1289  	_, err := s.SQLDB.ExecContext(ctx, sqlquery.DeleteHelmWorkload, helmEdgeID)
  1290  	if err != nil {
  1291  		return sqlerr.Wrap(err)
  1292  	}
  1293  	return nil
  1294  }
  1295  
  1296  func (s *helmService) GetSoftDeletedHelmWorkloads(ctx context.Context, clusterEdgeID string) ([]*model.HelmWorkload, error) {
  1297  	rows, err := s.SQLDB.QueryContext(ctx, sqlquery.GetSoftDeletedHelmWorkloadsByClusterEdgeID, clusterEdgeID)
  1298  	if err != nil {
  1299  		return nil, sqlerr.Wrap(err)
  1300  	}
  1301  	defer rows.Close()
  1302  
  1303  	var workloads = []*model.HelmWorkload{}
  1304  	for rows.Next() {
  1305  		var workload = &model.HelmWorkload{}
  1306  		err = rows.Scan(&workload.HelmEdgeID, &workload.Name)
  1307  		if err != nil {
  1308  			return nil, sqlerr.Wrap(err)
  1309  		}
  1310  		workloads = append(workloads, workload)
  1311  	}
  1312  	return workloads, nil
  1313  }
  1314  
  1315  func (s *helmService) getHelmWorkloadFromQueryData(ctx context.Context, queriedHelmWorkload types.HelmWorkloadQuery, helmChartVersionMap map[string]*model.HelmChartResponse) (*model.HelmWorkload, error) {
  1316  	secrets, err := s.GetAttachedHelmSecrets(ctx, &queriedHelmWorkload.HelmEdgeID)
  1317  	if err != nil {
  1318  		return nil, err
  1319  	}
  1320  	key := queriedHelmWorkload.HelmChart + queriedHelmWorkload.HelmRepoSecret
  1321  	helmChartVersion, ok := helmChartVersionMap[key]
  1322  	if !ok {
  1323  		chartVersions, err := s.GetHelmChartVersion(ctx, queriedHelmWorkload.HelmChart, queriedHelmWorkload.HelmRepoSecret, queriedHelmWorkload.ProjectID)
  1324  		if err != nil {
  1325  			return nil, err
  1326  		}
  1327  		helmChartVersion = chartVersions
  1328  		helmChartVersionMap[key] = chartVersions
  1329  	}
  1330  	configMaps, err := s.GetHelmWorkloadConfigmaps(ctx, queriedHelmWorkload.HelmEdgeID)
  1331  	if err != nil {
  1332  		return nil, err
  1333  	}
  1334  	labels, err := s.GetLabelsByHelmEdgeID(ctx, queriedHelmWorkload.HelmEdgeID)
  1335  	if err != nil {
  1336  		return nil, err
  1337  	}
  1338  	helmWorkload := mapper.UpdateHelmWorkload(mapper.ToHelmWorkload(queriedHelmWorkload), helmChartVersion.Versions, secrets, configMaps, labels)
  1339  	return helmWorkload, nil
  1340  }
  1341  
  1342  func (s *helmService) validateEdgeOSUpgrade(ctx context.Context, payload model.HelmReleasePayload, clusterInfraVersion string) error {
  1343  	upgradeVersion := payload.Version
  1344  	if clusterInfraVersion == "latest" || clusterInfraVersion == "" {
  1345  		clusterInfraVersion = version.New().SemVer
  1346  	}
  1347  	infraBaseVersion, edgeOSMinorVersion := utils.SetVersionToBaseAndMinorVersion(&clusterInfraVersion, &upgradeVersion)
  1348  	compatible, err := s.CompatibilityService.IsCompatible(ctx, model.ArtifactVersion{Name: fleet.Store, Version: infraBaseVersion}, model.ArtifactVersion{Name: constants.EdgeOSArtifact, Version: edgeOSMinorVersion})
  1349  	if err != nil {
  1350  		return err
  1351  	}
  1352  	if !compatible {
  1353  		return fmt.Errorf("the requested upgrade ien version %s is not compatible with the current store infra version %s", upgradeVersion, clusterInfraVersion)
  1354  	}
  1355  	return nil
  1356  }
  1357  
  1358  func NewHelmService(cfg *types.Config, chariotService ChariotService, gcpService GCPService,
  1359  	sqlDB *sql.DB, bqClient clients.BQClient, compatibilityService CompatibilityService) *helmService { //nolint stupid
  1360  	return &helmService{
  1361  		Config:               cfg,
  1362  		ChariotService:       chariotService,
  1363  		GCPService:           gcpService,
  1364  		SQLDB:                sqlDB,
  1365  		BQClient:             bqClient,
  1366  		CompatibilityService: compatibilityService,
  1367  	}
  1368  }
  1369  

View as plain text