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
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
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
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: "",
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
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
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
539
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
622
623
624 return true, sqlerr.Wrap(err)
625 }
626
627 for rows.Next() {
628 clustersFound++
629
630 if clustersFound > 1 {
631
632
633
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 ¤tHelmWorkload, 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
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
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
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
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
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
1058 if helmStatus == unknownStatus && createdTime.Equal(updatedTime) {
1059 return &model.HelmStatus{Status: status.Installing, Message: status.InstallingStatusMessage, Pods: pods}, nil
1060 }
1061
1062
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 {
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