From 0d48280083f5fa36d306cf0320a4284a01bd268a Mon Sep 17 00:00:00 2001 From: Prashant Ghildiyal <60953820+pghildiyal@users.noreply.github.com> Date: Mon, 23 Oct 2023 18:02:15 -0700 Subject: [PATCH] moved methods use by workflowdagexecture to it (#4147) Co-authored-by: Kripansh --- api/restHandler/PipelineTriggerRestHandler.go | 2 +- api/router/pubsub/ApplicationStatusHandler.go | 2 +- pkg/app/AppService.go | 1930 +--------------- pkg/pipeline/WorkflowDagExecutor.go | 2001 ++++++++++++++++- wire_gen.go | 2 +- 5 files changed, 2030 insertions(+), 1907 deletions(-) diff --git a/api/restHandler/PipelineTriggerRestHandler.go b/api/restHandler/PipelineTriggerRestHandler.go index 4963ccc73d..bd6235c478 100644 --- a/api/restHandler/PipelineTriggerRestHandler.go +++ b/api/restHandler/PipelineTriggerRestHandler.go @@ -21,6 +21,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/devtron-labs/devtron/pkg/app" "github.com/devtron-labs/devtron/util" "github.com/gorilla/mux" "go.opentelemetry.io/otel" @@ -30,7 +31,6 @@ import ( "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/api/restHandler/common" - "github.com/devtron-labs/devtron/pkg/app" "github.com/devtron-labs/devtron/pkg/deploymentGroup" "github.com/devtron-labs/devtron/pkg/pipeline" "github.com/devtron-labs/devtron/pkg/team" diff --git a/api/router/pubsub/ApplicationStatusHandler.go b/api/router/pubsub/ApplicationStatusHandler.go index cd27a60b6e..94378a3639 100644 --- a/api/router/pubsub/ApplicationStatusHandler.go +++ b/api/router/pubsub/ApplicationStatusHandler.go @@ -22,6 +22,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/devtron-labs/devtron/pkg/app" "time" "github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig" @@ -32,7 +33,6 @@ import ( v1alpha12 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" pubsub "github.com/devtron-labs/common-lib/pubsub-lib" - "github.com/devtron-labs/devtron/pkg/app" "github.com/devtron-labs/devtron/pkg/appStore/deployment/service" "github.com/devtron-labs/devtron/pkg/pipeline" "github.com/go-pg/pg" diff --git a/pkg/app/AppService.go b/pkg/app/AppService.go index be4ee286d9..3fc92f185b 100644 --- a/pkg/app/AppService.go +++ b/pkg/app/AppService.go @@ -20,15 +20,11 @@ package app import ( "context" "encoding/json" - error2 "errors" "fmt" "github.com/caarlos0/env" - pubsub "github.com/devtron-labs/common-lib/pubsub-lib" - k8s2 "github.com/devtron-labs/common-lib/utils/k8s" k8sCommonBean "github.com/devtron-labs/common-lib/utils/k8s/commonBean" "github.com/devtron-labs/common-lib/utils/k8s/health" client2 "github.com/devtron-labs/devtron/api/helm-app" - bean3 "github.com/devtron-labs/devtron/pkg/app/bean" status2 "github.com/devtron-labs/devtron/pkg/app/status" repository4 "github.com/devtron-labs/devtron/pkg/appStore/deployment/repository" "github.com/devtron-labs/devtron/pkg/appStore/deployment/service" @@ -38,18 +34,13 @@ import ( "github.com/devtron-labs/devtron/pkg/k8s" repository3 "github.com/devtron-labs/devtron/pkg/pipeline/history/repository" repository5 "github.com/devtron-labs/devtron/pkg/pipeline/repository" - "github.com/devtron-labs/devtron/pkg/resourceQualifiers" "github.com/devtron-labs/devtron/pkg/variables" "github.com/devtron-labs/devtron/pkg/variables/parsers" _ "github.com/devtron-labs/devtron/pkg/variables/repository" - repository6 "github.com/devtron-labs/devtron/pkg/variables/repository" "github.com/devtron-labs/devtron/util/argo" - "github.com/tidwall/gjson" - "github.com/tidwall/sjson" "go.opentelemetry.io/otel" "io/ioutil" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" chart2 "k8s.io/helm/pkg/proto/hapi/chart" "net/url" "os" @@ -70,13 +61,10 @@ import ( application2 "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/client/argocdServer" "github.com/devtron-labs/devtron/client/argocdServer/application" client "github.com/devtron-labs/devtron/client/events" - "github.com/devtron-labs/devtron/internal/middleware" - "github.com/devtron-labs/devtron/internal/sql/models" "github.com/devtron-labs/devtron/internal/sql/repository" "github.com/devtron-labs/devtron/internal/sql/repository/chartConfig" "github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig" @@ -88,11 +76,7 @@ import ( util "github.com/devtron-labs/devtron/util/event" "github.com/devtron-labs/devtron/util/rbac" "github.com/go-pg/pg" - errors2 "github.com/juju/errors" - "github.com/pkg/errors" "go.uber.org/zap" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" ) type AppServiceConfig struct { @@ -184,24 +168,26 @@ type AppServiceImpl struct { } type AppService interface { - TriggerRelease(overrideRequest *bean.ValuesOverrideRequest, ctx context.Context, triggeredAt time.Time, deployedBy int32) (releaseNo int, manifest []byte, err error) + //TriggerRelease(overrideRequest *bean.ValuesOverrideRequest, ctx context.Context, triggeredAt time.Time, deployedBy int32) (releaseNo int, manifest []byte, err error) UpdateReleaseStatus(request *bean.ReleaseStatusUpdateRequest) (bool, error) UpdateDeploymentStatusAndCheckIsSucceeded(app *v1alpha1.Application, statusTime time.Time, isAppStore bool) (bool, *chartConfig.PipelineOverride, error) - TriggerCD(artifact *repository.CiArtifact, cdWorkflowId, wfrId int, pipeline *pipelineConfig.Pipeline, triggeredAt time.Time) error + //TriggerCD(artifact *repository.CiArtifact, cdWorkflowId, wfrId int, pipeline *pipelineConfig.Pipeline, triggeredAt time.Time) error GetConfigMapAndSecretJson(appId int, envId int, pipelineId int) ([]byte, error) UpdateCdWorkflowRunnerByACDObject(app *v1alpha1.Application, cdWfrId int, updateTimedOutStatus bool) error GetCmSecretNew(appId int, envId int, isJob bool) (*bean.ConfigMapJson, *bean.ConfigSecretJson, error) - MarkImageScanDeployed(appId int, envId int, imageDigest string, clusterId int, isScanEnabled bool) error + //MarkImageScanDeployed(appId int, envId int, imageDigest string, clusterId int, isScanEnabled bool) error UpdateDeploymentStatusForGitOpsPipelines(app *v1alpha1.Application, statusTime time.Time, isAppStore bool) (bool, bool, *chartConfig.PipelineOverride, error) WriteCDSuccessEvent(appId int, envId int, override *chartConfig.PipelineOverride) GetGitOpsRepoPrefix() string - GetValuesOverrideForTrigger(overrideRequest *bean.ValuesOverrideRequest, triggeredAt time.Time, ctx context.Context) (*ValuesOverrideResponse, error) - GetEnvOverrideByTriggerType(overrideRequest *bean.ValuesOverrideRequest, triggeredAt time.Time, ctx context.Context) (*chartConfig.EnvConfigOverride, error) - GetAppMetricsByTriggerType(overrideRequest *bean.ValuesOverrideRequest, ctx context.Context) (bool, error) - GetDeploymentStrategyByTriggerType(overrideRequest *bean.ValuesOverrideRequest, ctx context.Context) (*chartConfig.PipelineStrategy, error) + //GetValuesOverrideForTrigger(overrideRequest *bean.ValuesOverrideRequest, triggeredAt time.Time, ctx context.Context) (*ValuesOverrideResponse, error) + //GetEnvOverrideByTriggerType(overrideRequest *bean.ValuesOverrideRequest, triggeredAt time.Time, ctx context.Context) (*chartConfig.EnvConfigOverride, error) + //GetAppMetricsByTriggerType(overrideRequest *bean.ValuesOverrideRequest, ctx context.Context) (bool, error) + //GetDeploymentStrategyByTriggerType(overrideRequest *bean.ValuesOverrideRequest, ctx context.Context) (*chartConfig.PipelineStrategy, error) CreateGitopsRepo(app *app.App, userId int32) (gitopsRepoName string, chartGitAttr *ChartGitAttribute, err error) GetDeployedManifestByPipelineIdAndCDWorkflowId(appId int, envId int, cdWorkflowId int, ctx context.Context) ([]byte, error) - SetPipelineFieldsInOverrideRequest(overrideRequest *bean.ValuesOverrideRequest, pipeline *pipelineConfig.Pipeline) + //SetPipelineFieldsInOverrideRequest(overrideRequest *bean.ValuesOverrideRequest, pipeline *pipelineConfig.Pipeline) + + BuildChartAndGetPath(appName string, envOverride *chartConfig.EnvConfigOverride, ctx context.Context) (string, error) } func NewAppService( @@ -339,66 +325,6 @@ const ( Failure = "FAILURE" ) -func (impl *AppServiceImpl) SetPipelineFieldsInOverrideRequest(overrideRequest *bean.ValuesOverrideRequest, pipeline *pipelineConfig.Pipeline) { - overrideRequest.PipelineId = pipeline.Id - overrideRequest.PipelineName = pipeline.Name - overrideRequest.EnvId = pipeline.EnvironmentId - overrideRequest.EnvName = pipeline.Environment.Name - overrideRequest.ClusterId = pipeline.Environment.ClusterId - overrideRequest.AppId = pipeline.AppId - overrideRequest.AppName = pipeline.App.AppName - overrideRequest.DeploymentAppType = pipeline.DeploymentAppType -} - -func (impl *AppServiceImpl) getValuesFileForEnv(environmentId int) string { - return fmt.Sprintf("_%d-values.yaml", environmentId) //-{envId}-values.yaml -} -func (impl *AppServiceImpl) createArgoApplicationIfRequired(appId int, envConfigOverride *chartConfig.EnvConfigOverride, pipeline *pipelineConfig.Pipeline, userId int32) (string, error) { - //repo has been registered while helm create - chart, err := impl.chartRepository.FindLatestChartForAppByAppId(appId) - if err != nil { - impl.logger.Errorw("no chart found ", "app", appId) - return "", err - } - envModel, err := impl.envRepository.FindById(envConfigOverride.TargetEnvironment) - if err != nil { - return "", err - } - argoAppName := pipeline.DeploymentAppName - if pipeline.DeploymentAppCreated { - return argoAppName, nil - } else { - //create - appNamespace := envConfigOverride.Namespace - if appNamespace == "" { - appNamespace = "default" - } - namespace := argocdServer.DevtronInstalationNs - appRequest := &argocdServer.AppTemplate{ - ApplicationName: argoAppName, - Namespace: namespace, - TargetNamespace: appNamespace, - TargetServer: envModel.Cluster.ServerUrl, - Project: "default", - ValuesFile: impl.getValuesFileForEnv(envModel.Id), - RepoPath: chart.ChartLocation, - RepoUrl: chart.GitRepoUrl, - } - - argoAppName, err := impl.ArgoK8sClient.CreateAcdApp(appRequest, envModel.Cluster) - if err != nil { - return "", err - } - //update cd pipeline to mark deployment app created - _, err = impl.updatePipeline(pipeline, userId) - if err != nil { - impl.logger.Errorw("error in update cd pipeline for deployment app created or not", "err", err) - return "", err - } - return argoAppName, nil - } -} - func (impl *AppServiceImpl) UpdateReleaseStatus(updateStatusRequest *bean.ReleaseStatusUpdateRequest) (bool, error) { count, err := impl.pipelineOverrideRepository.UpdateStatusByRequestIdentifier(updateStatusRequest.RequestId, updateStatusRequest.NewStatus) if err != nil { @@ -1017,84 +943,6 @@ type ValuesOverrideResponse struct { AppMetrics bool } -type EnvironmentOverride struct { - Enabled bool `json:"enabled"` - EnvValues []*KeyValue `json:"envValues"` -} - -type KeyValue struct { - Key string `json:"key"` - Value string `json:"value"` -} - -func (conf *EnvironmentOverride) appendEnvironmentVariable(key, value string) { - item := &KeyValue{Key: key, Value: value} - conf.EnvValues = append(conf.EnvValues, item) -} - -func (impl *AppServiceImpl) TriggerCD(artifact *repository.CiArtifact, cdWorkflowId, wfrId int, pipeline *pipelineConfig.Pipeline, triggeredAt time.Time) error { - impl.logger.Debugw("automatic pipeline trigger attempt async", "artifactId", artifact.Id) - - return impl.triggerReleaseAsync(artifact, cdWorkflowId, wfrId, pipeline, triggeredAt) -} - -func (impl *AppServiceImpl) triggerReleaseAsync(artifact *repository.CiArtifact, cdWorkflowId, wfrId int, pipeline *pipelineConfig.Pipeline, triggeredAt time.Time) error { - err := impl.validateAndTrigger(pipeline, artifact, cdWorkflowId, wfrId, triggeredAt) - if err != nil { - impl.logger.Errorw("error in trigger for pipeline", "pipelineId", strconv.Itoa(pipeline.Id)) - } - impl.logger.Debugw("trigger attempted for all pipeline ", "artifactId", artifact.Id) - return err -} - -func (impl *AppServiceImpl) validateAndTrigger(p *pipelineConfig.Pipeline, artifact *repository.CiArtifact, cdWorkflowId, wfrId int, triggeredAt time.Time) error { - object := impl.enforcerUtil.GetAppRBACNameByAppId(p.AppId) - envApp := strings.Split(object, "/") - if len(envApp) != 2 { - impl.logger.Error("invalid req, app and env not found from rbac") - return errors.New("invalid req, app and env not found from rbac") - } - err := impl.releasePipeline(p, artifact, cdWorkflowId, wfrId, triggeredAt) - return err -} - -func (impl *AppServiceImpl) releasePipeline(pipeline *pipelineConfig.Pipeline, artifact *repository.CiArtifact, cdWorkflowId, wfrId int, triggeredAt time.Time) error { - impl.logger.Debugw("triggering release for ", "cdPipelineId", pipeline.Id, "artifactId", artifact.Id) - - pipeline, err := impl.pipelineRepository.FindById(pipeline.Id) - if err != nil { - impl.logger.Errorw("error in fetching pipeline by pipelineId", "err", err) - return err - } - - request := &bean.ValuesOverrideRequest{ - PipelineId: pipeline.Id, - UserId: artifact.CreatedBy, - CiArtifactId: artifact.Id, - AppId: pipeline.AppId, - CdWorkflowId: cdWorkflowId, - ForceTrigger: true, - DeploymentWithConfig: bean.DEPLOYMENT_CONFIG_TYPE_LAST_SAVED, - WfrId: wfrId, - } - impl.SetPipelineFieldsInOverrideRequest(request, pipeline) - - ctx, err := impl.buildACDContext() - if err != nil { - impl.logger.Errorw("error in creating acd synch context", "pipelineId", pipeline.Id, "artifactId", artifact.Id, "err", err) - return err - } - //setting deployedBy as 1(system user) since case of auto trigger - id, _, err := impl.TriggerRelease(request, ctx, triggeredAt, 1) - if err != nil { - impl.logger.Errorw("error in auto cd pipeline trigger", "pipelineId", pipeline.Id, "artifactId", artifact.Id, "err", err) - } else { - impl.logger.Infow("pipeline successfully triggered ", "cdPipelineId", pipeline.Id, "artifactId", artifact.Id, "releaseId", id) - } - return err - -} - func (impl *AppServiceImpl) buildACDContext() (acdContext context.Context, err error) { //this method should only call in case of argo-integration and gitops configured acdToken, err := impl.argoUserService.GetLatestDevtronArgoCdUserToken() @@ -1107,525 +955,6 @@ func (impl *AppServiceImpl) buildACDContext() (acdContext context.Context, err e return ctx, nil } -func (impl *AppServiceImpl) getDbMigrationOverride(overrideRequest *bean.ValuesOverrideRequest, artifact *repository.CiArtifact, isRollback bool) (overrideJson []byte, err error) { - if isRollback { - return nil, fmt.Errorf("rollback not supported ye") - } - notConfigured := false - config, err := impl.dbMigrationConfigRepository.FindByPipelineId(overrideRequest.PipelineId) - if err != nil && !IsErrNoRows(err) { - impl.logger.Errorw("error in fetching pipeline override config", "req", overrideRequest, "err", err) - return nil, err - } else if IsErrNoRows(err) { - notConfigured = true - } - envVal := &EnvironmentOverride{} - if notConfigured { - impl.logger.Warnw("no active db migration found", "pipeline", overrideRequest.PipelineId) - envVal.Enabled = false - } else { - materialInfos, err := artifact.ParseMaterialInfo() - if err != nil { - return nil, err - } - - hash, ok := materialInfos[config.GitMaterial.Url] - if !ok { - impl.logger.Errorf("wrong url map ", "map", materialInfos, "url", config.GitMaterial.Url) - return nil, fmt.Errorf("configured url not found in material %s", config.GitMaterial.Url) - } - - envVal.Enabled = true - if config.GitMaterial.GitProvider.AuthMode != repository.AUTH_MODE_USERNAME_PASSWORD && - config.GitMaterial.GitProvider.AuthMode != repository.AUTH_MODE_ACCESS_TOKEN && - config.GitMaterial.GitProvider.AuthMode != repository.AUTH_MODE_ANONYMOUS { - return nil, fmt.Errorf("auth mode %s not supported for migration", config.GitMaterial.GitProvider.AuthMode) - } - envVal.appendEnvironmentVariable("GIT_REPO_URL", config.GitMaterial.Url) - envVal.appendEnvironmentVariable("GIT_USER", config.GitMaterial.GitProvider.UserName) - var password string - if config.GitMaterial.GitProvider.AuthMode == repository.AUTH_MODE_USERNAME_PASSWORD { - password = config.GitMaterial.GitProvider.Password - } else { - password = config.GitMaterial.GitProvider.AccessToken - } - envVal.appendEnvironmentVariable("GIT_AUTH_TOKEN", password) - // parse git-tag not required - //envVal.appendEnvironmentVariable("GIT_TAG", "") - envVal.appendEnvironmentVariable("GIT_HASH", hash) - envVal.appendEnvironmentVariable("SCRIPT_LOCATION", config.ScriptSource) - envVal.appendEnvironmentVariable("DB_TYPE", string(config.DbConfig.Type)) - envVal.appendEnvironmentVariable("DB_USER_NAME", config.DbConfig.UserName) - envVal.appendEnvironmentVariable("DB_PASSWORD", config.DbConfig.Password) - envVal.appendEnvironmentVariable("DB_HOST", config.DbConfig.Host) - envVal.appendEnvironmentVariable("DB_PORT", config.DbConfig.Port) - envVal.appendEnvironmentVariable("DB_NAME", config.DbConfig.DbName) - //Will be used for rollback don't delete it - //envVal.appendEnvironmentVariable("MIGRATE_TO_VERSION", strconv.Itoa(overrideRequest.TargetDbVersion)) - } - dbMigrationConfig := map[string]interface{}{"dbMigrationConfig": envVal} - confByte, err := json.Marshal(dbMigrationConfig) - if err != nil { - return nil, err - } - return confByte, nil -} - -func (impl *AppServiceImpl) GetAppMetricsByTriggerType(overrideRequest *bean.ValuesOverrideRequest, ctx context.Context) (bool, error) { - - var appMetrics bool - if overrideRequest.DeploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_SPECIFIC_TRIGGER { - _, span := otel.Tracer("orchestrator").Start(ctx, "deploymentTemplateHistoryRepository.GetHistoryByPipelineIdAndWfrId") - deploymentTemplateHistory, err := impl.deploymentTemplateHistoryRepository.GetHistoryByPipelineIdAndWfrId(overrideRequest.PipelineId, overrideRequest.WfrIdForDeploymentWithSpecificTrigger) - span.End() - if err != nil { - impl.logger.Errorw("error in getting deployed deployment template history by pipelineId and wfrId", "err", err, "pipelineId", &overrideRequest, "wfrId", overrideRequest.WfrIdForDeploymentWithSpecificTrigger) - return appMetrics, err - } - appMetrics = deploymentTemplateHistory.IsAppMetricsEnabled - - } else if overrideRequest.DeploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_LAST_SAVED { - _, span := otel.Tracer("orchestrator").Start(ctx, "appLevelMetricsRepository.FindByAppId") - appLevelMetrics, err := impl.appLevelMetricsRepository.FindByAppId(overrideRequest.AppId) - span.End() - if err != nil && !IsErrNoRows(err) { - impl.logger.Errorw("err", err) - return appMetrics, &ApiError{InternalMessage: "unable to fetch app level metrics flag"} - } - appMetrics = appLevelMetrics.AppMetrics - - _, span = otel.Tracer("orchestrator").Start(ctx, "envLevelMetricsRepository.FindByAppIdAndEnvId") - envLevelMetrics, err := impl.envLevelMetricsRepository.FindByAppIdAndEnvId(overrideRequest.AppId, overrideRequest.EnvId) - span.End() - if err != nil && !IsErrNoRows(err) { - impl.logger.Errorw("err", err) - return appMetrics, &ApiError{InternalMessage: "unable to fetch env level metrics flag"} - } - if envLevelMetrics.Id != 0 && envLevelMetrics.AppMetrics != nil { - appMetrics = *envLevelMetrics.AppMetrics - } - } - return appMetrics, nil -} - -func (impl *AppServiceImpl) GetDeploymentStrategyByTriggerType(overrideRequest *bean.ValuesOverrideRequest, ctx context.Context) (*chartConfig.PipelineStrategy, error) { - - strategy := &chartConfig.PipelineStrategy{} - var err error - if overrideRequest.DeploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_SPECIFIC_TRIGGER { - _, span := otel.Tracer("orchestrator").Start(ctx, "strategyHistoryRepository.GetHistoryByPipelineIdAndWfrId") - strategyHistory, err := impl.strategyHistoryRepository.GetHistoryByPipelineIdAndWfrId(overrideRequest.PipelineId, overrideRequest.WfrIdForDeploymentWithSpecificTrigger) - span.End() - if err != nil { - impl.logger.Errorw("error in getting deployed strategy history by pipleinId and wfrId", "err", err, "pipelineId", overrideRequest.PipelineId, "wfrId", overrideRequest.WfrIdForDeploymentWithSpecificTrigger) - return nil, err - } - strategy.Strategy = strategyHistory.Strategy - strategy.Config = strategyHistory.Config - strategy.PipelineId = overrideRequest.PipelineId - } else if overrideRequest.DeploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_LAST_SAVED { - if overrideRequest.ForceTrigger { - _, span := otel.Tracer("orchestrator").Start(ctx, "pipelineConfigRepository.GetDefaultStrategyByPipelineId") - strategy, err = impl.pipelineConfigRepository.GetDefaultStrategyByPipelineId(overrideRequest.PipelineId) - span.End() - } else { - var deploymentTemplate chartRepoRepository.DeploymentStrategy - if overrideRequest.DeploymentTemplate == "ROLLING" { - deploymentTemplate = chartRepoRepository.DEPLOYMENT_STRATEGY_ROLLING - } else if overrideRequest.DeploymentTemplate == "BLUE-GREEN" { - deploymentTemplate = chartRepoRepository.DEPLOYMENT_STRATEGY_BLUE_GREEN - } else if overrideRequest.DeploymentTemplate == "CANARY" { - deploymentTemplate = chartRepoRepository.DEPLOYMENT_STRATEGY_CANARY - } else if overrideRequest.DeploymentTemplate == "RECREATE" { - deploymentTemplate = chartRepoRepository.DEPLOYMENT_STRATEGY_RECREATE - } - - if len(deploymentTemplate) > 0 { - _, span := otel.Tracer("orchestrator").Start(ctx, "pipelineConfigRepository.FindByStrategyAndPipelineId") - strategy, err = impl.pipelineConfigRepository.FindByStrategyAndPipelineId(deploymentTemplate, overrideRequest.PipelineId) - span.End() - } else { - _, span := otel.Tracer("orchestrator").Start(ctx, "pipelineConfigRepository.GetDefaultStrategyByPipelineId") - strategy, err = impl.pipelineConfigRepository.GetDefaultStrategyByPipelineId(overrideRequest.PipelineId) - span.End() - } - } - if err != nil && errors2.IsNotFound(err) == false { - impl.logger.Errorf("invalid state", "err", err, "req", strategy) - return nil, err - } - } - return strategy, nil -} - -func (impl *AppServiceImpl) GetEnvOverrideByTriggerType(overrideRequest *bean.ValuesOverrideRequest, triggeredAt time.Time, ctx context.Context) (*chartConfig.EnvConfigOverride, error) { - - envOverride := &chartConfig.EnvConfigOverride{} - - var err error - if overrideRequest.DeploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_SPECIFIC_TRIGGER { - _, span := otel.Tracer("orchestrator").Start(ctx, "deploymentTemplateHistoryRepository.GetHistoryByPipelineIdAndWfrId") - deploymentTemplateHistory, err := impl.deploymentTemplateHistoryRepository.GetHistoryByPipelineIdAndWfrId(overrideRequest.PipelineId, overrideRequest.WfrIdForDeploymentWithSpecificTrigger) - //VARIABLE_SNAPSHOT_GET and resolve - - span.End() - if err != nil { - impl.logger.Errorw("error in getting deployed deployment template history by pipelineId and wfrId", "err", err, "pipelineId", &overrideRequest, "wfrId", overrideRequest.WfrIdForDeploymentWithSpecificTrigger) - return nil, err - } - templateName := deploymentTemplateHistory.TemplateName - templateVersion := deploymentTemplateHistory.TemplateVersion - if templateName == "Rollout Deployment" { - templateName = "" - } - //getting chart_ref by id - _, span = otel.Tracer("orchestrator").Start(ctx, "chartRefRepository.FindByVersionAndName") - chartRef, err := impl.chartRefRepository.FindByVersionAndName(templateName, templateVersion) - span.End() - if err != nil { - impl.logger.Errorw("error in getting chartRef by version and name", "err", err, "version", templateVersion, "name", templateName) - return nil, err - } - //assuming that if a chartVersion is deployed then it's envConfigOverride will be available - _, span = otel.Tracer("orchestrator").Start(ctx, "environmentConfigRepository.GetByAppIdEnvIdAndChartRefId") - envOverride, err = impl.environmentConfigRepository.GetByAppIdEnvIdAndChartRefId(overrideRequest.AppId, overrideRequest.EnvId, chartRef.Id) - span.End() - if err != nil { - impl.logger.Errorw("error in getting envConfigOverride for pipeline for specific chartVersion", "err", err, "appId", overrideRequest.AppId, "envId", overrideRequest.EnvId, "chartRefId", chartRef.Id) - return nil, err - } - - _, span = otel.Tracer("orchestrator").Start(ctx, "envRepository.FindById") - env, err := impl.envRepository.FindById(envOverride.TargetEnvironment) - span.End() - if err != nil { - impl.logger.Errorw("unable to find env", "err", err) - return nil, err - } - envOverride.Environment = env - - //updating historical data in envConfigOverride and appMetrics flag - envOverride.IsOverride = true - envOverride.EnvOverrideValues = deploymentTemplateHistory.Template - - resolvedTemplate, variableMap, err := impl.getResolvedTemplateWithSnapshot(deploymentTemplateHistory.Id, envOverride.EnvOverrideValues) - envOverride.ResolvedEnvOverrideValues = resolvedTemplate - envOverride.VariableSnapshot = variableMap - if err != nil { - return envOverride, err - } - } else if overrideRequest.DeploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_LAST_SAVED { - _, span := otel.Tracer("orchestrator").Start(ctx, "environmentConfigRepository.ActiveEnvConfigOverride") - envOverride, err = impl.environmentConfigRepository.ActiveEnvConfigOverride(overrideRequest.AppId, overrideRequest.EnvId) - - var chart *chartRepoRepository.Chart - span.End() - if err != nil { - impl.logger.Errorw("invalid state", "err", err, "req", overrideRequest) - return nil, err - } - if envOverride.Id == 0 { - _, span = otel.Tracer("orchestrator").Start(ctx, "chartRepository.FindLatestChartForAppByAppId") - chart, err = impl.chartRepository.FindLatestChartForAppByAppId(overrideRequest.AppId) - span.End() - if err != nil { - impl.logger.Errorw("invalid state", "err", err, "req", overrideRequest) - return nil, err - } - _, span = otel.Tracer("orchestrator").Start(ctx, "environmentConfigRepository.FindChartByAppIdAndEnvIdAndChartRefId") - envOverride, err = impl.environmentConfigRepository.FindChartByAppIdAndEnvIdAndChartRefId(overrideRequest.AppId, overrideRequest.EnvId, chart.ChartRefId) - span.End() - if err != nil && !errors2.IsNotFound(err) { - impl.logger.Errorw("invalid state", "err", err, "req", overrideRequest) - return nil, err - } - - //creating new env override config - if errors2.IsNotFound(err) || envOverride == nil { - _, span = otel.Tracer("orchestrator").Start(ctx, "envRepository.FindById") - environment, err := impl.envRepository.FindById(overrideRequest.EnvId) - span.End() - if err != nil && !IsErrNoRows(err) { - return nil, err - } - envOverride = &chartConfig.EnvConfigOverride{ - Active: true, - ManualReviewed: true, - Status: models.CHARTSTATUS_SUCCESS, - TargetEnvironment: overrideRequest.EnvId, - ChartId: chart.Id, - AuditLog: sql.AuditLog{UpdatedBy: overrideRequest.UserId, UpdatedOn: triggeredAt, CreatedOn: triggeredAt, CreatedBy: overrideRequest.UserId}, - Namespace: environment.Namespace, - IsOverride: false, - EnvOverrideValues: "{}", - Latest: false, - IsBasicViewLocked: chart.IsBasicViewLocked, - CurrentViewEditor: chart.CurrentViewEditor, - } - _, span = otel.Tracer("orchestrator").Start(ctx, "environmentConfigRepository.Save") - err = impl.environmentConfigRepository.Save(envOverride) - span.End() - if err != nil { - impl.logger.Errorw("error in creating envconfig", "data", envOverride, "error", err) - return nil, err - } - } - envOverride.Chart = chart - } else if envOverride.Id > 0 && !envOverride.IsOverride { - _, span = otel.Tracer("orchestrator").Start(ctx, "chartRepository.FindLatestChartForAppByAppId") - chart, err = impl.chartRepository.FindLatestChartForAppByAppId(overrideRequest.AppId) - span.End() - if err != nil { - impl.logger.Errorw("invalid state", "err", err, "req", overrideRequest) - return nil, err - } - envOverride.Chart = chart - } - - _, span = otel.Tracer("orchestrator").Start(ctx, "envRepository.FindById") - env, err := impl.envRepository.FindById(envOverride.TargetEnvironment) - span.End() - if err != nil { - impl.logger.Errorw("unable to find env", "err", err) - return nil, err - } - envOverride.Environment = env - - //VARIABLE different cases for variable resolution - scope := resourceQualifiers.Scope{ - AppId: overrideRequest.AppId, - EnvId: overrideRequest.EnvId, - ClusterId: overrideRequest.ClusterId, - SystemMetadata: &resourceQualifiers.SystemMetadata{ - EnvironmentName: env.Name, - ClusterName: env.Cluster.ClusterName, - Namespace: env.Namespace, - AppName: overrideRequest.AppName, - Image: overrideRequest.Image, - ImageTag: util3.GetImageTagFromImage(overrideRequest.Image), - }, - } - - if envOverride.IsOverride { - - resolvedTemplate, variableMap, err := impl.extractVariablesAndResolveTemplate(scope, envOverride.EnvOverrideValues, repository6.Entity{ - EntityType: repository6.EntityTypeDeploymentTemplateEnvLevel, - EntityId: envOverride.Id, - }) - envOverride.ResolvedEnvOverrideValues = resolvedTemplate - envOverride.VariableSnapshot = variableMap - if err != nil { - return envOverride, err - } - - } else { - resolvedTemplate, variableMap, err := impl.extractVariablesAndResolveTemplate(scope, chart.GlobalOverride, repository6.Entity{ - EntityType: repository6.EntityTypeDeploymentTemplateAppLevel, - EntityId: chart.Id, - }) - envOverride.Chart.ResolvedGlobalOverride = resolvedTemplate - envOverride.VariableSnapshot = variableMap - if err != nil { - return envOverride, err - } - - } - } - - return envOverride, nil -} - -func (impl *AppServiceImpl) getResolvedTemplateWithSnapshot(deploymentTemplateHistoryId int, template string) (string, map[string]string, error) { - - variableSnapshotMap := make(map[string]string) - reference := repository6.HistoryReference{ - HistoryReferenceId: deploymentTemplateHistoryId, - HistoryReferenceType: repository6.HistoryReferenceTypeDeploymentTemplate, - } - variableSnapshot, err := impl.variableSnapshotHistoryService.GetVariableHistoryForReferences([]repository6.HistoryReference{reference}) - if err != nil { - return template, variableSnapshotMap, err - } - - if _, ok := variableSnapshot[reference]; !ok { - return template, variableSnapshotMap, nil - } - - err = json.Unmarshal(variableSnapshot[reference].VariableSnapshot, &variableSnapshotMap) - if err != nil { - return template, variableSnapshotMap, err - } - - if len(variableSnapshotMap) == 0 { - return template, variableSnapshotMap, nil - } - scopedVariableData := parsers.GetScopedVarData(variableSnapshotMap, make(map[string]bool), true) - request := parsers.VariableParserRequest{Template: template, TemplateType: parsers.JsonVariableTemplate, Variables: scopedVariableData} - parserResponse := impl.variableTemplateParser.ParseTemplate(request) - err = parserResponse.Error - if err != nil { - return template, variableSnapshotMap, err - } - resolvedTemplate := parserResponse.ResolvedTemplate - return resolvedTemplate, variableSnapshotMap, nil -} - -func (impl *AppServiceImpl) extractVariablesAndResolveTemplate(scope resourceQualifiers.Scope, template string, entity repository6.Entity) (string, map[string]string, error) { - - variableMap := make(map[string]string) - entityToVariables, err := impl.variableEntityMappingService.GetAllMappingsForEntities([]repository6.Entity{entity}) - if err != nil { - return template, variableMap, err - } - - if vars, ok := entityToVariables[entity]; !ok || len(vars) == 0 { - return template, variableMap, nil - } - - // pre-populating variable map with variable so that the variables which don't have any resolved data - // is saved in snapshot - for _, variable := range entityToVariables[entity] { - variableMap[variable] = impl.scopedVariableService.GetFormattedVariableForName(variable) - } - - scopedVariables, err := impl.scopedVariableService.GetScopedVariables(scope, entityToVariables[entity], true) - if err != nil { - return template, variableMap, err - } - - for _, variable := range scopedVariables { - variableMap[variable.VariableName] = variable.VariableValue.StringValue() - } - - parserRequest := parsers.VariableParserRequest{Template: template, Variables: scopedVariables, TemplateType: parsers.JsonVariableTemplate} - parserResponse := impl.variableTemplateParser.ParseTemplate(parserRequest) - err = parserResponse.Error - if err != nil { - return template, variableMap, err - } - - resolvedTemplate := parserResponse.ResolvedTemplate - return resolvedTemplate, variableMap, nil -} - -func (impl *AppServiceImpl) GetValuesOverrideForTrigger(overrideRequest *bean.ValuesOverrideRequest, triggeredAt time.Time, ctx context.Context) (*ValuesOverrideResponse, error) { - if overrideRequest.DeploymentType == models.DEPLOYMENTTYPE_UNKNOWN { - overrideRequest.DeploymentType = models.DEPLOYMENTTYPE_DEPLOY - } - if len(overrideRequest.DeploymentWithConfig) == 0 { - overrideRequest.DeploymentWithConfig = bean.DEPLOYMENT_CONFIG_TYPE_LAST_SAVED - } - valuesOverrideResponse := &ValuesOverrideResponse{} - - pipeline, err := impl.pipelineRepository.FindById(overrideRequest.PipelineId) - valuesOverrideResponse.Pipeline = pipeline - if err != nil { - impl.logger.Errorw("error in fetching pipeline by pipeline id", "err", err, "pipeline-id-", overrideRequest.PipelineId) - return valuesOverrideResponse, err - } - - _, span := otel.Tracer("orchestrator").Start(ctx, "ciArtifactRepository.Get") - artifact, err := impl.ciArtifactRepository.Get(overrideRequest.CiArtifactId) - valuesOverrideResponse.Artifact = artifact - span.End() - if err != nil { - return valuesOverrideResponse, err - } - overrideRequest.Image = artifact.Image - - strategy, err := impl.GetDeploymentStrategyByTriggerType(overrideRequest, ctx) - valuesOverrideResponse.PipelineStrategy = strategy - if err != nil { - impl.logger.Errorw("error in getting strategy by trigger type", "err", err) - return valuesOverrideResponse, err - } - - envOverride, err := impl.GetEnvOverrideByTriggerType(overrideRequest, triggeredAt, ctx) - valuesOverrideResponse.EnvOverride = envOverride - if err != nil { - impl.logger.Errorw("error in getting env override by trigger type", "err", err) - return valuesOverrideResponse, err - } - appMetrics, err := impl.GetAppMetricsByTriggerType(overrideRequest, ctx) - valuesOverrideResponse.AppMetrics = appMetrics - if err != nil { - impl.logger.Errorw("error in getting app metrics by trigger type", "err", err) - return valuesOverrideResponse, err - } - - _, span = otel.Tracer("orchestrator").Start(ctx, "getDbMigrationOverride") - //FIXME: how to determine rollback - //we can't depend on ciArtifact ID because CI pipeline can be manually triggered in any order regardless of sourcecode status - dbMigrationOverride, err := impl.getDbMigrationOverride(overrideRequest, artifact, false) - span.End() - if err != nil { - impl.logger.Errorw("error in fetching db migration config", "req", overrideRequest, "err", err) - return valuesOverrideResponse, err - } - chartVersion := envOverride.Chart.ChartVersion - _, span = otel.Tracer("orchestrator").Start(ctx, "getConfigMapAndSecretJsonV2") - configMapJson, err := impl.getConfigMapAndSecretJsonV2(overrideRequest.AppId, envOverride.TargetEnvironment, overrideRequest.PipelineId, chartVersion, overrideRequest.DeploymentWithConfig, overrideRequest.WfrIdForDeploymentWithSpecificTrigger) - span.End() - if err != nil { - impl.logger.Errorw("error in fetching config map n secret ", "err", err) - configMapJson = nil - } - _, span = otel.Tracer("orchestrator").Start(ctx, "appCrudOperationService.GetLabelsByAppIdForDeployment") - appLabelJsonByte, err := impl.appCrudOperationService.GetLabelsByAppIdForDeployment(overrideRequest.AppId) - span.End() - if err != nil { - impl.logger.Errorw("error in fetching app labels for gitOps commit", "err", err) - appLabelJsonByte = nil - } - _, span = otel.Tracer("orchestrator").Start(ctx, "mergeAndSave") - pipelineOverride, err := impl.savePipelineOverride(overrideRequest, envOverride.Id, triggeredAt) - valuesOverrideResponse.PipelineOverride = pipelineOverride - if err != nil { - return valuesOverrideResponse, err - } - //TODO: check status and apply lock - releaseOverrideJson, err := impl.getReleaseOverride(envOverride, overrideRequest, artifact, pipelineOverride, strategy, &appMetrics) - valuesOverrideResponse.ReleaseOverrideJSON = releaseOverrideJson - if err != nil { - return valuesOverrideResponse, err - } - mergedValues, err := impl.mergeOverrideValues(envOverride, dbMigrationOverride, releaseOverrideJson, configMapJson, appLabelJsonByte, strategy) - - appName := fmt.Sprintf("%s-%s", overrideRequest.AppName, envOverride.Environment.Name) - mergedValues = impl.autoscalingCheckBeforeTrigger(ctx, appName, envOverride.Namespace, mergedValues, overrideRequest) - - _, span = otel.Tracer("orchestrator").Start(ctx, "dockerRegistryIpsConfigService.HandleImagePullSecretOnApplicationDeployment") - // handle image pull secret if access given - mergedValues, err = impl.dockerRegistryIpsConfigService.HandleImagePullSecretOnApplicationDeployment(envOverride.Environment, pipeline.CiPipelineId, mergedValues) - valuesOverrideResponse.MergedValues = string(mergedValues) - span.End() - if err != nil { - return valuesOverrideResponse, err - } - pipelineOverride.PipelineMergedValues = string(mergedValues) - err = impl.pipelineOverrideRepository.Update(pipelineOverride) - if err != nil { - return valuesOverrideResponse, err - } - return valuesOverrideResponse, err -} - -func (impl *AppServiceImpl) BuildManifestForTrigger(overrideRequest *bean.ValuesOverrideRequest, triggeredAt time.Time, ctx context.Context) (valuesOverrideResponse *ValuesOverrideResponse, builtChartPath string, err error) { - - valuesOverrideResponse = &ValuesOverrideResponse{} - valuesOverrideResponse, err = impl.GetValuesOverrideForTrigger(overrideRequest, triggeredAt, ctx) - if err != nil { - impl.logger.Errorw("error in fetching values for trigger", "err", err) - return valuesOverrideResponse, "", err - } - builtChartPath, err = impl.BuildChartAndGetPath(overrideRequest.AppName, valuesOverrideResponse.EnvOverride, ctx) - if err != nil { - impl.logger.Errorw("error in parsing reference chart", "err", err) - return valuesOverrideResponse, "", err - } - return valuesOverrideResponse, builtChartPath, err -} - func (impl *AppServiceImpl) GetDeployedManifestByPipelineIdAndCDWorkflowId(appId int, envId int, cdWorkflowId int, ctx context.Context) ([]byte, error) { manifestByteArray := make([]byte, 0) @@ -1727,244 +1056,27 @@ func (impl *AppServiceImpl) CreateGitopsRepo(app *app.App, userId int32) (gitops return gitOpsRepoName, chartGitAttr, nil } -func (impl *AppServiceImpl) DeployArgocdApp(overrideRequest *bean.ValuesOverrideRequest, valuesOverrideResponse *ValuesOverrideResponse, ctx context.Context) error { - - impl.logger.Debugw("new pipeline found", "pipeline", valuesOverrideResponse.Pipeline) - _, span := otel.Tracer("orchestrator").Start(ctx, "createArgoApplicationIfRequired") - name, err := impl.createArgoApplicationIfRequired(overrideRequest.AppId, valuesOverrideResponse.EnvOverride, valuesOverrideResponse.Pipeline, overrideRequest.UserId) +func (impl *AppServiceImpl) saveTimeline(overrideRequest *bean.ValuesOverrideRequest, status string, statusDetail string, ctx context.Context) { + // creating cd pipeline status timeline for git commit + timeline := &pipelineConfig.PipelineStatusTimeline{ + CdWorkflowRunnerId: overrideRequest.WfrId, + Status: status, + StatusDetail: statusDetail, + StatusTime: time.Now(), + AuditLog: sql.AuditLog{ + CreatedBy: overrideRequest.UserId, + CreatedOn: time.Now(), + UpdatedBy: overrideRequest.UserId, + UpdatedOn: time.Now(), + }, + } + _, span := otel.Tracer("orchestrator").Start(ctx, "cdPipelineStatusTimelineRepo.SaveTimeline") + timelineErr := impl.pipelineStatusTimelineService.SaveTimeline(timeline, nil, false) span.End() - if err != nil { - impl.logger.Errorw("acd application create error on cd trigger", "err", err, "req", overrideRequest) - return err + if timelineErr != nil { + impl.logger.Errorw("error in creating timeline status for git commit", "err", timelineErr, "timeline", timeline) } - impl.logger.Debugw("argocd application created", "name", name) - - _, span = otel.Tracer("orchestrator").Start(ctx, "updateArgoPipeline") - updateAppInArgocd, err := impl.updateArgoPipeline(overrideRequest.AppId, valuesOverrideResponse.Pipeline.Name, valuesOverrideResponse.EnvOverride, ctx) - span.End() - if err != nil { - impl.logger.Errorw("error in updating argocd app ", "err", err) - return err - } - if updateAppInArgocd { - impl.logger.Debug("argo-cd successfully updated") - } else { - impl.logger.Debug("argo-cd failed to update, ignoring it") - } - return nil -} - -func (impl *AppServiceImpl) DeployApp(overrideRequest *bean.ValuesOverrideRequest, valuesOverrideResponse *ValuesOverrideResponse, triggeredAt time.Time, ctx context.Context) error { - - if IsAcdApp(overrideRequest.DeploymentAppType) { - _, span := otel.Tracer("orchestrator").Start(ctx, "DeployArgocdApp") - err := impl.DeployArgocdApp(overrideRequest, valuesOverrideResponse, ctx) - span.End() - if err != nil { - impl.logger.Errorw("error in deploying app on argocd", "err", err) - return err - } - } else if IsHelmApp(overrideRequest.DeploymentAppType) { - _, span := otel.Tracer("orchestrator").Start(ctx, "createHelmAppForCdPipeline") - _, err := impl.createHelmAppForCdPipeline(overrideRequest, valuesOverrideResponse, triggeredAt, ctx) - span.End() - if err != nil { - impl.logger.Errorw("error in creating or updating helm application for cd pipeline", "err", err) - return err - } - } - return nil -} - -func (impl *AppServiceImpl) ValidateTriggerEvent(triggerEvent bean.TriggerEvent) (bool, error) { - - switch triggerEvent.DeploymentAppType { - case bean2.ArgoCd: - if !triggerEvent.PerformChartPush { - return false, errors2.New("For deployment type ArgoCd, PerformChartPush flag expected value = true, got false") - } - case bean2.Helm: - return true, nil - case bean2.GitOpsWithoutDeployment: - if triggerEvent.PerformDeploymentOnCluster { - return false, errors2.New("For deployment type GitOpsWithoutDeployment, PerformDeploymentOnCluster flag expected value = false, got value = true") - } - case bean2.ManifestDownload: - if triggerEvent.PerformChartPush { - return false, error2.New("For deployment type ManifestDownload, PerformChartPush flag expected value = false, got true") - } - if triggerEvent.PerformDeploymentOnCluster { - return false, error2.New("For deployment type ManifestDownload, PerformDeploymentOnCluster flag expected value = false, got true") - } - } - return true, nil - -} - -// write integration/unit test for each function -func (impl *AppServiceImpl) TriggerPipeline(overrideRequest *bean.ValuesOverrideRequest, triggerEvent bean.TriggerEvent, ctx context.Context) (releaseNo int, manifest []byte, err error) { - - isRequestValid, err := impl.ValidateTriggerEvent(triggerEvent) - if !isRequestValid { - return releaseNo, manifest, err - } - - valuesOverrideResponse, builtChartPath, err := impl.BuildManifestForTrigger(overrideRequest, triggerEvent.TriggerdAt, ctx) - _, span := otel.Tracer("orchestrator").Start(ctx, "CreateHistoriesForDeploymentTrigger") - err1 := impl.CreateHistoriesForDeploymentTrigger(valuesOverrideResponse.Pipeline, valuesOverrideResponse.PipelineStrategy, valuesOverrideResponse.EnvOverride, triggerEvent.TriggerdAt, triggerEvent.TriggeredBy) - if err1 != nil { - impl.logger.Errorw("error in saving histories for trigger", "err", err1, "pipelineId", valuesOverrideResponse.Pipeline.Id, "wfrId", overrideRequest.WfrId) - } - span.End() - if err != nil { - return releaseNo, manifest, err - } - - if triggerEvent.PerformChartPush { - manifestPushTemplate, err := impl.BuildManifestPushTemplate(overrideRequest, valuesOverrideResponse, builtChartPath, &manifest) - if err != nil { - impl.logger.Errorw("error in building manifest push template", "err", err) - return releaseNo, manifest, err - } - manifestPushService := impl.GetManifestPushService(triggerEvent) - manifestPushResponse := manifestPushService.PushChart(manifestPushTemplate, ctx) - if manifestPushResponse.Error != nil { - impl.logger.Errorw("Error in pushing manifest to git", "err", err, "git_repo_url", manifestPushTemplate.RepoUrl) - return releaseNo, manifest, err - } - pipelineOverrideUpdateRequest := &chartConfig.PipelineOverride{ - Id: valuesOverrideResponse.PipelineOverride.Id, - GitHash: manifestPushResponse.CommitHash, - CommitTime: manifestPushResponse.CommitTime, - EnvConfigOverrideId: valuesOverrideResponse.EnvOverride.Id, - PipelineOverrideValues: valuesOverrideResponse.ReleaseOverrideJSON, - PipelineId: overrideRequest.PipelineId, - CiArtifactId: overrideRequest.CiArtifactId, - PipelineMergedValues: valuesOverrideResponse.MergedValues, - AuditLog: sql.AuditLog{UpdatedOn: triggerEvent.TriggerdAt, UpdatedBy: overrideRequest.UserId}, - } - _, span := otel.Tracer("orchestrator").Start(ctx, "pipelineOverrideRepository.Update") - err = impl.pipelineOverrideRepository.Update(pipelineOverrideUpdateRequest) - span.End() - } - - if triggerEvent.PerformDeploymentOnCluster { - err = impl.DeployApp(overrideRequest, valuesOverrideResponse, triggerEvent.TriggerdAt, ctx) - if err != nil { - impl.logger.Errorw("error in deploying app", "err", err) - return releaseNo, manifest, err - } - } - - go impl.WriteCDTriggerEvent(overrideRequest, valuesOverrideResponse.Artifact, valuesOverrideResponse.PipelineOverride.PipelineReleaseCounter, valuesOverrideResponse.PipelineOverride.Id) - - _, spann := otel.Tracer("orchestrator").Start(ctx, "MarkImageScanDeployed") - _ = impl.MarkImageScanDeployed(overrideRequest.AppId, valuesOverrideResponse.EnvOverride.TargetEnvironment, valuesOverrideResponse.Artifact.ImageDigest, overrideRequest.ClusterId, valuesOverrideResponse.Artifact.ScanEnabled) - spann.End() - - middleware.CdTriggerCounter.WithLabelValues(overrideRequest.AppName, overrideRequest.EnvName).Inc() - - return valuesOverrideResponse.PipelineOverride.PipelineReleaseCounter, manifest, nil - -} - -func (impl *AppServiceImpl) GetTriggerEvent(deploymentAppType string, triggeredAt time.Time, deployedBy int32) bean.TriggerEvent { - // trigger event will decide whether to perform GitOps or deployment for a particular deployment app type - triggerEvent := bean.TriggerEvent{ - TriggeredBy: deployedBy, - TriggerdAt: triggeredAt, - } - switch deploymentAppType { - case bean2.ArgoCd: - triggerEvent.PerformChartPush = true - triggerEvent.PerformDeploymentOnCluster = true - triggerEvent.GetManifestInResponse = false - triggerEvent.DeploymentAppType = bean2.ArgoCd - triggerEvent.ManifestStorageType = bean2.ManifestStorageGit - case bean2.Helm: - triggerEvent.PerformChartPush = false - triggerEvent.PerformDeploymentOnCluster = true - triggerEvent.GetManifestInResponse = false - triggerEvent.DeploymentAppType = bean2.Helm - } - return triggerEvent -} - -func (impl *AppServiceImpl) TriggerRelease(overrideRequest *bean.ValuesOverrideRequest, ctx context.Context, triggeredAt time.Time, deployedBy int32) (releaseNo int, manifest []byte, err error) { - triggerEvent := impl.GetTriggerEvent(overrideRequest.DeploymentAppType, triggeredAt, deployedBy) - releaseNo, manifest, err = impl.TriggerPipeline(overrideRequest, triggerEvent, ctx) - if err != nil { - return 0, manifest, err - } - return releaseNo, manifest, nil -} - -func (impl *AppServiceImpl) GetManifestPushService(triggerEvent bean.TriggerEvent) ManifestPushService { - var manifestPushService ManifestPushService - if triggerEvent.ManifestStorageType == bean2.ManifestStorageGit { - manifestPushService = impl.GitOpsManifestPushService - } - return manifestPushService -} - -func (impl *AppServiceImpl) BuildManifestPushTemplate(overrideRequest *bean.ValuesOverrideRequest, valuesOverrideResponse *ValuesOverrideResponse, builtChartPath string, manifest *[]byte) (*bean3.ManifestPushTemplate, error) { - - manifestPushTemplate := &bean3.ManifestPushTemplate{ - WorkflowRunnerId: overrideRequest.WfrId, - AppId: overrideRequest.AppId, - ChartRefId: valuesOverrideResponse.EnvOverride.Chart.ChartRefId, - EnvironmentId: valuesOverrideResponse.EnvOverride.Environment.Id, - UserId: overrideRequest.UserId, - PipelineOverrideId: valuesOverrideResponse.PipelineOverride.Id, - AppName: overrideRequest.AppName, - TargetEnvironmentName: valuesOverrideResponse.EnvOverride.TargetEnvironment, - BuiltChartPath: builtChartPath, - BuiltChartBytes: manifest, - MergedValues: valuesOverrideResponse.MergedValues, - } - - manifestPushConfig, err := impl.manifestPushConfigRepository.GetManifestPushConfigByAppIdAndEnvId(overrideRequest.AppId, overrideRequest.EnvId) - if err != nil && err != pg.ErrNoRows { - impl.logger.Errorw("error in fetching manifest push config from db", "err", err) - return manifestPushTemplate, err - } - - if manifestPushConfig != nil { - if manifestPushConfig.StorageType == bean2.ManifestStorageGit { - // need to implement for git repo push - // currently manifest push config doesn't have git push config. Gitops config is derived from charts, chart_env_config_override and chart_ref table - } - } else { - manifestPushTemplate.ChartReferenceTemplate = valuesOverrideResponse.EnvOverride.Chart.ReferenceTemplate - manifestPushTemplate.ChartName = valuesOverrideResponse.EnvOverride.Chart.ChartName - manifestPushTemplate.ChartVersion = valuesOverrideResponse.EnvOverride.Chart.ChartVersion - manifestPushTemplate.ChartLocation = valuesOverrideResponse.EnvOverride.Chart.ChartLocation - manifestPushTemplate.RepoUrl = valuesOverrideResponse.EnvOverride.Chart.GitRepoUrl - } - return manifestPushTemplate, err -} - -func (impl *AppServiceImpl) saveTimeline(overrideRequest *bean.ValuesOverrideRequest, status string, statusDetail string, ctx context.Context) { - // creating cd pipeline status timeline for git commit - timeline := &pipelineConfig.PipelineStatusTimeline{ - CdWorkflowRunnerId: overrideRequest.WfrId, - Status: status, - StatusDetail: statusDetail, - StatusTime: time.Now(), - AuditLog: sql.AuditLog{ - CreatedBy: overrideRequest.UserId, - CreatedOn: time.Now(), - UpdatedBy: overrideRequest.UserId, - UpdatedOn: time.Now(), - }, - } - _, span := otel.Tracer("orchestrator").Start(ctx, "cdPipelineStatusTimelineRepo.SaveTimeline") - timelineErr := impl.pipelineStatusTimelineService.SaveTimeline(timeline, nil, false) - span.End() - if timelineErr != nil { - impl.logger.Errorw("error in creating timeline status for git commit", "err", timelineErr, "timeline", timeline) - } -} +} func (impl *AppServiceImpl) autoHealChartLocationInChart(ctx context.Context, envOverride *chartConfig.EnvConfigOverride) error { chartId := envOverride.Chart.Id @@ -2009,65 +1121,6 @@ func (impl *AppServiceImpl) autoHealChartLocationInChart(ctx context.Context, en return nil } -func (impl *AppServiceImpl) MarkImageScanDeployed(appId int, envId int, imageDigest string, clusterId int, isScanEnabled bool) error { - impl.logger.Debugw("mark image scan deployed for normal app, from cd auto or manual trigger", "imageDigest", imageDigest) - executionHistory, err := impl.imageScanHistoryRepository.FindByImageDigest(imageDigest) - if err != nil && err != pg.ErrNoRows { - impl.logger.Errorw("error in fetching execution history", "err", err) - return err - } - if executionHistory == nil || executionHistory.Id == 0 { - impl.logger.Errorw("no execution history found for digest", "digest", imageDigest) - return fmt.Errorf("no execution history found for digest - %s", imageDigest) - } - impl.logger.Debugw("mark image scan deployed for normal app, from cd auto or manual trigger", "executionHistory", executionHistory) - var ids []int - ids = append(ids, executionHistory.Id) - - ot, err := impl.imageScanDeployInfoRepository.FetchByAppIdAndEnvId(appId, envId, []string{security.ScanObjectType_APP}) - - if err == pg.ErrNoRows && !isScanEnabled { - //ignoring if no rows are found and scan is disabled - return nil - } - - if err != nil && err != pg.ErrNoRows { - return err - } else if err == pg.ErrNoRows && isScanEnabled { - imageScanDeployInfo := &security.ImageScanDeployInfo{ - ImageScanExecutionHistoryId: ids, - ScanObjectMetaId: appId, - ObjectType: security.ScanObjectType_APP, - EnvId: envId, - ClusterId: clusterId, - AuditLog: sql.AuditLog{ - CreatedOn: time.Now(), - CreatedBy: 1, - UpdatedOn: time.Now(), - UpdatedBy: 1, - }, - } - impl.logger.Debugw("mark image scan deployed for normal app, from cd auto or manual trigger", "imageScanDeployInfo", imageScanDeployInfo) - err = impl.imageScanDeployInfoRepository.Save(imageScanDeployInfo) - if err != nil { - impl.logger.Errorw("error in creating deploy info", "err", err) - } - } else { - // Updating Execution history for Latest Deployment to fetch out security Vulnerabilities for latest deployed info - if isScanEnabled { - ot.ImageScanExecutionHistoryId = ids - } else { - arr := []int{-1} - ot.ImageScanExecutionHistoryId = arr - } - err = impl.imageScanDeployInfoRepository.Update(ot) - if err != nil { - impl.logger.Errorw("error in updating deploy info for latest deployed image", "err", err) - } - } - return err -} - // FIXME tmp workaround func (impl *AppServiceImpl) GetCmSecretNew(appId int, envId int, isJob bool) (*bean.ConfigMapJson, *bean.ConfigSecretJson, error) { var configMapJson string @@ -2192,99 +1245,6 @@ func (impl *AppServiceImpl) GetConfigMapAndSecretJson(appId int, envId int, pipe return merged, nil } -func (impl *AppServiceImpl) getConfigMapAndSecretJsonV2(appId int, envId int, pipelineId int, chartVersion string, deploymentWithConfig bean.DeploymentConfigurationType, wfrIdForDeploymentWithSpecificTrigger int) ([]byte, error) { - - var configMapJson string - var secretDataJson string - var configMapJsonApp string - var secretDataJsonApp string - var configMapJsonEnv string - var secretDataJsonEnv string - var err error - //var configMapJsonPipeline string - //var secretDataJsonPipeline string - - merged := []byte("{}") - if deploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_LAST_SAVED { - configMapA, err := impl.configMapRepository.GetByAppIdAppLevel(appId) - if err != nil && pg.ErrNoRows != err { - return []byte("{}"), err - } - if configMapA != nil && configMapA.Id > 0 { - configMapJsonApp = configMapA.ConfigMapData - secretDataJsonApp = configMapA.SecretData - } - configMapE, err := impl.configMapRepository.GetByAppIdAndEnvIdEnvLevel(appId, envId) - if err != nil && pg.ErrNoRows != err { - return []byte("{}"), err - } - if configMapE != nil && configMapE.Id > 0 { - configMapJsonEnv = configMapE.ConfigMapData - secretDataJsonEnv = configMapE.SecretData - } - } else if deploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_SPECIFIC_TRIGGER { - //fetching history and setting envLevelConfig and not appLevelConfig because history already contains merged appLevel and envLevel configs - configMapHistory, err := impl.configMapHistoryRepository.GetHistoryByPipelineIdAndWfrId(pipelineId, wfrIdForDeploymentWithSpecificTrigger, repository3.CONFIGMAP_TYPE) - if err != nil { - impl.logger.Errorw("error in getting config map history config by pipelineId and wfrId ", "err", err, "pipelineId", pipelineId, "wfrid", wfrIdForDeploymentWithSpecificTrigger) - return []byte("{}"), err - } - configMapJsonEnv = configMapHistory.Data - secretHistory, err := impl.configMapHistoryRepository.GetHistoryByPipelineIdAndWfrId(pipelineId, wfrIdForDeploymentWithSpecificTrigger, repository3.SECRET_TYPE) - if err != nil { - impl.logger.Errorw("error in getting config map history config by pipelineId and wfrId ", "err", err, "pipelineId", pipelineId, "wfrid", wfrIdForDeploymentWithSpecificTrigger) - return []byte("{}"), err - } - secretDataJsonEnv = secretHistory.Data - } - configMapJson, err = impl.mergeUtil.ConfigMapMerge(configMapJsonApp, configMapJsonEnv) - if err != nil { - return []byte("{}"), err - } - chartMajorVersion, chartMinorVersion, err := util2.ExtractChartVersion(chartVersion) - if err != nil { - impl.logger.Errorw("chart version parsing", "err", err) - return []byte("{}"), err - } - secretDataJson, err = impl.mergeUtil.ConfigSecretMerge(secretDataJsonApp, secretDataJsonEnv, chartMajorVersion, chartMinorVersion, false) - if err != nil { - return []byte("{}"), err - } - configResponseR := bean.ConfigMapRootJson{} - configResponse := bean.ConfigMapJson{} - if configMapJson != "" { - err = json.Unmarshal([]byte(configMapJson), &configResponse) - if err != nil { - return []byte("{}"), err - } - } - configResponseR.ConfigMapJson = configResponse - secretResponseR := bean.ConfigSecretRootJson{} - secretResponse := bean.ConfigSecretJson{} - if configMapJson != "" { - err = json.Unmarshal([]byte(secretDataJson), &secretResponse) - if err != nil { - return []byte("{}"), err - } - } - secretResponseR.ConfigSecretJson = secretResponse - - configMapByte, err := json.Marshal(configResponseR) - if err != nil { - return []byte("{}"), err - } - secretDataByte, err := json.Marshal(secretResponseR) - if err != nil { - return []byte("{}"), err - } - - merged, err = impl.mergeUtil.JsonPatch(configMapByte, secretDataByte) - if err != nil { - return []byte("{}"), err - } - return merged, nil -} - func (impl *AppServiceImpl) synchCD(pipeline *pipelineConfig.Pipeline, ctx context.Context, overrideRequest *bean.ValuesOverrideRequest, envOverride *chartConfig.EnvConfigOverride) { req := new(application2.ApplicationSyncRequest) @@ -2301,44 +1261,6 @@ func (impl *AppServiceImpl) synchCD(pipeline *pipelineConfig.Pipeline, ctx conte } } -func (impl *AppServiceImpl) WriteCDTriggerEvent(overrideRequest *bean.ValuesOverrideRequest, artifact *repository.CiArtifact, releaseId, pipelineOverrideId int) { - - event := impl.eventFactory.Build(util.Trigger, &overrideRequest.PipelineId, overrideRequest.AppId, &overrideRequest.EnvId, util.CD) - impl.logger.Debugw("event WriteCDTriggerEvent", "event", event) - event = impl.eventFactory.BuildExtraCDData(event, nil, pipelineOverrideId, bean.CD_WORKFLOW_TYPE_DEPLOY) - _, evtErr := impl.eventClient.WriteNotificationEvent(event) - if evtErr != nil { - impl.logger.Errorw("CD trigger event not sent", "error", evtErr) - } - deploymentEvent := DeploymentEvent{ - ApplicationId: overrideRequest.AppId, - EnvironmentId: overrideRequest.EnvId, //check for production Environment - ReleaseId: releaseId, - PipelineOverrideId: pipelineOverrideId, - TriggerTime: time.Now(), - CiArtifactId: overrideRequest.CiArtifactId, - } - ciPipelineMaterials, err := impl.ciPipelineMaterialRepository.GetByPipelineId(artifact.PipelineId) - if err != nil { - impl.logger.Errorw("error in ") - } - materialInfoMap, mErr := artifact.ParseMaterialInfo() - if mErr != nil { - impl.logger.Errorw("material info map error", mErr) - return - } - for _, ciPipelineMaterial := range ciPipelineMaterials { - hash := materialInfoMap[ciPipelineMaterial.GitMaterial.Url] - pipelineMaterialInfo := &PipelineMaterialInfo{PipelineMaterialId: ciPipelineMaterial.Id, CommitHash: hash} - deploymentEvent.PipelineMaterials = append(deploymentEvent.PipelineMaterials, pipelineMaterialInfo) - } - impl.logger.Infow("triggering deployment event", "event", deploymentEvent) - err = impl.eventClient.WriteNatsEvent(pubsub.CD_SUCCESS, deploymentEvent) - if err != nil { - impl.logger.Errorw("error in writing cd trigger event", "err", err) - } -} - type DeploymentEvent struct { ApplicationId int EnvironmentId int @@ -2387,363 +1309,6 @@ type ReleaseAttributes struct { AppMetrics *bool } -func (impl *AppServiceImpl) getReleaseOverride(envOverride *chartConfig.EnvConfigOverride, overrideRequest *bean.ValuesOverrideRequest, artifact *repository.CiArtifact, pipelineOverride *chartConfig.PipelineOverride, strategy *chartConfig.PipelineStrategy, appMetrics *bool) (releaseOverride string, err error) { - - artifactImage := artifact.Image - imageTag := strings.Split(artifactImage, ":") - - imageTagLen := len(imageTag) - - imageName := "" - - for i := 0; i < imageTagLen-1; i++ { - if i != imageTagLen-2 { - imageName = imageName + imageTag[i] + ":" - } else { - imageName = imageName + imageTag[i] - } - } - - appId := strconv.Itoa(overrideRequest.AppId) - envId := strconv.Itoa(overrideRequest.EnvId) - - deploymentStrategy := "" - if strategy != nil { - deploymentStrategy = string(strategy.Strategy) - } - releaseAttribute := ReleaseAttributes{ - Name: imageName, - Tag: imageTag[imageTagLen-1], - PipelineName: overrideRequest.PipelineName, - ReleaseVersion: strconv.Itoa(pipelineOverride.PipelineReleaseCounter), - DeploymentType: deploymentStrategy, - App: appId, - Env: envId, - AppMetrics: appMetrics, - } - override, err := util2.Tprintf(envOverride.Chart.ImageDescriptorTemplate, releaseAttribute) - if err != nil { - return "", &ApiError{InternalMessage: "unable to render ImageDescriptorTemplate"} - } - if overrideRequest.AdditionalOverride != nil { - userOverride, err := overrideRequest.AdditionalOverride.MarshalJSON() - if err != nil { - return "", err - } - data, err := impl.mergeUtil.JsonPatch(userOverride, []byte(override)) - if err != nil { - return "", err - } - override = string(data) - } - return override, nil -} - -func (impl *AppServiceImpl) mergeOverrideValues(envOverride *chartConfig.EnvConfigOverride, - dbMigrationOverride []byte, - releaseOverrideJson string, - configMapJson []byte, - appLabelJsonByte []byte, - strategy *chartConfig.PipelineStrategy, -) (mergedValues []byte, err error) { - - //merge three values on the fly - //ordering is important here - //global < environment < db< release - var merged []byte - if !envOverride.IsOverride { - merged, err = impl.mergeUtil.JsonPatch([]byte("{}"), []byte(envOverride.Chart.ResolvedGlobalOverride)) - if err != nil { - return nil, err - } - } else { - merged, err = impl.mergeUtil.JsonPatch([]byte("{}"), []byte(envOverride.ResolvedEnvOverrideValues)) - if err != nil { - return nil, err - } - } - if strategy != nil && len(strategy.Config) > 0 { - merged, err = impl.mergeUtil.JsonPatch(merged, []byte(strategy.Config)) - if err != nil { - return nil, err - } - } - merged, err = impl.mergeUtil.JsonPatch(merged, dbMigrationOverride) - if err != nil { - return nil, err - } - merged, err = impl.mergeUtil.JsonPatch(merged, []byte(releaseOverrideJson)) - if err != nil { - return nil, err - } - if configMapJson != nil { - merged, err = impl.mergeUtil.JsonPatch(merged, configMapJson) - if err != nil { - return nil, err - } - } - if appLabelJsonByte != nil { - merged, err = impl.mergeUtil.JsonPatch(merged, appLabelJsonByte) - if err != nil { - return nil, err - } - } - return merged, nil -} - -func (impl *AppServiceImpl) mergeAndSave(envOverride *chartConfig.EnvConfigOverride, - overrideRequest *bean.ValuesOverrideRequest, - dbMigrationOverride []byte, - artifact *repository.CiArtifact, - pipeline *pipelineConfig.Pipeline, configMapJson, appLabelJsonByte []byte, strategy *chartConfig.PipelineStrategy, ctx context.Context, - triggeredAt time.Time, deployedBy int32, appMetrics *bool) (releaseId int, overrideId int, mergedValues string, err error) { - - //register release , obtain release id TODO: populate releaseId to template - override, err := impl.savePipelineOverride(overrideRequest, envOverride.Id, triggeredAt) - if err != nil { - return 0, 0, "", err - } - //TODO: check status and apply lock - overrideJson, err := impl.getReleaseOverride(envOverride, overrideRequest, artifact, override, strategy, appMetrics) - if err != nil { - return 0, 0, "", err - } - - //merge three values on the fly - //ordering is important here - //global < environment < db< release - var merged []byte - if !envOverride.IsOverride { - merged, err = impl.mergeUtil.JsonPatch([]byte("{}"), []byte(envOverride.Chart.GlobalOverride)) - if err != nil { - return 0, 0, "", err - } - } else { - merged, err = impl.mergeUtil.JsonPatch([]byte("{}"), []byte(envOverride.EnvOverrideValues)) - if err != nil { - return 0, 0, "", err - } - } - - //pipeline override here comes from pipeline strategy table - if strategy != nil && len(strategy.Config) > 0 { - merged, err = impl.mergeUtil.JsonPatch(merged, []byte(strategy.Config)) - if err != nil { - return 0, 0, "", err - } - } - merged, err = impl.mergeUtil.JsonPatch(merged, dbMigrationOverride) - if err != nil { - return 0, 0, "", err - } - merged, err = impl.mergeUtil.JsonPatch(merged, []byte(overrideJson)) - if err != nil { - return 0, 0, "", err - } - - if configMapJson != nil { - merged, err = impl.mergeUtil.JsonPatch(merged, configMapJson) - if err != nil { - return 0, 0, "", err - } - } - - if appLabelJsonByte != nil { - merged, err = impl.mergeUtil.JsonPatch(merged, appLabelJsonByte) - if err != nil { - return 0, 0, "", err - } - } - - appName := fmt.Sprintf("%s-%s", pipeline.App.AppName, envOverride.Environment.Name) - merged = impl.autoscalingCheckBeforeTrigger(ctx, appName, envOverride.Namespace, merged, overrideRequest) - - _, span := otel.Tracer("orchestrator").Start(ctx, "dockerRegistryIpsConfigService.HandleImagePullSecretOnApplicationDeployment") - // handle image pull secret if access given - merged, err = impl.dockerRegistryIpsConfigService.HandleImagePullSecretOnApplicationDeployment(envOverride.Environment, pipeline.CiPipelineId, merged) - span.End() - if err != nil { - return 0, 0, "", err - } - - commitHash := "" - commitTime := time.Time{} - if IsAcdApp(pipeline.DeploymentAppType) { - chartRepoName := impl.chartTemplateService.GetGitOpsRepoNameFromUrl(envOverride.Chart.GitRepoUrl) - _, span = otel.Tracer("orchestrator").Start(ctx, "chartTemplateService.GetUserEmailIdAndNameForGitOpsCommit") - //getting username & emailId for commit author data - userEmailId, userName := impl.chartTemplateService.GetUserEmailIdAndNameForGitOpsCommit(overrideRequest.UserId) - span.End() - chartGitAttr := &ChartConfig{ - FileName: fmt.Sprintf("_%d-values.yaml", envOverride.TargetEnvironment), - FileContent: string(merged), - ChartName: envOverride.Chart.ChartName, - ChartLocation: envOverride.Chart.ChartLocation, - ChartRepoName: chartRepoName, - ReleaseMessage: fmt.Sprintf("release-%d-env-%d ", override.Id, envOverride.TargetEnvironment), - UserName: userName, - UserEmailId: userEmailId, - } - gitOpsConfigBitbucket, err := impl.gitOpsConfigRepository.GetGitOpsConfigByProvider(BITBUCKET_PROVIDER) - if err != nil { - if err == pg.ErrNoRows { - gitOpsConfigBitbucket.BitBucketWorkspaceId = "" - } else { - return 0, 0, "", err - } - } - gitOpsConfig := &bean.GitOpsConfigDto{BitBucketWorkspaceId: gitOpsConfigBitbucket.BitBucketWorkspaceId} - _, span = otel.Tracer("orchestrator").Start(ctx, "gitFactory.Client.CommitValues") - commitHash, commitTime, err = impl.gitFactory.Client.CommitValues(chartGitAttr, gitOpsConfig) - span.End() - if err != nil { - impl.logger.Errorw("error in git commit", "err", err) - return 0, 0, "", err - } - } - if commitTime.IsZero() { - commitTime = time.Now() - } - pipelineOverride := &chartConfig.PipelineOverride{ - Id: override.Id, - GitHash: commitHash, - CommitTime: commitTime, - EnvConfigOverrideId: envOverride.Id, - PipelineOverrideValues: overrideJson, - PipelineId: overrideRequest.PipelineId, - CiArtifactId: overrideRequest.CiArtifactId, - PipelineMergedValues: string(merged), - AuditLog: sql.AuditLog{UpdatedOn: triggeredAt, UpdatedBy: deployedBy}, - } - _, span = otel.Tracer("orchestrator").Start(ctx, "pipelineOverrideRepository.Update") - err = impl.pipelineOverrideRepository.Update(pipelineOverride) - span.End() - if err != nil { - return 0, 0, "", err - } - mergedValues = string(merged) - return override.PipelineReleaseCounter, override.Id, mergedValues, nil -} - -func (impl *AppServiceImpl) savePipelineOverride(overrideRequest *bean.ValuesOverrideRequest, envOverrideId int, triggeredAt time.Time) (override *chartConfig.PipelineOverride, err error) { - currentReleaseNo, err := impl.pipelineOverrideRepository.GetCurrentPipelineReleaseCounter(overrideRequest.PipelineId) - if err != nil { - return nil, err - } - po := &chartConfig.PipelineOverride{ - EnvConfigOverrideId: envOverrideId, - Status: models.CHARTSTATUS_NEW, - PipelineId: overrideRequest.PipelineId, - CiArtifactId: overrideRequest.CiArtifactId, - PipelineReleaseCounter: currentReleaseNo + 1, - CdWorkflowId: overrideRequest.CdWorkflowId, - AuditLog: sql.AuditLog{CreatedBy: overrideRequest.UserId, CreatedOn: triggeredAt, UpdatedOn: triggeredAt, UpdatedBy: overrideRequest.UserId}, - DeploymentType: overrideRequest.DeploymentType, - } - - err = impl.pipelineOverrideRepository.Save(po) - if err != nil { - return nil, err - } - err = impl.checkAndFixDuplicateReleaseNo(po) - if err != nil { - impl.logger.Errorw("error in checking release no duplicacy", "pipeline", po, "err", err) - return nil, err - } - return po, nil -} - -func (impl *AppServiceImpl) checkAndFixDuplicateReleaseNo(override *chartConfig.PipelineOverride) error { - - uniqueVerified := false - retryCount := 0 - - for !uniqueVerified && retryCount < 5 { - retryCount = retryCount + 1 - overrides, err := impl.pipelineOverrideRepository.GetByPipelineIdAndReleaseNo(override.PipelineId, override.PipelineReleaseCounter) - if err != nil { - return err - } - if overrides[0].Id == override.Id { - uniqueVerified = true - } else { - //duplicate might be due to concurrency, lets fix it - currentReleaseNo, err := impl.pipelineOverrideRepository.GetCurrentPipelineReleaseCounter(override.PipelineId) - if err != nil { - return err - } - override.PipelineReleaseCounter = currentReleaseNo + 1 - err = impl.pipelineOverrideRepository.Save(override) - if err != nil { - return err - } - } - } - if !uniqueVerified { - return fmt.Errorf("duplicate verification retry count exide max overrideId: %d ,count: %d", override.Id, retryCount) - } - return nil -} - -func (impl *AppServiceImpl) updateArgoPipeline(appId int, pipelineName string, envOverride *chartConfig.EnvConfigOverride, ctx context.Context) (bool, error) { - //repo has been registered while helm create - if ctx == nil { - impl.logger.Errorw("err in syncing ACD, ctx is NULL", "pipelineName", pipelineName) - return false, nil - } - app, err := impl.appRepository.FindById(appId) - if err != nil { - impl.logger.Errorw("no app found ", "err", err) - return false, err - } - envModel, err := impl.envRepository.FindById(envOverride.TargetEnvironment) - if err != nil { - return false, err - } - argoAppName := fmt.Sprintf("%s-%s", app.AppName, envModel.Name) - impl.logger.Infow("received payload, updateArgoPipeline", "appId", appId, "pipelineName", pipelineName, "envId", envOverride.TargetEnvironment, "argoAppName", argoAppName, "context", ctx) - application, err := impl.acdClient.Get(ctx, &application2.ApplicationQuery{Name: &argoAppName}) - if err != nil { - impl.logger.Errorw("no argo app exists", "app", argoAppName, "pipeline", pipelineName) - return false, err - } - //if status, ok:=status.FromError(err);ok{ - appStatus, _ := status.FromError(err) - - if appStatus.Code() == codes.OK { - impl.logger.Debugw("argo app exists", "app", argoAppName, "pipeline", pipelineName) - if application.Spec.Source.Path != envOverride.Chart.ChartLocation || application.Spec.Source.TargetRevision != "master" { - patchReq := v1alpha1.Application{Spec: v1alpha1.ApplicationSpec{Source: v1alpha1.ApplicationSource{Path: envOverride.Chart.ChartLocation, RepoURL: envOverride.Chart.GitRepoUrl, TargetRevision: "master"}}} - reqbyte, err := json.Marshal(patchReq) - if err != nil { - impl.logger.Errorw("error in creating patch", "err", err) - } - reqString := string(reqbyte) - patchType := "merge" - _, err = impl.acdClient.Patch(ctx, &application2.ApplicationPatchRequest{Patch: &reqString, Name: &argoAppName, PatchType: &patchType}) - if err != nil { - impl.logger.Errorw("error in creating argo pipeline ", "name", pipelineName, "patch", string(reqbyte), "err", err) - return false, err - } - impl.logger.Debugw("pipeline update req ", "res", patchReq) - } else { - impl.logger.Debug("pipeline no need to update ") - } - // Doing normal refresh to avoid the sync delay in argo-cd. - err2 := impl.argoClientWrapperService.GetArgoAppWithNormalRefresh(ctx, argoAppName) - if err2 != nil { - impl.logger.Errorw("error in getting argo application with normal refresh", "argoAppName", argoAppName, "pipelineName", pipelineName) - } - return true, nil - } else if appStatus.Code() == codes.NotFound { - impl.logger.Errorw("argo app not found", "app", argoAppName, "pipeline", pipelineName) - return false, nil - } else { - impl.logger.Errorw("err in checking application on gocd", "err", err, "pipeline", pipelineName) - return false, err - } -} - func (impl *AppServiceImpl) UpdateInstalledAppVersionHistoryByACDObject(app *v1alpha1.Application, installedAppVersionHistoryId int, updateTimedOutStatus bool) error { installedAppVersionHistory, err := impl.installedAppVersionHistoryRepository.GetInstalledAppVersionHistory(installedAppVersionHistoryId) if err != nil { @@ -2811,443 +1376,6 @@ const nameOverride = "nameOverride" const enabled = "enabled" const replicaCount = "replicaCount" -func (impl *AppServiceImpl) getAutoScalingReplicaCount(templateMap map[string]interface{}, appName string) *util2.HpaResourceRequest { - hasOverride := false - if _, ok := templateMap[fullnameOverride]; ok { - appNameOverride := templateMap[fullnameOverride].(string) - if len(appNameOverride) > 0 { - appName = appNameOverride - hasOverride = true - } - } - if !hasOverride { - if _, ok := templateMap[nameOverride]; ok { - nameOverride := templateMap[nameOverride].(string) - if len(nameOverride) > 0 { - appName = fmt.Sprintf("%s-%s", appName, nameOverride) - } - } - } - hpaResourceRequest := &util2.HpaResourceRequest{} - hpaResourceRequest.Version = "" - hpaResourceRequest.Group = autoscaling.ServiceName - hpaResourceRequest.Kind = HorizontalPodAutoscaler - impl.logger.Infow("getAutoScalingReplicaCount", "hpaResourceRequest", hpaResourceRequest) - if _, ok := templateMap[kedaAutoscaling]; ok { - as := templateMap[kedaAutoscaling] - asd := as.(map[string]interface{}) - if _, ok := asd[enabled]; ok { - impl.logger.Infow("getAutoScalingReplicaCount", "hpaResourceRequest", hpaResourceRequest) - enable := asd[enabled].(bool) - if enable { - hpaResourceRequest.IsEnable = enable - hpaResourceRequest.ReqReplicaCount = templateMap[replicaCount].(float64) - hpaResourceRequest.ReqMaxReplicas = asd["maxReplicaCount"].(float64) - hpaResourceRequest.ReqMinReplicas = asd["minReplicaCount"].(float64) - hpaResourceRequest.ResourceName = fmt.Sprintf("%s-%s-%s", "keda-hpa", appName, "keda") - impl.logger.Infow("getAutoScalingReplicaCount", "hpaResourceRequest", hpaResourceRequest) - return hpaResourceRequest - } - } - } - - if _, ok := templateMap[autoscaling.ServiceName]; ok { - as := templateMap[autoscaling.ServiceName] - asd := as.(map[string]interface{}) - if _, ok := asd[enabled]; ok { - enable := asd[enabled].(bool) - if enable { - hpaResourceRequest.IsEnable = asd[enabled].(bool) - hpaResourceRequest.ReqReplicaCount = templateMap[replicaCount].(float64) - hpaResourceRequest.ReqMaxReplicas = asd["MaxReplicas"].(float64) - hpaResourceRequest.ReqMinReplicas = asd["MinReplicas"].(float64) - hpaResourceRequest.ResourceName = fmt.Sprintf("%s-%s", appName, "hpa") - return hpaResourceRequest - } - } - } - return hpaResourceRequest - -} - -func (impl *AppServiceImpl) autoscalingCheckBeforeTrigger(ctx context.Context, appName string, namespace string, merged []byte, overrideRequest *bean.ValuesOverrideRequest) []byte { - //pipeline := overrideRequest.Pipeline - var appId = overrideRequest.AppId - pipelineId := overrideRequest.PipelineId - var appDeploymentType = overrideRequest.DeploymentAppType - var clusterId = overrideRequest.ClusterId - deploymentType := overrideRequest.DeploymentType - templateMap := make(map[string]interface{}) - err := json.Unmarshal(merged, &templateMap) - if err != nil { - return merged - } - - hpaResourceRequest := impl.getAutoScalingReplicaCount(templateMap, appName) - impl.logger.Debugw("autoscalingCheckBeforeTrigger", "hpaResourceRequest", hpaResourceRequest) - if hpaResourceRequest.IsEnable { - resourceManifest := make(map[string]interface{}) - if IsAcdApp(appDeploymentType) { - query := &application2.ApplicationResourceRequest{ - Name: &appName, - Version: &hpaResourceRequest.Version, - Group: &hpaResourceRequest.Group, - Kind: &hpaResourceRequest.Kind, - ResourceName: &hpaResourceRequest.ResourceName, - Namespace: &namespace, - } - recv, err := impl.acdClient.GetResource(ctx, query) - impl.logger.Debugw("resource manifest get replica count", "response", recv) - if err != nil { - impl.logger.Errorw("ACD Get Resource API Failed", "err", err) - middleware.AcdGetResourceCounter.WithLabelValues(strconv.Itoa(appId), namespace, appName).Inc() - return merged - } - if recv != nil && len(*recv.Manifest) > 0 { - err := json.Unmarshal([]byte(*recv.Manifest), &resourceManifest) - if err != nil { - impl.logger.Errorw("unmarshal failed for hpa check", "err", err) - return merged - } - } - } else { - version := "v2beta2" - k8sResource, err := impl.K8sCommonService.GetResource(ctx, &k8s.ResourceRequestBean{ClusterId: clusterId, - K8sRequest: &k8s2.K8sRequestBean{ResourceIdentifier: k8s2.ResourceIdentifier{Name: hpaResourceRequest.ResourceName, - Namespace: namespace, GroupVersionKind: schema.GroupVersionKind{Group: hpaResourceRequest.Group, Kind: hpaResourceRequest.Kind, Version: version}}}}) - if err != nil { - impl.logger.Errorw("error occurred while fetching resource for app", "resourceName", hpaResourceRequest.ResourceName, "err", err) - return merged - } - resourceManifest = k8sResource.Manifest.Object - } - if len(resourceManifest) > 0 { - statusMap := resourceManifest["status"].(map[string]interface{}) - currentReplicaVal := statusMap["currentReplicas"] - currentReplicaCount, err := util2.ParseFloatNumber(currentReplicaVal) - if err != nil { - impl.logger.Errorw("error occurred while parsing replica count", "currentReplicas", currentReplicaVal, "err", err) - return merged - } - - reqReplicaCount := impl.fetchRequiredReplicaCount(currentReplicaCount, hpaResourceRequest.ReqMaxReplicas, hpaResourceRequest.ReqMinReplicas) - templateMap["replicaCount"] = reqReplicaCount - merged, err = json.Marshal(&templateMap) - if err != nil { - impl.logger.Errorw("marshaling failed for hpa check", "err", err) - return merged - } - } - } else { - impl.logger.Errorw("autoscaling is not enabled", "pipelineId", pipelineId) - } - - //check for custom chart support - if autoscalingEnabledPath, ok := templateMap[bean2.CustomAutoScalingEnabledPathKey]; ok { - if deploymentType == models.DEPLOYMENTTYPE_STOP { - merged, err = impl.setScalingValues(templateMap, bean2.CustomAutoScalingEnabledPathKey, merged, false) - if err != nil { - return merged - } - merged, err = impl.setScalingValues(templateMap, bean2.CustomAutoscalingReplicaCountPathKey, merged, 0) - if err != nil { - return merged - } - } else { - autoscalingEnabled := false - autoscalingEnabledValue := gjson.Get(string(merged), autoscalingEnabledPath.(string)).Value() - if val, ok := autoscalingEnabledValue.(bool); ok { - autoscalingEnabled = val - } - if autoscalingEnabled { - // extract replica count, min, max and check for required value - replicaCount, err := impl.getReplicaCountFromCustomChart(templateMap, merged) - if err != nil { - return merged - } - merged, err = impl.setScalingValues(templateMap, bean2.CustomAutoscalingReplicaCountPathKey, merged, replicaCount) - if err != nil { - return merged - } - } - } - } - - return merged -} - -func (impl *AppServiceImpl) getReplicaCountFromCustomChart(templateMap map[string]interface{}, merged []byte) (float64, error) { - autoscalingMinVal, err := impl.extractParamValue(templateMap, bean2.CustomAutoscalingMinPathKey, merged) - if err != nil { - return 0, err - } - autoscalingMaxVal, err := impl.extractParamValue(templateMap, bean2.CustomAutoscalingMaxPathKey, merged) - if err != nil { - return 0, err - } - autoscalingReplicaCountVal, err := impl.extractParamValue(templateMap, bean2.CustomAutoscalingReplicaCountPathKey, merged) - if err != nil { - return 0, err - } - return impl.fetchRequiredReplicaCount(autoscalingReplicaCountVal, autoscalingMaxVal, autoscalingMinVal), nil -} - -func (impl *AppServiceImpl) extractParamValue(inputMap map[string]interface{}, key string, merged []byte) (float64, error) { - if _, ok := inputMap[key]; !ok { - return 0, errors.New("empty-val-err") - } - floatNumber, err := util2.ParseFloatNumber(gjson.Get(string(merged), inputMap[key].(string)).Value()) - if err != nil { - impl.logger.Errorw("error occurred while parsing float number", "key", key, "err", err) - } - return floatNumber, err -} - -func (impl *AppServiceImpl) setScalingValues(templateMap map[string]interface{}, customScalingKey string, merged []byte, value interface{}) ([]byte, error) { - autoscalingJsonPath := templateMap[customScalingKey] - autoscalingJsonPathKey := autoscalingJsonPath.(string) - mergedRes, err := sjson.Set(string(merged), autoscalingJsonPathKey, value) - if err != nil { - impl.logger.Errorw("error occurred while setting autoscaling key", "JsonPathKey", autoscalingJsonPathKey, "err", err) - return []byte{}, err - } - return []byte(mergedRes), nil -} - -func (impl *AppServiceImpl) fetchRequiredReplicaCount(currentReplicaCount float64, reqMaxReplicas float64, reqMinReplicas float64) float64 { - var reqReplicaCount float64 - if currentReplicaCount <= reqMaxReplicas && currentReplicaCount >= reqMinReplicas { - reqReplicaCount = currentReplicaCount - } else if currentReplicaCount > reqMaxReplicas { - reqReplicaCount = reqMaxReplicas - } else if currentReplicaCount < reqMinReplicas { - reqReplicaCount = reqMinReplicas - } - return reqReplicaCount -} - -func (impl *AppServiceImpl) CreateHistoriesForDeploymentTrigger(pipeline *pipelineConfig.Pipeline, strategy *chartConfig.PipelineStrategy, envOverride *chartConfig.EnvConfigOverride, deployedOn time.Time, deployedBy int32) error { - //creating history for deployment template - deploymentTemplateHistory, err := impl.deploymentTemplateHistoryService.CreateDeploymentTemplateHistoryForDeploymentTrigger(pipeline, envOverride, envOverride.Chart.ImageDescriptorTemplate, deployedOn, deployedBy) - if err != nil { - impl.logger.Errorw("error in creating deployment template history for deployment trigger", "err", err) - return err - } - err = impl.configMapHistoryService.CreateCMCSHistoryForDeploymentTrigger(pipeline, deployedOn, deployedBy) - if err != nil { - impl.logger.Errorw("error in creating CM/CS history for deployment trigger", "err", err) - return err - } - if strategy != nil { - err = impl.pipelineStrategyHistoryService.CreateStrategyHistoryForDeploymentTrigger(strategy, deployedOn, deployedBy, pipeline.TriggerType) - if err != nil { - impl.logger.Errorw("error in creating strategy history for deployment trigger", "err", err) - return err - } - } - //VARIABLE_SNAPSHOT_SAVE - if envOverride.VariableSnapshot != nil && len(envOverride.VariableSnapshot) > 0 { - variableMapBytes, _ := json.Marshal(envOverride.VariableSnapshot) - variableSnapshotHistory := &repository6.VariableSnapshotHistoryBean{ - VariableSnapshot: variableMapBytes, - HistoryReference: repository6.HistoryReference{ - HistoryReferenceId: deploymentTemplateHistory.Id, - HistoryReferenceType: repository6.HistoryReferenceTypeDeploymentTemplate, - }, - } - err = impl.variableSnapshotHistoryService.SaveVariableHistoriesForTrigger([]*repository6.VariableSnapshotHistoryBean{variableSnapshotHistory}, deployedBy) - if err != nil { - return err - } - } - return nil -} - -func (impl *AppServiceImpl) updatePipeline(pipeline *pipelineConfig.Pipeline, userId int32) (bool, error) { - err := impl.pipelineRepository.SetDeploymentAppCreatedInPipeline(true, pipeline.Id, userId) - if err != nil { - impl.logger.Errorw("error on updating cd pipeline for setting deployment app created", "err", err) - return false, err - } - return true, nil -} - -func (impl *AppServiceImpl) createHelmAppForCdPipeline(overrideRequest *bean.ValuesOverrideRequest, valuesOverrideResponse *ValuesOverrideResponse, triggeredAt time.Time, ctx context.Context) (bool, error) { - - pipeline := valuesOverrideResponse.Pipeline - envOverride := valuesOverrideResponse.EnvOverride - mergeAndSave := valuesOverrideResponse.MergedValues - - chartMetaData := &chart2.Metadata{ - Name: pipeline.App.AppName, - Version: envOverride.Chart.ChartVersion, - } - referenceTemplatePath := path.Join(string(impl.refChartDir), envOverride.Chart.ReferenceTemplate) - - if IsHelmApp(pipeline.DeploymentAppType) { - referenceChartByte := envOverride.Chart.ReferenceChart - // here updating reference chart into database. - if len(envOverride.Chart.ReferenceChart) == 0 { - refChartByte, err := impl.chartTemplateService.GetByteArrayRefChart(chartMetaData, referenceTemplatePath) - if err != nil { - impl.logger.Errorw("ref chart commit error on cd trigger", "err", err, "req", overrideRequest) - return false, err - } - ch := envOverride.Chart - ch.ReferenceChart = refChartByte - ch.UpdatedOn = time.Now() - ch.UpdatedBy = overrideRequest.UserId - err = impl.chartRepository.Update(ch) - if err != nil { - impl.logger.Errorw("chart update error", "err", err, "req", overrideRequest) - return false, err - } - referenceChartByte = refChartByte - } - - releaseName := pipeline.DeploymentAppName - cluster := envOverride.Environment.Cluster - bearerToken := cluster.Config[k8s2.BearerToken] - clusterConfig := &client2.ClusterConfig{ - ClusterName: cluster.ClusterName, - Token: bearerToken, - ApiServerUrl: cluster.ServerUrl, - InsecureSkipTLSVerify: cluster.InsecureSkipTlsVerify, - } - if cluster.InsecureSkipTlsVerify == false { - clusterConfig.KeyData = cluster.Config[k8s2.TlsKey] - clusterConfig.CertData = cluster.Config[k8s2.CertData] - clusterConfig.CaData = cluster.Config[k8s2.CertificateAuthorityData] - } - releaseIdentifier := &client2.ReleaseIdentifier{ - ReleaseName: releaseName, - ReleaseNamespace: envOverride.Namespace, - ClusterConfig: clusterConfig, - } - - if pipeline.DeploymentAppCreated { - req := &client2.UpgradeReleaseRequest{ - ReleaseIdentifier: releaseIdentifier, - ValuesYaml: mergeAndSave, - HistoryMax: impl.helmAppService.GetRevisionHistoryMaxValue(client2.SOURCE_DEVTRON_APP), - ChartContent: &client2.ChartContent{Content: referenceChartByte}, - } - - updateApplicationResponse, err := impl.helmAppClient.UpdateApplication(ctx, req) - - // For cases where helm release was not found but db flag for deployment app created was true - if err != nil && strings.Contains(err.Error(), "release: not found") { - - // retry install - _, err = impl.helmInstallReleaseWithCustomChart(ctx, releaseIdentifier, referenceChartByte, mergeAndSave) - - // if retry failed, return - if err != nil { - impl.logger.Errorw("release not found, failed to re-install helm application", "err", err) - return false, err - } - } else if err != nil { - impl.logger.Errorw("error in updating helm application for cd pipeline", "err", err) - return false, err - } else { - impl.logger.Debugw("updated helm application", "response", updateApplicationResponse, "isSuccess", updateApplicationResponse.Success) - } - - } else { - - helmResponse, err := impl.helmInstallReleaseWithCustomChart(ctx, releaseIdentifier, referenceChartByte, mergeAndSave) - - // For connection related errors, no need to update the db - if err != nil && strings.Contains(err.Error(), "connection error") { - impl.logger.Errorw("error in helm install custom chart", "err", err) - return false, err - } - - // IMP: update cd pipeline to mark deployment app created, even if helm install fails - // If the helm install fails, it still creates the app in failed state, so trying to - // re-create the app results in error from helm that cannot re-use name which is still in use - _, pgErr := impl.updatePipeline(pipeline, overrideRequest.UserId) - - if err != nil { - impl.logger.Errorw("error in helm install custom chart", "err", err) - - if pgErr != nil { - impl.logger.Errorw("failed to update deployment app created flag in pipeline table", "err", err) - } - return false, err - } - - if pgErr != nil { - impl.logger.Errorw("failed to update deployment app created flag in pipeline table", "err", err) - return false, err - } - - impl.logger.Debugw("received helm release response", "helmResponse", helmResponse, "isSuccess", helmResponse.Success) - } - - //update workflow runner status, used in app workflow view - cdWf, err := impl.cdWorkflowRepository.FindByWorkflowIdAndRunnerType(ctx, overrideRequest.CdWorkflowId, bean.CD_WORKFLOW_TYPE_DEPLOY) - if err != nil && err != pg.ErrNoRows { - impl.logger.Errorw("err on fetching cd workflow", "err", err) - return false, err - } - cdWorkflowId := cdWf.CdWorkflowId - if cdWf.CdWorkflowId == 0 { - cdWf := &pipelineConfig.CdWorkflow{ - CiArtifactId: overrideRequest.CiArtifactId, - PipelineId: overrideRequest.PipelineId, - AuditLog: sql.AuditLog{CreatedOn: triggeredAt, CreatedBy: overrideRequest.UserId, UpdatedOn: triggeredAt, UpdatedBy: overrideRequest.UserId}, - } - err := impl.cdWorkflowRepository.SaveWorkFlow(ctx, cdWf) - if err != nil { - impl.logger.Errorw("err on updating cd workflow for status update", "err", err) - return false, err - } - cdWorkflowId = cdWf.Id - runner := &pipelineConfig.CdWorkflowRunner{ - Id: cdWf.Id, - Name: pipeline.Name, - WorkflowType: bean.CD_WORKFLOW_TYPE_DEPLOY, - ExecutorType: pipelineConfig.WORKFLOW_EXECUTOR_TYPE_AWF, - Status: pipelineConfig.WorkflowInProgress, - TriggeredBy: overrideRequest.UserId, - StartedOn: triggeredAt, - CdWorkflowId: cdWorkflowId, - AuditLog: sql.AuditLog{CreatedOn: triggeredAt, CreatedBy: overrideRequest.UserId, UpdatedOn: triggeredAt, UpdatedBy: overrideRequest.UserId}, - } - _, err = impl.cdWorkflowRepository.SaveWorkFlowRunner(runner) - if err != nil { - impl.logger.Errorw("err on updating cd workflow runner for status update", "err", err) - return false, err - } - } else { - cdWf.Status = pipelineConfig.WorkflowInProgress - cdWf.FinishedOn = time.Now() - cdWf.UpdatedBy = overrideRequest.UserId - cdWf.UpdatedOn = time.Now() - err = impl.cdWorkflowRepository.UpdateWorkFlowRunner(&cdWf) - if err != nil { - impl.logger.Errorw("error on update cd workflow runner", "cdWf", cdWf, "err", err) - return false, err - } - } - } - return true, nil -} - -// helmInstallReleaseWithCustomChart performs helm install with custom chart -func (impl *AppServiceImpl) helmInstallReleaseWithCustomChart(ctx context.Context, releaseIdentifier *client2.ReleaseIdentifier, referenceChartByte []byte, valuesYaml string) (*client2.HelmInstallCustomResponse, error) { - - helmInstallRequest := client2.HelmInstallCustomRequest{ - ValuesYaml: valuesYaml, - ChartContent: &client2.ChartContent{Content: referenceChartByte}, - ReleaseIdentifier: releaseIdentifier, - } - - // Request exec - return impl.helmAppClient.InstallReleaseWithCustomChart(ctx, &helmInstallRequest) -} - func (impl *AppServiceImpl) GetGitOpsRepoPrefix() string { return impl.globalEnvVariables.GitOpsRepoPrefix } diff --git a/pkg/pipeline/WorkflowDagExecutor.go b/pkg/pipeline/WorkflowDagExecutor.go index 1c0f0edafd..1a66caafe5 100644 --- a/pkg/pipeline/WorkflowDagExecutor.go +++ b/pkg/pipeline/WorkflowDagExecutor.go @@ -20,21 +20,43 @@ package pipeline import ( "context" "encoding/json" + errors3 "errors" "fmt" + "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/aws/aws-sdk-go/service/autoscaling" blob_storage "github.com/devtron-labs/common-lib/blob-storage" util5 "github.com/devtron-labs/common-lib/utils/k8s" "github.com/devtron-labs/common-lib/utils/k8s/health" + client2 "github.com/devtron-labs/devtron/api/helm-app" + "github.com/devtron-labs/devtron/client/argocdServer" + application2 "github.com/devtron-labs/devtron/client/argocdServer/application" gitSensorClient "github.com/devtron-labs/devtron/client/gitSensor" + "github.com/devtron-labs/devtron/internal/middleware" + app2 "github.com/devtron-labs/devtron/internal/sql/repository/app" + bean4 "github.com/devtron-labs/devtron/pkg/app/bean" "github.com/devtron-labs/devtron/pkg/app/status" + "github.com/devtron-labs/devtron/pkg/chartRepo/repository" + "github.com/devtron-labs/devtron/pkg/dockerRegistry" "github.com/devtron-labs/devtron/pkg/k8s" bean3 "github.com/devtron-labs/devtron/pkg/pipeline/bean" repository4 "github.com/devtron-labs/devtron/pkg/pipeline/repository" "github.com/devtron-labs/devtron/pkg/resourceQualifiers" "github.com/devtron-labs/devtron/pkg/variables" + "github.com/devtron-labs/devtron/pkg/variables/parsers" repository5 "github.com/devtron-labs/devtron/pkg/variables/repository" util4 "github.com/devtron-labs/devtron/util" "github.com/devtron-labs/devtron/util/argo" + errors2 "github.com/juju/errors" + "github.com/pkg/errors" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" "go.opentelemetry.io/otel" + "google.golang.org/grpc/codes" + status2 "google.golang.org/grpc/status" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/helm/pkg/proto/hapi/chart" + "path" "strconv" "strings" "time" @@ -119,8 +141,51 @@ type WorkflowDagExecutorImpl struct { config *CdConfig variableSnapshotHistoryService variables.VariableSnapshotHistoryService + + deploymentTemplateHistoryService history2.DeploymentTemplateHistoryService + configMapHistoryService history2.ConfigMapHistoryService + pipelineStrategyHistoryService history2.PipelineStrategyHistoryService + manifestPushConfigRepository repository4.ManifestPushConfigRepository + gitOpsManifestPushService app.GitOpsPushService + ciPipelineMaterialRepository pipelineConfig.CiPipelineMaterialRepository + imageScanHistoryRepository security.ImageScanHistoryRepository + imageScanDeployInfoRepository security.ImageScanDeployInfoRepository + appCrudOperationService app.AppCrudOperationService + pipelineConfigRepository chartConfig.PipelineConfigRepository + dockerRegistryIpsConfigService dockerRegistry.DockerRegistryIpsConfigService + chartRepository chartRepoRepository.ChartRepository + chartTemplateService util.ChartTemplateService + strategyHistoryRepository repository3.PipelineStrategyHistoryRepository + appRepository app2.AppRepository + deploymentTemplateHistoryRepository repository3.DeploymentTemplateHistoryRepository + argoK8sClient argocdServer.ArgoK8sClient + configMapRepository chartConfig.ConfigMapRepository + configMapHistoryRepository repository3.ConfigMapHistoryRepository + refChartDir chartRepoRepository.RefChartDir + helmAppService client2.HelmAppService + helmAppClient client2.HelmAppClient + chartRefRepository chartRepoRepository.ChartRefRepository + environmentConfigRepository chartConfig.EnvConfigOverrideRepository + appLevelMetricsRepository repository.AppLevelMetricsRepository + envLevelMetricsRepository repository.EnvLevelAppMetricsRepository + dbMigrationConfigRepository pipelineConfig.DbMigrationConfigRepository + mergeUtil *util.MergeUtil + gitOpsConfigRepository repository.GitOpsConfigRepository + gitFactory *util.GitFactory + acdClient application2.ServiceClient + variableEntityMappingService variables.VariableEntityMappingService + variableTemplateParser parsers.VariableTemplateParser + argoClientWrapperService argocdServer.ArgoClientWrapperService + scopedVariableService variables.ScopedVariableService } +const kedaAutoscaling = "kedaAutoscaling" +const horizontalPodAutoscaler = "HorizontalPodAutoscaler" +const fullnameOverride = "fullnameOverride" +const nameOverride = "nameOverride" +const enabled = "enabled" +const replicaCount = "replicaCount" + const ( CD_PIPELINE_ENV_NAME_KEY = "CD_PIPELINE_ENV_NAME" CD_PIPELINE_CLUSTER_NAME_KEY = "CD_PIPELINE_CLUSTER_NAME" @@ -212,6 +277,42 @@ func NewWorkflowDagExecutorImpl(Logger *zap.SugaredLogger, pipelineRepository pi appLabelRepository pipelineConfig.AppLabelRepository, gitSensorGrpcClient gitSensorClient.Client, pipelineStageService PipelineStageService, k8sCommonService k8s.K8sCommonService, variableSnapshotHistoryService variables.VariableSnapshotHistoryService, + + deploymentTemplateHistoryService history2.DeploymentTemplateHistoryService, + configMapHistoryService history2.ConfigMapHistoryService, + pipelineStrategyHistoryService history2.PipelineStrategyHistoryService, + manifestPushConfigRepository repository4.ManifestPushConfigRepository, + gitOpsManifestPushService app.GitOpsPushService, + ciPipelineMaterialRepository pipelineConfig.CiPipelineMaterialRepository, + imageScanHistoryRepository security.ImageScanHistoryRepository, + imageScanDeployInfoRepository security.ImageScanDeployInfoRepository, + appCrudOperationService app.AppCrudOperationService, + pipelineConfigRepository chartConfig.PipelineConfigRepository, + dockerRegistryIpsConfigService dockerRegistry.DockerRegistryIpsConfigService, + chartRepository chartRepoRepository.ChartRepository, + chartTemplateService util.ChartTemplateService, + strategyHistoryRepository repository3.PipelineStrategyHistoryRepository, + appRepository app2.AppRepository, + deploymentTemplateHistoryRepository repository3.DeploymentTemplateHistoryRepository, + ArgoK8sClient argocdServer.ArgoK8sClient, + configMapRepository chartConfig.ConfigMapRepository, + configMapHistoryRepository repository3.ConfigMapHistoryRepository, + refChartDir chartRepoRepository.RefChartDir, + helmAppService client2.HelmAppService, + helmAppClient client2.HelmAppClient, + chartRefRepository chartRepoRepository.ChartRefRepository, + environmentConfigRepository chartConfig.EnvConfigOverrideRepository, + appLevelMetricsRepository repository.AppLevelMetricsRepository, + envLevelMetricsRepository repository.EnvLevelAppMetricsRepository, + dbMigrationConfigRepository pipelineConfig.DbMigrationConfigRepository, + mergeUtil *util.MergeUtil, + gitOpsConfigRepository repository.GitOpsConfigRepository, + gitFactory *util.GitFactory, + acdClient application2.ServiceClient, + variableEntityMappingService variables.VariableEntityMappingService, + variableTemplateParser parsers.VariableTemplateParser, + argoClientWrapperService argocdServer.ArgoClientWrapperService, + scopedVariableService variables.ScopedVariableService, ) *WorkflowDagExecutorImpl { wde := &WorkflowDagExecutorImpl{logger: Logger, pipelineRepository: pipelineRepository, @@ -246,6 +347,42 @@ func NewWorkflowDagExecutorImpl(Logger *zap.SugaredLogger, pipelineRepository pi k8sCommonService: k8sCommonService, pipelineStageService: pipelineStageService, variableSnapshotHistoryService: variableSnapshotHistoryService, + + deploymentTemplateHistoryService: deploymentTemplateHistoryService, + configMapHistoryService: configMapHistoryService, + pipelineStrategyHistoryService: pipelineStrategyHistoryService, + manifestPushConfigRepository: manifestPushConfigRepository, + gitOpsManifestPushService: gitOpsManifestPushService, + ciPipelineMaterialRepository: ciPipelineMaterialRepository, + imageScanHistoryRepository: imageScanHistoryRepository, + imageScanDeployInfoRepository: imageScanDeployInfoRepository, + appCrudOperationService: appCrudOperationService, + pipelineConfigRepository: pipelineConfigRepository, + dockerRegistryIpsConfigService: dockerRegistryIpsConfigService, + chartRepository: chartRepository, + chartTemplateService: chartTemplateService, + strategyHistoryRepository: strategyHistoryRepository, + appRepository: appRepository, + deploymentTemplateHistoryRepository: deploymentTemplateHistoryRepository, + argoK8sClient: ArgoK8sClient, + configMapRepository: configMapRepository, + configMapHistoryRepository: configMapHistoryRepository, + refChartDir: refChartDir, + helmAppService: helmAppService, + helmAppClient: helmAppClient, + chartRefRepository: chartRefRepository, + environmentConfigRepository: environmentConfigRepository, + appLevelMetricsRepository: appLevelMetricsRepository, + envLevelMetricsRepository: envLevelMetricsRepository, + dbMigrationConfigRepository: dbMigrationConfigRepository, + mergeUtil: mergeUtil, + gitOpsConfigRepository: gitOpsConfigRepository, + gitFactory: gitFactory, + acdClient: acdClient, + variableEntityMappingService: variableEntityMappingService, + variableTemplateParser: variableTemplateParser, + argoClientWrapperService: argoClientWrapperService, + scopedVariableService: scopedVariableService, } config, err := GetCdConfig() if err != nil { @@ -1449,7 +1586,7 @@ func (impl *WorkflowDagExecutorImpl) TriggerDeployment(cdWf *pipelineConfig.CdWo return nil } - err = impl.appService.TriggerCD(artifact, cdWf.Id, savedWfr.Id, pipeline, triggeredAt) + err = impl.TriggerCD(artifact, cdWf.Id, savedWfr.Id, pipeline, triggeredAt) err1 := impl.updatePreviousDeploymentStatus(runner, pipeline.Id, err, triggeredAt, triggeredBy) if err1 != nil || err != nil { impl.logger.Errorw("error while update previous cd workflow runners", "err", err, "runner", runner, "pipelineId", pipeline.Id) @@ -1722,7 +1859,7 @@ func (impl *WorkflowDagExecutorImpl) ManualCdTrigger(overrideRequest *bean.Value impl.logger.Errorf("invalid req", "err", err, "req", overrideRequest) return 0, err } - impl.appService.SetPipelineFieldsInOverrideRequest(overrideRequest, cdPipeline) + impl.SetPipelineFieldsInOverrideRequest(overrideRequest, cdPipeline) if overrideRequest.CdWorkflowType == bean.CD_WORKFLOW_TYPE_PRE { _, span = otel.Tracer("orchestrator").Start(ctx, "ciArtifactRepository.Get") @@ -1849,7 +1986,7 @@ func (impl *WorkflowDagExecutorImpl) ManualCdTrigger(overrideRequest *bean.Value return 0, fmt.Errorf("found vulnerability for image digest %s", artifact.ImageDigest) } _, span = otel.Tracer("orchestrator").Start(ctx, "appService.TriggerRelease") - releaseId, _, err = impl.appService.TriggerRelease(overrideRequest, ctx, triggeredAt, overrideRequest.UserId) + releaseId, _, err = impl.TriggerRelease(overrideRequest, ctx, triggeredAt, overrideRequest.UserId) span.End() if overrideRequest.DeploymentAppType == util.PIPELINE_DEPLOYMENT_TYPE_MANIFEST_DOWNLOAD { @@ -2108,3 +2245,1861 @@ func (impl *WorkflowDagExecutorImpl) buildACDContext() (acdContext context.Conte ctx = context.WithValue(ctx, "token", acdToken) return ctx, nil } + +func (impl *WorkflowDagExecutorImpl) TriggerRelease(overrideRequest *bean.ValuesOverrideRequest, ctx context.Context, triggeredAt time.Time, deployedBy int32) (releaseNo int, manifest []byte, err error) { + triggerEvent := impl.GetTriggerEvent(overrideRequest.DeploymentAppType, triggeredAt, deployedBy) + releaseNo, manifest, err = impl.TriggerPipeline(overrideRequest, triggerEvent, ctx) + if err != nil { + return 0, manifest, err + } + return releaseNo, manifest, nil +} + +func (impl *WorkflowDagExecutorImpl) TriggerCD(artifact *repository.CiArtifact, cdWorkflowId, wfrId int, pipeline *pipelineConfig.Pipeline, triggeredAt time.Time) error { + impl.logger.Debugw("automatic pipeline trigger attempt async", "artifactId", artifact.Id) + + return impl.triggerReleaseAsync(artifact, cdWorkflowId, wfrId, pipeline, triggeredAt) +} + +func (impl *WorkflowDagExecutorImpl) triggerReleaseAsync(artifact *repository.CiArtifact, cdWorkflowId, wfrId int, pipeline *pipelineConfig.Pipeline, triggeredAt time.Time) error { + err := impl.validateAndTrigger(pipeline, artifact, cdWorkflowId, wfrId, triggeredAt) + if err != nil { + impl.logger.Errorw("error in trigger for pipeline", "pipelineId", strconv.Itoa(pipeline.Id)) + } + impl.logger.Debugw("trigger attempted for all pipeline ", "artifactId", artifact.Id) + return err +} + +func (impl *WorkflowDagExecutorImpl) validateAndTrigger(p *pipelineConfig.Pipeline, artifact *repository.CiArtifact, cdWorkflowId, wfrId int, triggeredAt time.Time) error { + object := impl.enforcerUtil.GetAppRBACNameByAppId(p.AppId) + envApp := strings.Split(object, "/") + if len(envApp) != 2 { + impl.logger.Error("invalid req, app and env not found from rbac") + return errors.New("invalid req, app and env not found from rbac") + } + err := impl.releasePipeline(p, artifact, cdWorkflowId, wfrId, triggeredAt) + return err +} + +func (impl *WorkflowDagExecutorImpl) releasePipeline(pipeline *pipelineConfig.Pipeline, artifact *repository.CiArtifact, cdWorkflowId, wfrId int, triggeredAt time.Time) error { + impl.logger.Debugw("triggering release for ", "cdPipelineId", pipeline.Id, "artifactId", artifact.Id) + + pipeline, err := impl.pipelineRepository.FindById(pipeline.Id) + if err != nil { + impl.logger.Errorw("error in fetching pipeline by pipelineId", "err", err) + return err + } + + request := &bean.ValuesOverrideRequest{ + PipelineId: pipeline.Id, + UserId: artifact.CreatedBy, + CiArtifactId: artifact.Id, + AppId: pipeline.AppId, + CdWorkflowId: cdWorkflowId, + ForceTrigger: true, + DeploymentWithConfig: bean.DEPLOYMENT_CONFIG_TYPE_LAST_SAVED, + WfrId: wfrId, + } + impl.SetPipelineFieldsInOverrideRequest(request, pipeline) + + ctx, err := impl.buildACDContext() + if err != nil { + impl.logger.Errorw("error in creating acd synch context", "pipelineId", pipeline.Id, "artifactId", artifact.Id, "err", err) + return err + } + //setting deployedBy as 1(system user) since case of auto trigger + id, _, err := impl.TriggerRelease(request, ctx, triggeredAt, 1) + if err != nil { + impl.logger.Errorw("error in auto cd pipeline trigger", "pipelineId", pipeline.Id, "artifactId", artifact.Id, "err", err) + } else { + impl.logger.Infow("pipeline successfully triggered ", "cdPipelineId", pipeline.Id, "artifactId", artifact.Id, "releaseId", id) + } + return err + +} + +func (impl *WorkflowDagExecutorImpl) SetPipelineFieldsInOverrideRequest(overrideRequest *bean.ValuesOverrideRequest, pipeline *pipelineConfig.Pipeline) { + overrideRequest.PipelineId = pipeline.Id + overrideRequest.PipelineName = pipeline.Name + overrideRequest.EnvId = pipeline.EnvironmentId + overrideRequest.EnvName = pipeline.Environment.Name + overrideRequest.ClusterId = pipeline.Environment.ClusterId + overrideRequest.AppId = pipeline.AppId + overrideRequest.AppName = pipeline.App.AppName + overrideRequest.DeploymentAppType = pipeline.DeploymentAppType +} + +func (impl *WorkflowDagExecutorImpl) GetTriggerEvent(deploymentAppType string, triggeredAt time.Time, deployedBy int32) bean.TriggerEvent { + // trigger event will decide whether to perform GitOps or deployment for a particular deployment app type + triggerEvent := bean.TriggerEvent{ + TriggeredBy: deployedBy, + TriggerdAt: triggeredAt, + } + switch deploymentAppType { + case bean2.ArgoCd: + triggerEvent.PerformChartPush = true + triggerEvent.PerformDeploymentOnCluster = true + triggerEvent.GetManifestInResponse = false + triggerEvent.DeploymentAppType = bean2.ArgoCd + triggerEvent.ManifestStorageType = bean2.ManifestStorageGit + case bean2.Helm: + triggerEvent.PerformChartPush = false + triggerEvent.PerformDeploymentOnCluster = true + triggerEvent.GetManifestInResponse = false + triggerEvent.DeploymentAppType = bean2.Helm + } + return triggerEvent +} + +// write integration/unit test for each function +func (impl *WorkflowDagExecutorImpl) TriggerPipeline(overrideRequest *bean.ValuesOverrideRequest, triggerEvent bean.TriggerEvent, ctx context.Context) (releaseNo int, manifest []byte, err error) { + + isRequestValid, err := impl.ValidateTriggerEvent(triggerEvent) + if !isRequestValid { + return releaseNo, manifest, err + } + + valuesOverrideResponse, builtChartPath, err := impl.BuildManifestForTrigger(overrideRequest, triggerEvent.TriggerdAt, ctx) + _, span := otel.Tracer("orchestrator").Start(ctx, "CreateHistoriesForDeploymentTrigger") + err1 := impl.CreateHistoriesForDeploymentTrigger(valuesOverrideResponse.Pipeline, valuesOverrideResponse.PipelineStrategy, valuesOverrideResponse.EnvOverride, triggerEvent.TriggerdAt, triggerEvent.TriggeredBy) + if err1 != nil { + impl.logger.Errorw("error in saving histories for trigger", "err", err1, "pipelineId", valuesOverrideResponse.Pipeline.Id, "wfrId", overrideRequest.WfrId) + } + span.End() + if err != nil { + return releaseNo, manifest, err + } + + if triggerEvent.PerformChartPush { + manifestPushTemplate, err := impl.BuildManifestPushTemplate(overrideRequest, valuesOverrideResponse, builtChartPath, &manifest) + if err != nil { + impl.logger.Errorw("error in building manifest push template", "err", err) + return releaseNo, manifest, err + } + manifestPushService := impl.GetManifestPushService(triggerEvent) + manifestPushResponse := manifestPushService.PushChart(manifestPushTemplate, ctx) + if manifestPushResponse.Error != nil { + impl.logger.Errorw("Error in pushing manifest to git", "err", err, "git_repo_url", manifestPushTemplate.RepoUrl) + return releaseNo, manifest, err + } + pipelineOverrideUpdateRequest := &chartConfig.PipelineOverride{ + Id: valuesOverrideResponse.PipelineOverride.Id, + GitHash: manifestPushResponse.CommitHash, + CommitTime: manifestPushResponse.CommitTime, + EnvConfigOverrideId: valuesOverrideResponse.EnvOverride.Id, + PipelineOverrideValues: valuesOverrideResponse.ReleaseOverrideJSON, + PipelineId: overrideRequest.PipelineId, + CiArtifactId: overrideRequest.CiArtifactId, + PipelineMergedValues: valuesOverrideResponse.MergedValues, + AuditLog: sql.AuditLog{UpdatedOn: triggerEvent.TriggerdAt, UpdatedBy: overrideRequest.UserId}, + } + _, span := otel.Tracer("orchestrator").Start(ctx, "pipelineOverrideRepository.Update") + err = impl.pipelineOverrideRepository.Update(pipelineOverrideUpdateRequest) + span.End() + } + + if triggerEvent.PerformDeploymentOnCluster { + err = impl.DeployApp(overrideRequest, valuesOverrideResponse, triggerEvent.TriggerdAt, ctx) + if err != nil { + impl.logger.Errorw("error in deploying app", "err", err) + return releaseNo, manifest, err + } + } + + go impl.WriteCDTriggerEvent(overrideRequest, valuesOverrideResponse.Artifact, valuesOverrideResponse.PipelineOverride.PipelineReleaseCounter, valuesOverrideResponse.PipelineOverride.Id) + + _, spann := otel.Tracer("orchestrator").Start(ctx, "MarkImageScanDeployed") + _ = impl.MarkImageScanDeployed(overrideRequest.AppId, valuesOverrideResponse.EnvOverride.TargetEnvironment, valuesOverrideResponse.Artifact.ImageDigest, overrideRequest.ClusterId, valuesOverrideResponse.Artifact.ScanEnabled) + spann.End() + + middleware.CdTriggerCounter.WithLabelValues(overrideRequest.AppName, overrideRequest.EnvName).Inc() + + return valuesOverrideResponse.PipelineOverride.PipelineReleaseCounter, manifest, nil + +} + +func (impl *WorkflowDagExecutorImpl) ValidateTriggerEvent(triggerEvent bean.TriggerEvent) (bool, error) { + + switch triggerEvent.DeploymentAppType { + case bean2.ArgoCd: + if !triggerEvent.PerformChartPush { + return false, errors2.New("For deployment type ArgoCd, PerformChartPush flag expected value = true, got false") + } + case bean2.Helm: + return true, nil + case bean2.GitOpsWithoutDeployment: + if triggerEvent.PerformDeploymentOnCluster { + return false, errors2.New("For deployment type GitOpsWithoutDeployment, PerformDeploymentOnCluster flag expected value = false, got value = true") + } + case bean2.ManifestDownload: + if triggerEvent.PerformChartPush { + return false, errors3.New("For deployment type ManifestDownload, PerformChartPush flag expected value = false, got true") + } + if triggerEvent.PerformDeploymentOnCluster { + return false, errors3.New("For deployment type ManifestDownload, PerformDeploymentOnCluster flag expected value = false, got true") + } + } + return true, nil + +} + +func (impl *WorkflowDagExecutorImpl) BuildManifestForTrigger(overrideRequest *bean.ValuesOverrideRequest, triggeredAt time.Time, ctx context.Context) (valuesOverrideResponse *app.ValuesOverrideResponse, builtChartPath string, err error) { + + valuesOverrideResponse = &app.ValuesOverrideResponse{} + valuesOverrideResponse, err = impl.GetValuesOverrideForTrigger(overrideRequest, triggeredAt, ctx) + if err != nil { + impl.logger.Errorw("error in fetching values for trigger", "err", err) + return valuesOverrideResponse, "", err + } + builtChartPath, err = impl.appService.BuildChartAndGetPath(overrideRequest.AppName, valuesOverrideResponse.EnvOverride, ctx) + if err != nil { + impl.logger.Errorw("error in parsing reference chart", "err", err) + return valuesOverrideResponse, "", err + } + return valuesOverrideResponse, builtChartPath, err +} + +func (impl *WorkflowDagExecutorImpl) CreateHistoriesForDeploymentTrigger(pipeline *pipelineConfig.Pipeline, strategy *chartConfig.PipelineStrategy, envOverride *chartConfig.EnvConfigOverride, deployedOn time.Time, deployedBy int32) error { + //creating history for deployment template + deploymentTemplateHistory, err := impl.deploymentTemplateHistoryService.CreateDeploymentTemplateHistoryForDeploymentTrigger(pipeline, envOverride, envOverride.Chart.ImageDescriptorTemplate, deployedOn, deployedBy) + if err != nil { + impl.logger.Errorw("error in creating deployment template history for deployment trigger", "err", err) + return err + } + err = impl.configMapHistoryService.CreateCMCSHistoryForDeploymentTrigger(pipeline, deployedOn, deployedBy) + if err != nil { + impl.logger.Errorw("error in creating CM/CS history for deployment trigger", "err", err) + return err + } + if strategy != nil { + err = impl.pipelineStrategyHistoryService.CreateStrategyHistoryForDeploymentTrigger(strategy, deployedOn, deployedBy, pipeline.TriggerType) + if err != nil { + impl.logger.Errorw("error in creating strategy history for deployment trigger", "err", err) + return err + } + } + //VARIABLE_SNAPSHOT_SAVE + if envOverride.VariableSnapshot != nil && len(envOverride.VariableSnapshot) > 0 { + variableMapBytes, _ := json.Marshal(envOverride.VariableSnapshot) + variableSnapshotHistory := &repository5.VariableSnapshotHistoryBean{ + VariableSnapshot: variableMapBytes, + HistoryReference: repository5.HistoryReference{ + HistoryReferenceId: deploymentTemplateHistory.Id, + HistoryReferenceType: repository5.HistoryReferenceTypeDeploymentTemplate, + }, + } + err = impl.variableSnapshotHistoryService.SaveVariableHistoriesForTrigger([]*repository5.VariableSnapshotHistoryBean{variableSnapshotHistory}, deployedBy) + if err != nil { + return err + } + } + return nil +} + +func (impl *WorkflowDagExecutorImpl) BuildManifestPushTemplate(overrideRequest *bean.ValuesOverrideRequest, valuesOverrideResponse *app.ValuesOverrideResponse, builtChartPath string, manifest *[]byte) (*bean4.ManifestPushTemplate, error) { + + manifestPushTemplate := &bean4.ManifestPushTemplate{ + WorkflowRunnerId: overrideRequest.WfrId, + AppId: overrideRequest.AppId, + ChartRefId: valuesOverrideResponse.EnvOverride.Chart.ChartRefId, + EnvironmentId: valuesOverrideResponse.EnvOverride.Environment.Id, + UserId: overrideRequest.UserId, + PipelineOverrideId: valuesOverrideResponse.PipelineOverride.Id, + AppName: overrideRequest.AppName, + TargetEnvironmentName: valuesOverrideResponse.EnvOverride.TargetEnvironment, + BuiltChartPath: builtChartPath, + BuiltChartBytes: manifest, + MergedValues: valuesOverrideResponse.MergedValues, + } + + manifestPushConfig, err := impl.manifestPushConfigRepository.GetManifestPushConfigByAppIdAndEnvId(overrideRequest.AppId, overrideRequest.EnvId) + if err != nil && err != pg.ErrNoRows { + impl.logger.Errorw("error in fetching manifest push config from db", "err", err) + return manifestPushTemplate, err + } + + if manifestPushConfig != nil { + if manifestPushConfig.StorageType == bean2.ManifestStorageGit { + // need to implement for git repo push + // currently manifest push config doesn't have git push config. Gitops config is derived from charts, chart_env_config_override and chart_ref table + } + } else { + manifestPushTemplate.ChartReferenceTemplate = valuesOverrideResponse.EnvOverride.Chart.ReferenceTemplate + manifestPushTemplate.ChartName = valuesOverrideResponse.EnvOverride.Chart.ChartName + manifestPushTemplate.ChartVersion = valuesOverrideResponse.EnvOverride.Chart.ChartVersion + manifestPushTemplate.ChartLocation = valuesOverrideResponse.EnvOverride.Chart.ChartLocation + manifestPushTemplate.RepoUrl = valuesOverrideResponse.EnvOverride.Chart.GitRepoUrl + } + return manifestPushTemplate, err +} + +func (impl *WorkflowDagExecutorImpl) GetManifestPushService(triggerEvent bean.TriggerEvent) app.ManifestPushService { + var manifestPushService app.ManifestPushService + if triggerEvent.ManifestStorageType == bean2.ManifestStorageGit { + manifestPushService = impl.gitOpsManifestPushService + } + return manifestPushService +} + +func (impl *WorkflowDagExecutorImpl) DeployApp(overrideRequest *bean.ValuesOverrideRequest, valuesOverrideResponse *app.ValuesOverrideResponse, triggeredAt time.Time, ctx context.Context) error { + + if util.IsAcdApp(overrideRequest.DeploymentAppType) { + _, span := otel.Tracer("orchestrator").Start(ctx, "DeployArgocdApp") + err := impl.DeployArgocdApp(overrideRequest, valuesOverrideResponse, ctx) + span.End() + if err != nil { + impl.logger.Errorw("error in deploying app on argocd", "err", err) + return err + } + } else if util.IsHelmApp(overrideRequest.DeploymentAppType) { + _, span := otel.Tracer("orchestrator").Start(ctx, "createHelmAppForCdPipeline") + _, err := impl.createHelmAppForCdPipeline(overrideRequest, valuesOverrideResponse, triggeredAt, ctx) + span.End() + if err != nil { + impl.logger.Errorw("error in creating or updating helm application for cd pipeline", "err", err) + return err + } + } + return nil +} + +func (impl *WorkflowDagExecutorImpl) WriteCDTriggerEvent(overrideRequest *bean.ValuesOverrideRequest, artifact *repository.CiArtifact, releaseId, pipelineOverrideId int) { + + event := impl.eventFactory.Build(util2.Trigger, &overrideRequest.PipelineId, overrideRequest.AppId, &overrideRequest.EnvId, util2.CD) + impl.logger.Debugw("event WriteCDTriggerEvent", "event", event) + event = impl.eventFactory.BuildExtraCDData(event, nil, pipelineOverrideId, bean.CD_WORKFLOW_TYPE_DEPLOY) + _, evtErr := impl.eventClient.WriteNotificationEvent(event) + if evtErr != nil { + impl.logger.Errorw("CD trigger event not sent", "error", evtErr) + } + deploymentEvent := app.DeploymentEvent{ + ApplicationId: overrideRequest.AppId, + EnvironmentId: overrideRequest.EnvId, //check for production Environment + ReleaseId: releaseId, + PipelineOverrideId: pipelineOverrideId, + TriggerTime: time.Now(), + CiArtifactId: overrideRequest.CiArtifactId, + } + ciPipelineMaterials, err := impl.ciPipelineMaterialRepository.GetByPipelineId(artifact.PipelineId) + if err != nil { + impl.logger.Errorw("error in ") + } + materialInfoMap, mErr := artifact.ParseMaterialInfo() + if mErr != nil { + impl.logger.Errorw("material info map error", mErr) + return + } + for _, ciPipelineMaterial := range ciPipelineMaterials { + hash := materialInfoMap[ciPipelineMaterial.GitMaterial.Url] + pipelineMaterialInfo := &app.PipelineMaterialInfo{PipelineMaterialId: ciPipelineMaterial.Id, CommitHash: hash} + deploymentEvent.PipelineMaterials = append(deploymentEvent.PipelineMaterials, pipelineMaterialInfo) + } + impl.logger.Infow("triggering deployment event", "event", deploymentEvent) + err = impl.eventClient.WriteNatsEvent(pubsub.CD_SUCCESS, deploymentEvent) + if err != nil { + impl.logger.Errorw("error in writing cd trigger event", "err", err) + } +} + +func (impl *WorkflowDagExecutorImpl) MarkImageScanDeployed(appId int, envId int, imageDigest string, clusterId int, isScanEnabled bool) error { + impl.logger.Debugw("mark image scan deployed for normal app, from cd auto or manual trigger", "imageDigest", imageDigest) + executionHistory, err := impl.imageScanHistoryRepository.FindByImageDigest(imageDigest) + if err != nil && err != pg.ErrNoRows { + impl.logger.Errorw("error in fetching execution history", "err", err) + return err + } + if executionHistory == nil || executionHistory.Id == 0 { + impl.logger.Errorw("no execution history found for digest", "digest", imageDigest) + return fmt.Errorf("no execution history found for digest - %s", imageDigest) + } + impl.logger.Debugw("mark image scan deployed for normal app, from cd auto or manual trigger", "executionHistory", executionHistory) + var ids []int + ids = append(ids, executionHistory.Id) + + ot, err := impl.imageScanDeployInfoRepository.FetchByAppIdAndEnvId(appId, envId, []string{security.ScanObjectType_APP}) + + if err == pg.ErrNoRows && !isScanEnabled { + //ignoring if no rows are found and scan is disabled + return nil + } + + if err != nil && err != pg.ErrNoRows { + return err + } else if err == pg.ErrNoRows && isScanEnabled { + imageScanDeployInfo := &security.ImageScanDeployInfo{ + ImageScanExecutionHistoryId: ids, + ScanObjectMetaId: appId, + ObjectType: security.ScanObjectType_APP, + EnvId: envId, + ClusterId: clusterId, + AuditLog: sql.AuditLog{ + CreatedOn: time.Now(), + CreatedBy: 1, + UpdatedOn: time.Now(), + UpdatedBy: 1, + }, + } + impl.logger.Debugw("mark image scan deployed for normal app, from cd auto or manual trigger", "imageScanDeployInfo", imageScanDeployInfo) + err = impl.imageScanDeployInfoRepository.Save(imageScanDeployInfo) + if err != nil { + impl.logger.Errorw("error in creating deploy info", "err", err) + } + } else { + // Updating Execution history for Latest Deployment to fetch out security Vulnerabilities for latest deployed info + if isScanEnabled { + ot.ImageScanExecutionHistoryId = ids + } else { + arr := []int{-1} + ot.ImageScanExecutionHistoryId = arr + } + err = impl.imageScanDeployInfoRepository.Update(ot) + if err != nil { + impl.logger.Errorw("error in updating deploy info for latest deployed image", "err", err) + } + } + return err +} + +func (impl *WorkflowDagExecutorImpl) GetValuesOverrideForTrigger(overrideRequest *bean.ValuesOverrideRequest, triggeredAt time.Time, ctx context.Context) (*app.ValuesOverrideResponse, error) { + if overrideRequest.DeploymentType == models.DEPLOYMENTTYPE_UNKNOWN { + overrideRequest.DeploymentType = models.DEPLOYMENTTYPE_DEPLOY + } + if len(overrideRequest.DeploymentWithConfig) == 0 { + overrideRequest.DeploymentWithConfig = bean.DEPLOYMENT_CONFIG_TYPE_LAST_SAVED + } + valuesOverrideResponse := &app.ValuesOverrideResponse{} + + pipeline, err := impl.pipelineRepository.FindById(overrideRequest.PipelineId) + valuesOverrideResponse.Pipeline = pipeline + if err != nil { + impl.logger.Errorw("error in fetching pipeline by pipeline id", "err", err, "pipeline-id-", overrideRequest.PipelineId) + return valuesOverrideResponse, err + } + + _, span := otel.Tracer("orchestrator").Start(ctx, "ciArtifactRepository.Get") + artifact, err := impl.ciArtifactRepository.Get(overrideRequest.CiArtifactId) + valuesOverrideResponse.Artifact = artifact + span.End() + if err != nil { + return valuesOverrideResponse, err + } + overrideRequest.Image = artifact.Image + + strategy, err := impl.GetDeploymentStrategyByTriggerType(overrideRequest, ctx) + valuesOverrideResponse.PipelineStrategy = strategy + if err != nil { + impl.logger.Errorw("error in getting strategy by trigger type", "err", err) + return valuesOverrideResponse, err + } + + envOverride, err := impl.GetEnvOverrideByTriggerType(overrideRequest, triggeredAt, ctx) + valuesOverrideResponse.EnvOverride = envOverride + if err != nil { + impl.logger.Errorw("error in getting env override by trigger type", "err", err) + return valuesOverrideResponse, err + } + appMetrics, err := impl.GetAppMetricsByTriggerType(overrideRequest, ctx) + valuesOverrideResponse.AppMetrics = appMetrics + if err != nil { + impl.logger.Errorw("error in getting app metrics by trigger type", "err", err) + return valuesOverrideResponse, err + } + + _, span = otel.Tracer("orchestrator").Start(ctx, "getDbMigrationOverride") + //FIXME: how to determine rollback + //we can't depend on ciArtifact ID because CI pipeline can be manually triggered in any order regardless of sourcecode status + dbMigrationOverride, err := impl.getDbMigrationOverride(overrideRequest, artifact, false) + span.End() + if err != nil { + impl.logger.Errorw("error in fetching db migration config", "req", overrideRequest, "err", err) + return valuesOverrideResponse, err + } + chartVersion := envOverride.Chart.ChartVersion + _, span = otel.Tracer("orchestrator").Start(ctx, "getConfigMapAndSecretJsonV2") + configMapJson, err := impl.getConfigMapAndSecretJsonV2(overrideRequest.AppId, envOverride.TargetEnvironment, overrideRequest.PipelineId, chartVersion, overrideRequest.DeploymentWithConfig, overrideRequest.WfrIdForDeploymentWithSpecificTrigger) + span.End() + if err != nil { + impl.logger.Errorw("error in fetching config map n secret ", "err", err) + configMapJson = nil + } + _, span = otel.Tracer("orchestrator").Start(ctx, "appCrudOperationService.GetLabelsByAppIdForDeployment") + appLabelJsonByte, err := impl.appCrudOperationService.GetLabelsByAppIdForDeployment(overrideRequest.AppId) + span.End() + if err != nil { + impl.logger.Errorw("error in fetching app labels for gitOps commit", "err", err) + appLabelJsonByte = nil + } + _, span = otel.Tracer("orchestrator").Start(ctx, "mergeAndSave") + pipelineOverride, err := impl.savePipelineOverride(overrideRequest, envOverride.Id, triggeredAt) + valuesOverrideResponse.PipelineOverride = pipelineOverride + if err != nil { + return valuesOverrideResponse, err + } + //TODO: check status and apply lock + releaseOverrideJson, err := impl.getReleaseOverride(envOverride, overrideRequest, artifact, pipelineOverride, strategy, &appMetrics) + valuesOverrideResponse.ReleaseOverrideJSON = releaseOverrideJson + if err != nil { + return valuesOverrideResponse, err + } + mergedValues, err := impl.mergeOverrideValues(envOverride, dbMigrationOverride, releaseOverrideJson, configMapJson, appLabelJsonByte, strategy) + + appName := fmt.Sprintf("%s-%s", overrideRequest.AppName, envOverride.Environment.Name) + mergedValues = impl.autoscalingCheckBeforeTrigger(ctx, appName, envOverride.Namespace, mergedValues, overrideRequest) + + _, span = otel.Tracer("orchestrator").Start(ctx, "dockerRegistryIpsConfigService.HandleImagePullSecretOnApplicationDeployment") + // handle image pull secret if access given + mergedValues, err = impl.dockerRegistryIpsConfigService.HandleImagePullSecretOnApplicationDeployment(envOverride.Environment, pipeline.CiPipelineId, mergedValues) + valuesOverrideResponse.MergedValues = string(mergedValues) + span.End() + if err != nil { + return valuesOverrideResponse, err + } + pipelineOverride.PipelineMergedValues = string(mergedValues) + err = impl.pipelineOverrideRepository.Update(pipelineOverride) + if err != nil { + return valuesOverrideResponse, err + } + return valuesOverrideResponse, err +} + +func (impl *WorkflowDagExecutorImpl) DeployArgocdApp(overrideRequest *bean.ValuesOverrideRequest, valuesOverrideResponse *app.ValuesOverrideResponse, ctx context.Context) error { + + impl.logger.Debugw("new pipeline found", "pipeline", valuesOverrideResponse.Pipeline) + _, span := otel.Tracer("orchestrator").Start(ctx, "createArgoApplicationIfRequired") + name, err := impl.createArgoApplicationIfRequired(overrideRequest.AppId, valuesOverrideResponse.EnvOverride, valuesOverrideResponse.Pipeline, overrideRequest.UserId) + span.End() + if err != nil { + impl.logger.Errorw("acd application create error on cd trigger", "err", err, "req", overrideRequest) + return err + } + impl.logger.Debugw("argocd application created", "name", name) + + _, span = otel.Tracer("orchestrator").Start(ctx, "updateArgoPipeline") + updateAppInArgocd, err := impl.updateArgoPipeline(overrideRequest.AppId, valuesOverrideResponse.Pipeline.Name, valuesOverrideResponse.EnvOverride, ctx) + span.End() + if err != nil { + impl.logger.Errorw("error in updating argocd app ", "err", err) + return err + } + if updateAppInArgocd { + impl.logger.Debug("argo-cd successfully updated") + } else { + impl.logger.Debug("argo-cd failed to update, ignoring it") + } + return nil +} +func (impl *WorkflowDagExecutorImpl) createArgoApplicationIfRequired(appId int, envConfigOverride *chartConfig.EnvConfigOverride, pipeline *pipelineConfig.Pipeline, userId int32) (string, error) { + //repo has been registered while helm create + chart, err := impl.chartRepository.FindLatestChartForAppByAppId(appId) + if err != nil { + impl.logger.Errorw("no chart found ", "app", appId) + return "", err + } + envModel, err := impl.envRepository.FindById(envConfigOverride.TargetEnvironment) + if err != nil { + return "", err + } + argoAppName := pipeline.DeploymentAppName + if pipeline.DeploymentAppCreated { + return argoAppName, nil + } else { + //create + appNamespace := envConfigOverride.Namespace + if appNamespace == "" { + appNamespace = "default" + } + namespace := argocdServer.DevtronInstalationNs + appRequest := &argocdServer.AppTemplate{ + ApplicationName: argoAppName, + Namespace: namespace, + TargetNamespace: appNamespace, + TargetServer: envModel.Cluster.ServerUrl, + Project: "default", + ValuesFile: impl.getValuesFileForEnv(envModel.Id), + RepoPath: chart.ChartLocation, + RepoUrl: chart.GitRepoUrl, + } + + argoAppName, err := impl.argoK8sClient.CreateAcdApp(appRequest, envModel.Cluster) + if err != nil { + return "", err + } + //update cd pipeline to mark deployment app created + _, err = impl.updatePipeline(pipeline, userId) + if err != nil { + impl.logger.Errorw("error in update cd pipeline for deployment app created or not", "err", err) + return "", err + } + return argoAppName, nil + } +} + +func (impl *WorkflowDagExecutorImpl) createHelmAppForCdPipeline(overrideRequest *bean.ValuesOverrideRequest, valuesOverrideResponse *app.ValuesOverrideResponse, triggeredAt time.Time, ctx context.Context) (bool, error) { + + pipeline := valuesOverrideResponse.Pipeline + envOverride := valuesOverrideResponse.EnvOverride + mergeAndSave := valuesOverrideResponse.MergedValues + + chartMetaData := &chart.Metadata{ + Name: pipeline.App.AppName, + Version: envOverride.Chart.ChartVersion, + } + referenceTemplatePath := path.Join(string(impl.refChartDir), envOverride.Chart.ReferenceTemplate) + + if util.IsHelmApp(pipeline.DeploymentAppType) { + referenceChartByte := envOverride.Chart.ReferenceChart + // here updating reference chart into database. + if len(envOverride.Chart.ReferenceChart) == 0 { + refChartByte, err := impl.chartTemplateService.GetByteArrayRefChart(chartMetaData, referenceTemplatePath) + if err != nil { + impl.logger.Errorw("ref chart commit error on cd trigger", "err", err, "req", overrideRequest) + return false, err + } + ch := envOverride.Chart + ch.ReferenceChart = refChartByte + ch.UpdatedOn = time.Now() + ch.UpdatedBy = overrideRequest.UserId + err = impl.chartRepository.Update(ch) + if err != nil { + impl.logger.Errorw("chart update error", "err", err, "req", overrideRequest) + return false, err + } + referenceChartByte = refChartByte + } + + releaseName := pipeline.DeploymentAppName + cluster := envOverride.Environment.Cluster + bearerToken := cluster.Config[util5.BearerToken] + clusterConfig := &client2.ClusterConfig{ + ClusterName: cluster.ClusterName, + Token: bearerToken, + ApiServerUrl: cluster.ServerUrl, + InsecureSkipTLSVerify: cluster.InsecureSkipTlsVerify, + } + if cluster.InsecureSkipTlsVerify == false { + clusterConfig.KeyData = cluster.Config[util5.TlsKey] + clusterConfig.CertData = cluster.Config[util5.CertData] + clusterConfig.CaData = cluster.Config[util5.CertificateAuthorityData] + } + releaseIdentifier := &client2.ReleaseIdentifier{ + ReleaseName: releaseName, + ReleaseNamespace: envOverride.Namespace, + ClusterConfig: clusterConfig, + } + + if pipeline.DeploymentAppCreated { + req := &client2.UpgradeReleaseRequest{ + ReleaseIdentifier: releaseIdentifier, + ValuesYaml: mergeAndSave, + HistoryMax: impl.helmAppService.GetRevisionHistoryMaxValue(client2.SOURCE_DEVTRON_APP), + ChartContent: &client2.ChartContent{Content: referenceChartByte}, + } + + updateApplicationResponse, err := impl.helmAppClient.UpdateApplication(ctx, req) + + // For cases where helm release was not found but db flag for deployment app created was true + if err != nil && strings.Contains(err.Error(), "release: not found") { + + // retry install + _, err = impl.helmInstallReleaseWithCustomChart(ctx, releaseIdentifier, referenceChartByte, mergeAndSave) + + // if retry failed, return + if err != nil { + impl.logger.Errorw("release not found, failed to re-install helm application", "err", err) + return false, err + } + } else if err != nil { + impl.logger.Errorw("error in updating helm application for cd pipeline", "err", err) + return false, err + } else { + impl.logger.Debugw("updated helm application", "response", updateApplicationResponse, "isSuccess", updateApplicationResponse.Success) + } + + } else { + + helmResponse, err := impl.helmInstallReleaseWithCustomChart(ctx, releaseIdentifier, referenceChartByte, mergeAndSave) + + // For connection related errors, no need to update the db + if err != nil && strings.Contains(err.Error(), "connection error") { + impl.logger.Errorw("error in helm install custom chart", "err", err) + return false, err + } + + // IMP: update cd pipeline to mark deployment app created, even if helm install fails + // If the helm install fails, it still creates the app in failed state, so trying to + // re-create the app results in error from helm that cannot re-use name which is still in use + _, pgErr := impl.updatePipeline(pipeline, overrideRequest.UserId) + + if err != nil { + impl.logger.Errorw("error in helm install custom chart", "err", err) + + if pgErr != nil { + impl.logger.Errorw("failed to update deployment app created flag in pipeline table", "err", err) + } + return false, err + } + + if pgErr != nil { + impl.logger.Errorw("failed to update deployment app created flag in pipeline table", "err", err) + return false, err + } + + impl.logger.Debugw("received helm release response", "helmResponse", helmResponse, "isSuccess", helmResponse.Success) + } + + //update workflow runner status, used in app workflow view + cdWf, err := impl.cdWorkflowRepository.FindByWorkflowIdAndRunnerType(ctx, overrideRequest.CdWorkflowId, bean.CD_WORKFLOW_TYPE_DEPLOY) + if err != nil && err != pg.ErrNoRows { + impl.logger.Errorw("err on fetching cd workflow", "err", err) + return false, err + } + cdWorkflowId := cdWf.CdWorkflowId + if cdWf.CdWorkflowId == 0 { + cdWf := &pipelineConfig.CdWorkflow{ + CiArtifactId: overrideRequest.CiArtifactId, + PipelineId: overrideRequest.PipelineId, + AuditLog: sql.AuditLog{CreatedOn: triggeredAt, CreatedBy: overrideRequest.UserId, UpdatedOn: triggeredAt, UpdatedBy: overrideRequest.UserId}, + } + err := impl.cdWorkflowRepository.SaveWorkFlow(ctx, cdWf) + if err != nil { + impl.logger.Errorw("err on updating cd workflow for status update", "err", err) + return false, err + } + cdWorkflowId = cdWf.Id + runner := &pipelineConfig.CdWorkflowRunner{ + Id: cdWf.Id, + Name: pipeline.Name, + WorkflowType: bean.CD_WORKFLOW_TYPE_DEPLOY, + ExecutorType: pipelineConfig.WORKFLOW_EXECUTOR_TYPE_AWF, + Status: pipelineConfig.WorkflowInProgress, + TriggeredBy: overrideRequest.UserId, + StartedOn: triggeredAt, + CdWorkflowId: cdWorkflowId, + AuditLog: sql.AuditLog{CreatedOn: triggeredAt, CreatedBy: overrideRequest.UserId, UpdatedOn: triggeredAt, UpdatedBy: overrideRequest.UserId}, + } + _, err = impl.cdWorkflowRepository.SaveWorkFlowRunner(runner) + if err != nil { + impl.logger.Errorw("err on updating cd workflow runner for status update", "err", err) + return false, err + } + } else { + cdWf.Status = pipelineConfig.WorkflowInProgress + cdWf.FinishedOn = time.Now() + cdWf.UpdatedBy = overrideRequest.UserId + cdWf.UpdatedOn = time.Now() + err = impl.cdWorkflowRepository.UpdateWorkFlowRunner(&cdWf) + if err != nil { + impl.logger.Errorw("error on update cd workflow runner", "cdWf", cdWf, "err", err) + return false, err + } + } + } + return true, nil +} + +func (impl *WorkflowDagExecutorImpl) GetDeploymentStrategyByTriggerType(overrideRequest *bean.ValuesOverrideRequest, ctx context.Context) (*chartConfig.PipelineStrategy, error) { + + strategy := &chartConfig.PipelineStrategy{} + var err error + if overrideRequest.DeploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_SPECIFIC_TRIGGER { + _, span := otel.Tracer("orchestrator").Start(ctx, "strategyHistoryRepository.GetHistoryByPipelineIdAndWfrId") + strategyHistory, err := impl.strategyHistoryRepository.GetHistoryByPipelineIdAndWfrId(overrideRequest.PipelineId, overrideRequest.WfrIdForDeploymentWithSpecificTrigger) + span.End() + if err != nil { + impl.logger.Errorw("error in getting deployed strategy history by pipleinId and wfrId", "err", err, "pipelineId", overrideRequest.PipelineId, "wfrId", overrideRequest.WfrIdForDeploymentWithSpecificTrigger) + return nil, err + } + strategy.Strategy = strategyHistory.Strategy + strategy.Config = strategyHistory.Config + strategy.PipelineId = overrideRequest.PipelineId + } else if overrideRequest.DeploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_LAST_SAVED { + if overrideRequest.ForceTrigger { + _, span := otel.Tracer("orchestrator").Start(ctx, "pipelineConfigRepository.GetDefaultStrategyByPipelineId") + strategy, err = impl.pipelineConfigRepository.GetDefaultStrategyByPipelineId(overrideRequest.PipelineId) + span.End() + } else { + var deploymentTemplate chartRepoRepository.DeploymentStrategy + if overrideRequest.DeploymentTemplate == "ROLLING" { + deploymentTemplate = chartRepoRepository.DEPLOYMENT_STRATEGY_ROLLING + } else if overrideRequest.DeploymentTemplate == "BLUE-GREEN" { + deploymentTemplate = chartRepoRepository.DEPLOYMENT_STRATEGY_BLUE_GREEN + } else if overrideRequest.DeploymentTemplate == "CANARY" { + deploymentTemplate = chartRepoRepository.DEPLOYMENT_STRATEGY_CANARY + } else if overrideRequest.DeploymentTemplate == "RECREATE" { + deploymentTemplate = chartRepoRepository.DEPLOYMENT_STRATEGY_RECREATE + } + + if len(deploymentTemplate) > 0 { + _, span := otel.Tracer("orchestrator").Start(ctx, "pipelineConfigRepository.FindByStrategyAndPipelineId") + strategy, err = impl.pipelineConfigRepository.FindByStrategyAndPipelineId(deploymentTemplate, overrideRequest.PipelineId) + span.End() + } else { + _, span := otel.Tracer("orchestrator").Start(ctx, "pipelineConfigRepository.GetDefaultStrategyByPipelineId") + strategy, err = impl.pipelineConfigRepository.GetDefaultStrategyByPipelineId(overrideRequest.PipelineId) + span.End() + } + } + if err != nil && errors2.IsNotFound(err) == false { + impl.logger.Errorf("invalid state", "err", err, "req", strategy) + return nil, err + } + } + return strategy, nil +} + +func (impl *WorkflowDagExecutorImpl) GetEnvOverrideByTriggerType(overrideRequest *bean.ValuesOverrideRequest, triggeredAt time.Time, ctx context.Context) (*chartConfig.EnvConfigOverride, error) { + + envOverride := &chartConfig.EnvConfigOverride{} + + var err error + if overrideRequest.DeploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_SPECIFIC_TRIGGER { + _, span := otel.Tracer("orchestrator").Start(ctx, "deploymentTemplateHistoryRepository.GetHistoryByPipelineIdAndWfrId") + deploymentTemplateHistory, err := impl.deploymentTemplateHistoryRepository.GetHistoryByPipelineIdAndWfrId(overrideRequest.PipelineId, overrideRequest.WfrIdForDeploymentWithSpecificTrigger) + //VARIABLE_SNAPSHOT_GET and resolve + + span.End() + if err != nil { + impl.logger.Errorw("error in getting deployed deployment template history by pipelineId and wfrId", "err", err, "pipelineId", &overrideRequest, "wfrId", overrideRequest.WfrIdForDeploymentWithSpecificTrigger) + return nil, err + } + templateName := deploymentTemplateHistory.TemplateName + templateVersion := deploymentTemplateHistory.TemplateVersion + if templateName == "Rollout Deployment" { + templateName = "" + } + //getting chart_ref by id + _, span = otel.Tracer("orchestrator").Start(ctx, "chartRefRepository.FindByVersionAndName") + chartRef, err := impl.chartRefRepository.FindByVersionAndName(templateName, templateVersion) + span.End() + if err != nil { + impl.logger.Errorw("error in getting chartRef by version and name", "err", err, "version", templateVersion, "name", templateName) + return nil, err + } + //assuming that if a chartVersion is deployed then it's envConfigOverride will be available + _, span = otel.Tracer("orchestrator").Start(ctx, "environmentConfigRepository.GetByAppIdEnvIdAndChartRefId") + envOverride, err = impl.environmentConfigRepository.GetByAppIdEnvIdAndChartRefId(overrideRequest.AppId, overrideRequest.EnvId, chartRef.Id) + span.End() + if err != nil { + impl.logger.Errorw("error in getting envConfigOverride for pipeline for specific chartVersion", "err", err, "appId", overrideRequest.AppId, "envId", overrideRequest.EnvId, "chartRefId", chartRef.Id) + return nil, err + } + + _, span = otel.Tracer("orchestrator").Start(ctx, "envRepository.FindById") + env, err := impl.envRepository.FindById(envOverride.TargetEnvironment) + span.End() + if err != nil { + impl.logger.Errorw("unable to find env", "err", err) + return nil, err + } + envOverride.Environment = env + + //updating historical data in envConfigOverride and appMetrics flag + envOverride.IsOverride = true + envOverride.EnvOverrideValues = deploymentTemplateHistory.Template + + resolvedTemplate, variableMap, err := impl.getResolvedTemplateWithSnapshot(deploymentTemplateHistory.Id, envOverride.EnvOverrideValues) + envOverride.ResolvedEnvOverrideValues = resolvedTemplate + envOverride.VariableSnapshot = variableMap + if err != nil { + return envOverride, err + } + } else if overrideRequest.DeploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_LAST_SAVED { + _, span := otel.Tracer("orchestrator").Start(ctx, "environmentConfigRepository.ActiveEnvConfigOverride") + envOverride, err = impl.environmentConfigRepository.ActiveEnvConfigOverride(overrideRequest.AppId, overrideRequest.EnvId) + + var chart *chartRepoRepository.Chart + span.End() + if err != nil { + impl.logger.Errorw("invalid state", "err", err, "req", overrideRequest) + return nil, err + } + if envOverride.Id == 0 { + _, span = otel.Tracer("orchestrator").Start(ctx, "chartRepository.FindLatestChartForAppByAppId") + chart, err = impl.chartRepository.FindLatestChartForAppByAppId(overrideRequest.AppId) + span.End() + if err != nil { + impl.logger.Errorw("invalid state", "err", err, "req", overrideRequest) + return nil, err + } + _, span = otel.Tracer("orchestrator").Start(ctx, "environmentConfigRepository.FindChartByAppIdAndEnvIdAndChartRefId") + envOverride, err = impl.environmentConfigRepository.FindChartByAppIdAndEnvIdAndChartRefId(overrideRequest.AppId, overrideRequest.EnvId, chart.ChartRefId) + span.End() + if err != nil && !errors2.IsNotFound(err) { + impl.logger.Errorw("invalid state", "err", err, "req", overrideRequest) + return nil, err + } + + //creating new env override config + if errors2.IsNotFound(err) || envOverride == nil { + _, span = otel.Tracer("orchestrator").Start(ctx, "envRepository.FindById") + environment, err := impl.envRepository.FindById(overrideRequest.EnvId) + span.End() + if err != nil && !util.IsErrNoRows(err) { + return nil, err + } + envOverride = &chartConfig.EnvConfigOverride{ + Active: true, + ManualReviewed: true, + Status: models.CHARTSTATUS_SUCCESS, + TargetEnvironment: overrideRequest.EnvId, + ChartId: chart.Id, + AuditLog: sql.AuditLog{UpdatedBy: overrideRequest.UserId, UpdatedOn: triggeredAt, CreatedOn: triggeredAt, CreatedBy: overrideRequest.UserId}, + Namespace: environment.Namespace, + IsOverride: false, + EnvOverrideValues: "{}", + Latest: false, + IsBasicViewLocked: chart.IsBasicViewLocked, + CurrentViewEditor: chart.CurrentViewEditor, + } + _, span = otel.Tracer("orchestrator").Start(ctx, "environmentConfigRepository.Save") + err = impl.environmentConfigRepository.Save(envOverride) + span.End() + if err != nil { + impl.logger.Errorw("error in creating envconfig", "data", envOverride, "error", err) + return nil, err + } + } + envOverride.Chart = chart + } else if envOverride.Id > 0 && !envOverride.IsOverride { + _, span = otel.Tracer("orchestrator").Start(ctx, "chartRepository.FindLatestChartForAppByAppId") + chart, err = impl.chartRepository.FindLatestChartForAppByAppId(overrideRequest.AppId) + span.End() + if err != nil { + impl.logger.Errorw("invalid state", "err", err, "req", overrideRequest) + return nil, err + } + envOverride.Chart = chart + } + + _, span = otel.Tracer("orchestrator").Start(ctx, "envRepository.FindById") + env, err := impl.envRepository.FindById(envOverride.TargetEnvironment) + span.End() + if err != nil { + impl.logger.Errorw("unable to find env", "err", err) + return nil, err + } + envOverride.Environment = env + + //VARIABLE different cases for variable resolution + scope := resourceQualifiers.Scope{ + AppId: overrideRequest.AppId, + EnvId: overrideRequest.EnvId, + ClusterId: overrideRequest.ClusterId, + SystemMetadata: &resourceQualifiers.SystemMetadata{ + EnvironmentName: env.Name, + ClusterName: env.Cluster.ClusterName, + Namespace: env.Namespace, + AppName: overrideRequest.AppName, + Image: overrideRequest.Image, + ImageTag: util3.GetImageTagFromImage(overrideRequest.Image), + }, + } + + if envOverride.IsOverride { + + resolvedTemplate, variableMap, err := impl.extractVariablesAndResolveTemplate(scope, envOverride.EnvOverrideValues, repository5.Entity{ + EntityType: repository5.EntityTypeDeploymentTemplateEnvLevel, + EntityId: envOverride.Id, + }) + envOverride.ResolvedEnvOverrideValues = resolvedTemplate + envOverride.VariableSnapshot = variableMap + if err != nil { + return envOverride, err + } + + } else { + resolvedTemplate, variableMap, err := impl.extractVariablesAndResolveTemplate(scope, chart.GlobalOverride, repository5.Entity{ + EntityType: repository5.EntityTypeDeploymentTemplateAppLevel, + EntityId: chart.Id, + }) + envOverride.Chart.ResolvedGlobalOverride = resolvedTemplate + envOverride.VariableSnapshot = variableMap + if err != nil { + return envOverride, err + } + + } + } + + return envOverride, nil +} + +func (impl *WorkflowDagExecutorImpl) GetAppMetricsByTriggerType(overrideRequest *bean.ValuesOverrideRequest, ctx context.Context) (bool, error) { + + var appMetrics bool + if overrideRequest.DeploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_SPECIFIC_TRIGGER { + _, span := otel.Tracer("orchestrator").Start(ctx, "deploymentTemplateHistoryRepository.GetHistoryByPipelineIdAndWfrId") + deploymentTemplateHistory, err := impl.deploymentTemplateHistoryRepository.GetHistoryByPipelineIdAndWfrId(overrideRequest.PipelineId, overrideRequest.WfrIdForDeploymentWithSpecificTrigger) + span.End() + if err != nil { + impl.logger.Errorw("error in getting deployed deployment template history by pipelineId and wfrId", "err", err, "pipelineId", &overrideRequest, "wfrId", overrideRequest.WfrIdForDeploymentWithSpecificTrigger) + return appMetrics, err + } + appMetrics = deploymentTemplateHistory.IsAppMetricsEnabled + + } else if overrideRequest.DeploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_LAST_SAVED { + _, span := otel.Tracer("orchestrator").Start(ctx, "appLevelMetricsRepository.FindByAppId") + appLevelMetrics, err := impl.appLevelMetricsRepository.FindByAppId(overrideRequest.AppId) + span.End() + if err != nil && !util.IsErrNoRows(err) { + impl.logger.Errorw("err", err) + return appMetrics, &util.ApiError{InternalMessage: "unable to fetch app level metrics flag"} + } + appMetrics = appLevelMetrics.AppMetrics + + _, span = otel.Tracer("orchestrator").Start(ctx, "envLevelMetricsRepository.FindByAppIdAndEnvId") + envLevelMetrics, err := impl.envLevelMetricsRepository.FindByAppIdAndEnvId(overrideRequest.AppId, overrideRequest.EnvId) + span.End() + if err != nil && !util.IsErrNoRows(err) { + impl.logger.Errorw("err", err) + return appMetrics, &util.ApiError{InternalMessage: "unable to fetch env level metrics flag"} + } + if envLevelMetrics.Id != 0 && envLevelMetrics.AppMetrics != nil { + appMetrics = *envLevelMetrics.AppMetrics + } + } + return appMetrics, nil +} + +func (impl *WorkflowDagExecutorImpl) getDbMigrationOverride(overrideRequest *bean.ValuesOverrideRequest, artifact *repository.CiArtifact, isRollback bool) (overrideJson []byte, err error) { + if isRollback { + return nil, fmt.Errorf("rollback not supported ye") + } + notConfigured := false + config, err := impl.dbMigrationConfigRepository.FindByPipelineId(overrideRequest.PipelineId) + if err != nil && !util.IsErrNoRows(err) { + impl.logger.Errorw("error in fetching pipeline override config", "req", overrideRequest, "err", err) + return nil, err + } else if util.IsErrNoRows(err) { + notConfigured = true + } + envVal := &EnvironmentOverride{} + if notConfigured { + impl.logger.Warnw("no active db migration found", "pipeline", overrideRequest.PipelineId) + envVal.Enabled = false + } else { + materialInfos, err := artifact.ParseMaterialInfo() + if err != nil { + return nil, err + } + + hash, ok := materialInfos[config.GitMaterial.Url] + if !ok { + impl.logger.Errorf("wrong url map ", "map", materialInfos, "url", config.GitMaterial.Url) + return nil, fmt.Errorf("configured url not found in material %s", config.GitMaterial.Url) + } + + envVal.Enabled = true + if config.GitMaterial.GitProvider.AuthMode != repository.AUTH_MODE_USERNAME_PASSWORD && + config.GitMaterial.GitProvider.AuthMode != repository.AUTH_MODE_ACCESS_TOKEN && + config.GitMaterial.GitProvider.AuthMode != repository.AUTH_MODE_ANONYMOUS { + return nil, fmt.Errorf("auth mode %s not supported for migration", config.GitMaterial.GitProvider.AuthMode) + } + envVal.appendEnvironmentVariable("GIT_REPO_URL", config.GitMaterial.Url) + envVal.appendEnvironmentVariable("GIT_USER", config.GitMaterial.GitProvider.UserName) + var password string + if config.GitMaterial.GitProvider.AuthMode == repository.AUTH_MODE_USERNAME_PASSWORD { + password = config.GitMaterial.GitProvider.Password + } else { + password = config.GitMaterial.GitProvider.AccessToken + } + envVal.appendEnvironmentVariable("GIT_AUTH_TOKEN", password) + // parse git-tag not required + //envVal.appendEnvironmentVariable("GIT_TAG", "") + envVal.appendEnvironmentVariable("GIT_HASH", hash) + envVal.appendEnvironmentVariable("SCRIPT_LOCATION", config.ScriptSource) + envVal.appendEnvironmentVariable("DB_TYPE", string(config.DbConfig.Type)) + envVal.appendEnvironmentVariable("DB_USER_NAME", config.DbConfig.UserName) + envVal.appendEnvironmentVariable("DB_PASSWORD", config.DbConfig.Password) + envVal.appendEnvironmentVariable("DB_HOST", config.DbConfig.Host) + envVal.appendEnvironmentVariable("DB_PORT", config.DbConfig.Port) + envVal.appendEnvironmentVariable("DB_NAME", config.DbConfig.DbName) + //Will be used for rollback don't delete it + //envVal.appendEnvironmentVariable("MIGRATE_TO_VERSION", strconv.Itoa(overrideRequest.TargetDbVersion)) + } + dbMigrationConfig := map[string]interface{}{"dbMigrationConfig": envVal} + confByte, err := json.Marshal(dbMigrationConfig) + if err != nil { + return nil, err + } + return confByte, nil +} + +func (impl *WorkflowDagExecutorImpl) getConfigMapAndSecretJsonV2(appId int, envId int, pipelineId int, chartVersion string, deploymentWithConfig bean.DeploymentConfigurationType, wfrIdForDeploymentWithSpecificTrigger int) ([]byte, error) { + + var configMapJson string + var secretDataJson string + var configMapJsonApp string + var secretDataJsonApp string + var configMapJsonEnv string + var secretDataJsonEnv string + var err error + //var configMapJsonPipeline string + //var secretDataJsonPipeline string + + merged := []byte("{}") + if deploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_LAST_SAVED { + configMapA, err := impl.configMapRepository.GetByAppIdAppLevel(appId) + if err != nil && pg.ErrNoRows != err { + return []byte("{}"), err + } + if configMapA != nil && configMapA.Id > 0 { + configMapJsonApp = configMapA.ConfigMapData + secretDataJsonApp = configMapA.SecretData + } + configMapE, err := impl.configMapRepository.GetByAppIdAndEnvIdEnvLevel(appId, envId) + if err != nil && pg.ErrNoRows != err { + return []byte("{}"), err + } + if configMapE != nil && configMapE.Id > 0 { + configMapJsonEnv = configMapE.ConfigMapData + secretDataJsonEnv = configMapE.SecretData + } + } else if deploymentWithConfig == bean.DEPLOYMENT_CONFIG_TYPE_SPECIFIC_TRIGGER { + //fetching history and setting envLevelConfig and not appLevelConfig because history already contains merged appLevel and envLevel configs + configMapHistory, err := impl.configMapHistoryRepository.GetHistoryByPipelineIdAndWfrId(pipelineId, wfrIdForDeploymentWithSpecificTrigger, repository3.CONFIGMAP_TYPE) + if err != nil { + impl.logger.Errorw("error in getting config map history config by pipelineId and wfrId ", "err", err, "pipelineId", pipelineId, "wfrid", wfrIdForDeploymentWithSpecificTrigger) + return []byte("{}"), err + } + configMapJsonEnv = configMapHistory.Data + secretHistory, err := impl.configMapHistoryRepository.GetHistoryByPipelineIdAndWfrId(pipelineId, wfrIdForDeploymentWithSpecificTrigger, repository3.SECRET_TYPE) + if err != nil { + impl.logger.Errorw("error in getting config map history config by pipelineId and wfrId ", "err", err, "pipelineId", pipelineId, "wfrid", wfrIdForDeploymentWithSpecificTrigger) + return []byte("{}"), err + } + secretDataJsonEnv = secretHistory.Data + } + configMapJson, err = impl.mergeUtil.ConfigMapMerge(configMapJsonApp, configMapJsonEnv) + if err != nil { + return []byte("{}"), err + } + chartMajorVersion, chartMinorVersion, err := util4.ExtractChartVersion(chartVersion) + if err != nil { + impl.logger.Errorw("chart version parsing", "err", err) + return []byte("{}"), err + } + secretDataJson, err = impl.mergeUtil.ConfigSecretMerge(secretDataJsonApp, secretDataJsonEnv, chartMajorVersion, chartMinorVersion, false) + if err != nil { + return []byte("{}"), err + } + configResponseR := bean.ConfigMapRootJson{} + configResponse := bean.ConfigMapJson{} + if configMapJson != "" { + err = json.Unmarshal([]byte(configMapJson), &configResponse) + if err != nil { + return []byte("{}"), err + } + } + configResponseR.ConfigMapJson = configResponse + secretResponseR := bean.ConfigSecretRootJson{} + secretResponse := bean.ConfigSecretJson{} + if configMapJson != "" { + err = json.Unmarshal([]byte(secretDataJson), &secretResponse) + if err != nil { + return []byte("{}"), err + } + } + secretResponseR.ConfigSecretJson = secretResponse + + configMapByte, err := json.Marshal(configResponseR) + if err != nil { + return []byte("{}"), err + } + secretDataByte, err := json.Marshal(secretResponseR) + if err != nil { + return []byte("{}"), err + } + + merged, err = impl.mergeUtil.JsonPatch(configMapByte, secretDataByte) + if err != nil { + return []byte("{}"), err + } + return merged, nil +} + +func (impl *WorkflowDagExecutorImpl) savePipelineOverride(overrideRequest *bean.ValuesOverrideRequest, envOverrideId int, triggeredAt time.Time) (override *chartConfig.PipelineOverride, err error) { + currentReleaseNo, err := impl.pipelineOverrideRepository.GetCurrentPipelineReleaseCounter(overrideRequest.PipelineId) + if err != nil { + return nil, err + } + po := &chartConfig.PipelineOverride{ + EnvConfigOverrideId: envOverrideId, + Status: models.CHARTSTATUS_NEW, + PipelineId: overrideRequest.PipelineId, + CiArtifactId: overrideRequest.CiArtifactId, + PipelineReleaseCounter: currentReleaseNo + 1, + CdWorkflowId: overrideRequest.CdWorkflowId, + AuditLog: sql.AuditLog{CreatedBy: overrideRequest.UserId, CreatedOn: triggeredAt, UpdatedOn: triggeredAt, UpdatedBy: overrideRequest.UserId}, + DeploymentType: overrideRequest.DeploymentType, + } + + err = impl.pipelineOverrideRepository.Save(po) + if err != nil { + return nil, err + } + err = impl.checkAndFixDuplicateReleaseNo(po) + if err != nil { + impl.logger.Errorw("error in checking release no duplicacy", "pipeline", po, "err", err) + return nil, err + } + return po, nil +} + +func (impl *WorkflowDagExecutorImpl) getReleaseOverride(envOverride *chartConfig.EnvConfigOverride, overrideRequest *bean.ValuesOverrideRequest, artifact *repository.CiArtifact, pipelineOverride *chartConfig.PipelineOverride, strategy *chartConfig.PipelineStrategy, appMetrics *bool) (releaseOverride string, err error) { + + artifactImage := artifact.Image + imageTag := strings.Split(artifactImage, ":") + + imageTagLen := len(imageTag) + + imageName := "" + + for i := 0; i < imageTagLen-1; i++ { + if i != imageTagLen-2 { + imageName = imageName + imageTag[i] + ":" + } else { + imageName = imageName + imageTag[i] + } + } + + appId := strconv.Itoa(overrideRequest.AppId) + envId := strconv.Itoa(overrideRequest.EnvId) + + deploymentStrategy := "" + if strategy != nil { + deploymentStrategy = string(strategy.Strategy) + } + releaseAttribute := app.ReleaseAttributes{ + Name: imageName, + Tag: imageTag[imageTagLen-1], + PipelineName: overrideRequest.PipelineName, + ReleaseVersion: strconv.Itoa(pipelineOverride.PipelineReleaseCounter), + DeploymentType: deploymentStrategy, + App: appId, + Env: envId, + AppMetrics: appMetrics, + } + override, err := util4.Tprintf(envOverride.Chart.ImageDescriptorTemplate, releaseAttribute) + if err != nil { + return "", &util.ApiError{InternalMessage: "unable to render ImageDescriptorTemplate"} + } + if overrideRequest.AdditionalOverride != nil { + userOverride, err := overrideRequest.AdditionalOverride.MarshalJSON() + if err != nil { + return "", err + } + data, err := impl.mergeUtil.JsonPatch(userOverride, []byte(override)) + if err != nil { + return "", err + } + override = string(data) + } + return override, nil +} + +func (impl *WorkflowDagExecutorImpl) mergeAndSave(envOverride *chartConfig.EnvConfigOverride, + overrideRequest *bean.ValuesOverrideRequest, + dbMigrationOverride []byte, + artifact *repository.CiArtifact, + pipeline *pipelineConfig.Pipeline, configMapJson, appLabelJsonByte []byte, strategy *chartConfig.PipelineStrategy, ctx context.Context, + triggeredAt time.Time, deployedBy int32, appMetrics *bool) (releaseId int, overrideId int, mergedValues string, err error) { + + //register release , obtain release id TODO: populate releaseId to template + override, err := impl.savePipelineOverride(overrideRequest, envOverride.Id, triggeredAt) + if err != nil { + return 0, 0, "", err + } + //TODO: check status and apply lock + overrideJson, err := impl.getReleaseOverride(envOverride, overrideRequest, artifact, override, strategy, appMetrics) + if err != nil { + return 0, 0, "", err + } + + //merge three values on the fly + //ordering is important here + //global < environment < db< release + var merged []byte + if !envOverride.IsOverride { + merged, err = impl.mergeUtil.JsonPatch([]byte("{}"), []byte(envOverride.Chart.GlobalOverride)) + if err != nil { + return 0, 0, "", err + } + } else { + merged, err = impl.mergeUtil.JsonPatch([]byte("{}"), []byte(envOverride.EnvOverrideValues)) + if err != nil { + return 0, 0, "", err + } + } + + //pipeline override here comes from pipeline strategy table + if strategy != nil && len(strategy.Config) > 0 { + merged, err = impl.mergeUtil.JsonPatch(merged, []byte(strategy.Config)) + if err != nil { + return 0, 0, "", err + } + } + merged, err = impl.mergeUtil.JsonPatch(merged, dbMigrationOverride) + if err != nil { + return 0, 0, "", err + } + merged, err = impl.mergeUtil.JsonPatch(merged, []byte(overrideJson)) + if err != nil { + return 0, 0, "", err + } + + if configMapJson != nil { + merged, err = impl.mergeUtil.JsonPatch(merged, configMapJson) + if err != nil { + return 0, 0, "", err + } + } + + if appLabelJsonByte != nil { + merged, err = impl.mergeUtil.JsonPatch(merged, appLabelJsonByte) + if err != nil { + return 0, 0, "", err + } + } + + appName := fmt.Sprintf("%s-%s", pipeline.App.AppName, envOverride.Environment.Name) + merged = impl.autoscalingCheckBeforeTrigger(ctx, appName, envOverride.Namespace, merged, overrideRequest) + + _, span := otel.Tracer("orchestrator").Start(ctx, "dockerRegistryIpsConfigService.HandleImagePullSecretOnApplicationDeployment") + // handle image pull secret if access given + merged, err = impl.dockerRegistryIpsConfigService.HandleImagePullSecretOnApplicationDeployment(envOverride.Environment, pipeline.CiPipelineId, merged) + span.End() + if err != nil { + return 0, 0, "", err + } + + commitHash := "" + commitTime := time.Time{} + if util.IsAcdApp(pipeline.DeploymentAppType) { + chartRepoName := impl.chartTemplateService.GetGitOpsRepoNameFromUrl(envOverride.Chart.GitRepoUrl) + _, span = otel.Tracer("orchestrator").Start(ctx, "chartTemplateService.GetUserEmailIdAndNameForGitOpsCommit") + //getting username & emailId for commit author data + userEmailId, userName := impl.chartTemplateService.GetUserEmailIdAndNameForGitOpsCommit(overrideRequest.UserId) + span.End() + chartGitAttr := &util.ChartConfig{ + FileName: fmt.Sprintf("_%d-values.yaml", envOverride.TargetEnvironment), + FileContent: string(merged), + ChartName: envOverride.Chart.ChartName, + ChartLocation: envOverride.Chart.ChartLocation, + ChartRepoName: chartRepoName, + ReleaseMessage: fmt.Sprintf("release-%d-env-%d ", override.Id, envOverride.TargetEnvironment), + UserName: userName, + UserEmailId: userEmailId, + } + gitOpsConfigBitbucket, err := impl.gitOpsConfigRepository.GetGitOpsConfigByProvider(util.BITBUCKET_PROVIDER) + if err != nil { + if err == pg.ErrNoRows { + gitOpsConfigBitbucket.BitBucketWorkspaceId = "" + } else { + return 0, 0, "", err + } + } + gitOpsConfig := &bean.GitOpsConfigDto{BitBucketWorkspaceId: gitOpsConfigBitbucket.BitBucketWorkspaceId} + _, span = otel.Tracer("orchestrator").Start(ctx, "gitFactory.Client.CommitValues") + commitHash, commitTime, err = impl.gitFactory.Client.CommitValues(chartGitAttr, gitOpsConfig) + span.End() + if err != nil { + impl.logger.Errorw("error in git commit", "err", err) + return 0, 0, "", err + } + } + if commitTime.IsZero() { + commitTime = time.Now() + } + pipelineOverride := &chartConfig.PipelineOverride{ + Id: override.Id, + GitHash: commitHash, + CommitTime: commitTime, + EnvConfigOverrideId: envOverride.Id, + PipelineOverrideValues: overrideJson, + PipelineId: overrideRequest.PipelineId, + CiArtifactId: overrideRequest.CiArtifactId, + PipelineMergedValues: string(merged), + AuditLog: sql.AuditLog{UpdatedOn: triggeredAt, UpdatedBy: deployedBy}, + } + _, span = otel.Tracer("orchestrator").Start(ctx, "pipelineOverrideRepository.Update") + err = impl.pipelineOverrideRepository.Update(pipelineOverride) + span.End() + if err != nil { + return 0, 0, "", err + } + mergedValues = string(merged) + return override.PipelineReleaseCounter, override.Id, mergedValues, nil +} + +func (impl *WorkflowDagExecutorImpl) mergeOverrideValues(envOverride *chartConfig.EnvConfigOverride, + dbMigrationOverride []byte, + releaseOverrideJson string, + configMapJson []byte, + appLabelJsonByte []byte, + strategy *chartConfig.PipelineStrategy, +) (mergedValues []byte, err error) { + + //merge three values on the fly + //ordering is important here + //global < environment < db< release + var merged []byte + if !envOverride.IsOverride { + merged, err = impl.mergeUtil.JsonPatch([]byte("{}"), []byte(envOverride.Chart.ResolvedGlobalOverride)) + if err != nil { + return nil, err + } + } else { + merged, err = impl.mergeUtil.JsonPatch([]byte("{}"), []byte(envOverride.ResolvedEnvOverrideValues)) + if err != nil { + return nil, err + } + } + if strategy != nil && len(strategy.Config) > 0 { + merged, err = impl.mergeUtil.JsonPatch(merged, []byte(strategy.Config)) + if err != nil { + return nil, err + } + } + merged, err = impl.mergeUtil.JsonPatch(merged, dbMigrationOverride) + if err != nil { + return nil, err + } + merged, err = impl.mergeUtil.JsonPatch(merged, []byte(releaseOverrideJson)) + if err != nil { + return nil, err + } + if configMapJson != nil { + merged, err = impl.mergeUtil.JsonPatch(merged, configMapJson) + if err != nil { + return nil, err + } + } + if appLabelJsonByte != nil { + merged, err = impl.mergeUtil.JsonPatch(merged, appLabelJsonByte) + if err != nil { + return nil, err + } + } + return merged, nil +} + +func (impl *WorkflowDagExecutorImpl) autoscalingCheckBeforeTrigger(ctx context.Context, appName string, namespace string, merged []byte, overrideRequest *bean.ValuesOverrideRequest) []byte { + //pipeline := overrideRequest.Pipeline + var appId = overrideRequest.AppId + pipelineId := overrideRequest.PipelineId + var appDeploymentType = overrideRequest.DeploymentAppType + var clusterId = overrideRequest.ClusterId + deploymentType := overrideRequest.DeploymentType + templateMap := make(map[string]interface{}) + err := json.Unmarshal(merged, &templateMap) + if err != nil { + return merged + } + + hpaResourceRequest := impl.getAutoScalingReplicaCount(templateMap, appName) + impl.logger.Debugw("autoscalingCheckBeforeTrigger", "hpaResourceRequest", hpaResourceRequest) + if hpaResourceRequest.IsEnable { + resourceManifest := make(map[string]interface{}) + if util.IsAcdApp(appDeploymentType) { + query := &application.ApplicationResourceRequest{ + Name: &appName, + Version: &hpaResourceRequest.Version, + Group: &hpaResourceRequest.Group, + Kind: &hpaResourceRequest.Kind, + ResourceName: &hpaResourceRequest.ResourceName, + Namespace: &namespace, + } + recv, err := impl.acdClient.GetResource(ctx, query) + impl.logger.Debugw("resource manifest get replica count", "response", recv) + if err != nil { + impl.logger.Errorw("ACD Get Resource API Failed", "err", err) + middleware.AcdGetResourceCounter.WithLabelValues(strconv.Itoa(appId), namespace, appName).Inc() + return merged + } + if recv != nil && len(*recv.Manifest) > 0 { + err := json.Unmarshal([]byte(*recv.Manifest), &resourceManifest) + if err != nil { + impl.logger.Errorw("unmarshal failed for hpa check", "err", err) + return merged + } + } + } else { + version := "v2beta2" + k8sResource, err := impl.k8sCommonService.GetResource(ctx, &k8s.ResourceRequestBean{ClusterId: clusterId, + K8sRequest: &util5.K8sRequestBean{ResourceIdentifier: util5.ResourceIdentifier{Name: hpaResourceRequest.ResourceName, + Namespace: namespace, GroupVersionKind: schema.GroupVersionKind{Group: hpaResourceRequest.Group, Kind: hpaResourceRequest.Kind, Version: version}}}}) + if err != nil { + impl.logger.Errorw("error occurred while fetching resource for app", "resourceName", hpaResourceRequest.ResourceName, "err", err) + return merged + } + resourceManifest = k8sResource.Manifest.Object + } + if len(resourceManifest) > 0 { + statusMap := resourceManifest["status"].(map[string]interface{}) + currentReplicaVal := statusMap["currentReplicas"] + currentReplicaCount, err := util4.ParseFloatNumber(currentReplicaVal) + if err != nil { + impl.logger.Errorw("error occurred while parsing replica count", "currentReplicas", currentReplicaVal, "err", err) + return merged + } + + reqReplicaCount := impl.fetchRequiredReplicaCount(currentReplicaCount, hpaResourceRequest.ReqMaxReplicas, hpaResourceRequest.ReqMinReplicas) + templateMap["replicaCount"] = reqReplicaCount + merged, err = json.Marshal(&templateMap) + if err != nil { + impl.logger.Errorw("marshaling failed for hpa check", "err", err) + return merged + } + } + } else { + impl.logger.Errorw("autoscaling is not enabled", "pipelineId", pipelineId) + } + + //check for custom chart support + if autoscalingEnabledPath, ok := templateMap[bean2.CustomAutoScalingEnabledPathKey]; ok { + if deploymentType == models.DEPLOYMENTTYPE_STOP { + merged, err = impl.setScalingValues(templateMap, bean2.CustomAutoScalingEnabledPathKey, merged, false) + if err != nil { + return merged + } + merged, err = impl.setScalingValues(templateMap, bean2.CustomAutoscalingReplicaCountPathKey, merged, 0) + if err != nil { + return merged + } + } else { + autoscalingEnabled := false + autoscalingEnabledValue := gjson.Get(string(merged), autoscalingEnabledPath.(string)).Value() + if val, ok := autoscalingEnabledValue.(bool); ok { + autoscalingEnabled = val + } + if autoscalingEnabled { + // extract replica count, min, max and check for required value + replicaCount, err := impl.getReplicaCountFromCustomChart(templateMap, merged) + if err != nil { + return merged + } + merged, err = impl.setScalingValues(templateMap, bean2.CustomAutoscalingReplicaCountPathKey, merged, replicaCount) + if err != nil { + return merged + } + } + } + } + + return merged +} + +func (impl *WorkflowDagExecutorImpl) updateArgoPipeline(appId int, pipelineName string, envOverride *chartConfig.EnvConfigOverride, ctx context.Context) (bool, error) { + //repo has been registered while helm create + if ctx == nil { + impl.logger.Errorw("err in syncing ACD, ctx is NULL", "pipelineName", pipelineName) + return false, nil + } + app, err := impl.appRepository.FindById(appId) + if err != nil { + impl.logger.Errorw("no app found ", "err", err) + return false, err + } + envModel, err := impl.envRepository.FindById(envOverride.TargetEnvironment) + if err != nil { + return false, err + } + argoAppName := fmt.Sprintf("%s-%s", app.AppName, envModel.Name) + impl.logger.Infow("received payload, updateArgoPipeline", "appId", appId, "pipelineName", pipelineName, "envId", envOverride.TargetEnvironment, "argoAppName", argoAppName, "context", ctx) + application3, err := impl.acdClient.Get(ctx, &application.ApplicationQuery{Name: &argoAppName}) + if err != nil { + impl.logger.Errorw("no argo app exists", "app", argoAppName, "pipeline", pipelineName) + return false, err + } + //if status, ok:=status.FromError(err);ok{ + appStatus, _ := status2.FromError(err) + + if appStatus.Code() == codes.OK { + impl.logger.Debugw("argo app exists", "app", argoAppName, "pipeline", pipelineName) + if application3.Spec.Source.Path != envOverride.Chart.ChartLocation || application3.Spec.Source.TargetRevision != "master" { + patchReq := v1alpha1.Application{Spec: v1alpha1.ApplicationSpec{Source: v1alpha1.ApplicationSource{Path: envOverride.Chart.ChartLocation, RepoURL: envOverride.Chart.GitRepoUrl, TargetRevision: "master"}}} + reqbyte, err := json.Marshal(patchReq) + if err != nil { + impl.logger.Errorw("error in creating patch", "err", err) + } + reqString := string(reqbyte) + patchType := "merge" + _, err = impl.acdClient.Patch(ctx, &application.ApplicationPatchRequest{Patch: &reqString, Name: &argoAppName, PatchType: &patchType}) + if err != nil { + impl.logger.Errorw("error in creating argo pipeline ", "name", pipelineName, "patch", string(reqbyte), "err", err) + return false, err + } + impl.logger.Debugw("pipeline update req ", "res", patchReq) + } else { + impl.logger.Debug("pipeline no need to update ") + } + // Doing normal refresh to avoid the sync delay in argo-cd. + err2 := impl.argoClientWrapperService.GetArgoAppWithNormalRefresh(ctx, argoAppName) + if err2 != nil { + impl.logger.Errorw("error in getting argo application with normal refresh", "argoAppName", argoAppName, "pipelineName", pipelineName) + } + return true, nil + } else if appStatus.Code() == codes.NotFound { + impl.logger.Errorw("argo app not found", "app", argoAppName, "pipeline", pipelineName) + return false, nil + } else { + impl.logger.Errorw("err in checking application on gocd", "err", err, "pipeline", pipelineName) + return false, err + } +} + +func (impl *WorkflowDagExecutorImpl) getValuesFileForEnv(environmentId int) string { + return fmt.Sprintf("_%d-values.yaml", environmentId) //-{envId}-values.yaml +} + +func (impl *WorkflowDagExecutorImpl) updatePipeline(pipeline *pipelineConfig.Pipeline, userId int32) (bool, error) { + err := impl.pipelineRepository.SetDeploymentAppCreatedInPipeline(true, pipeline.Id, userId) + if err != nil { + impl.logger.Errorw("error on updating cd pipeline for setting deployment app created", "err", err) + return false, err + } + return true, nil +} + +// helmInstallReleaseWithCustomChart performs helm install with custom chart +func (impl *WorkflowDagExecutorImpl) helmInstallReleaseWithCustomChart(ctx context.Context, releaseIdentifier *client2.ReleaseIdentifier, referenceChartByte []byte, valuesYaml string) (*client2.HelmInstallCustomResponse, error) { + + helmInstallRequest := client2.HelmInstallCustomRequest{ + ValuesYaml: valuesYaml, + ChartContent: &client2.ChartContent{Content: referenceChartByte}, + ReleaseIdentifier: releaseIdentifier, + } + + // Request exec + return impl.helmAppClient.InstallReleaseWithCustomChart(ctx, &helmInstallRequest) +} + +func (impl *WorkflowDagExecutorImpl) getResolvedTemplateWithSnapshot(deploymentTemplateHistoryId int, template string) (string, map[string]string, error) { + + variableSnapshotMap := make(map[string]string) + reference := repository5.HistoryReference{ + HistoryReferenceId: deploymentTemplateHistoryId, + HistoryReferenceType: repository5.HistoryReferenceTypeDeploymentTemplate, + } + variableSnapshot, err := impl.variableSnapshotHistoryService.GetVariableHistoryForReferences([]repository5.HistoryReference{reference}) + if err != nil { + return template, variableSnapshotMap, err + } + + if _, ok := variableSnapshot[reference]; !ok { + return template, variableSnapshotMap, nil + } + + err = json.Unmarshal(variableSnapshot[reference].VariableSnapshot, &variableSnapshotMap) + if err != nil { + return template, variableSnapshotMap, err + } + + if len(variableSnapshotMap) == 0 { + return template, variableSnapshotMap, nil + } + scopedVariableData := parsers.GetScopedVarData(variableSnapshotMap, make(map[string]bool), true) + request := parsers.VariableParserRequest{Template: template, TemplateType: parsers.JsonVariableTemplate, Variables: scopedVariableData} + parserResponse := impl.variableTemplateParser.ParseTemplate(request) + err = parserResponse.Error + if err != nil { + return template, variableSnapshotMap, err + } + resolvedTemplate := parserResponse.ResolvedTemplate + return resolvedTemplate, variableSnapshotMap, nil +} + +func (impl *WorkflowDagExecutorImpl) extractVariablesAndResolveTemplate(scope resourceQualifiers.Scope, template string, entity repository5.Entity) (string, map[string]string, error) { + + variableMap := make(map[string]string) + entityToVariables, err := impl.variableEntityMappingService.GetAllMappingsForEntities([]repository5.Entity{entity}) + if err != nil { + return template, variableMap, err + } + + if vars, ok := entityToVariables[entity]; !ok || len(vars) == 0 { + return template, variableMap, nil + } + + // pre-populating variable map with variable so that the variables which don't have any resolved data + // is saved in snapshot + for _, variable := range entityToVariables[entity] { + variableMap[variable] = impl.scopedVariableService.GetFormattedVariableForName(variable) + } + + scopedVariables, err := impl.scopedVariableService.GetScopedVariables(scope, entityToVariables[entity], true) + if err != nil { + return template, variableMap, err + } + + for _, variable := range scopedVariables { + variableMap[variable.VariableName] = variable.VariableValue.StringValue() + } + + parserRequest := parsers.VariableParserRequest{Template: template, Variables: scopedVariables, TemplateType: parsers.JsonVariableTemplate} + parserResponse := impl.variableTemplateParser.ParseTemplate(parserRequest) + err = parserResponse.Error + if err != nil { + return template, variableMap, err + } + + resolvedTemplate := parserResponse.ResolvedTemplate + return resolvedTemplate, variableMap, nil +} + +type EnvironmentOverride struct { + Enabled bool `json:"enabled"` + EnvValues []*KeyValue `json:"envValues"` +} + +type KeyValue struct { + Key string `json:"key"` + Value string `json:"value"` +} + +func (conf *EnvironmentOverride) appendEnvironmentVariable(key, value string) { + item := &KeyValue{Key: key, Value: value} + conf.EnvValues = append(conf.EnvValues, item) +} + +func (impl *WorkflowDagExecutorImpl) checkAndFixDuplicateReleaseNo(override *chartConfig.PipelineOverride) error { + + uniqueVerified := false + retryCount := 0 + + for !uniqueVerified && retryCount < 5 { + retryCount = retryCount + 1 + overrides, err := impl.pipelineOverrideRepository.GetByPipelineIdAndReleaseNo(override.PipelineId, override.PipelineReleaseCounter) + if err != nil { + return err + } + if overrides[0].Id == override.Id { + uniqueVerified = true + } else { + //duplicate might be due to concurrency, lets fix it + currentReleaseNo, err := impl.pipelineOverrideRepository.GetCurrentPipelineReleaseCounter(override.PipelineId) + if err != nil { + return err + } + override.PipelineReleaseCounter = currentReleaseNo + 1 + err = impl.pipelineOverrideRepository.Save(override) + if err != nil { + return err + } + } + } + if !uniqueVerified { + return fmt.Errorf("duplicate verification retry count exide max overrideId: %d ,count: %d", override.Id, retryCount) + } + return nil +} + +func (impl *WorkflowDagExecutorImpl) getAutoScalingReplicaCount(templateMap map[string]interface{}, appName string) *util4.HpaResourceRequest { + hasOverride := false + if _, ok := templateMap[fullnameOverride]; ok { + appNameOverride := templateMap[fullnameOverride].(string) + if len(appNameOverride) > 0 { + appName = appNameOverride + hasOverride = true + } + } + if !hasOverride { + if _, ok := templateMap[nameOverride]; ok { + nameOverride := templateMap[nameOverride].(string) + if len(nameOverride) > 0 { + appName = fmt.Sprintf("%s-%s", appName, nameOverride) + } + } + } + hpaResourceRequest := &util4.HpaResourceRequest{} + hpaResourceRequest.Version = "" + hpaResourceRequest.Group = autoscaling.ServiceName + hpaResourceRequest.Kind = horizontalPodAutoscaler + impl.logger.Infow("getAutoScalingReplicaCount", "hpaResourceRequest", hpaResourceRequest) + if _, ok := templateMap[kedaAutoscaling]; ok { + as := templateMap[kedaAutoscaling] + asd := as.(map[string]interface{}) + if _, ok := asd[enabled]; ok { + impl.logger.Infow("getAutoScalingReplicaCount", "hpaResourceRequest", hpaResourceRequest) + enable := asd[enabled].(bool) + if enable { + hpaResourceRequest.IsEnable = enable + hpaResourceRequest.ReqReplicaCount = templateMap[replicaCount].(float64) + hpaResourceRequest.ReqMaxReplicas = asd["maxReplicaCount"].(float64) + hpaResourceRequest.ReqMinReplicas = asd["minReplicaCount"].(float64) + hpaResourceRequest.ResourceName = fmt.Sprintf("%s-%s-%s", "keda-hpa", appName, "keda") + impl.logger.Infow("getAutoScalingReplicaCount", "hpaResourceRequest", hpaResourceRequest) + return hpaResourceRequest + } + } + } + + if _, ok := templateMap[autoscaling.ServiceName]; ok { + as := templateMap[autoscaling.ServiceName] + asd := as.(map[string]interface{}) + if _, ok := asd[enabled]; ok { + enable := asd[enabled].(bool) + if enable { + hpaResourceRequest.IsEnable = asd[enabled].(bool) + hpaResourceRequest.ReqReplicaCount = templateMap[replicaCount].(float64) + hpaResourceRequest.ReqMaxReplicas = asd["MaxReplicas"].(float64) + hpaResourceRequest.ReqMinReplicas = asd["MinReplicas"].(float64) + hpaResourceRequest.ResourceName = fmt.Sprintf("%s-%s", appName, "hpa") + return hpaResourceRequest + } + } + } + return hpaResourceRequest + +} + +func (impl *WorkflowDagExecutorImpl) fetchRequiredReplicaCount(currentReplicaCount float64, reqMaxReplicas float64, reqMinReplicas float64) float64 { + var reqReplicaCount float64 + if currentReplicaCount <= reqMaxReplicas && currentReplicaCount >= reqMinReplicas { + reqReplicaCount = currentReplicaCount + } else if currentReplicaCount > reqMaxReplicas { + reqReplicaCount = reqMaxReplicas + } else if currentReplicaCount < reqMinReplicas { + reqReplicaCount = reqMinReplicas + } + return reqReplicaCount +} + +func (impl *WorkflowDagExecutorImpl) getReplicaCountFromCustomChart(templateMap map[string]interface{}, merged []byte) (float64, error) { + autoscalingMinVal, err := impl.extractParamValue(templateMap, bean2.CustomAutoscalingMinPathKey, merged) + if err != nil { + return 0, err + } + autoscalingMaxVal, err := impl.extractParamValue(templateMap, bean2.CustomAutoscalingMaxPathKey, merged) + if err != nil { + return 0, err + } + autoscalingReplicaCountVal, err := impl.extractParamValue(templateMap, bean2.CustomAutoscalingReplicaCountPathKey, merged) + if err != nil { + return 0, err + } + return impl.fetchRequiredReplicaCount(autoscalingReplicaCountVal, autoscalingMaxVal, autoscalingMinVal), nil +} + +func (impl *WorkflowDagExecutorImpl) setScalingValues(templateMap map[string]interface{}, customScalingKey string, merged []byte, value interface{}) ([]byte, error) { + autoscalingJsonPath := templateMap[customScalingKey] + autoscalingJsonPathKey := autoscalingJsonPath.(string) + mergedRes, err := sjson.Set(string(merged), autoscalingJsonPathKey, value) + if err != nil { + impl.logger.Errorw("error occurred while setting autoscaling key", "JsonPathKey", autoscalingJsonPathKey, "err", err) + return []byte{}, err + } + return []byte(mergedRes), nil +} + +func (impl *WorkflowDagExecutorImpl) extractParamValue(inputMap map[string]interface{}, key string, merged []byte) (float64, error) { + if _, ok := inputMap[key]; !ok { + return 0, errors.New("empty-val-err") + } + floatNumber, err := util4.ParseFloatNumber(gjson.Get(string(merged), inputMap[key].(string)).Value()) + if err != nil { + impl.logger.Errorw("error occurred while parsing float number", "key", key, "err", err) + } + return floatNumber, err +} diff --git a/wire_gen.go b/wire_gen.go index 6114d9c762..f6352874d4 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -446,7 +446,7 @@ func InitializeApp() (*App, error) { pipelineStageRepositoryImpl := repository11.NewPipelineStageRepository(sugaredLogger, db) globalPluginRepositoryImpl := repository12.NewGlobalPluginRepository(sugaredLogger, db) pipelineStageServiceImpl := pipeline.NewPipelineStageService(sugaredLogger, pipelineStageRepositoryImpl, globalPluginRepositoryImpl, pipelineRepositoryImpl, scopedVariableServiceImpl, variableEntityMappingServiceImpl, variableTemplateParserImpl) - workflowDagExecutorImpl := pipeline.NewWorkflowDagExecutorImpl(sugaredLogger, pipelineRepositoryImpl, cdWorkflowRepositoryImpl, pubSubClientServiceImpl, appServiceImpl, workflowServiceImpl, ciArtifactRepositoryImpl, ciPipelineRepositoryImpl, materialRepositoryImpl, pipelineOverrideRepositoryImpl, userServiceImpl, deploymentGroupRepositoryImpl, environmentRepositoryImpl, enforcerImpl, enforcerUtilImpl, tokenCache, acdAuthConfig, eventSimpleFactoryImpl, eventRESTClientImpl, cvePolicyRepositoryImpl, imageScanResultRepositoryImpl, appWorkflowRepositoryImpl, prePostCdScriptHistoryServiceImpl, argoUserServiceImpl, pipelineStatusTimelineRepositoryImpl, pipelineStatusTimelineServiceImpl, ciTemplateRepositoryImpl, ciWorkflowRepositoryImpl, appLabelRepositoryImpl, clientImpl, pipelineStageServiceImpl, k8sCommonServiceImpl, variableSnapshotHistoryServiceImpl) + workflowDagExecutorImpl := pipeline.NewWorkflowDagExecutorImpl(sugaredLogger, pipelineRepositoryImpl, cdWorkflowRepositoryImpl, pubSubClientServiceImpl, appServiceImpl, workflowServiceImpl, ciArtifactRepositoryImpl, ciPipelineRepositoryImpl, materialRepositoryImpl, pipelineOverrideRepositoryImpl, userServiceImpl, deploymentGroupRepositoryImpl, environmentRepositoryImpl, enforcerImpl, enforcerUtilImpl, tokenCache, acdAuthConfig, eventSimpleFactoryImpl, eventRESTClientImpl, cvePolicyRepositoryImpl, imageScanResultRepositoryImpl, appWorkflowRepositoryImpl, prePostCdScriptHistoryServiceImpl, argoUserServiceImpl, pipelineStatusTimelineRepositoryImpl, pipelineStatusTimelineServiceImpl, ciTemplateRepositoryImpl, ciWorkflowRepositoryImpl, appLabelRepositoryImpl, clientImpl, pipelineStageServiceImpl, k8sCommonServiceImpl, variableSnapshotHistoryServiceImpl, deploymentTemplateHistoryServiceImpl, configMapHistoryServiceImpl, pipelineStrategyHistoryServiceImpl, manifestPushConfigRepositoryImpl, gitOpsManifestPushServiceImpl, ciPipelineMaterialRepositoryImpl, imageScanHistoryRepositoryImpl, imageScanDeployInfoRepositoryImpl, appCrudOperationServiceImpl, pipelineConfigRepositoryImpl, dockerRegistryIpsConfigServiceImpl, chartRepositoryImpl, chartTemplateServiceImpl, pipelineStrategyHistoryRepositoryImpl, appRepositoryImpl, deploymentTemplateHistoryRepositoryImpl, argoK8sClientImpl, configMapRepositoryImpl, configMapHistoryRepositoryImpl, refChartDir, helmAppServiceImpl, helmAppClientImpl, chartRefRepositoryImpl, envConfigOverrideRepositoryImpl, appLevelMetricsRepositoryImpl, envLevelAppMetricsRepositoryImpl, dbMigrationConfigRepositoryImpl, mergeUtil, gitOpsConfigRepositoryImpl, gitFactory, applicationServiceClientImpl, variableEntityMappingServiceImpl, variableTemplateParserImpl, argoClientWrapperServiceImpl, scopedVariableServiceImpl) deploymentGroupAppRepositoryImpl := repository.NewDeploymentGroupAppRepositoryImpl(sugaredLogger, db) deploymentGroupServiceImpl := deploymentGroup.NewDeploymentGroupServiceImpl(appRepositoryImpl, sugaredLogger, pipelineRepositoryImpl, ciPipelineRepositoryImpl, deploymentGroupRepositoryImpl, environmentRepositoryImpl, deploymentGroupAppRepositoryImpl, ciArtifactRepositoryImpl, appWorkflowRepositoryImpl, workflowDagExecutorImpl) deploymentConfigServiceImpl := pipeline.NewDeploymentConfigServiceImpl(sugaredLogger, envConfigOverrideRepositoryImpl, chartRepositoryImpl, pipelineRepositoryImpl, envLevelAppMetricsRepositoryImpl, appLevelMetricsRepositoryImpl, pipelineConfigRepositoryImpl, configMapRepositoryImpl, configMapHistoryServiceImpl, chartRefRepositoryImpl, variableEntityMappingServiceImpl, scopedVariableServiceImpl, variableTemplateParserImpl)