Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Helm async deploy Devtron Apps #4045

Merged
merged 58 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
a8b50fd
wip: refactored deployment code
Ash-exp Oct 9, 2023
091f113
feat: helm async install for devtron apps
Ash-exp Oct 9, 2023
e7802ee
Merge branch 'main' into feat-async-install-devtron
Ash-exp Oct 9, 2023
6fbe666
updated: pubsub lib version
Ash-exp Oct 9, 2023
9786c0f
removed unnecessary comments
Ash-exp Oct 9, 2023
61968e9
removed: unnecessary code
Ash-exp Oct 10, 2023
2a76b77
Merge branch 'main' into feat-async-install-devtron
Ash-exp Oct 10, 2023
0ba51de
feat: deployment cron updated
Ash-exp Oct 10, 2023
a88b62c
updated: review comments
Ash-exp Oct 10, 2023
11157af
refactored: deployment status cron job logic
Ash-exp Oct 11, 2023
bffcbb0
Merge branch 'main' into feat-async-install-devtron
Ash-exp Oct 11, 2023
f1a9c55
updated: runner states for deployment
Ash-exp Oct 11, 2023
d9bb5d8
fixed: unable to update cdWorkflowRunner
Ash-exp Oct 11, 2023
63d9923
Merge branch 'main' into feat-async-install-devtron
Ash-exp Oct 12, 2023
3023319
chore: main merge
Ash-exp Oct 12, 2023
9d06bfe
updated: cdWfr for gitops deployment
Ash-exp Oct 13, 2023
5217ce3
fixed: cdWfr skipped status list
Ash-exp Oct 13, 2023
cd88d1d
fixed: test file arguments
Ash-exp Oct 13, 2023
c433178
fixed: handled for event redelivery case
Ash-exp Oct 13, 2023
d8ebf23
used the constant
Ash-exp Oct 13, 2023
c3a46ba
handling for context deadline exceeded
Ash-exp Oct 13, 2023
0401195
updated: context deadline error expression
Ash-exp Oct 13, 2023
b59543b
handled: error in unmarshalling
Ash-exp Oct 13, 2023
a53ac2c
fixed: context deadline error in cdWfr
Ash-exp Oct 13, 2023
8c68538
handled: pending-install state
Ash-exp Oct 16, 2023
fed6eeb
chore: main merge
Ash-exp Oct 19, 2023
f69e92e
feat: helm install/upgrade with ctx
Ash-exp Oct 20, 2023
51c48c0
Merge branch 'main' into feat-async-install-devtron
Ash-exp Oct 20, 2023
97d6c50
handling: nil pointer
Ash-exp Oct 20, 2023
b8f2b1a
updated: error handling
Ash-exp Oct 20, 2023
f83c55e
updated: error message
Ash-exp Oct 20, 2023
940ad76
fixed: updatePreviousDeploymentStatus handling
Ash-exp Oct 20, 2023
5e3ff18
fixed: updatePreviousDeploymentStatus handling
Ash-exp Oct 20, 2023
ea3af2d
feat: refactored
Ash-exp Oct 20, 2023
acfa3ec
chore: main merge
Ash-exp Oct 23, 2023
17fb50f
chore: main merge
Ash-exp Oct 25, 2023
8f74652
updated variable name
Ash-exp Oct 25, 2023
81b15ae
Merge branch 'main' into feat-async-install-devtron
Ash-exp Oct 26, 2023
9dc6e25
updated default value for env
Ash-exp Oct 26, 2023
6cff43a
updated GetValuesOverrideForTrigger
Ash-exp Oct 26, 2023
2f7b842
updated GetValuesOverrideForTrigger
Ash-exp Oct 26, 2023
88018fd
Merge branch 'main' into feat-async-install-devtron
Ash-exp Oct 26, 2023
5df6382
code review comments
kripanshdevtron Oct 30, 2023
ad53ee0
Merge branch 'main' into feat-async-install-devtron
Ash-exp Oct 30, 2023
243ac2e
incorporated review suggestions
Ash-exp Oct 31, 2023
25f72d4
chore: main merge
Ash-exp Oct 31, 2023
aaad742
Merge branch 'main' into feat-async-install-devtron
Ash-exp Oct 31, 2023
f94a3fa
chore: removed unnecessary env flag
Ash-exp Oct 31, 2023
27c377b
handled: context deadline error
Ash-exp Oct 31, 2023
06f2256
fixed: migration
Ash-exp Oct 31, 2023
fc67c41
fixed: update status in progress
Ash-exp Oct 31, 2023
12560d0
fine-tuned and refactoring
Ash-exp Nov 1, 2023
93aacf9
chore: main merge
Ash-exp Nov 1, 2023
6ff5310
handling for hibernate app
Ash-exp Nov 1, 2023
5b81688
chore: main merge
Ash-exp Nov 16, 2023
0ec86ae
chore: main merge
Ash-exp Nov 16, 2023
add60a9
Merge branch 'main' into feat-async-install-devtron
Ash-exp Nov 20, 2023
115c62a
chore: main merge
Ash-exp Nov 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
updated: review comments
  • Loading branch information
Ash-exp committed Oct 10, 2023
commit a88b62c8bb3c24b8e53b22b7817e21247f5bd6ae
7 changes: 7 additions & 0 deletions internal/util/CollectionUtil.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ func CompareUnOrdered(a, b []int) bool {
sort.Ints(b)
return cmp.Equal(a, b)
}

func GetMaxInteger(a, b int) int {
if a > b {
return a
}
return b
}
61 changes: 34 additions & 27 deletions pkg/app/AppService.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ type AppServiceConfig struct {
HelmPipelineStatusCheckEligibleTime string `env:"HELM_PIPELINE_STATUS_CHECK_ELIGIBLE_TIME" envDefault:"120"` //in seconds
ExposeCDMetrics bool `env:"EXPOSE_CD_METRICS" envDefault:"false"`
EnableAsyncInstallDevtronChart bool `env:"ENABLE_ASYNC_INSTALL_DEVTRON_CHART" envDefault:"true"`
DevtronChartInstallTimeout int `env:"DEVTRON_CHART_INSTALL_TIMEOUT" envDefault:"6"` //in minutes
DevtronChartInstallRequestTimeout int `env:"DEVTRON_CHART_INSTALL_REQUEST_TIMEOUT" envDefault:"6"` //in minutes
HelmInstallationTimeout int `env:"HELM_INSTALLATION_TIMEOUT" envDefault:"5"` //in minutes
}

func GetAppServiceConfig() (*AppServiceConfig, error) {
Expand Down Expand Up @@ -1892,35 +1893,41 @@ func (impl *AppServiceImpl) GetTriggerEvent(deploymentAppType string, triggeredA

func (impl *AppServiceImpl) HandleCDTriggerRelease(overrideRequest *bean.ValuesOverrideRequest, ctx context.Context, triggeredAt time.Time, deployedBy int32) (releaseNo int, manifest []byte, err error) {
if impl.isDevtronAsyncInstallModeEnabled(overrideRequest.DeploymentAppType) {
event := &bean.AsyncCdDeployEvent{
ValuesOverrideRequest: overrideRequest,
TriggeredAt: triggeredAt,
TriggeredBy: deployedBy,
}
payload, err := json.Marshal(event)
if err != nil {
impl.logger.Errorw("failed to marshal helm async CD deploy event request", "request", event, "err", err)
return 0, manifest, err
}

// publish nats event for async installation
err = impl.pubsubClient.Publish(pubsub.DEVTRON_CHART_INSTALL_TOPIC, string(payload))
if err != nil {
impl.logger.Errorw("failed to publish trigger request event", "topic", pubsub.DEVTRON_CHART_INSTALL_TOPIC, "payload", payload, "err", err)
}

//update workflow runner status, used in app workflow view
err = impl.UpdateCDWorkflowRunnerStatus(ctx, overrideRequest, triggeredAt, pipelineConfig.WorkflowInQueue)
if err != nil {
return 0, manifest, err
}
return 0, manifest, nil
// asynchronous mode of installation
return impl.TriggerHelmAsyncRelease(overrideRequest, ctx, triggeredAt, deployedBy)
}
// synchronous mode of installation
return impl.TriggerRelease(overrideRequest, ctx, triggeredAt, deployedBy)
}

// TriggerRelease used to trigger Install/Upgrade Devtron App releases synchronously
// TriggerHelmAsyncRelease will publish async helm Install/Upgrade request event for Devtron App releases
func (impl *AppServiceImpl) TriggerHelmAsyncRelease(overrideRequest *bean.ValuesOverrideRequest, ctx context.Context, triggeredAt time.Time, triggeredBy int32) (releaseNo int, manifest []byte, err error) {
event := &bean.AsyncCdDeployEvent{
ValuesOverrideRequest: overrideRequest,
TriggeredAt: triggeredAt,
TriggeredBy: triggeredBy,
}
payload, err := json.Marshal(event)
if err != nil {
impl.logger.Errorw("failed to marshal helm async CD deploy event request", "request", event, "err", err)
return 0, manifest, err
}

// publish nats event for async installation
err = impl.pubsubClient.Publish(pubsub.DEVTRON_CHART_INSTALL_TOPIC, string(payload))
if err != nil {
impl.logger.Errorw("failed to publish trigger request event", "topic", pubsub.DEVTRON_CHART_INSTALL_TOPIC, "payload", payload, "err", err)
}

//update workflow runner status, used in app workflow view
err = impl.UpdateCDWorkflowRunnerStatus(ctx, overrideRequest, triggeredAt, pipelineConfig.WorkflowInQueue)
if err != nil {
return 0, manifest, err
}
return 0, manifest, nil
}

// TriggerRelease will trigger Install/Upgrade request for Devtron App releases synchronously
func (impl *AppServiceImpl) TriggerRelease(overrideRequest *bean.ValuesOverrideRequest, ctx context.Context, triggeredAt time.Time, triggeredBy int32) (releaseNo int, manifest []byte, err error) {
// Handling for auto trigger
if overrideRequest.UserId == 0 {
Expand Down Expand Up @@ -3149,7 +3156,7 @@ func (impl *AppServiceImpl) createHelmAppForCdPipeline(overrideRequest *bean.Val
HistoryMax: impl.helmAppService.GetRevisionHistoryMaxValue(client2.SOURCE_DEVTRON_APP),
ChartContent: &client2.ChartContent{Content: referenceChartByte},
}

// For cases where helm release was not found, kubelink will install the same configuration
updateApplicationResponse, err := impl.helmAppClient.UpdateApplication(ctx, req)
if err != nil {
impl.logger.Errorw("error in updating helm application for cd pipeline", "err", err)
Expand Down Expand Up @@ -3244,7 +3251,7 @@ func (impl *AppServiceImpl) UpdateCDWorkflowRunnerStatus(ctx context.Context, ov
// if the current cdWfr status is already a terminal status and then don't update the status
// e.g: Status : Failed --> Progressing (not allowed)
if slices.Contains(pipelineConfig.WfrTerminalStatusList, cdWfr.Status) {
impl.logger.Errorw("deployment has already been terminated for workflow runner", "workflowRunnerId", cdWfr.Id, "err", err)
impl.logger.Warnw("deployment has already been terminated for workflow runner", "workflowRunnerId", cdWfr.Id, "err", err)
return nil
}
cdWfr.Status = status
Expand Down
7 changes: 6 additions & 1 deletion pkg/pipeline/CdHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,12 @@ func (impl *CdHandlerImpl) CheckHelmAppStatusPeriodicallyAndUpdateInDb(helmPipel
impl.Logger.Warnw("release mismatched, skipping helm apps status update for this trigger", "appIdentifier", appIdentifier, "err", err)
continue
} else if helmInstalledDevtronApp.GetReleaseStatus() == serverBean.HelmReleaseStatusPendingInstall {
if time.Now().After(helmInstalledDevtronApp.GetLastDeployed().AsTime().Add(5 * time.Minute)) {
appServiceConfig, err := app.GetAppServiceConfig()
if err != nil {
impl.Logger.Errorw("error in parsing app status config variables", "err", err)
continue
}
if time.Now().After(helmInstalledDevtronApp.GetLastDeployed().AsTime().Add(time.Duration(appServiceConfig.HelmInstallationTimeout) * time.Minute)) {
// If release status is in pending-install for more than 5 mins, then mark the deployment as failure
wfr.Status = pipelineConfig.WorkflowFailed
wfr.FinishedOn = time.Now()
Expand Down
65 changes: 31 additions & 34 deletions pkg/pipeline/WorkflowDagExecutor.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ func NewWorkflowDagExecutorImpl(Logger *zap.SugaredLogger, pipelineRepository pi
if err != nil {
return nil
}
err = wde.SubscribeDevtronAsyncInstallRequest()
err = wde.SubscribeDevtronAsyncHelmInstallRequest()
if err != nil {
return nil
}
Expand Down Expand Up @@ -312,55 +312,52 @@ func (impl *WorkflowDagExecutorImpl) Subscribe() error {
return nil
}

func (impl *WorkflowDagExecutorImpl) SubscribeDevtronAsyncInstallRequest() error {
func (impl *WorkflowDagExecutorImpl) extractOverrideRequestFromCDAsyncInstallEvent(msg *pubsub.PubSubMsg) *bean.AsyncCdDeployEvent {
CDAsyncInstallNatsMessage := &bean.AsyncCdDeployEvent{}
err := json.Unmarshal([]byte(msg.Data), CDAsyncInstallNatsMessage)
if err != nil {
impl.logger.Errorw("error in unmarshalling CD async install request nats message", "err", err)
return nil
}
overrideRequest := CDAsyncInstallNatsMessage.ValuesOverrideRequest
pipeline, err := impl.pipelineRepository.FindById(overrideRequest.PipelineId)
if err != nil {
impl.logger.Errorw("error in fetching pipeline by pipelineId", "err", err)
return nil
}
impl.appService.SetPipelineFieldsInOverrideRequest(overrideRequest, pipeline)
return CDAsyncInstallNatsMessage
}

func (impl *WorkflowDagExecutorImpl) SubscribeDevtronAsyncHelmInstallRequest() error {
callback := func(msg *pubsub.PubSubMsg) {
impl.logger.Debug("received Devtron App helm async install request event, SubscribeDevtronAsyncInstallRequest", "data", msg.Data)
CDAsyncInstallNatsMessage := &bean.AsyncCdDeployEvent{}
err := json.Unmarshal([]byte(msg.Data), CDAsyncInstallNatsMessage)
if err != nil {
impl.logger.Errorw("error in unmarshalling CD async install request nats message, SubscribeDevtronAsyncInstallRequest", "err", err)
return
}
impl.logger.Debug("received Devtron App helm async install request event, SubscribeDevtronAsyncHelmInstallRequest", "data", msg.Data)
CDAsyncInstallNatsMessage := impl.extractOverrideRequestFromCDAsyncInstallEvent(msg)
overrideRequest := CDAsyncInstallNatsMessage.ValuesOverrideRequest
appServiceConfig, err := app.GetAppServiceConfig()
if err != nil {
impl.logger.Errorw("error in parsing app status config variables, SubscribeDevtronAsyncInstallRequest", "err", err)
return
}
pipeline, err := impl.pipelineRepository.FindById(overrideRequest.PipelineId)
if err != nil {
impl.logger.Errorw("error in fetching pipeline by pipelineId, SubscribeDevtronAsyncInstallRequest", "err", err)
return
}
impl.appService.SetPipelineFieldsInOverrideRequest(overrideRequest, pipeline)
if overrideRequest.DeploymentAppType != bean2.Helm {
impl.logger.Errorw("invalid deployment type for CD async install event, SubscribeDevtronAsyncInstallRequest", "overrideRequest", overrideRequest)
return
}
if overrideRequest.WfrId < 1 {
impl.logger.Errorw("invalid overrideRequest WfrId for CD async install event, SubscribeDevtronAsyncInstallRequest", "overrideRequest", overrideRequest)
return
}
cdWfr, err := impl.cdWorkflowRepository.FindWorkflowRunnerById(overrideRequest.WfrId)
if err != nil && err != pg.ErrNoRows {
impl.logger.Errorw("err on fetching cd workflow, SubscribeDevtronAsyncInstallRequest", "err", err)
impl.logger.Errorw("err on fetching cd workflow, SubscribeDevtronAsyncHelmInstallRequest", "err", err)
return
}

// skip if the cdWfr.Status is already in a terminal state
if cdWfr != nil && slices.Contains(pipelineConfig.WfrTerminalStatusList, cdWfr.Status) {
impl.logger.Errorw("err on fetching cd workflow, SubscribeDevtronAsyncInstallRequest", "err", err)
impl.logger.Warnw("skipped deployment as the runner status is already in terminal state", "cdWfrId", cdWfr, "status", cdWfr.Status)
return
}

ctx, cancel := context.WithTimeout(context.Background(), time.Duration(appServiceConfig.DevtronChartInstallTimeout)*time.Minute)
appServiceConfig, err := app.GetAppServiceConfig()
if err != nil {
impl.logger.Errorw("error in parsing app status config variables, SubscribeDevtronAsyncHelmInstallRequest", "err", err)
return
}
timeout := util.GetMaxInteger(appServiceConfig.HelmInstallationTimeout, appServiceConfig.DevtronChartInstallRequestTimeout)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Minute)
defer cancel()
_, span := otel.Tracer("orchestrator").Start(ctx, "appService.TriggerRelease")
releaseId, _, releaseErr := impl.appService.TriggerRelease(overrideRequest, ctx, CDAsyncInstallNatsMessage.TriggeredAt, CDAsyncInstallNatsMessage.TriggeredBy)
span.End()
if releaseErr != nil {
if err = impl.MarkCurrentDeploymentFailed(cdWfr, releaseErr, overrideRequest.UserId); err != nil {
impl.logger.Errorw("error while updating current runner status to failed, SubscribeDevtronAsyncInstallRequest", "cdWfr", cdWfr.Id, "err", err)
impl.logger.Errorw("error while updating current runner status to failed, SubscribeDevtronAsyncHelmInstallRequest", "cdWfr", cdWfr.Id, "err", err)
}
return
} else {
Expand Down
Loading