diff --git a/.github/workflows/pr-issue-validator.yaml b/.github/workflows/pr-issue-validator.yaml index 24836de7f4..ca7dee58f5 100644 --- a/.github/workflows/pr-issue-validator.yaml +++ b/.github/workflows/pr-issue-validator.yaml @@ -11,7 +11,7 @@ on: - 'main' - 'release-**' - 'develop' - - 'hotfix-v0**' + - 'hotfix-**' # paths-ignore: # - 'docs/**' # - '.github/' @@ -19,267 +19,27 @@ on: # - 'charts/' # - 'manifests/' # - 'sample-docker-templates/' - jobs: validate-PR-issue: runs-on: ubuntu-latest - permissions: - issues: write - contents: read - pull-requests: write - repository-projects: read + steps: - name: Checkout repository uses: actions/checkout@v2 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - - name: Validate Issue Reference + - name: Set up jq (for parsing JSON) + run: sudo apt-get install -y jq + + - name: PR Validation Script env: - GH_TOKEN: ${{ github.token }} PR_BODY: ${{ github.event.pull_request.body }} PRNUM: ${{ github.event.pull_request.number }} TITLE: ${{ github.event.pull_request.title }} + GH_TOKEN: ${{ github.token }} + GH_PR_VALIDATOR_TOKEN: ${{ secrets.GH_PR_VALIDATOR_TOKEN }} + BASE_REPO: ${{ github.event.pull_request.base.repo.full_name }} + HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} run: | - - echo "base or target repo : ${{ github.event.pull_request.base.repo.full_name }}" - echo "head or source repo : ${{ github.event.pull_request.head.repo.full_name }}" - - if [[ ${{ github.event.pull_request.head.repo.full_name }} == ${{ github.event.pull_request.base.repo.full_name }} ]]; then - export forked=false - else - export forked=true - fi - - set -x - # Skip validation for documentation or chore PRs - if [[ "$TITLE" =~ ^(doc:|docs:|chore:|misc:|Release:|release:|Sync:|sync:) ]]; then - echo "Skipping validation for docs/chore PR." - echo "PR NUMBER-: $PRNUM " - gh pr edit $PRNUM --remove-label "PR:Issue-verification-failed" - gh pr edit $PRNUM --add-label "PR:Ready-to-Review" - exit 0 - fi - - # Define all issue matching patterns - patterns=( - "((Fixes|Resolves) #[0-9]+)" - "((Fixes|Resolves) https://github.com/devtron-labs/devtron/issues/[0-9]+)" - "((Fixes|Resolves) devtron-labs/devtron#[0-9]+)" - "(Fixes|Resolves):?\\s+\\[#([0-9]+)\\]" - "((Fixes|Resolves):? #devtron-labs/devops-sprint/issues/[0-9]+)" - "((Fixes|Resolves):? #devtron-labs/sprint-tasks/issues/[0-9]+)" - "((Fixes|Resolves) https://github.com/devtron-labs/devops-sprint/issues/[0-9]+)" - "((Fixes|Resolves) https://github.com/devtron-labs/sprint-tasks/issues/[0-9]+)" - "((Fixes|Resolves):? #devtron-labs/sprint-tasks#[0-9]+)" - ) - - # Extract issue number and repo from PR body - extract_issue_number() { - local pattern="$1" # Get the pattern as the first argument to the function - - # Check if PR_BODY matches the provided pattern using Bash's =~ regex operator - if [[ "$PR_BODY" =~ $pattern ]]; then - echo "matched for this pattern $pattern" - - issue_num=$(echo "$PR_BODY" | grep -oE "$pattern" | grep -oE "[0-9]+") - - # Extract the repository name (e.g., devtron-labs/devtron) from PR_BODY using grep - repo=$(echo "$PR_BODY" | grep -oE "devtron-labs/[a-zA-Z0-9_-]+") - echo "Extracted issue number: $issue_num from repo: $repo" - - return 0 # Return success - else - echo "No match for the pattern $pattern" - fi - return 1 # Return failure if no match - } - - issue_num="" - repo="devtron-labs/devtron" # Default repo - for pattern in "${patterns[@]}"; do - echo "Now checking for $pattern" - extract_issue_number "$pattern" && break - done - - if [[ -z "$issue_num" ]]; then - echo "No valid issue number found." - gh pr edit $PRNUM --add-label "PR:Issue-verification-failed" - gh pr edit $PRNUM --remove-label "PR:Ready-to-Review" - exit 1 - fi - - # Form the issue API URL dynamically - issue_api_url="https://api.github.com/repos/$repo/issues/$issue_num" - echo "API URL: $issue_api_url" - - if [[ $repo == "devtron-labs/devtron" || $repo == "devtron-labs/devtron-services" || $repo == "devtron-labs/dashboard" ]]; then - echo "No extra arguments needed: public repository detected." - response_code=$(curl -s -o /dev/null -w "%{http_code}" \ - "$issue_api_url") - - else - echo "Adding extra arguments for authentication: private repository detected." - response_code=$(curl -s -o /dev/null -w "%{http_code}" \ - --header "authorization: Bearer ${{ secrets.GH_PR_VALIDATOR_TOKEN }}" \ - "$issue_api_url") - fi - - echo "Response Code: $response_code" - if [[ "$response_code" -eq 200 ]]; then - echo "Issue #$issue_num is valid and exists in $repo." - - # Fetch the current state of the issue (open/closed) from the private repository. - if [[ $repo == "devtron-labs/devtron" || $repo == "devtron-labs/devtron-services" || $repo == "devtron-labs/dashboard" ]]; then - echo "No extra arguments needed: public repository detected." - issue_status=$(curl -s \ - "$issue_api_url"| jq '.state'|tr -d \") - else - echo "Adding extra arguments for authentication: private repository detected." - issue_status=$(curl -s \ - --header "authorization: Bearer ${{ secrets.GH_PR_VALIDATOR_TOKEN }}" \ - "$issue_api_url"| jq '.state'|tr -d \") - fi - echo "Issue Number : $issue_num Status: $issue_status" - # Check if the issue is still open. - # if [[ "$issue_status" == open ]]; then - # echo "Issue #$issue_num is opened." - if [[ $forked == true ]]; then - echo "PR:Ready-to-Review, exiting gracefully" - exit 0 - fi - # Remove the 'Issue-verification-failed' label (if present) and add 'Ready-to-Review'. - gh pr edit $PRNUM --remove-label "PR:Issue-verification-failed" - gh pr edit $PRNUM --add-label "PR:Ready-to-Review" - echo "PR:Ready-to-Review, exiting gracefully" - exit 0 - # else - # echo "Issue #$issue_num is closed. Please link an open issue to proceed." - # if [[ $forked == true ]]; then - # echo "PR:Ready-to-Review, exiting gracefully" - # exit 0 - # fi - # Add a comment to the PR indicating the issue is not linked correctly. - # gh pr comment $PRNUM --body "PR is linked to a closed issue. Please link an open issue to proceed." - - # Add the 'Issue-verification-failed' label and remove 'Ready-to-Review'. - # gh pr edit $PRNUM --add-label "PR:Issue-verification-failed" - # gh pr edit $PRNUM --remove-label "PR:Ready-to-Review" - # exit 1 - #fi - else - echo "Issue not found. Invalid URL or issue number." - # Add a comment to the PR indicating the issue is not linked correctly. - gh pr comment $PRNUM --body "PR is not linked to a valid issue. Please update the issue link." - - # Apply 'Issue-verification-failed' label and remove 'Ready-to-Review' label. - gh pr edit $PRNUM --add-label "PR:Issue-verification-failed" - gh pr edit $PRNUM --remove-label "PR:Ready-to-Review" - exit 1 - fi - - name: Check SQL file format and duplicates - shell: bash - env: - pr_no: ${{ github.event.pull_request.number }} - GH_TOKEN: ${{ github.token }} - run: | - # Fetch the latest changes from the main branch - git fetch origin main - - # Get the list of changed files - git diff origin/main...HEAD --name-only > diff - - # Specify the directory containing migration files - MIGRATION_DIR="scripts/sql" - ls - pwd - - # Print changed files - echo "Changed files:" - cat diff - - changed_files="" - while IFS= read -r file; do - if [[ $file == $MIGRATION_DIR/* && $file == *.up.sql ]]; then - changed_files+="$file\n" - fi - done < diff - - # Print the filtered .up.sql files - echo "Filtered .up.sql files:" - echo -e "$changed_files" - - # Check if there are any .up.sql migration files in the changed files list - if [ -z "$changed_files" ]; then - echo "No .up.sql migration files found in the changes." - else - # Extract unique migration numbers from the directory (considering only .up.sql files) - existing_migrations=$(ls $MIGRATION_DIR | grep -E "\.up\.sql$" | grep -oE "[0-9]{3}[0-9]{3}[0-9]{2}" | sort | uniq) - - # Exclude migration numbers from changed files in existing_migrations - while read -r file; do - migration_number=$(basename "$file" | grep -oE "[0-9]{3}[0-9]{3}[0-9]{2}") - existing_migrations=$(echo "$existing_migrations" | grep -v "$migration_number") - done <<< "$changed_files" - - # Validate each changed .up.sql migration file - is_valid=true - processed_migrations=() - while read -r file; do - # Extract migration number from the filename - migration_number=$(basename "$file" | grep -oE "[0-9]{3}[0-9]{3}[0-9]{2}") - - # Check if the filename has the full XXXPPPNN format - if [[ ! $(basename "$file") =~ ^[0-9]{3}[0-9]{3}[0-9]{2}_ ]]; then - echo "Error: Migration file $file does not have the complete XXXPPPNN format." - is_valid=false - continue - fi - - if [ -z "$migration_number" ]; then - echo "Warning: Could not extract migration number from $file." - continue - fi - - # Check if this migration number has already been processed - if [[ " ${processed_migrations[@]} " =~ " $migration_number " ]]; then - continue - fi - processed_migrations+=("$migration_number") - - # Check if the migration number is unique - if echo "$existing_migrations" | grep -q "$migration_number"; then - echo "Error: Migration number $migration_number already exists." - is_valid=false - fi - - # Check if the migration number is greater than previous ones - last_migration=$(echo "$existing_migrations" | tail -n 1) - if [ "$migration_number" -le "$last_migration" ]; then - echo "Error: Migration number $migration_number is not greater than the latest ($last_migration)." - is_valid=false - fi - - # Check for sequential hotfix requirement (if NN > 01, check for NN-1) - hotfix_number=$(echo "$migration_number" | grep -oE "[0-9]{2}$") - if [ "$hotfix_number" -gt "01" ]; then - previous_hotfix=$(printf "%02d" $((10#$hotfix_number - 1))) - expected_previous_number="${migration_number:0:6}$previous_hotfix" - if ! echo "$existing_migrations" | grep -q "$expected_previous_number"; then - echo "Error: Previous hotfix migration $expected_previous_number not found for $migration_number." - is_valid=false - fi - fi - - done <<< "$changed_files" - - if [ "$is_valid" = false ]; then - echo "Validation failed. Please fix the errors before merging." - gh pr comment $pr_no --body "The Migration files providede inside of the PR does not pass the criteria!!" - exit 1 - fi - - echo "All .up.sql migration file validations passed." - gh pr comment $pr_no --body "The migration files have successfully passed the criteria!!" - fi + wget https://raw.githubusercontent.com/devtron-labs/utilities/feat/central-pr-validator/.github/workflows/validateIssue.sh + chmod +x validateIssue.sh + ./validateIssue.sh \ No newline at end of file diff --git a/App.go b/App.go index 649c319298..924319f39f 100644 --- a/App.go +++ b/App.go @@ -56,6 +56,7 @@ type App struct { server *http.Server db *pg.DB posthogClient *posthogTelemetry.PosthogClient + userService user.UserService // eventProcessor.CentralEventProcessor is used to register event processors centralEventProcessor *eventProcessor.CentralEventProcessor // do not remove this. // used for local dev only @@ -79,6 +80,7 @@ func NewApp(router *router.MuxRouter, pubSubClient *pubsub.PubSubClientServiceImpl, workflowEventProcessorImpl *in.WorkflowEventProcessorImpl, enforcerV2 *casbinv2.SyncedEnforcer, + userService user.UserService, ) *App { //check argo connection //todo - check argo-cd version on acd integration installation @@ -97,6 +99,7 @@ func NewApp(router *router.MuxRouter, centralEventProcessor: centralEventProcessor, pubSubClient: pubSubClient, workflowEventProcessorImpl: workflowEventProcessorImpl, + userService: userService, } return app } @@ -112,7 +115,7 @@ func (app *App) Start() { app.MuxRouter.Init() //authEnforcer := casbin2.Create() - server := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: authMiddleware.Authorizer(app.sessionManager2, user.WhitelistChecker, nil)(app.MuxRouter.Router)} + server := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: authMiddleware.Authorizer(app.sessionManager2, user.WhitelistChecker, app.userService.CheckUserStatusAndUpdateLoginAudit)(app.MuxRouter.Router)} app.MuxRouter.Router.Use(app.loggingMiddleware.LoggingMiddleware) app.MuxRouter.Router.Use(middleware.PrometheusMiddleware) app.MuxRouter.Router.Use(middlewares.Recovery) diff --git a/Wire.go b/Wire.go index f239eb7d06..9e0bbff87b 100644 --- a/Wire.go +++ b/Wire.go @@ -303,6 +303,9 @@ func InitializeApp() (*App, error) { pipeline.NewCiMaterialConfigServiceImpl, wire.Bind(new(pipeline.CiMaterialConfigService), new(*pipeline.CiMaterialConfigServiceImpl)), + app.NewDeploymentEventHandlerImpl, + wire.Bind(new(app.DeploymentEventHandler), new(*app.DeploymentEventHandlerImpl)), + pipeline.NewAppArtifactManagerImpl, wire.Bind(new(pipeline.AppArtifactManager), new(*pipeline.AppArtifactManagerImpl)), pipeline.NewDevtronAppCMCSServiceImpl, diff --git a/api/bean/AppView/AppView.go b/api/bean/AppView/AppView.go index 8779691a25..cc93ff7221 100644 --- a/api/bean/AppView/AppView.go +++ b/api/bean/AppView/AppView.go @@ -64,6 +64,7 @@ type GenericNoteResponseBean struct { Description string `json:"description"` UpdatedBy string `json:"updatedBy"` UpdatedOn time.Time `json:"updatedOn"` + CreatedBy string `json:"createdBy"` } type JobContainer struct { @@ -100,6 +101,7 @@ type JobListingContainer struct { LastTriggeredEnvironmentName string `sql:"last_triggered_environment_name" json:"lastTriggeredEnvironmentName"` LastTriggeredEnvironmentId int `sql:"last_triggered_environment_id" json:"lastEnvironmentId"` ProjectId int `sql:"team_id" json:"projectId"` + CreatedBy int32 `sql:"created_by" json:"createdBy"` } type CiPipelineLastSucceededTime struct { diff --git a/api/helm-app/gRPC/applicationClient.go b/api/helm-app/gRPC/applicationClient.go index 5f79ebae40..57c3af7c94 100644 --- a/api/helm-app/gRPC/applicationClient.go +++ b/api/helm-app/gRPC/applicationClient.go @@ -107,7 +107,7 @@ func (impl *HelmAppClientImpl) getConnection() (*grpc.ClientConn, error) { grpc.MaxCallRecvMsgSize(impl.grpcConfig.KubelinkMaxSendMsgSize*1024*1024), // GRPC Request size grpc.MaxCallSendMsgSize(impl.grpcConfig.KubelinkMaxRecvMsgSize*1024*1024), // GRPC Response size ), - grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`), + grpc.WithDefaultServiceConfig(impl.grpcConfig.KubelinkGRPCServiceConfig), ) endpoint := fmt.Sprintf("dns:///%s", impl.helmClientConfig.Url) conn, err := grpc.DialContext(ctx, endpoint, opts...) diff --git a/api/k8s/application/k8sApplicationRestHandler.go b/api/k8s/application/k8sApplicationRestHandler.go index 644830a36c..7c92322f60 100644 --- a/api/k8s/application/k8sApplicationRestHandler.go +++ b/api/k8s/application/k8sApplicationRestHandler.go @@ -1025,6 +1025,8 @@ func (handler *K8sApplicationRestHandlerImpl) CreateEphemeralContainer(w http.Re return } request.UserId = userId + request.ExternalArgoAppIdentifier = resourceRequestBean.ExternalArgoAppIdentifier + err = handler.k8sApplicationService.CreatePodEphemeralContainers(&request) if err != nil { handler.logger.Errorw("error occurred in creating ephemeral container", "err", err, "requestPayload", request) @@ -1073,6 +1075,7 @@ func (handler *K8sApplicationRestHandlerImpl) DeleteEphemeralContainer(w http.Re return } request.UserId = userId + request.ExternalArgoAppIdentifier = resourceRequestBean.ExternalArgoAppIdentifier _, err = handler.k8sApplicationService.TerminatePodEphemeralContainer(request) if err != nil { handler.logger.Errorw("error occurred in terminating ephemeral container", "err", err, "requestPayload", request) @@ -1156,6 +1159,7 @@ func (handler *K8sApplicationRestHandlerImpl) verifyRbacForAppRequests(token str handler.logger.Errorw("error in decoding appId", "err", err, "appId", request.AppId) return false, err } + request.ExternalArgoAppIdentifier = argoAppIdentifier request.ClusterId = argoAppIdentifier.ClusterId request.ExternalArgoApplicationName = argoAppIdentifier.AppName valid, err := handler.argoApplicationReadService.ValidateArgoResourceRequest(r.Context(), argoAppIdentifier, request.K8sRequest) diff --git a/api/restHandler/BatchOperationRestHandler.go b/api/restHandler/BatchOperationRestHandler.go index fd598e5725..f9df83eeed 100644 --- a/api/restHandler/BatchOperationRestHandler.go +++ b/api/restHandler/BatchOperationRestHandler.go @@ -22,6 +22,7 @@ import ( "errors" "net/http" + "github.com/devtron-labs/common-lib/async" "github.com/devtron-labs/devtron/api/restHandler/common" "github.com/devtron-labs/devtron/pkg/apis/devtron/v1" "github.com/devtron-labs/devtron/pkg/apis/devtron/v1/validation" @@ -44,10 +45,11 @@ type BatchOperationRestHandlerImpl struct { teamService team.TeamService logger *zap.SugaredLogger enforcerUtil rbac.EnforcerUtil + asyncRunnable *async.Runnable } func NewBatchOperationRestHandlerImpl(userAuthService user.UserService, enforcer casbin.Enforcer, workflowAction batch.WorkflowAction, - teamService team.TeamService, logger *zap.SugaredLogger, enforcerUtil rbac.EnforcerUtil) *BatchOperationRestHandlerImpl { + teamService team.TeamService, logger *zap.SugaredLogger, enforcerUtil rbac.EnforcerUtil, asyncRunnable *async.Runnable) *BatchOperationRestHandlerImpl { return &BatchOperationRestHandlerImpl{ userAuthService: userAuthService, enforcer: enforcer, @@ -55,6 +57,7 @@ func NewBatchOperationRestHandlerImpl(userAuthService user.UserService, enforcer teamService: teamService, logger: logger, enforcerUtil: enforcerUtil, + asyncRunnable: asyncRunnable, } } @@ -104,13 +107,14 @@ func (handler BatchOperationRestHandlerImpl) Operate(w http.ResponseWriter, r *h ctx, cancel := context.WithCancel(r.Context()) if cn, ok := w.(http.CloseNotifier); ok { - go func(done <-chan struct{}, closed <-chan bool) { + runnableFunc := func(done <-chan struct{}, closed <-chan bool) { select { case <-done: case <-closed: cancel() } - }(ctx.Done(), cn.CloseNotify()) + } + handler.asyncRunnable.Execute(func() { runnableFunc(ctx.Done(), cn.CloseNotify()) }) } err = handler.workflowAction.Execute(&workflow, emptyProps, r.Context()) if err != nil { diff --git a/api/restHandler/NotificationRestHandler.go b/api/restHandler/NotificationRestHandler.go index 6ba1b93dcf..d7ddbd76ff 100644 --- a/api/restHandler/NotificationRestHandler.go +++ b/api/restHandler/NotificationRestHandler.go @@ -51,7 +51,6 @@ const ( ) type NotificationRestHandler interface { - SaveNotificationSettings(w http.ResponseWriter, r *http.Request) SaveNotificationSettingsV2(w http.ResponseWriter, r *http.Request) UpdateNotificationSettings(w http.ResponseWriter, r *http.Request) SaveNotificationChannelConfig(w http.ResponseWriter, r *http.Request) @@ -118,66 +117,6 @@ func NewNotificationRestHandlerImpl(dockerRegistryConfig pipeline.DockerRegistry } } -// SaveNotificationSettings will be deprecated in future -func (impl NotificationRestHandlerImpl) SaveNotificationSettings(w http.ResponseWriter, r *http.Request) { - userId, err := impl.userAuthService.GetLoggedInUser(r) - if userId == 0 || err != nil { - common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) - return - } - var notificationSetting beans.NotificationRequest - err = json.NewDecoder(r.Body).Decode(¬ificationSetting) - if err != nil { - impl.logger.Errorw("request err, SaveNotificationSettings", "err", err, "payload", notificationSetting) - common.WriteJsonResp(w, err, nil, http.StatusBadRequest) - return - } - impl.logger.Infow("request payload, SaveNotificationSettings", "err", err, "payload", notificationSetting) - err = impl.validator.Struct(notificationSetting) - if err != nil { - impl.logger.Errorw("validation err, SaveNotificationSettings", "err", err, "payload", notificationSetting) - common.WriteJsonResp(w, err, nil, http.StatusBadRequest) - return - } - - //RBAC - token := r.Header.Get("token") - if isSuperAdmin := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !isSuperAdmin { - common.WriteJsonResp(w, err, nil, http.StatusForbidden) - return - } - //RBAC - - providers := notificationSetting.Providers - - if len(providers) != 0 { - for _, provider := range providers { - if provider.Destination == util.SMTP || provider.Destination == util.SES { - if provider.Recipient == "" { - userEmail, err := impl.userAuthService.GetEmailById(int32(provider.ConfigId)) - if err != nil { - impl.logger.Errorw("service err, SaveNotificationSettings", "err", err, "payload", notificationSetting) - common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) - return - } - provider.Recipient = userEmail - } - // get default configID for SES and SMTP - provider.ConfigId = notificationSetting.SesConfigId - } - } - } - - res, err := impl.notificationService.CreateOrUpdateNotificationSettings(¬ificationSetting, userId) - if err != nil { - impl.logger.Errorw("service err, SaveNotificationSettings", "err", err, "payload", notificationSetting) - common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - common.WriteJsonResp(w, nil, res, http.StatusOK) -} - func (impl NotificationRestHandlerImpl) SaveNotificationSettingsV2(w http.ResponseWriter, r *http.Request) { userId, err := impl.userAuthService.GetLoggedInUser(r) if userId == 0 || err != nil { diff --git a/api/restHandler/app/pipeline/AutoCompleteRestHandler.go b/api/restHandler/app/pipeline/AutoCompleteRestHandler.go index 165afaf581..8bae0d4b8e 100644 --- a/api/restHandler/app/pipeline/AutoCompleteRestHandler.go +++ b/api/restHandler/app/pipeline/AutoCompleteRestHandler.go @@ -18,6 +18,7 @@ package pipeline import ( "context" + "errors" "fmt" "github.com/devtron-labs/devtron/pkg/auth/user" "github.com/devtron-labs/devtron/pkg/build/git/gitProvider/read" @@ -93,6 +94,24 @@ func (handler DevtronAppAutoCompleteRestHandlerImpl) GetAppListForAutocomplete(w v := r.URL.Query() teamId := v.Get("teamId") appName := v.Get("appName") + offset := 0 + size := 0 // default value is 0, it means if not provided in query param it will fetch all + sizeStr := v.Get("size") + if sizeStr != "" { + size, err = strconv.Atoi(sizeStr) + if err != nil || size < 0 { + common.WriteJsonResp(w, errors.New("invalid size"), nil, http.StatusBadRequest) + return + } + } + offsetStr := v.Get("offset") + if offsetStr != "" { + offset, err = strconv.Atoi(offsetStr) + if err != nil || offset < 0 { + common.WriteJsonResp(w, errors.New("invalid offset"), nil, http.StatusBadRequest) + return + } + } appTypeParam := v.Get("appType") var appType int if appTypeParam != "" { @@ -131,6 +150,7 @@ func (handler DevtronAppAutoCompleteRestHandlerImpl) GetAppListForAutocomplete(w } } if isActionUserSuperAdmin { + apps = handler.getPaginatedResultsForApps(offset, size, apps) common.WriteJsonResp(w, err, apps, http.StatusOK) return } @@ -161,6 +181,7 @@ func (handler DevtronAppAutoCompleteRestHandlerImpl) GetAppListForAutocomplete(w } span.End() // RBAC + accessedApps = handler.getPaginatedResultsForApps(offset, size, accessedApps) common.WriteJsonResp(w, err, accessedApps, http.StatusOK) } @@ -290,3 +311,14 @@ func (handler DevtronAppAutoCompleteRestHandlerImpl) TeamListAutocomplete(w http common.WriteJsonResp(w, err, result, http.StatusOK) } + +func (handler DevtronAppAutoCompleteRestHandlerImpl) getPaginatedResultsForApps(offset int, size int, apps []*pipeline.AppBean) []*pipeline.AppBean { + if size > 0 { + if offset+size <= len(apps) { + apps = apps[offset : offset+size] + } else { + apps = apps[offset:] + } + } + return apps +} diff --git a/api/restHandler/app/pipeline/configure/BuildPipelineRestHandler.go b/api/restHandler/app/pipeline/configure/BuildPipelineRestHandler.go index c7d11e12aa..57b048e7db 100644 --- a/api/restHandler/app/pipeline/configure/BuildPipelineRestHandler.go +++ b/api/restHandler/app/pipeline/configure/BuildPipelineRestHandler.go @@ -1085,6 +1085,16 @@ func (handler *PipelineConfigRestHandlerImpl) GetBuildLogs(w http.ResponseWriter common.WriteJsonResp(w, err, nil, http.StatusBadRequest) return } + followLogs := true + if ok := r.URL.Query().Has("followLogs"); ok { + followLogsStr := r.URL.Query().Get("followLogs") + follow, err := strconv.ParseBool(followLogsStr) + if err != nil { + common.WriteJsonResp(w, err, "followLogs is not a valid bool", http.StatusBadRequest) + return + } + followLogs = follow + } workflowId, err := strconv.Atoi(vars["workflowId"]) if err != nil { @@ -1116,7 +1126,7 @@ func (handler *PipelineConfigRestHandlerImpl) GetBuildLogs(w http.ResponseWriter return } } - logsReader, cleanUp, err := handler.ciHandlerService.GetRunningWorkflowLogs(workflowId) + logsReader, cleanUp, err := handler.ciHandlerService.GetRunningWorkflowLogs(workflowId, followLogs) if err != nil { handler.Logger.Errorw("service err, GetBuildLogs", "err", err, "pipelineId", pipelineId, "workflowId", workflowId, "lastEventId", lastEventId) common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) diff --git a/api/restHandler/app/pipeline/configure/DeploymentPipelineRestHandler.go b/api/restHandler/app/pipeline/configure/DeploymentPipelineRestHandler.go index 4a94fff0b5..d784037519 100644 --- a/api/restHandler/app/pipeline/configure/DeploymentPipelineRestHandler.go +++ b/api/restHandler/app/pipeline/configure/DeploymentPipelineRestHandler.go @@ -1609,6 +1609,16 @@ func (handler *PipelineConfigRestHandlerImpl) GetPrePostDeploymentLogs(w http.Re common.WriteJsonResp(w, err, nil, http.StatusBadRequest) return } + followLogs := true + if ok := r.URL.Query().Has("followLogs"); ok { + followLogsStr := r.URL.Query().Get("followLogs") + follow, err := strconv.ParseBool(followLogsStr) + if err != nil { + common.WriteJsonResp(w, err, "followLogs is not a valid bool", http.StatusBadRequest) + return + } + followLogs = follow + } handler.Logger.Infow("request payload, GetPrePostDeploymentLogs", "err", err, "appId", appId, "environmentId", environmentId, "pipelineId", pipelineId, "workflowId", workflowId) // RBAC CHECK @@ -1619,7 +1629,7 @@ func (handler *PipelineConfigRestHandlerImpl) GetPrePostDeploymentLogs(w http.Re } // RBAC CHECK - logsReader, cleanUp, err := handler.cdHandlerService.GetRunningWorkflowLogs(environmentId, pipelineId, workflowId) + logsReader, cleanUp, err := handler.cdHandlerService.GetRunningWorkflowLogs(environmentId, pipelineId, workflowId, followLogs) if err != nil { handler.Logger.Errorw("service err, GetPrePostDeploymentLogs", "err", err, "appId", appId, "environmentId", environmentId, "pipelineId", pipelineId, "workflowId", workflowId) common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) diff --git a/api/router/NotificationRouter.go b/api/router/NotificationRouter.go index f0de1d33f3..2180478b16 100644 --- a/api/router/NotificationRouter.go +++ b/api/router/NotificationRouter.go @@ -32,9 +32,9 @@ func NewNotificationRouterImpl(notificationRestHandler restHandler.NotificationR return &NotificationRouterImpl{notificationRestHandler: notificationRestHandler} } func (impl NotificationRouterImpl) InitNotificationRegRouter(configRouter *mux.Router) { - // to maintain backward compatibility, will be deprecated in future + // do not remove it, this path is used in devtcl cli configRouter.Path(""). - HandlerFunc(impl.notificationRestHandler.SaveNotificationSettings). + HandlerFunc(impl.notificationRestHandler.SaveNotificationSettingsV2). Methods("POST") // new router to save notification settings configRouter.Path("/v2"). diff --git a/client/argocdServer/connection/Connection.go b/client/argocdServer/connection/Connection.go index 7e690864d5..24f8141442 100644 --- a/client/argocdServer/connection/Connection.go +++ b/client/argocdServer/connection/Connection.go @@ -21,6 +21,7 @@ import ( "fmt" "github.com/argoproj/argo-cd/v2/pkg/apiclient/account" "github.com/argoproj/argo-cd/v2/util/settings" + "github.com/devtron-labs/common-lib/async" "github.com/devtron-labs/common-lib/utils/k8s" "github.com/devtron-labs/devtron/client/argocdServer/bean" config2 "github.com/devtron-labs/devtron/client/argocdServer/config" @@ -81,6 +82,7 @@ type ArgoCDConnectionManagerImpl struct { gitOpsConfigReadService config.GitOpsConfigReadService runTimeConfig *k8s.RuntimeConfig argoCDConfigGetter config2.ArgoCDConfigGetter + asyncRunnable *async.Runnable } func NewArgoCDConnectionManagerImpl(Logger *zap.SugaredLogger, @@ -92,7 +94,8 @@ func NewArgoCDConnectionManagerImpl(Logger *zap.SugaredLogger, versionService version.VersionService, gitOpsConfigReadService config.GitOpsConfigReadService, runTimeConfig *k8s.RuntimeConfig, - argoCDConfigGetter config2.ArgoCDConfigGetter) (*ArgoCDConnectionManagerImpl, error) { + argoCDConfigGetter config2.ArgoCDConfigGetter, + asyncRunnable *async.Runnable) (*ArgoCDConnectionManagerImpl, error) { argoUserServiceImpl := &ArgoCDConnectionManagerImpl{ logger: Logger, settingsManager: settingsManager, @@ -105,13 +108,17 @@ func NewArgoCDConnectionManagerImpl(Logger *zap.SugaredLogger, gitOpsConfigReadService: gitOpsConfigReadService, runTimeConfig: runTimeConfig, argoCDConfigGetter: argoCDConfigGetter, + asyncRunnable: asyncRunnable, } if !runTimeConfig.LocalDevMode { grpcConfig, err := argoCDConfigGetter.GetGRPCConfig() if err != nil { Logger.Errorw("error in GetAllGRPCConfigs", "error", err) } - go argoUserServiceImpl.ValidateGitOpsAndGetOrUpdateArgoCdUserDetail(grpcConfig) + runnableFunc := func() { + argoUserServiceImpl.ValidateGitOpsAndGetOrUpdateArgoCdUserDetail(grpcConfig) + } + asyncRunnable.Execute(runnableFunc) } return argoUserServiceImpl, nil } diff --git a/client/events/EventBuilder.go b/client/events/EventBuilder.go index 45eac8855d..991c186069 100644 --- a/client/events/EventBuilder.go +++ b/client/events/EventBuilder.go @@ -206,6 +206,26 @@ func (impl *EventSimpleFactoryImpl) BuildExtraCIData(event Event, material *buil payload.TriggeredBy = user.EmailId event.Payload = payload } + + // fetching all the envs which are directly or indirectly linked with the ci pipeline + if event.PipelineId > 0 { + // Get the pipeline to check if it's external + ciPipeline, err := impl.ciPipelineRepository.FindById(event.PipelineId) + if err != nil { + impl.logger.Errorw("error in getting ci pipeline", "pipelineId", event.PipelineId, "err", err) + } else { + envs, err := impl.envRepository.FindEnvLinkedWithCiPipelines(ciPipeline.IsExternal, []int{event.PipelineId}) + if err != nil { + impl.logger.Errorw("error in finding environments linked with ci pipeline", "pipelineId", event.PipelineId, "err", err) + } else { + event.EnvIdsForCiPipeline = make([]int, 0, len(envs)) + for _, env := range envs { + event.EnvIdsForCiPipeline = append(event.EnvIdsForCiPipeline, env.Id) + } + } + } + } + return event } diff --git a/client/events/EventClient.go b/client/events/EventClient.go index b15c23b513..2a6e409d02 100644 --- a/client/events/EventClient.go +++ b/client/events/EventClient.go @@ -18,6 +18,7 @@ package client import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -38,6 +39,7 @@ import ( type EventClientConfig struct { DestinationURL string `env:"EVENT_URL" envDefault:"http://localhost:3000/notify" description:"Notifier service url"` NotificationMedium NotificationMedium `env:"NOTIFICATION_MEDIUM" envDefault:"rest" description:"notification medium"` + EnableNotifierV2 bool `env:"ENABLE_NOTIFIER_V2" envDefault:"false" description:"enable notifier v2"` } type NotificationMedium string @@ -58,24 +60,25 @@ type EventClient interface { } type Event struct { - EventTypeId int `json:"eventTypeId"` - EventName string `json:"eventName"` - PipelineId int `json:"pipelineId"` - PipelineType string `json:"pipelineType"` - CorrelationId string `json:"correlationId"` - Payload *Payload `json:"payload"` - EventTime string `json:"eventTime"` - TeamId int `json:"teamId"` - AppId int `json:"appId"` - EnvId int `json:"envId"` - IsProdEnv bool `json:"isProdEnv"` - ClusterId int `json:"clusterId"` - CdWorkflowType bean.WorkflowType `json:"cdWorkflowType,omitempty"` - CdWorkflowRunnerId int `json:"cdWorkflowRunnerId"` - CiWorkflowRunnerId int `json:"ciWorkflowRunnerId"` - CiArtifactId int `json:"ciArtifactId"` - BaseUrl string `json:"baseUrl"` - UserId int `json:"-"` + EventTypeId int `json:"eventTypeId"` + EventName string `json:"eventName"` + PipelineId int `json:"pipelineId"` + PipelineType string `json:"pipelineType"` + CorrelationId string `json:"correlationId"` + Payload *Payload `json:"payload"` + EventTime string `json:"eventTime"` + TeamId int `json:"teamId"` + AppId int `json:"appId"` + EnvId int `json:"envId"` + IsProdEnv bool `json:"isProdEnv"` + ClusterId int `json:"clusterId"` + CdWorkflowType bean.WorkflowType `json:"cdWorkflowType,omitempty"` + CdWorkflowRunnerId int `json:"cdWorkflowRunnerId"` + CiWorkflowRunnerId int `json:"ciWorkflowRunnerId"` + CiArtifactId int `json:"ciArtifactId"` + EnvIdsForCiPipeline []int `json:"envIdsForCiPipeline"` + BaseUrl string `json:"baseUrl"` + UserId int `json:"-"` } type Payload struct { @@ -95,22 +98,25 @@ type Payload struct { } type EventRESTClientImpl struct { - logger *zap.SugaredLogger - client *http.Client - config *EventClientConfig - pubsubClient *pubsub.PubSubClientServiceImpl - ciPipelineRepository pipelineConfig.CiPipelineRepository - pipelineRepository pipelineConfig.PipelineRepository - attributesRepository repository.AttributesRepository - moduleService module.ModuleService + logger *zap.SugaredLogger + client *http.Client + config *EventClientConfig + pubsubClient *pubsub.PubSubClientServiceImpl + ciPipelineRepository pipelineConfig.CiPipelineRepository + pipelineRepository pipelineConfig.PipelineRepository + attributesRepository repository.AttributesRepository + moduleService module.ModuleService + notificationSettingsRepository repository.NotificationSettingsRepository } func NewEventRESTClientImpl(logger *zap.SugaredLogger, client *http.Client, config *EventClientConfig, pubsubClient *pubsub.PubSubClientServiceImpl, ciPipelineRepository pipelineConfig.CiPipelineRepository, pipelineRepository pipelineConfig.PipelineRepository, - attributesRepository repository.AttributesRepository, moduleService module.ModuleService) *EventRESTClientImpl { + attributesRepository repository.AttributesRepository, moduleService module.ModuleService, + notificationSettingsRepository repository.NotificationSettingsRepository) *EventRESTClientImpl { return &EventRESTClientImpl{logger: logger, client: client, config: config, pubsubClient: pubsubClient, ciPipelineRepository: ciPipelineRepository, pipelineRepository: pipelineRepository, - attributesRepository: attributesRepository, moduleService: moduleService} + attributesRepository: attributesRepository, moduleService: moduleService, + notificationSettingsRepository: notificationSettingsRepository} } func (impl *EventRESTClientImpl) buildFinalPayload(event Event, cdPipeline *pipelineConfig.Pipeline, ciPipeline *pipelineConfig.CiPipeline) *Payload { @@ -235,34 +241,131 @@ func (impl *EventRESTClientImpl) sendEventsOnNats(body []byte) error { // do not call this method if notification module is not installed func (impl *EventRESTClientImpl) sendEvent(event Event) (bool, error) { impl.logger.Debugw("event before send", "event", event) - body, err := json.Marshal(event) + + // Step 1: Create payload and destination URL based on config + bodyBytes, destinationUrl, err := impl.createPayloadAndDestination(event) if err != nil { - impl.logger.Errorw("error while marshaling event request ", "err", err) return false, err } + + // Step 2: Send via appropriate medium (NATS or REST) + return impl.deliverEvent(bodyBytes, destinationUrl) +} + +func (impl *EventRESTClientImpl) createPayloadAndDestination(event Event) ([]byte, string, error) { + if impl.config.EnableNotifierV2 { + return impl.createV2PayloadAndDestination(event) + } + return impl.createDefaultPayloadAndDestination(event) +} + +func (impl *EventRESTClientImpl) createV2PayloadAndDestination(event Event) ([]byte, string, error) { + destinationUrl := impl.config.DestinationURL + "/v2" + + // Fetch notification settings + req := repository.GetRulesRequest{ + TeamId: event.TeamId, + EnvId: event.EnvId, + AppId: event.AppId, + PipelineId: event.PipelineId, + PipelineType: event.PipelineType, + IsProdEnv: &event.IsProdEnv, + ClusterId: event.ClusterId, + EnvIdsForCiPipeline: event.EnvIdsForCiPipeline, + } + notificationSettings, err := impl.notificationSettingsRepository.FindNotificationSettingsWithRules( + context.Background(), event.EventTypeId, req, + ) + if err != nil { + impl.logger.Errorw("error while fetching notification settings", "err", err) + return nil, "", err + } + + // Process notification settings into beans + notificationSettingsBean, err := impl.processNotificationSettings(notificationSettings) + if err != nil { + return nil, "", err + } + + // Create combined payload + combinedPayload := map[string]interface{}{ + "event": event, + "notificationSettings": notificationSettingsBean, + } + + bodyBytes, err := json.Marshal(combinedPayload) + if err != nil { + impl.logger.Errorw("error while marshaling combined event request", "err", err) + return nil, "", err + } + + return bodyBytes, destinationUrl, nil +} + +func (impl *EventRESTClientImpl) createDefaultPayloadAndDestination(event Event) ([]byte, string, error) { + bodyBytes, err := json.Marshal(event) + if err != nil { + impl.logger.Errorw("error while marshaling event request", "err", err) + return nil, "", err + } + return bodyBytes, impl.config.DestinationURL, nil +} + +func (impl *EventRESTClientImpl) processNotificationSettings(notificationSettings []repository.NotificationSettings) ([]*repository.NotificationSettingsBean, error) { + notificationSettingsBean := make([]*repository.NotificationSettingsBean, 0) + for _, item := range notificationSettings { + config := make([]repository.ConfigEntry, 0) + if item.Config != "" { + if err := json.Unmarshal([]byte(item.Config), &config); err != nil { + impl.logger.Errorw("error while unmarshaling config", "err", err) + return nil, err + } + } + notificationSettingsBean = append(notificationSettingsBean, &repository.NotificationSettingsBean{ + Id: item.Id, + TeamId: item.TeamId, + AppId: item.AppId, + EnvId: item.EnvId, + PipelineId: item.PipelineId, + PipelineType: item.PipelineType, + EventTypeId: item.EventTypeId, + Config: config, + ViewId: item.ViewId, + }) + } + return notificationSettingsBean, nil +} + +func (impl *EventRESTClientImpl) deliverEvent(bodyBytes []byte, destinationUrl string) (bool, error) { if impl.config.NotificationMedium == PUB_SUB { - err = impl.sendEventsOnNats(body) - if err != nil { - impl.logger.Errorw("error while publishing event ", "err", err) + if err := impl.sendEventsOnNats(bodyBytes); err != nil { + impl.logger.Errorw("error while publishing event", "err", err) return false, err } return true, nil } - var reqBody = []byte(body) - req, err := http.NewRequest(http.MethodPost, impl.config.DestinationURL, bytes.NewBuffer(reqBody)) + + req, err := http.NewRequest(http.MethodPost, destinationUrl, bytes.NewBuffer(bodyBytes)) if err != nil { - impl.logger.Errorw("error while writing event", "err", err) + impl.logger.Errorw("error while creating HTTP request", "err", err) return false, err } req.Header.Set("Content-Type", "application/json") + resp, err := impl.client.Do(req) if err != nil { - impl.logger.Errorw("error while UpdateJiraTransition request ", "err", err) + impl.logger.Errorw("error while sending HTTP request", "err", err) return false, err } defer resp.Body.Close() - impl.logger.Debugw("event completed", "event resp", resp) - return true, err + + if resp.StatusCode >= 300 { + impl.logger.Errorw("unexpected response from notifier", "status", resp.StatusCode) + return false, fmt.Errorf("unexpected response code: %d", resp.StatusCode) + } + + impl.logger.Debugw("event successfully delivered", "status", resp.StatusCode) + return true, nil } func (impl *EventRESTClientImpl) WriteNatsEvent(topic string, payload interface{}) error { diff --git a/client/gitSensor/GitSensorClient.go b/client/gitSensor/GitSensorClient.go index e36154beea..aefe4f01da 100644 --- a/client/gitSensor/GitSensorClient.go +++ b/client/gitSensor/GitSensorClient.go @@ -81,9 +81,10 @@ func NewGitSensorClient(logger *zap.SugaredLogger, config *ClientConfig) (*Clien // CATEGORY=INFRA_SETUP type ClientConfig struct { - Url string `env:"GIT_SENSOR_URL" envDefault:"127.0.0.1:7070" description:"git-sensor micro-service url "` - Protocol string `env:"GIT_SENSOR_PROTOCOL" envDefault:"REST" description:"Protocol to connect with git-sensor micro-service"` - Timeout int `env:"GIT_SENSOR_TIMEOUT" envDefault:"0" description:"Timeout for getting response from the git-sensor"` // in seconds + Url string `env:"GIT_SENSOR_URL" envDefault:"127.0.0.1:7070" description:"git-sensor micro-service url "` + Protocol string `env:"GIT_SENSOR_PROTOCOL" envDefault:"REST" description:"Protocol to connect with git-sensor micro-service"` + Timeout int `env:"GIT_SENSOR_TIMEOUT" envDefault:"0" description:"Timeout for getting response from the git-sensor"` // in seconds + ServiceConfig string `env:"GIT_SENSOR_SERVICE_CONFIG" envDefault:"{\"loadBalancingPolicy\":\"pick_first\"}" description:"git-sensor grpc service config"` } func GetConfig() (*ClientConfig, error) { diff --git a/client/gitSensor/GitSensorGrpcClient.go b/client/gitSensor/GitSensorGrpcClient.go index 0988b47abf..9f78482736 100644 --- a/client/gitSensor/GitSensorGrpcClient.go +++ b/client/gitSensor/GitSensorGrpcClient.go @@ -90,7 +90,7 @@ func (client *GrpcApiClientImpl) getConnection() (*grpc.ClientConn, error) { grpc.WithChainUnaryInterceptor(grpc_prometheus.UnaryClientInterceptor, otelgrpc.UnaryClientInterceptor()), grpc.WithBlock(), grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`), + grpc.WithDefaultServiceConfig(client.config.ServiceConfig), ) endpoint := fmt.Sprintf("dns:///%s", client.config.Url) diff --git a/client/telemetry/TelemetryEventClient.go b/client/telemetry/TelemetryEventClient.go index df44eb6130..2572aa099b 100644 --- a/client/telemetry/TelemetryEventClient.go +++ b/client/telemetry/TelemetryEventClient.go @@ -263,6 +263,8 @@ func (impl *TelemetryEventClientImpl) SummaryDetailsForTelemetry() (cluster []be return clusters, users, k8sServerVersion, hostURL, ssoSetup, HelmAppAccessCount, ChartStoreVisitCount, SkippedOnboarding, HelmAppUpdateCounter, helmChartSuccessfulDeploymentCount, ExternalHelmAppClusterCount } +// New methods for collecting additional telemetry metrics + func (impl *TelemetryEventClientImpl) SummaryEventForTelemetryEA() { err := impl.SendSummaryEvent(string(Summary)) if err != nil { @@ -310,6 +312,11 @@ func (impl *TelemetryEventClientImpl) SendSummaryEvent(eventType string) error { payload.HelmChartSuccessfulDeploymentCount = helmChartSuccessfulDeploymentCount payload.ExternalHelmAppClusterCount = ExternalHelmAppClusterCount + // Collect EA-mode compatible telemetry metrics + payload.HelmAppCount = impl.getHelmAppCount() + payload.PhysicalClusterCount, payload.IsolatedClusterCount = impl.getClusterCounts() + payload.ActiveUsersLast30Days = impl.getActiveUsersLast30Days() + payload.ClusterProvider, err = impl.GetCloudProvider() if err != nil { impl.logger.Errorw("error while getting cluster provider", "error", err) diff --git a/client/telemetry/TelemetryEventClientExtended.go b/client/telemetry/TelemetryEventClientExtended.go index 87de1d237f..3f9d982d55 100644 --- a/client/telemetry/TelemetryEventClientExtended.go +++ b/client/telemetry/TelemetryEventClientExtended.go @@ -26,6 +26,7 @@ import ( installedAppReader "github.com/devtron-labs/devtron/pkg/appStore/installedApp/read" "github.com/devtron-labs/devtron/pkg/auth/sso" user2 "github.com/devtron-labs/devtron/pkg/auth/user" + authPolicyRepository "github.com/devtron-labs/devtron/pkg/auth/user/repository" "github.com/devtron-labs/devtron/pkg/build/git/gitMaterial/read" repository3 "github.com/devtron-labs/devtron/pkg/build/git/gitProvider/repository" "github.com/devtron-labs/devtron/pkg/build/pipeline/bean" @@ -34,6 +35,8 @@ import ( "github.com/devtron-labs/devtron/pkg/cluster/environment" "github.com/devtron-labs/devtron/pkg/deployment/gitOps/config" moduleRepo "github.com/devtron-labs/devtron/pkg/module/repo" + pluginRepository "github.com/devtron-labs/devtron/pkg/plugin/repository" + cvePolicyRepository "github.com/devtron-labs/devtron/pkg/policyGovernance/security/imageScanning/repository" serverDataStore "github.com/devtron-labs/devtron/pkg/server/store" ucidService "github.com/devtron-labs/devtron/pkg/ucid" util3 "github.com/devtron-labs/devtron/pkg/util" @@ -68,6 +71,11 @@ type TelemetryEventClientImplExtended struct { chartRepository chartRepoRepository.ChartRepository ciBuildConfigService pipeline.CiBuildConfigService gitOpsConfigReadService config.GitOpsConfigReadService + // Additional repositories for FULL-mode telemetry metrics + pluginRepository pluginRepository.GlobalPluginRepository + cvePolicyRepository cvePolicyRepository.CvePolicyRepository + defaultAuthPolicyRepository authPolicyRepository.DefaultAuthPolicyRepository + rbacPolicyRepository authPolicyRepository.RbacPolicyDataRepository *TelemetryEventClientImpl } @@ -85,7 +93,12 @@ func NewTelemetryEventClientImplExtended(logger *zap.SugaredLogger, client *http ciBuildConfigService pipeline.CiBuildConfigService, moduleRepository moduleRepo.ModuleRepository, serverDataStore *serverDataStore.ServerDataStore, helmAppClient client.HelmAppClient, installedAppReadService installedAppReader.InstalledAppReadService, userAttributesRepository repository.UserAttributesRepository, cloudProviderIdentifierService cloudProviderIdentifier.ProviderIdentifierService, cronLogger *cron3.CronLoggerImpl, - gitOpsConfigReadService config.GitOpsConfigReadService, envVariables *util.EnvironmentVariables) (*TelemetryEventClientImplExtended, error) { + gitOpsConfigReadService config.GitOpsConfigReadService, envVariables *util.EnvironmentVariables, + // Optional repositories for additional telemetry metrics + pluginRepository pluginRepository.GlobalPluginRepository, + cvePolicyRepository cvePolicyRepository.CvePolicyRepository, + defaultAuthPolicyRepository authPolicyRepository.DefaultAuthPolicyRepository, + rbacPolicyRepository authPolicyRepository.RbacPolicyDataRepository) (*TelemetryEventClientImplExtended, error) { cron := cron.New( cron.WithChain(cron.Recover(cronLogger))) @@ -105,6 +118,11 @@ func NewTelemetryEventClientImplExtended(logger *zap.SugaredLogger, client *http chartRepository: chartRepository, ciBuildConfigService: ciBuildConfigService, gitOpsConfigReadService: gitOpsConfigReadService, + // Initialize FULL-mode specific repositories + pluginRepository: pluginRepository, + cvePolicyRepository: cvePolicyRepository, + defaultAuthPolicyRepository: defaultAuthPolicyRepository, + rbacPolicyRepository: rbacPolicyRepository, TelemetryEventClientImpl: &TelemetryEventClientImpl{ cron: cron, logger: logger, @@ -319,6 +337,21 @@ func (impl *TelemetryEventClientImplExtended) SendSummaryEvent(eventType string) payload.HelmChartSuccessfulDeploymentCount = HelmChartSuccessfulDeploymentCount payload.ExternalHelmAppClusterCount = ExternalHelmAppClusterCount + // Collect new telemetry metrics + payload.HelmAppCount = impl.getHelmAppCount() + payload.DevtronAppCount = impl.getDevtronAppCount() + payload.JobCount = impl.getJobCount() + payload.JobPipelineCount = impl.getJobPipelineCount() + payload.JobPipelineTriggeredLast24h = impl.getJobPipelineTriggeredLast24h() + payload.JobPipelineSucceededLast24h = impl.getJobPipelineSucceededLast24h() + payload.UserCreatedPluginCount = impl.getUserCreatedPluginCount() + payload.PolicyCount = impl.getPolicyCount() + payload.AppliedPolicyRowCount = impl.getAppliedPolicyRowCount() + payload.PhysicalClusterCount, payload.IsolatedClusterCount = impl.getClusterCounts() + payload.ActiveUsersLast30Days = impl.getActiveUsersLast30Days() + payload.GitOpsPipelineCount = impl.getGitOpsPipelineCount() + payload.HelmPipelineCount = impl.helmPipelineCount() + payload.ClusterProvider, err = impl.GetCloudProvider() if err != nil { impl.logger.Errorw("error while getting cluster provider", "error", err) @@ -374,7 +407,11 @@ func (impl *TelemetryEventClientImplExtended) getCiBuildTypeData() (int, int, in func (impl *TelemetryEventClientImplExtended) getCiBuildTypeVsStatusVsCount() (successCount map[bean.CiBuildType]int, failureCount map[bean.CiBuildType]int) { successCount = make(map[bean.CiBuildType]int) failureCount = make(map[bean.CiBuildType]int) - buildTypeAndStatusVsCount := impl.ciWorkflowRepository.FindBuildTypeAndStatusDataOfLast1Day() + buildTypeAndStatusVsCount, err := impl.ciWorkflowRepository.FindBuildTypeAndStatusDataOfLast1Day() + if err != nil { + impl.logger.Errorw("error getting build type vs status vs count data", "err", err) + return successCount, failureCount + } for _, buildTypeCount := range buildTypeAndStatusVsCount { if buildTypeCount == nil { continue diff --git a/client/telemetry/bean.go b/client/telemetry/bean.go index f3e95dd0e7..ff7e384e20 100644 --- a/client/telemetry/bean.go +++ b/client/telemetry/bean.go @@ -75,6 +75,11 @@ type TelemetryEventEA struct { HelmChartSuccessfulDeploymentCount int `json:"helmChartSuccessfulDeploymentCount,omitempty"` ExternalHelmAppClusterCount map[int32]int `json:"ExternalHelmAppClusterCount,omitempty"` ClusterProvider string `json:"clusterProvider,omitempty"` + // New telemetry fields + HelmAppCount int `json:"helmAppCount,omitempty"` + PhysicalClusterCount int `json:"physicalClusterCount,omitempty"` + IsolatedClusterCount int `json:"isolatedClusterCount,omitempty"` + ActiveUsersLast30Days int `json:"activeUsersLast30Days,omitempty"` } const AppsCount int = 50 @@ -136,4 +141,19 @@ type TelemetryEventDto struct { HelmChartSuccessfulDeploymentCount int `json:"helmChartSuccessfulDeploymentCount,omitempty"` ExternalHelmAppClusterCount map[int32]int `json:"ExternalHelmAppClusterCount"` ClusterProvider string `json:"clusterProvider,omitempty"` + // New telemetry fields + HelmAppCount int `json:"helmAppCount,omitempty"` + DevtronAppCount int `json:"devtronAppCount,omitempty"` + JobCount int `json:"jobCount,omitempty"` + JobPipelineCount int `json:"jobPipelineCount,omitempty"` + JobPipelineTriggeredLast24h int `json:"jobPipelineTriggeredLast24h,omitempty"` + JobPipelineSucceededLast24h int `json:"jobPipelineSucceededLast24h,omitempty"` + UserCreatedPluginCount int `json:"userCreatedPluginCount,omitempty"` + PolicyCount int `json:"policyCount,omitempty"` + AppliedPolicyRowCount int `json:"appliedPolicyRowCount,omitempty"` + PhysicalClusterCount int `json:"physicalClusterCount,omitempty"` + IsolatedClusterCount int `json:"isolatedClusterCount,omitempty"` + ActiveUsersLast30Days int `json:"activeUsersLast30Days,omitempty"` + GitOpsPipelineCount int `json:"gitOpsPipelineCount,omitempty"` + HelmPipelineCount int `json:"helmPipelineCount,omitempty"` } diff --git a/client/telemetry/telemetryQueries.go b/client/telemetry/telemetryQueries.go new file mode 100644 index 0000000000..7b1f4dacdc --- /dev/null +++ b/client/telemetry/telemetryQueries.go @@ -0,0 +1,58 @@ +package telemetry + +// EA-mode compatible telemetry queries - no imports needed for current methods + +func (impl *TelemetryEventClientImpl) getHelmAppCount() int { + if impl.installedAppReadService == nil { + impl.logger.Warnw("installedAppReadService not available for helm app count") + return -1 + } + count, err := impl.installedAppReadService.GetActiveInstalledAppCount() + if err != nil { + impl.logger.Errorw("error getting helm app count", "err", err) + return -1 + } + return count +} + +// EA-mode compatible telemetry methods + +func (impl *TelemetryEventClientImpl) getClusterCounts() (physicalCount int, isolatedCount int) { + clusters, err := impl.clusterService.FindAllActive() + if err != nil { + impl.logger.Errorw("error getting cluster counts", "err", err) + return -1, -1 + } + + physicalCount = 0 + isolatedCount = 0 + + for _, cluster := range clusters { + if cluster.IsVirtualCluster { + isolatedCount++ + } else { + physicalCount++ + } + } + + return physicalCount, isolatedCount +} + +// Note: FULL-mode specific methods like getDevtronAppCount, getJobCount, etc. +// are now implemented in TelemetryEventClientImplExtended in telemetryQueriesExtended.go + +func (impl *TelemetryEventClientImpl) getActiveUsersLast30Days() int { + if impl.userAuditService == nil { + impl.logger.Warnw("userAuditService not available for active users count") + return -1 + } + + count, err := impl.userAuditService.GetActiveUsersCountInLast30Days() + if err != nil { + impl.logger.Errorw("error getting active users count in last 30 days", "err", err) + return -1 + } + + impl.logger.Debugw("counted active users in last 30 days", "count", count) + return count +} diff --git a/client/telemetry/telemetryQueriesExtended.go b/client/telemetry/telemetryQueriesExtended.go new file mode 100644 index 0000000000..9ec1538328 --- /dev/null +++ b/client/telemetry/telemetryQueriesExtended.go @@ -0,0 +1,137 @@ +package telemetry + +import ( + "github.com/devtron-labs/devtron/pkg/build/pipeline/bean/common" + pluginRepository "github.com/devtron-labs/devtron/pkg/plugin/repository" +) + +// FULL-mode specific telemetry methods for TelemetryEventClientImplExtended + +func (impl *TelemetryEventClientImplExtended) getDevtronAppCount() int { + devtronAppCount, err := impl.appRepository.FindDevtronAppCount() + if err != nil { + impl.logger.Errorw("error getting all apps for devtron app count", "err", err) + return -1 + } + return devtronAppCount +} + +func (impl *TelemetryEventClientImplExtended) getJobCount() int { + jobCount, err := impl.appRepository.FindJobCount() + if err != nil { + impl.logger.Errorw("error getting all apps for job count", "err", err) + return -1 + } + return jobCount +} + +func (impl *TelemetryEventClientImplExtended) getJobPipelineTriggeredLast24h() int { + // Get build type and status data for the last 24 hours + buildTypeStatusData, err := impl.ciWorkflowRepository.FindBuildTypeAndStatusDataOfLast1Day() + if err != nil { + impl.logger.Warnw("no build type status data available for last 24 hours") + return -1 + } + + // Count job pipeline triggers + // Job pipelines have build type "CI_JOB" + jobTriggeredCount := 0 + for _, data := range buildTypeStatusData { + if data.Type == string(common.CI_JOB) { + jobTriggeredCount += data.Count + } + } + + return jobTriggeredCount +} + +func (impl *TelemetryEventClientImplExtended) getJobPipelineSucceededLast24h() int { + // Get build type and status data for the last 24 hours + buildTypeStatusData, err := impl.ciWorkflowRepository.FindBuildTypeAndStatusDataOfLast1Day() + if err != nil { + impl.logger.Warnw("no build type status data available for last 24 hours") + return -1 + } + + // Count successful job pipeline runs + // Job pipelines have build type "CI_JOB" + successfulJobCount := 0 + for _, data := range buildTypeStatusData { + if data.Type == "CI_JOB" && data.Status == "Succeeded" { + successfulJobCount += data.Count + } + } + + impl.logger.Debugw("counted successful job pipeline runs in last 24h", "count", successfulJobCount) + return successfulJobCount +} + +func (impl *TelemetryEventClientImplExtended) getUserCreatedPluginCount() int { + // Get all user-created plugins (SHARED type) + plugins, err := impl.pluginRepository.GetAllPluginMinDataByType(string(pluginRepository.PLUGIN_TYPE_SHARED)) + if err != nil { + impl.logger.Errorw("error getting user created plugin count", "err", err) + return -1 + } + + return len(plugins) +} + +func (impl *TelemetryEventClientImplExtended) getPolicyCount() int { + // Get global policies + globalPolicies, err := impl.cvePolicyRepository.GetGlobalPolicies() + if err != nil { + impl.logger.Errorw("error getting global CVE policies", "err", err) + return -1 + } + return len(globalPolicies) +} + +func (impl *TelemetryEventClientImplExtended) getGitOpsPipelineCount() int { + var count int + query := ` + SELECT COUNT(DISTINCT p.id) + FROM pipeline p + WHERE p.deleted = false AND p.deployment_app_type = 'argo_cd' + ` + + dbConnection := impl.cdWorkflowRepository.GetConnection() + _, err := dbConnection.Query(&count, query) + if err != nil { + impl.logger.Errorw("error getting GitOps pipeline count", "err", err) + return -1 + } + + impl.logger.Debugw("counted GitOps pipelines", "count", count) + return count +} + +func (impl *TelemetryEventClientImplExtended) helmPipelineCount() int { + // Get the pipeline repository from cdWorkflowRepository connection + var count int + query := ` + SELECT COUNT(DISTINCT p.id) + FROM pipeline p + WHERE p.deleted = false AND p.deployment_app_type = 'helm' + ` + + dbConnection := impl.cdWorkflowRepository.GetConnection() + _, err := dbConnection.Query(&count, query) + if err != nil { + impl.logger.Errorw("error getting No-GitOps pipeline count", "err", err) + return -1 + } + + impl.logger.Debugw("counted No-GitOps pipelines", "count", count) + return count +} + +// getJobPipelineCount returns 0 for now as implementation is not yet available +func (impl *TelemetryEventClientImplExtended) getJobPipelineCount() int { + return -1 +} + +// getAppliedPolicyRowCount returns 0 for now as implementation is not yet available +func (impl *TelemetryEventClientImplExtended) getAppliedPolicyRowCount() int { + return -1 +} diff --git a/cmd/external-app/externalApp.go b/cmd/external-app/externalApp.go index dba06f4eea..68c0539818 100644 --- a/cmd/external-app/externalApp.go +++ b/cmd/external-app/externalApp.go @@ -42,6 +42,7 @@ type App struct { server *http.Server telemetry telemetry.TelemetryEventClient posthogClient *posthogTelemetry.PosthogClient + userService user.UserService } func NewApp(db *pg.DB, @@ -49,7 +50,8 @@ func NewApp(db *pg.DB, MuxRouter *MuxRouter, telemetry telemetry.TelemetryEventClient, posthogClient *posthogTelemetry.PosthogClient, - Logger *zap.SugaredLogger) *App { + Logger *zap.SugaredLogger, + userService user.UserService) *App { return &App{ db: db, sessionManager: sessionManager, @@ -57,6 +59,7 @@ func NewApp(db *pg.DB, Logger: Logger, telemetry: telemetry, posthogClient: posthogClient, + userService: userService, } } func (app *App) Start() { @@ -73,7 +76,7 @@ func (app *App) Start() { if err != nil { app.Logger.Warnw("telemetry installation success event failed", "err", err) } - server := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: authMiddleware.Authorizer(app.sessionManager, user.WhitelistChecker, nil)(app.MuxRouter.Router)} + server := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: authMiddleware.Authorizer(app.sessionManager, user.WhitelistChecker, app.userService.CheckUserStatusAndUpdateLoginAudit)(app.MuxRouter.Router)} app.MuxRouter.Router.Use(middleware.PrometheusMiddleware) app.MuxRouter.Router.Use(middlewares.Recovery) app.server = server diff --git a/cmd/external-app/wire.go b/cmd/external-app/wire.go index 8986381ea6..9213d08eea 100644 --- a/cmd/external-app/wire.go +++ b/cmd/external-app/wire.go @@ -74,6 +74,7 @@ import ( repository4 "github.com/devtron-labs/devtron/pkg/appStore/chartGroup/repository" deployment2 "github.com/devtron-labs/devtron/pkg/appStore/installedApp/service/EAMode/deployment" "github.com/devtron-labs/devtron/pkg/appStore/installedApp/service/FullMode/deployment" + "github.com/devtron-labs/devtron/pkg/asyncProvider" "github.com/devtron-labs/devtron/pkg/attributes" "github.com/devtron-labs/devtron/pkg/build/git/gitMaterial" "github.com/devtron-labs/devtron/pkg/commonService" @@ -152,6 +153,7 @@ func InitializeApp() (*App, error) { grafana.GetGrafanaClientConfig, grafana.NewGrafanaClientImpl, wire.Bind(new(grafana.GrafanaClient), new(*grafana.GrafanaClientImpl)), + asyncProvider.WireSet, commonEnforcementFunctionsUtil.NewCommonEnforcementUtilImpl, wire.Bind(new(commonEnforcementFunctionsUtil.CommonEnforcementUtil), new(*commonEnforcementFunctionsUtil.CommonEnforcementUtilImpl)), diff --git a/cmd/external-app/wire_gen.go b/cmd/external-app/wire_gen.go index 12a4fb16d2..8c20cdd737 100644 --- a/cmd/external-app/wire_gen.go +++ b/cmd/external-app/wire_gen.go @@ -80,6 +80,7 @@ import ( "github.com/devtron-labs/devtron/pkg/argoApplication" read9 "github.com/devtron-labs/devtron/pkg/argoApplication/read" config3 "github.com/devtron-labs/devtron/pkg/argoApplication/read/config" + "github.com/devtron-labs/devtron/pkg/asyncProvider" "github.com/devtron-labs/devtron/pkg/attributes" "github.com/devtron-labs/devtron/pkg/auth/authentication" "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" @@ -238,7 +239,8 @@ func InitializeApp() (*App, error) { k8sInformerFactoryImpl := informer.NewK8sInformerFactoryImpl(sugaredLogger, syncMap, k8sServiceImpl) cronLoggerImpl := cron.NewCronLoggerImpl(sugaredLogger) clusterReadServiceImpl := read2.NewClusterReadServiceImpl(sugaredLogger, clusterRepositoryImpl) - clusterServiceImpl, err := cluster.NewClusterServiceImpl(clusterRepositoryImpl, sugaredLogger, k8sServiceImpl, k8sInformerFactoryImpl, userAuthRepositoryImpl, userRepositoryImpl, roleGroupRepositoryImpl, environmentVariables, cronLoggerImpl, clusterReadServiceImpl) + runnable := asyncProvider.NewAsyncRunnable(sugaredLogger) + clusterServiceImpl, err := cluster.NewClusterServiceImpl(clusterRepositoryImpl, sugaredLogger, k8sServiceImpl, k8sInformerFactoryImpl, userAuthRepositoryImpl, userRepositoryImpl, roleGroupRepositoryImpl, environmentVariables, cronLoggerImpl, clusterReadServiceImpl, runnable) if err != nil { return nil, err } @@ -359,10 +361,10 @@ func InitializeApp() (*App, error) { k8sResourceHistoryRepositoryImpl := repository10.NewK8sResourceHistoryRepositoryImpl(db, sugaredLogger) k8sResourceHistoryServiceImpl := kubernetesResourceAuditLogs.Newk8sResourceHistoryServiceImpl(k8sResourceHistoryRepositoryImpl, sugaredLogger, appRepositoryImpl, environmentRepositoryImpl) argoApplicationConfigServiceImpl := config3.NewArgoApplicationConfigServiceImpl(sugaredLogger, k8sServiceImpl, clusterRepositoryImpl) - k8sCommonServiceImpl := k8s2.NewK8sCommonServiceImpl(sugaredLogger, k8sServiceImpl, argoApplicationConfigServiceImpl, clusterReadServiceImpl) + k8sCommonServiceImpl := k8s2.NewK8sCommonServiceImpl(sugaredLogger, k8sServiceImpl, argoApplicationConfigServiceImpl, clusterReadServiceImpl, runnable) ephemeralContainersRepositoryImpl := repository3.NewEphemeralContainersRepositoryImpl(db, transactionUtilImpl) ephemeralContainerServiceImpl := cluster.NewEphemeralContainerServiceImpl(ephemeralContainersRepositoryImpl, sugaredLogger) - terminalSessionHandlerImpl := terminal.NewTerminalSessionHandlerImpl(environmentServiceImpl, sugaredLogger, k8sServiceImpl, ephemeralContainerServiceImpl, argoApplicationConfigServiceImpl, clusterReadServiceImpl) + terminalSessionHandlerImpl := terminal.NewTerminalSessionHandlerImpl(environmentServiceImpl, sugaredLogger, k8sServiceImpl, ephemeralContainerServiceImpl, argoApplicationConfigServiceImpl, clusterReadServiceImpl, runnable) k8sApplicationServiceImpl, err := application.NewK8sApplicationServiceImpl(sugaredLogger, clusterServiceImpl, pumpImpl, helmAppServiceImpl, k8sServiceImpl, acdAuthConfig, k8sResourceHistoryServiceImpl, k8sCommonServiceImpl, terminalSessionHandlerImpl, ephemeralContainerServiceImpl, ephemeralContainersRepositoryImpl, fluxApplicationServiceImpl, clusterReadServiceImpl) if err != nil { return nil, err @@ -466,7 +468,7 @@ func InitializeApp() (*App, error) { if err != nil { return nil, err } - userTerminalAccessServiceImpl, err := clusterTerminalAccess.NewUserTerminalAccessServiceImpl(sugaredLogger, terminalAccessRepositoryImpl, userTerminalSessionConfig, k8sCommonServiceImpl, terminalSessionHandlerImpl, k8sCapacityServiceImpl, k8sServiceImpl, cronLoggerImpl) + userTerminalAccessServiceImpl, err := clusterTerminalAccess.NewUserTerminalAccessServiceImpl(sugaredLogger, terminalAccessRepositoryImpl, userTerminalSessionConfig, k8sCommonServiceImpl, terminalSessionHandlerImpl, k8sCapacityServiceImpl, k8sServiceImpl, cronLoggerImpl, runnable) if err != nil { return nil, err } @@ -498,6 +500,6 @@ func InitializeApp() (*App, error) { restHandlerImpl := userResource2.NewUserResourceRestHandler(sugaredLogger, userServiceImpl, userResourceServiceImpl) routerImpl := userResource2.NewUserResourceRouterImpl(restHandlerImpl) muxRouter := NewMuxRouter(sugaredLogger, ssoLoginRouterImpl, teamRouterImpl, userAuthRouterImpl, userRouterImpl, commonRouterImpl, clusterRouterImpl, dashboardRouterImpl, helmAppRouterImpl, environmentRouterImpl, k8sApplicationRouterImpl, chartRepositoryRouterImpl, appStoreDiscoverRouterImpl, appStoreValuesRouterImpl, appStoreDeploymentRouterImpl, chartProviderRouterImpl, dockerRegRouterImpl, dashboardTelemetryRouterImpl, commonDeploymentRouterImpl, externalLinkRouterImpl, moduleRouterImpl, serverRouterImpl, apiTokenRouterImpl, k8sCapacityRouterImpl, webhookHelmRouterImpl, userAttributesRouterImpl, telemetryRouterImpl, userTerminalAccessRouterImpl, attributesRouterImpl, appRouterEAModeImpl, rbacRoleRouterImpl, argoApplicationRouterImpl, fluxApplicationRouterImpl, routerImpl) - mainApp := NewApp(db, sessionManager, muxRouter, telemetryEventClientImpl, posthogClient, sugaredLogger) + mainApp := NewApp(db, sessionManager, muxRouter, telemetryEventClientImpl, posthogClient, sugaredLogger, userServiceImpl) return mainApp, nil } diff --git a/env_gen.json b/env_gen.json index 2681e575b9..e137f7d5b3 100644 --- a/env_gen.json +++ b/env_gen.json @@ -1 +1 @@ -[{"Category":"CD","Fields":[{"Env":"ARGO_APP_MANUAL_SYNC_TIME","EnvType":"int","EnvValue":"3","EnvDescription":"retry argocd app manual sync if the timeline is stuck in ARGOCD_SYNC_INITIATED state for more than this defined time (in mins)","Example":"","Deprecated":"false"},{"Env":"CD_HELM_PIPELINE_STATUS_CRON_TIME","EnvType":"string","EnvValue":"*/2 * * * *","EnvDescription":"Cron time to check the pipeline status ","Example":"","Deprecated":"false"},{"Env":"CD_PIPELINE_STATUS_CRON_TIME","EnvType":"string","EnvValue":"*/2 * * * *","EnvDescription":"Cron time for CD pipeline status","Example":"","Deprecated":"false"},{"Env":"CD_PIPELINE_STATUS_TIMEOUT_DURATION","EnvType":"string","EnvValue":"20","EnvDescription":"Timeout for CD pipeline to get healthy","Example":"","Deprecated":"false"},{"Env":"DEPLOY_STATUS_CRON_GET_PIPELINE_DEPLOYED_WITHIN_HOURS","EnvType":"int","EnvValue":"12","EnvDescription":"This flag is used to fetch the deployment status of the application. It retrieves the status of deployments that occurred between 12 hours and 10 minutes prior to the current time. It fetches non-terminal statuses.","Example":"","Deprecated":"false"},{"Env":"DEVTRON_CHART_ARGO_CD_INSTALL_REQUEST_TIMEOUT","EnvType":"int","EnvValue":"1","EnvDescription":"Context timeout for gitops concurrent async deployments","Example":"","Deprecated":"false"},{"Env":"DEVTRON_CHART_INSTALL_REQUEST_TIMEOUT","EnvType":"int","EnvValue":"6","EnvDescription":"Context timeout for no gitops concurrent async deployments","Example":"","Deprecated":"false"},{"Env":"EXPOSE_CD_METRICS","EnvType":"bool","EnvValue":"false","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"FEATURE_MIGRATE_ARGOCD_APPLICATION_ENABLE","EnvType":"bool","EnvValue":"false","EnvDescription":"enable migration of external argocd application to devtron pipeline","Example":"","Deprecated":"false"},{"Env":"HELM_PIPELINE_STATUS_CHECK_ELIGIBLE_TIME","EnvType":"string","EnvValue":"120","EnvDescription":"eligible time for checking helm app status periodically and update in db, value is in seconds., default is 120, if wfr is updated within configured time i.e. HELM_PIPELINE_STATUS_CHECK_ELIGIBLE_TIME then do not include for this cron cycle.","Example":"","Deprecated":"false"},{"Env":"IS_INTERNAL_USE","EnvType":"bool","EnvValue":"true","EnvDescription":"If enabled then cd pipeline and helm apps will not need the deployment app type mandatorily. Couple this flag with HIDE_GITOPS_OR_HELM_OPTION (in Dashborad) and if gitops is configured and allowed for the env, pipeline/ helm app will gitops else no-gitops.","Example":"","Deprecated":"false"},{"Env":"MIGRATE_DEPLOYMENT_CONFIG_DATA","EnvType":"bool","EnvValue":"false","EnvDescription":"migrate deployment config data from charts table to deployment_config table","Example":"","Deprecated":"false"},{"Env":"PIPELINE_DEGRADED_TIME","EnvType":"string","EnvValue":"10","EnvDescription":"Time to mark a pipeline degraded if not healthy in defined time","Example":"","Deprecated":"false"},{"Env":"REVISION_HISTORY_LIMIT_DEVTRON_APP","EnvType":"int","EnvValue":"1","EnvDescription":"Count for devtron application rivision history","Example":"","Deprecated":"false"},{"Env":"REVISION_HISTORY_LIMIT_EXTERNAL_HELM_APP","EnvType":"int","EnvValue":"0","EnvDescription":"Count for external helm application rivision history","Example":"","Deprecated":"false"},{"Env":"REVISION_HISTORY_LIMIT_HELM_APP","EnvType":"int","EnvValue":"1","EnvDescription":"To set the history limit for the helm app being deployed through devtron","Example":"","Deprecated":"false"},{"Env":"REVISION_HISTORY_LIMIT_LINKED_HELM_APP","EnvType":"int","EnvValue":"15","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"RUN_HELM_INSTALL_IN_ASYNC_MODE_HELM_APPS","EnvType":"bool","EnvValue":"false","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"SHOULD_CHECK_NAMESPACE_ON_CLONE","EnvType":"bool","EnvValue":"false","EnvDescription":"should we check if namespace exists or not while cloning app","Example":"","Deprecated":"false"},{"Env":"USE_DEPLOYMENT_CONFIG_DATA","EnvType":"bool","EnvValue":"false","EnvDescription":"use deployment config data from deployment_config table","Example":"","Deprecated":"true"}]},{"Category":"CI_RUNNER","Fields":[{"Env":"AZURE_ACCOUNT_KEY","EnvType":"string","EnvValue":"","EnvDescription":"If blob storage is being used of azure then pass the secret key to access the bucket","Example":"","Deprecated":"false"},{"Env":"AZURE_ACCOUNT_NAME","EnvType":"string","EnvValue":"","EnvDescription":"Account name for azure blob storage","Example":"","Deprecated":"false"},{"Env":"AZURE_BLOB_CONTAINER_CI_CACHE","EnvType":"string","EnvValue":"","EnvDescription":"Cache bucket name for azure blob storage","Example":"","Deprecated":"false"},{"Env":"AZURE_BLOB_CONTAINER_CI_LOG","EnvType":"string","EnvValue":"","EnvDescription":"Log bucket for azure blob storage","Example":"","Deprecated":"false"},{"Env":"AZURE_GATEWAY_CONNECTION_INSECURE","EnvType":"bool","EnvValue":"true","EnvDescription":"Azure gateway connection allows insecure if true","Example":"","Deprecated":"false"},{"Env":"AZURE_GATEWAY_URL","EnvType":"string","EnvValue":"http://devtron-minio.devtroncd:9000","EnvDescription":"Sent to CI runner for blob","Example":"","Deprecated":"false"},{"Env":"BASE_LOG_LOCATION_PATH","EnvType":"string","EnvValue":"/home/devtron/","EnvDescription":"Used to store, download logs of ci workflow, artifact","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_GCP_CREDENTIALS_JSON","EnvType":"string","EnvValue":"","EnvDescription":"GCP cred json for GCS blob storage","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_PROVIDER","EnvType":"","EnvValue":"S3","EnvDescription":"Blob storage provider name(AWS/GCP/Azure)","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_S3_ACCESS_KEY","EnvType":"string","EnvValue":"","EnvDescription":"S3 access key for s3 blob storage","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_S3_BUCKET_VERSIONED","EnvType":"bool","EnvValue":"true","EnvDescription":"To enable buctet versioning for blob storage","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_S3_ENDPOINT","EnvType":"string","EnvValue":"","EnvDescription":"S3 endpoint URL for s3 blob storage","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_S3_ENDPOINT_INSECURE","EnvType":"bool","EnvValue":"false","EnvDescription":"To use insecure s3 endpoint","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_S3_SECRET_KEY","EnvType":"string","EnvValue":"","EnvDescription":"Secret key for s3 blob storage","Example":"","Deprecated":"false"},{"Env":"BUILDX_CACHE_PATH","EnvType":"string","EnvValue":"/var/lib/devtron/buildx","EnvDescription":"Path for the buildx cache","Example":"","Deprecated":"false"},{"Env":"BUILDX_K8S_DRIVER_OPTIONS","EnvType":"string","EnvValue":"","EnvDescription":"To enable the k8s driver and pass args for k8s driver in buildx","Example":"","Deprecated":"false"},{"Env":"BUILDX_PROVENANCE_MODE","EnvType":"string","EnvValue":"","EnvDescription":"provinance is set to true by default by docker. this will add some build related data in generated build manifest.it also adds some unknown:unknown key:value pair which may not be compatible by some container registries. with buildx k8s driver , provinenance=true is causing issue when push manifest to quay registry, so setting it to false","Example":"","Deprecated":"false"},{"Env":"BUILD_LOG_TTL_VALUE_IN_SECS","EnvType":"int","EnvValue":"3600","EnvDescription":"This is the time that the pods of ci/pre-cd/post-cd live after completion state.","Example":"","Deprecated":"false"},{"Env":"CACHE_LIMIT","EnvType":"int64","EnvValue":"5000000000","EnvDescription":"Cache limit.","Example":"","Deprecated":"false"},{"Env":"CD_DEFAULT_ADDRESS_POOL_BASE_CIDR","EnvType":"string","EnvValue":"","EnvDescription":"To pass the IP cidr for Pre/Post cd ","Example":"","Deprecated":"false"},{"Env":"CD_DEFAULT_ADDRESS_POOL_SIZE","EnvType":"int","EnvValue":"","EnvDescription":"The subnet size to allocate from the base pool for CD","Example":"","Deprecated":"false"},{"Env":"CD_LIMIT_CI_CPU","EnvType":"string","EnvValue":"0.5","EnvDescription":"CPU Resource Limit Pre/Post CD","Example":"","Deprecated":"false"},{"Env":"CD_LIMIT_CI_MEM","EnvType":"string","EnvValue":"3G","EnvDescription":"Memory Resource Limit Pre/Post CD","Example":"","Deprecated":"false"},{"Env":"CD_NODE_LABEL_SELECTOR","EnvType":"","EnvValue":"","EnvDescription":"Node label selector for Pre/Post CD","Example":"","Deprecated":"false"},{"Env":"CD_NODE_TAINTS_KEY","EnvType":"string","EnvValue":"dedicated","EnvDescription":"Toleration key for Pre/Post CD","Example":"","Deprecated":"false"},{"Env":"CD_NODE_TAINTS_VALUE","EnvType":"string","EnvValue":"ci","EnvDescription":"Toleration value for Pre/Post CD","Example":"","Deprecated":"false"},{"Env":"CD_REQ_CI_CPU","EnvType":"string","EnvValue":"0.5","EnvDescription":"CPU Resource Rquest Pre/Post CD","Example":"","Deprecated":"false"},{"Env":"CD_REQ_CI_MEM","EnvType":"string","EnvValue":"3G","EnvDescription":"Memory Resource Rquest Pre/Post CD","Example":"","Deprecated":"false"},{"Env":"CD_WORKFLOW_EXECUTOR_TYPE","EnvType":"","EnvValue":"AWF","EnvDescription":"Executor type for Pre/Post CD(AWF,System)","Example":"","Deprecated":"false"},{"Env":"CD_WORKFLOW_SERVICE_ACCOUNT","EnvType":"string","EnvValue":"cd-runner","EnvDescription":"Service account to be used in Pre/Post CD pod","Example":"","Deprecated":"false"},{"Env":"CI_DEFAULT_ADDRESS_POOL_BASE_CIDR","EnvType":"string","EnvValue":"","EnvDescription":"To pass the IP cidr for CI","Example":"","Deprecated":"false"},{"Env":"CI_DEFAULT_ADDRESS_POOL_SIZE","EnvType":"int","EnvValue":"","EnvDescription":"The subnet size to allocate from the base pool for CI","Example":"","Deprecated":"false"},{"Env":"CI_IGNORE_DOCKER_CACHE","EnvType":"bool","EnvValue":"","EnvDescription":"Ignoring docker cache ","Example":"","Deprecated":"false"},{"Env":"CI_LOGS_KEY_PREFIX","EnvType":"string","EnvValue":"","EnvDescription":"Prefix for build logs","Example":"","Deprecated":"false"},{"Env":"CI_NODE_LABEL_SELECTOR","EnvType":"","EnvValue":"","EnvDescription":"Node label selector for CI","Example":"","Deprecated":"false"},{"Env":"CI_NODE_TAINTS_KEY","EnvType":"string","EnvValue":"","EnvDescription":"Toleration key for CI","Example":"","Deprecated":"false"},{"Env":"CI_NODE_TAINTS_VALUE","EnvType":"string","EnvValue":"","EnvDescription":"Toleration value for CI","Example":"","Deprecated":"false"},{"Env":"CI_RUNNER_DOCKER_MTU_VALUE","EnvType":"int","EnvValue":"-1","EnvDescription":"this is to control the bytes of inofrmation passed in a network packet in ci-runner. default is -1 (defaults to the underlying node mtu value)","Example":"","Deprecated":"false"},{"Env":"CI_SUCCESS_AUTO_TRIGGER_BATCH_SIZE","EnvType":"int","EnvValue":"1","EnvDescription":"this is to control the no of linked pipelines should be hanled in one go when a ci-success event of an parent ci is received","Example":"","Deprecated":"false"},{"Env":"CI_VOLUME_MOUNTS_JSON","EnvType":"string","EnvValue":"","EnvDescription":"additional volume mount data for CI and JOB","Example":"","Deprecated":"false"},{"Env":"CI_WORKFLOW_EXECUTOR_TYPE","EnvType":"","EnvValue":"AWF","EnvDescription":"Executor type for CI(AWF,System)","Example":"","Deprecated":"false"},{"Env":"DEFAULT_ARTIFACT_KEY_LOCATION","EnvType":"string","EnvValue":"arsenal-v1/ci-artifacts","EnvDescription":"Key location for artifacts being created","Example":"","Deprecated":"false"},{"Env":"DEFAULT_BUILD_LOGS_BUCKET","EnvType":"string","EnvValue":"devtron-pro-ci-logs","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEFAULT_BUILD_LOGS_KEY_PREFIX","EnvType":"string","EnvValue":"arsenal-v1","EnvDescription":"Bucket prefix for build logs","Example":"","Deprecated":"false"},{"Env":"DEFAULT_CACHE_BUCKET","EnvType":"string","EnvValue":"ci-caching","EnvDescription":"Bucket name for build cache","Example":"","Deprecated":"false"},{"Env":"DEFAULT_CACHE_BUCKET_REGION","EnvType":"string","EnvValue":"us-east-2","EnvDescription":"Build Cache bucket region","Example":"","Deprecated":"false"},{"Env":"DEFAULT_CD_ARTIFACT_KEY_LOCATION","EnvType":"string","EnvValue":"","EnvDescription":"Bucket prefix for build cache","Example":"","Deprecated":"false"},{"Env":"DEFAULT_CD_LOGS_BUCKET_REGION","EnvType":"string","EnvValue":"us-east-2","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEFAULT_CD_NAMESPACE","EnvType":"string","EnvValue":"","EnvDescription":"Namespace for devtron stack","Example":"","Deprecated":"false"},{"Env":"DEFAULT_CD_TIMEOUT","EnvType":"int64","EnvValue":"3600","EnvDescription":"Timeout for Pre/Post-Cd to be completed","Example":"","Deprecated":"false"},{"Env":"DEFAULT_CI_IMAGE","EnvType":"string","EnvValue":"686244538589.dkr.ecr.us-east-2.amazonaws.com/cirunner:47","EnvDescription":"To pass the ci-runner image","Example":"","Deprecated":"false"},{"Env":"DEFAULT_NAMESPACE","EnvType":"string","EnvValue":"devtron-ci","EnvDescription":"Timeout for CI to be completed","Example":"","Deprecated":"false"},{"Env":"DEFAULT_TARGET_PLATFORM","EnvType":"string","EnvValue":"","EnvDescription":"Default architecture for buildx","Example":"","Deprecated":"false"},{"Env":"DOCKER_BUILD_CACHE_PATH","EnvType":"string","EnvValue":"/var/lib/docker","EnvDescription":"Path to store cache of docker build (/var/lib/docker-\u003e for legacy docker build, /var/lib/devtron-\u003e for buildx)","Example":"","Deprecated":"false"},{"Env":"ENABLE_BUILD_CONTEXT","EnvType":"bool","EnvValue":"false","EnvDescription":"To Enable build context in Devtron.","Example":"","Deprecated":"false"},{"Env":"ENABLE_WORKFLOW_EXECUTION_STAGE","EnvType":"bool","EnvValue":"true","EnvDescription":"if enabled then we will display build stages separately for CI/Job/Pre-Post CD","Example":"true","Deprecated":"false"},{"Env":"EXTERNAL_BLOB_STORAGE_CM_NAME","EnvType":"string","EnvValue":"blob-storage-cm","EnvDescription":"name of the config map(contains bucket name, etc.) in external cluster when there is some operation related to external cluster, for example:-downloading cd artifact pushed in external cluster's env and we need to download from there, downloads ci logs pushed in external cluster's blob","Example":"","Deprecated":"false"},{"Env":"EXTERNAL_BLOB_STORAGE_SECRET_NAME","EnvType":"string","EnvValue":"blob-storage-secret","EnvDescription":"name of the secret(contains password, accessId,passKeys, etc.) in external cluster when there is some operation related to external cluster, for example:-downloading cd artifact pushed in external cluster's env and we need to download from there, downloads ci logs pushed in external cluster's blob","Example":"","Deprecated":"false"},{"Env":"EXTERNAL_CD_NODE_LABEL_SELECTOR","EnvType":"","EnvValue":"","EnvDescription":"This is an array of strings used when submitting a workflow for pre or post-CD execution. If the ","Example":"","Deprecated":"false"},{"Env":"EXTERNAL_CD_NODE_TAINTS_KEY","EnvType":"string","EnvValue":"dedicated","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"EXTERNAL_CD_NODE_TAINTS_VALUE","EnvType":"string","EnvValue":"ci","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"EXTERNAL_CI_API_SECRET","EnvType":"string","EnvValue":"devtroncd-secret","EnvDescription":"External CI API secret.","Example":"","Deprecated":"false"},{"Env":"EXTERNAL_CI_PAYLOAD","EnvType":"string","EnvValue":"{\"ciProjectDetails\":[{\"gitRepository\":\"https://github.com/vikram1601/getting-started-nodejs.git\",\"checkoutPath\":\"./abc\",\"commitHash\":\"239077135f8cdeeccb7857e2851348f558cb53d3\",\"commitTime\":\"2022-10-30T20:00:00\",\"branch\":\"master\",\"message\":\"Update README.md\",\"author\":\"User Name \"}],\"dockerImage\":\"445808685819.dkr.ecr.us-east-2.amazonaws.com/orch:23907713-2\"}","EnvDescription":"External CI payload with project details.","Example":"","Deprecated":"false"},{"Env":"EXTERNAL_CI_WEB_HOOK_URL","EnvType":"string","EnvValue":"","EnvDescription":"default is {{HOST_URL}}/orchestrator/webhook/ext-ci. It is used for external ci.","Example":"","Deprecated":"false"},{"Env":"IGNORE_CM_CS_IN_CI_JOB","EnvType":"bool","EnvValue":"false","EnvDescription":"Ignore CM/CS in CI-pipeline as Job","Example":"","Deprecated":"false"},{"Env":"IMAGE_RETRY_COUNT","EnvType":"int","EnvValue":"0","EnvDescription":"push artifact(image) in ci retry count ","Example":"","Deprecated":"false"},{"Env":"IMAGE_RETRY_INTERVAL","EnvType":"int","EnvValue":"5","EnvDescription":"image retry interval takes value in seconds","Example":"","Deprecated":"false"},{"Env":"IMAGE_SCANNER_ENDPOINT","EnvType":"string","EnvValue":"http://image-scanner-new-demo-devtroncd-service.devtroncd:80","EnvDescription":"Image-scanner micro-service URL","Example":"","Deprecated":"false"},{"Env":"IMAGE_SCAN_MAX_RETRIES","EnvType":"int","EnvValue":"3","EnvDescription":"Max retry count for image-scanning","Example":"","Deprecated":"false"},{"Env":"IMAGE_SCAN_RETRY_DELAY","EnvType":"int","EnvValue":"5","EnvDescription":"Delay for the image-scaning to start","Example":"","Deprecated":"false"},{"Env":"IN_APP_LOGGING_ENABLED","EnvType":"bool","EnvValue":"false","EnvDescription":"Used in case of argo workflow is enabled. If enabled logs push will be managed by us, else will be managed by argo workflow.","Example":"","Deprecated":"false"},{"Env":"MAX_CD_WORKFLOW_RUNNER_RETRIES","EnvType":"int","EnvValue":"0","EnvDescription":"Maximum time pre/post-cd-workflow create pod if it fails to complete","Example":"","Deprecated":"false"},{"Env":"MAX_CI_WORKFLOW_RETRIES","EnvType":"int","EnvValue":"0","EnvDescription":"Maximum time CI-workflow create pod if it fails to complete","Example":"","Deprecated":"false"},{"Env":"MODE","EnvType":"string","EnvValue":"DEV","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"NATS_SERVER_HOST","EnvType":"string","EnvValue":"localhost:4222","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"ORCH_HOST","EnvType":"string","EnvValue":"http://devtroncd-orchestrator-service-prod.devtroncd/webhook/msg/nats","EnvDescription":"Orchestrator micro-service URL ","Example":"","Deprecated":"false"},{"Env":"ORCH_TOKEN","EnvType":"string","EnvValue":"","EnvDescription":"Orchestrator token","Example":"","Deprecated":"false"},{"Env":"PRE_CI_CACHE_PATH","EnvType":"string","EnvValue":"/devtroncd-cache","EnvDescription":"Cache path for Pre CI tasks","Example":"","Deprecated":"false"},{"Env":"SHOW_DOCKER_BUILD_ARGS","EnvType":"bool","EnvValue":"true","EnvDescription":"To enable showing the args passed for CI in build logs","Example":"","Deprecated":"false"},{"Env":"SKIP_CI_JOB_BUILD_CACHE_PUSH_PULL","EnvType":"bool","EnvValue":"false","EnvDescription":"To skip cache Push/Pull for ci job","Example":"","Deprecated":"false"},{"Env":"SKIP_CREATING_ECR_REPO","EnvType":"bool","EnvValue":"false","EnvDescription":"By disabling this ECR repo won't get created if it's not available on ECR from build configuration","Example":"","Deprecated":"false"},{"Env":"TERMINATION_GRACE_PERIOD_SECS","EnvType":"int","EnvValue":"180","EnvDescription":"this is the time given to workflow pods to shutdown. (grace full termination time)","Example":"","Deprecated":"false"},{"Env":"USE_ARTIFACT_LISTING_QUERY_V2","EnvType":"bool","EnvValue":"true","EnvDescription":"To use the V2 query for listing artifacts","Example":"","Deprecated":"false"},{"Env":"USE_BLOB_STORAGE_CONFIG_IN_CD_WORKFLOW","EnvType":"bool","EnvValue":"true","EnvDescription":"To enable blob storage in pre and post cd","Example":"","Deprecated":"false"},{"Env":"USE_BLOB_STORAGE_CONFIG_IN_CI_WORKFLOW","EnvType":"bool","EnvValue":"true","EnvDescription":"To enable blob storage in pre and post ci","Example":"","Deprecated":"false"},{"Env":"USE_BUILDX","EnvType":"bool","EnvValue":"false","EnvDescription":"To enable buildx feature globally","Example":"","Deprecated":"false"},{"Env":"USE_DOCKER_API_TO_GET_DIGEST","EnvType":"bool","EnvValue":"false","EnvDescription":"when user do not pass the digest then this flag controls , finding the image digest using docker API or not. if set to true we get the digest from docker API call else use docker pull command. [logic in ci-runner]","Example":"","Deprecated":"false"},{"Env":"USE_EXTERNAL_NODE","EnvType":"bool","EnvValue":"false","EnvDescription":"It is used in case of Pre/ Post Cd with run in application mode. If enabled the node lebels are read from EXTERNAL_CD_NODE_LABEL_SELECTOR else from CD_NODE_LABEL_SELECTOR MODE: if the vale is DEV, it will read the local kube config file or else from the cluser location.","Example":"","Deprecated":"false"},{"Env":"USE_IMAGE_TAG_FROM_GIT_PROVIDER_FOR_TAG_BASED_BUILD","EnvType":"bool","EnvValue":"false","EnvDescription":"To use the same tag in container image as that of git tag","Example":"","Deprecated":"false"},{"Env":"WF_CONTROLLER_INSTANCE_ID","EnvType":"string","EnvValue":"devtron-runner","EnvDescription":"Workflow controller instance ID.","Example":"","Deprecated":"false"},{"Env":"WORKFLOW_CACHE_CONFIG","EnvType":"string","EnvValue":"{}","EnvDescription":"flag is used to configure how Docker caches are handled during a CI/CD ","Example":"","Deprecated":"false"},{"Env":"WORKFLOW_SERVICE_ACCOUNT","EnvType":"string","EnvValue":"ci-runner","EnvDescription":"","Example":"","Deprecated":"false"}]},{"Category":"DEVTRON","Fields":[{"Env":"-","EnvType":"","EnvValue":"","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"ADDITIONAL_NODE_GROUP_LABELS","EnvType":"","EnvValue":"","EnvDescription":"Add comma separated list of additional node group labels to default labels","Example":"karpenter.sh/nodepool,cloud.google.com/gke-nodepool","Deprecated":"false"},{"Env":"APP_SYNC_IMAGE","EnvType":"string","EnvValue":"quay.io/devtron/chart-sync:1227622d-132-3775","EnvDescription":"For the app sync image, this image will be used in app-manual sync job","Example":"","Deprecated":"false"},{"Env":"APP_SYNC_JOB_RESOURCES_OBJ","EnvType":"string","EnvValue":"","EnvDescription":"To pass the resource of app sync","Example":"","Deprecated":"false"},{"Env":"APP_SYNC_SERVICE_ACCOUNT","EnvType":"string","EnvValue":"chart-sync","EnvDescription":"Service account to be used in app sync Job","Example":"","Deprecated":"false"},{"Env":"APP_SYNC_SHUTDOWN_WAIT_DURATION","EnvType":"int","EnvValue":"120","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"ARGO_AUTO_SYNC_ENABLED","EnvType":"bool","EnvValue":"true","EnvDescription":"If enabled all argocd application will have auto sync enabled","Example":"","Deprecated":"false"},{"Env":"ARGO_GIT_COMMIT_RETRY_COUNT_ON_CONFLICT","EnvType":"int","EnvValue":"3","EnvDescription":"retry argocd app manual sync if the timeline is stuck in ARGOCD_SYNC_INITIATED state for more than this defined time (in mins)","Example":"","Deprecated":"false"},{"Env":"ARGO_GIT_COMMIT_RETRY_DELAY_ON_CONFLICT","EnvType":"int","EnvValue":"1","EnvDescription":"Delay on retrying the maifest commit the on gitops","Example":"","Deprecated":"false"},{"Env":"ARGO_REPO_REGISTER_RETRY_COUNT","EnvType":"int","EnvValue":"3","EnvDescription":"Argo app registration in argo retries on deployment","Example":"","Deprecated":"false"},{"Env":"ARGO_REPO_REGISTER_RETRY_DELAY","EnvType":"int","EnvValue":"10","EnvDescription":"Argo app registration in argo cd on deployment delay between retry","Example":"","Deprecated":"false"},{"Env":"ASYNC_BUILDX_CACHE_EXPORT","EnvType":"bool","EnvValue":"false","EnvDescription":"To enable async container image cache export","Example":"","Deprecated":"false"},{"Env":"BATCH_SIZE","EnvType":"int","EnvValue":"5","EnvDescription":"there is feature to get URL's of services/ingresses. so to extract those, we need to parse all the servcie and ingress objects of the application. this BATCH_SIZE flag controls the no of these objects get parsed in one go.","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_ENABLED","EnvType":"bool","EnvValue":"false","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"BUILDX_CACHE_MODE_MIN","EnvType":"bool","EnvValue":"false","EnvDescription":"To set build cache mode to minimum in buildx","Example":"","Deprecated":"false"},{"Env":"CD_HOST","EnvType":"string","EnvValue":"localhost","EnvDescription":"Host for the devtron stack","Example":"","Deprecated":"false"},{"Env":"CD_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"CD_PORT","EnvType":"string","EnvValue":"8000","EnvDescription":"Port for pre/post-cd","Example":"","Deprecated":"false"},{"Env":"CExpirationTime","EnvType":"int","EnvValue":"600","EnvDescription":"Caching expiration time.","Example":"","Deprecated":"false"},{"Env":"CI_TRIGGER_CRON_TIME","EnvType":"int","EnvValue":"2","EnvDescription":"For image poll plugin","Example":"","Deprecated":"false"},{"Env":"CI_WORKFLOW_STATUS_UPDATE_CRON","EnvType":"string","EnvValue":"*/5 * * * *","EnvDescription":"Cron schedule for CI pipeline status","Example":"","Deprecated":"false"},{"Env":"CLI_CMD_TIMEOUT_GLOBAL_SECONDS","EnvType":"int","EnvValue":"0","EnvDescription":"Used in git cli opeartion timeout","Example":"","Deprecated":"false"},{"Env":"CLUSTER_STATUS_CRON_TIME","EnvType":"int","EnvValue":"15","EnvDescription":"Cron schedule for cluster status on resource browser","Example":"","Deprecated":"false"},{"Env":"CONSUMER_CONFIG_JSON","EnvType":"string","EnvValue":"","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEFAULT_LOG_TIME_LIMIT","EnvType":"int64","EnvValue":"1","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEFAULT_TIMEOUT","EnvType":"float64","EnvValue":"3600","EnvDescription":"Timeout for CI to be completed","Example":"","Deprecated":"false"},{"Env":"DEVTRON_BOM_URL","EnvType":"string","EnvValue":"https://raw.githubusercontent.com/devtron-labs/devtron/%s/charts/devtron/devtron-bom.yaml","EnvDescription":"Path to devtron-bom.yaml of devtron charts, used for module installation and devtron upgrade","Example":"","Deprecated":"false"},{"Env":"DEVTRON_DEFAULT_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEVTRON_DEX_SECRET_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"Namespace of dex secret","Example":"","Deprecated":"false"},{"Env":"DEVTRON_HELM_RELEASE_CHART_NAME","EnvType":"string","EnvValue":"devtron-operator","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEVTRON_HELM_RELEASE_NAME","EnvType":"string","EnvValue":"devtron","EnvDescription":"Name of the Devtron Helm release. ","Example":"","Deprecated":"false"},{"Env":"DEVTRON_HELM_RELEASE_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"Namespace of the Devtron Helm release","Example":"","Deprecated":"false"},{"Env":"DEVTRON_HELM_REPO_NAME","EnvType":"string","EnvValue":"devtron","EnvDescription":"Is used to install modules (stack manager)","Example":"","Deprecated":"false"},{"Env":"DEVTRON_HELM_REPO_URL","EnvType":"string","EnvValue":"https://helm.devtron.ai","EnvDescription":"Is used to install modules (stack manager)","Example":"","Deprecated":"false"},{"Env":"DEVTRON_INSTALLATION_TYPE","EnvType":"string","EnvValue":"","EnvDescription":"Devtron Installation type(EA/Full)","Example":"","Deprecated":"false"},{"Env":"DEVTRON_MODULES_IDENTIFIER_IN_HELM_VALUES","EnvType":"string","EnvValue":"installer.modules","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEVTRON_SECRET_NAME","EnvType":"string","EnvValue":"devtron-secret","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEVTRON_VERSION_IDENTIFIER_IN_HELM_VALUES","EnvType":"string","EnvValue":"installer.release","EnvDescription":"devtron operator version identifier in helm values yaml","Example":"","Deprecated":"false"},{"Env":"DEX_CID","EnvType":"string","EnvValue":"example-app","EnvDescription":"dex client id ","Example":"","Deprecated":"false"},{"Env":"DEX_CLIENT_ID","EnvType":"string","EnvValue":"argo-cd","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEX_CSTOREKEY","EnvType":"string","EnvValue":"","EnvDescription":"DEX CSTOREKEY.","Example":"","Deprecated":"false"},{"Env":"DEX_JWTKEY","EnvType":"string","EnvValue":"","EnvDescription":"DEX JWT key. ","Example":"","Deprecated":"false"},{"Env":"DEX_RURL","EnvType":"string","EnvValue":"http://127.0.0.1:8080/callback","EnvDescription":"Dex redirect URL(http://argocd-dex-server.devtroncd:8080/callback)","Example":"","Deprecated":"false"},{"Env":"DEX_SCOPES","EnvType":"","EnvValue":"","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEX_SECRET","EnvType":"string","EnvValue":"","EnvDescription":"Dex secret","Example":"","Deprecated":"false"},{"Env":"DEX_URL","EnvType":"string","EnvValue":"","EnvDescription":"Dex service endpoint with dex path(http://argocd-dex-server.devtroncd:5556/dex)","Example":"","Deprecated":"false"},{"Env":"ECR_REPO_NAME_PREFIX","EnvType":"string","EnvValue":"test/","EnvDescription":"Prefix for ECR repo to be created in does not exist","Example":"","Deprecated":"false"},{"Env":"ENABLE_ASYNC_ARGO_CD_INSTALL_DEVTRON_CHART","EnvType":"bool","EnvValue":"false","EnvDescription":"To enable async installation of gitops application","Example":"","Deprecated":"false"},{"Env":"ENABLE_ASYNC_INSTALL_DEVTRON_CHART","EnvType":"bool","EnvValue":"false","EnvDescription":"To enable async installation of no-gitops application","Example":"","Deprecated":"false"},{"Env":"EPHEMERAL_SERVER_VERSION_REGEX","EnvType":"string","EnvValue":"v[1-9]\\.\\b(2[3-9]\\|[3-9][0-9])\\b.*","EnvDescription":"ephemeral containers support version regex that is compared with k8sServerVersion","Example":"","Deprecated":"false"},{"Env":"EVENT_URL","EnvType":"string","EnvValue":"http://localhost:3000/notify","EnvDescription":"Notifier service url","Example":"","Deprecated":"false"},{"Env":"EXECUTE_WIRE_NIL_CHECKER","EnvType":"bool","EnvValue":"false","EnvDescription":"checks for any nil pointer in wire.go","Example":"","Deprecated":"false"},{"Env":"EXPOSE_CI_METRICS","EnvType":"bool","EnvValue":"false","EnvDescription":"To expose CI metrics","Example":"","Deprecated":"false"},{"Env":"FEATURE_RESTART_WORKLOAD_BATCH_SIZE","EnvType":"int","EnvValue":"1","EnvDescription":"restart workload retrieval batch size ","Example":"","Deprecated":"false"},{"Env":"FEATURE_RESTART_WORKLOAD_WORKER_POOL_SIZE","EnvType":"int","EnvValue":"5","EnvDescription":"restart workload retrieval pool size","Example":"","Deprecated":"false"},{"Env":"FORCE_SECURITY_SCANNING","EnvType":"bool","EnvValue":"false","EnvDescription":"By enabling this no one can disable image scaning on ci-pipeline from UI","Example":"","Deprecated":"false"},{"Env":"GITOPS_REPO_PREFIX","EnvType":"string","EnvValue":"","EnvDescription":"Prefix for Gitops repo being creation for argocd application","Example":"","Deprecated":"false"},{"Env":"GO_RUNTIME_ENV","EnvType":"string","EnvValue":"production","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"GRAFANA_HOST","EnvType":"string","EnvValue":"localhost","EnvDescription":"Host URL for the grafana dashboard","Example":"","Deprecated":"false"},{"Env":"GRAFANA_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"Namespace for grafana","Example":"","Deprecated":"false"},{"Env":"GRAFANA_ORG_ID","EnvType":"int","EnvValue":"2","EnvDescription":"Org ID for grafana for application metrics","Example":"","Deprecated":"false"},{"Env":"GRAFANA_PASSWORD","EnvType":"string","EnvValue":"prom-operator","EnvDescription":"Password for grafana dashboard","Example":"","Deprecated":"false"},{"Env":"GRAFANA_PORT","EnvType":"string","EnvValue":"8090","EnvDescription":"Port for grafana micro-service","Example":"","Deprecated":"false"},{"Env":"GRAFANA_URL","EnvType":"string","EnvValue":"","EnvDescription":"Host URL for the grafana dashboard","Example":"","Deprecated":"false"},{"Env":"GRAFANA_USERNAME","EnvType":"string","EnvValue":"admin","EnvDescription":"Username for grafana ","Example":"","Deprecated":"false"},{"Env":"HIDE_IMAGE_TAGGING_HARD_DELETE","EnvType":"bool","EnvValue":"false","EnvDescription":"Flag to hide the hard delete option in the image tagging service","Example":"","Deprecated":"false"},{"Env":"IGNORE_AUTOCOMPLETE_AUTH_CHECK","EnvType":"bool","EnvValue":"false","EnvDescription":"flag for ignoring auth check in autocomplete apis.","Example":"","Deprecated":"false"},{"Env":"INSTALLED_MODULES","EnvType":"","EnvValue":"","EnvDescription":"List of installed modules given in helm values/yaml are written in cm and used by devtron to know which modules are given","Example":"security.trivy,security.clair","Deprecated":"false"},{"Env":"INSTALLER_CRD_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"namespace where Custom Resource Definitions get installed","Example":"","Deprecated":"false"},{"Env":"INSTALLER_CRD_OBJECT_GROUP_NAME","EnvType":"string","EnvValue":"installer.devtron.ai","EnvDescription":"Devtron installer CRD group name, partially deprecated.","Example":"","Deprecated":"false"},{"Env":"INSTALLER_CRD_OBJECT_RESOURCE","EnvType":"string","EnvValue":"installers","EnvDescription":"Devtron installer CRD resource name, partially deprecated","Example":"","Deprecated":"false"},{"Env":"INSTALLER_CRD_OBJECT_VERSION","EnvType":"string","EnvValue":"v1alpha1","EnvDescription":"version of the CRDs. default is v1alpha1","Example":"","Deprecated":"false"},{"Env":"IS_AIR_GAP_ENVIRONMENT","EnvType":"bool","EnvValue":"false","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"JwtExpirationTime","EnvType":"int","EnvValue":"120","EnvDescription":"JWT expiration time.","Example":"","Deprecated":"false"},{"Env":"K8s_CLIENT_MAX_IDLE_CONNS_PER_HOST","EnvType":"int","EnvValue":"25","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"K8s_TCP_IDLE_CONN_TIMEOUT","EnvType":"int","EnvValue":"300","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"K8s_TCP_KEEPALIVE","EnvType":"int","EnvValue":"30","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"K8s_TCP_TIMEOUT","EnvType":"int","EnvValue":"30","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"K8s_TLS_HANDSHAKE_TIMEOUT","EnvType":"int","EnvValue":"10","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"KUBELINK_GRPC_MAX_RECEIVE_MSG_SIZE","EnvType":"int","EnvValue":"20","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"KUBELINK_GRPC_MAX_SEND_MSG_SIZE","EnvType":"int","EnvValue":"4","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"LENS_TIMEOUT","EnvType":"int","EnvValue":"0","EnvDescription":"Lens microservice timeout.","Example":"","Deprecated":"false"},{"Env":"LENS_URL","EnvType":"string","EnvValue":"http://lens-milandevtron-service:80","EnvDescription":"Lens micro-service URL","Example":"","Deprecated":"false"},{"Env":"LIMIT_CI_CPU","EnvType":"string","EnvValue":"0.5","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"LIMIT_CI_MEM","EnvType":"string","EnvValue":"3G","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"LOGGER_DEV_MODE","EnvType":"bool","EnvValue":"false","EnvDescription":"Enables a different logger theme.","Example":"","Deprecated":"false"},{"Env":"LOG_LEVEL","EnvType":"int","EnvValue":"-1","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"MAX_SESSION_PER_USER","EnvType":"int","EnvValue":"5","EnvDescription":"max no of cluster terminal pods can be created by an user","Example":"","Deprecated":"false"},{"Env":"MODULE_METADATA_API_URL","EnvType":"string","EnvValue":"https://api.devtron.ai/module?name=%s","EnvDescription":"Modules list and meta info will be fetched from this server, that is central api server of devtron.","Example":"","Deprecated":"false"},{"Env":"MODULE_STATUS_HANDLING_CRON_DURATION_MIN","EnvType":"int","EnvValue":"3","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"NATS_MSG_ACK_WAIT_IN_SECS","EnvType":"int","EnvValue":"120","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"NATS_MSG_BUFFER_SIZE","EnvType":"int","EnvValue":"-1","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"NATS_MSG_MAX_AGE","EnvType":"int","EnvValue":"86400","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"NATS_MSG_PROCESSING_BATCH_SIZE","EnvType":"int","EnvValue":"1","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"NATS_MSG_REPLICAS","EnvType":"int","EnvValue":"0","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"NOTIFICATION_MEDIUM","EnvType":"NotificationMedium","EnvValue":"rest","EnvDescription":"notification medium","Example":"","Deprecated":"false"},{"Env":"OTEL_COLLECTOR_URL","EnvType":"string","EnvValue":"","EnvDescription":"Opentelemetry URL ","Example":"","Deprecated":"false"},{"Env":"PARALLELISM_LIMIT_FOR_TAG_PROCESSING","EnvType":"int","EnvValue":"","EnvDescription":"App manual sync job parallel tag processing count.","Example":"","Deprecated":"false"},{"Env":"PG_EXPORT_PROM_METRICS","EnvType":"bool","EnvValue":"true","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"PG_LOG_ALL_FAILURE_QUERIES","EnvType":"bool","EnvValue":"true","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"PG_LOG_ALL_QUERY","EnvType":"bool","EnvValue":"false","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"PG_LOG_SLOW_QUERY","EnvType":"bool","EnvValue":"true","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"PG_QUERY_DUR_THRESHOLD","EnvType":"int64","EnvValue":"5000","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"PLUGIN_NAME","EnvType":"string","EnvValue":"Pull images from container repository","EnvDescription":"Handles image retrieval from a container repository and triggers subsequent CI processes upon detecting new images.Current default plugin name: Pull Images from Container Repository.","Example":"","Deprecated":"false"},{"Env":"PROPAGATE_EXTRA_LABELS","EnvType":"bool","EnvValue":"false","EnvDescription":"Add additional propagate labels like api.devtron.ai/appName, api.devtron.ai/envName, api.devtron.ai/project along with the user defined ones.","Example":"","Deprecated":"false"},{"Env":"PROXY_SERVICE_CONFIG","EnvType":"string","EnvValue":"{}","EnvDescription":"Proxy configuration for micro-service to be accessible on orhcestrator ingress","Example":"","Deprecated":"false"},{"Env":"REQ_CI_CPU","EnvType":"string","EnvValue":"0.5","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"REQ_CI_MEM","EnvType":"string","EnvValue":"3G","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"RESTRICT_TERMINAL_ACCESS_FOR_NON_SUPER_USER","EnvType":"bool","EnvValue":"false","EnvDescription":"To restrict the cluster terminal from user having non-super admin acceess","Example":"","Deprecated":"false"},{"Env":"RUNTIME_CONFIG_LOCAL_DEV","EnvType":"LocalDevMode","EnvValue":"true","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"SCOPED_VARIABLE_ENABLED","EnvType":"bool","EnvValue":"false","EnvDescription":"To enable scoped variable option","Example":"","Deprecated":"false"},{"Env":"SCOPED_VARIABLE_FORMAT","EnvType":"string","EnvValue":"@{{%s}}","EnvDescription":"Its a scope format for varialbe name.","Example":"","Deprecated":"false"},{"Env":"SCOPED_VARIABLE_HANDLE_PRIMITIVES","EnvType":"bool","EnvValue":"false","EnvDescription":"This describe should we handle primitives or not in scoped variable template parsing.","Example":"","Deprecated":"false"},{"Env":"SCOPED_VARIABLE_NAME_REGEX","EnvType":"string","EnvValue":"^[a-zA-Z][a-zA-Z0-9_-]{0,62}[a-zA-Z0-9]$","EnvDescription":"Regex for scoped variable name that must passed this regex.","Example":"","Deprecated":"false"},{"Env":"SOCKET_DISCONNECT_DELAY_SECONDS","EnvType":"int","EnvValue":"5","EnvDescription":"The server closes a session when a client receiving connection have not been seen for a while.This delay is configured by this setting. By default the session is closed when a receiving connection wasn't seen for 5 seconds.","Example":"","Deprecated":"false"},{"Env":"SOCKET_HEARTBEAT_SECONDS","EnvType":"int","EnvValue":"25","EnvDescription":"In order to keep proxies and load balancers from closing long running http requests we need to pretend that the connection is active and send a heartbeat packet once in a while. This setting controls how often this is done. By default a heartbeat packet is sent every 25 seconds.","Example":"","Deprecated":"false"},{"Env":"STREAM_CONFIG_JSON","EnvType":"string","EnvValue":"","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"SYSTEM_VAR_PREFIX","EnvType":"string","EnvValue":"DEVTRON_","EnvDescription":"Scoped variable prefix, variable name must have this prefix.","Example":"","Deprecated":"false"},{"Env":"TERMINAL_POD_DEFAULT_NAMESPACE","EnvType":"string","EnvValue":"default","EnvDescription":"Cluster terminal default namespace","Example":"","Deprecated":"false"},{"Env":"TERMINAL_POD_INACTIVE_DURATION_IN_MINS","EnvType":"int","EnvValue":"10","EnvDescription":"Timeout for cluster terminal to be inactive","Example":"","Deprecated":"false"},{"Env":"TERMINAL_POD_STATUS_SYNC_In_SECS","EnvType":"int","EnvValue":"600","EnvDescription":"this is the time interval at which the status of the cluster terminal pod","Example":"","Deprecated":"false"},{"Env":"TEST_APP","EnvType":"string","EnvValue":"orchestrator","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"TEST_PG_ADDR","EnvType":"string","EnvValue":"127.0.0.1","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"TEST_PG_DATABASE","EnvType":"string","EnvValue":"orchestrator","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"TEST_PG_LOG_QUERY","EnvType":"bool","EnvValue":"true","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"TEST_PG_PASSWORD","EnvType":"string","EnvValue":"postgrespw","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"TEST_PG_PORT","EnvType":"string","EnvValue":"55000","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"TEST_PG_USER","EnvType":"string","EnvValue":"postgres","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"TIMEOUT_FOR_FAILED_CI_BUILD","EnvType":"string","EnvValue":"15","EnvDescription":"Timeout for Failed CI build ","Example":"","Deprecated":"false"},{"Env":"TIMEOUT_IN_SECONDS","EnvType":"int","EnvValue":"5","EnvDescription":"timeout to compute the urls from services and ingress objects of an application","Example":"","Deprecated":"false"},{"Env":"USER_SESSION_DURATION_SECONDS","EnvType":"int","EnvValue":"86400","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"USE_ARTIFACT_LISTING_API_V2","EnvType":"bool","EnvValue":"true","EnvDescription":"To use the V2 API for listing artifacts in Listing the images in pipeline","Example":"","Deprecated":"false"},{"Env":"USE_CUSTOM_HTTP_TRANSPORT","EnvType":"bool","EnvValue":"false","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"USE_GIT_CLI","EnvType":"bool","EnvValue":"false","EnvDescription":"To enable git cli","Example":"","Deprecated":"false"},{"Env":"USE_RBAC_CREATION_V2","EnvType":"bool","EnvValue":"true","EnvDescription":"To use the V2 for RBAC creation","Example":"","Deprecated":"false"},{"Env":"VARIABLE_CACHE_ENABLED","EnvType":"bool","EnvValue":"true","EnvDescription":"This is used to control caching of all the scope variables defined in the system.","Example":"","Deprecated":"false"},{"Env":"VARIABLE_EXPRESSION_REGEX","EnvType":"string","EnvValue":"@{{([^}]+)}}","EnvDescription":"Scoped variable expression regex","Example":"","Deprecated":"false"},{"Env":"WEBHOOK_TOKEN","EnvType":"string","EnvValue":"","EnvDescription":"If you want to continue using jenkins for CI then please provide this for authentication of requests","Example":"","Deprecated":"false"}]},{"Category":"GITOPS","Fields":[{"Env":"ACD_CM","EnvType":"string","EnvValue":"argocd-cm","EnvDescription":"Name of the argocd CM","Example":"","Deprecated":"false"},{"Env":"ACD_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"To pass the argocd namespace","Example":"","Deprecated":"false"},{"Env":"ACD_PASSWORD","EnvType":"string","EnvValue":"","EnvDescription":"Password for the Argocd (deprecated)","Example":"","Deprecated":"false"},{"Env":"ACD_USERNAME","EnvType":"string","EnvValue":"admin","EnvDescription":"User name for argocd","Example":"","Deprecated":"false"},{"Env":"GITOPS_SECRET_NAME","EnvType":"string","EnvValue":"devtron-gitops-secret","EnvDescription":"devtron-gitops-secret","Example":"","Deprecated":"false"},{"Env":"RESOURCE_LIST_FOR_REPLICAS","EnvType":"string","EnvValue":"Deployment,Rollout,StatefulSet,ReplicaSet","EnvDescription":"this holds the list of k8s resource names which support replicas key. this list used in hibernate/un hibernate process","Example":"","Deprecated":"false"},{"Env":"RESOURCE_LIST_FOR_REPLICAS_BATCH_SIZE","EnvType":"int","EnvValue":"5","EnvDescription":"this the batch size to control no of above resources can be parsed in one go to determine hibernate status","Example":"","Deprecated":"false"}]},{"Category":"INFRA_SETUP","Fields":[{"Env":"DASHBOARD_HOST","EnvType":"string","EnvValue":"localhost","EnvDescription":"Dashboard micro-service URL","Example":"","Deprecated":"false"},{"Env":"DASHBOARD_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"Dashboard micro-service namespace","Example":"","Deprecated":"false"},{"Env":"DASHBOARD_PORT","EnvType":"string","EnvValue":"3000","EnvDescription":"Port for dashboard micro-service","Example":"","Deprecated":"false"},{"Env":"DEX_HOST","EnvType":"string","EnvValue":"http://localhost","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEX_PORT","EnvType":"string","EnvValue":"5556","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"GIT_SENSOR_PROTOCOL","EnvType":"string","EnvValue":"REST","EnvDescription":"Protocol to connect with git-sensor micro-service","Example":"","Deprecated":"false"},{"Env":"GIT_SENSOR_TIMEOUT","EnvType":"int","EnvValue":"0","EnvDescription":"Timeout for getting response from the git-sensor","Example":"","Deprecated":"false"},{"Env":"GIT_SENSOR_URL","EnvType":"string","EnvValue":"127.0.0.1:7070","EnvDescription":"git-sensor micro-service url ","Example":"","Deprecated":"false"},{"Env":"HELM_CLIENT_URL","EnvType":"string","EnvValue":"127.0.0.1:50051","EnvDescription":"Kubelink micro-service url ","Example":"","Deprecated":"false"}]},{"Category":"POSTGRES","Fields":[{"Env":"APP","EnvType":"string","EnvValue":"orchestrator","EnvDescription":"Application name","Example":"","Deprecated":"false"},{"Env":"CASBIN_DATABASE","EnvType":"string","EnvValue":"casbin","EnvDescription":"Database for casbin","Example":"","Deprecated":"false"},{"Env":"PG_ADDR","EnvType":"string","EnvValue":"127.0.0.1","EnvDescription":"address of postgres service","Example":"postgresql-postgresql.devtroncd","Deprecated":"false"},{"Env":"PG_DATABASE","EnvType":"string","EnvValue":"orchestrator","EnvDescription":"postgres database to be made connection with","Example":"orchestrator, casbin, git_sensor, lens","Deprecated":"false"},{"Env":"PG_PASSWORD","EnvType":"string","EnvValue":"{password}","EnvDescription":"password for postgres, associated with PG_USER","Example":"confidential ;)","Deprecated":"false"},{"Env":"PG_PORT","EnvType":"string","EnvValue":"5432","EnvDescription":"port of postgresql service","Example":"5432","Deprecated":"false"},{"Env":"PG_READ_TIMEOUT","EnvType":"int64","EnvValue":"30","EnvDescription":"Time out for read operation in postgres","Example":"","Deprecated":"false"},{"Env":"PG_USER","EnvType":"string","EnvValue":"postgres","EnvDescription":"user for postgres","Example":"postgres","Deprecated":"false"},{"Env":"PG_WRITE_TIMEOUT","EnvType":"int64","EnvValue":"30","EnvDescription":"Time out for write operation in postgres","Example":"","Deprecated":"false"}]},{"Category":"RBAC","Fields":[{"Env":"ENFORCER_CACHE","EnvType":"bool","EnvValue":"false","EnvDescription":"To Enable enforcer cache.","Example":"","Deprecated":"false"},{"Env":"ENFORCER_CACHE_EXPIRATION_IN_SEC","EnvType":"int","EnvValue":"86400","EnvDescription":"Expiration time (in seconds) for enforcer cache. ","Example":"","Deprecated":"false"},{"Env":"ENFORCER_MAX_BATCH_SIZE","EnvType":"int","EnvValue":"1","EnvDescription":"Maximum batch size for the enforcer.","Example":"","Deprecated":"false"},{"Env":"USE_CASBIN_V2","EnvType":"bool","EnvValue":"true","EnvDescription":"To enable casbin V2 API","Example":"","Deprecated":"false"}]}] \ No newline at end of file +[{"Category":"CD","Fields":[{"Env":"ARGO_APP_MANUAL_SYNC_TIME","EnvType":"int","EnvValue":"3","EnvDescription":"retry argocd app manual sync if the timeline is stuck in ARGOCD_SYNC_INITIATED state for more than this defined time (in mins)","Example":"","Deprecated":"false"},{"Env":"CD_HELM_PIPELINE_STATUS_CRON_TIME","EnvType":"string","EnvValue":"*/2 * * * *","EnvDescription":"Cron time to check the pipeline status ","Example":"","Deprecated":"false"},{"Env":"CD_PIPELINE_STATUS_CRON_TIME","EnvType":"string","EnvValue":"*/2 * * * *","EnvDescription":"Cron time for CD pipeline status","Example":"","Deprecated":"false"},{"Env":"CD_PIPELINE_STATUS_TIMEOUT_DURATION","EnvType":"string","EnvValue":"20","EnvDescription":"Timeout for CD pipeline to get healthy","Example":"","Deprecated":"false"},{"Env":"DEPLOY_STATUS_CRON_GET_PIPELINE_DEPLOYED_WITHIN_HOURS","EnvType":"int","EnvValue":"12","EnvDescription":"This flag is used to fetch the deployment status of the application. It retrieves the status of deployments that occurred between 12 hours and 10 minutes prior to the current time. It fetches non-terminal statuses.","Example":"","Deprecated":"false"},{"Env":"DEVTRON_CHART_ARGO_CD_INSTALL_REQUEST_TIMEOUT","EnvType":"int","EnvValue":"1","EnvDescription":"Context timeout for gitops concurrent async deployments","Example":"","Deprecated":"false"},{"Env":"DEVTRON_CHART_INSTALL_REQUEST_TIMEOUT","EnvType":"int","EnvValue":"6","EnvDescription":"Context timeout for no gitops concurrent async deployments","Example":"","Deprecated":"false"},{"Env":"EXPOSE_CD_METRICS","EnvType":"bool","EnvValue":"false","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"FEATURE_MIGRATE_ARGOCD_APPLICATION_ENABLE","EnvType":"bool","EnvValue":"false","EnvDescription":"enable migration of external argocd application to devtron pipeline","Example":"","Deprecated":"false"},{"Env":"HELM_PIPELINE_STATUS_CHECK_ELIGIBLE_TIME","EnvType":"string","EnvValue":"120","EnvDescription":"eligible time for checking helm app status periodically and update in db, value is in seconds., default is 120, if wfr is updated within configured time i.e. HELM_PIPELINE_STATUS_CHECK_ELIGIBLE_TIME then do not include for this cron cycle.","Example":"","Deprecated":"false"},{"Env":"IS_INTERNAL_USE","EnvType":"bool","EnvValue":"true","EnvDescription":"If enabled then cd pipeline and helm apps will not need the deployment app type mandatorily. Couple this flag with HIDE_GITOPS_OR_HELM_OPTION (in Dashborad) and if gitops is configured and allowed for the env, pipeline/ helm app will gitops else no-gitops.","Example":"","Deprecated":"false"},{"Env":"MIGRATE_DEPLOYMENT_CONFIG_DATA","EnvType":"bool","EnvValue":"false","EnvDescription":"migrate deployment config data from charts table to deployment_config table","Example":"","Deprecated":"false"},{"Env":"PIPELINE_DEGRADED_TIME","EnvType":"string","EnvValue":"10","EnvDescription":"Time to mark a pipeline degraded if not healthy in defined time","Example":"","Deprecated":"false"},{"Env":"REVISION_HISTORY_LIMIT_DEVTRON_APP","EnvType":"int","EnvValue":"1","EnvDescription":"Count for devtron application rivision history","Example":"","Deprecated":"false"},{"Env":"REVISION_HISTORY_LIMIT_EXTERNAL_HELM_APP","EnvType":"int","EnvValue":"0","EnvDescription":"Count for external helm application rivision history","Example":"","Deprecated":"false"},{"Env":"REVISION_HISTORY_LIMIT_HELM_APP","EnvType":"int","EnvValue":"1","EnvDescription":"To set the history limit for the helm app being deployed through devtron","Example":"","Deprecated":"false"},{"Env":"REVISION_HISTORY_LIMIT_LINKED_HELM_APP","EnvType":"int","EnvValue":"15","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"RUN_HELM_INSTALL_IN_ASYNC_MODE_HELM_APPS","EnvType":"bool","EnvValue":"false","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"SHOULD_CHECK_NAMESPACE_ON_CLONE","EnvType":"bool","EnvValue":"false","EnvDescription":"should we check if namespace exists or not while cloning app","Example":"","Deprecated":"false"},{"Env":"USE_DEPLOYMENT_CONFIG_DATA","EnvType":"bool","EnvValue":"false","EnvDescription":"use deployment config data from deployment_config table","Example":"","Deprecated":"true"}]},{"Category":"CI_RUNNER","Fields":[{"Env":"AZURE_ACCOUNT_KEY","EnvType":"string","EnvValue":"","EnvDescription":"If blob storage is being used of azure then pass the secret key to access the bucket","Example":"","Deprecated":"false"},{"Env":"AZURE_ACCOUNT_NAME","EnvType":"string","EnvValue":"","EnvDescription":"Account name for azure blob storage","Example":"","Deprecated":"false"},{"Env":"AZURE_BLOB_CONTAINER_CI_CACHE","EnvType":"string","EnvValue":"","EnvDescription":"Cache bucket name for azure blob storage","Example":"","Deprecated":"false"},{"Env":"AZURE_BLOB_CONTAINER_CI_LOG","EnvType":"string","EnvValue":"","EnvDescription":"Log bucket for azure blob storage","Example":"","Deprecated":"false"},{"Env":"AZURE_GATEWAY_CONNECTION_INSECURE","EnvType":"bool","EnvValue":"true","EnvDescription":"Azure gateway connection allows insecure if true","Example":"","Deprecated":"false"},{"Env":"AZURE_GATEWAY_URL","EnvType":"string","EnvValue":"http://devtron-minio.devtroncd:9000","EnvDescription":"Sent to CI runner for blob","Example":"","Deprecated":"false"},{"Env":"BASE_LOG_LOCATION_PATH","EnvType":"string","EnvValue":"/home/devtron/","EnvDescription":"Used to store, download logs of ci workflow, artifact","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_GCP_CREDENTIALS_JSON","EnvType":"string","EnvValue":"","EnvDescription":"GCP cred json for GCS blob storage","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_PROVIDER","EnvType":"","EnvValue":"S3","EnvDescription":"Blob storage provider name(AWS/GCP/Azure)","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_S3_ACCESS_KEY","EnvType":"string","EnvValue":"","EnvDescription":"S3 access key for s3 blob storage","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_S3_BUCKET_VERSIONED","EnvType":"bool","EnvValue":"true","EnvDescription":"To enable buctet versioning for blob storage","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_S3_ENDPOINT","EnvType":"string","EnvValue":"","EnvDescription":"S3 endpoint URL for s3 blob storage","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_S3_ENDPOINT_INSECURE","EnvType":"bool","EnvValue":"false","EnvDescription":"To use insecure s3 endpoint","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_S3_SECRET_KEY","EnvType":"string","EnvValue":"","EnvDescription":"Secret key for s3 blob storage","Example":"","Deprecated":"false"},{"Env":"BUILDX_CACHE_PATH","EnvType":"string","EnvValue":"/var/lib/devtron/buildx","EnvDescription":"Path for the buildx cache","Example":"","Deprecated":"false"},{"Env":"BUILDX_K8S_DRIVER_OPTIONS","EnvType":"string","EnvValue":"","EnvDescription":"To enable the k8s driver and pass args for k8s driver in buildx","Example":"","Deprecated":"false"},{"Env":"BUILDX_PROVENANCE_MODE","EnvType":"string","EnvValue":"","EnvDescription":"provinance is set to true by default by docker. this will add some build related data in generated build manifest.it also adds some unknown:unknown key:value pair which may not be compatible by some container registries. with buildx k8s driver , provinenance=true is causing issue when push manifest to quay registry, so setting it to false","Example":"","Deprecated":"false"},{"Env":"BUILD_LOG_TTL_VALUE_IN_SECS","EnvType":"int","EnvValue":"3600","EnvDescription":"This is the time that the pods of ci/pre-cd/post-cd live after completion state.","Example":"","Deprecated":"false"},{"Env":"CACHE_LIMIT","EnvType":"int64","EnvValue":"5000000000","EnvDescription":"Cache limit.","Example":"","Deprecated":"false"},{"Env":"CD_DEFAULT_ADDRESS_POOL_BASE_CIDR","EnvType":"string","EnvValue":"","EnvDescription":"To pass the IP cidr for Pre/Post cd ","Example":"","Deprecated":"false"},{"Env":"CD_DEFAULT_ADDRESS_POOL_SIZE","EnvType":"int","EnvValue":"","EnvDescription":"The subnet size to allocate from the base pool for CD","Example":"","Deprecated":"false"},{"Env":"CD_LIMIT_CI_CPU","EnvType":"string","EnvValue":"0.5","EnvDescription":"CPU Resource Limit Pre/Post CD","Example":"","Deprecated":"false"},{"Env":"CD_LIMIT_CI_MEM","EnvType":"string","EnvValue":"3G","EnvDescription":"Memory Resource Limit Pre/Post CD","Example":"","Deprecated":"false"},{"Env":"CD_NODE_LABEL_SELECTOR","EnvType":"","EnvValue":"","EnvDescription":"Node label selector for Pre/Post CD","Example":"","Deprecated":"false"},{"Env":"CD_NODE_TAINTS_KEY","EnvType":"string","EnvValue":"dedicated","EnvDescription":"Toleration key for Pre/Post CD","Example":"","Deprecated":"false"},{"Env":"CD_NODE_TAINTS_VALUE","EnvType":"string","EnvValue":"ci","EnvDescription":"Toleration value for Pre/Post CD","Example":"","Deprecated":"false"},{"Env":"CD_REQ_CI_CPU","EnvType":"string","EnvValue":"0.5","EnvDescription":"CPU Resource Rquest Pre/Post CD","Example":"","Deprecated":"false"},{"Env":"CD_REQ_CI_MEM","EnvType":"string","EnvValue":"3G","EnvDescription":"Memory Resource Rquest Pre/Post CD","Example":"","Deprecated":"false"},{"Env":"CD_WORKFLOW_EXECUTOR_TYPE","EnvType":"","EnvValue":"AWF","EnvDescription":"Executor type for Pre/Post CD(AWF,System)","Example":"","Deprecated":"false"},{"Env":"CD_WORKFLOW_SERVICE_ACCOUNT","EnvType":"string","EnvValue":"cd-runner","EnvDescription":"Service account to be used in Pre/Post CD pod","Example":"","Deprecated":"false"},{"Env":"CI_DEFAULT_ADDRESS_POOL_BASE_CIDR","EnvType":"string","EnvValue":"","EnvDescription":"To pass the IP cidr for CI","Example":"","Deprecated":"false"},{"Env":"CI_DEFAULT_ADDRESS_POOL_SIZE","EnvType":"int","EnvValue":"","EnvDescription":"The subnet size to allocate from the base pool for CI","Example":"","Deprecated":"false"},{"Env":"CI_IGNORE_DOCKER_CACHE","EnvType":"bool","EnvValue":"","EnvDescription":"Ignoring docker cache ","Example":"","Deprecated":"false"},{"Env":"CI_LOGS_KEY_PREFIX","EnvType":"string","EnvValue":"","EnvDescription":"Prefix for build logs","Example":"","Deprecated":"false"},{"Env":"CI_NODE_LABEL_SELECTOR","EnvType":"","EnvValue":"","EnvDescription":"Node label selector for CI","Example":"","Deprecated":"false"},{"Env":"CI_NODE_TAINTS_KEY","EnvType":"string","EnvValue":"","EnvDescription":"Toleration key for CI","Example":"","Deprecated":"false"},{"Env":"CI_NODE_TAINTS_VALUE","EnvType":"string","EnvValue":"","EnvDescription":"Toleration value for CI","Example":"","Deprecated":"false"},{"Env":"CI_RUNNER_DOCKER_MTU_VALUE","EnvType":"int","EnvValue":"-1","EnvDescription":"this is to control the bytes of inofrmation passed in a network packet in ci-runner. default is -1 (defaults to the underlying node mtu value)","Example":"","Deprecated":"false"},{"Env":"CI_SUCCESS_AUTO_TRIGGER_BATCH_SIZE","EnvType":"int","EnvValue":"1","EnvDescription":"this is to control the no of linked pipelines should be hanled in one go when a ci-success event of an parent ci is received","Example":"","Deprecated":"false"},{"Env":"CI_VOLUME_MOUNTS_JSON","EnvType":"string","EnvValue":"","EnvDescription":"additional volume mount data for CI and JOB","Example":"","Deprecated":"false"},{"Env":"CI_WORKFLOW_EXECUTOR_TYPE","EnvType":"","EnvValue":"AWF","EnvDescription":"Executor type for CI(AWF,System)","Example":"","Deprecated":"false"},{"Env":"DEFAULT_ARTIFACT_KEY_LOCATION","EnvType":"string","EnvValue":"arsenal-v1/ci-artifacts","EnvDescription":"Key location for artifacts being created","Example":"","Deprecated":"false"},{"Env":"DEFAULT_BUILD_LOGS_BUCKET","EnvType":"string","EnvValue":"devtron-pro-ci-logs","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEFAULT_BUILD_LOGS_KEY_PREFIX","EnvType":"string","EnvValue":"arsenal-v1","EnvDescription":"Bucket prefix for build logs","Example":"","Deprecated":"false"},{"Env":"DEFAULT_CACHE_BUCKET","EnvType":"string","EnvValue":"ci-caching","EnvDescription":"Bucket name for build cache","Example":"","Deprecated":"false"},{"Env":"DEFAULT_CACHE_BUCKET_REGION","EnvType":"string","EnvValue":"us-east-2","EnvDescription":"Build Cache bucket region","Example":"","Deprecated":"false"},{"Env":"DEFAULT_CD_ARTIFACT_KEY_LOCATION","EnvType":"string","EnvValue":"","EnvDescription":"Bucket prefix for build cache","Example":"","Deprecated":"false"},{"Env":"DEFAULT_CD_LOGS_BUCKET_REGION","EnvType":"string","EnvValue":"us-east-2","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEFAULT_CD_NAMESPACE","EnvType":"string","EnvValue":"","EnvDescription":"Namespace for devtron stack","Example":"","Deprecated":"false"},{"Env":"DEFAULT_CD_TIMEOUT","EnvType":"int64","EnvValue":"3600","EnvDescription":"Timeout for Pre/Post-Cd to be completed","Example":"","Deprecated":"false"},{"Env":"DEFAULT_CI_IMAGE","EnvType":"string","EnvValue":"686244538589.dkr.ecr.us-east-2.amazonaws.com/cirunner:47","EnvDescription":"To pass the ci-runner image","Example":"","Deprecated":"false"},{"Env":"DEFAULT_NAMESPACE","EnvType":"string","EnvValue":"devtron-ci","EnvDescription":"Timeout for CI to be completed","Example":"","Deprecated":"false"},{"Env":"DEFAULT_TARGET_PLATFORM","EnvType":"string","EnvValue":"","EnvDescription":"Default architecture for buildx","Example":"","Deprecated":"false"},{"Env":"DOCKER_BUILD_CACHE_PATH","EnvType":"string","EnvValue":"/var/lib/docker","EnvDescription":"Path to store cache of docker build (/var/lib/docker-\u003e for legacy docker build, /var/lib/devtron-\u003e for buildx)","Example":"","Deprecated":"false"},{"Env":"ENABLE_BUILD_CONTEXT","EnvType":"bool","EnvValue":"false","EnvDescription":"To Enable build context in Devtron.","Example":"","Deprecated":"false"},{"Env":"ENABLE_WORKFLOW_EXECUTION_STAGE","EnvType":"bool","EnvValue":"true","EnvDescription":"if enabled then we will display build stages separately for CI/Job/Pre-Post CD","Example":"true","Deprecated":"false"},{"Env":"EXTERNAL_BLOB_STORAGE_CM_NAME","EnvType":"string","EnvValue":"blob-storage-cm","EnvDescription":"name of the config map(contains bucket name, etc.) in external cluster when there is some operation related to external cluster, for example:-downloading cd artifact pushed in external cluster's env and we need to download from there, downloads ci logs pushed in external cluster's blob","Example":"","Deprecated":"false"},{"Env":"EXTERNAL_BLOB_STORAGE_SECRET_NAME","EnvType":"string","EnvValue":"blob-storage-secret","EnvDescription":"name of the secret(contains password, accessId,passKeys, etc.) in external cluster when there is some operation related to external cluster, for example:-downloading cd artifact pushed in external cluster's env and we need to download from there, downloads ci logs pushed in external cluster's blob","Example":"","Deprecated":"false"},{"Env":"EXTERNAL_CD_NODE_LABEL_SELECTOR","EnvType":"","EnvValue":"","EnvDescription":"This is an array of strings used when submitting a workflow for pre or post-CD execution. If the ","Example":"","Deprecated":"false"},{"Env":"EXTERNAL_CD_NODE_TAINTS_KEY","EnvType":"string","EnvValue":"dedicated","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"EXTERNAL_CD_NODE_TAINTS_VALUE","EnvType":"string","EnvValue":"ci","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"EXTERNAL_CI_API_SECRET","EnvType":"string","EnvValue":"devtroncd-secret","EnvDescription":"External CI API secret.","Example":"","Deprecated":"false"},{"Env":"EXTERNAL_CI_PAYLOAD","EnvType":"string","EnvValue":"{\"ciProjectDetails\":[{\"gitRepository\":\"https://github.com/vikram1601/getting-started-nodejs.git\",\"checkoutPath\":\"./abc\",\"commitHash\":\"239077135f8cdeeccb7857e2851348f558cb53d3\",\"commitTime\":\"2022-10-30T20:00:00\",\"branch\":\"master\",\"message\":\"Update README.md\",\"author\":\"User Name \"}],\"dockerImage\":\"445808685819.dkr.ecr.us-east-2.amazonaws.com/orch:23907713-2\"}","EnvDescription":"External CI payload with project details.","Example":"","Deprecated":"false"},{"Env":"EXTERNAL_CI_WEB_HOOK_URL","EnvType":"string","EnvValue":"","EnvDescription":"default is {{HOST_URL}}/orchestrator/webhook/ext-ci. It is used for external ci.","Example":"","Deprecated":"false"},{"Env":"IGNORE_CM_CS_IN_CI_JOB","EnvType":"bool","EnvValue":"false","EnvDescription":"Ignore CM/CS in CI-pipeline as Job","Example":"","Deprecated":"false"},{"Env":"IMAGE_RETRY_COUNT","EnvType":"int","EnvValue":"0","EnvDescription":"push artifact(image) in ci retry count ","Example":"","Deprecated":"false"},{"Env":"IMAGE_RETRY_INTERVAL","EnvType":"int","EnvValue":"5","EnvDescription":"image retry interval takes value in seconds","Example":"","Deprecated":"false"},{"Env":"IMAGE_SCANNER_ENDPOINT","EnvType":"string","EnvValue":"http://image-scanner-new-demo-devtroncd-service.devtroncd:80","EnvDescription":"Image-scanner micro-service URL","Example":"","Deprecated":"false"},{"Env":"IMAGE_SCAN_MAX_RETRIES","EnvType":"int","EnvValue":"3","EnvDescription":"Max retry count for image-scanning","Example":"","Deprecated":"false"},{"Env":"IMAGE_SCAN_RETRY_DELAY","EnvType":"int","EnvValue":"5","EnvDescription":"Delay for the image-scaning to start","Example":"","Deprecated":"false"},{"Env":"IN_APP_LOGGING_ENABLED","EnvType":"bool","EnvValue":"false","EnvDescription":"Used in case of argo workflow is enabled. If enabled logs push will be managed by us, else will be managed by argo workflow.","Example":"","Deprecated":"false"},{"Env":"MAX_CD_WORKFLOW_RUNNER_RETRIES","EnvType":"int","EnvValue":"0","EnvDescription":"Maximum time pre/post-cd-workflow create pod if it fails to complete","Example":"","Deprecated":"false"},{"Env":"MAX_CI_WORKFLOW_RETRIES","EnvType":"int","EnvValue":"0","EnvDescription":"Maximum time CI-workflow create pod if it fails to complete","Example":"","Deprecated":"false"},{"Env":"MODE","EnvType":"string","EnvValue":"DEV","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"NATS_SERVER_HOST","EnvType":"string","EnvValue":"localhost:4222","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"ORCH_HOST","EnvType":"string","EnvValue":"http://devtroncd-orchestrator-service-prod.devtroncd/webhook/msg/nats","EnvDescription":"Orchestrator micro-service URL ","Example":"","Deprecated":"false"},{"Env":"ORCH_TOKEN","EnvType":"string","EnvValue":"","EnvDescription":"Orchestrator token","Example":"","Deprecated":"false"},{"Env":"PRE_CI_CACHE_PATH","EnvType":"string","EnvValue":"/devtroncd-cache","EnvDescription":"Cache path for Pre CI tasks","Example":"","Deprecated":"false"},{"Env":"SHOW_DOCKER_BUILD_ARGS","EnvType":"bool","EnvValue":"true","EnvDescription":"To enable showing the args passed for CI in build logs","Example":"","Deprecated":"false"},{"Env":"SKIP_CI_JOB_BUILD_CACHE_PUSH_PULL","EnvType":"bool","EnvValue":"false","EnvDescription":"To skip cache Push/Pull for ci job","Example":"","Deprecated":"false"},{"Env":"SKIP_CREATING_ECR_REPO","EnvType":"bool","EnvValue":"false","EnvDescription":"By disabling this ECR repo won't get created if it's not available on ECR from build configuration","Example":"","Deprecated":"false"},{"Env":"TERMINATION_GRACE_PERIOD_SECS","EnvType":"int","EnvValue":"180","EnvDescription":"this is the time given to workflow pods to shutdown. (grace full termination time)","Example":"","Deprecated":"false"},{"Env":"USE_ARTIFACT_LISTING_QUERY_V2","EnvType":"bool","EnvValue":"true","EnvDescription":"To use the V2 query for listing artifacts","Example":"","Deprecated":"false"},{"Env":"USE_BLOB_STORAGE_CONFIG_IN_CD_WORKFLOW","EnvType":"bool","EnvValue":"true","EnvDescription":"To enable blob storage in pre and post cd","Example":"","Deprecated":"false"},{"Env":"USE_BLOB_STORAGE_CONFIG_IN_CI_WORKFLOW","EnvType":"bool","EnvValue":"true","EnvDescription":"To enable blob storage in pre and post ci","Example":"","Deprecated":"false"},{"Env":"USE_BUILDX","EnvType":"bool","EnvValue":"false","EnvDescription":"To enable buildx feature globally","Example":"","Deprecated":"false"},{"Env":"USE_DOCKER_API_TO_GET_DIGEST","EnvType":"bool","EnvValue":"false","EnvDescription":"when user do not pass the digest then this flag controls , finding the image digest using docker API or not. if set to true we get the digest from docker API call else use docker pull command. [logic in ci-runner]","Example":"","Deprecated":"false"},{"Env":"USE_EXTERNAL_NODE","EnvType":"bool","EnvValue":"false","EnvDescription":"It is used in case of Pre/ Post Cd with run in application mode. If enabled the node lebels are read from EXTERNAL_CD_NODE_LABEL_SELECTOR else from CD_NODE_LABEL_SELECTOR MODE: if the vale is DEV, it will read the local kube config file or else from the cluser location.","Example":"","Deprecated":"false"},{"Env":"USE_IMAGE_TAG_FROM_GIT_PROVIDER_FOR_TAG_BASED_BUILD","EnvType":"bool","EnvValue":"false","EnvDescription":"To use the same tag in container image as that of git tag","Example":"","Deprecated":"false"},{"Env":"WF_CONTROLLER_INSTANCE_ID","EnvType":"string","EnvValue":"devtron-runner","EnvDescription":"Workflow controller instance ID.","Example":"","Deprecated":"false"},{"Env":"WORKFLOW_CACHE_CONFIG","EnvType":"string","EnvValue":"{}","EnvDescription":"flag is used to configure how Docker caches are handled during a CI/CD ","Example":"","Deprecated":"false"},{"Env":"WORKFLOW_SERVICE_ACCOUNT","EnvType":"string","EnvValue":"ci-runner","EnvDescription":"","Example":"","Deprecated":"false"}]},{"Category":"DEVTRON","Fields":[{"Env":"-","EnvType":"","EnvValue":"","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"ADDITIONAL_NODE_GROUP_LABELS","EnvType":"","EnvValue":"","EnvDescription":"Add comma separated list of additional node group labels to default labels","Example":"karpenter.sh/nodepool,cloud.google.com/gke-nodepool","Deprecated":"false"},{"Env":"APP_SYNC_IMAGE","EnvType":"string","EnvValue":"quay.io/devtron/chart-sync:1227622d-132-3775","EnvDescription":"For the app sync image, this image will be used in app-manual sync job","Example":"","Deprecated":"false"},{"Env":"APP_SYNC_JOB_RESOURCES_OBJ","EnvType":"string","EnvValue":"","EnvDescription":"To pass the resource of app sync","Example":"","Deprecated":"false"},{"Env":"APP_SYNC_SERVICE_ACCOUNT","EnvType":"string","EnvValue":"chart-sync","EnvDescription":"Service account to be used in app sync Job","Example":"","Deprecated":"false"},{"Env":"APP_SYNC_SHUTDOWN_WAIT_DURATION","EnvType":"int","EnvValue":"120","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"ARGO_AUTO_SYNC_ENABLED","EnvType":"bool","EnvValue":"true","EnvDescription":"If enabled all argocd application will have auto sync enabled","Example":"","Deprecated":"false"},{"Env":"ARGO_GIT_COMMIT_RETRY_COUNT_ON_CONFLICT","EnvType":"int","EnvValue":"3","EnvDescription":"retry argocd app manual sync if the timeline is stuck in ARGOCD_SYNC_INITIATED state for more than this defined time (in mins)","Example":"","Deprecated":"false"},{"Env":"ARGO_GIT_COMMIT_RETRY_DELAY_ON_CONFLICT","EnvType":"int","EnvValue":"1","EnvDescription":"Delay on retrying the maifest commit the on gitops","Example":"","Deprecated":"false"},{"Env":"ARGO_REPO_REGISTER_RETRY_COUNT","EnvType":"int","EnvValue":"3","EnvDescription":"Argo app registration in argo retries on deployment","Example":"","Deprecated":"false"},{"Env":"ARGO_REPO_REGISTER_RETRY_DELAY","EnvType":"int","EnvValue":"10","EnvDescription":"Argo app registration in argo cd on deployment delay between retry","Example":"","Deprecated":"false"},{"Env":"ASYNC_BUILDX_CACHE_EXPORT","EnvType":"bool","EnvValue":"false","EnvDescription":"To enable async container image cache export","Example":"","Deprecated":"false"},{"Env":"BATCH_SIZE","EnvType":"int","EnvValue":"5","EnvDescription":"there is feature to get URL's of services/ingresses. so to extract those, we need to parse all the servcie and ingress objects of the application. this BATCH_SIZE flag controls the no of these objects get parsed in one go.","Example":"","Deprecated":"false"},{"Env":"BLOB_STORAGE_ENABLED","EnvType":"bool","EnvValue":"false","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"BUILDX_CACHE_MODE_MIN","EnvType":"bool","EnvValue":"false","EnvDescription":"To set build cache mode to minimum in buildx","Example":"","Deprecated":"false"},{"Env":"CD_HOST","EnvType":"string","EnvValue":"localhost","EnvDescription":"Host for the devtron stack","Example":"","Deprecated":"false"},{"Env":"CD_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"CD_PORT","EnvType":"string","EnvValue":"8000","EnvDescription":"Port for pre/post-cd","Example":"","Deprecated":"false"},{"Env":"CExpirationTime","EnvType":"int","EnvValue":"600","EnvDescription":"Caching expiration time.","Example":"","Deprecated":"false"},{"Env":"CI_TRIGGER_CRON_TIME","EnvType":"int","EnvValue":"2","EnvDescription":"For image poll plugin","Example":"","Deprecated":"false"},{"Env":"CI_WORKFLOW_STATUS_UPDATE_CRON","EnvType":"string","EnvValue":"*/5 * * * *","EnvDescription":"Cron schedule for CI pipeline status","Example":"","Deprecated":"false"},{"Env":"CLI_CMD_TIMEOUT_GLOBAL_SECONDS","EnvType":"int","EnvValue":"0","EnvDescription":"Used in git cli opeartion timeout","Example":"","Deprecated":"false"},{"Env":"CLUSTER_STATUS_CRON_TIME","EnvType":"int","EnvValue":"15","EnvDescription":"Cron schedule for cluster status on resource browser","Example":"","Deprecated":"false"},{"Env":"CONSUMER_CONFIG_JSON","EnvType":"string","EnvValue":"","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEFAULT_LOG_TIME_LIMIT","EnvType":"int64","EnvValue":"1","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEFAULT_TIMEOUT","EnvType":"float64","EnvValue":"3600","EnvDescription":"Timeout for CI to be completed","Example":"","Deprecated":"false"},{"Env":"DEVTRON_BOM_URL","EnvType":"string","EnvValue":"https://raw.githubusercontent.com/devtron-labs/devtron/%s/charts/devtron/devtron-bom.yaml","EnvDescription":"Path to devtron-bom.yaml of devtron charts, used for module installation and devtron upgrade","Example":"","Deprecated":"false"},{"Env":"DEVTRON_DEFAULT_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEVTRON_DEX_SECRET_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"Namespace of dex secret","Example":"","Deprecated":"false"},{"Env":"DEVTRON_HELM_RELEASE_CHART_NAME","EnvType":"string","EnvValue":"devtron-operator","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEVTRON_HELM_RELEASE_NAME","EnvType":"string","EnvValue":"devtron","EnvDescription":"Name of the Devtron Helm release. ","Example":"","Deprecated":"false"},{"Env":"DEVTRON_HELM_RELEASE_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"Namespace of the Devtron Helm release","Example":"","Deprecated":"false"},{"Env":"DEVTRON_HELM_REPO_NAME","EnvType":"string","EnvValue":"devtron","EnvDescription":"Is used to install modules (stack manager)","Example":"","Deprecated":"false"},{"Env":"DEVTRON_HELM_REPO_URL","EnvType":"string","EnvValue":"https://helm.devtron.ai","EnvDescription":"Is used to install modules (stack manager)","Example":"","Deprecated":"false"},{"Env":"DEVTRON_INSTALLATION_TYPE","EnvType":"string","EnvValue":"","EnvDescription":"Devtron Installation type(EA/Full)","Example":"","Deprecated":"false"},{"Env":"DEVTRON_INSTALLER_MODULES_PATH","EnvType":"string","EnvValue":"installer.modules","EnvDescription":"Path to devtron installer modules, used to find the helm charts and values files","Example":"","Deprecated":"false"},{"Env":"DEVTRON_INSTALLER_RELEASE_PATH","EnvType":"string","EnvValue":"installer.release","EnvDescription":"Path to devtron installer release, used to find the helm charts and values files","Example":"","Deprecated":"false"},{"Env":"DEVTRON_MODULES_IDENTIFIER_IN_HELM_VALUES","EnvType":"string","EnvValue":"installer.modules","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEVTRON_OPERATOR_BASE_PATH","EnvType":"string","EnvValue":"","EnvDescription":"Base path for devtron operator, used to find the helm charts and values files","Example":"","Deprecated":"false"},{"Env":"DEVTRON_SECRET_NAME","EnvType":"string","EnvValue":"devtron-secret","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEVTRON_VERSION_IDENTIFIER_IN_HELM_VALUES","EnvType":"string","EnvValue":"installer.release","EnvDescription":"devtron operator version identifier in helm values yaml","Example":"","Deprecated":"false"},{"Env":"DEX_CID","EnvType":"string","EnvValue":"example-app","EnvDescription":"dex client id ","Example":"","Deprecated":"false"},{"Env":"DEX_CLIENT_ID","EnvType":"string","EnvValue":"argo-cd","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEX_CSTOREKEY","EnvType":"string","EnvValue":"","EnvDescription":"DEX CSTOREKEY.","Example":"","Deprecated":"false"},{"Env":"DEX_JWTKEY","EnvType":"string","EnvValue":"","EnvDescription":"DEX JWT key. ","Example":"","Deprecated":"false"},{"Env":"DEX_RURL","EnvType":"string","EnvValue":"http://127.0.0.1:8080/callback","EnvDescription":"Dex redirect URL(http://argocd-dex-server.devtroncd:8080/callback)","Example":"","Deprecated":"false"},{"Env":"DEX_SCOPES","EnvType":"","EnvValue":"","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEX_SECRET","EnvType":"string","EnvValue":"","EnvDescription":"Dex secret","Example":"","Deprecated":"false"},{"Env":"DEX_URL","EnvType":"string","EnvValue":"","EnvDescription":"Dex service endpoint with dex path(http://argocd-dex-server.devtroncd:5556/dex)","Example":"","Deprecated":"false"},{"Env":"ECR_REPO_NAME_PREFIX","EnvType":"string","EnvValue":"test/","EnvDescription":"Prefix for ECR repo to be created in does not exist","Example":"","Deprecated":"false"},{"Env":"ENABLE_ASYNC_ARGO_CD_INSTALL_DEVTRON_CHART","EnvType":"bool","EnvValue":"false","EnvDescription":"To enable async installation of gitops application","Example":"","Deprecated":"false"},{"Env":"ENABLE_ASYNC_INSTALL_DEVTRON_CHART","EnvType":"bool","EnvValue":"false","EnvDescription":"To enable async installation of no-gitops application","Example":"","Deprecated":"false"},{"Env":"ENABLE_NOTIFIER_V2","EnvType":"bool","EnvValue":"false","EnvDescription":"enable notifier v2","Example":"","Deprecated":"false"},{"Env":"EPHEMERAL_SERVER_VERSION_REGEX","EnvType":"string","EnvValue":"v[1-9]\\.\\b(2[3-9]\\|[3-9][0-9])\\b.*","EnvDescription":"ephemeral containers support version regex that is compared with k8sServerVersion","Example":"","Deprecated":"false"},{"Env":"EVENT_URL","EnvType":"string","EnvValue":"http://localhost:3000/notify","EnvDescription":"Notifier service url","Example":"","Deprecated":"false"},{"Env":"EXECUTE_WIRE_NIL_CHECKER","EnvType":"bool","EnvValue":"false","EnvDescription":"checks for any nil pointer in wire.go","Example":"","Deprecated":"false"},{"Env":"EXPOSE_CI_METRICS","EnvType":"bool","EnvValue":"false","EnvDescription":"To expose CI metrics","Example":"","Deprecated":"false"},{"Env":"FEATURE_RESTART_WORKLOAD_BATCH_SIZE","EnvType":"int","EnvValue":"1","EnvDescription":"restart workload retrieval batch size ","Example":"","Deprecated":"false"},{"Env":"FEATURE_RESTART_WORKLOAD_WORKER_POOL_SIZE","EnvType":"int","EnvValue":"5","EnvDescription":"restart workload retrieval pool size","Example":"","Deprecated":"false"},{"Env":"FORCE_SECURITY_SCANNING","EnvType":"bool","EnvValue":"false","EnvDescription":"By enabling this no one can disable image scaning on ci-pipeline from UI","Example":"","Deprecated":"false"},{"Env":"GITOPS_REPO_PREFIX","EnvType":"string","EnvValue":"","EnvDescription":"Prefix for Gitops repo being creation for argocd application","Example":"","Deprecated":"false"},{"Env":"GO_RUNTIME_ENV","EnvType":"string","EnvValue":"production","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"GRAFANA_HOST","EnvType":"string","EnvValue":"localhost","EnvDescription":"Host URL for the grafana dashboard","Example":"","Deprecated":"false"},{"Env":"GRAFANA_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"Namespace for grafana","Example":"","Deprecated":"false"},{"Env":"GRAFANA_ORG_ID","EnvType":"int","EnvValue":"2","EnvDescription":"Org ID for grafana for application metrics","Example":"","Deprecated":"false"},{"Env":"GRAFANA_PASSWORD","EnvType":"string","EnvValue":"prom-operator","EnvDescription":"Password for grafana dashboard","Example":"","Deprecated":"false"},{"Env":"GRAFANA_PORT","EnvType":"string","EnvValue":"8090","EnvDescription":"Port for grafana micro-service","Example":"","Deprecated":"false"},{"Env":"GRAFANA_URL","EnvType":"string","EnvValue":"","EnvDescription":"Host URL for the grafana dashboard","Example":"","Deprecated":"false"},{"Env":"GRAFANA_USERNAME","EnvType":"string","EnvValue":"admin","EnvDescription":"Username for grafana ","Example":"","Deprecated":"false"},{"Env":"HIDE_IMAGE_TAGGING_HARD_DELETE","EnvType":"bool","EnvValue":"false","EnvDescription":"Flag to hide the hard delete option in the image tagging service","Example":"","Deprecated":"false"},{"Env":"IGNORE_AUTOCOMPLETE_AUTH_CHECK","EnvType":"bool","EnvValue":"false","EnvDescription":"flag for ignoring auth check in autocomplete apis.","Example":"","Deprecated":"false"},{"Env":"INSTALLED_MODULES","EnvType":"","EnvValue":"","EnvDescription":"List of installed modules given in helm values/yaml are written in cm and used by devtron to know which modules are given","Example":"security.trivy,security.clair","Deprecated":"false"},{"Env":"INSTALLER_CRD_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"namespace where Custom Resource Definitions get installed","Example":"","Deprecated":"false"},{"Env":"INSTALLER_CRD_OBJECT_GROUP_NAME","EnvType":"string","EnvValue":"installer.devtron.ai","EnvDescription":"Devtron installer CRD group name, partially deprecated.","Example":"","Deprecated":"false"},{"Env":"INSTALLER_CRD_OBJECT_RESOURCE","EnvType":"string","EnvValue":"installers","EnvDescription":"Devtron installer CRD resource name, partially deprecated","Example":"","Deprecated":"false"},{"Env":"INSTALLER_CRD_OBJECT_VERSION","EnvType":"string","EnvValue":"v1alpha1","EnvDescription":"version of the CRDs. default is v1alpha1","Example":"","Deprecated":"false"},{"Env":"IS_AIR_GAP_ENVIRONMENT","EnvType":"bool","EnvValue":"false","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"JwtExpirationTime","EnvType":"int","EnvValue":"120","EnvDescription":"JWT expiration time.","Example":"","Deprecated":"false"},{"Env":"K8s_CLIENT_MAX_IDLE_CONNS_PER_HOST","EnvType":"int","EnvValue":"25","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"K8s_TCP_IDLE_CONN_TIMEOUT","EnvType":"int","EnvValue":"300","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"K8s_TCP_KEEPALIVE","EnvType":"int","EnvValue":"30","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"K8s_TCP_TIMEOUT","EnvType":"int","EnvValue":"30","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"K8s_TLS_HANDSHAKE_TIMEOUT","EnvType":"int","EnvValue":"10","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"LENS_TIMEOUT","EnvType":"int","EnvValue":"0","EnvDescription":"Lens microservice timeout.","Example":"","Deprecated":"false"},{"Env":"LENS_URL","EnvType":"string","EnvValue":"http://lens-milandevtron-service:80","EnvDescription":"Lens micro-service URL","Example":"","Deprecated":"false"},{"Env":"LIMIT_CI_CPU","EnvType":"string","EnvValue":"0.5","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"LIMIT_CI_MEM","EnvType":"string","EnvValue":"3G","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"LOGGER_DEV_MODE","EnvType":"bool","EnvValue":"false","EnvDescription":"Enables a different logger theme.","Example":"","Deprecated":"false"},{"Env":"LOG_LEVEL","EnvType":"int","EnvValue":"-1","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"MAX_SESSION_PER_USER","EnvType":"int","EnvValue":"5","EnvDescription":"max no of cluster terminal pods can be created by an user","Example":"","Deprecated":"false"},{"Env":"MODULE_METADATA_API_URL","EnvType":"string","EnvValue":"https://api.devtron.ai/module?name=%s","EnvDescription":"Modules list and meta info will be fetched from this server, that is central api server of devtron.","Example":"","Deprecated":"false"},{"Env":"MODULE_STATUS_HANDLING_CRON_DURATION_MIN","EnvType":"int","EnvValue":"3","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"NATS_MSG_ACK_WAIT_IN_SECS","EnvType":"int","EnvValue":"120","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"NATS_MSG_BUFFER_SIZE","EnvType":"int","EnvValue":"-1","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"NATS_MSG_MAX_AGE","EnvType":"int","EnvValue":"86400","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"NATS_MSG_PROCESSING_BATCH_SIZE","EnvType":"int","EnvValue":"1","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"NATS_MSG_REPLICAS","EnvType":"int","EnvValue":"0","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"NOTIFICATION_MEDIUM","EnvType":"NotificationMedium","EnvValue":"rest","EnvDescription":"notification medium","Example":"","Deprecated":"false"},{"Env":"OTEL_COLLECTOR_URL","EnvType":"string","EnvValue":"","EnvDescription":"Opentelemetry URL ","Example":"","Deprecated":"false"},{"Env":"PARALLELISM_LIMIT_FOR_TAG_PROCESSING","EnvType":"int","EnvValue":"","EnvDescription":"App manual sync job parallel tag processing count.","Example":"","Deprecated":"false"},{"Env":"PG_EXPORT_PROM_METRICS","EnvType":"bool","EnvValue":"true","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"PG_LOG_ALL_FAILURE_QUERIES","EnvType":"bool","EnvValue":"true","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"PG_LOG_ALL_QUERY","EnvType":"bool","EnvValue":"false","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"PG_LOG_SLOW_QUERY","EnvType":"bool","EnvValue":"true","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"PG_QUERY_DUR_THRESHOLD","EnvType":"int64","EnvValue":"5000","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"PLUGIN_NAME","EnvType":"string","EnvValue":"Pull images from container repository","EnvDescription":"Handles image retrieval from a container repository and triggers subsequent CI processes upon detecting new images.Current default plugin name: Pull Images from Container Repository.","Example":"","Deprecated":"false"},{"Env":"PROPAGATE_EXTRA_LABELS","EnvType":"bool","EnvValue":"false","EnvDescription":"Add additional propagate labels like api.devtron.ai/appName, api.devtron.ai/envName, api.devtron.ai/project along with the user defined ones.","Example":"","Deprecated":"false"},{"Env":"PROXY_SERVICE_CONFIG","EnvType":"string","EnvValue":"{}","EnvDescription":"Proxy configuration for micro-service to be accessible on orhcestrator ingress","Example":"","Deprecated":"false"},{"Env":"REQ_CI_CPU","EnvType":"string","EnvValue":"0.5","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"REQ_CI_MEM","EnvType":"string","EnvValue":"3G","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"RESTRICT_TERMINAL_ACCESS_FOR_NON_SUPER_USER","EnvType":"bool","EnvValue":"false","EnvDescription":"To restrict the cluster terminal from user having non-super admin acceess","Example":"","Deprecated":"false"},{"Env":"RUNTIME_CONFIG_LOCAL_DEV","EnvType":"LocalDevMode","EnvValue":"true","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"SCOPED_VARIABLE_ENABLED","EnvType":"bool","EnvValue":"false","EnvDescription":"To enable scoped variable option","Example":"","Deprecated":"false"},{"Env":"SCOPED_VARIABLE_FORMAT","EnvType":"string","EnvValue":"@{{%s}}","EnvDescription":"Its a scope format for varialbe name.","Example":"","Deprecated":"false"},{"Env":"SCOPED_VARIABLE_HANDLE_PRIMITIVES","EnvType":"bool","EnvValue":"false","EnvDescription":"This describe should we handle primitives or not in scoped variable template parsing.","Example":"","Deprecated":"false"},{"Env":"SCOPED_VARIABLE_NAME_REGEX","EnvType":"string","EnvValue":"^[a-zA-Z][a-zA-Z0-9_-]{0,62}[a-zA-Z0-9]$","EnvDescription":"Regex for scoped variable name that must passed this regex.","Example":"","Deprecated":"false"},{"Env":"SOCKET_DISCONNECT_DELAY_SECONDS","EnvType":"int","EnvValue":"5","EnvDescription":"The server closes a session when a client receiving connection have not been seen for a while.This delay is configured by this setting. By default the session is closed when a receiving connection wasn't seen for 5 seconds.","Example":"","Deprecated":"false"},{"Env":"SOCKET_HEARTBEAT_SECONDS","EnvType":"int","EnvValue":"25","EnvDescription":"In order to keep proxies and load balancers from closing long running http requests we need to pretend that the connection is active and send a heartbeat packet once in a while. This setting controls how often this is done. By default a heartbeat packet is sent every 25 seconds.","Example":"","Deprecated":"false"},{"Env":"STREAM_CONFIG_JSON","EnvType":"string","EnvValue":"","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"SYSTEM_VAR_PREFIX","EnvType":"string","EnvValue":"DEVTRON_","EnvDescription":"Scoped variable prefix, variable name must have this prefix.","Example":"","Deprecated":"false"},{"Env":"TERMINAL_POD_DEFAULT_NAMESPACE","EnvType":"string","EnvValue":"default","EnvDescription":"Cluster terminal default namespace","Example":"","Deprecated":"false"},{"Env":"TERMINAL_POD_INACTIVE_DURATION_IN_MINS","EnvType":"int","EnvValue":"10","EnvDescription":"Timeout for cluster terminal to be inactive","Example":"","Deprecated":"false"},{"Env":"TERMINAL_POD_STATUS_SYNC_In_SECS","EnvType":"int","EnvValue":"600","EnvDescription":"this is the time interval at which the status of the cluster terminal pod","Example":"","Deprecated":"false"},{"Env":"TEST_APP","EnvType":"string","EnvValue":"orchestrator","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"TEST_PG_ADDR","EnvType":"string","EnvValue":"127.0.0.1","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"TEST_PG_DATABASE","EnvType":"string","EnvValue":"orchestrator","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"TEST_PG_LOG_QUERY","EnvType":"bool","EnvValue":"true","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"TEST_PG_PASSWORD","EnvType":"string","EnvValue":"postgrespw","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"TEST_PG_PORT","EnvType":"string","EnvValue":"55000","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"TEST_PG_USER","EnvType":"string","EnvValue":"postgres","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"TIMEOUT_FOR_FAILED_CI_BUILD","EnvType":"string","EnvValue":"15","EnvDescription":"Timeout for Failed CI build ","Example":"","Deprecated":"false"},{"Env":"TIMEOUT_IN_SECONDS","EnvType":"int","EnvValue":"5","EnvDescription":"timeout to compute the urls from services and ingress objects of an application","Example":"","Deprecated":"false"},{"Env":"USER_SESSION_DURATION_SECONDS","EnvType":"int","EnvValue":"86400","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"USE_ARTIFACT_LISTING_API_V2","EnvType":"bool","EnvValue":"true","EnvDescription":"To use the V2 API for listing artifacts in Listing the images in pipeline","Example":"","Deprecated":"false"},{"Env":"USE_CUSTOM_HTTP_TRANSPORT","EnvType":"bool","EnvValue":"false","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"USE_GIT_CLI","EnvType":"bool","EnvValue":"false","EnvDescription":"To enable git cli","Example":"","Deprecated":"false"},{"Env":"USE_RBAC_CREATION_V2","EnvType":"bool","EnvValue":"true","EnvDescription":"To use the V2 for RBAC creation","Example":"","Deprecated":"false"},{"Env":"VARIABLE_CACHE_ENABLED","EnvType":"bool","EnvValue":"true","EnvDescription":"This is used to control caching of all the scope variables defined in the system.","Example":"","Deprecated":"false"},{"Env":"VARIABLE_EXPRESSION_REGEX","EnvType":"string","EnvValue":"@{{([^}]+)}}","EnvDescription":"Scoped variable expression regex","Example":"","Deprecated":"false"},{"Env":"WEBHOOK_TOKEN","EnvType":"string","EnvValue":"","EnvDescription":"If you want to continue using jenkins for CI then please provide this for authentication of requests","Example":"","Deprecated":"false"}]},{"Category":"GITOPS","Fields":[{"Env":"ACD_CM","EnvType":"string","EnvValue":"argocd-cm","EnvDescription":"Name of the argocd CM","Example":"","Deprecated":"false"},{"Env":"ACD_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"To pass the argocd namespace","Example":"","Deprecated":"false"},{"Env":"ACD_PASSWORD","EnvType":"string","EnvValue":"","EnvDescription":"Password for the Argocd (deprecated)","Example":"","Deprecated":"false"},{"Env":"ACD_USERNAME","EnvType":"string","EnvValue":"admin","EnvDescription":"User name for argocd","Example":"","Deprecated":"false"},{"Env":"GITOPS_SECRET_NAME","EnvType":"string","EnvValue":"devtron-gitops-secret","EnvDescription":"devtron-gitops-secret","Example":"","Deprecated":"false"},{"Env":"RESOURCE_LIST_FOR_REPLICAS","EnvType":"string","EnvValue":"Deployment,Rollout,StatefulSet,ReplicaSet","EnvDescription":"this holds the list of k8s resource names which support replicas key. this list used in hibernate/un hibernate process","Example":"","Deprecated":"false"},{"Env":"RESOURCE_LIST_FOR_REPLICAS_BATCH_SIZE","EnvType":"int","EnvValue":"5","EnvDescription":"this the batch size to control no of above resources can be parsed in one go to determine hibernate status","Example":"","Deprecated":"false"}]},{"Category":"INFRA_SETUP","Fields":[{"Env":"DASHBOARD_HOST","EnvType":"string","EnvValue":"localhost","EnvDescription":"Dashboard micro-service URL","Example":"","Deprecated":"false"},{"Env":"DASHBOARD_NAMESPACE","EnvType":"string","EnvValue":"devtroncd","EnvDescription":"Dashboard micro-service namespace","Example":"","Deprecated":"false"},{"Env":"DASHBOARD_PORT","EnvType":"string","EnvValue":"3000","EnvDescription":"Port for dashboard micro-service","Example":"","Deprecated":"false"},{"Env":"DEX_HOST","EnvType":"string","EnvValue":"http://localhost","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"DEX_PORT","EnvType":"string","EnvValue":"5556","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"GIT_SENSOR_PROTOCOL","EnvType":"string","EnvValue":"REST","EnvDescription":"Protocol to connect with git-sensor micro-service","Example":"","Deprecated":"false"},{"Env":"GIT_SENSOR_SERVICE_CONFIG","EnvType":"string","EnvValue":"{\"loadBalancingPolicy\":\"pick_first\"}","EnvDescription":"git-sensor grpc service config","Example":"","Deprecated":"false"},{"Env":"GIT_SENSOR_TIMEOUT","EnvType":"int","EnvValue":"0","EnvDescription":"Timeout for getting response from the git-sensor","Example":"","Deprecated":"false"},{"Env":"GIT_SENSOR_URL","EnvType":"string","EnvValue":"127.0.0.1:7070","EnvDescription":"git-sensor micro-service url ","Example":"","Deprecated":"false"},{"Env":"HELM_CLIENT_URL","EnvType":"string","EnvValue":"127.0.0.1:50051","EnvDescription":"Kubelink micro-service url ","Example":"","Deprecated":"false"},{"Env":"KUBELINK_GRPC_MAX_RECEIVE_MSG_SIZE","EnvType":"int","EnvValue":"20","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"KUBELINK_GRPC_MAX_SEND_MSG_SIZE","EnvType":"int","EnvValue":"4","EnvDescription":"","Example":"","Deprecated":"false"},{"Env":"KUBELINK_GRPC_SERVICE_CONFIG","EnvType":"string","EnvValue":"{\"loadBalancingPolicy\":\"round_robin\"}","EnvDescription":"kubelink grpc service config","Example":"","Deprecated":"false"}]},{"Category":"POSTGRES","Fields":[{"Env":"APP","EnvType":"string","EnvValue":"orchestrator","EnvDescription":"Application name","Example":"","Deprecated":"false"},{"Env":"CASBIN_DATABASE","EnvType":"string","EnvValue":"casbin","EnvDescription":"Database for casbin","Example":"","Deprecated":"false"},{"Env":"PG_ADDR","EnvType":"string","EnvValue":"127.0.0.1","EnvDescription":"address of postgres service","Example":"postgresql-postgresql.devtroncd","Deprecated":"false"},{"Env":"PG_DATABASE","EnvType":"string","EnvValue":"orchestrator","EnvDescription":"postgres database to be made connection with","Example":"orchestrator, casbin, git_sensor, lens","Deprecated":"false"},{"Env":"PG_PASSWORD","EnvType":"string","EnvValue":"{password}","EnvDescription":"password for postgres, associated with PG_USER","Example":"confidential ;)","Deprecated":"false"},{"Env":"PG_PORT","EnvType":"string","EnvValue":"5432","EnvDescription":"port of postgresql service","Example":"5432","Deprecated":"false"},{"Env":"PG_READ_TIMEOUT","EnvType":"int64","EnvValue":"30","EnvDescription":"Time out for read operation in postgres","Example":"","Deprecated":"false"},{"Env":"PG_USER","EnvType":"string","EnvValue":"postgres","EnvDescription":"user for postgres","Example":"postgres","Deprecated":"false"},{"Env":"PG_WRITE_TIMEOUT","EnvType":"int64","EnvValue":"30","EnvDescription":"Time out for write operation in postgres","Example":"","Deprecated":"false"}]},{"Category":"RBAC","Fields":[{"Env":"ENFORCER_CACHE","EnvType":"bool","EnvValue":"false","EnvDescription":"To Enable enforcer cache.","Example":"","Deprecated":"false"},{"Env":"ENFORCER_CACHE_EXPIRATION_IN_SEC","EnvType":"int","EnvValue":"86400","EnvDescription":"Expiration time (in seconds) for enforcer cache. ","Example":"","Deprecated":"false"},{"Env":"ENFORCER_MAX_BATCH_SIZE","EnvType":"int","EnvValue":"1","EnvDescription":"Maximum batch size for the enforcer.","Example":"","Deprecated":"false"},{"Env":"USE_CASBIN_V2","EnvType":"bool","EnvValue":"true","EnvDescription":"To enable casbin V2 API","Example":"","Deprecated":"false"}]}] \ No newline at end of file diff --git a/env_gen.md b/env_gen.md index 0f6f039a1c..287d84c6d7 100644 --- a/env_gen.md +++ b/env_gen.md @@ -160,7 +160,10 @@ | DEVTRON_HELM_REPO_NAME | string |devtron | Is used to install modules (stack manager) | | false | | DEVTRON_HELM_REPO_URL | string |https://helm.devtron.ai | Is used to install modules (stack manager) | | false | | DEVTRON_INSTALLATION_TYPE | string | | Devtron Installation type(EA/Full) | | false | + | DEVTRON_INSTALLER_MODULES_PATH | string |installer.modules | Path to devtron installer modules, used to find the helm charts and values files | | false | + | DEVTRON_INSTALLER_RELEASE_PATH | string |installer.release | Path to devtron installer release, used to find the helm charts and values files | | false | | DEVTRON_MODULES_IDENTIFIER_IN_HELM_VALUES | string |installer.modules | | | false | + | DEVTRON_OPERATOR_BASE_PATH | string | | Base path for devtron operator, used to find the helm charts and values files | | false | | DEVTRON_SECRET_NAME | string |devtron-secret | | | false | | DEVTRON_VERSION_IDENTIFIER_IN_HELM_VALUES | string |installer.release | devtron operator version identifier in helm values yaml | | false | | DEX_CID | string |example-app | dex client id | | false | @@ -174,6 +177,7 @@ | ECR_REPO_NAME_PREFIX | string |test/ | Prefix for ECR repo to be created in does not exist | | false | | ENABLE_ASYNC_ARGO_CD_INSTALL_DEVTRON_CHART | bool |false | To enable async installation of gitops application | | false | | ENABLE_ASYNC_INSTALL_DEVTRON_CHART | bool |false | To enable async installation of no-gitops application | | false | + | ENABLE_NOTIFIER_V2 | bool |false | enable notifier v2 | | false | | EPHEMERAL_SERVER_VERSION_REGEX | string |v[1-9]\.\b(2[3-9]\|[3-9][0-9])\b.* | ephemeral containers support version regex that is compared with k8sServerVersion | | false | | EVENT_URL | string |http://localhost:3000/notify | Notifier service url | | false | | EXECUTE_WIRE_NIL_CHECKER | bool |false | checks for any nil pointer in wire.go | | false | @@ -204,8 +208,6 @@ | K8s_TCP_KEEPALIVE | int |30 | | | false | | K8s_TCP_TIMEOUT | int |30 | | | false | | K8s_TLS_HANDSHAKE_TIMEOUT | int |10 | | | false | - | KUBELINK_GRPC_MAX_RECEIVE_MSG_SIZE | int |20 | | | false | - | KUBELINK_GRPC_MAX_SEND_MSG_SIZE | int |4 | | | false | | LENS_TIMEOUT | int |0 | Lens microservice timeout. | | false | | LENS_URL | string |http://lens-milandevtron-service:80 | Lens micro-service URL | | false | | LIMIT_CI_CPU | string |0.5 | | | false | @@ -286,9 +288,13 @@ | DEX_HOST | string |http://localhost | | | false | | DEX_PORT | string |5556 | | | false | | GIT_SENSOR_PROTOCOL | string |REST | Protocol to connect with git-sensor micro-service | | false | + | GIT_SENSOR_SERVICE_CONFIG | string |{"loadBalancingPolicy":"pick_first"} | git-sensor grpc service config | | false | | GIT_SENSOR_TIMEOUT | int |0 | Timeout for getting response from the git-sensor | | false | | GIT_SENSOR_URL | string |127.0.0.1:7070 | git-sensor micro-service url | | false | | HELM_CLIENT_URL | string |127.0.0.1:50051 | Kubelink micro-service url | | false | + | KUBELINK_GRPC_MAX_RECEIVE_MSG_SIZE | int |20 | | | false | + | KUBELINK_GRPC_MAX_SEND_MSG_SIZE | int |4 | | | false | + | KUBELINK_GRPC_SERVICE_CONFIG | string |{"loadBalancingPolicy":"round_robin"} | kubelink grpc service config | | false | ## POSTGRES Related Environment Variables diff --git a/go.mod b/go.mod index 1c981b9ffe..a38b247229 100644 --- a/go.mod +++ b/go.mod @@ -307,8 +307,8 @@ require ( replace ( github.com/argoproj/argo-workflows/v3 v3.5.13 => github.com/devtron-labs/argo-workflows/v3 v3.5.13 - github.com/devtron-labs/authenticator => github.com/devtron-labs/devtron-services/authenticator v0.0.0-20250521130159-a5d26dc2ab8d - github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20250521130159-a5d26dc2ab8d + github.com/devtron-labs/authenticator => github.com/devtron-labs/devtron-services/authenticator v0.0.0-20250604112749-a4fc904430f9 + github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20250604112749-a4fc904430f9 github.com/go-check/check => github.com/go-check/check v0.0.0-20180628173108-788fd7840127 github.com/googleapis/gnostic => github.com/googleapis/gnostic v0.5.5 k8s.io/api => k8s.io/api v0.29.7 diff --git a/go.sum b/go.sum index 8cc7ac1d40..ea1c3fb07b 100644 --- a/go.sum +++ b/go.sum @@ -829,10 +829,10 @@ github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzq github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/devtron-labs/argo-workflows/v3 v3.5.13 h1:3pINq0gXOSeTw2z/vYe+j80lRpSN5Rp/8mfQORh8SmU= github.com/devtron-labs/argo-workflows/v3 v3.5.13/go.mod h1:/vqxcovDPT4zqr4DjR5v7CF8ggpY1l3TSa2CIG3jmjA= -github.com/devtron-labs/devtron-services/authenticator v0.0.0-20250521130159-a5d26dc2ab8d h1:qpuY6DRamKhlou0ODh2hC/JlzUEnb/3ziEWs6wyKpx8= -github.com/devtron-labs/devtron-services/authenticator v0.0.0-20250521130159-a5d26dc2ab8d/go.mod h1:FfaLDXN1ZXxyRpnskBqVIYkpkWDCzBmDgIO9xqLnxdQ= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20250521130159-a5d26dc2ab8d h1:q0a5FgU3HowrydPq+BjIMHCF0izHjl1oPc71JDLsv+8= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20250521130159-a5d26dc2ab8d/go.mod h1:CXQGEo+kZc7JPX5hn4jJf1msal9q/ExSdAYGkHNPnQw= +github.com/devtron-labs/devtron-services/authenticator v0.0.0-20250604112749-a4fc904430f9 h1:OFZ1AMLSm428qw/IXXUK4sKcsIiUbmYTxhBZBzouau0= +github.com/devtron-labs/devtron-services/authenticator v0.0.0-20250604112749-a4fc904430f9/go.mod h1:FfaLDXN1ZXxyRpnskBqVIYkpkWDCzBmDgIO9xqLnxdQ= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20250604112749-a4fc904430f9 h1:VVTeMCYeQqPVxP+f5nKhY/C24n7RwbxZQgfQTQijZPs= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20250604112749-a4fc904430f9/go.mod h1:HQVUnQI7WHwVq89Bib/18xJqM89S1+xI0O7REctMMrA= github.com/devtron-labs/go-bitbucket v0.9.60-beta h1:VEx1jvDgdtDPS6A1uUFoaEi0l1/oLhbr+90xOwr6sDU= github.com/devtron-labs/go-bitbucket v0.9.60-beta/go.mod h1:GnuiCesvh8xyHeMCb+twm8lBR/kQzJYSKL28ZfObp1Y= github.com/devtron-labs/protos v0.0.3-0.20250323220609-ecf8a0f7305e h1:U6UdYbW8a7xn5IzFPd8cywjVVPfutGJCudjePAfL/Hs= diff --git a/internal/sql/repository/NotificationSettingsRepository.go b/internal/sql/repository/NotificationSettingsRepository.go index 90a6981e46..337d7b549a 100644 --- a/internal/sql/repository/NotificationSettingsRepository.go +++ b/internal/sql/repository/NotificationSettingsRepository.go @@ -17,11 +17,13 @@ package repository import ( + "context" "fmt" "github.com/devtron-labs/devtron/pkg/resourceQualifiers" "github.com/devtron-labs/devtron/pkg/sql" "github.com/go-pg/pg" "github.com/go-pg/pg/orm" + "go.opentelemetry.io/otel" "k8s.io/utils/pointer" "strconv" ) @@ -44,6 +46,7 @@ type NotificationSettingsRepository interface { FindNotificationSettingBuildOptions(settingRequest *SearchRequest) ([]*SettingOptionDTO, error) FetchNotificationSettingGroupBy(viewId int) ([]NotificationSettings, error) FindNotificationSettingsByConfigIdAndConfigType(configId int, configType string) ([]*NotificationSettings, error) + FindNotificationSettingsWithRules(ctx context.Context, eventId int, req GetRulesRequest) ([]NotificationSettings, error) } type NotificationSettingsRepositoryImpl struct { @@ -89,6 +92,25 @@ type NotificationSettings struct { ClusterId *int `sql:"cluster_id"` } +type NotificationSettingsBean struct { + Id int `json:"id"` + TeamId *int `json:"team_id"` + AppId *int `json:"app_id"` + EnvId *int `json:"env_id"` + PipelineId *int `json:"pipeline_id"` + PipelineType string `json:"pipeline_type"` + EventTypeId int `json:"event_type_id"` + Config []ConfigEntry `json:"config"` + ViewId int `json:"view_id"` +} + +type ConfigEntry struct { + Dest string `json:"dest"` + Rule string `json:"rule"` + ConfigId int `json:"configId"` + Recipient string `json:"recipient"` +} + type SettingOptionDTO struct { //TeamId int `json:"-"` //AppId int `json:"-"` @@ -101,6 +123,17 @@ type SettingOptionDTO struct { ClusterName string `json:"clusterName"` } +type GetRulesRequest struct { + TeamId int + EnvId int + AppId int + PipelineId int + PipelineType string + IsProdEnv *bool + ClusterId int + EnvIdsForCiPipeline []int +} + func (impl *NotificationSettingsRepositoryImpl) FindNSViewCount() (int, error) { count, err := impl.dbConnection.Model(&NotificationSettingsView{}). WhereGroup(func(query *orm.Query) (*orm.Query, error) { @@ -355,3 +388,254 @@ func (impl *NotificationSettingsRepositoryImpl) FindNotificationSettingsByConfig } return notificationSettings, nil } + +func (impl *NotificationSettingsRepositoryImpl) FindNotificationSettingsWithRules(ctx context.Context, eventId int, req GetRulesRequest) ([]NotificationSettings, error) { + _, span := otel.Tracer("NotificationSettingsRepository").Start(ctx, "FindNotificationSettingsWithRules") + defer span.End() + if len(req.PipelineType) == 0 || eventId == 0 { + return nil, pg.ErrNoRows + } + + // Handle special case for event type 6 (deployment blocked with auto trigger) + if eventId == 6 { + // This is the case when deployment is blocked and pipeline is set to auto trigger + eventId = 3 + } + + var notificationSettings []NotificationSettings + settingsFilerConditions := func(query *orm.Query) (*orm.Query, error) { + query = query. + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id = ?", req.AppId). + Where("notification_settings.env_id IS NULL"). + Where("notification_settings.team_id IS NULL"). + Where("notification_settings.pipeline_id IS NULL"). + Where("notification_settings.cluster_id IS NULL") + return query, nil + }). + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id IS NULL"). + Where("notification_settings.env_id = ?", req.EnvId). + Where("notification_settings.team_id IS NULL"). + Where("notification_settings.pipeline_id IS NULL"). + Where("notification_settings.cluster_id IS NULL") + return query, nil + }). + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id IS NULL"). + Where("notification_settings.env_id IS NULL"). + Where("notification_settings.team_id = ?", req.TeamId). + Where("notification_settings.pipeline_id IS NULL"). + Where("notification_settings.cluster_id IS NULL") + return query, nil + }). + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id IS NULL"). + Where("notification_settings.env_id IS NULL"). + Where("notification_settings.team_id IS NULL"). + Where("notification_settings.pipeline_id = ?", req.PipelineId). + Where("notification_settings.cluster_id IS NULL") + return query, nil + }). + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id IS NULL"). + Where("notification_settings.env_id = ?", req.EnvId). + Where("notification_settings.team_id = ?", req.TeamId). + Where("notification_settings.pipeline_id IS NULL"). + Where("notification_settings.cluster_id IS NULL") + return query, nil + }). + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id = ?", req.AppId). + Where("notification_settings.team_id IS NULL"). + Where("notification_settings.env_id = ?", req.EnvId). + Where("notification_settings.pipeline_id IS NULL"). + Where("notification_settings.cluster_id IS NULL") + return query, nil + }). + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id = ?", req.AppId). + Where("notification_settings.env_id = ?", req.EnvId). + Where("notification_settings.team_id = ?", req.TeamId). + WhereOr("notification_settings.pipeline_id = ?", req.PipelineId) + return query, nil + }). // all envs of cluster , env,app and team are null + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id IS NULL"). + Where("notification_settings.team_id IS NULL"). + Where("notification_settings.env_id IS NULL"). + Where("notification_settings.cluster_id = ?", req.ClusterId) + return query, nil + }). // all envs of cluster in a app + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id = ?", req.AppId). + Where("notification_settings.env_id IS NULL"). + Where("notification_settings.cluster_id = ?", req.ClusterId) + return query, nil + }). // all envs of cluster in a team, app is null + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id IS NULL"). + Where("notification_settings.team_id = ?", req.TeamId). + Where("notification_settings.env_id IS NULL"). + Where("notification_settings.cluster_id = ?", req.ClusterId) + return query, nil + }) + + if req.IsProdEnv != nil { + envIdentifier := resourceQualifiers.AllExistingAndFutureNonProdEnvsInt + if *req.IsProdEnv { + envIdentifier = resourceQualifiers.AllExistingAndFutureProdEnvsInt + } + + query = query. + // for all prod/non-prod envs across for pipelines of a project, app,cluster and pipeline is null + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id IS NULL"). + Where("notification_settings.env_id = ?", envIdentifier). + Where("notification_settings.team_id = ?", req.TeamId). + Where("notification_settings.pipeline_id IS NULL"). + Where("notification_settings.cluster_id IS NULL") + return query, nil + }). + // for all prod/non-prod envs across for pipelines of an app, project,cluster and pipeline is null + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id = ?", req.AppId). + Where("notification_settings.team_id IS NULL"). + Where("notification_settings.env_id = ?", envIdentifier). + Where("notification_settings.pipeline_id IS NULL"). + Where("notification_settings.cluster_id IS NULL") + return query, nil + }). + // for all prod/non-prod envs across all clusters, cluster, app and team is null + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id IS NULL"). + Where("notification_settings.env_id = ?", envIdentifier). + Where("notification_settings.team_id IS NULL"). + Where("notification_settings.pipeline_id IS NULL") + return query, nil + }). + // all prod envs of a cluster , app and team is null + // all non-prod envs of a cluster , app and team is null + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id IS NULL"). + Where("notification_settings.team_id IS NULL"). + Where("notification_settings.env_id = ?", envIdentifier). + Where("notification_settings.cluster_id = ?", req.ClusterId) + return query, nil + }). // all prod envs of a cluster in a app + // all non prod envs of a cluster in a app + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id = ?", req.AppId). + Where("notification_settings.env_id = ? ", envIdentifier). + Where("notification_settings.cluster_id = ?", req.ClusterId) + return query, nil + }). // all prod envs of a cluster in a team, app is null + // all non prod envs of a cluster in a team, app is null + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id IS NULL"). + Where("notification_settings.team_id = ?", req.TeamId). + Where("notification_settings.env_id = ?", envIdentifier). + Where("notification_settings.cluster_id = ?", req.ClusterId) + return query, nil + }) + } + if len(req.EnvIdsForCiPipeline) > 0 { + query = query. + // for all envs across for pipelines of a project, app,cluster and pipeline is null + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id IS NULL"). + Where("notification_settings.env_id in (?)", pg.In(req.EnvIdsForCiPipeline)). + Where("notification_settings.team_id = ?", req.TeamId). + Where("notification_settings.pipeline_id IS NULL"). + Where("notification_settings.cluster_id IS NULL") + return query, nil + }). + // for all envs across for pipelines of an app, project,cluster and pipeline is null + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id = ?", req.AppId). + Where("notification_settings.team_id IS NULL"). + Where("notification_settings.env_id in (?)", pg.In(req.EnvIdsForCiPipeline)). + Where("notification_settings.pipeline_id IS NULL"). + Where("notification_settings.cluster_id IS NULL") + return query, nil + }). + // for all envs across all clusters, cluster, app and team is null + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id IS NULL"). + Where("notification_settings.env_id in (?)", pg.In(req.EnvIdsForCiPipeline)). + Where("notification_settings.team_id IS NULL"). + Where("notification_settings.pipeline_id IS NULL") + return query, nil + }). + // for all envs ids of an app within a project with pipeline id + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id = ?", req.AppId). + Where("notification_settings.env_id in (?)", pg.In(req.EnvIdsForCiPipeline)). + Where("notification_settings.team_id = ?", req.TeamId). + Where("notification_settings.pipeline_id = ?", req.PipelineId) + return query, nil + }). + // all envs of a cluster , app and team is null + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id IS NULL"). + Where("notification_settings.team_id IS NULL"). + Where("notification_settings.env_id in (?)", pg.In(req.EnvIdsForCiPipeline)). + Where("notification_settings.cluster_id = ?", req.ClusterId) + return query, nil + }). + // all envs of a cluster in an app + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id = ?", req.AppId). + Where("notification_settings.env_id in (?) ", pg.In(req.EnvIdsForCiPipeline)). + Where("notification_settings.cluster_id = ?", req.ClusterId) + return query, nil + }). + // all envs of a cluster in a team, app is null + WhereOrGroup(func(query *orm.Query) (*orm.Query, error) { + query = query. + Where("notification_settings.app_id IS NULL"). + Where("notification_settings.team_id = ?", req.TeamId). + Where("notification_settings.env_id in (?)", pg.In(req.EnvIdsForCiPipeline)). + Where("notification_settings.cluster_id = ?", req.ClusterId) + return query, nil + }) + } + return query, nil + + } + + query := impl.dbConnection. + Model(¬ificationSettings). + Column("notification_settings.*", "NotificationRule"). + Where("notification_settings.pipeline_type = ?", req.PipelineType). + Where("notification_settings.event_type_id = ?", eventId). + WhereGroup(settingsFilerConditions) + + err := query.Select() + if err != nil { + return nil, err + } + return notificationSettings, nil +} diff --git a/internal/sql/repository/app/AppRepository.go b/internal/sql/repository/app/AppRepository.go index 2ccf064c9a..93710da779 100644 --- a/internal/sql/repository/app/AppRepository.go +++ b/internal/sql/repository/app/AppRepository.go @@ -92,6 +92,8 @@ type AppRepository interface { FindAppAndProjectByIdsIn(ids []int) ([]*App, error) FetchAppIdsByDisplayNamesForJobs(names []string) (map[int]string, []int, error) GetActiveCiCdAppsCount() (int, error) + FindDevtronAppCount() (int, error) + FindJobCount() (int, error) UpdateAppOfferingModeForAppIds(successAppIds []*int, appOfferingMode string, userId int32) error } @@ -520,6 +522,20 @@ func (repo AppRepositoryImpl) GetActiveCiCdAppsCount() (int, error) { Count() } +func (repo AppRepositoryImpl) FindDevtronAppCount() (int, error) { + return repo.dbConnection.Model(&App{}). + Where("active=?", true). + Where("app_type=?", helper.CustomApp). + Count() +} + +func (repo AppRepositoryImpl) FindJobCount() (int, error) { + return repo.dbConnection.Model(&App{}). + Where("active=?", true). + Where("app_type=?", helper.Job). + Count() +} + func (repo AppRepositoryImpl) UpdateAppOfferingModeForAppIds(successAppIds []*int, appOfferingMode string, userId int32) error { query := "update app set app_offering_mode = ?,updated_by = ?, updated_on = ? where id in (?);" var app *App diff --git a/internal/sql/repository/appWorkflow/AppWorkflowRepository.go b/internal/sql/repository/appWorkflow/AppWorkflowRepository.go index e0f47b3229..49b71eb106 100644 --- a/internal/sql/repository/appWorkflow/AppWorkflowRepository.go +++ b/internal/sql/repository/appWorkflow/AppWorkflowRepository.go @@ -32,7 +32,7 @@ type AppWorkflowRepository interface { FindByIds(ids []int) (*AppWorkflow, error) FindByAppId(appId int) (appWorkflow []*AppWorkflow, err error) FindByAppIds(appIds []int) (appWorkflow []*AppWorkflow, err error) - DeleteAppWorkflow(appWorkflow *AppWorkflow, tx *pg.Tx) error + DeleteAppWorkflowAndAllMappings(appWorkflow *AppWorkflow, tx *pg.Tx) error SaveAppWorkflowMapping(wf *AppWorkflowMapping, tx *pg.Tx) (*AppWorkflowMapping, error) FindByWorkflowId(workflowId int) ([]*AppWorkflowMapping, error) @@ -164,26 +164,21 @@ func (impl AppWorkflowRepositoryImpl) FindByIds(ids []int) (*AppWorkflow, error) return appWorkflow, err } -func (impl AppWorkflowRepositoryImpl) DeleteAppWorkflow(appWorkflow *AppWorkflow, tx *pg.Tx) error { - appWorkflowMappings, err := impl.FindWFCIMappingByWorkflowId(appWorkflow.Id) - if err != nil && pg.ErrNoRows != err { - impl.Logger.Errorw("err", err) - return err - } - if len(appWorkflowMappings) > 0 { - for _, item := range appWorkflowMappings { - err = impl.DeleteAppWorkflowMapping(item, tx) - if err != nil { - impl.Logger.Errorw("err", err) - return err - } +func (impl AppWorkflowRepositoryImpl) DeleteAppWorkflowAndAllMappings(appWorkflow *AppWorkflow, tx *pg.Tx) error { + // Delete app workflow mapping + mapping, err := impl.FindWFAllMappingByWorkflowId(appWorkflow.Id) + for _, item := range mapping { + err := impl.DeleteAppWorkflowMapping(item, tx) + if err != nil { + impl.Logger.Errorw("error in deleting workflow mapping", "err", err) + return err } } appWorkflow.Active = false - err = impl.dbConnection.Update(appWorkflow) + err = tx.Update(appWorkflow) if err != nil { - impl.Logger.Errorw("err", err) + impl.Logger.Errorw("error in deleting app workflow", "appWorkflow", appWorkflow, "err", err) return err } return nil diff --git a/internal/sql/repository/deploymentConfig/repository.go b/internal/sql/repository/deploymentConfig/repository.go index f2ec110523..854996b1e2 100644 --- a/internal/sql/repository/deploymentConfig/repository.go +++ b/internal/sql/repository/deploymentConfig/repository.go @@ -58,8 +58,8 @@ type Repository interface { SaveAll(tx *pg.Tx, configs []*DeploymentConfig) ([]*DeploymentConfig, error) Update(tx *pg.Tx, config *DeploymentConfig) (*DeploymentConfig, error) UpdateAll(tx *pg.Tx, config []*DeploymentConfig) ([]*DeploymentConfig, error) - GetByAppIdAndEnvId(appId, envId int) (*DeploymentConfig, error) - GetAppLevelConfigForDevtronApps(appId int) (*DeploymentConfig, error) + GetByAppIdAndEnvId(tx *pg.Tx, appId, envId int) (*DeploymentConfig, error) + GetAppLevelConfigForDevtronApps(tx *pg.Tx, appId int) (*DeploymentConfig, error) GetAppAndEnvLevelConfigsInBulk(appIdToEnvIdsMap map[int][]int) ([]*DeploymentConfig, error) GetByAppIdAndEnvIdEvenIfInactive(appId, envId int) (*DeploymentConfig, error) GetConfigByAppIds(appIds []int) ([]*DeploymentConfig, error) @@ -121,9 +121,15 @@ func (impl *RepositoryImpl) UpdateAll(tx *pg.Tx, configs []*DeploymentConfig) ([ return configs, err } -func (impl *RepositoryImpl) GetByAppIdAndEnvId(appId, envId int) (*DeploymentConfig, error) { +func (impl *RepositoryImpl) GetByAppIdAndEnvId(tx *pg.Tx, appId, envId int) (*DeploymentConfig, error) { result := &DeploymentConfig{} - err := impl.dbConnection.Model(result). + var query *orm.Query + if tx != nil { + query = tx.Model(result) + } else { + query = impl.dbConnection.Model(result) + } + err := query. Join("INNER JOIN app a"). JoinOn("deployment_config.app_id = a.id"). Join("INNER JOIN environment e"). @@ -138,9 +144,15 @@ func (impl *RepositoryImpl) GetByAppIdAndEnvId(appId, envId int) (*DeploymentCon return result, err } -func (impl *RepositoryImpl) GetAppLevelConfigForDevtronApps(appId int) (*DeploymentConfig, error) { +func (impl *RepositoryImpl) GetAppLevelConfigForDevtronApps(tx *pg.Tx, appId int) (*DeploymentConfig, error) { result := &DeploymentConfig{} - err := impl.dbConnection.Model(result). + var query *orm.Query + if tx != nil { + query = tx.Model(result) + } else { + query = impl.dbConnection.Model(result) + } + err := query. Join("INNER JOIN app a"). JoinOn("deployment_config.app_id = a.id"). Where("a.active = ?", true). diff --git a/internal/sql/repository/helper/AppListingRepositoryQueryBuilder.go b/internal/sql/repository/helper/AppListingRepositoryQueryBuilder.go index b527185e19..8767773b5c 100644 --- a/internal/sql/repository/helper/AppListingRepositoryQueryBuilder.go +++ b/internal/sql/repository/helper/AppListingRepositoryQueryBuilder.go @@ -73,7 +73,7 @@ const ( func (impl AppListingRepositoryQueryBuilder) BuildJobListingQuery(appIDs []int, statuses []string, environmentIds []int, sortOrder string) (string, []interface{}) { var queryParams []interface{} query := `select ci_pipeline.name as ci_pipeline_name,ci_pipeline.id as ci_pipeline_id,app.id as job_id,app.display_name - as job_name, app.app_name,app.description,app.team_id,cwr.started_on,cwr.status,cem.environment_id,cwr.environment_id as last_triggered_environment_id from app left join ci_pipeline on + as job_name, app.app_name,app.description,app.created_by,app.team_id,cwr.started_on,cwr.status,cem.environment_id,cwr.environment_id as last_triggered_environment_id from app left join ci_pipeline on app.id = ci_pipeline.app_id and ci_pipeline.active=true left join (select cw.ci_pipeline_id, cw.status, cw.started_on, cw.environment_id from ci_workflow cw inner join (select ci_pipeline_id, MAX(started_on) max_started_on from ci_workflow group by ci_pipeline_id ) cws on cw.ci_pipeline_id = cws.ci_pipeline_id diff --git a/internal/sql/repository/pipelineConfig/CiWorkflowRepository.go b/internal/sql/repository/pipelineConfig/CiWorkflowRepository.go index eca7268854..ddea3864a4 100644 --- a/internal/sql/repository/pipelineConfig/CiWorkflowRepository.go +++ b/internal/sql/repository/pipelineConfig/CiWorkflowRepository.go @@ -45,7 +45,7 @@ type CiWorkflowRepository interface { FindLastTriggeredWorkflowGitTriggersByArtifactId(ciArtifactId int) (ciWorkflow *CiWorkflow, err error) FindLastTriggeredWorkflowGitTriggersByArtifactIds(ciArtifactIds []int) ([]*WorkflowWithArtifact, error) ExistsByStatus(status string) (bool, error) - FindBuildTypeAndStatusDataOfLast1Day() []*BuildTypeCount + FindBuildTypeAndStatusDataOfLast1Day() ([]*BuildTypeCount, error) FIndCiWorkflowStatusesByAppId(appId int) ([]*CiWorkflowStatus, error) MigrateIsArtifactUploaded(wfId int, isArtifactUploaded bool) @@ -347,14 +347,15 @@ func (impl *CiWorkflowRepositoryImpl) ExistsByStatus(status string) (bool, error return exists, err } -func (impl *CiWorkflowRepositoryImpl) FindBuildTypeAndStatusDataOfLast1Day() []*BuildTypeCount { +func (impl *CiWorkflowRepositoryImpl) FindBuildTypeAndStatusDataOfLast1Day() ([]*BuildTypeCount, error) { var buildTypeCounts []*BuildTypeCount query := "select status,ci_build_type as type, count(*) from ci_workflow where status in ('Succeeded','Failed') and started_on > ? group by (ci_build_type, status)" _, err := impl.dbConnection.Query(&buildTypeCounts, query, time.Now().AddDate(0, 0, -1)) if err != nil { impl.logger.Errorw("error occurred while fetching build type vs status vs count data", "err", err) + return nil, err } - return buildTypeCounts + return buildTypeCounts, nil } func (impl *CiWorkflowRepositoryImpl) FIndCiWorkflowStatusesByAppId(appId int) ([]*CiWorkflowStatus, error) { @@ -372,6 +373,7 @@ func (impl *CiWorkflowRepositoryImpl) FIndCiWorkflowStatusesByAppId(appId int) ( _, err := impl.dbConnection.Query(&ciworkflowStatuses, query, appId) //, pg.In(ciPipelineIds)) if err != nil { impl.logger.Errorw("error occurred while fetching build type vs status vs count data", "err", err) + return ciworkflowStatuses, err } return ciworkflowStatuses, err } diff --git a/internal/sql/repository/pipelineConfig/CiWorkflowRepository_test.go b/internal/sql/repository/pipelineConfig/CiWorkflowRepository_test.go index 191b4e6fe9..b9dd70ff54 100644 --- a/internal/sql/repository/pipelineConfig/CiWorkflowRepository_test.go +++ b/internal/sql/repository/pipelineConfig/CiWorkflowRepository_test.go @@ -32,7 +32,7 @@ func TestCiWorkflowRepository(t *testing.T) { con, _ := sql.NewDbConnection(cfg, logger) assert.Nil(t, err) workflowRepositoryImpl := NewCiWorkflowRepositoryImpl(con, logger) - statusData := workflowRepositoryImpl.FindBuildTypeAndStatusDataOfLast1Day() + statusData, _ := workflowRepositoryImpl.FindBuildTypeAndStatusDataOfLast1Day() for _, statusDatum := range statusData { fmt.Println(statusDatum.Count) } diff --git a/internal/sql/repository/pipelineConfig/PipelineRepository.go b/internal/sql/repository/pipelineConfig/PipelineRepository.go index 0c3461f46e..0bd629a0e0 100644 --- a/internal/sql/repository/pipelineConfig/PipelineRepository.go +++ b/internal/sql/repository/pipelineConfig/PipelineRepository.go @@ -146,6 +146,7 @@ type PipelineRepository interface { GetAllAppsByClusterAndDeploymentAppType(clusterIds []int, deploymentAppName string) ([]*PipelineDeploymentConfigObj, error) GetAllArgoAppInfoByDeploymentAppNames(deploymentAppNames []string) ([]*PipelineDeploymentConfigObj, error) FindEnvIdsByIdsInIncludingDeleted(ids []int) ([]int, error) + GetPipelineCountByDeploymentType(deploymentType string) (int, error) } type CiArtifactDTO struct { @@ -669,14 +670,14 @@ func (impl *PipelineRepositoryImpl) GetAppAndEnvDetailsForDeploymentAppTypePipel func (impl *PipelineRepositoryImpl) GetArgoPipelinesHavingTriggersStuckInLastPossibleNonTerminalTimelines(pendingSinceSeconds int, timeForDegradation int) ([]*Pipeline, error) { var pipelines []*Pipeline - queryString := `select p.* from pipeline p inner join cd_workflow cw on cw.pipeline_id = p.id - inner join cd_workflow_runner cwr on cwr.cd_workflow_id=cw.id + queryString := `select p.* from pipeline p inner join cd_workflow cw on cw.pipeline_id = p.id + inner join cd_workflow_runner cwr on cwr.cd_workflow_id=cw.id left join deployment_config dc on dc.active=true and dc.app_id = p.app_id and dc.environment_id=p.environment_id - where cwr.id in (select cd_workflow_runner_id from pipeline_status_timeline - where id in - (select DISTINCT ON (cd_workflow_runner_id) max(id) as id from pipeline_status_timeline - group by cd_workflow_runner_id, id order by cd_workflow_runner_id,id desc) - and status in (?) and status_time < NOW() - INTERVAL '? seconds') + where cwr.id in (select cd_workflow_runner_id from pipeline_status_timeline + where id in + (select DISTINCT ON (cd_workflow_runner_id) max(id) as id from pipeline_status_timeline + group by cd_workflow_runner_id, id order by cd_workflow_runner_id,id desc) + and status in (?) and status_time < NOW() - INTERVAL '? seconds') and cwr.started_on > NOW() - INTERVAL '? minutes' and (p.deployment_app_type=? or dc.deployment_app_type=?) and p.deleted=?;` _, err := impl.dbConnection.Query(&pipelines, queryString, pg.In([]timelineStatus.TimelineStatus{timelineStatus.TIMELINE_STATUS_KUBECTL_APPLY_SYNCED, @@ -691,11 +692,11 @@ func (impl *PipelineRepositoryImpl) GetArgoPipelinesHavingTriggersStuckInLastPos func (impl *PipelineRepositoryImpl) GetArgoPipelinesHavingLatestTriggerStuckInNonTerminalStatuses(getPipelineDeployedBeforeMinutes int, getPipelineDeployedWithinHours int) ([]*Pipeline, error) { var pipelines []*Pipeline - queryString := `select p.id from pipeline p inner join cd_workflow cw on cw.pipeline_id = p.id - inner join cd_workflow_runner cwr on cwr.cd_workflow_id=cw.id + queryString := `select p.id from pipeline p inner join cd_workflow cw on cw.pipeline_id = p.id + inner join cd_workflow_runner cwr on cwr.cd_workflow_id=cw.id left join deployment_config dc on dc.active=true and dc.app_id = p.app_id and dc.environment_id=p.environment_id - where cwr.id in (select id from cd_workflow_runner - where started_on < NOW() - INTERVAL '? minutes' and started_on > NOW() - INTERVAL '? hours' and status not in (?) + where cwr.id in (select id from cd_workflow_runner + where started_on < NOW() - INTERVAL '? minutes' and started_on > NOW() - INTERVAL '? hours' and status not in (?) and workflow_type=? and cd_workflow_id in (select DISTINCT ON (pipeline_id) max(id) as id from cd_workflow group by pipeline_id, id order by pipeline_id, id desc)) and (p.deployment_app_type=? or dc.deployment_app_type=?) and p.deleted=?;` @@ -964,3 +965,24 @@ func (impl *PipelineRepositoryImpl) FindEnvIdsByIdsInIncludingDeleted(ids []int) } return envIds, err } + +func (impl *PipelineRepositoryImpl) GetPipelineCountByDeploymentType(deploymentType string) (int, error) { + var count int + // Count pipelines by deployment type, considering both pipeline table and deployment_config table + // The deployment_config table can override the deployment type from the pipeline table + query := ` + SELECT COUNT(DISTINCT p.id) + FROM pipeline p + LEFT JOIN deployment_config dc ON dc.active = true + AND dc.app_id = p.app_id + AND dc.environment_id = p.environment_id + WHERE p.deleted = false + AND (p.deployment_app_type = ? OR dc.deployment_app_type = ?) + ` + _, err := impl.dbConnection.Query(&count, query, deploymentType, deploymentType) + if err != nil { + impl.logger.Errorw("error getting pipeline count by deployment type", "deploymentType", deploymentType, "err", err) + return 0, err + } + return count, nil +} diff --git a/manifests/installation-script b/manifests/installation-script index 2591e81125..805d928c56 100644 --- a/manifests/installation-script +++ b/manifests/installation-script @@ -1,4 +1,10 @@ LTAG="v1.6.0"; REPO_RAW_URL="https://raw.githubusercontent.com/devtron-labs/devtron/"; - +shebang = `#!/bin/bash +`; +sleep50 = shebang + ` +sleep 50`; +ignore = shellScript sleep50; +ignore = shellScript sleep50; +ignore = shellScript sleep50; log("executed devtron setup installation"); diff --git a/pkg/app/AppListingService.go b/pkg/app/AppListingService.go index a5d5654ff2..004bc158f2 100644 --- a/pkg/app/AppListingService.go +++ b/pkg/app/AppListingService.go @@ -360,9 +360,14 @@ func (impl AppListingServiceImpl) FetchJobs(fetchJobListingRequest FetchAppListi impl.Logger.Errorw("error in fetching job list", "error", err, jobListingFilter) return []*AppView.JobContainer{}, err } + userEmailMap, err := impl.extractEmailIdFromUserId(jobListingContainers) + if err != nil { + impl.Logger.Errorw("Error in extractEmailIdFromUserId", "jobContainers", jobListingContainers, "err", err) + return nil, err + } CiPipelineIDs := GetCIPipelineIDs(jobListingContainers) JobsLastSucceededOnTime, err := impl.appListingRepository.FetchJobsLastSucceededOn(CiPipelineIDs) - jobContainers := BuildJobListingResponse(jobListingContainers, JobsLastSucceededOnTime) + jobContainers := BuildJobListingResponse(jobListingContainers, JobsLastSucceededOnTime, userEmailMap) return jobContainers, nil } @@ -553,7 +558,7 @@ func GetCIPipelineIDs(jobContainers []*AppView.JobListingContainer) []int { } return ciPipelineIDs } -func BuildJobListingResponse(jobContainers []*AppView.JobListingContainer, JobsLastSucceededOnTime []*AppView.CiPipelineLastSucceededTime) []*AppView.JobContainer { +func BuildJobListingResponse(jobContainers []*AppView.JobListingContainer, JobsLastSucceededOnTime []*AppView.CiPipelineLastSucceededTime, userEmailMap map[int32]string) []*AppView.JobContainer { jobContainersMapping := make(map[int]AppView.JobContainer) var appIds []int @@ -572,6 +577,7 @@ func BuildJobListingResponse(jobContainers []*AppView.JobListingContainer, JobsL val.JobName = jobContainer.JobName val.JobActualName = jobContainer.JobActualName val.ProjectId = jobContainer.ProjectId + val.Description = AppView.GenericNoteResponseBean{Description: jobContainer.Description, CreatedBy: userEmailMap[jobContainer.CreatedBy]} } if len(val.JobCiPipelines) == 0 { @@ -881,3 +887,24 @@ func (impl AppListingServiceImpl) RedirectToLinkouts(Id int, appId int, envId in return link, nil } + +func (impl AppListingServiceImpl) extractEmailIdFromUserId(jobContainers []*AppView.JobListingContainer) (map[int32]string, error) { + var userIds []int32 + userEmailMap := make(map[int32]string) + for _, job := range jobContainers { + if job.CreatedBy != 0 { + userIds = append(userIds, job.CreatedBy) + } + } + if len(userIds) > 0 { + users, err := impl.userRepository.GetByIds(userIds) + if err != nil { + impl.Logger.Errorw("Error in getting users", "userIds", userIds, "err", err) + return userEmailMap, err + } + for _, user := range users { + userEmailMap[user.Id] = user.EmailId + } + } + return userEmailMap, nil +} diff --git a/pkg/app/AppService.go b/pkg/app/AppService.go index c6df69e9a4..51969779f6 100644 --- a/pkg/app/AppService.go +++ b/pkg/app/AppService.go @@ -20,6 +20,9 @@ import ( "context" "errors" "fmt" + "strconv" + "time" + health2 "github.com/argoproj/gitops-engine/pkg/health" argoApplication "github.com/devtron-labs/devtron/client/argocdServer/bean" "github.com/devtron-labs/devtron/internal/sql/models" @@ -40,9 +43,6 @@ import ( "github.com/devtron-labs/devtron/pkg/deployment/manifest/deploymentTemplate/read" bean4 "github.com/devtron-labs/devtron/pkg/deployment/trigger/devtronApps/bean" "github.com/devtron-labs/devtron/pkg/workflow/cd" - "net/url" - "strconv" - "time" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/caarlos0/env" @@ -124,6 +124,7 @@ type AppServiceImpl struct { deploymentConfigService common2.DeploymentConfigService envConfigOverrideReadService read.EnvConfigOverrideService cdWorkflowRunnerService cd.CdWorkflowRunnerService + deploymentEventHandler DeploymentEventHandler } type AppService interface { @@ -132,7 +133,6 @@ type AppService interface { GetConfigMapAndSecretJson(appId int, envId int) ([]byte, error) UpdateCdWorkflowRunnerByACDObject(app *v1alpha1.Application, cdWfrId int, updateTimedOutStatus bool) error UpdateDeploymentStatusForGitOpsPipelines(app *v1alpha1.Application, applicationClusterId int, statusTime time.Time, isAppStore bool) (bool, bool, *chartConfig.PipelineOverride, error) - WriteCDSuccessEvent(appId int, envId int, override *chartConfig.PipelineOverride) CreateGitOpsRepo(app *app.App, targetRevision string, userId int32) (gitopsRepoName string, chartGitAttr *commonBean.ChartGitAttribute, err error) // TODO: move inside reader service @@ -164,7 +164,8 @@ func NewAppService( appListingService AppListingService, deploymentConfigService common2.DeploymentConfigService, envConfigOverrideReadService read.EnvConfigOverrideService, - cdWorkflowRunnerService cd.CdWorkflowRunnerService) *AppServiceImpl { + cdWorkflowRunnerService cd.CdWorkflowRunnerService, + deploymentEventHandler DeploymentEventHandler) *AppServiceImpl { appServiceImpl := &AppServiceImpl{ mergeUtil: mergeUtil, pipelineOverrideRepository: pipelineOverrideRepository, @@ -195,6 +196,7 @@ func NewAppService( deploymentConfigService: deploymentConfigService, envConfigOverrideReadService: envConfigOverrideReadService, cdWorkflowRunnerService: cdWorkflowRunnerService, + deploymentEventHandler: deploymentEventHandler, } return appServiceImpl } @@ -304,6 +306,7 @@ func (impl *AppServiceImpl) UpdateDeploymentStatusForGitOpsPipelines(app *v1alph impl.logger.Errorw("error on update cd workflow runner", "CdWorkflowId", pipelineOverride.CdWorkflowId, "status", cdWorkflow2.WorkflowTimedOut, "err", err) return isSucceeded, isTimelineUpdated, pipelineOverride, err } + impl.deploymentEventHandler.WriteCDNotificationEventAsync(cdPipeline.AppId, cdPipeline.EnvironmentId, pipelineOverride, eventUtil.Fail) return isSucceeded, isTimelineUpdated, pipelineOverride, nil } if reconciledAt.IsZero() || (kubectlSyncedTimeline != nil && kubectlSyncedTimeline.Id > 0 && reconciledAt.After(kubectlSyncedTimeline.StatusTime)) { @@ -320,7 +323,7 @@ func (impl *AppServiceImpl) UpdateDeploymentStatusForGitOpsPipelines(app *v1alph } if isSucceeded { impl.logger.Infow("writing cd success event", "gitHash", gitHash, "pipelineOverride", pipelineOverride) - go impl.WriteCDSuccessEvent(cdPipeline.AppId, cdPipeline.EnvironmentId, pipelineOverride) + impl.deploymentEventHandler.WriteCDNotificationEventAsync(cdPipeline.AppId, cdPipeline.EnvironmentId, pipelineOverride, eventUtil.Success) } } else { impl.logger.Debugw("event received for older triggered revision", "gitHash", gitHash) @@ -768,23 +771,6 @@ func (impl *AppServiceImpl) UpdatePipelineStatusTimelineForApplicationChanges(ap return isTimelineUpdated, isTimelineTimedOut, kubectlApplySyncedTimeline, nil } -func (impl *AppServiceImpl) WriteCDSuccessEvent(appId int, envId int, override *chartConfig.PipelineOverride) { - event, _ := impl.eventFactory.Build(eventUtil.Success, &override.PipelineId, appId, &envId, eventUtil.CD) - impl.logger.Debugw("event WriteCDSuccessEvent", "event", event, "override", override) - event = impl.eventFactory.BuildExtraCDData(event, nil, override.Id, bean.CD_WORKFLOW_TYPE_DEPLOY) - _, evtErr := impl.eventClient.WriteNotificationEvent(event) - if evtErr != nil { - impl.logger.Errorw("error in writing event", "event", event, "err", evtErr) - } -} - -func (impl *AppServiceImpl) BuildCDSuccessPayload(appName string, environmentName string) *client.Payload { - payload := &client.Payload{} - payload.AppName = appName - payload.EnvName = environmentName - return payload -} - type ValuesOverrideResponse struct { MergedValues string ReleaseOverrideJSON string @@ -882,29 +868,6 @@ type PipelineMaterialInfo struct { CommitHash string } -func buildCDTriggerEvent(impl *AppServiceImpl, overrideRequest *bean.ValuesOverrideRequest, pipeline *pipelineConfig.Pipeline, - envOverride *chartConfig.EnvConfigOverride, materialInfo map[string]string, artifact *repository.CiArtifact) client.Event { - event, _ := impl.eventFactory.Build(eventUtil.Trigger, &pipeline.Id, pipeline.AppId, &pipeline.EnvironmentId, eventUtil.CD) - return event -} - -func (impl *AppServiceImpl) BuildPayload(overrideRequest *bean.ValuesOverrideRequest, pipeline *pipelineConfig.Pipeline, - envOverride *chartConfig.EnvConfigOverride, materialInfo map[string]string, artifact *repository.CiArtifact) *client.Payload { - payload := &client.Payload{} - payload.AppName = pipeline.App.AppName - payload.PipelineName = pipeline.Name - payload.EnvName = envOverride.Environment.Name - - var revision string - for _, v := range materialInfo { - revision = v - break - } - payload.Source = url.PathEscape(revision) - payload.DockerImageUrl = artifact.Image - return payload -} - type ReleaseAttributes struct { Name string Tag string diff --git a/pkg/app/DeploymentEventHandler.go b/pkg/app/DeploymentEventHandler.go deleted file mode 100644 index 564622486a..0000000000 --- a/pkg/app/DeploymentEventHandler.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2020-2024. Devtron Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package app - -import ( - argoApplication "github.com/devtron-labs/devtron/client/argocdServer/bean" - "strings" - "time" - - "github.com/devtron-labs/devtron/api/bean" - client "github.com/devtron-labs/devtron/client/events" - "github.com/devtron-labs/devtron/internal/sql/repository" - util "github.com/devtron-labs/devtron/util/event" - "go.uber.org/zap" -) - -// DeploymentEventHandler is not being used, TODO: find reason for creation and then remove -type DeploymentEventHandler interface { - WriteCDDeploymentEvent(pipelineId, appId, envId int, eventType util.EventType) -} - -type DeploymentEventHandlerImpl struct { - logger *zap.SugaredLogger - appListingService AppListingService - eventFactory client.EventFactory - eventClient client.EventClient -} - -func NewDeploymentEventHandlerImpl(logger *zap.SugaredLogger, appListingService AppListingService, eventClient client.EventClient, eventFactory client.EventFactory) *DeploymentEventHandlerImpl { - deploymentEventHandlerImpl := &DeploymentEventHandlerImpl{ - logger: logger, - appListingService: appListingService, - eventClient: eventClient, - eventFactory: eventFactory, - } - return deploymentEventHandlerImpl -} - -func (impl *DeploymentEventHandlerImpl) WriteCDDeploymentEvent(pipelineId, appId, envId int, eventType util.EventType) { - event, _ := impl.eventFactory.Build(eventType, &pipelineId, appId, &envId, util.CD) - impl.logger.Debugw("event WriteCDDeploymentEvent", "event", event) - event = impl.eventFactory.BuildExtraCDData(event, nil, 0, bean.CD_WORKFLOW_TYPE_DEPLOY) - _, evtErr := impl.eventClient.WriteNotificationEvent(event) - if evtErr != nil { - impl.logger.Errorw("error in writing event", "err", evtErr) - } -} - -func (impl *DeploymentEventHandlerImpl) BuildPayload(appName string, deploymentFailureTime time.Time) *client.Payload { - applicationName := appName[:strings.LastIndex(appName, "-")] - evnName := appName[strings.LastIndex(appName, "-")+1:] - payload := &client.Payload{} - payload.AppName = applicationName - payload.EnvName = evnName - //payload["deploymentFailureTime"] = deploymentFailureTime.Format(bean.LayoutRFC3339) - return payload -} - -func (impl *DeploymentEventHandlerImpl) isDeploymentFailed(ds repository.DeploymentStatus) bool { - return ds.Status == argoApplication.Degraded && time.Since(ds.UpdatedOn) > 5*time.Minute -} diff --git a/pkg/app/deploymentEventHandler.go b/pkg/app/deploymentEventHandler.go new file mode 100644 index 0000000000..c4ebe9db8f --- /dev/null +++ b/pkg/app/deploymentEventHandler.go @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app + +import ( + "github.com/devtron-labs/common-lib/async" + "github.com/devtron-labs/devtron/api/bean" + client "github.com/devtron-labs/devtron/client/events" + "github.com/devtron-labs/devtron/internal/sql/repository/chartConfig" + util "github.com/devtron-labs/devtron/util/event" + "go.uber.org/zap" +) + +type DeploymentEventHandler interface { + WriteCDNotificationEventAsync(appId int, envId int, override *chartConfig.PipelineOverride, eventType util.EventType) +} + +type DeploymentEventHandlerImpl struct { + logger *zap.SugaredLogger + eventFactory client.EventFactory + eventClient client.EventClient + asyncRunnable *async.Runnable +} + +func NewDeploymentEventHandlerImpl(logger *zap.SugaredLogger, eventClient client.EventClient, eventFactory client.EventFactory, asyncRunnable *async.Runnable) *DeploymentEventHandlerImpl { + deploymentEventHandlerImpl := &DeploymentEventHandlerImpl{ + logger: logger, + eventClient: eventClient, + eventFactory: eventFactory, + asyncRunnable: asyncRunnable, + } + return deploymentEventHandlerImpl +} + +func (impl *DeploymentEventHandlerImpl) writeCDNotificationEvent(appId int, envId int, override *chartConfig.PipelineOverride, eventType util.EventType) { + event, _ := impl.eventFactory.Build(eventType, &override.PipelineId, appId, &envId, util.CD) + impl.logger.Debugw("event WriteCDNotificationEvent", "event", event, "override", override) + event = impl.eventFactory.BuildExtraCDData(event, nil, override.Id, bean.CD_WORKFLOW_TYPE_DEPLOY) + _, evtErr := impl.eventClient.WriteNotificationEvent(event) + if evtErr != nil { + impl.logger.Errorw("error in writing event", "event", event, "err", evtErr) + } +} + +// WriteCDNotificationEventAsync executes WriteCDNotificationEvent in a panic-safe goroutine +func (impl *DeploymentEventHandlerImpl) WriteCDNotificationEventAsync(appId int, envId int, override *chartConfig.PipelineOverride, eventType util.EventType) { + impl.asyncRunnable.Execute(func() { + impl.writeCDNotificationEvent(appId, envId, override, eventType) + }) +} diff --git a/pkg/appStore/installedApp/read/InstalledAppReadEAService.go b/pkg/appStore/installedApp/read/InstalledAppReadEAService.go index dedaca3e9e..77dd397889 100644 --- a/pkg/appStore/installedApp/read/InstalledAppReadEAService.go +++ b/pkg/appStore/installedApp/read/InstalledAppReadEAService.go @@ -36,6 +36,8 @@ type InstalledAppReadServiceEA interface { GetAllArgoAppNamesByDeploymentAppNames(deploymentAppNames []string) ([]string, error) // IsChartStoreAppManagedByArgoCd returns if a chart store app is deployed via argo-cd or not IsChartStoreAppManagedByArgoCd(appId int) (bool, error) + // GetActiveInstalledAppCount returns the count of all active installed apps + GetActiveInstalledAppCount() (int, error) } type InstalledAppReadServiceEAImpl struct { @@ -114,3 +116,12 @@ func (impl *InstalledAppReadServiceEAImpl) IsChartStoreAppManagedByArgoCd(appId } return util.IsAcdApp(installedAppModel.DeploymentAppType), nil } + +func (impl *InstalledAppReadServiceEAImpl) GetActiveInstalledAppCount() (int, error) { + count, err := impl.installedAppRepository.GetActiveInstalledAppCount() + if err != nil { + impl.logger.Errorw("error while getting active installed app count", "error", err) + return 0, err + } + return count, nil +} diff --git a/pkg/appStore/installedApp/repository/InstalledAppRepository.go b/pkg/appStore/installedApp/repository/InstalledAppRepository.go index 5849972249..57d0a3f9ae 100644 --- a/pkg/appStore/installedApp/repository/InstalledAppRepository.go +++ b/pkg/appStore/installedApp/repository/InstalledAppRepository.go @@ -170,6 +170,8 @@ type InstalledAppRepository interface { GetInstalledAppsMinByAppId(appId int) (*InstalledApps, error) GetAllArgoAppsByDeploymentAppNames(deploymentAppNames []string) ([]string, error) GetAllAppsByClusterAndDeploymentAppType(clusterIds []int, deploymentAppType string) ([]bean.DeployedInstalledAppInfo, error) + // GetActiveInstalledAppCount returns the count of all active installed apps + GetActiveInstalledAppCount() (int, error) } type InstalledAppRepositoryImpl struct { @@ -1085,3 +1087,16 @@ func (impl *InstalledAppRepositoryImpl) GetAllArgoAppsByDeploymentAppNames(deplo Select(&result) return result, err } + +func (impl *InstalledAppRepositoryImpl) GetActiveInstalledAppCount() (int, error) { + var count int + count, err := impl.dbConnection.Model(). + Table("installed_apps"). + Where("active = ?", true). + Count() + if err != nil { + impl.Logger.Errorw("error in getting active installed app count", "err", err) + return 0, err + } + return count, nil +} diff --git a/pkg/appWorkflow/AppWorkflowService.go b/pkg/appWorkflow/AppWorkflowService.go index 7cd00c8147..66fcf67a39 100644 --- a/pkg/appWorkflow/AppWorkflowService.go +++ b/pkg/appWorkflow/AppWorkflowService.go @@ -270,20 +270,11 @@ func (impl AppWorkflowServiceImpl) DeleteAppWorkflow(appWorkflowId int, userId i defer tx.Rollback() // Deleting workflow - err = impl.appWorkflowRepository.DeleteAppWorkflow(wf, tx) + err = impl.appWorkflowRepository.DeleteAppWorkflowAndAllMappings(wf, tx) if err != nil { impl.Logger.Errorw("err", err) return err } - // Delete app workflow mapping - mapping, err := impl.appWorkflowRepository.FindWFAllMappingByWorkflowId(wf.Id) - for _, item := range mapping { - err := impl.appWorkflowRepository.DeleteAppWorkflowMapping(item, tx) - if err != nil { - impl.Logger.Errorw("error in deleting workflow mapping", "err", err) - return err - } - } err = impl.userAuthService.DeleteRoles(bean3.WorkflowType, app.AppName, tx, "", wf.Name) if err != nil { impl.Logger.Errorw("error in deleting auth roles", "err", err) diff --git a/pkg/argoApplication/ArgoApplicationServiceExtended.go b/pkg/argoApplication/ArgoApplicationServiceExtended.go index 0cfb81b2d3..0fce636655 100644 --- a/pkg/argoApplication/ArgoApplicationServiceExtended.go +++ b/pkg/argoApplication/ArgoApplicationServiceExtended.go @@ -22,6 +22,7 @@ import ( "fmt" application2 "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/devtron-labs/common-lib/async" "github.com/devtron-labs/common-lib/utils/k8s" openapi "github.com/devtron-labs/devtron/api/helm-app/openapiClient" "github.com/devtron-labs/devtron/client/argocdServer" @@ -46,6 +47,7 @@ type ArgoApplicationServiceExtendedImpl struct { argoApplicationReadService read.ArgoApplicationReadService clusterService cluster.ClusterService acdClientWrapper argocdServer.ArgoClientWrapperService + asyncRunnable *async.Runnable } func NewArgoApplicationServiceExtendedServiceImpl( @@ -54,6 +56,7 @@ func NewArgoApplicationServiceExtendedServiceImpl( acdClientWrapper argocdServer.ArgoClientWrapperService, argoApplicationReadService read.ArgoApplicationReadService, clusterService cluster.ClusterService, + asyncRunnable *async.Runnable, ) *ArgoApplicationServiceExtendedImpl { return &ArgoApplicationServiceExtendedImpl{ aCDAuthConfig: aCDAuthConfig, @@ -61,6 +64,7 @@ func NewArgoApplicationServiceExtendedServiceImpl( argoApplicationReadService: argoApplicationReadService, acdClientWrapper: acdClientWrapper, ArgoApplicationServiceImpl: argoApplicationServiceImpl, + asyncRunnable: asyncRunnable, } } @@ -328,7 +332,7 @@ func (c *ArgoApplicationServiceExtendedImpl) parseResult(resp *v1alpha1.Applicat for _, node := range queryNodes { rQuery := transform(node, query.ApplicationName) qCount++ - go func(request application2.ApplicationResourceRequest) { + runnableFunc := func(request application2.ApplicationResourceRequest) { ctx, cancel := context.WithTimeout(ctx, 60*time.Second) defer cancel() startTime := time.Now() @@ -343,7 +347,8 @@ func (c *ArgoApplicationServiceExtendedImpl) parseResult(resp *v1alpha1.Applicat } else { response <- argoApplication.Result{Response: nil, Error: fmt.Errorf("connection closed by client"), Request: &request} } - }(*rQuery) + } + c.asyncRunnable.Execute(func() { runnableFunc(*rQuery) }) } if qCount == 0 { diff --git a/pkg/argoApplication/read/config/ArgoApplicationConfigService.go b/pkg/argoApplication/read/config/ArgoApplicationConfigService.go index 0c5a8bf819..ed64ba6ecf 100644 --- a/pkg/argoApplication/read/config/ArgoApplicationConfigService.go +++ b/pkg/argoApplication/read/config/ArgoApplicationConfigService.go @@ -23,8 +23,8 @@ type ArgoApplicationConfigServiceImpl struct { } type ArgoApplicationConfigService interface { - GetRestConfigForExternalArgo(ctx context.Context, clusterId int, externalArgoApplicationName string) (*rest.Config, error) - GetClusterConfigFromAllClusters(clusterId int) (*k8s.ClusterConfig, clusterRepository.Cluster, map[string]int, error) + GetRestConfigForExternalArgo(ctx context.Context, externalArgoAppIdentifier *bean.ArgoAppIdentifier) (*rest.Config, error) + GetClusterConfigFromAllClusters(clusterId int) (*k8s.ClusterConfig, clusterRepository.Cluster, map[string]*k8s.ClusterConfig, error) } func NewArgoApplicationConfigServiceImpl(logger *zap.SugaredLogger, @@ -37,19 +37,20 @@ func NewArgoApplicationConfigServiceImpl(logger *zap.SugaredLogger, } } -func (impl *ArgoApplicationConfigServiceImpl) GetClusterConfigFromAllClusters(clusterId int) (*k8s.ClusterConfig, clusterRepository.Cluster, map[string]int, error) { +func (impl *ArgoApplicationConfigServiceImpl) GetClusterConfigFromAllClusters(clusterId int) (*k8s.ClusterConfig, clusterRepository.Cluster, map[string]*k8s.ClusterConfig, error) { clusters, err := impl.clusterRepository.FindAllActive() var clusterWithApplicationObject clusterRepository.Cluster if err != nil { impl.logger.Errorw("error in getting all active clusters", "err", err) return nil, clusterWithApplicationObject, nil, err } - clusterServerUrlIdMap := make(map[string]int, len(clusters)) + clusterServerUrlIdMap := make(map[string]*k8s.ClusterConfig, len(clusters)) for _, cluster := range clusters { if cluster.Id == clusterId { clusterWithApplicationObject = cluster } - clusterServerUrlIdMap[cluster.ServerUrl] = cluster.Id + clusterBean := adapter.GetClusterBean(cluster) + clusterServerUrlIdMap[cluster.ServerUrl] = clusterBean.GetClusterConfig() } if len(clusterWithApplicationObject.ErrorInConnecting) != 0 { return nil, clusterWithApplicationObject, nil, fmt.Errorf("error in connecting to cluster") @@ -59,20 +60,20 @@ func (impl *ArgoApplicationConfigServiceImpl) GetClusterConfigFromAllClusters(cl return clusterConfig, clusterWithApplicationObject, clusterServerUrlIdMap, err } -func (impl *ArgoApplicationConfigServiceImpl) GetRestConfigForExternalArgo(ctx context.Context, clusterId int, externalArgoApplicationName string) (*rest.Config, error) { - clusterConfig, clusterWithApplicationObject, clusterServerUrlIdMap, err := impl.GetClusterConfigFromAllClusters(clusterId) +func (impl *ArgoApplicationConfigServiceImpl) GetRestConfigForExternalArgo(ctx context.Context, externalArgoAppIdentifier *bean.ArgoAppIdentifier) (*rest.Config, error) { + clusterConfig, clusterWithApplicationObject, clusterServerUrlIdMap, err := impl.GetClusterConfigFromAllClusters(externalArgoAppIdentifier.ClusterId) if err != nil { - impl.logger.Errorw("error in getting cluster config", "err", err, "clusterId", clusterId) + impl.logger.Errorw("error in getting cluster config", "err", err, "clusterId", externalArgoAppIdentifier.ClusterId) return nil, err } restConfig, err := impl.k8sUtil.GetRestConfigByCluster(clusterConfig) if err != nil { - impl.logger.Errorw("error in getting rest config", "err", err, "clusterId", clusterId) + impl.logger.Errorw("error in getting rest config", "err", err, "clusterId", externalArgoAppIdentifier.ClusterId) return nil, err } - resourceResp, err := impl.k8sUtil.GetResource(ctx, bean.DevtronCDNamespae, externalArgoApplicationName, bean.GvkForArgoApplication, restConfig) + resourceResp, err := impl.k8sUtil.GetResource(ctx, externalArgoAppIdentifier.Namespace, externalArgoAppIdentifier.AppName, bean.GvkForArgoApplication, restConfig) if err != nil { - impl.logger.Errorw("not on external cluster", "err", err, "externalArgoApplicationName", externalArgoApplicationName) + impl.logger.Errorw("not on external cluster", "err", err, "externalArgoApplicationName", externalArgoAppIdentifier.AppName, "externalArgoApplicationNamespace", externalArgoAppIdentifier.Namespace) return nil, err } restConfig, err = impl.GetServerConfigIfClusterIsNotAddedOnDevtron(resourceResp, restConfig, clusterWithApplicationObject, clusterServerUrlIdMap) @@ -84,59 +85,63 @@ func (impl *ArgoApplicationConfigServiceImpl) GetRestConfigForExternalArgo(ctx c } func (impl *ArgoApplicationConfigServiceImpl) GetServerConfigIfClusterIsNotAddedOnDevtron(resourceResp *k8s.ManifestResponse, restConfig *rest.Config, - clusterWithApplicationObject clusterRepository.Cluster, clusterServerUrlIdMap map[string]int) (*rest.Config, error) { + clusterWithApplicationObject clusterRepository.Cluster, clusterServerUrlIdMap map[string]*k8s.ClusterConfig) (*rest.Config, error) { var destinationServer string if resourceResp != nil && resourceResp.Manifest.Object != nil { _, _, destinationServer, _ = helper.GetHealthSyncStatusDestinationServerAndManagedResourcesForArgoK8sRawObject(resourceResp.Manifest.Object) } - appDeployedOnClusterId := 0 if destinationServer == k8sCommonBean.DefaultClusterUrl { - appDeployedOnClusterId = clusterWithApplicationObject.Id - } else if clusterIdFromMap, ok := clusterServerUrlIdMap[destinationServer]; ok { - appDeployedOnClusterId = clusterIdFromMap - } - var configOfClusterWhereAppIsDeployed *bean.ArgoClusterConfigObj - if appDeployedOnClusterId < 1 { - // cluster is not added on devtron, need to get server config from secret which argo-cd saved - coreV1Client, err := impl.k8sUtil.GetCoreV1ClientByRestConfig(restConfig) - secrets, err := coreV1Client.Secrets(bean.AllNamespaces).List(context.Background(), v1.ListOptions{ - LabelSelector: labels.SelectorFromSet(labels.Set{"argocd.argoproj.io/secret-type": "cluster"}).String(), - }) + return restConfig, nil + } else if clusterConfig, ok := clusterServerUrlIdMap[destinationServer]; ok { + restConfig, err := impl.k8sUtil.GetRestConfigByCluster(clusterConfig) if err != nil { - impl.logger.Errorw("error in getting resource list, secrets", "err", err) + impl.logger.Errorw("error in GetRestConfigByCluster, GetServerConfigIfClusterIsNotAddedOnDevtron", "err", err, "serverUrl", destinationServer) return nil, err } - for _, secret := range secrets.Items { - if secret.Data != nil { - if val, ok := secret.Data[bean.Server]; ok { - if string(val) == destinationServer { - if config, ok := secret.Data[bean.Config]; ok { - err = json.Unmarshal(config, &configOfClusterWhereAppIsDeployed) - if err != nil { - impl.logger.Errorw("error in unmarshaling", "err", err) - return nil, err - } - break + return restConfig, nil + } + + var configOfClusterWhereAppIsDeployed *bean.ArgoClusterConfigObj + // cluster is not added on devtron, need to get server config from secret which argo-cd saved + coreV1Client, err := impl.k8sUtil.GetCoreV1ClientByRestConfig(restConfig) + secrets, err := coreV1Client.Secrets(bean.AllNamespaces).List(context.Background(), v1.ListOptions{ + LabelSelector: labels.SelectorFromSet(labels.Set{"argocd.argoproj.io/secret-type": "cluster"}).String(), + }) + if err != nil { + impl.logger.Errorw("error in getting resource list, secrets", "err", err) + return nil, err + } + for _, secret := range secrets.Items { + if secret.Data != nil { + if val, ok := secret.Data[bean.Server]; ok { + if string(val) == destinationServer { + if config, ok := secret.Data[bean.Config]; ok { + err = json.Unmarshal(config, &configOfClusterWhereAppIsDeployed) + if err != nil { + impl.logger.Errorw("error in unmarshaling", "err", err) + return nil, err } + break } } } } - if configOfClusterWhereAppIsDeployed != nil { - restConfig, err = impl.k8sUtil.GetRestConfigByCluster(&k8s.ClusterConfig{ - Host: destinationServer, - BearerToken: configOfClusterWhereAppIsDeployed.BearerToken, - InsecureSkipTLSVerify: configOfClusterWhereAppIsDeployed.TlsClientConfig.Insecure, - KeyData: configOfClusterWhereAppIsDeployed.TlsClientConfig.KeyData, - CAData: configOfClusterWhereAppIsDeployed.TlsClientConfig.CaData, - CertData: configOfClusterWhereAppIsDeployed.TlsClientConfig.CertData, - }) - if err != nil { - impl.logger.Errorw("error in GetRestConfigByCluster, GetServerConfigIfClusterIsNotAddedOnDevtron", "err", err, "serverUrl", destinationServer) - return nil, err - } + } + if configOfClusterWhereAppIsDeployed != nil { + restConfig, err = impl.k8sUtil.GetRestConfigByCluster(&k8s.ClusterConfig{ + Host: destinationServer, + BearerToken: configOfClusterWhereAppIsDeployed.BearerToken, + InsecureSkipTLSVerify: configOfClusterWhereAppIsDeployed.TlsClientConfig.Insecure, + KeyData: configOfClusterWhereAppIsDeployed.TlsClientConfig.KeyData, + CAData: configOfClusterWhereAppIsDeployed.TlsClientConfig.CaData, + CertData: configOfClusterWhereAppIsDeployed.TlsClientConfig.CertData, + }) + if err != nil { + impl.logger.Errorw("error in GetRestConfigByCluster, GetServerConfigIfClusterIsNotAddedOnDevtron", "err", err, "serverUrl", destinationServer) + return nil, err } } + return restConfig, nil } diff --git a/pkg/auth/user/UserAuditService.go b/pkg/auth/user/UserAuditService.go index 38a8e78b6f..d53b4f888a 100644 --- a/pkg/auth/user/UserAuditService.go +++ b/pkg/auth/user/UserAuditService.go @@ -36,6 +36,7 @@ type UserAuditService interface { GetLatestByUserId(userId int32) (*UserAudit, error) GetLatestUser() (*UserAudit, error) Update(userAudit *UserAudit) error + GetActiveUsersCountInLast30Days() (int, error) } type UserAuditServiceImpl struct { @@ -123,3 +124,13 @@ func (impl UserAuditServiceImpl) GetLatestUser() (*UserAudit, error) { UpdatedOn: userAuditDb.UpdatedOn, }, nil } + +func (impl UserAuditServiceImpl) GetActiveUsersCountInLast30Days() (int, error) { + impl.logger.Info("Getting active users count in last 30 days") + count, err := impl.userAuditRepository.GetActiveUsersCountInLast30Days() + if err != nil { + impl.logger.Errorw("error while getting active users count in last 30 days", "err", err) + return 0, err + } + return count, nil +} diff --git a/pkg/auth/user/UserSelfRegistrationService.go b/pkg/auth/user/UserSelfRegistrationService.go index 671ed723ab..7a13b105e2 100644 --- a/pkg/auth/user/UserSelfRegistrationService.go +++ b/pkg/auth/user/UserSelfRegistrationService.go @@ -126,20 +126,15 @@ func (impl *UserSelfRegistrationServiceImpl) CheckAndCreateUserIfConfigured(clai emailId = sub } exists := impl.userService.UserExists(emailId) - var id int32 if !exists { impl.logger.Infow("self registering user, ", "email", emailId) user, err := impl.SelfRegister(emailId) if err != nil { impl.logger.Errorw("error while register user", "error", err) } else if user != nil && user.Id > 0 { - id = user.Id exists = true } } - if exists { - impl.userService.SaveLoginAudit(emailId, "localhost", id) - } impl.logger.Infow("user status", "email", emailId, "status", exists) return exists } diff --git a/pkg/auth/user/UserService.go b/pkg/auth/user/UserService.go index 04a56374dc..aff943faa0 100644 --- a/pkg/auth/user/UserService.go +++ b/pkg/auth/user/UserService.go @@ -86,6 +86,7 @@ type UserService interface { GetRoleFiltersByUserRoleGroups(userRoleGroups []userBean.UserRoleGroup) ([]userBean.RoleFilter, error) SaveLoginAudit(emailId, clientIp string, id int32) CheckIfTokenIsValid(email string, version string) error + CheckUserStatusAndUpdateLoginAudit(token string) (bool, int32, string, error) } type UserServiceImpl struct { @@ -1826,3 +1827,34 @@ func (impl *UserServiceImpl) GetUserBasicDataByEmailId(emailId string) (*userBea } return response, nil } + +func (impl *UserServiceImpl) CheckUserStatusAndUpdateLoginAudit(token string) (bool, int32, string, error) { + emailId, version, err := impl.GetEmailAndVersionFromToken(token) + if err != nil { + impl.logger.Error("unable to fetch user by token") + err = &util.ApiError{HttpStatusCode: 401, UserMessage: "Invalid User", InternalMessage: "unable to fetch user by token"} + return false, 0, "", err + } + userId, isInactive, err := impl.getUserWithTimeoutWindowConfiguration(emailId) + if err != nil { + err = &util.ApiError{HttpStatusCode: 401, UserMessage: "Invalid User", InternalMessage: err.Error()} + impl.logger.Errorw("unable to fetch user by email, %s", token) + return isInactive, userId, "", err + } + //if user is inactive, no need to store audit log + if !isInactive { + impl.SaveLoginAudit(emailId, "localhost", userId) + } + // checking length of version, to ensure backward compatibility as earlier we did not + // have version for api-tokens + // therefore, for tokens without version we will skip the below part + if strings.HasPrefix(emailId, userBean.API_TOKEN_USER_EMAIL_PREFIX) && len(version) > 0 { + err := impl.CheckIfTokenIsValid(emailId, version) + if err != nil { + impl.logger.Errorw("token is not valid", "error", err, "token", token) + return isInactive, userId, "", err + } + } + + return isInactive, userId, emailId, nil +} diff --git a/pkg/auth/user/UserService_ent.go b/pkg/auth/user/UserService_ent.go index 39e94f450c..efb0a333cb 100644 --- a/pkg/auth/user/UserService_ent.go +++ b/pkg/auth/user/UserService_ent.go @@ -8,6 +8,7 @@ import ( userBean "github.com/devtron-labs/devtron/pkg/auth/user/bean" userrepo "github.com/devtron-labs/devtron/pkg/auth/user/repository" "github.com/go-pg/pg" + "github.com/juju/errors" "strings" ) @@ -204,3 +205,14 @@ func (impl *UserServiceImpl) checkValidationAndPerformOperationsForUpdate(token } return false, isUserSuperAdmin, nil } + +func (impl *UserServiceImpl) getUserWithTimeoutWindowConfiguration(emailId string) (int32, bool, error) { + user, err := impl.userRepository.FetchActiveUserByEmail(emailId) + if err != nil { + impl.logger.Errorw("error while fetching user from db", "error", err) + errMsg := fmt.Sprintf("failed to fetch user by email id, err: %s", err.Error()) + return 0, false, errors.New(errMsg) + } + // here false is always returned to match signature of authoriser function. + return user.Id, false, nil +} diff --git a/pkg/auth/user/repository/UserAuditRepository.go b/pkg/auth/user/repository/UserAuditRepository.go index 67e8f4b654..33d73d3497 100644 --- a/pkg/auth/user/repository/UserAuditRepository.go +++ b/pkg/auth/user/repository/UserAuditRepository.go @@ -39,6 +39,7 @@ type UserAuditRepository interface { GetLatestByUserId(userId int32) (*UserAudit, error) GetLatestUser() (*UserAudit, error) Update(userAudit *UserAudit) error + GetActiveUsersCountInLast30Days() (int, error) } type UserAuditRepositoryImpl struct { @@ -86,3 +87,16 @@ func (impl UserAuditRepositoryImpl) GetLatestUser() (*UserAudit, error) { Select() return userAudit, err } + +func (impl UserAuditRepositoryImpl) GetActiveUsersCountInLast30Days() (int, error) { + // Calculated the date 30 days ago from now + thirtyDaysAgo := time.Now().AddDate(0, 0, -30) + + count, err := impl.dbConnection.Model(). + Table("user_audit"). + ColumnExpr("COUNT(DISTINCT user_id)"). + Where("updated_on >= ?", thirtyDaysAgo). + Count() + + return count, err +} diff --git a/pkg/build/trigger/HandlerService.go b/pkg/build/trigger/HandlerService.go index 413edd3964..faf1d54647 100644 --- a/pkg/build/trigger/HandlerService.go +++ b/pkg/build/trigger/HandlerService.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" "github.com/caarlos0/env" + "github.com/devtron-labs/common-lib/async" blob_storage "github.com/devtron-labs/common-lib/blob-storage" "github.com/devtron-labs/common-lib/utils" bean4 "github.com/devtron-labs/common-lib/utils/bean" @@ -78,7 +79,7 @@ type HandlerService interface { StartCiWorkflowAndPrepareWfRequest(trigger types.Trigger) (*pipelineConfig.CiPipeline, map[string]string, *pipelineConfig.CiWorkflow, *types.WorkflowRequest, error) CancelBuild(workflowId int, forceAbort bool) (int, error) - GetRunningWorkflowLogs(workflowId int) (*bufio.Reader, func() error, error) + GetRunningWorkflowLogs(workflowId int, followLogs bool) (*bufio.Reader, func() error, error) GetHistoricBuildLogs(workflowId int, ciWorkflow *pipelineConfig.CiWorkflow) (map[string]string, error) DownloadCiWorkflowArtifacts(pipelineId int, buildId int) (*os.File, error) } @@ -116,6 +117,7 @@ type HandlerServiceImpl struct { clusterService cluster.ClusterService envService environment.EnvironmentService K8sUtil *k8s.K8sServiceImpl + asyncRunnable *async.Runnable } func NewHandlerServiceImpl(Logger *zap.SugaredLogger, workflowService executor.WorkflowService, @@ -141,6 +143,7 @@ func NewHandlerServiceImpl(Logger *zap.SugaredLogger, workflowService executor.W clusterService cluster.ClusterService, envService environment.EnvironmentService, K8sUtil *k8s.K8sServiceImpl, + asyncRunnable *async.Runnable, ) *HandlerServiceImpl { buildxCacheFlags := &BuildxCacheFlags{} err := env.Parse(buildxCacheFlags) @@ -174,6 +177,7 @@ func NewHandlerServiceImpl(Logger *zap.SugaredLogger, workflowService executor.W clusterService: clusterService, envService: envService, K8sUtil: K8sUtil, + asyncRunnable: asyncRunnable, } config, err := types.GetCiConfig() if err != nil { @@ -628,7 +632,12 @@ func (impl *HandlerServiceImpl) triggerCiPipeline(trigger types.Trigger) (int, e } middleware.CiTriggerCounter.WithLabelValues(pipeline.App.AppName, pipeline.Name).Inc() - go impl.ciService.WriteCITriggerEvent(trigger, pipeline, workflowRequest) + + runnableFunc := func() { + impl.ciService.WriteCITriggerEvent(trigger, pipeline, workflowRequest) + } + impl.asyncRunnable.Execute(runnableFunc) + return savedCiWf.Id, err } @@ -1685,16 +1694,16 @@ func (impl *HandlerServiceImpl) getRestConfig(workflow *pipelineConfig.CiWorkflo return restConfig, nil } -func (impl *HandlerServiceImpl) GetRunningWorkflowLogs(workflowId int) (*bufio.Reader, func() error, error) { +func (impl *HandlerServiceImpl) GetRunningWorkflowLogs(workflowId int, followLogs bool) (*bufio.Reader, func() error, error) { ciWorkflow, err := impl.ciWorkflowRepository.FindById(workflowId) if err != nil { impl.Logger.Errorw("err", "err", err) return nil, nil, err } - return impl.getWorkflowLogs(ciWorkflow) + return impl.getWorkflowLogs(ciWorkflow, followLogs) } -func (impl *HandlerServiceImpl) getWorkflowLogs(ciWorkflow *pipelineConfig.CiWorkflow) (*bufio.Reader, func() error, error) { +func (impl *HandlerServiceImpl) getWorkflowLogs(ciWorkflow *pipelineConfig.CiWorkflow, followLogs bool) (*bufio.Reader, func() error, error) { if string(v1alpha1.NodePending) == ciWorkflow.PodStatus { return bufio.NewReader(strings.NewReader("")), func() error { return nil }, nil } @@ -1717,7 +1726,7 @@ func (impl *HandlerServiceImpl) getWorkflowLogs(ciWorkflow *pipelineConfig.CiWor isExt = true } - logStream, cleanUp, err := impl.ciLogService.FetchRunningWorkflowLogs(ciLogRequest, clusterConfig, isExt) + logStream, cleanUp, err := impl.ciLogService.FetchRunningWorkflowLogs(ciLogRequest, clusterConfig, isExt, followLogs) if logStream == nil || err != nil { if !ciWorkflow.BlobStorageEnabled { return nil, nil, &util.ApiError{Code: "200", HttpStatusCode: 400, UserMessage: "logs-not-stored-in-repository"} diff --git a/pkg/cluster/ClusterService.go b/pkg/cluster/ClusterService.go index e0716586c0..5ddd499945 100644 --- a/pkg/cluster/ClusterService.go +++ b/pkg/cluster/ClusterService.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/devtron-labs/common-lib/async" informerBean "github.com/devtron-labs/common-lib/informer" "github.com/devtron-labs/common-lib/utils/k8s/commonBean" "github.com/devtron-labs/devtron/pkg/cluster/adapter" @@ -97,6 +98,7 @@ type ClusterServiceImpl struct { userRepository repository3.UserRepository roleGroupRepository repository3.RoleGroupRepository clusterReadService read.ClusterReadService + asyncRunnable *async.Runnable } func NewClusterServiceImpl(repository repository.ClusterRepository, logger *zap.SugaredLogger, @@ -105,7 +107,8 @@ func NewClusterServiceImpl(repository repository.ClusterRepository, logger *zap. roleGroupRepository repository3.RoleGroupRepository, envVariables *globalUtil.EnvironmentVariables, cronLogger *cronUtil.CronLoggerImpl, - clusterReadService read.ClusterReadService) (*ClusterServiceImpl, error) { + clusterReadService read.ClusterReadService, + asyncRunnable *async.Runnable) (*ClusterServiceImpl, error) { clusterService := &ClusterServiceImpl{ clusterRepository: repository, logger: logger, @@ -115,6 +118,7 @@ func NewClusterServiceImpl(repository repository.ClusterRepository, logger *zap. userRepository: userRepository, roleGroupRepository: roleGroupRepository, clusterReadService: clusterReadService, + asyncRunnable: asyncRunnable, } // initialise cron newCron := cron.New(cron.WithChain(cron.Recover(cronLogger))) @@ -767,13 +771,14 @@ func (impl *ClusterServiceImpl) updateConnectionStatusForVirtualCluster(respMap func (impl *ClusterServiceImpl) ConnectClustersInBatch(clusters []*bean.ClusterBean, clusterExistInDb bool) { var wg sync.WaitGroup respMap := &sync.Map{} - for idx, cluster := range clusters { + for idx := range clusters { + cluster := clusters[idx] if cluster.IsVirtualCluster { impl.updateConnectionStatusForVirtualCluster(respMap, cluster.Id, cluster.ClusterName) continue } wg.Add(1) - go func(idx int, cluster *bean.ClusterBean) { + runnableFunc := func(idx int, cluster *bean.ClusterBean) { defer wg.Done() clusterConfig := cluster.GetClusterConfig() _, _, k8sClientSet, err := impl.K8sUtil.GetK8sConfigAndClients(clusterConfig) @@ -787,7 +792,8 @@ func (impl *ClusterServiceImpl) ConnectClustersInBatch(clusters []*bean.ClusterB id = idx } impl.GetAndUpdateConnectionStatusForOneCluster(k8sClientSet, id, respMap) - }(idx, cluster) + } + impl.asyncRunnable.Execute(func() { runnableFunc(idx, cluster) }) } wg.Wait() diff --git a/pkg/cluster/environment/bean/ephemeralContainerBean.go b/pkg/cluster/environment/bean/ephemeralContainerBean.go index 577c2dac1c..5db8b7a25b 100644 --- a/pkg/cluster/environment/bean/ephemeralContainerBean.go +++ b/pkg/cluster/environment/bean/ephemeralContainerBean.go @@ -1,15 +1,20 @@ package bean -import "github.com/devtron-labs/devtron/pkg/cluster/repository" +import ( + "github.com/devtron-labs/devtron/pkg/argoApplication/bean" + "github.com/devtron-labs/devtron/pkg/cluster/repository" +) type EphemeralContainerRequest struct { - BasicData *EphemeralContainerBasicData `json:"basicData"` - AdvancedData *EphemeralContainerAdvancedData `json:"advancedData"` - Namespace string `json:"namespace" validate:"required"` - ClusterId int `json:"clusterId" validate:"gt=0"` - PodName string `json:"podName" validate:"required"` - ExternalArgoApplicationName string `json:"externalArgoApplicationName,omitempty"` - UserId int32 `json:"-"` + BasicData *EphemeralContainerBasicData `json:"basicData"` + AdvancedData *EphemeralContainerAdvancedData `json:"advancedData"` + Namespace string `json:"namespace" validate:"required"` + ClusterId int `json:"clusterId" validate:"gt=0"` + PodName string `json:"podName" validate:"required"` + ExternalArgoApplicationName string `json:"externalArgoApplicationName,omitempty"` + ExternalArgoApplicationNamespace string `json:"externalArgoApplicationNamespace,omitempty"` + ExternalArgoAppIdentifier *bean.ArgoAppIdentifier `json:"externalArgoAppIdentifier"` + UserId int32 `json:"-"` } type EphemeralContainerAdvancedData struct { diff --git a/pkg/clusterTerminalAccess/UserTerminalAccessService.go b/pkg/clusterTerminalAccess/UserTerminalAccessService.go index b6ab4a6b09..299cd30b6f 100644 --- a/pkg/clusterTerminalAccess/UserTerminalAccessService.go +++ b/pkg/clusterTerminalAccess/UserTerminalAccessService.go @@ -21,7 +21,13 @@ import ( "encoding/json" "errors" "fmt" + "strconv" + "strings" + "sync" + "time" + "github.com/caarlos0/env/v6" + "github.com/devtron-labs/common-lib/async" k8s2 "github.com/devtron-labs/common-lib/utils/k8s" "github.com/devtron-labs/devtron/api/helm-app/service/bean" "github.com/devtron-labs/devtron/internal/sql/models" @@ -43,10 +49,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "strconv" - "strings" - "sync" - "time" ) type UserTerminalAccessService interface { @@ -75,6 +77,7 @@ type UserTerminalAccessServiceImpl struct { terminalSessionHandler terminal.TerminalSessionHandler K8sCapacityService capacity.K8sCapacityService k8sUtil *k8s2.K8sServiceImpl + asyncRunnable *async.Runnable } type UserTerminalAccessSessionData struct { @@ -98,7 +101,12 @@ func GetTerminalAccessConfig() (*models.UserTerminalSessionConfig, error) { return config, err } -func NewUserTerminalAccessServiceImpl(logger *zap.SugaredLogger, terminalAccessRepository repository.TerminalAccessRepository, config *models.UserTerminalSessionConfig, k8sCommonService k8s.K8sCommonService, terminalSessionHandler terminal.TerminalSessionHandler, K8sCapacityService capacity.K8sCapacityService, k8sUtil *k8s2.K8sServiceImpl, cronLogger *cron3.CronLoggerImpl) (*UserTerminalAccessServiceImpl, error) { +func NewUserTerminalAccessServiceImpl(logger *zap.SugaredLogger, + terminalAccessRepository repository.TerminalAccessRepository, + config *models.UserTerminalSessionConfig, k8sCommonService k8s.K8sCommonService, + terminalSessionHandler terminal.TerminalSessionHandler, + K8sCapacityService capacity.K8sCapacityService, k8sUtil *k8s2.K8sServiceImpl, + cronLogger *cron3.CronLoggerImpl, asyncRunnable *async.Runnable) (*UserTerminalAccessServiceImpl, error) { //fetches all running and starting entities from db and start SyncStatus podStatusSyncCron := cron.New(cron.WithChain(cron.Recover(cronLogger))) terminalAccessDataArrayMutex := &sync.RWMutex{} @@ -114,6 +122,7 @@ func NewUserTerminalAccessServiceImpl(logger *zap.SugaredLogger, terminalAccessR terminalSessionHandler: terminalSessionHandler, K8sCapacityService: K8sCapacityService, k8sUtil: k8sUtil, + asyncRunnable: asyncRunnable, } podStatusSyncCron.Start() _, err := podStatusSyncCron.AddFunc(fmt.Sprintf("@every %ds", config.TerminalPodStatusSyncTimeInSecs), accessServiceImpl.SyncPodStatus) @@ -121,7 +130,7 @@ func NewUserTerminalAccessServiceImpl(logger *zap.SugaredLogger, terminalAccessR logger.Errorw("error occurred while starting cron job", "time in secs", config.TerminalPodStatusSyncTimeInSecs) return nil, err } - go accessServiceImpl.SyncRunningInstances() + accessServiceImpl.asyncRunnable.Execute(func() { accessServiceImpl.SyncRunningInstances() }) return accessServiceImpl, err } func (impl *UserTerminalAccessServiceImpl) ValidateShell(podName, namespace, shellName, containerName string, clusterId int) (bool, string, error) { diff --git a/pkg/deployment/common/deploymentConfigService.go b/pkg/deployment/common/deploymentConfigService.go index 1e648c4b5c..a35150498d 100644 --- a/pkg/deployment/common/deploymentConfigService.go +++ b/pkg/deployment/common/deploymentConfigService.go @@ -114,7 +114,7 @@ func (impl *DeploymentConfigServiceImpl) CreateOrUpdateConfig(tx *pg.Tx, config return nil, err } - configDbObj, err := impl.GetConfigDBObj(config.AppId, config.EnvironmentId) + configDbObj, err := impl.GetConfigDBObj(tx, config.AppId, config.EnvironmentId) if err != nil && !errors.Is(err, pg.ErrNoRows) { impl.logger.Errorw("error in fetching deployment config from DB by appId and envId", "appId", config.AppId, "envId", config.EnvironmentId, "err", err) @@ -275,7 +275,7 @@ func (impl *DeploymentConfigServiceImpl) GetAndMigrateConfigIfAbsentForHelmApp(a func (impl *DeploymentConfigServiceImpl) UpdateRepoUrlForAppAndEnvId(repoURL string, appId, envId int) error { - dbObj, err := impl.deploymentConfigRepository.GetByAppIdAndEnvId(appId, envId) + dbObj, err := impl.deploymentConfigRepository.GetByAppIdAndEnvId(nil, appId, envId) if err != nil { impl.logger.Errorw("error in getting deployment config by appId", "appId", appId, "envId", envId, "err", err) return err @@ -445,7 +445,7 @@ func (impl *DeploymentConfigServiceImpl) getConfigForHelmApps(appId int, envId i helmDeploymentConfig *bean.DeploymentConfig isMigrationNeeded bool ) - config, err := impl.deploymentConfigRepository.GetByAppIdAndEnvId(appId, envId) + config, err := impl.deploymentConfigRepository.GetByAppIdAndEnvId(nil, appId, envId) if err != nil && !errors.Is(err, pg.ErrNoRows) { impl.logger.Errorw("error in fetching deployment config by by appId and envId", "appId", appId, "envId", envId, "err", err) return nil, err @@ -596,18 +596,17 @@ func (impl *DeploymentConfigServiceImpl) getAllEnvLevelConfigsForLinkedReleases( } return configs, nil } - -func (impl *DeploymentConfigServiceImpl) GetConfigDBObj(appId, envId int) (*deploymentConfig.DeploymentConfig, error) { +func (impl *DeploymentConfigServiceImpl) GetConfigDBObj(tx *pg.Tx, appId, envId int) (*deploymentConfig.DeploymentConfig, error) { var configDbObj *deploymentConfig.DeploymentConfig var err error if envId == 0 { - configDbObj, err = impl.deploymentConfigRepository.GetAppLevelConfigForDevtronApps(appId) + configDbObj, err = impl.deploymentConfigRepository.GetAppLevelConfigForDevtronApps(tx, appId) if err != nil { impl.logger.Errorw("error in getting deployment config db object by appId", "appId", appId, "err", err) return nil, err } } else { - configDbObj, err = impl.deploymentConfigRepository.GetByAppIdAndEnvId(appId, envId) + configDbObj, err = impl.deploymentConfigRepository.GetByAppIdAndEnvId(tx, appId, envId) if err != nil { impl.logger.Errorw("error in getting deployment config db object by appId and envId", "appId", appId, "envId", envId, "err", err) return nil, err diff --git a/pkg/deployment/common/read/deploymentConfigReadService.go b/pkg/deployment/common/read/deploymentConfigReadService.go index f8c5d8254d..fd672bd73d 100644 --- a/pkg/deployment/common/read/deploymentConfigReadService.go +++ b/pkg/deployment/common/read/deploymentConfigReadService.go @@ -123,7 +123,7 @@ func (impl *DeploymentConfigReadServiceImpl) GetDeploymentConfigForApp(appId int appLevelConfig *bean.DeploymentConfig isMigrationNeeded bool ) - appLevelConfigDbObj, err := impl.deploymentConfigRepository.GetAppLevelConfigForDevtronApps(appId) + appLevelConfigDbObj, err := impl.deploymentConfigRepository.GetAppLevelConfigForDevtronApps(nil, appId) if err != nil && !interalUtil.IsErrNoRows(err) { impl.logger.Errorw("error in getting deployment config db object by appId", "appId", appId, "err", err) return appLevelConfig, isMigrationNeeded, err @@ -158,7 +158,7 @@ func (impl *DeploymentConfigReadServiceImpl) GetDeploymentConfigForAppAndEnv(app envLevelConfig *bean.DeploymentConfig isMigrationNeeded bool ) - appAndEnvLevelConfigDBObj, err := impl.deploymentConfigRepository.GetByAppIdAndEnvId(appId, envId) + appAndEnvLevelConfigDBObj, err := impl.deploymentConfigRepository.GetByAppIdAndEnvId(nil, appId, envId) if err != nil && !interalUtil.IsErrNoRows(err) { impl.logger.Errorw("error in getting deployment config db object by appId and envId", "appId", appId, "envId", envId, "err", err) return envLevelConfig, isMigrationNeeded, err diff --git a/pkg/deployment/deployedApp/status/resourceTree/ResourceTreeService.go b/pkg/deployment/deployedApp/status/resourceTree/ResourceTreeService.go index b6fd6b5f69..66a6756574 100644 --- a/pkg/deployment/deployedApp/status/resourceTree/ResourceTreeService.go +++ b/pkg/deployment/deployedApp/status/resourceTree/ResourceTreeService.go @@ -19,6 +19,10 @@ package resourceTree import ( "context" "fmt" + "github.com/devtron-labs/common-lib/async" + "strconv" + "time" + "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" "github.com/argoproj/gitops-engine/pkg/health" k8sCommonBean "github.com/devtron-labs/common-lib/utils/k8s/commonBean" @@ -42,8 +46,6 @@ import ( application2 "github.com/devtron-labs/devtron/pkg/k8s/application" util2 "github.com/devtron-labs/devtron/util" "go.uber.org/zap" - "strconv" - "time" ) type Service interface { @@ -66,6 +68,7 @@ type ServiceImpl struct { k8sApplicationService application2.K8sApplicationService k8sCommonService k8s.K8sCommonService environmentReadService read2.EnvironmentReadService + asyncRunnable *async.Runnable } func NewServiceImpl(logger *zap.SugaredLogger, @@ -78,6 +81,7 @@ func NewServiceImpl(logger *zap.SugaredLogger, k8sApplicationService application2.K8sApplicationService, k8sCommonService k8s.K8sCommonService, environmentReadService read2.EnvironmentReadService, + asyncRunnable *async.Runnable, ) *ServiceImpl { serviceImpl := &ServiceImpl{ logger: logger, @@ -90,6 +94,7 @@ func NewServiceImpl(logger *zap.SugaredLogger, k8sApplicationService: k8sApplicationService, k8sCommonService: k8sCommonService, environmentReadService: environmentReadService, + asyncRunnable: asyncRunnable, } return serviceImpl } @@ -164,7 +169,7 @@ func (impl *ServiceImpl) FetchResourceTree(ctx context.Context, appId int, envId } } resourceTree = util2.InterfaceToMapAdapter(resp) - go func() { + impl.asyncRunnable.Execute(func() { if resp.Status == string(health.HealthStatusHealthy) { err = impl.cdApplicationStatusUpdateHandler.SyncPipelineStatusForResourceTreeCall(cdPipeline) if err != nil { @@ -176,7 +181,7 @@ func (impl *ServiceImpl) FetchResourceTree(ctx context.Context, appId int, envId if err != nil { impl.logger.Warnw("error in updating app status", "err", err, "appId", cdPipeline.AppId, "envId", cdPipeline.EnvironmentId) } - }() + }) k8sAppDetail := AppView.AppDetailContainer{ DeploymentDetailContainer: AppView.DeploymentDetailContainer{ ClusterId: cdPipeline.Environment.ClusterId, diff --git a/pkg/deployment/trigger/devtronApps/HandlerService.go b/pkg/deployment/trigger/devtronApps/HandlerService.go index 4382f14ff0..8d1ea29100 100644 --- a/pkg/deployment/trigger/devtronApps/HandlerService.go +++ b/pkg/deployment/trigger/devtronApps/HandlerService.go @@ -19,6 +19,10 @@ package devtronApps import ( "bufio" "context" + "github.com/devtron-labs/common-lib/async" + "os" + "time" + pubsub "github.com/devtron-labs/common-lib/pubsub-lib" util5 "github.com/devtron-labs/common-lib/utils/k8s" bean3 "github.com/devtron-labs/devtron/api/bean" @@ -70,8 +74,6 @@ import ( util2 "github.com/devtron-labs/devtron/util/event" "github.com/devtron-labs/devtron/util/rbac" "go.uber.org/zap" - "os" - "time" ) /* @@ -102,7 +104,7 @@ type HandlerService interface { CancelStage(workflowRunnerId int, forceAbort bool, userId int32) (int, error) DownloadCdWorkflowArtifacts(buildId int) (*os.File, error) - GetRunningWorkflowLogs(environmentId int, pipelineId int, workflowId int) (*bufio.Reader, func() error, error) + GetRunningWorkflowLogs(environmentId int, pipelineId int, workflowId int, followLogs bool) (*bufio.Reader, func() error, error) } type HandlerServiceImpl struct { @@ -166,6 +168,8 @@ type HandlerServiceImpl struct { ciLogService pipeline.CiLogService workflowService executor.WorkflowService blobConfigStorageService pipeline.BlobStorageConfigService + deploymentEventHandler app.DeploymentEventHandler + asyncRunnable *async.Runnable } func NewHandlerServiceImpl(logger *zap.SugaredLogger, @@ -227,7 +231,8 @@ func NewHandlerServiceImpl(logger *zap.SugaredLogger, ciLogService pipeline.CiLogService, workflowService executor.WorkflowService, blobConfigStorageService pipeline.BlobStorageConfigService, -) (*HandlerServiceImpl, error) { + deploymentEventHandler app.DeploymentEventHandler, + asyncRunnable *async.Runnable) (*HandlerServiceImpl, error) { impl := &HandlerServiceImpl{ logger: logger, cdWorkflowCommonService: cdWorkflowCommonService, @@ -293,6 +298,8 @@ func NewHandlerServiceImpl(logger *zap.SugaredLogger, ciLogService: ciLogService, workflowService: workflowService, blobConfigStorageService: blobConfigStorageService, + deploymentEventHandler: deploymentEventHandler, + asyncRunnable: asyncRunnable, } config, err := types.GetCdConfig() if err != nil { diff --git a/pkg/deployment/trigger/devtronApps/deployStageHandlerCode.go b/pkg/deployment/trigger/devtronApps/deployStageHandlerCode.go index 70f7620216..e93ac0a701 100644 --- a/pkg/deployment/trigger/devtronApps/deployStageHandlerCode.go +++ b/pkg/deployment/trigger/devtronApps/deployStageHandlerCode.go @@ -20,6 +20,14 @@ import ( "context" "errors" "fmt" + util2 "github.com/devtron-labs/devtron/util/event" + "net/http" + "path" + "regexp" + "strconv" + "strings" + "time" + bean3 "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/api/bean/gitOps" bean6 "github.com/devtron-labs/devtron/api/helm-app/bean" @@ -57,12 +65,6 @@ import ( "google.golang.org/grpc/codes" status2 "google.golang.org/grpc/status" "helm.sh/helm/v3/pkg/chart" - "net/http" - "path" - "regexp" - "strconv" - "strings" - "time" ) func (impl *HandlerServiceImpl) TriggerStageForBulk(triggerRequest bean.TriggerRequest) error { @@ -316,6 +318,12 @@ func (impl *HandlerServiceImpl) ManualCdTrigger(triggerContext bean.TriggerConte if err != nil { impl.logger.Errorw("error while updating current runner status to failed", "cdWfr", runner.Id, "err", err) } + pipelineOverride, err := impl.pipelineOverrideRepository.FindLatestByCdWorkflowId(runner.CdWorkflowId) + if err != nil { + impl.logger.Errorw("error in getting latest pipeline override by cdWorkflowId", "err", err, "cdWorkflowId", runner.CdWorkflowId) + return 0, "", nil, err + } + impl.deploymentEventHandler.WriteCDNotificationEventAsync(pipelineOverride.Pipeline.AppId, pipelineOverride.Pipeline.EnvironmentId, pipelineOverride, util2.Fail) return 0, "", nil, releaseErr } @@ -741,7 +749,9 @@ func (impl *HandlerServiceImpl) triggerPipeline(overrideRequest *bean3.ValuesOve } } - go impl.writeCDTriggerEvent(overrideRequest, valuesOverrideResponse.Artifact, valuesOverrideResponse.PipelineOverride.PipelineReleaseCounter, valuesOverrideResponse.PipelineOverride.Id, overrideRequest.WfrId) + impl.asyncRunnable.Execute(func() { + impl.writeCDTriggerEvent(overrideRequest, valuesOverrideResponse.Artifact, valuesOverrideResponse.PipelineOverride.PipelineReleaseCounter, valuesOverrideResponse.PipelineOverride.Id, overrideRequest.WfrId) + }) _ = impl.markImageScanDeployed(newCtx, overrideRequest.AppId, overrideRequest.EnvId, overrideRequest.ClusterId, valuesOverrideResponse.Artifact.ImageDigest, valuesOverrideResponse.Artifact.ScanEnabled, valuesOverrideResponse.Artifact.Image) diff --git a/pkg/deployment/trigger/devtronApps/prePostWfAndLogsCode.go b/pkg/deployment/trigger/devtronApps/prePostWfAndLogsCode.go index d02d1e4cd9..932f2a22d8 100644 --- a/pkg/deployment/trigger/devtronApps/prePostWfAndLogsCode.go +++ b/pkg/deployment/trigger/devtronApps/prePostWfAndLogsCode.go @@ -224,7 +224,7 @@ func (impl *HandlerServiceImpl) DownloadCdWorkflowArtifacts(buildId int) (*os.Fi return file, nil } -func (impl *HandlerServiceImpl) GetRunningWorkflowLogs(environmentId int, pipelineId int, wfrId int) (*bufio.Reader, func() error, error) { +func (impl *HandlerServiceImpl) GetRunningWorkflowLogs(environmentId int, pipelineId int, wfrId int, followLogs bool) (*bufio.Reader, func() error, error) { cdWorkflow, err := impl.cdWorkflowRepository.FindWorkflowRunnerById(wfrId) if err != nil { impl.logger.Errorw("error on fetch wf runner", "err", err) @@ -253,16 +253,16 @@ func (impl *HandlerServiceImpl) GetRunningWorkflowLogs(environmentId int, pipeli } else if cdWorkflow.WorkflowType == types.POST { isExtCluster = pipeline.RunPostStageInEnv } - return impl.getWorkflowLogs(pipelineId, cdWorkflow, clusterConfig, isExtCluster) + return impl.getWorkflowLogs(pipelineId, cdWorkflow, clusterConfig, isExtCluster, followLogs) } -func (impl *HandlerServiceImpl) getWorkflowLogs(pipelineId int, cdWorkflow *pipelineConfig.CdWorkflowRunner, clusterConfig *k8s.ClusterConfig, runStageInEnv bool) (*bufio.Reader, func() error, error) { +func (impl *HandlerServiceImpl) getWorkflowLogs(pipelineId int, cdWorkflow *pipelineConfig.CdWorkflowRunner, clusterConfig *k8s.ClusterConfig, runStageInEnv bool, followLogs bool) (*bufio.Reader, func() error, error) { cdLogRequest := types.BuildLogRequest{ PodName: cdWorkflow.PodName, Namespace: cdWorkflow.Namespace, } - logStream, cleanUp, err := impl.ciLogService.FetchRunningWorkflowLogs(cdLogRequest, clusterConfig, runStageInEnv) + logStream, cleanUp, err := impl.ciLogService.FetchRunningWorkflowLogs(cdLogRequest, clusterConfig, runStageInEnv, followLogs) if logStream == nil || err != nil { if !cdWorkflow.BlobStorageEnabled { return nil, nil, errors.New("logs-not-stored-in-repository") diff --git a/pkg/eventProcessor/in/WorkflowEventProcessorService.go b/pkg/eventProcessor/in/WorkflowEventProcessorService.go index 7012b20adf..aa47da205a 100644 --- a/pkg/eventProcessor/in/WorkflowEventProcessorService.go +++ b/pkg/eventProcessor/in/WorkflowEventProcessorService.go @@ -21,7 +21,13 @@ import ( "encoding/json" "errors" "fmt" + "slices" + "strconv" + "sync" + "time" + "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + "github.com/devtron-labs/common-lib/async" pubsub "github.com/devtron-labs/common-lib/pubsub-lib" "github.com/devtron-labs/common-lib/pubsub-lib/model" "github.com/devtron-labs/common-lib/utils/registry" @@ -63,10 +69,6 @@ import ( "go.uber.org/zap" "gopkg.in/go-playground/validator.v9" "k8s.io/utils/pointer" - "slices" - "strconv" - "sync" - "time" ) type WorkflowEventProcessorImpl struct { @@ -90,6 +92,7 @@ type WorkflowEventProcessorImpl struct { cdPipelineConfigService pipeline.CdPipelineConfigService userDeploymentRequestService service.UserDeploymentRequestService ucid ucid.Service + asyncRunnable *async.Runnable devtronAppReleaseContextMap map[int]bean.DevtronAppReleaseContextType devtronAppReleaseContextMapLock *sync.Mutex @@ -127,7 +130,8 @@ func NewWorkflowEventProcessorImpl(logger *zap.SugaredLogger, ciArtifactRepository repository.CiArtifactRepository, cdWorkflowRepository pipelineConfig.CdWorkflowRepository, deploymentConfigService common.DeploymentConfigService, - ciHandlerService trigger.HandlerService) (*WorkflowEventProcessorImpl, error) { + ciHandlerService trigger.HandlerService, + asyncRunnable *async.Runnable) (*WorkflowEventProcessorImpl, error) { impl := &WorkflowEventProcessorImpl{ logger: logger, pubSubClient: pubSubClient, @@ -156,6 +160,7 @@ func NewWorkflowEventProcessorImpl(logger *zap.SugaredLogger, cdWorkflowRepository: cdWorkflowRepository, deploymentConfigService: deploymentConfigService, ciHandlerService: ciHandlerService, + asyncRunnable: asyncRunnable, } appServiceConfig, err := app.GetAppServiceConfig() if err != nil { @@ -163,7 +168,7 @@ func NewWorkflowEventProcessorImpl(logger *zap.SugaredLogger, } impl.appServiceConfig = appServiceConfig // handle incomplete deployment requests after restart - go impl.ProcessIncompleteDeploymentReq() + impl.asyncRunnable.Execute(func() { impl.ProcessIncompleteDeploymentReq() }) return impl, nil } diff --git a/pkg/k8s/K8sCommonService.go b/pkg/k8s/K8sCommonService.go index 360b8ab53d..a4a849acf8 100644 --- a/pkg/k8s/K8sCommonService.go +++ b/pkg/k8s/K8sCommonService.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "github.com/caarlos0/env" + "github.com/devtron-labs/common-lib/async" "github.com/devtron-labs/common-lib/utils/k8s" k8sCommonBean "github.com/devtron-labs/common-lib/utils/k8s/commonBean" "github.com/devtron-labs/devtron/api/bean/AppView" @@ -75,6 +76,7 @@ type K8sCommonServiceImpl struct { K8sApplicationServiceConfig *K8sApplicationServiceConfig argoApplicationConfigService config.ArgoApplicationConfigService ClusterReadService read.ClusterReadService + asyncRunnable *async.Runnable } type K8sApplicationServiceConfig struct { BatchSize int `env:"BATCH_SIZE" envDefault:"5" description:"there is feature to get URL's of services/ingresses. so to extract those, we need to parse all the servcie and ingress objects of the application. this BATCH_SIZE flag controls the no of these objects get parsed in one go."` @@ -83,7 +85,7 @@ type K8sApplicationServiceConfig struct { func NewK8sCommonServiceImpl(Logger *zap.SugaredLogger, k8sUtils *k8s.K8sServiceImpl, argoApplicationConfigService config.ArgoApplicationConfigService, - ClusterReadService read.ClusterReadService) *K8sCommonServiceImpl { + ClusterReadService read.ClusterReadService, asyncRunnable *async.Runnable) *K8sCommonServiceImpl { cfg := &K8sApplicationServiceConfig{} err := env.Parse(cfg) if err != nil { @@ -95,6 +97,7 @@ func NewK8sCommonServiceImpl(Logger *zap.SugaredLogger, k8sUtils *k8s.K8sService K8sApplicationServiceConfig: cfg, argoApplicationConfigService: argoApplicationConfigService, ClusterReadService: ClusterReadService, + asyncRunnable: asyncRunnable, } } @@ -192,10 +195,10 @@ func (impl *K8sCommonServiceImpl) UpdateResource(ctx context.Context, request *b func (impl *K8sCommonServiceImpl) GetRestConfigOfCluster(ctx context.Context, request *bean5.ResourceRequestBean) (*rest.Config, error) { //getting rest config by clusterId clusterId := request.ClusterId - if len(request.ExternalArgoApplicationName) > 0 { - restConfig, err := impl.argoApplicationConfigService.GetRestConfigForExternalArgo(ctx, clusterId, request.ExternalArgoApplicationName) + if request.ExternalArgoAppIdentifier != nil { + restConfig, err := impl.argoApplicationConfigService.GetRestConfigForExternalArgo(ctx, request.ExternalArgoAppIdentifier) if err != nil { - impl.logger.Errorw("error in getting rest config", "err", err, "clusterId", clusterId, "externalArgoApplicationName", request.ExternalArgoApplicationName) + impl.logger.Errorw("error in getting rest config", "err", err, "clusterId", clusterId, "externalArgoApplicationName", request.ExternalArgoAppIdentifier.AppName) return nil, err } return restConfig, nil @@ -295,7 +298,8 @@ func (impl *K8sCommonServiceImpl) GetManifestsByBatch(ctx context.Context, reque var res []bean5.BatchResourceResponse ctx, cancel := context.WithTimeout(ctx, time.Duration(impl.K8sApplicationServiceConfig.TimeOutInSeconds)*time.Second) defer cancel() - go func() { + + runnableFunc := func() { ans := impl.getManifestsByBatch(ctx, requests) select { case <-ctx.Done(): @@ -303,7 +307,9 @@ func (impl *K8sCommonServiceImpl) GetManifestsByBatch(ctx context.Context, reque default: ch <- ans } - }() + } + impl.asyncRunnable.Execute(runnableFunc) + select { case ans := <-ch: res = ans @@ -398,16 +404,18 @@ func (impl *K8sCommonServiceImpl) getManifestsByBatch(ctx context.Context, reque var wg sync.WaitGroup for j := 0; j < batchSize; j++ { wg.Add(1) - go func(j int) { + runnableFunc := func(index int) { resp := bean5.BatchResourceResponse{} - response, err := impl.GetResource(ctx, &requests[i+j]) + response, err := impl.GetResource(ctx, &requests[index]) if response != nil { resp.ManifestResponse = response.ManifestResponse } resp.Err = err - res[i+j] = resp + res[index] = resp wg.Done() - }(j) + } + index := i + j + impl.asyncRunnable.Execute(func() { runnableFunc(index) }) } wg.Wait() i += batchSize @@ -460,7 +468,7 @@ func (impl *K8sCommonServiceImpl) GetCoreClientByClusterId(clusterId int) (*kube } func (impl *K8sCommonServiceImpl) GetCoreClientByClusterIdForExternalArgoApps(req *bean4.EphemeralContainerRequest) (*kubernetes.Clientset, *clientV1.CoreV1Client, error) { - restConfig, err := impl.argoApplicationConfigService.GetRestConfigForExternalArgo(context.Background(), req.ClusterId, req.ExternalArgoApplicationName) + restConfig, err := impl.argoApplicationConfigService.GetRestConfigForExternalArgo(context.Background(), req.ExternalArgoAppIdentifier) if err != nil { impl.logger.Errorw("error in getting rest config", "err", err, "clusterId", req.ClusterId, "externalArgoApplicationName", req.ExternalArgoApplicationName) } diff --git a/pkg/k8s/application/k8sApplicationService.go b/pkg/k8s/application/k8sApplicationService.go index 0ba1876c94..b8ee4533ea 100644 --- a/pkg/k8s/application/k8sApplicationService.go +++ b/pkg/k8s/application/k8sApplicationService.go @@ -263,6 +263,7 @@ func (impl *K8sApplicationServiceImpl) ValidatePodLogsRequestQuery(r *http.Reque request.ClusterId = appIdentifier.ClusterId request.K8sRequest.ResourceIdentifier.Namespace = namespace request.AppId = appId + request.ExternalArgoAppIdentifier = appIdentifier } else if request.AppType == bean3.HelmAppType { // For Helm App resources appIdentifier, err := impl.helmAppService.DecodeAppId(appId) @@ -370,6 +371,8 @@ func (impl *K8sApplicationServiceImpl) ValidateTerminalRequestQuery(r *http.Requ resourceRequestBean.ClusterId = appIdentifier.ClusterId request.ClusterId = appIdentifier.ClusterId request.ExternalArgoApplicationName = appIdentifier.AppName + request.ExternalArgoApplicationNamespace = appIdentifier.Namespace + request.ExternalArgoAppIdentifier = appIdentifier } } else { // Validate Cluster Id @@ -876,7 +879,7 @@ func (impl *K8sApplicationServiceImpl) CreatePodEphemeralContainers(req *bean5.E var clientSet *kubernetes.Clientset var v1Client *v1.CoreV1Client var err error - if len(req.ExternalArgoApplicationName) > 0 { + if req.ExternalArgoAppIdentifier != nil { clientSet, v1Client, err = impl.k8sCommonService.GetCoreClientByClusterIdForExternalArgoApps(req) if err != nil { impl.logger.Errorw("error in getting coreV1 client by clusterId", "err", err, "req", req) diff --git a/pkg/k8s/bean/bean.go b/pkg/k8s/bean/bean.go index 19cb19ee9f..9ad53a11f6 100644 --- a/pkg/k8s/bean/bean.go +++ b/pkg/k8s/bean/bean.go @@ -19,6 +19,7 @@ package bean import ( "github.com/devtron-labs/common-lib/utils/k8s" helmBean "github.com/devtron-labs/devtron/api/helm-app/service/bean" + bean3 "github.com/devtron-labs/devtron/pkg/argoApplication/bean" bean2 "github.com/devtron-labs/devtron/pkg/fluxApplication/bean" "github.com/devtron-labs/devtron/pkg/k8s/application/bean" ) @@ -33,6 +34,7 @@ type ResourceRequestBean struct { ClusterId int `json:"clusterId"` // clusterId is used when request is for direct cluster (not for helm release) ExternalArgoApplicationName string `json:"externalArgoApplicationName,omitempty"` ExternalFluxAppIdentifier *bean2.FluxAppIdentifier `json: "-"` + ExternalArgoAppIdentifier *bean3.ArgoAppIdentifier `json:"-"` } func (r *ResourceRequestBean) IsValidAppType() bool { diff --git a/pkg/module/ModuleService.go b/pkg/module/ModuleService.go index fd9c50dff5..0584db9a40 100644 --- a/pkg/module/ModuleService.go +++ b/pkg/module/ModuleService.go @@ -251,7 +251,7 @@ func (impl ModuleServiceImpl) handleModuleNotFoundStatus(moduleName string) (bea // if check non-cicd module status if moduleName != bean.ModuleNameCiCd { - isEnabled := gjson.Get(releaseValues, moduleUtil.BuildModuleEnableKey(moduleName)).Bool() + isEnabled := gjson.Get(releaseValues, moduleUtil.BuildModuleEnableKey(impl.serverEnvConfig.DevtronOperatorBasePath, moduleName)).Bool() if isEnabled { status, err := impl.saveModuleAsInstalled(moduleName, moduleType, flagForEnablingState) return status, moduleType, flagForActiveTool, err @@ -259,7 +259,7 @@ func (impl ModuleServiceImpl) handleModuleNotFoundStatus(moduleName string) (bea } else if util2.IsBaseStack() { // check if cicd is in installing state // if devtron is installed with cicd module, then cicd module should be shown as installing - installerModulesIface := gjson.Get(releaseValues, bean.INSTALLER_MODULES_HELM_KEY).Value() + installerModulesIface := gjson.Get(releaseValues, impl.serverEnvConfig.DevtronInstallerModulesPath).Value() if installerModulesIface != nil { installerModulesIfaceKind := reflect.TypeOf(installerModulesIface).Kind() if installerModulesIfaceKind == reflect.Slice { @@ -410,20 +410,20 @@ func (impl ModuleServiceImpl) HandleModuleAction(userId int32, moduleName string } extraValues := make(map[string]interface{}) - extraValues["installer.release"] = moduleActionRequest.Version - extraValues[bean.INSTALLER_MODULES_HELM_KEY] = []interface{}{moduleName} + extraValues[impl.serverEnvConfig.DevtronInstallerReleasePath] = moduleActionRequest.Version + extraValues[impl.serverEnvConfig.DevtronInstallerModulesPath] = []interface{}{moduleName} alreadyInstalledModuleNames, err := impl.moduleRepository.GetInstalledModuleNames() if err != nil { impl.logger.Errorw("error in getting modules with installed status ", "err", err) return nil, err } - moduleEnableKeys := moduleUtil.BuildAllModuleEnableKeys(moduleName) + moduleEnableKeys := moduleUtil.BuildAllModuleEnableKeys(impl.serverEnvConfig.DevtronOperatorBasePath, moduleName) for _, moduleEnableKey := range moduleEnableKeys { extraValues[moduleEnableKey] = true } for _, alreadyInstalledModuleName := range alreadyInstalledModuleNames { if alreadyInstalledModuleName != moduleName { - alreadyInstalledModuleEnableKeys := moduleUtil.BuildAllModuleEnableKeys(alreadyInstalledModuleName) + alreadyInstalledModuleEnableKeys := moduleUtil.BuildAllModuleEnableKeys(impl.serverEnvConfig.DevtronOperatorBasePath, alreadyInstalledModuleName) for _, alreadyInstalledModuleEnableKey := range alreadyInstalledModuleEnableKeys { extraValues[alreadyInstalledModuleEnableKey] = true } diff --git a/pkg/module/bean/bean.go b/pkg/module/bean/bean.go index a808ea07ee..0c0399c199 100644 --- a/pkg/module/bean/bean.go +++ b/pkg/module/bean/bean.go @@ -80,7 +80,6 @@ type ModuleStatus = string type ModuleName = string const BlobStorage = "blob-storage" -const INSTALLER_MODULES_HELM_KEY = "installer.modules" const ( CLAIR_V4 = "V4" CLAIR_V2 = "V2" diff --git a/pkg/module/util/ModuleUtil.go b/pkg/module/util/ModuleUtil.go index 3fd2fe24b3..f38014b8b4 100644 --- a/pkg/module/util/ModuleUtil.go +++ b/pkg/module/util/ModuleUtil.go @@ -21,16 +21,19 @@ import ( "strings" ) -func BuildAllModuleEnableKeys(moduleName string) []string { +func BuildAllModuleEnableKeys(basePath string, moduleName string) []string { var keys []string - keys = append(keys, BuildModuleEnableKey(moduleName)) + keys = append(keys, BuildModuleEnableKey(basePath, moduleName)) if strings.Contains(moduleName, ".") { parent := strings.Split(moduleName, ".")[0] - keys = append(keys, BuildModuleEnableKey(parent)) + keys = append(keys, BuildModuleEnableKey(basePath, parent)) } return keys } -func BuildModuleEnableKey(moduleName string) string { +func BuildModuleEnableKey(basePath string, moduleName string) string { + if len(basePath) > 0 { + return fmt.Sprintf("%s.%s.%s", basePath, moduleName, "enabled") + } return fmt.Sprintf("%s.%s", moduleName, "enabled") } diff --git a/pkg/notifier/NotificationConfigService.go b/pkg/notifier/NotificationConfigService.go index 00681337eb..aa6cad0651 100644 --- a/pkg/notifier/NotificationConfigService.go +++ b/pkg/notifier/NotificationConfigService.go @@ -780,15 +780,6 @@ func (impl *NotificationConfigServiceImpl) FindNotificationSettingOptions(settin } } - if teamResponse != nil || appResponse != nil { - ciMatching := &beans.SearchFilterResponse{ - PipelineType: string(util.CI), - TeamResponse: teamResponse, - AppResponse: appResponse, - } - searchFilterResponse = append(searchFilterResponse, ciMatching) - } - if teamResponse != nil || appResponse != nil || envResponse != nil || clusterResponse != nil { cdMatching := &beans.SearchFilterResponse{ PipelineType: string(util.CD), @@ -798,6 +789,18 @@ func (impl *NotificationConfigServiceImpl) FindNotificationSettingOptions(settin ClusterResponse: clusterResponse, } searchFilterResponse = append(searchFilterResponse, cdMatching) + + // functionality for current and future CI pipelines for cluster is not supported + if clusterResponse == nil { + ciMatching := &beans.SearchFilterResponse{ + PipelineType: string(util.CI), + TeamResponse: teamResponse, + AppResponse: appResponse, + EnvResponse: envResponse, + ClusterResponse: clusterResponse, + } + searchFilterResponse = append(searchFilterResponse, ciMatching) + } } if searchFilterResponse == nil { diff --git a/pkg/pipeline/CdHandler.go b/pkg/pipeline/CdHandler.go index 4257622796..8d468eed6c 100644 --- a/pkg/pipeline/CdHandler.go +++ b/pkg/pipeline/CdHandler.go @@ -735,11 +735,13 @@ func (impl *CdHandlerImpl) FetchAppWorkflowStatusForTriggerViewForEnvironment(re // filter out pipelines for unauthorized apps but not envs appResults, _ := request.CheckAuthBatch(token, appObjectArr, envObjectArr) for _, pipeline := range pipelines { - if _, ok := objects[pipeline.Id]; !ok { - impl.Logger.Warnw("skipping pipeline as no object found for it", "pipelineId", pipeline.Id) + // Safety check to prevent index-out-of-range panic + objectArr, ok := objects[pipeline.Id] + if !ok { + impl.Logger.Warnw("skipping pipeline with missing object data", "pipelineId", pipeline.Id) continue } - appObject := objects[pipeline.Id][0] + appObject := objectArr[0] if !(appResults[appObject]) { // if user unauthorized, skip items continue diff --git a/pkg/pipeline/CiCdPipelineOrchestrator.go b/pkg/pipeline/CiCdPipelineOrchestrator.go index a617e4635f..f5f9a9c135 100644 --- a/pkg/pipeline/CiCdPipelineOrchestrator.go +++ b/pkg/pipeline/CiCdPipelineOrchestrator.go @@ -1369,6 +1369,20 @@ func (impl CiCdPipelineOrchestratorImpl) DeleteApp(appId int, userId int32) erro } // Rollback tx on error. defer tx.Rollback() + // deleting deployment config first as it is dependent on app + appDeploymentConfig, err := impl.deploymentConfigService.GetAndMigrateConfigIfAbsentForDevtronApps(appId, 0) + if err != nil && !errors.Is(err, pg.ErrNoRows) { + impl.logger.Errorw("error in fetching environment deployment config by appId and envId", "appId", appId, "err", err) + return err + } else if err == nil && appDeploymentConfig != nil { + appDeploymentConfig.Active = false + appDeploymentConfig, err = impl.deploymentConfigService.CreateOrUpdateConfig(tx, appDeploymentConfig, userId) + if err != nil { + impl.logger.Errorw("error in deleting deployment config for pipeline", "appId", appId, "err", err) + return err + } + } + // deleting app app.Active = false app.UpdatedOn = time.Now() app.UpdatedBy = userId @@ -1383,18 +1397,6 @@ func (impl CiCdPipelineOrchestratorImpl) DeleteApp(appId int, userId int32) erro impl.logger.Errorw("error in deleting auth roles", "err", err) return err } - appDeploymentConfig, err := impl.deploymentConfigService.GetAndMigrateConfigIfAbsentForDevtronApps(appId, 0) - if err != nil && !errors.Is(err, pg.ErrNoRows) { - impl.logger.Errorw("error in fetching environment deployment config by appId and envId", "appId", appId, "err", err) - return err - } else if err == nil && appDeploymentConfig != nil { - appDeploymentConfig.Active = false - appDeploymentConfig, err = impl.deploymentConfigService.CreateOrUpdateConfig(tx, appDeploymentConfig, userId) - if err != nil { - impl.logger.Errorw("error in deleting deployment config for pipeline", "appId", appId, "err", err) - return err - } - } err = tx.Commit() if err != nil { return err diff --git a/pkg/pipeline/CiLogService.go b/pkg/pipeline/CiLogService.go index 1c0b75d320..37010d544f 100644 --- a/pkg/pipeline/CiLogService.go +++ b/pkg/pipeline/CiLogService.go @@ -30,7 +30,7 @@ import ( ) type CiLogService interface { - FetchRunningWorkflowLogs(ciLogRequest types.BuildLogRequest, clusterConfig *k8s.ClusterConfig, isExt bool) (io.ReadCloser, func() error, error) + FetchRunningWorkflowLogs(ciLogRequest types.BuildLogRequest, clusterConfig *k8s.ClusterConfig, isExt bool, followLogs bool) (io.ReadCloser, func() error, error) FetchLogs(baseLogLocationPathConfig string, ciLogRequest types.BuildLogRequest) (*os.File, func() error, error) } @@ -53,7 +53,7 @@ func NewCiLogServiceImpl(logger *zap.SugaredLogger, k8sUtil *k8s.K8sServiceImpl) }, nil } -func (impl *CiLogServiceImpl) FetchRunningWorkflowLogs(ciLogRequest types.BuildLogRequest, clusterConfig *k8s.ClusterConfig, isExt bool) (io.ReadCloser, func() error, error) { +func (impl *CiLogServiceImpl) FetchRunningWorkflowLogs(ciLogRequest types.BuildLogRequest, clusterConfig *k8s.ClusterConfig, isExt bool, followLogs bool) (io.ReadCloser, func() error, error) { var kubeClient *kubernetes.Clientset kubeClient = impl.kubeClient var err error @@ -64,7 +64,7 @@ func (impl *CiLogServiceImpl) FetchRunningWorkflowLogs(ciLogRequest types.BuildL return nil, nil, err } } - req := impl.k8sUtil.GetLogsForAPod(kubeClient, ciLogRequest.Namespace, ciLogRequest.PodName, bean.Main, true) + req := impl.k8sUtil.GetLogsForAPod(kubeClient, ciLogRequest.Namespace, ciLogRequest.PodName, bean.Main, followLogs) podLogs, err := req.Stream(context.Background()) if err != nil { impl.logger.Errorw("error in opening stream", "name", ciLogRequest.PodName, "err", err) diff --git a/pkg/pipeline/CiService.go b/pkg/pipeline/CiService.go index c88ae5ca11..c0e898ed37 100644 --- a/pkg/pipeline/CiService.go +++ b/pkg/pipeline/CiService.go @@ -79,6 +79,7 @@ func (impl *CiServiceImpl) WriteCITriggerEvent(trigger types.Trigger, pipeline * event.UserId = int(trigger.TriggeredBy) event.CiWorkflowRunnerId = workflowRequest.WorkflowId event = impl.eventFactory.BuildExtraCIData(event, material) + _, evtErr := impl.eventClient.WriteNotificationEvent(event) if evtErr != nil { impl.Logger.Errorw("error in writing event", "err", evtErr) diff --git a/pkg/pipeline/DeploymentPipelineConfigService.go b/pkg/pipeline/DeploymentPipelineConfigService.go index b8862ce02a..00bfc63b60 100644 --- a/pkg/pipeline/DeploymentPipelineConfigService.go +++ b/pkg/pipeline/DeploymentPipelineConfigService.go @@ -494,6 +494,10 @@ func (impl *CdPipelineConfigServiceImpl) CreateCdPipelines(pipelineCreateRequest } for _, pipeline := range pipelineCreateRequest.Pipelines { + // skip creation of pipeline if envId is not set + if pipeline.EnvironmentId <= 0 || pipeline.IsSwitchCiPipelineRequest() { + continue + } env, err := impl.environmentRepository.FindById(pipeline.EnvironmentId) if err != nil { impl.logger.Errorw("error in fetching env by id", "envId", pipeline.EnvironmentId, "err", err) @@ -1337,7 +1341,8 @@ func (impl *CdPipelineConfigServiceImpl) DeleteCdPipeline(pipeline *pipelineConf impl.logger.Errorw("error in deleting workflow mapping", "err", err) return deleteResponse, err } - err = impl.appWorkflowRepository.DeleteAppWorkflow(appWorkflow, tx) + //delete app workflow and all it's mappings + err = impl.appWorkflowRepository.DeleteAppWorkflowAndAllMappings(appWorkflow, tx) if err != nil { impl.logger.Errorw("error in deleting workflow mapping", "err", err) return deleteResponse, err diff --git a/pkg/pipeline/DevtronAppConfigService.go b/pkg/pipeline/DevtronAppConfigService.go index 0c7c7a5f46..b9a1bb14eb 100644 --- a/pkg/pipeline/DevtronAppConfigService.go +++ b/pkg/pipeline/DevtronAppConfigService.go @@ -20,6 +20,7 @@ import ( "github.com/devtron-labs/devtron/internal/sql/repository/app" "github.com/devtron-labs/devtron/internal/sql/repository/helper" "github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig" + "github.com/devtron-labs/devtron/pkg/auth/user/repository" "github.com/devtron-labs/devtron/pkg/bean" "github.com/devtron-labs/devtron/pkg/resourceGroup" "github.com/devtron-labs/devtron/util/rbac" @@ -79,6 +80,7 @@ type DevtronAppConfigServiceImpl struct { enforcerUtil rbac.EnforcerUtil ciMaterialConfigService CiMaterialConfigService + userRepository repository.UserRepository } func NewDevtronAppConfigServiceImpl( @@ -88,7 +90,8 @@ func NewDevtronAppConfigServiceImpl( pipelineRepository pipelineConfig.PipelineRepository, resourceGroupService resourceGroup.ResourceGroupService, enforcerUtil rbac.EnforcerUtil, - ciMaterialConfigService CiMaterialConfigService) *DevtronAppConfigServiceImpl { + ciMaterialConfigService CiMaterialConfigService, + userRepository repository.UserRepository) *DevtronAppConfigServiceImpl { return &DevtronAppConfigServiceImpl{ logger: logger, ciCdPipelineOrchestrator: ciCdPipelineOrchestrator, @@ -97,6 +100,7 @@ func NewDevtronAppConfigServiceImpl( resourceGroupService: resourceGroupService, enforcerUtil: enforcerUtil, ciMaterialConfigService: ciMaterialConfigService, + userRepository: userRepository, } } @@ -179,12 +183,17 @@ func (impl *DevtronAppConfigServiceImpl) FindAllMatchesByAppName(appName string, impl.logger.Errorw("error while fetching app", "err", err) return nil, err } + userEmailMap, err := impl.getUserEmailFromApps(apps) + if err != nil { + impl.logger.Errorw("Error in getUserEmailFromApps", "apps", apps, "err", err) + return nil, err + } for _, app := range apps { name := app.AppName if appType == helper.Job { name = app.DisplayName } - appsRes = append(appsRes, &AppBean{Id: app.Id, Name: name}) + appsRes = append(appsRes, &AppBean{Id: app.Id, Name: name, Description: app.Description, CreatedBy: userEmailMap[app.CreatedBy]}) } return appsRes, err } @@ -244,8 +253,13 @@ func (impl *DevtronAppConfigServiceImpl) FindAppsByTeamId(teamId int) ([]*AppBea impl.logger.Errorw("error while fetching app", "err", err) return nil, err } + userEmailMap, err := impl.getUserEmailFromApps(apps) + if err != nil { + impl.logger.Errorw("Error in getUserEmailFromApps", "apps", apps, "err", err) + return nil, err + } for _, app := range apps { - appsRes = append(appsRes, &AppBean{Id: app.Id, Name: app.AppName}) + appsRes = append(appsRes, &AppBean{Id: app.Id, Name: app.AppName, Description: app.Description, CreatedBy: userEmailMap[app.CreatedBy]}) } return appsRes, err } @@ -262,3 +276,24 @@ func (impl *DevtronAppConfigServiceImpl) FindAppsByTeamName(teamName string) ([] } return appsRes, err } + +func (impl *DevtronAppConfigServiceImpl) getUserEmailFromApps(apps []*app.App) (map[int32]string, error) { + var userIds []int32 + userEmailMap := make(map[int32]string) + for _, app := range apps { + if app.CreatedBy != 0 { + userIds = append(userIds, app.CreatedBy) + } + } + if len(userIds) > 0 { + users, err := impl.userRepository.GetByIds(userIds) + if err != nil { + impl.logger.Errorw("Error in getting users", "userIds", userIds, "err", err) + return userEmailMap, err + } + for _, user := range users { + userEmailMap[user.Id] = user.EmailId + } + } + return userEmailMap, nil +} diff --git a/pkg/pipeline/PipelineBuilder.go b/pkg/pipeline/PipelineBuilder.go index 74d70fede1..6fe330b399 100644 --- a/pkg/pipeline/PipelineBuilder.go +++ b/pkg/pipeline/PipelineBuilder.go @@ -264,9 +264,11 @@ type TeamAppBean struct { } type AppBean struct { - Id int `json:"id"` - Name string `json:"name,notnull"` - TeamId int `json:"teamId,omitempty"` + Id int `json:"id"` + Name string `json:"name,notnull"` + TeamId int `json:"teamId,omitempty"` + CreatedBy string `json:"createdBy"` + Description string `json:"description"` } type PipelineStrategiesResponse struct { diff --git a/pkg/server/ServerService.go b/pkg/server/ServerService.go index 12c278d7ef..16b72fc66b 100644 --- a/pkg/server/ServerService.go +++ b/pkg/server/ServerService.go @@ -158,14 +158,14 @@ func (impl ServerServiceImpl) HandleServerAction(userId int32, serverActionReque } extraValues := make(map[string]interface{}) - extraValues["installer.release"] = serverActionRequest.Version + extraValues[impl.serverEnvConfig.DevtronInstallerReleasePath] = serverActionRequest.Version alreadyInstalledModuleNames, err := impl.moduleRepository.GetInstalledModuleNames() if err != nil { impl.logger.Errorw("error in getting modules with installed status ", "err", err) return nil, err } for _, alreadyInstalledModuleName := range alreadyInstalledModuleNames { - alreadyInstalledModuleEnableKeys := moduleUtil.BuildAllModuleEnableKeys(alreadyInstalledModuleName) + alreadyInstalledModuleEnableKeys := moduleUtil.BuildAllModuleEnableKeys(impl.serverEnvConfig.DevtronOperatorBasePath, alreadyInstalledModuleName) for _, alreadyInstalledModuleEnableKey := range alreadyInstalledModuleEnableKeys { extraValues[alreadyInstalledModuleEnableKey] = true } diff --git a/pkg/server/config/ServerEnvConfig.go b/pkg/server/config/ServerEnvConfig.go index 72f6e608ec..d3948bf098 100644 --- a/pkg/server/config/ServerEnvConfig.go +++ b/pkg/server/config/ServerEnvConfig.go @@ -41,6 +41,9 @@ type ServerEnvConfig struct { ModuleMetaDataApiUrl string `env:"MODULE_METADATA_API_URL" envDefault:"https://api.devtron.ai/module?name=%s" description:"Modules list and meta info will be fetched from this server, that is central api server of devtron."` ParallelismLimitForTagProcessing int `env:"PARALLELISM_LIMIT_FOR_TAG_PROCESSING" description:"App manual sync job parallel tag processing count."` AppSyncJobShutDownWaitDuration int `env:"APP_SYNC_SHUTDOWN_WAIT_DURATION" envDefault:"120"` + DevtronOperatorBasePath string `env:"DEVTRON_OPERATOR_BASE_PATH" envDefault:"" description:"Base path for devtron operator, used to find the helm charts and values files"` + DevtronInstallerModulesPath string `env:"DEVTRON_INSTALLER_MODULES_PATH" envDefault:"installer.modules" description:"Path to devtron installer modules, used to find the helm charts and values files"` + DevtronInstallerReleasePath string `env:"DEVTRON_INSTALLER_RELEASE_PATH" envDefault:"installer.release" description:"Path to devtron installer release, used to find the helm charts and values files"` ErrorEncounteredOnGettingDevtronHelmRelease error } diff --git a/pkg/terminal/terminalSesion.go b/pkg/terminal/terminalSesion.go index bc889b542c..355ee126ed 100644 --- a/pkg/terminal/terminalSesion.go +++ b/pkg/terminal/terminalSesion.go @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package terminal import ( @@ -23,9 +24,19 @@ import ( "encoding/json" errors2 "errors" "fmt" + "github.com/devtron-labs/common-lib/async" + "io" + "log" + "net/http" + "strconv" + "strings" + "sync" + "time" + "github.com/caarlos0/env" "github.com/devtron-labs/common-lib/utils/k8s" "github.com/devtron-labs/devtron/internal/middleware" + bean3 "github.com/devtron-labs/devtron/pkg/argoApplication/bean" "github.com/devtron-labs/devtron/pkg/argoApplication/read/config" "github.com/devtron-labs/devtron/pkg/cluster" "github.com/devtron-labs/devtron/pkg/cluster/bean" @@ -35,14 +46,7 @@ import ( "github.com/devtron-labs/devtron/pkg/cluster/repository" errors1 "github.com/juju/errors" "go.uber.org/zap" - "io" "k8s.io/apimachinery/pkg/api/errors" - "log" - "net/http" - "strconv" - "strings" - "sync" - "time" "gopkg.in/igm/sockjs-go.v3/sockjs" v1 "k8s.io/api/core/v1" @@ -393,9 +397,11 @@ type TerminalSessionRequest struct { EnvironmentId int AppId int //ClusterId is optional - ClusterId int - UserId int32 - ExternalArgoApplicationName string + ClusterId int + UserId int32 + ExternalArgoApplicationName string + ExternalArgoApplicationNamespace string + ExternalArgoAppIdentifier *bean3.ArgoAppIdentifier } const CommandExecutionFailed = "Failed to Execute Command" @@ -457,12 +463,13 @@ type TerminalSessionHandlerImpl struct { ephemeralContainerService cluster.EphemeralContainerService argoApplicationConfigService config.ArgoApplicationConfigService ClusterReadService read.ClusterReadService + asyncRunnable *async.Runnable } func NewTerminalSessionHandlerImpl(environmentService environment.EnvironmentService, logger *zap.SugaredLogger, k8sUtil *k8s.K8sServiceImpl, ephemeralContainerService cluster.EphemeralContainerService, argoApplicationConfigService config.ArgoApplicationConfigService, - ClusterReadService read.ClusterReadService) *TerminalSessionHandlerImpl { + ClusterReadService read.ClusterReadService, asyncRunnable *async.Runnable) *TerminalSessionHandlerImpl { return &TerminalSessionHandlerImpl{ environmentService: environmentService, logger: logger, @@ -470,6 +477,7 @@ func NewTerminalSessionHandlerImpl(environmentService environment.EnvironmentSer ephemeralContainerService: ephemeralContainerService, argoApplicationConfigService: argoApplicationConfigService, ClusterReadService: ClusterReadService, + asyncRunnable: asyncRunnable, } } @@ -515,18 +523,18 @@ func (impl *TerminalSessionHandlerImpl) GetTerminalSession(req *TerminalSessionR }) config, client, err := impl.getClientSetAndRestConfigForTerminalConn(req) - go func() { + impl.asyncRunnable.Execute(func() { err := impl.saveEphemeralContainerTerminalAccessAudit(req) if err != nil { impl.logger.Errorw("error in saving ephemeral container terminal access audit,so skipping auditing", "err", err) } - }() + }) if err != nil { impl.logger.Errorw("error in fetching config", "err", err) return http.StatusInternalServerError, nil, err } - go WaitForTerminal(client, config, req) + impl.asyncRunnable.Execute(func() { WaitForTerminal(client, config, req) }) return http.StatusOK, &TerminalMessage{SessionID: sessionID}, nil } @@ -535,8 +543,8 @@ func (impl *TerminalSessionHandlerImpl) getClientSetAndRestConfigForTerminalConn var clusterConfig *k8s.ClusterConfig var restConfig *rest.Config var err error - if len(req.ExternalArgoApplicationName) > 0 { - restConfig, err = impl.argoApplicationConfigService.GetRestConfigForExternalArgo(context.Background(), req.ClusterId, req.ExternalArgoApplicationName) + if req.ExternalArgoAppIdentifier != nil { + restConfig, err = impl.argoApplicationConfigService.GetRestConfigForExternalArgo(context.Background(), req.ExternalArgoAppIdentifier) if err != nil { impl.logger.Errorw("error in getting rest config", "err", err, "clusterId", req.ClusterId, "externalArgoApplicationName", req.ExternalArgoApplicationName) return nil, nil, err @@ -566,23 +574,17 @@ func (impl *TerminalSessionHandlerImpl) getClientSetAndRestConfigForTerminalConn } clusterConfig = clusterBean.GetClusterConfig() - restConfig, err = impl.k8sUtil.GetRestConfigByCluster(clusterConfig) + restConfig, err = impl.k8sUtil.GetRestConfigByCluster(clusterConfig, k8s.WithDefaultHttpTransport()) if err != nil { impl.logger.Errorw("error in getting rest config by cluster", "err", err, "clusterName", clusterConfig.ClusterName) return nil, nil, err } - _, clientSet, err := impl.k8sUtil.GetK8sConfigAndClientsByRestConfig(restConfig) + _, clientSet, err := impl.k8sUtil.GetK8sConfigAndClientsByRestConfig(restConfig, k8s.WithDefaultHttpTransport()) if err != nil { impl.logger.Errorw("error in clientSet", "err", err) return nil, nil, err } - - // we have to get the clientSet before setting the custom transport to nil - // we need re populate the tls config in the restConfig. - // rest config with custom transport will break spdy client - clusterConfig.PopulateTlsConfigurationsInto(restConfig) - restConfig.Transport = nil return restConfig, clientSet, nil } } @@ -656,8 +658,8 @@ func (impl *TerminalSessionHandlerImpl) RunCmdInRemotePod(req *TerminalSessionRe func (impl *TerminalSessionHandlerImpl) saveEphemeralContainerTerminalAccessAudit(req *TerminalSessionRequest) error { var restConfig *rest.Config var err error - if len(req.ExternalArgoApplicationName) > 0 { - restConfig, err = impl.argoApplicationConfigService.GetRestConfigForExternalArgo(context.Background(), req.ClusterId, req.ExternalArgoApplicationName) + if req.ExternalArgoAppIdentifier != nil { + restConfig, err = impl.argoApplicationConfigService.GetRestConfigForExternalArgo(context.Background(), req.ExternalArgoAppIdentifier) if err != nil { impl.logger.Errorw("error in getting rest config", "err", err, "clusterId", req.ClusterId, "externalArgoApplicationName", req.ExternalArgoApplicationName) return err diff --git a/pkg/variables/ScopedVariableService.go b/pkg/variables/ScopedVariableService.go index bf9e628e84..bfde4840b9 100644 --- a/pkg/variables/ScopedVariableService.go +++ b/pkg/variables/ScopedVariableService.go @@ -18,8 +18,13 @@ package variables import ( "fmt" + "regexp" + "strings" + "sync" + "github.com/argoproj/argo-workflows/v3/errors" "github.com/caarlos0/env" + "github.com/devtron-labs/common-lib/async" "github.com/devtron-labs/devtron/internal/sql/repository/app" repository3 "github.com/devtron-labs/devtron/pkg/cluster/environment/repository" "github.com/devtron-labs/devtron/pkg/cluster/repository" @@ -34,9 +39,6 @@ import ( "github.com/go-pg/pg" "go.uber.org/zap" "golang.org/x/exp/slices" - "regexp" - "strings" - "sync" ) type ScopedVariableService interface { @@ -55,15 +57,17 @@ type ScopedVariableServiceImpl struct { qualifierMappingService resourceQualifiers.QualifierMappingService VariableNameConfig *VariableConfig VariableCache *cache.VariableCacheObj + asyncRunnable *async.Runnable } func NewScopedVariableServiceImpl(logger *zap.SugaredLogger, scopedVariableRepository repository2.ScopedVariableRepository, appRepository app.AppRepository, environmentRepository repository3.EnvironmentRepository, devtronResourceSearchableKeyService read.DevtronResourceSearchableKeyService, clusterRepository repository.ClusterRepository, - qualifierMappingService resourceQualifiers.QualifierMappingService) (*ScopedVariableServiceImpl, error) { + qualifierMappingService resourceQualifiers.QualifierMappingService, asyncRunnable *async.Runnable) (*ScopedVariableServiceImpl, error) { scopedVariableService := &ScopedVariableServiceImpl{ logger: logger, scopedVariableRepository: scopedVariableRepository, qualifierMappingService: qualifierMappingService, VariableCache: &cache.VariableCacheObj{CacheLock: &sync.Mutex{}}, + asyncRunnable: asyncRunnable, } cfg, err := GetVariableNameConfig() if err != nil { @@ -83,7 +87,7 @@ type VariableConfig struct { func loadVariableCache(cfg *VariableConfig, service *ScopedVariableServiceImpl) { if cfg.VariableCacheEnabled { - go service.loadVarCache() + service.asyncRunnable.Execute(func() { service.loadVarCache() }) } } func GetVariableNameConfig() (*VariableConfig, error) { diff --git a/pkg/workflow/dag/WorkflowDagExecutor.go b/pkg/workflow/dag/WorkflowDagExecutor.go index a53e90dbd6..aed15b9c0e 100644 --- a/pkg/workflow/dag/WorkflowDagExecutor.go +++ b/pkg/workflow/dag/WorkflowDagExecutor.go @@ -1050,7 +1050,10 @@ func (impl *WorkflowDagExecutorImpl) HandleCiSuccessEvent(triggerContext trigger } else { ciArtifactArr = append(ciArtifactArr, pluginArtifacts[0]) } - go impl.WriteCiSuccessEvent(request, pipelineModal, buildArtifact) + runnableFunc := func() { + impl.WriteCiSuccessEvent(request, pipelineModal, buildArtifact) + } + impl.asyncRunnable.Execute(runnableFunc) async := false // execute auto trigger in batch on CI success event @@ -1072,7 +1075,7 @@ func (impl *WorkflowDagExecutorImpl) HandleCiSuccessEvent(triggerContext trigger for j := 0; j < batchSize; j++ { wg.Add(1) index := i + j - go func(index int) { + runnableFunc := func(index int) { defer wg.Done() ciArtifact := ciArtifactArr[index] // handle individual CiArtifact success event @@ -1080,7 +1083,8 @@ func (impl *WorkflowDagExecutorImpl) HandleCiSuccessEvent(triggerContext trigger if err != nil { impl.logger.Errorw("error on handle ci success event", "ciArtifactId", ciArtifact.Id, "err", err) } - }(index) + } + impl.asyncRunnable.Execute(func() { runnableFunc(index) }) } wg.Wait() i += batchSize diff --git a/pkg/workflow/status/WorkflowStatusService.go b/pkg/workflow/status/WorkflowStatusService.go index 78d962f884..56d9918747 100644 --- a/pkg/workflow/status/WorkflowStatusService.go +++ b/pkg/workflow/status/WorkflowStatusService.go @@ -19,6 +19,9 @@ package status import ( "context" "fmt" + util "github.com/devtron-labs/devtron/util/event" + "time" + bean2 "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/api/helm-app/service/bean" "github.com/devtron-labs/devtron/client/argocdServer" @@ -46,7 +49,6 @@ import ( "github.com/go-pg/pg" "go.uber.org/zap" "k8s.io/utils/strings/slices" - "time" ) type WorkflowStatusService interface { @@ -88,6 +90,7 @@ type WorkflowStatusServiceImpl struct { appListingService app.AppListingService deploymentConfigService common2.DeploymentConfigService cdWorkflowRunnerService cd.CdWorkflowRunnerService + deploymentEventHandler app.DeploymentEventHandler } func NewWorkflowStatusServiceImpl(logger *zap.SugaredLogger, @@ -110,7 +113,7 @@ func NewWorkflowStatusServiceImpl(logger *zap.SugaredLogger, appListingService app.AppListingService, deploymentConfigService common2.DeploymentConfigService, cdWorkflowRunnerService cd.CdWorkflowRunnerService, -) (*WorkflowStatusServiceImpl, error) { + deploymentEventHandler app.DeploymentEventHandler) (*WorkflowStatusServiceImpl, error) { impl := &WorkflowStatusServiceImpl{ logger: logger, workflowDagExecutor: workflowDagExecutor, @@ -134,6 +137,7 @@ func NewWorkflowStatusServiceImpl(logger *zap.SugaredLogger, appListingService: appListingService, deploymentConfigService: deploymentConfigService, cdWorkflowRunnerService: cdWorkflowRunnerService, + deploymentEventHandler: deploymentEventHandler, } config, err := types.GetCdConfig() if err != nil { @@ -166,12 +170,20 @@ func (impl *WorkflowStatusServiceImpl) CheckHelmAppStatusPeriodicallyAndUpdateIn } wfr.UpdatedBy = 1 wfr.UpdatedOn = time.Now() + + pipelineOverride, err := impl.pipelineOverrideRepository.FindLatestByCdWorkflowId(wfr.CdWorkflowId) + if err != nil { + impl.logger.Errorw("error in getting latest pipeline override by cdWorkflowId", "err", err, "cdWorkflowId", wfr.CdWorkflowId) + return err + } + if wfr.Status == cdWorkflow2.WorkflowFailed { err = impl.pipelineStatusTimelineService.MarkPipelineStatusTimelineSuperseded(wfr.RefCdWorkflowRunnerId) if err != nil { impl.logger.Errorw("error updating CdPipelineStatusTimeline", "err", err) return err } + impl.deploymentEventHandler.WriteCDNotificationEventAsync(pipelineOverride.Pipeline.AppId, pipelineOverride.Pipeline.EnvironmentId, pipelineOverride, util.Fail) } err = impl.cdWorkflowRunnerService.UpdateCdWorkflowRunnerWithStage(wfr) if err != nil { @@ -191,17 +203,14 @@ func (impl *WorkflowStatusServiceImpl) CheckHelmAppStatusPeriodicallyAndUpdateIn impl.logger.Infow("updated workflow runner status for helm app", "wfr", wfr) if wfr.Status == cdWorkflow2.WorkflowSucceeded { - pipelineOverride, err := impl.pipelineOverrideRepository.FindLatestByCdWorkflowId(wfr.CdWorkflowId) - if err != nil { - impl.logger.Errorw("error in getting latest pipeline override by cdWorkflowId", "err", err, "cdWorkflowId", wfr.CdWorkflowId) - return err - } - go impl.appService.WriteCDSuccessEvent(pipelineOverride.Pipeline.AppId, pipelineOverride.Pipeline.EnvironmentId, pipelineOverride) + impl.deploymentEventHandler.WriteCDNotificationEventAsync(pipelineOverride.Pipeline.AppId, pipelineOverride.Pipeline.EnvironmentId, pipelineOverride, util.Success) err = impl.workflowDagExecutor.HandleDeploymentSuccessEvent(bean3.TriggerContext{}, pipelineOverride) if err != nil { impl.logger.Errorw("error on handling deployment success event", "wfr", wfr, "err", err) return err } + } else if wfr.Status == cdWorkflow2.WorkflowTimedOut { + impl.deploymentEventHandler.WriteCDNotificationEventAsync(pipelineOverride.Pipeline.AppId, pipelineOverride.Pipeline.EnvironmentId, pipelineOverride, util.Fail) } } return nil @@ -313,7 +322,7 @@ func (impl *WorkflowStatusServiceImpl) UpdatePipelineTimelineAndStatusByLiveAppl } dc, err := impl.deploymentConfigService.GetConfigForHelmApps(installedApp.AppId, installedApp.EnvironmentId) if err != nil { - impl.logger.Errorw("error, GetConfigForDevtronApps", "appId", pipeline.AppId, "environmentId", pipeline.EnvironmentId, "err", err) + impl.logger.Errorw("error, GetConfigForDevtronApps", "appId", installedApp.AppId, "environmentId", installedApp.EnvironmentId, "err", err) return nil, isTimelineUpdated } applicationObjectClusterId := dc.GetApplicationObjectClusterId() diff --git a/vendor/github.com/devtron-labs/authenticator/middleware/sessionmanager.go b/vendor/github.com/devtron-labs/authenticator/middleware/sessionmanager.go index 3e0fdb653a..58cea2054d 100644 --- a/vendor/github.com/devtron-labs/authenticator/middleware/sessionmanager.go +++ b/vendor/github.com/devtron-labs/authenticator/middleware/sessionmanager.go @@ -47,6 +47,8 @@ const ( // ApiTokenClaimIssuer is the issuer who generated api-token for APIs ApiTokenClaimIssuer = "apiTokenIssuer" + LicenseManagerClaimIssuer = "licenseManagerIssuer" + // invalidLoginError, for security purposes, doesn't say whether the username or password was invalid. This does not mitigate the potential for timing attacks to determine which is which. invalidLoginError = "Invalid username or password" blankPasswordError = "Blank passwords are not allowed" @@ -221,6 +223,8 @@ func (mgr *SessionManager) VerifyToken(tokenString string) (jwt.Claims, error) { return mgr.Parse(tokenString) case ApiTokenClaimIssuer: return mgr.ParseApiToken(tokenString) + case LicenseManagerClaimIssuer: + return mgr.ParseApiToken(tokenString) default: // IDP signed token prov, err := mgr.provider() diff --git a/vendor/github.com/devtron-labs/common-lib/utils/grpc/GrpcConfig.go b/vendor/github.com/devtron-labs/common-lib/utils/grpc/GrpcConfig.go index 0ac82eedac..f9c7e4e9a9 100644 --- a/vendor/github.com/devtron-labs/common-lib/utils/grpc/GrpcConfig.go +++ b/vendor/github.com/devtron-labs/common-lib/utils/grpc/GrpcConfig.go @@ -2,9 +2,11 @@ package grpc import "github.com/caarlos0/env" +// CATEGORY=INFRA_SETUP type Configuration struct { - KubelinkMaxRecvMsgSize int `env:"KUBELINK_GRPC_MAX_RECEIVE_MSG_SIZE" envDefault:"20"` // In mb - KubelinkMaxSendMsgSize int `env:"KUBELINK_GRPC_MAX_SEND_MSG_SIZE" envDefault:"4"` // In mb + KubelinkMaxRecvMsgSize int `env:"KUBELINK_GRPC_MAX_RECEIVE_MSG_SIZE" envDefault:"20"` // In mb + KubelinkMaxSendMsgSize int `env:"KUBELINK_GRPC_MAX_SEND_MSG_SIZE" envDefault:"4"` // In mb + KubelinkGRPCServiceConfig string `env:"KUBELINK_GRPC_SERVICE_CONFIG" envDefault:"{\"loadBalancingPolicy\":\"round_robin\"}" description:"kubelink grpc service config"` } func GetConfiguration() (*Configuration, error) { diff --git a/vendor/github.com/devtron-labs/common-lib/utils/k8s/HttpTansport.go b/vendor/github.com/devtron-labs/common-lib/utils/k8s/HttpTansport.go new file mode 100644 index 0000000000..c220c28c48 --- /dev/null +++ b/vendor/github.com/devtron-labs/common-lib/utils/k8s/HttpTansport.go @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2020-2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package k8s + +import ( + "github.com/caarlos0/env" + "go.uber.org/zap" + utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/client-go/rest" + "net" + "net/http" + "time" +) + +type DefaultK8sHttpTransportConfig struct{} + +type CustomK8sHttpTransportConfig struct { + UseCustomTransport bool `env:"USE_CUSTOM_HTTP_TRANSPORT" envDefault:"false"` + TimeOut int `env:"K8s_TCP_TIMEOUT" envDefault:"30"` + KeepAlive int `env:"K8s_TCP_KEEPALIVE" envDefault:"30"` + TLSHandshakeTimeout int `env:"K8s_TLS_HANDSHAKE_TIMEOUT" envDefault:"10"` + MaxIdleConnsPerHost int `env:"K8s_CLIENT_MAX_IDLE_CONNS_PER_HOST" envDefault:"25"` + IdleConnTimeout int `env:"K8s_TCP_IDLE_CONN_TIMEOUT" envDefault:"300"` +} + +func NewDefaultK8sHttpTransportConfig() *DefaultK8sHttpTransportConfig { + return &DefaultK8sHttpTransportConfig{} +} + +func NewCustomK8sHttpTransportConfig(logger *zap.SugaredLogger) *CustomK8sHttpTransportConfig { + customK8sHttpTransportConfig := &CustomK8sHttpTransportConfig{} + err := env.Parse(customK8sHttpTransportConfig) + if err != nil { + logger.Errorw("error in parsing custom k8s http configurations from env", "err", err) + } + return customK8sHttpTransportConfig +} + +type TransportType string + +const ( + TransportTypeDefault TransportType = "default" + TransportTypeOverridden TransportType = "overridden" +) + +type HttpTransportConfig struct { + customHttpClientConfig HttpTransportInterface + defaultHttpClientConfig HttpTransportInterface +} + +func NewHttpTransportConfig(logger *zap.SugaredLogger) *HttpTransportConfig { + return &HttpTransportConfig{ + customHttpClientConfig: NewCustomK8sHttpTransportConfig(logger), + defaultHttpClientConfig: NewDefaultK8sHttpTransportConfig(), + } +} + +type HttpTransportInterface interface { + OverrideConfigWithCustomTransport(config *rest.Config) (*rest.Config, error) +} + +// OverrideConfigWithCustomTransport +// sets returns the given rest config without any modifications even if UseCustomTransport is enabled. +// This is used when we want to use the default rest.Config provided by the client-go library. +func (impl *DefaultK8sHttpTransportConfig) OverrideConfigWithCustomTransport(config *rest.Config) (*rest.Config, error) { + return config, nil +} + +// OverrideConfigWithCustomTransport +// overrides the given rest config with custom transport if UseCustomTransport is enabled. +// if the config already has a defined transport, we don't override it. +func (impl *CustomK8sHttpTransportConfig) OverrideConfigWithCustomTransport(config *rest.Config) (*rest.Config, error) { + if !impl.UseCustomTransport || config.Transport != nil { + return config, nil + } + + dial := (&net.Dialer{ + Timeout: time.Duration(impl.TimeOut) * time.Second, + KeepAlive: time.Duration(impl.KeepAlive) * time.Second, + }).DialContext + + // Get the TLS options for this client config + tlsConfig, err := rest.TLSConfigFor(config) + if err != nil { + return nil, err + } + + transport := utilnet.SetTransportDefaults(&http.Transport{ + Proxy: config.Proxy, + TLSHandshakeTimeout: time.Duration(impl.TLSHandshakeTimeout) * time.Second, + TLSClientConfig: tlsConfig, + MaxIdleConns: impl.MaxIdleConnsPerHost, + MaxConnsPerHost: impl.MaxIdleConnsPerHost, + MaxIdleConnsPerHost: impl.MaxIdleConnsPerHost, + DialContext: dial, + DisableCompression: config.DisableCompression, + IdleConnTimeout: time.Duration(impl.IdleConnTimeout) * time.Second, + }) + + rt, err := rest.HTTPWrappersForConfig(config, transport) + if err != nil { + return nil, err + } + + config.Transport = rt + config.Timeout = time.Duration(impl.TimeOut) * time.Second + + // set default tls config and remove auth/exec provides since we use it in a custom transport. + // we already set tls config in the transport + config.TLSClientConfig = rest.TLSClientConfig{} + config.AuthProvider = nil + config.ExecProvider = nil + + return config, nil +} diff --git a/vendor/github.com/devtron-labs/common-lib/utils/k8s/K8sService.go b/vendor/github.com/devtron-labs/common-lib/utils/k8s/K8sService.go new file mode 100644 index 0000000000..e055c1cbb1 --- /dev/null +++ b/vendor/github.com/devtron-labs/common-lib/utils/k8s/K8sService.go @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package k8s + +import ( + "context" + "flag" + "go.uber.org/zap" + "io" + batchV1 "k8s.io/api/batch/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/version" + "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + v12 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/rest" + "k8s.io/metrics/pkg/apis/metrics/v1beta1" + metrics "k8s.io/metrics/pkg/client/clientset/versioned" + "net/http" + "os/user" + "path/filepath" +) + +type K8sService interface { + GetLogsForAPod(kubeClient *kubernetes.Clientset, namespace string, podName string, container string, follow bool) *rest.Request + GetMetricsClientSet(restConfig *rest.Config, k8sHttpClient *http.Client) (*metrics.Clientset, error) + GetNmByName(ctx context.Context, metricsClientSet *metrics.Clientset, name string) (*v1beta1.NodeMetrics, error) + GetNmList(ctx context.Context, metricsClientSet *metrics.Clientset) (*v1beta1.NodeMetricsList, error) + GetPodsListForNamespace(ctx context.Context, k8sClientSet *kubernetes.Clientset, namespace string) (*v1.PodList, error) + GetServerVersionFromDiscoveryClient(k8sClientSet *kubernetes.Clientset) (*version.Info, error) + GetServerGroups(k8sClientSet *kubernetes.Clientset) (*metav1.APIGroupList, error) + GetNodeByName(ctx context.Context, k8sClientSet *kubernetes.Clientset, name string) (*v1.Node, error) + GetNodesList(ctx context.Context, k8sClientSet *kubernetes.Clientset) (*v1.NodeList, error) + GetCoreV1ClientByRestConfig(restConfig *rest.Config) (*v12.CoreV1Client, error) + GetCoreV1ClientInCluster(opts ...K8sServiceOpts) (*v12.CoreV1Client, error) + GetKubeVersion() (*version.Info, error) + ValidateResource(resourceObj map[string]interface{}, gvk schema.GroupVersionKind, validateCallback func(namespace string, group string, kind string, resourceName string) bool) bool + BuildK8sObjectListTableData(manifest *unstructured.UnstructuredList, namespaced bool, gvk schema.GroupVersionKind, includeMetadata bool, validateResourceAccess func(namespace string, group string, kind string, resourceName string) bool) (*ClusterResourceListMap, error) + ValidateForResource(namespace string, resourceRef interface{}, validateCallback func(namespace string, group string, kind string, resourceName string) bool) bool + GetPodByName(namespace string, name string, client *v12.CoreV1Client) (*v1.Pod, error) + GetResourceInfoByLabelSelector(ctx context.Context, namespace string, labelSelector string) (*v1.Pod, error) + GetClientByToken(serverUrl string, token map[string]string) (*v12.CoreV1Client, error) + ListNamespaces(client *v12.CoreV1Client) (*v1.NamespaceList, error) + DeleteAndCreateJob(content []byte, namespace string, clusterConfig *ClusterConfig) error + DeletePodByLabel(namespace string, labels string, clusterConfig *ClusterConfig, opts ...K8sServiceOpts) error + CreateJob(namespace string, name string, clusterConfig *ClusterConfig, job *batchV1.Job, opts ...K8sServiceOpts) error + GetLiveZCall(path string, k8sClientSet *kubernetes.Clientset) ([]byte, error) + DiscoveryClientGetLiveZCall(cluster *ClusterConfig, opts ...K8sServiceOpts) ([]byte, error) + DeleteJob(namespace string, name string, clusterConfig *ClusterConfig, opts ...K8sServiceOpts) error + DeleteSecret(namespace string, name string, client *v12.CoreV1Client) error + UpdateSecret(namespace string, secret *v1.Secret, client *v12.CoreV1Client) (*v1.Secret, error) + CreateSecretData(namespace string, secret *v1.Secret, v1Client *v12.CoreV1Client) (*v1.Secret, error) + CreateSecret(namespace string, data map[string][]byte, secretName string, secretType v1.SecretType, client *v12.CoreV1Client, labels map[string]string, stringData map[string]string) (*v1.Secret, error) + GetSecret(namespace string, name string, client *v12.CoreV1Client) (*v1.Secret, error) + GetSecretWithCtx(ctx context.Context, namespace string, name string, client *v12.CoreV1Client) (*v1.Secret, error) + PatchConfigMapJsonType(namespace string, clusterConfig *ClusterConfig, name string, data interface{}, path string) (*v1.ConfigMap, error) + PatchConfigMap(namespace string, clusterConfig *ClusterConfig, name string, data map[string]interface{}) (*v1.ConfigMap, error) + UpdateConfigMap(namespace string, cm *v1.ConfigMap, client *v12.CoreV1Client) (*v1.ConfigMap, error) + CreateConfigMap(namespace string, cm *v1.ConfigMap, client *v12.CoreV1Client) (*v1.ConfigMap, error) + GetConfigMap(namespace string, name string, client *v12.CoreV1Client) (*v1.ConfigMap, error) + GetConfigMapWithCtx(ctx context.Context, namespace string, name string, client *v12.CoreV1Client) (*v1.ConfigMap, error) + GetNsIfExists(namespace string, client *v12.CoreV1Client) (ns *v1.Namespace, exists bool, err error) + CreateNsIfNotExists(namespace string, clusterConfig *ClusterConfig) (ns *v1.Namespace, nsCreated bool, err error) + UpdateNSLabels(namespace *v1.Namespace, labels map[string]string, clusterConfig *ClusterConfig) (ns *v1.Namespace, err error) + GetK8sDiscoveryClientInCluster(opts ...K8sServiceOpts) (*discovery.DiscoveryClient, error) + GetK8sDiscoveryClient(clusterConfig *ClusterConfig, opts ...K8sServiceOpts) (*discovery.DiscoveryClient, error) + GetClientForInCluster(opts ...K8sServiceOpts) (*v12.CoreV1Client, error) + GetCoreV1Client(clusterConfig *ClusterConfig, opts ...K8sServiceOpts) (*v12.CoreV1Client, error) + GetResource(ctx context.Context, namespace string, name string, gvk schema.GroupVersionKind, restConfig *rest.Config) (*ManifestResponse, error) + UpdateResource(ctx context.Context, restConfig *rest.Config, gvk schema.GroupVersionKind, namespace string, k8sRequestPatch string) (*ManifestResponse, error) + DeleteResource(ctx context.Context, restConfig *rest.Config, gvk schema.GroupVersionKind, namespace string, name string, forceDelete bool) (*ManifestResponse, error) + GetPodListByLabel(namespace, label string, clientSet *kubernetes.Clientset) ([]v1.Pod, error) + ExtractK8sServerMajorAndMinorVersion(k8sServerVersion *version.Info) (int, int, error) + GetK8sServerVersion(clientSet *kubernetes.Clientset) (*version.Info, error) + DecodeGroupKindversion(data string) (*schema.GroupVersionKind, error) + GetApiResources(restConfig *rest.Config, includeOnlyVerb string) ([]*K8sApiResource, error) + CreateResources(ctx context.Context, restConfig *rest.Config, manifest string, gvk schema.GroupVersionKind, namespace string) (*ManifestResponse, error) + PatchResourceRequest(ctx context.Context, restConfig *rest.Config, pt types.PatchType, manifest string, name string, namespace string, gvk schema.GroupVersionKind) (*ManifestResponse, error) + GetResourceList(ctx context.Context, restConfig *rest.Config, gvk schema.GroupVersionKind, namespace string, asTable bool, listOptions *metav1.ListOptions) (*ResourceListResponse, bool, error) + GetResourceIfWithAcceptHeader(restConfig *rest.Config, groupVersionKind schema.GroupVersionKind, asTable bool) (resourceIf dynamic.NamespaceableResourceInterface, namespaced bool, err error) + GetPodLogs(ctx context.Context, restConfig *rest.Config, name string, namespace string, sinceTime *metav1.Time, tailLines int, sinceSeconds int, follow bool, containerName string, isPrevContainerLogsEnabled bool) (io.ReadCloser, error) + ListEvents(restConfig *rest.Config, namespace string, groupVersionKind schema.GroupVersionKind, ctx context.Context, name string) (*v1.EventList, error) + GetResourceIf(restConfig *rest.Config, groupVersionKind schema.GroupVersionKind) (resourceIf dynamic.NamespaceableResourceInterface, namespaced bool, err error) + FetchConnectionStatusForCluster(k8sClientSet *kubernetes.Clientset) error + CreateK8sClientSet(restConfig *rest.Config) (*kubernetes.Clientset, error) + CreateOrUpdateSecretByName(client *v12.CoreV1Client, namespace, uniqueSecretName string, secretLabel map[string]string, secretData map[string]string) error + + // below functions are exposed for K8sUtilExtended + + CreateNsWithLabels(namespace string, labels map[string]string, client *v12.CoreV1Client) (ns *v1.Namespace, err error) + CreateNs(namespace string, client *v12.CoreV1Client) (ns *v1.Namespace, err error) + GetGVRForCRD(config *rest.Config, CRDName string) (schema.GroupVersionResource, error) + GetResourceByGVR(ctx context.Context, config *rest.Config, GVR schema.GroupVersionResource, resourceName, namespace string) (*unstructured.Unstructured, error) + PatchResourceByGVR(ctx context.Context, config *rest.Config, GVR schema.GroupVersionResource, resourceName, namespace string, patchType types.PatchType, patchData []byte) (*unstructured.Unstructured, error) + DeleteResourceByGVR(ctx context.Context, config *rest.Config, GVR schema.GroupVersionResource, resourceName, namespace string, forceDelete bool) error + GetRestClientForCRD(config *ClusterConfig, groupVersion *schema.GroupVersion) (*rest.RESTClient, error) + PatchResourceByRestClient(restClient *rest.RESTClient, resource, name, namespace string, pt types.PatchType, data []byte, subresources ...string) rest.Result + + // k8s rest config methods + + GetK8sInClusterRestConfig(opts ...K8sServiceOpts) (*rest.Config, error) + GetK8sConfigAndClients(clusterConfig *ClusterConfig, opts ...K8sServiceOpts) (*rest.Config, *http.Client, *kubernetes.Clientset, error) + GetK8sInClusterConfigAndDynamicClients(opts ...K8sServiceOpts) (*rest.Config, *http.Client, dynamic.Interface, error) + GetK8sInClusterConfigAndClients(opts ...K8sServiceOpts) (*rest.Config, *http.Client, *kubernetes.Clientset, error) + GetRestConfigByCluster(clusterConfig *ClusterConfig, opts ...K8sServiceOpts) (*rest.Config, error) + OverrideRestConfigWithCustomTransport(restConfig *rest.Config, opts ...K8sServiceOpts) (*rest.Config, error) + GetK8sConfigAndClientsByRestConfig(restConfig *rest.Config, opts ...K8sServiceOpts) (*http.Client, *kubernetes.Clientset, error) +} + +type K8sServiceImpl struct { + logger *zap.SugaredLogger + runTimeConfig *RuntimeConfig + kubeConfigBuilder KubeConfigBuilderInterface + httpTransportConfig *HttpTransportConfig + kubeconfig *string + opts Options +} + +func (impl *K8sServiceImpl) SetCustomHttpClientConfig(customHttpClientConfig HttpTransportInterface) *K8sServiceImpl { + impl.httpTransportConfig.customHttpClientConfig = customHttpClientConfig + return impl +} + +func (impl *K8sServiceImpl) GetCustomHttpClientConfig() HttpTransportInterface { + return impl.httpTransportConfig.customHttpClientConfig +} + +func (impl *K8sServiceImpl) SetDefaultHttpClientConfig(defaultHttpClientConfig HttpTransportInterface) *K8sServiceImpl { + impl.httpTransportConfig.defaultHttpClientConfig = defaultHttpClientConfig + return impl +} + +func (impl *K8sServiceImpl) GetDefaultHttpClientConfig() HttpTransportInterface { + return impl.httpTransportConfig.defaultHttpClientConfig +} + +type Options struct { + transportType TransportType +} + +func (opt *Options) SetTransportType(transportType TransportType) { + opt.transportType = transportType +} + +func (opt *Options) GetTransportType() TransportType { + return opt.transportType +} + +func NewK8sUtil( + logger *zap.SugaredLogger, + runTimeConfig *RuntimeConfig, +) (*K8sServiceImpl, error) { + return NewK8sUtilBuilder(logger, runTimeConfig, NewKubeConfigBuilder()) +} + +func NewK8sUtilBuilder( + logger *zap.SugaredLogger, + runTimeConfig *RuntimeConfig, + kubeConfigBuilder KubeConfigBuilderInterface, +) (*K8sServiceImpl, error) { + var kubeconfig *string + if runTimeConfig.LocalDevMode { + usr, err := user.Current() + if err != nil { + logger.Errorw("error in NewK8sUtil, failed to get current user", "err", err) + return nil, err + } + kubeconfig = flag.String("kubeconfig-authenticator-xyz", filepath.Join(usr.HomeDir, ".kube", "config"), "(optional) absolute path to the kubeconfig file") + } + flag.Parse() + return &K8sServiceImpl{ + logger: logger, + runTimeConfig: runTimeConfig, + kubeconfig: kubeconfig, + httpTransportConfig: NewHttpTransportConfig(logger), + kubeConfigBuilder: kubeConfigBuilder, + }, nil +} + +func (impl *K8sServiceImpl) NewKubeConfigImpl( + httpTransportConfig HttpTransportInterface, +) *KubeConfigImpl { + return NewKubeConfigImpl( + impl.logger, + impl.runTimeConfig, + impl.kubeconfig, + httpTransportConfig, + impl.kubeConfigBuilder, + ) +} + +// WithHttpTransport toggles between default and overridden transport. +// This is used to create a new KubeConfigImpl with the specified transport type. +// It is used in K8sUtilExtended to override the NewKubeConfigBuilder. +// NOTE: Any modifications here is subject to change in K8sUtilExtended as well. +func (impl *K8sServiceImpl) WithHttpTransport(opt Options) KubeConfigInterface { + switch opt.GetTransportType() { + case TransportTypeDefault: + return impl.NewKubeConfigImpl(impl.GetDefaultHttpClientConfig()) + default: + // default fallback is custom transport + return impl.NewKubeConfigImpl(impl.GetCustomHttpClientConfig()) + } +} + +type K8sServiceOpts func(Options) Options + +// WithDefaultHttpTransport ensures the transport type is default and rest config is not modified +// This is necessary when we use clients such as SPDY, that has its own transport +func WithDefaultHttpTransport() K8sServiceOpts { + return func(opts Options) Options { + opts.transportType = TransportTypeDefault + return opts + } +} + +// WithOverriddenHttpTransport ensures the transport is overridden and rest config is modified +func WithOverriddenHttpTransport() K8sServiceOpts { + return func(opts Options) Options { + opts.transportType = TransportTypeOverridden + return opts + } +} diff --git a/vendor/github.com/devtron-labs/common-lib/utils/k8s/K8sUtil.go b/vendor/github.com/devtron-labs/common-lib/utils/k8s/K8sUtil.go index 49cdd3a3e6..ac38ea06e8 100644 --- a/vendor/github.com/devtron-labs/common-lib/utils/k8s/K8sUtil.go +++ b/vendor/github.com/devtron-labs/common-lib/utils/k8s/K8sUtil.go @@ -20,17 +20,13 @@ import ( "context" "encoding/json" error2 "errors" - "flag" "fmt" "github.com/devtron-labs/common-lib/utils" http2 "github.com/devtron-labs/common-lib/utils/http" "github.com/devtron-labs/common-lib/utils/k8s/commonBean" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "io" - v13 "k8s.io/api/policy/v1" - v1beta12 "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/validation" "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes/scheme" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/metrics/pkg/apis/metrics/v1beta1" metrics "k8s.io/metrics/pkg/client/clientset/versioned" @@ -38,8 +34,6 @@ import ( "log" "net/http" "net/url" - "os/user" - "path/filepath" "strconv" "strings" "time" @@ -48,8 +42,6 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/version" - "go.uber.org/zap" - v14 "k8s.io/api/apps/v1" batchV1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" @@ -60,157 +52,19 @@ import ( "k8s.io/client-go/kubernetes" v12 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/yaml" ) -type K8sServiceImpl struct { - logger *zap.SugaredLogger - runTimeConfig *RuntimeConfig - httpClientConfig *CustomK8sHttpTransportConfig - kubeconfig *string -} - -type K8sService interface { - GetLogsForAPod(kubeClient *kubernetes.Clientset, namespace string, podName string, container string, follow bool) *rest.Request - GetMetricsClientSet(restConfig *rest.Config, k8sHttpClient *http.Client) (*metrics.Clientset, error) - GetNmByName(ctx context.Context, metricsClientSet *metrics.Clientset, name string) (*v1beta1.NodeMetrics, error) - GetNmList(ctx context.Context, metricsClientSet *metrics.Clientset) (*v1beta1.NodeMetricsList, error) - GetPodsListForNamespace(ctx context.Context, k8sClientSet *kubernetes.Clientset, namespace string) (*v1.PodList, error) - GetServerVersionFromDiscoveryClient(k8sClientSet *kubernetes.Clientset) (*version.Info, error) - GetServerGroups(k8sClientSet *kubernetes.Clientset) (*metav1.APIGroupList, error) - GetNodeByName(ctx context.Context, k8sClientSet *kubernetes.Clientset, name string) (*v1.Node, error) - GetNodesList(ctx context.Context, k8sClientSet *kubernetes.Clientset) (*v1.NodeList, error) - GetCoreV1ClientByRestConfig(restConfig *rest.Config) (*v12.CoreV1Client, error) - GetCoreV1ClientInCluster() (*v12.CoreV1Client, error) - GetKubeVersion() (*version.Info, error) - ValidateResource(resourceObj map[string]interface{}, gvk schema.GroupVersionKind, validateCallback func(namespace string, group string, kind string, resourceName string) bool) bool - BuildK8sObjectListTableData(manifest *unstructured.UnstructuredList, namespaced bool, gvk schema.GroupVersionKind, includeMetadata bool, validateResourceAccess func(namespace string, group string, kind string, resourceName string) bool) (*ClusterResourceListMap, error) - ValidateForResource(namespace string, resourceRef interface{}, validateCallback func(namespace string, group string, kind string, resourceName string) bool) bool - GetPodByName(namespace string, name string, client *v12.CoreV1Client) (*v1.Pod, error) - GetK8sInClusterRestConfig() (*rest.Config, error) - GetResourceInfoByLabelSelector(ctx context.Context, namespace string, labelSelector string) (*v1.Pod, error) - GetClientByToken(serverUrl string, token map[string]string) (*v12.CoreV1Client, error) - ListNamespaces(client *v12.CoreV1Client) (*v1.NamespaceList, error) - DeleteAndCreateJob(content []byte, namespace string, clusterConfig *ClusterConfig) error - DeletePodByLabel(namespace string, labels string, clusterConfig *ClusterConfig) error - CreateJob(namespace string, name string, clusterConfig *ClusterConfig, job *batchV1.Job) error - GetLiveZCall(path string, k8sClientSet *kubernetes.Clientset) ([]byte, error) - DiscoveryClientGetLiveZCall(cluster *ClusterConfig) ([]byte, error) - GetK8sConfigAndClientsByRestConfig(restConfig *rest.Config) (*http.Client, *kubernetes.Clientset, error) - GetK8sConfigAndClients(clusterConfig *ClusterConfig) (*rest.Config, *http.Client, *kubernetes.Clientset, error) - GetK8sInClusterConfigAndDynamicClients() (*rest.Config, *http.Client, dynamic.Interface, error) - GetK8sInClusterConfigAndClients() (*rest.Config, *http.Client, *kubernetes.Clientset, error) - DeleteJob(namespace string, name string, clusterConfig *ClusterConfig) error - DeleteSecret(namespace string, name string, client *v12.CoreV1Client) error - UpdateSecret(namespace string, secret *v1.Secret, client *v12.CoreV1Client) (*v1.Secret, error) - CreateSecretData(namespace string, secret *v1.Secret, v1Client *v12.CoreV1Client) (*v1.Secret, error) - CreateSecret(namespace string, data map[string][]byte, secretName string, secretType v1.SecretType, client *v12.CoreV1Client, labels map[string]string, stringData map[string]string) (*v1.Secret, error) - GetSecret(namespace string, name string, client *v12.CoreV1Client) (*v1.Secret, error) - GetSecretWithCtx(ctx context.Context, namespace string, name string, client *v12.CoreV1Client) (*v1.Secret, error) - PatchConfigMapJsonType(namespace string, clusterConfig *ClusterConfig, name string, data interface{}, path string) (*v1.ConfigMap, error) - PatchConfigMap(namespace string, clusterConfig *ClusterConfig, name string, data map[string]interface{}) (*v1.ConfigMap, error) - UpdateConfigMap(namespace string, cm *v1.ConfigMap, client *v12.CoreV1Client) (*v1.ConfigMap, error) - CreateConfigMap(namespace string, cm *v1.ConfigMap, client *v12.CoreV1Client) (*v1.ConfigMap, error) - GetConfigMap(namespace string, name string, client *v12.CoreV1Client) (*v1.ConfigMap, error) - GetConfigMapWithCtx(ctx context.Context, namespace string, name string, client *v12.CoreV1Client) (*v1.ConfigMap, error) - GetNsIfExists(namespace string, client *v12.CoreV1Client) (ns *v1.Namespace, exists bool, err error) - CreateNsIfNotExists(namespace string, clusterConfig *ClusterConfig) (ns *v1.Namespace, nsCreated bool, err error) - UpdateNSLabels(namespace *v1.Namespace, labels map[string]string, clusterConfig *ClusterConfig) (ns *v1.Namespace, err error) - GetK8sDiscoveryClientInCluster() (*discovery.DiscoveryClient, error) - GetK8sDiscoveryClient(clusterConfig *ClusterConfig) (*discovery.DiscoveryClient, error) - GetClientForInCluster() (*v12.CoreV1Client, error) - GetCoreV1Client(clusterConfig *ClusterConfig) (*v12.CoreV1Client, error) - GetRestConfigByCluster(clusterConfig *ClusterConfig) (*rest.Config, error) - GetResource(ctx context.Context, namespace string, name string, gvk schema.GroupVersionKind, restConfig *rest.Config) (*ManifestResponse, error) - UpdateResource(ctx context.Context, restConfig *rest.Config, gvk schema.GroupVersionKind, namespace string, k8sRequestPatch string) (*ManifestResponse, error) - DeleteResource(ctx context.Context, restConfig *rest.Config, gvk schema.GroupVersionKind, namespace string, name string, forceDelete bool) (*ManifestResponse, error) - GetPodListByLabel(namespace, label string, clientSet *kubernetes.Clientset) ([]v1.Pod, error) - ExtractK8sServerMajorAndMinorVersion(k8sServerVersion *version.Info) (int, int, error) - GetK8sServerVersion(clientSet *kubernetes.Clientset) (*version.Info, error) - DecodeGroupKindversion(data string) (*schema.GroupVersionKind, error) - GetApiResources(restConfig *rest.Config, includeOnlyVerb string) ([]*K8sApiResource, error) - CreateResources(ctx context.Context, restConfig *rest.Config, manifest string, gvk schema.GroupVersionKind, namespace string) (*ManifestResponse, error) - PatchResourceRequest(ctx context.Context, restConfig *rest.Config, pt types.PatchType, manifest string, name string, namespace string, gvk schema.GroupVersionKind) (*ManifestResponse, error) - GetResourceList(ctx context.Context, restConfig *rest.Config, gvk schema.GroupVersionKind, namespace string, asTable bool, listOptions *metav1.ListOptions) (*ResourceListResponse, bool, error) - GetResourceIfWithAcceptHeader(restConfig *rest.Config, groupVersionKind schema.GroupVersionKind, asTable bool) (resourceIf dynamic.NamespaceableResourceInterface, namespaced bool, err error) - GetPodLogs(ctx context.Context, restConfig *rest.Config, name string, namespace string, sinceTime *metav1.Time, tailLines int, sinceSeconds int, follow bool, containerName string, isPrevContainerLogsEnabled bool) (io.ReadCloser, error) - ListEvents(restConfig *rest.Config, namespace string, groupVersionKind schema.GroupVersionKind, ctx context.Context, name string) (*v1.EventList, error) - GetResourceIf(restConfig *rest.Config, groupVersionKind schema.GroupVersionKind) (resourceIf dynamic.NamespaceableResourceInterface, namespaced bool, err error) - FetchConnectionStatusForCluster(k8sClientSet *kubernetes.Clientset) error - CreateK8sClientSet(restConfig *rest.Config) (*kubernetes.Clientset, error) - CreateOrUpdateSecretByName(client *v12.CoreV1Client, namespace, uniqueSecretName string, secretLabel map[string]string, secretData map[string]string) error - //CreateK8sClientSetWithCustomHttpTransport(restConfig *rest.Config) (*kubernetes.Clientset, error) - - //below functions are exposed for K8sUtilExtended - GetRestConfigByClusterWithoutCustomTransport(clusterConfig *ClusterConfig) (*rest.Config, error) - OverrideRestConfigWithCustomTransport(restConfig *rest.Config) (*rest.Config, error) - CreateNsWithLabels(namespace string, labels map[string]string, client *v12.CoreV1Client) (ns *v1.Namespace, err error) - CreateNs(namespace string, client *v12.CoreV1Client) (ns *v1.Namespace, err error) - GetGVRForCRD(config *rest.Config, CRDName string) (schema.GroupVersionResource, error) - GetResourceByGVR(ctx context.Context, config *rest.Config, GVR schema.GroupVersionResource, resourceName, namespace string) (*unstructured.Unstructured, error) - PatchResourceByGVR(ctx context.Context, config *rest.Config, GVR schema.GroupVersionResource, resourceName, namespace string, patchType types.PatchType, patchData []byte) (*unstructured.Unstructured, error) - DeleteResourceByGVR(ctx context.Context, config *rest.Config, GVR schema.GroupVersionResource, resourceName, namespace string, forceDelete bool) error -} - -func NewK8sUtil(logger *zap.SugaredLogger, runTimeConfig *RuntimeConfig) (*K8sServiceImpl, error) { - var kubeconfig *string - if runTimeConfig.LocalDevMode { - usr, err := user.Current() - if err != nil { - logger.Errorw("error in NewK8sUtil, failed to get current user", "err", err) - return nil, err - } - kubeconfig = flag.String("kubeconfig-authenticator-xyz", filepath.Join(usr.HomeDir, ".kube", "config"), "(optional) absolute path to the kubeconfig file") - } - - httpClientConfig := NewCustomK8sHttpTransportConfig() - flag.Parse() - return &K8sServiceImpl{logger: logger, runTimeConfig: runTimeConfig, kubeconfig: kubeconfig, httpClientConfig: httpClientConfig}, nil -} - -func (impl *K8sServiceImpl) GetRestConfigByCluster(clusterConfig *ClusterConfig) (*rest.Config, error) { - restConfig, err := impl.GetRestConfigByClusterWithoutCustomTransport(clusterConfig) - if err != nil { - impl.logger.Errorw("error, GetRestConfigByClusterWithoutCustomTransport", "err", err) - return nil, err - } - restConfig, err = impl.OverrideRestConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding rest config with custom transport configurations", "err", err) - } - return restConfig, err -} - -func (impl *K8sServiceImpl) GetRestConfigByClusterWithoutCustomTransport(clusterConfig *ClusterConfig) (*rest.Config, error) { - bearerToken := clusterConfig.BearerToken - var restConfig *rest.Config - var err error - if clusterConfig.Host == commonBean.DefaultClusterUrl && len(bearerToken) == 0 { - restConfig, err = impl.GetK8sInClusterRestConfig() - if err != nil { - impl.logger.Errorw("error in getting rest config for default cluster", "err", err) - return nil, err - } - } else { - restConfig = &rest.Config{Host: clusterConfig.Host, BearerToken: bearerToken} - clusterConfig.PopulateTlsConfigurationsInto(restConfig) - } - return restConfig, nil -} - -func (impl *K8sServiceImpl) OverrideRestConfigWithCustomTransport(restConfig *rest.Config) (*rest.Config, error) { - var err error - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding rest config with custom transport configurations", "err", err) - return nil, err +func (impl *K8sServiceImpl) getOpts(opts []K8sServiceOpts) Options { + customOpts := impl.opts + for _, opt := range opts { + customOpts = opt(customOpts) } - return restConfig, nil + return customOpts } -func (impl *K8sServiceImpl) GetCoreV1Client(clusterConfig *ClusterConfig) (*v12.CoreV1Client, error) { - cfg, err := impl.GetRestConfigByCluster(clusterConfig) +func (impl *K8sServiceImpl) GetCoreV1Client(clusterConfig *ClusterConfig, opts ...K8sServiceOpts) (*v12.CoreV1Client, error) { + cfg, err := impl.GetRestConfigByCluster(clusterConfig, opts...) if err != nil { impl.logger.Errorw("error in getting rest config for default cluster", "err", err) return nil, err @@ -218,20 +72,14 @@ func (impl *K8sServiceImpl) GetCoreV1Client(clusterConfig *ClusterConfig) (*v12. return impl.GetCoreV1ClientByRestConfig(cfg) } -func (impl *K8sServiceImpl) GetClientForInCluster() (*v12.CoreV1Client, error) { +func (impl *K8sServiceImpl) GetClientForInCluster(opts ...K8sServiceOpts) (*v12.CoreV1Client, error) { // creates the in-cluster config - config, err := impl.GetK8sInClusterRestConfig() + config, err := impl.GetK8sInClusterRestConfig(opts...) if err != nil { impl.logger.Errorw("error in getting config", "err", err) return nil, err } - config, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(config) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, err - } - // creates the clientset httpClient, err := OverrideK8sHttpClientWithTracer(config) if err != nil { @@ -246,19 +94,13 @@ func (impl *K8sServiceImpl) GetClientForInCluster() (*v12.CoreV1Client, error) { return clientset, err } -func (impl *K8sServiceImpl) GetK8sDiscoveryClient(clusterConfig *ClusterConfig) (*discovery.DiscoveryClient, error) { - cfg, err := impl.GetRestConfigByCluster(clusterConfig) +func (impl *K8sServiceImpl) GetK8sDiscoveryClient(clusterConfig *ClusterConfig, opts ...K8sServiceOpts) (*discovery.DiscoveryClient, error) { + cfg, err := impl.GetRestConfigByCluster(clusterConfig, opts...) if err != nil { impl.logger.Errorw("error in getting rest config for default cluster", "err", err) return nil, err } - cfg, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(cfg) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, err - } - httpClient, err := OverrideK8sHttpClientWithTracer(cfg) if err != nil { impl.logger.Errorw("error in getting http client for default cluster", "err", err) @@ -272,19 +114,13 @@ func (impl *K8sServiceImpl) GetK8sDiscoveryClient(clusterConfig *ClusterConfig) return discoveryClient, err } -func (impl *K8sServiceImpl) GetK8sDiscoveryClientInCluster() (*discovery.DiscoveryClient, error) { - config, err := impl.GetK8sInClusterRestConfig() +func (impl *K8sServiceImpl) GetK8sDiscoveryClientInCluster(opts ...K8sServiceOpts) (*discovery.DiscoveryClient, error) { + config, err := impl.GetK8sInClusterRestConfig(opts...) if err != nil { impl.logger.Errorw("error in getting config", "err", err) return nil, err } - config, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(config) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, err - } - httpClient, err := OverrideK8sHttpClientWithTracer(config) if err != nil { impl.logger.Errorw("error in getting http client for default cluster", "err", err) @@ -377,11 +213,6 @@ func (impl *K8sServiceImpl) CreateNsWithLabels(namespace string, labels map[stri } } -func (impl *K8sServiceImpl) deleteNs(namespace string, client *v12.CoreV1Client) error { - err := client.Namespaces().Delete(context.Background(), namespace, metav1.DeleteOptions{}) - return err -} - func (impl *K8sServiceImpl) GetConfigMap(namespace string, name string, client *v12.CoreV1Client) (*v1.ConfigMap, error) { return impl.GetConfigMapWithCtx(context.Background(), namespace, name, client) } @@ -468,12 +299,6 @@ func (impl *K8sServiceImpl) PatchConfigMapJsonType(namespace string, clusterConf return cm, nil } -type JsonPatchType struct { - Op string `json:"op"` - Path string `json:"path"` - Value interface{} `json:"value"` -} - func (impl *K8sServiceImpl) GetSecret(namespace string, name string, client *v12.CoreV1Client) (*v1.Secret, error) { return impl.GetSecretWithCtx(context.Background(), namespace, name, client) } @@ -533,8 +358,8 @@ func (impl *K8sServiceImpl) DeleteSecret(namespace string, name string, client * return nil } -func (impl *K8sServiceImpl) DeleteJob(namespace string, name string, clusterConfig *ClusterConfig) error { - _, _, clientSet, err := impl.GetK8sConfigAndClients(clusterConfig) +func (impl *K8sServiceImpl) DeleteJob(namespace string, name string, clusterConfig *ClusterConfig, opts ...K8sServiceOpts) error { + _, _, clientSet, err := impl.GetK8sConfigAndClients(clusterConfig, opts...) if err != nil { impl.logger.Errorw("clientSet err, DeleteJob", "err", err) return err @@ -558,47 +383,6 @@ func (impl *K8sServiceImpl) DeleteJob(namespace string, name string, clusterConf return nil } -func (impl *K8sServiceImpl) GetK8sInClusterConfigAndClients() (*rest.Config, *http.Client, *kubernetes.Clientset, error) { - restConfig, err := impl.GetK8sInClusterRestConfig() - if err != nil { - impl.logger.Errorw("error in getting rest config for in cluster", "err", err) - return nil, nil, nil, err - } - - k8sHttpClient, k8sClientSet, err := impl.GetK8sConfigAndClientsByRestConfig(restConfig) - if err != nil { - impl.logger.Errorw("error in getting client set by rest config for in cluster", "err", err) - return nil, nil, nil, err - } - return restConfig, k8sHttpClient, k8sClientSet, nil -} - -func (impl *K8sServiceImpl) GetK8sInClusterConfigAndDynamicClients() (*rest.Config, *http.Client, dynamic.Interface, error) { - restConfig, err := impl.GetK8sInClusterRestConfig() - if err != nil { - impl.logger.Errorw("error in getting rest config for in cluster", "err", err) - return nil, nil, nil, err - } - - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, nil, nil, err - } - - k8sHttpClient, err := OverrideK8sHttpClientWithTracer(restConfig) - if err != nil { - impl.logger.Errorw("error in getting k8s http client set by rest config for in cluster", "err", err) - return nil, nil, nil, err - } - dynamicClientSet, err := dynamic.NewForConfigAndClient(restConfig, k8sHttpClient) - if err != nil { - impl.logger.Errorw("error in getting client set by rest config for in cluster", "err", err) - return nil, nil, nil, err - } - return restConfig, k8sHttpClient, dynamicClientSet, nil -} - func (impl *K8sServiceImpl) GetK8sDynamicClient(restConfig *rest.Config, k8sHttpClient *http.Client) (dynamic.Interface, error) { dynamicClientSet, err := dynamic.NewForConfigAndClient(restConfig, k8sHttpClient) if err != nil { @@ -608,44 +392,8 @@ func (impl *K8sServiceImpl) GetK8sDynamicClient(restConfig *rest.Config, k8sHttp return dynamicClientSet, nil } -func (impl *K8sServiceImpl) GetK8sConfigAndClients(clusterConfig *ClusterConfig) (*rest.Config, *http.Client, *kubernetes.Clientset, error) { - restConfig, err := impl.GetRestConfigByCluster(clusterConfig) - if err != nil { - impl.logger.Errorw("error in getting rest config by cluster", "err", err, "clusterName", clusterConfig.ClusterName) - return nil, nil, nil, err - } - - k8sHttpClient, k8sClientSet, err := impl.GetK8sConfigAndClientsByRestConfig(restConfig) - if err != nil { - impl.logger.Errorw("error in getting client set by rest config", "err", err, "clusterName", clusterConfig.ClusterName) - return nil, nil, nil, err - } - return restConfig, k8sHttpClient, k8sClientSet, nil -} - -func (impl *K8sServiceImpl) GetK8sConfigAndClientsByRestConfig(restConfig *rest.Config) (*http.Client, *kubernetes.Clientset, error) { - var err error - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, nil, err - } - - k8sHttpClient, err := OverrideK8sHttpClientWithTracer(restConfig) - if err != nil { - impl.logger.Errorw("error in getting k8s http client set by rest config", "err", err) - return nil, nil, err - } - k8sClientSet, err := kubernetes.NewForConfigAndClient(restConfig, k8sHttpClient) - if err != nil { - impl.logger.Errorw("error in getting client set by rest config", "err", err) - return nil, nil, err - } - return k8sHttpClient, k8sClientSet, nil -} - -func (impl *K8sServiceImpl) DiscoveryClientGetLiveZCall(cluster *ClusterConfig) ([]byte, error) { - _, _, k8sClientSet, err := impl.GetK8sConfigAndClients(cluster) +func (impl *K8sServiceImpl) DiscoveryClientGetLiveZCall(cluster *ClusterConfig, opts ...K8sServiceOpts) ([]byte, error) { + _, _, k8sClientSet, err := impl.GetK8sConfigAndClients(cluster, opts...) if err != nil { impl.logger.Errorw("errir in getting clients and configs", "err", err, "clusterName", cluster.ClusterName) return nil, err @@ -659,6 +407,7 @@ func (impl *K8sServiceImpl) DiscoveryClientGetLiveZCall(cluster *ClusterConfig) return response, err } + func (impl *K8sServiceImpl) GetLiveZCall(path string, k8sClientSet *kubernetes.Clientset) ([]byte, error) { response, err := k8sClientSet.Discovery().RESTClient().Get().AbsPath(path).DoRaw(context.Background()) if err != nil { @@ -668,8 +417,8 @@ func (impl *K8sServiceImpl) GetLiveZCall(path string, k8sClientSet *kubernetes.C return response, err } -func (impl *K8sServiceImpl) CreateJob(namespace string, name string, clusterConfig *ClusterConfig, job *batchV1.Job) error { - _, _, clientSet, err := impl.GetK8sConfigAndClients(clusterConfig) +func (impl *K8sServiceImpl) CreateJob(namespace string, name string, clusterConfig *ClusterConfig, job *batchV1.Job, opts ...K8sServiceOpts) error { + _, _, clientSet, err := impl.GetK8sConfigAndClients(clusterConfig, opts...) if err != nil { impl.logger.Errorw("clientSet err, CreateJob", "err", err) } @@ -696,8 +445,8 @@ func (impl *K8sServiceImpl) CreateJob(namespace string, name string, clusterConf // DeletePod delete pods with label job-name -func (impl *K8sServiceImpl) DeletePodByLabel(namespace string, labels string, clusterConfig *ClusterConfig) error { - _, _, clientSet, err := impl.GetK8sConfigAndClients(clusterConfig) +func (impl *K8sServiceImpl) DeletePodByLabel(namespace string, labels string, clusterConfig *ClusterConfig, opts ...K8sServiceOpts) error { + _, _, clientSet, err := impl.GetK8sConfigAndClients(clusterConfig, opts...) if err != nil { impl.logger.Errorw("clientSet err, DeletePod", "err", err) return err @@ -802,25 +551,6 @@ func (impl *K8sServiceImpl) GetResourceInfoByLabelSelector(ctx context.Context, } } -func (impl *K8sServiceImpl) GetK8sInClusterRestConfig() (*rest.Config, error) { - impl.logger.Debug("getting k8s rest config") - if impl.runTimeConfig.LocalDevMode { - restConfig, err := clientcmd.BuildConfigFromFlags("", *impl.kubeconfig) - if err != nil { - impl.logger.Errorw("Error while building config from flags", "error", err) - return nil, err - } - return restConfig, nil - } else { - clusterConfig, err := rest.InClusterConfig() - if err != nil { - impl.logger.Errorw("error in fetch default cluster config", "err", err) - return nil, err - } - return clusterConfig, nil - } -} - func (impl *K8sServiceImpl) GetPodByName(namespace string, name string, client *v12.CoreV1Client) (*v1.Pod, error) { pod, err := client.Pods(namespace).Get(context.Background(), name, metav1.GetOptions{}) if err != nil { @@ -1026,29 +756,6 @@ func (impl *K8sServiceImpl) ValidateForResource(namespace string, resourceRef in return false } -func (impl *K8sServiceImpl) getEventKindHeader() ([]string, map[int]string) { - headers := []string{"type", "message", "namespace", "involved object", "source", "count", "age", "last seen"} - columnIndexes := make(map[int]string) - columnIndexes[0] = "last seen" - columnIndexes[1] = "type" - columnIndexes[2] = "namespace" - columnIndexes[3] = "involved object" - columnIndexes[5] = "source" - columnIndexes[6] = "message" - columnIndexes[7] = "age" - columnIndexes[8] = "count" - return headers, columnIndexes -} - -func OverrideK8sHttpClientWithTracer(restConfig *rest.Config) (*http.Client, error) { - httpClientFor, err := rest.HTTPClientFor(restConfig) - if err != nil { - fmt.Println("error occurred while overriding k8s client", "reason", err) - return nil, err - } - httpClientFor.Transport = otelhttp.NewTransport(httpClientFor.Transport) - return httpClientFor, nil -} func (impl *K8sServiceImpl) GetKubeVersion() (*version.Info, error) { discoveryClient, err := impl.GetK8sDiscoveryClientInCluster() if err != nil { @@ -1063,9 +770,9 @@ func (impl *K8sServiceImpl) GetKubeVersion() (*version.Info, error) { return k8sServerVersion, err } -func (impl *K8sServiceImpl) GetCoreV1ClientInCluster() (*v12.CoreV1Client, error) { +func (impl *K8sServiceImpl) GetCoreV1ClientInCluster(opts ...K8sServiceOpts) (*v12.CoreV1Client, error) { restConfig := &rest.Config{} - restConfig, err := impl.GetK8sInClusterRestConfig() + restConfig, err := impl.GetK8sInClusterRestConfig(opts...) if err != nil { impl.logger.Error("Error in creating config for default cluster", "err", err) return nil, err @@ -1074,14 +781,6 @@ func (impl *K8sServiceImpl) GetCoreV1ClientInCluster() (*v12.CoreV1Client, error } func (impl *K8sServiceImpl) GetCoreV1ClientByRestConfig(restConfig *rest.Config) (*v12.CoreV1Client, error) { - - var err error - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, err - } - httpClientFor, err := rest.HTTPClientFor(restConfig) if err != nil { impl.logger.Error("error occurred while overriding k8s client", "reason", err) @@ -1103,6 +802,7 @@ func (impl *K8sServiceImpl) GetNodesList(ctx context.Context, k8sClientSet *kube } return nodeList, err } + func (impl *K8sServiceImpl) GetNodeByName(ctx context.Context, k8sClientSet *kubernetes.Clientset, name string) (*v1.Node, error) { node, err := k8sClientSet.CoreV1().Nodes().Get(ctx, name, metav1.GetOptions{}) if err != nil { @@ -1141,6 +841,7 @@ func (impl *K8sServiceImpl) GetPodsListForNamespace(ctx context.Context, k8sClie } return podList, err } + func (impl *K8sServiceImpl) GetNmList(ctx context.Context, metricsClientSet *metrics.Clientset) (*v1beta1.NodeMetricsList, error) { nmList, err := metricsClientSet.MetricsV1beta1().NodeMetricses().List(ctx, metav1.ListOptions{}) if err != nil { @@ -1149,6 +850,7 @@ func (impl *K8sServiceImpl) GetNmList(ctx context.Context, metricsClientSet *met } return nmList, err } + func (impl *K8sServiceImpl) GetNmByName(ctx context.Context, metricsClientSet *metrics.Clientset, name string) (*v1beta1.NodeMetrics, error) { nodeMetrics, err := metricsClientSet.MetricsV1beta1().NodeMetricses().Get(ctx, name, metav1.GetOptions{}) if err != nil { @@ -1157,6 +859,7 @@ func (impl *K8sServiceImpl) GetNmByName(ctx context.Context, metricsClientSet *m } return nodeMetrics, err } + func (impl *K8sServiceImpl) GetMetricsClientSet(restConfig *rest.Config, k8sHttpClient *http.Client) (*metrics.Clientset, error) { metricsClientSet, err := metrics.NewForConfigAndClient(restConfig, k8sHttpClient) if err != nil { @@ -1165,6 +868,7 @@ func (impl *K8sServiceImpl) GetMetricsClientSet(restConfig *rest.Config, k8sHttp } return metricsClientSet, err } + func (impl *K8sServiceImpl) GetLogsForAPod(kubeClient *kubernetes.Clientset, namespace string, podName string, container string, follow bool) *rest.Request { podLogOpts := &v1.PodLogOptions{ Container: container, @@ -1174,71 +878,7 @@ func (impl *K8sServiceImpl) GetLogsForAPod(kubeClient *kubernetes.Clientset, nam return req } -// DeletePod will delete the given pod, or return an error if it couldn't -func DeletePod(pod v1.Pod, k8sClientSet *kubernetes.Clientset, deleteOptions metav1.DeleteOptions) error { - return k8sClientSet.CoreV1().Pods(pod.Namespace).Delete(context.Background(), pod.Name, deleteOptions) -} - -// EvictPod will evict the given pod, or return an error if it couldn't -func EvictPod(pod v1.Pod, k8sClientSet *kubernetes.Clientset, evictionGroupVersion schema.GroupVersion, deleteOptions metav1.DeleteOptions) error { - switch evictionGroupVersion { - case v13.SchemeGroupVersion: - // send policy/v1 if the server supports it - eviction := &v13.Eviction{ - ObjectMeta: metav1.ObjectMeta{ - Name: pod.Name, - Namespace: pod.Namespace, - }, - DeleteOptions: &deleteOptions, - } - return k8sClientSet.PolicyV1().Evictions(eviction.Namespace).Evict(context.TODO(), eviction) - - default: - // otherwise, fall back to policy/v1beta1, supported by all servers that support the eviction subresource - eviction := &v1beta12.Eviction{ - ObjectMeta: metav1.ObjectMeta{ - Name: pod.Name, - Namespace: pod.Namespace, - }, - DeleteOptions: &deleteOptions, - } - return k8sClientSet.PolicyV1beta1().Evictions(eviction.Namespace).Evict(context.TODO(), eviction) - } -} - -// CheckEvictionSupport uses Discovery API to find out if the server support -// eviction subresource If support, it will return its groupVersion; Otherwise, -// it will return an empty GroupVersion -func CheckEvictionSupport(clientset kubernetes.Interface) (schema.GroupVersion, error) { - discoveryClient := clientset.Discovery() - - // version info available in subresources since v1.8.0 in https://github.com/kubernetes/kubernetes/pull/49971 - resourceList, err := discoveryClient.ServerResourcesForGroupVersion("v1") - if err != nil { - return schema.GroupVersion{}, err - } - for _, resource := range resourceList.APIResources { - if resource.Name == commonBean.EvictionSubresource && resource.Kind == commonBean.EvictionKind && - len(resource.Group) > 0 && len(resource.Version) > 0 { - return schema.GroupVersion{Group: resource.Group, Version: resource.Version}, nil - } - } - return schema.GroupVersion{}, nil -} - -func UpdateNodeUnschedulableProperty(desiredUnschedulable bool, node *v1.Node, k8sClientSet *kubernetes.Clientset) (*v1.Node, error) { - node.Spec.Unschedulable = desiredUnschedulable - node, err := k8sClientSet.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) - return node, err -} - func (impl *K8sServiceImpl) CreateK8sClientSet(restConfig *rest.Config) (*kubernetes.Clientset, error) { - var err error - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, err - } k8sHttpClient, err := OverrideK8sHttpClientWithTracer(restConfig) if err != nil { impl.logger.Errorw("service err, OverrideK8sHttpClientWithTracer", "err", err) @@ -1282,29 +922,7 @@ func (impl *K8sServiceImpl) FetchConnectionStatusForCluster(k8sClientSet *kubern return err } -func CheckIfValidLabel(labelKey string, labelValue string) error { - labelKey = strings.TrimSpace(labelKey) - labelValue = strings.TrimSpace(labelValue) - - errs := validation.IsQualifiedName(labelKey) - if len(labelKey) == 0 || len(errs) > 0 { - return error2.New(fmt.Sprintf("Validation error - label key - %s is not satisfying the label key criteria", labelKey)) - } - - errs = validation.IsValidLabelValue(labelValue) - if len(labelValue) == 0 || len(errs) > 0 { - return error2.New(fmt.Sprintf("Validation error - label value - %s is not satisfying the label value criteria for label key - %s", labelValue, labelKey)) - } - return nil -} - func (impl *K8sServiceImpl) GetResourceIf(restConfig *rest.Config, groupVersionKind schema.GroupVersionKind) (resourceIf dynamic.NamespaceableResourceInterface, namespaced bool, err error) { - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, false, err - } - httpClient, err := OverrideK8sHttpClientWithTracer(restConfig) if err != nil { return nil, false, err @@ -1329,12 +947,6 @@ func (impl *K8sServiceImpl) GetResourceIf(restConfig *rest.Config, groupVersionK } func (impl *K8sServiceImpl) ListEvents(restConfig *rest.Config, namespace string, groupVersionKind schema.GroupVersionKind, ctx context.Context, name string) (*v1.EventList, error) { - var err error - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, err - } _, namespaced, err := impl.GetResourceIf(restConfig, groupVersionKind) if err != nil { impl.logger.Errorw("error in getting dynamic interface for resource", "err", err, "resource", name) @@ -1374,13 +986,6 @@ func (impl *K8sServiceImpl) ListEvents(restConfig *rest.Config, namespace string } func (impl *K8sServiceImpl) GetPodLogs(ctx context.Context, restConfig *rest.Config, name string, namespace string, sinceTime *metav1.Time, tailLines int, sinceSeconds int, follow bool, containerName string, isPrevContainerLogsEnabled bool) (io.ReadCloser, error) { - var err error - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, err - } - httpClient, err := OverrideK8sHttpClientWithTracer(restConfig) if err != nil { impl.logger.Errorw("error in getting pod logs", "err", err) @@ -1418,14 +1023,8 @@ func (impl *K8sServiceImpl) GetPodLogs(ctx context.Context, restConfig *rest.Con } return stream, nil } -func (impl *K8sServiceImpl) GetResourceIfWithAcceptHeader(restConfig *rest.Config, groupVersionKind schema.GroupVersionKind, asTable bool) (resourceIf dynamic.NamespaceableResourceInterface, namespaced bool, err error) { - - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, false, err - } +func (impl *K8sServiceImpl) GetResourceIfWithAcceptHeader(restConfig *rest.Config, groupVersionKind schema.GroupVersionKind, asTable bool) (resourceIf dynamic.NamespaceableResourceInterface, namespaced bool, err error) { httpClient, err := OverrideK8sHttpClientWithTracer(restConfig) if err != nil { impl.logger.Errorw("error in getting http client", "err", err) @@ -1466,26 +1065,7 @@ func (impl *K8sServiceImpl) GetResourceIfWithAcceptHeader(restConfig *rest.Confi return dynamicIf.Resource(resource), apiResource.Namespaced, nil } -func ServerResourceForGroupVersionKind(discoveryClient discovery.DiscoveryInterface, gvk schema.GroupVersionKind) (*metav1.APIResource, error) { - resources, err := discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String()) - if err != nil { - return nil, err - } - for _, r := range resources.APIResources { - if r.Kind == gvk.Kind { - return &r, nil - } - } - return nil, errors.NewNotFound(schema.GroupResource{Group: gvk.Group, Resource: gvk.Kind}, "") -} func (impl *K8sServiceImpl) GetResourceList(ctx context.Context, restConfig *rest.Config, gvk schema.GroupVersionKind, namespace string, asTable bool, listOptions *metav1.ListOptions) (*ResourceListResponse, bool, error) { - var err error - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, false, err - } - resourceIf, namespaced, err := impl.GetResourceIfWithAcceptHeader(restConfig, gvk, asTable) if err != nil { impl.logger.Errorw("error in getting dynamic interface for resource", "err", err, "namespace", namespace) @@ -1512,15 +1092,8 @@ func (impl *K8sServiceImpl) GetResourceList(ctx context.Context, restConfig *res return &ResourceListResponse{*resp}, namespaced, nil } -func (impl *K8sServiceImpl) PatchResourceRequest(ctx context.Context, restConfig *rest.Config, pt types.PatchType, manifest string, name string, namespace string, gvk schema.GroupVersionKind) (*ManifestResponse, error) { - - var err error - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, err - } +func (impl *K8sServiceImpl) PatchResourceRequest(ctx context.Context, restConfig *rest.Config, pt types.PatchType, manifest string, name string, namespace string, gvk schema.GroupVersionKind) (*ManifestResponse, error) { resourceIf, namespaced, err := impl.GetResourceIf(restConfig, gvk) if err != nil { impl.logger.Errorw("error in getting dynamic interface for resource", "err", err, "resource", name, "namespace", namespace) @@ -1540,15 +1113,9 @@ func (impl *K8sServiceImpl) PatchResourceRequest(ctx context.Context, restConfig return &ManifestResponse{Manifest: *resp}, nil } +// GetApiResources returns the list of api resources from k8s. // if verb is supplied empty, that means - return all func (impl *K8sServiceImpl) GetApiResources(restConfig *rest.Config, includeOnlyVerb string) ([]*K8sApiResource, error) { - var err error - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, err - } - discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig) if err != nil { impl.logger.Errorw("error in getting dynamic k8s client", "err", err) @@ -1617,15 +1184,8 @@ func (impl *K8sServiceImpl) GetApiResources(restConfig *rest.Config, includeOnly } return apiResources, nil } -func (impl *K8sServiceImpl) CreateResources(ctx context.Context, restConfig *rest.Config, manifest string, gvk schema.GroupVersionKind, namespace string) (*ManifestResponse, error) { - - var err error - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, err - } +func (impl *K8sServiceImpl) CreateResources(ctx context.Context, restConfig *rest.Config, manifest string, gvk schema.GroupVersionKind, namespace string) (*ManifestResponse, error) { resourceIf, namespaced, err := impl.GetResourceIf(restConfig, gvk) if err != nil { impl.logger.Errorw("error in getting dynamic interface for resource", "err", err, "namespace", namespace) @@ -1649,15 +1209,8 @@ func (impl *K8sServiceImpl) CreateResources(ctx context.Context, restConfig *res } return &ManifestResponse{Manifest: *resp}, nil } -func (impl *K8sServiceImpl) GetResource(ctx context.Context, namespace string, name string, gvk schema.GroupVersionKind, restConfig *rest.Config) (*ManifestResponse, error) { - - var err error - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, err - } +func (impl *K8sServiceImpl) GetResource(ctx context.Context, namespace string, name string, gvk schema.GroupVersionKind, restConfig *rest.Config) (*ManifestResponse, error) { resourceIf, namespaced, err := impl.GetResourceIf(restConfig, gvk) if err != nil { impl.logger.Errorw("error in getting dynamic interface for resource", "err", err, "namespace", namespace) @@ -1675,15 +1228,8 @@ func (impl *K8sServiceImpl) GetResource(ctx context.Context, namespace string, n } return &ManifestResponse{Manifest: *resp}, nil } -func (impl *K8sServiceImpl) UpdateResource(ctx context.Context, restConfig *rest.Config, gvk schema.GroupVersionKind, namespace string, k8sRequestPatch string) (*ManifestResponse, error) { - - var err error - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, err - } +func (impl *K8sServiceImpl) UpdateResource(ctx context.Context, restConfig *rest.Config, gvk schema.GroupVersionKind, namespace string, k8sRequestPatch string) (*ManifestResponse, error) { resourceIf, namespaced, err := impl.GetResourceIf(restConfig, gvk) if err != nil { impl.logger.Errorw("error in getting dynamic interface for resource", "err", err, "namespace", namespace) @@ -1709,14 +1255,6 @@ func (impl *K8sServiceImpl) UpdateResource(ctx context.Context, restConfig *rest } func (impl *K8sServiceImpl) DeleteResource(ctx context.Context, restConfig *rest.Config, gvk schema.GroupVersionKind, namespace string, name string, forceDelete bool) (*ManifestResponse, error) { - - var err error - restConfig, err = impl.httpClientConfig.OverrideConfigWithCustomTransport(restConfig) - if err != nil { - impl.logger.Errorw("error in overriding reset config", "err", err) - return nil, err - } - resourceIf, namespaced, err := impl.GetResourceIf(restConfig, gvk) if err != nil { impl.logger.Errorw("error in getting dynamic interface for resource", "err", err, "resource", name, "namespace", namespace) @@ -1791,100 +1329,6 @@ func (impl *K8sServiceImpl) GetPodListByLabel(namespace, label string, clientSet return podList.Items, nil } -//func GetHealthCheckFunc(gvk schema.GroupVersionKind) func(obj *unstructured.Unstructured) (*health.HealthStatus, error) { -// return health.GetHealthCheckFunc(gvk) -//} - -func isServiceAccountTokenSecret(un *unstructured.Unstructured) (bool, metav1.OwnerReference) { - ref := metav1.OwnerReference{ - APIVersion: "v1", - Kind: commonBean.ServiceAccountKind, - } - - if typeVal, ok, err := unstructured.NestedString(un.Object, "type"); !ok || err != nil || typeVal != "kubernetes.io/service-account-token" { - return false, ref - } - - annotations := un.GetAnnotations() - if annotations == nil { - return false, ref - } - - id, okId := annotations["kubernetes.io/service-account.uid"] - name, okName := annotations["kubernetes.io/service-account.name"] - if okId && okName { - ref.Name = name - ref.UID = types.UID(id) - } - return ref.Name != "" && ref.UID != "", ref -} - -func ResolveResourceReferences(un *unstructured.Unstructured) ([]metav1.OwnerReference, func(ResourceKey) bool) { - var isInferredParentOf func(_ ResourceKey) bool - ownerRefs := un.GetOwnerReferences() - gvk := un.GroupVersionKind() - - switch { - - // Special case for endpoint. Remove after https://github.com/kubernetes/kubernetes/issues/28483 is fixed - case gvk.Group == "" && gvk.Kind == commonBean.EndpointsKind && len(un.GetOwnerReferences()) == 0: - ownerRefs = append(ownerRefs, metav1.OwnerReference{ - Name: un.GetName(), - Kind: commonBean.ServiceKind, - APIVersion: "v1", - }) - - // Special case for Operator Lifecycle Manager ClusterServiceVersion: - case un.GroupVersionKind().Group == "operators.coreos.com" && un.GetKind() == "ClusterServiceVersion": - if un.GetAnnotations()["olm.operatorGroup"] != "" { - ownerRefs = append(ownerRefs, metav1.OwnerReference{ - Name: un.GetAnnotations()["olm.operatorGroup"], - Kind: "OperatorGroup", - APIVersion: "operators.coreos.com/v1", - }) - } - - // Edge case: consider auto-created service account tokens as a child of service account objects - case un.GetKind() == commonBean.SecretKind && un.GroupVersionKind().Group == "": - if yes, ref := isServiceAccountTokenSecret(un); yes { - ownerRefs = append(ownerRefs, ref) - } - - case (un.GroupVersionKind().Group == "apps" || un.GroupVersionKind().Group == "extensions") && un.GetKind() == commonBean.StatefulSetKind: - if refs, err := isStatefulSetChild(un); err != nil { - fmt.Println("error") - } else { - isInferredParentOf = refs - } - } - - return ownerRefs, isInferredParentOf -} - -func isStatefulSetChild(un *unstructured.Unstructured) (func(ResourceKey) bool, error) { - sts := v14.StatefulSet{} - data, err := json.Marshal(un) - if err != nil { - return nil, err - } - err = json.Unmarshal(data, &sts) - if err != nil { - return nil, err - } - - templates := sts.Spec.VolumeClaimTemplates - return func(key ResourceKey) bool { - if key.Kind == commonBean.PersistentVolumeClaimKind && key.GroupKind().Group == "" { - for _, templ := range templates { - if strings.HasPrefix(key.Name, fmt.Sprintf("%s-%s-", templ.Name, un.GetName())) { - return true - } - } - } - return false - }, nil -} - func (impl *K8sServiceImpl) CreateOrUpdateSecretByName(client *v12.CoreV1Client, namespace, uniqueSecretName string, secretLabel map[string]string, secretData map[string]string) error { secret, err := impl.GetSecret(namespace, uniqueSecretName, client) @@ -1911,31 +1355,6 @@ func (impl *K8sServiceImpl) CreateOrUpdateSecretByName(client *v12.CoreV1Client, return nil } -func (impl *K8sServiceImpl) GetGVRForCRD(config *rest.Config, CRDName string) (schema.GroupVersionResource, error) { - apiExtClient, err := apiextensionsclient.NewForConfig(config) - if err != nil { - impl.logger.Error("error in getting api extension client", "err", err) - return schema.GroupVersionResource{}, err - } - crd, err := apiExtClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), CRDName, metav1.GetOptions{}) - if err != nil { - impl.logger.Error("error in getting terraform crd", "err", err) - return schema.GroupVersionResource{}, err - } - var servedVersion string - for _, v := range crd.Spec.Versions { - if v.Served { - servedVersion = v.Name - break - } - } - return schema.GroupVersionResource{ - Group: crd.Spec.Group, - Version: servedVersion, - Resource: crd.Spec.Names.Plural, - }, nil -} - func (impl *K8sServiceImpl) GetResourceByGVR(ctx context.Context, config *rest.Config, GVR schema.GroupVersionResource, resourceName, namespace string) (*unstructured.Unstructured, error) { dynClient, err := dynamic.NewForConfig(config) if err != nil { @@ -1981,3 +1400,108 @@ func (impl *K8sServiceImpl) DeleteResourceByGVR(ctx context.Context, config *res } return nil } + +func (impl *K8sServiceImpl) GetK8sInClusterRestConfig(opts ...K8sServiceOpts) (*rest.Config, error) { + return impl.WithHttpTransport(impl.getOpts(opts)).GetK8sInClusterRestConfig() +} + +func (impl *K8sServiceImpl) GetK8sConfigAndClients(clusterConfig *ClusterConfig, opts ...K8sServiceOpts) (*rest.Config, *http.Client, *kubernetes.Clientset, error) { + return impl.WithHttpTransport(impl.getOpts(opts)).GetK8sConfigAndClients(clusterConfig) +} + +func (impl *K8sServiceImpl) GetK8sInClusterConfigAndDynamicClients(opts ...K8sServiceOpts) (*rest.Config, *http.Client, dynamic.Interface, error) { + return impl.WithHttpTransport(impl.getOpts(opts)).GetK8sInClusterConfigAndDynamicClients() +} + +func (impl *K8sServiceImpl) GetK8sInClusterConfigAndClients(opts ...K8sServiceOpts) (*rest.Config, *http.Client, *kubernetes.Clientset, error) { + return impl.WithHttpTransport(impl.getOpts(opts)).GetK8sInClusterConfigAndClients() +} + +func (impl *K8sServiceImpl) GetRestConfigByCluster(clusterConfig *ClusterConfig, opts ...K8sServiceOpts) (*rest.Config, error) { + return impl.WithHttpTransport(impl.getOpts(opts)).GetRestConfigByCluster(clusterConfig) +} + +func (impl *K8sServiceImpl) OverrideRestConfigWithCustomTransport(restConfig *rest.Config, opts ...K8sServiceOpts) (*rest.Config, error) { + return impl.WithHttpTransport(impl.getOpts(opts)).OverrideRestConfigWithCustomTransport(restConfig) +} + +func (impl *K8sServiceImpl) GetK8sConfigAndClientsByRestConfig(restConfig *rest.Config, opts ...K8sServiceOpts) (*http.Client, *kubernetes.Clientset, error) { + return impl.WithHttpTransport(impl.getOpts(opts)).GetK8sConfigAndClientsByRestConfig(restConfig) +} + +func (impl *K8sServiceImpl) DeleteNs(namespace string, client *v12.CoreV1Client) error { + err := client.Namespaces().Delete(context.Background(), namespace, metav1.DeleteOptions{}) + return err +} + +func (impl *K8sServiceImpl) getEventKindHeader() ([]string, map[int]string) { + headers := []string{"type", "message", "namespace", "involved object", "source", "count", "age", "last seen"} + columnIndexes := make(map[int]string) + columnIndexes[0] = "last seen" + columnIndexes[1] = "type" + columnIndexes[2] = "namespace" + columnIndexes[3] = "involved object" + columnIndexes[5] = "source" + columnIndexes[6] = "message" + columnIndexes[7] = "age" + columnIndexes[8] = "count" + return headers, columnIndexes +} + +func (impl *K8sServiceImpl) GetGVRForCRD(config *rest.Config, CRDName string) (schema.GroupVersionResource, error) { + apiExtClient, err := apiextensionsclient.NewForConfig(config) + if err != nil { + impl.logger.Error("error in getting api extension client", "err", err) + return schema.GroupVersionResource{}, err + } + crd, err := apiExtClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), CRDName, metav1.GetOptions{}) + if err != nil { + impl.logger.Error("error in getting terraform crd", "err", err) + return schema.GroupVersionResource{}, err + } + var servedVersion string + for _, v := range crd.Spec.Versions { + if v.Served { + servedVersion = v.Name + break + } + } + return schema.GroupVersionResource{ + Group: crd.Spec.Group, + Version: servedVersion, + Resource: crd.Spec.Names.Plural, + }, nil +} + +func (impl *K8sServiceImpl) GetRestClientForCRD(config *ClusterConfig, groupVersion *schema.GroupVersion) (*rest.RESTClient, error) { + + restConfig, err := impl.GetRestConfigByCluster(config) + if err != nil { + return nil, err + } + + restConfig.ContentConfig = rest.ContentConfig{ + GroupVersion: groupVersion, + NegotiatedSerializer: scheme.Codecs.WithoutConversion(), + } + restConfig.APIPath = "/apis" + + restClient, err := rest.RESTClientFor(restConfig) + if err != nil { + impl.logger.Errorw("error in getting rest client", "gvr", groupVersion.String(), "err", err) + return nil, err + } + + return restClient, nil +} + +func (impl *K8sServiceImpl) PatchResourceByRestClient(restClient *rest.RESTClient, resource, name, namespace string, pt types.PatchType, data []byte, subresources ...string) rest.Result { + result := restClient.Patch(pt). + Namespace(namespace). + Resource(resource). + Name(name). + SubResource(subresources...). + Body(data). + Do(context.Background()) + return result +} diff --git a/vendor/github.com/devtron-labs/common-lib/utils/k8s/KubeConfig.go b/vendor/github.com/devtron-labs/common-lib/utils/k8s/KubeConfig.go new file mode 100644 index 0000000000..45c49d31c8 --- /dev/null +++ b/vendor/github.com/devtron-labs/common-lib/utils/k8s/KubeConfig.go @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2020-2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package k8s + +import ( + "github.com/devtron-labs/common-lib/utils/k8s/commonBean" + "go.uber.org/zap" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "net/http" +) + +type KubeConfigImpl struct { + logger *zap.SugaredLogger + runTimeConfig *RuntimeConfig + kubeconfig *string + httpTransportConfig HttpTransportInterface + kubeConfigBuilder KubeConfigBuilderInterface +} + +func NewKubeConfigImpl( + logger *zap.SugaredLogger, + runTimeConfig *RuntimeConfig, + kubeconfig *string, + httpTransportConfig HttpTransportInterface, + kubeConfigBuilder KubeConfigBuilderInterface) *KubeConfigImpl { + return &KubeConfigImpl{ + logger: logger, + runTimeConfig: runTimeConfig, + kubeconfig: kubeconfig, + httpTransportConfig: httpTransportConfig, + kubeConfigBuilder: kubeConfigBuilder, + } +} + +type KubeConfigInterface interface { + GetK8sInClusterRestConfig() (*rest.Config, error) + GetK8sConfigAndClients(clusterConfig *ClusterConfig) (*rest.Config, *http.Client, *kubernetes.Clientset, error) + GetK8sInClusterConfigAndDynamicClients() (*rest.Config, *http.Client, dynamic.Interface, error) + GetK8sInClusterConfigAndClients() (*rest.Config, *http.Client, *kubernetes.Clientset, error) + GetRestConfigByCluster(clusterConfig *ClusterConfig) (*rest.Config, error) + OverrideRestConfigWithCustomTransport(restConfig *rest.Config) (*rest.Config, error) + GetK8sConfigAndClientsByRestConfig(restConfig *rest.Config) (*http.Client, *kubernetes.Clientset, error) +} + +func (impl *KubeConfigImpl) GetK8sInClusterRestConfig() (*rest.Config, error) { + impl.logger.Debug("getting k8s rest config") + if impl.runTimeConfig.LocalDevMode { + restConfig, err := clientcmd.BuildConfigFromFlags("", *impl.kubeconfig) + if err != nil { + impl.logger.Errorw("Error while building config from flags", "error", err) + return nil, err + } + return impl.httpTransportConfig.OverrideConfigWithCustomTransport(restConfig) + } else { + clusterConfig, err := rest.InClusterConfig() + if err != nil { + impl.logger.Errorw("error in fetch default cluster config", "err", err) + return nil, err + } + return impl.httpTransportConfig.OverrideConfigWithCustomTransport(clusterConfig) + } +} + +func (impl *KubeConfigImpl) GetK8sConfigAndClients(clusterConfig *ClusterConfig) (*rest.Config, *http.Client, *kubernetes.Clientset, error) { + restConfig, err := impl.GetRestConfigByCluster(clusterConfig) + if err != nil { + impl.logger.Errorw("error in getting rest config by cluster", "err", err, "clusterName", clusterConfig.ClusterName) + return nil, nil, nil, err + } + + k8sHttpClient, k8sClientSet, err := impl.GetK8sConfigAndClientsByRestConfig(restConfig) + if err != nil { + impl.logger.Errorw("error in getting client set by rest config", "err", err, "clusterName", clusterConfig.ClusterName) + return nil, nil, nil, err + } + return restConfig, k8sHttpClient, k8sClientSet, nil +} + +func (impl *KubeConfigImpl) GetK8sInClusterConfigAndDynamicClients() (*rest.Config, *http.Client, dynamic.Interface, error) { + restConfig, err := impl.GetK8sInClusterRestConfig() + if err != nil { + impl.logger.Errorw("error in getting rest config for in cluster", "err", err) + return nil, nil, nil, err + } + + k8sHttpClient, err := OverrideK8sHttpClientWithTracer(restConfig) + if err != nil { + impl.logger.Errorw("error in getting k8s http client set by rest config for in cluster", "err", err) + return nil, nil, nil, err + } + dynamicClientSet, err := dynamic.NewForConfigAndClient(restConfig, k8sHttpClient) + if err != nil { + impl.logger.Errorw("error in getting client set by rest config for in cluster", "err", err) + return nil, nil, nil, err + } + return restConfig, k8sHttpClient, dynamicClientSet, nil +} + +func (impl *KubeConfigImpl) GetK8sInClusterConfigAndClients() (*rest.Config, *http.Client, *kubernetes.Clientset, error) { + restConfig, err := impl.GetK8sInClusterRestConfig() + if err != nil { + impl.logger.Errorw("error in getting rest config for in cluster", "err", err) + return nil, nil, nil, err + } + + k8sHttpClient, k8sClientSet, err := impl.GetK8sConfigAndClientsByRestConfig(restConfig) + if err != nil { + impl.logger.Errorw("error in getting client set by rest config for in cluster", "err", err) + return nil, nil, nil, err + } + return restConfig, k8sHttpClient, k8sClientSet, nil +} + +func (impl *KubeConfigImpl) GetRestConfigByCluster(clusterConfig *ClusterConfig) (*rest.Config, error) { + var restConfig *rest.Config + var err error + if clusterConfig.Host == commonBean.DefaultClusterUrl && len(clusterConfig.BearerToken) == 0 { + return impl.GetK8sInClusterRestConfig() + } + restConfig, err = impl.kubeConfigBuilder.BuildKubeConfigForCluster(clusterConfig) + if err != nil { + impl.logger.Errorw("error in getting rest config for cluster", "err", err, "clusterName", clusterConfig.ClusterName) + return nil, err + } + return impl.OverrideRestConfigWithCustomTransport(restConfig) +} + +func (impl *KubeConfigImpl) OverrideRestConfigWithCustomTransport(restConfig *rest.Config) (*rest.Config, error) { + var err error + restConfig, err = impl.httpTransportConfig.OverrideConfigWithCustomTransport(restConfig) + if err != nil { + impl.logger.Errorw("error in overriding rest config with custom transport configurations", "err", err) + return nil, err + } + return restConfig, nil +} + +func (impl *KubeConfigImpl) GetK8sConfigAndClientsByRestConfig(restConfig *rest.Config) (*http.Client, *kubernetes.Clientset, error) { + k8sHttpClient, err := OverrideK8sHttpClientWithTracer(restConfig) + if err != nil { + impl.logger.Errorw("error in getting k8s http client set by rest config", "err", err) + return nil, nil, err + } + k8sClientSet, err := kubernetes.NewForConfigAndClient(restConfig, k8sHttpClient) + if err != nil { + impl.logger.Errorw("error in getting client set by rest config", "err", err) + return nil, nil, err + } + return k8sHttpClient, k8sClientSet, nil +} + +type KubeConfigBuilder struct{} + +type KubeConfigBuilderInterface interface { + BuildKubeConfigForCluster(clusterConfig *ClusterConfig) (*rest.Config, error) +} + +func NewKubeConfigBuilder() *KubeConfigBuilder { + return &KubeConfigBuilder{} +} + +// BuildKubeConfigForCluster builds a kubeconfig for the given cluster configuration. +// This function is used in KubeConfigExtended for extended implementation. +func (impl *KubeConfigBuilder) BuildKubeConfigForCluster(clusterConfig *ClusterConfig) (*rest.Config, error) { + restConfig := &rest.Config{Host: clusterConfig.Host, BearerToken: clusterConfig.BearerToken} + clusterConfig.PopulateTlsConfigurationsInto(restConfig) + return restConfig, nil +} diff --git a/vendor/github.com/devtron-labs/common-lib/utils/k8s/bean.go b/vendor/github.com/devtron-labs/common-lib/utils/k8s/bean.go index 21f0a21be3..afd05a3209 100644 --- a/vendor/github.com/devtron-labs/common-lib/utils/k8s/bean.go +++ b/vendor/github.com/devtron-labs/common-lib/utils/k8s/bean.go @@ -27,12 +27,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - utilnet "k8s.io/apimachinery/pkg/util/net" "k8s.io/client-go/rest" - "log" - "net" - "net/http" - "time" ) type ClusterConfig struct { @@ -168,15 +163,6 @@ func GetResourceKey(obj *unstructured.Unstructured) ResourceKey { return NewResourceKey(gvk.Group, gvk.Kind, obj.GetNamespace(), obj.GetName()) } -type CustomK8sHttpTransportConfig struct { - UseCustomTransport bool `env:"USE_CUSTOM_HTTP_TRANSPORT" envDefault:"false"` - TimeOut int `env:"K8s_TCP_TIMEOUT" envDefault:"30"` - KeepAlive int `env:"K8s_TCP_KEEPALIVE" envDefault:"30"` - TLSHandshakeTimeout int `env:"K8s_TLS_HANDSHAKE_TIMEOUT" envDefault:"10"` - MaxIdleConnsPerHost int `env:"K8s_CLIENT_MAX_IDLE_CONNS_PER_HOST" envDefault:"25"` - IdleConnTimeout int `env:"K8s_TCP_IDLE_CONN_TIMEOUT" envDefault:"300"` -} - type LocalDevMode bool type RuntimeConfig struct { @@ -189,65 +175,14 @@ func GetRuntimeConfig() (*RuntimeConfig, error) { return cfg, err } -func NewCustomK8sHttpTransportConfig() *CustomK8sHttpTransportConfig { - customK8sHttpTransportConfig := &CustomK8sHttpTransportConfig{} - err := env.Parse(customK8sHttpTransportConfig) - if err != nil { - log.Println("error in parsing custom k8s http configurations from env : ", "err : ", err) - } - return customK8sHttpTransportConfig -} - -// OverrideConfigWithCustomTransport -// overrides the given rest config with custom transport if UseCustomTransport is enabled. -// if the config already has a defined transport, we don't override it. -func (impl *CustomK8sHttpTransportConfig) OverrideConfigWithCustomTransport(config *rest.Config) (*rest.Config, error) { - if !impl.UseCustomTransport || config.Transport != nil { - return config, nil - } - - dial := (&net.Dialer{ - Timeout: time.Duration(impl.TimeOut) * time.Second, - KeepAlive: time.Duration(impl.KeepAlive) * time.Second, - }).DialContext - - // Get the TLS options for this client config - tlsConfig, err := rest.TLSConfigFor(config) - if err != nil { - return nil, err - } - - transport := utilnet.SetTransportDefaults(&http.Transport{ - Proxy: config.Proxy, - TLSHandshakeTimeout: time.Duration(impl.TLSHandshakeTimeout) * time.Second, - TLSClientConfig: tlsConfig, - MaxIdleConns: impl.MaxIdleConnsPerHost, - MaxConnsPerHost: impl.MaxIdleConnsPerHost, - MaxIdleConnsPerHost: impl.MaxIdleConnsPerHost, - DialContext: dial, - DisableCompression: config.DisableCompression, - IdleConnTimeout: time.Duration(impl.IdleConnTimeout) * time.Second, - }) - - rt, err := rest.HTTPWrappersForConfig(config, transport) - if err != nil { - return nil, err - } - - config.Transport = rt - config.Timeout = time.Duration(impl.TimeOut) * time.Second - - // set default tls config and remove auth/exec provides since we use it in a custom transport. - // we already set tls config in the transport - config.TLSClientConfig = rest.TLSClientConfig{} - config.AuthProvider = nil - config.ExecProvider = nil - - return config, nil -} - var NotFoundError = errors.New("not found") func IsNotFoundError(err error) bool { return errors.Is(err, NotFoundError) } + +type JsonPatchType struct { + Op string `json:"op"` + Path string `json:"path"` + Value interface{} `json:"value"` +} diff --git a/vendor/github.com/devtron-labs/common-lib/utils/k8s/helper.go b/vendor/github.com/devtron-labs/common-lib/utils/k8s/helper.go new file mode 100644 index 0000000000..892979931d --- /dev/null +++ b/vendor/github.com/devtron-labs/common-lib/utils/k8s/helper.go @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package k8s + +import ( + "context" + "encoding/json" + errors "errors" + "fmt" + "github.com/devtron-labs/common-lib/utils/k8s/commonBean" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + v14 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + v13 "k8s.io/api/policy/v1" + v1beta12 "k8s.io/api/policy/v1beta1" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/client-go/discovery" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "net/http" + "strings" +) + +func ServerResourceForGroupVersionKind(discoveryClient discovery.DiscoveryInterface, gvk schema.GroupVersionKind) (*metav1.APIResource, error) { + resources, err := discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String()) + if err != nil { + return nil, err + } + for _, r := range resources.APIResources { + if r.Kind == gvk.Kind { + return &r, nil + } + } + return nil, k8sErrors.NewNotFound(schema.GroupResource{Group: gvk.Group, Resource: gvk.Kind}, "") +} + +func isServiceAccountTokenSecret(un *unstructured.Unstructured) (bool, metav1.OwnerReference) { + ref := metav1.OwnerReference{ + APIVersion: "v1", + Kind: commonBean.ServiceAccountKind, + } + + if typeVal, ok, err := unstructured.NestedString(un.Object, "type"); !ok || err != nil || typeVal != "kubernetes.io/service-account-token" { + return false, ref + } + + annotations := un.GetAnnotations() + if annotations == nil { + return false, ref + } + + id, okId := annotations["kubernetes.io/service-account.uid"] + name, okName := annotations["kubernetes.io/service-account.name"] + if okId && okName { + ref.Name = name + ref.UID = types.UID(id) + } + return ref.Name != "" && ref.UID != "", ref +} + +func ResolveResourceReferences(un *unstructured.Unstructured) ([]metav1.OwnerReference, func(ResourceKey) bool) { + var isInferredParentOf func(_ ResourceKey) bool + ownerRefs := un.GetOwnerReferences() + gvk := un.GroupVersionKind() + + switch { + + // Special case for endpoint. Remove after https://github.com/kubernetes/kubernetes/issues/28483 is fixed + case gvk.Group == "" && gvk.Kind == commonBean.EndpointsKind && len(un.GetOwnerReferences()) == 0: + ownerRefs = append(ownerRefs, metav1.OwnerReference{ + Name: un.GetName(), + Kind: commonBean.ServiceKind, + APIVersion: "v1", + }) + + // Special case for Operator Lifecycle Manager ClusterServiceVersion: + case un.GroupVersionKind().Group == "operators.coreos.com" && un.GetKind() == "ClusterServiceVersion": + if un.GetAnnotations()["olm.operatorGroup"] != "" { + ownerRefs = append(ownerRefs, metav1.OwnerReference{ + Name: un.GetAnnotations()["olm.operatorGroup"], + Kind: "OperatorGroup", + APIVersion: "operators.coreos.com/v1", + }) + } + + // Edge case: consider auto-created service account tokens as a child of service account objects + case un.GetKind() == commonBean.SecretKind && un.GroupVersionKind().Group == "": + if yes, ref := isServiceAccountTokenSecret(un); yes { + ownerRefs = append(ownerRefs, ref) + } + + case (un.GroupVersionKind().Group == "apps" || un.GroupVersionKind().Group == "extensions") && un.GetKind() == commonBean.StatefulSetKind: + if refs, err := isStatefulSetChild(un); err != nil { + fmt.Println("error") + } else { + isInferredParentOf = refs + } + } + + return ownerRefs, isInferredParentOf +} + +func isStatefulSetChild(un *unstructured.Unstructured) (func(ResourceKey) bool, error) { + sts := v14.StatefulSet{} + data, err := json.Marshal(un) + if err != nil { + return nil, err + } + err = json.Unmarshal(data, &sts) + if err != nil { + return nil, err + } + + templates := sts.Spec.VolumeClaimTemplates + return func(key ResourceKey) bool { + if key.Kind == commonBean.PersistentVolumeClaimKind && key.GroupKind().Group == "" { + for _, templ := range templates { + if strings.HasPrefix(key.Name, fmt.Sprintf("%s-%s-", templ.Name, un.GetName())) { + return true + } + } + } + return false + }, nil +} + +func CheckIfValidLabel(labelKey string, labelValue string) error { + labelKey = strings.TrimSpace(labelKey) + labelValue = strings.TrimSpace(labelValue) + + errs := validation.IsQualifiedName(labelKey) + if len(labelKey) == 0 || len(errs) > 0 { + return errors.New(fmt.Sprintf("Validation error - label key - %s is not satisfying the label key criteria", labelKey)) + } + + errs = validation.IsValidLabelValue(labelValue) + if len(labelValue) == 0 || len(errs) > 0 { + return errors.New(fmt.Sprintf("Validation error - label value - %s is not satisfying the label value criteria for label key - %s", labelValue, labelKey)) + } + return nil +} + +// DeletePod will delete the given pod, or return an error if it couldn't +func DeletePod(pod v1.Pod, k8sClientSet *kubernetes.Clientset, deleteOptions metav1.DeleteOptions) error { + return k8sClientSet.CoreV1().Pods(pod.Namespace).Delete(context.Background(), pod.Name, deleteOptions) +} + +// EvictPod will evict the given pod, or return an error if it couldn't +func EvictPod(pod v1.Pod, k8sClientSet *kubernetes.Clientset, evictionGroupVersion schema.GroupVersion, deleteOptions metav1.DeleteOptions) error { + switch evictionGroupVersion { + case v13.SchemeGroupVersion: + // send policy/v1 if the server supports it + eviction := &v13.Eviction{ + ObjectMeta: metav1.ObjectMeta{ + Name: pod.Name, + Namespace: pod.Namespace, + }, + DeleteOptions: &deleteOptions, + } + return k8sClientSet.PolicyV1().Evictions(eviction.Namespace).Evict(context.TODO(), eviction) + + default: + // otherwise, fall back to policy/v1beta1, supported by all servers that support the eviction subresource + eviction := &v1beta12.Eviction{ + ObjectMeta: metav1.ObjectMeta{ + Name: pod.Name, + Namespace: pod.Namespace, + }, + DeleteOptions: &deleteOptions, + } + return k8sClientSet.PolicyV1beta1().Evictions(eviction.Namespace).Evict(context.TODO(), eviction) + } +} + +// CheckEvictionSupport uses Discovery API to find out if the server support +// eviction subresource If support, it will return its groupVersion; Otherwise, +// it will return an empty GroupVersion +func CheckEvictionSupport(clientset kubernetes.Interface) (schema.GroupVersion, error) { + discoveryClient := clientset.Discovery() + + // version info available in subresources since v1.8.0 in https://github.com/kubernetes/kubernetes/pull/49971 + resourceList, err := discoveryClient.ServerResourcesForGroupVersion("v1") + if err != nil { + return schema.GroupVersion{}, err + } + for _, resource := range resourceList.APIResources { + if resource.Name == commonBean.EvictionSubresource && resource.Kind == commonBean.EvictionKind && + len(resource.Group) > 0 && len(resource.Version) > 0 { + return schema.GroupVersion{Group: resource.Group, Version: resource.Version}, nil + } + } + return schema.GroupVersion{}, nil +} + +func UpdateNodeUnschedulableProperty(desiredUnschedulable bool, node *v1.Node, k8sClientSet *kubernetes.Clientset) (*v1.Node, error) { + node.Spec.Unschedulable = desiredUnschedulable + node, err := k8sClientSet.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) + return node, err +} + +func OverrideK8sHttpClientWithTracer(restConfig *rest.Config) (*http.Client, error) { + httpClientFor, err := rest.HTTPClientFor(restConfig) + if err != nil { + fmt.Println("error occurred while overriding k8s client", "reason", err) + return nil, err + } + httpClientFor.Transport = otelhttp.NewTransport(httpClientFor.Transport) + return httpClientFor, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index a13d2e8ad3..f6c728cc9e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -472,7 +472,7 @@ github.com/davecgh/go-spew/spew # github.com/deckarep/golang-set v1.8.0 ## explicit; go 1.17 github.com/deckarep/golang-set -# github.com/devtron-labs/authenticator v0.4.35-0.20240809073103-6e11da8083f8 => github.com/devtron-labs/devtron-services/authenticator v0.0.0-20250521130159-a5d26dc2ab8d +# github.com/devtron-labs/authenticator v0.4.35-0.20240809073103-6e11da8083f8 => github.com/devtron-labs/devtron-services/authenticator v0.0.0-20250604112749-a4fc904430f9 ## explicit; go 1.21 github.com/devtron-labs/authenticator/apiToken github.com/devtron-labs/authenticator/client @@ -480,7 +480,7 @@ github.com/devtron-labs/authenticator/jwt github.com/devtron-labs/authenticator/middleware github.com/devtron-labs/authenticator/oidc github.com/devtron-labs/authenticator/password -# github.com/devtron-labs/common-lib v0.18.1-0.20241001061923-eda545dc839e => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20250521130159-a5d26dc2ab8d +# github.com/devtron-labs/common-lib v0.18.1-0.20241001061923-eda545dc839e => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20250604112749-a4fc904430f9 ## explicit; go 1.21 github.com/devtron-labs/common-lib/async github.com/devtron-labs/common-lib/blob-storage @@ -2358,8 +2358,8 @@ xorm.io/xorm/log xorm.io/xorm/names xorm.io/xorm/schemas xorm.io/xorm/tags -# github.com/devtron-labs/authenticator => github.com/devtron-labs/devtron-services/authenticator v0.0.0-20250521130159-a5d26dc2ab8d -# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20250521130159-a5d26dc2ab8d +# github.com/devtron-labs/authenticator => github.com/devtron-labs/devtron-services/authenticator v0.0.0-20250604112749-a4fc904430f9 +# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20250604112749-a4fc904430f9 # github.com/go-check/check => github.com/go-check/check v0.0.0-20180628173108-788fd7840127 # github.com/googleapis/gnostic => github.com/googleapis/gnostic v0.5.5 # k8s.io/api => k8s.io/api v0.29.7 diff --git a/wire_gen.go b/wire_gen.go index 4e2dea94c5..31520f79bb 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -365,7 +365,8 @@ func InitializeApp() (*App, error) { k8sInformerFactoryImpl := informer.NewK8sInformerFactoryImpl(sugaredLogger, syncMap, k8sServiceImpl) cronLoggerImpl := cron.NewCronLoggerImpl(sugaredLogger) clusterReadServiceImpl := read2.NewClusterReadServiceImpl(sugaredLogger, clusterRepositoryImpl) - clusterServiceImpl, err := cluster.NewClusterServiceImpl(clusterRepositoryImpl, sugaredLogger, k8sServiceImpl, k8sInformerFactoryImpl, userAuthRepositoryImpl, userRepositoryImpl, roleGroupRepositoryImpl, environmentVariables, cronLoggerImpl, clusterReadServiceImpl) + runnable := asyncProvider.NewAsyncRunnable(sugaredLogger) + clusterServiceImpl, err := cluster.NewClusterServiceImpl(clusterRepositoryImpl, sugaredLogger, k8sServiceImpl, k8sInformerFactoryImpl, userAuthRepositoryImpl, userRepositoryImpl, roleGroupRepositoryImpl, environmentVariables, cronLoggerImpl, clusterReadServiceImpl, runnable) if err != nil { return nil, err } @@ -378,14 +379,14 @@ func InitializeApp() (*App, error) { return nil, err } argoApplicationConfigServiceImpl := config2.NewArgoApplicationConfigServiceImpl(sugaredLogger, k8sServiceImpl, clusterRepositoryImpl) - k8sCommonServiceImpl := k8s2.NewK8sCommonServiceImpl(sugaredLogger, k8sServiceImpl, argoApplicationConfigServiceImpl, clusterReadServiceImpl) + k8sCommonServiceImpl := k8s2.NewK8sCommonServiceImpl(sugaredLogger, k8sServiceImpl, argoApplicationConfigServiceImpl, clusterReadServiceImpl, runnable) versionServiceImpl := version.NewVersionServiceImpl(sugaredLogger) acdAuthConfig, err := util3.GetACDAuthConfig() if err != nil { return nil, err } argoCDConfigGetterImpl := config3.NewArgoCDConfigGetter(beanConfig, environmentVariables, acdAuthConfig, clusterReadServiceImpl, sugaredLogger, k8sServiceImpl) - argoCDConnectionManagerImpl, err := connection.NewArgoCDConnectionManagerImpl(sugaredLogger, settingsManager, moduleRepositoryImpl, environmentVariables, k8sServiceImpl, k8sCommonServiceImpl, versionServiceImpl, gitOpsConfigReadServiceImpl, k8sRuntimeConfig, argoCDConfigGetterImpl) + argoCDConnectionManagerImpl, err := connection.NewArgoCDConnectionManagerImpl(sugaredLogger, settingsManager, moduleRepositoryImpl, environmentVariables, k8sServiceImpl, k8sCommonServiceImpl, versionServiceImpl, gitOpsConfigReadServiceImpl, k8sRuntimeConfig, argoCDConfigGetterImpl, runnable) if err != nil { return nil, err } @@ -404,7 +405,6 @@ func InitializeApp() (*App, error) { } chartTemplateServiceImpl := util.NewChartTemplateServiceImpl(sugaredLogger) gitOperationServiceImpl := git.NewGitOperationServiceImpl(sugaredLogger, gitFactory, gitOpsConfigReadServiceImpl, chartTemplateServiceImpl, environmentVariables) - runnable := asyncProvider.NewAsyncRunnable(sugaredLogger) repositoryCredsK8sClientImpl := repoCredsK8sClient.NewRepositoryCredsK8sClientImpl(sugaredLogger, k8sServiceImpl) argoClientWrapperServiceEAImpl := argocdServer.NewArgoClientWrapperServiceEAImpl(sugaredLogger, repositoryCredsK8sClientImpl, argoCDConfigGetterImpl) argoK8sClientImpl := argocdServer.NewArgoK8sClientImpl(sugaredLogger, k8sServiceImpl) @@ -518,7 +518,7 @@ func InitializeApp() (*App, error) { if err != nil { return nil, err } - scopedVariableServiceImpl, err := variables.NewScopedVariableServiceImpl(sugaredLogger, scopedVariableRepositoryImpl, appRepositoryImpl, environmentRepositoryImpl, devtronResourceSearchableKeyServiceImpl, clusterRepositoryImpl, qualifierMappingServiceImpl) + scopedVariableServiceImpl, err := variables.NewScopedVariableServiceImpl(sugaredLogger, scopedVariableRepositoryImpl, appRepositoryImpl, environmentRepositoryImpl, devtronResourceSearchableKeyServiceImpl, clusterRepositoryImpl, qualifierMappingServiceImpl, runnable) if err != nil { return nil, err } @@ -581,7 +581,8 @@ func InitializeApp() (*App, error) { scanToolMetadataRepositoryImpl := repository16.NewScanToolMetadataRepositoryImpl(db, sugaredLogger) scanToolMetadataServiceImpl := scanTool.NewScanToolMetadataServiceImpl(sugaredLogger, scanToolMetadataRepositoryImpl) moduleServiceImpl := module.NewModuleServiceImpl(sugaredLogger, serverEnvConfigServerEnvConfig, moduleRepositoryImpl, moduleActionAuditLogRepositoryImpl, helmAppServiceImpl, serverDataStoreServerDataStore, serverCacheServiceImpl, moduleCacheServiceImpl, moduleCronServiceImpl, moduleServiceHelperImpl, moduleResourceStatusRepositoryImpl, scanToolMetadataServiceImpl, environmentVariables, moduleEnvConfig) - eventRESTClientImpl := client2.NewEventRESTClientImpl(sugaredLogger, httpClient, eventClientConfig, pubSubClientServiceImpl, ciPipelineRepositoryImpl, pipelineRepositoryImpl, attributesRepositoryImpl, moduleServiceImpl) + notificationSettingsRepositoryImpl := repository2.NewNotificationSettingsRepositoryImpl(db) + eventRESTClientImpl := client2.NewEventRESTClientImpl(sugaredLogger, httpClient, eventClientConfig, pubSubClientServiceImpl, ciPipelineRepositoryImpl, pipelineRepositoryImpl, attributesRepositoryImpl, moduleServiceImpl, notificationSettingsRepositoryImpl) cdWorkflowRepositoryImpl := pipelineConfig.NewCdWorkflowRepositoryImpl(db, sugaredLogger) ciWorkflowRepositoryImpl := pipelineConfig.NewCiWorkflowRepositoryImpl(db, sugaredLogger) ciPipelineMaterialRepositoryImpl := pipelineConfig.NewCiPipelineMaterialRepositoryImpl(db, sugaredLogger) @@ -619,7 +620,8 @@ func InitializeApp() (*App, error) { workflowStageRepositoryImpl := repository18.NewWorkflowStageRepositoryImpl(sugaredLogger, db) workFlowStageStatusServiceImpl := workflowStatus.NewWorkflowStageFlowStatusServiceImpl(sugaredLogger, workflowStageRepositoryImpl, ciWorkflowRepositoryImpl, cdWorkflowRepositoryImpl, transactionUtilImpl) cdWorkflowRunnerServiceImpl := cd.NewCdWorkflowRunnerServiceImpl(sugaredLogger, cdWorkflowRepositoryImpl, workFlowStageStatusServiceImpl, transactionUtilImpl) - appServiceImpl := app2.NewAppService(pipelineOverrideRepositoryImpl, utilMergeUtil, sugaredLogger, pipelineRepositoryImpl, eventRESTClientImpl, eventSimpleFactoryImpl, appRepositoryImpl, configMapRepositoryImpl, chartRepositoryImpl, cdWorkflowRepositoryImpl, commonServiceImpl, chartTemplateServiceImpl, pipelineStatusTimelineRepositoryImpl, pipelineStatusTimelineResourcesServiceImpl, pipelineStatusSyncDetailServiceImpl, pipelineStatusTimelineServiceImpl, appServiceConfig, appStatusServiceImpl, installedAppReadServiceImpl, installedAppVersionHistoryRepositoryImpl, scopedVariableCMCSManagerImpl, acdConfig, gitOpsConfigReadServiceImpl, gitOperationServiceImpl, deploymentTemplateServiceImpl, appListingServiceImpl, deploymentConfigServiceImpl, envConfigOverrideReadServiceImpl, cdWorkflowRunnerServiceImpl) + deploymentEventHandlerImpl := app2.NewDeploymentEventHandlerImpl(sugaredLogger, eventRESTClientImpl, eventSimpleFactoryImpl, runnable) + appServiceImpl := app2.NewAppService(pipelineOverrideRepositoryImpl, utilMergeUtil, sugaredLogger, pipelineRepositoryImpl, eventRESTClientImpl, eventSimpleFactoryImpl, appRepositoryImpl, configMapRepositoryImpl, chartRepositoryImpl, cdWorkflowRepositoryImpl, commonServiceImpl, chartTemplateServiceImpl, pipelineStatusTimelineRepositoryImpl, pipelineStatusTimelineResourcesServiceImpl, pipelineStatusSyncDetailServiceImpl, pipelineStatusTimelineServiceImpl, appServiceConfig, appStatusServiceImpl, installedAppReadServiceImpl, installedAppVersionHistoryRepositoryImpl, scopedVariableCMCSManagerImpl, acdConfig, gitOpsConfigReadServiceImpl, gitOperationServiceImpl, deploymentTemplateServiceImpl, appListingServiceImpl, deploymentConfigServiceImpl, envConfigOverrideReadServiceImpl, cdWorkflowRunnerServiceImpl, deploymentEventHandlerImpl) scopedVariableManagerImpl, err := variables.NewScopedVariableManagerImpl(sugaredLogger, scopedVariableServiceImpl, variableEntityMappingServiceImpl, variableSnapshotHistoryServiceImpl, variableTemplateParserImpl) if err != nil { return nil, err @@ -685,7 +687,7 @@ func InitializeApp() (*App, error) { return nil, err } blobStorageConfigServiceImpl := pipeline.NewBlobStorageConfigServiceImpl(sugaredLogger, k8sServiceImpl, ciCdConfig) - handlerServiceImpl := trigger.NewHandlerServiceImpl(sugaredLogger, workflowServiceImpl, ciPipelineMaterialRepositoryImpl, ciPipelineRepositoryImpl, ciArtifactRepositoryImpl, pipelineStageServiceImpl, userServiceImpl, ciTemplateReadServiceImpl, appCrudOperationServiceImpl, environmentRepositoryImpl, appRepositoryImpl, scopedVariableManagerImpl, customTagServiceImpl, ciCdPipelineOrchestratorImpl, attributesServiceImpl, pluginInputVariableParserImpl, globalPluginServiceImpl, ciServiceImpl, ciWorkflowRepositoryImpl, clientImpl, ciLogServiceImpl, blobStorageConfigServiceImpl, clusterServiceImplExtended, environmentServiceImpl, k8sServiceImpl) + handlerServiceImpl := trigger.NewHandlerServiceImpl(sugaredLogger, workflowServiceImpl, ciPipelineMaterialRepositoryImpl, ciPipelineRepositoryImpl, ciArtifactRepositoryImpl, pipelineStageServiceImpl, userServiceImpl, ciTemplateReadServiceImpl, appCrudOperationServiceImpl, environmentRepositoryImpl, appRepositoryImpl, scopedVariableManagerImpl, customTagServiceImpl, ciCdPipelineOrchestratorImpl, attributesServiceImpl, pluginInputVariableParserImpl, globalPluginServiceImpl, ciServiceImpl, ciWorkflowRepositoryImpl, clientImpl, ciLogServiceImpl, blobStorageConfigServiceImpl, clusterServiceImplExtended, environmentServiceImpl, k8sServiceImpl, runnable) gitWebhookServiceImpl := gitWebhook.NewGitWebhookServiceImpl(sugaredLogger, gitWebhookRepositoryImpl, handlerServiceImpl) gitWebhookRestHandlerImpl := restHandler.NewGitWebhookRestHandlerImpl(sugaredLogger, gitWebhookServiceImpl) ecrConfig, err := pipeline.GetEcrConfig() @@ -733,7 +735,7 @@ func InitializeApp() (*App, error) { return nil, err } appDeploymentTypeChangeManagerImpl := pipeline.NewAppDeploymentTypeChangeManagerImpl(sugaredLogger, pipelineRepositoryImpl, appServiceImpl, appStatusRepositoryImpl, helmAppServiceImpl, appArtifactManagerImpl, cdPipelineConfigServiceImpl, gitOpsConfigReadServiceImpl, chartServiceImpl, workflowEventPublishServiceImpl, deploymentConfigServiceImpl, chartReadServiceImpl, deploymentConfigReadServiceImpl) - devtronAppConfigServiceImpl := pipeline.NewDevtronAppConfigServiceImpl(sugaredLogger, ciCdPipelineOrchestratorImpl, appRepositoryImpl, pipelineRepositoryImpl, resourceGroupServiceImpl, enforcerUtilImpl, ciMaterialConfigServiceImpl) + devtronAppConfigServiceImpl := pipeline.NewDevtronAppConfigServiceImpl(sugaredLogger, ciCdPipelineOrchestratorImpl, appRepositoryImpl, pipelineRepositoryImpl, resourceGroupServiceImpl, enforcerUtilImpl, ciMaterialConfigServiceImpl, userRepositoryImpl) pipelineBuilderImpl := pipeline.NewPipelineBuilderImpl(sugaredLogger, gitMaterialReadServiceImpl, chartRepositoryImpl, ciPipelineConfigServiceImpl, ciMaterialConfigServiceImpl, appArtifactManagerImpl, devtronAppCMCSServiceImpl, devtronAppStrategyServiceImpl, appDeploymentTypeChangeManagerImpl, cdPipelineConfigServiceImpl, devtronAppConfigServiceImpl) deploymentTemplateValidationServiceEntImpl := validator.NewDeploymentTemplateValidationServiceEntImpl() deploymentTemplateValidationServiceImpl := validator.NewDeploymentTemplateValidationServiceImpl(sugaredLogger, chartRefServiceImpl, scopedVariableManagerImpl, deployedAppMetricsServiceImpl, deploymentTemplateValidationServiceEntImpl) @@ -770,7 +772,7 @@ func InitializeApp() (*App, error) { scanToolExecutionHistoryMappingRepositoryImpl := repository24.NewScanToolExecutionHistoryMappingRepositoryImpl(db, sugaredLogger) cdWorkflowReadServiceImpl := read20.NewCdWorkflowReadServiceImpl(sugaredLogger, cdWorkflowRepositoryImpl) imageScanServiceImpl := imageScanning.NewImageScanServiceImpl(sugaredLogger, imageScanHistoryRepositoryImpl, imageScanResultRepositoryImpl, imageScanObjectMetaRepositoryImpl, cveStoreRepositoryImpl, imageScanDeployInfoRepositoryImpl, userServiceImpl, appRepositoryImpl, environmentServiceImpl, ciArtifactRepositoryImpl, policyServiceImpl, pipelineRepositoryImpl, ciPipelineRepositoryImpl, scanToolMetadataRepositoryImpl, scanToolExecutionHistoryMappingRepositoryImpl, cvePolicyRepositoryImpl, cdWorkflowReadServiceImpl) - devtronAppsHandlerServiceImpl, err := devtronApps.NewHandlerServiceImpl(sugaredLogger, cdWorkflowCommonServiceImpl, gitOpsManifestPushServiceImpl, gitOpsConfigReadServiceImpl, argoK8sClientImpl, acdConfig, argoClientWrapperServiceImpl, pipelineStatusTimelineServiceImpl, chartTemplateServiceImpl, workflowEventPublishServiceImpl, manifestCreationServiceImpl, deployedConfigurationHistoryServiceImpl, pipelineStageServiceImpl, globalPluginServiceImpl, customTagServiceImpl, pluginInputVariableParserImpl, prePostCdScriptHistoryServiceImpl, scopedVariableCMCSManagerImpl, imageDigestPolicyServiceImpl, userServiceImpl, helmAppServiceImpl, enforcerUtilImpl, userDeploymentRequestServiceImpl, helmAppClientImpl, eventSimpleFactoryImpl, eventRESTClientImpl, environmentVariables, appRepositoryImpl, ciPipelineMaterialRepositoryImpl, imageScanHistoryReadServiceImpl, imageScanDeployInfoReadServiceImpl, imageScanDeployInfoServiceImpl, pipelineRepositoryImpl, pipelineOverrideRepositoryImpl, manifestPushConfigRepositoryImpl, chartRepositoryImpl, environmentRepositoryImpl, cdWorkflowRepositoryImpl, ciWorkflowRepositoryImpl, ciArtifactRepositoryImpl, ciTemplateReadServiceImpl, gitMaterialReadServiceImpl, appLabelRepositoryImpl, ciPipelineRepositoryImpl, appWorkflowRepositoryImpl, dockerArtifactStoreRepositoryImpl, imageScanServiceImpl, k8sServiceImpl, transactionUtilImpl, deploymentConfigServiceImpl, ciCdPipelineOrchestratorImpl, gitOperationServiceImpl, attributesServiceImpl, clusterRepositoryImpl, cdWorkflowRunnerServiceImpl, clusterServiceImplExtended, ciLogServiceImpl, workflowServiceImpl, blobStorageConfigServiceImpl) + devtronAppsHandlerServiceImpl, err := devtronApps.NewHandlerServiceImpl(sugaredLogger, cdWorkflowCommonServiceImpl, gitOpsManifestPushServiceImpl, gitOpsConfigReadServiceImpl, argoK8sClientImpl, acdConfig, argoClientWrapperServiceImpl, pipelineStatusTimelineServiceImpl, chartTemplateServiceImpl, workflowEventPublishServiceImpl, manifestCreationServiceImpl, deployedConfigurationHistoryServiceImpl, pipelineStageServiceImpl, globalPluginServiceImpl, customTagServiceImpl, pluginInputVariableParserImpl, prePostCdScriptHistoryServiceImpl, scopedVariableCMCSManagerImpl, imageDigestPolicyServiceImpl, userServiceImpl, helmAppServiceImpl, enforcerUtilImpl, userDeploymentRequestServiceImpl, helmAppClientImpl, eventSimpleFactoryImpl, eventRESTClientImpl, environmentVariables, appRepositoryImpl, ciPipelineMaterialRepositoryImpl, imageScanHistoryReadServiceImpl, imageScanDeployInfoReadServiceImpl, imageScanDeployInfoServiceImpl, pipelineRepositoryImpl, pipelineOverrideRepositoryImpl, manifestPushConfigRepositoryImpl, chartRepositoryImpl, environmentRepositoryImpl, cdWorkflowRepositoryImpl, ciWorkflowRepositoryImpl, ciArtifactRepositoryImpl, ciTemplateReadServiceImpl, gitMaterialReadServiceImpl, appLabelRepositoryImpl, ciPipelineRepositoryImpl, appWorkflowRepositoryImpl, dockerArtifactStoreRepositoryImpl, imageScanServiceImpl, k8sServiceImpl, transactionUtilImpl, deploymentConfigServiceImpl, ciCdPipelineOrchestratorImpl, gitOperationServiceImpl, attributesServiceImpl, clusterRepositoryImpl, cdWorkflowRunnerServiceImpl, clusterServiceImplExtended, ciLogServiceImpl, workflowServiceImpl, blobStorageConfigServiceImpl, deploymentEventHandlerImpl, runnable) if err != nil { return nil, err } @@ -800,7 +802,6 @@ func InitializeApp() (*App, error) { chartProviderServiceImpl := chartProvider.NewChartProviderServiceImpl(sugaredLogger, chartRepoRepositoryImpl, chartRepositoryServiceImpl, dockerArtifactStoreRepositoryImpl, ociRegistryConfigRepositoryImpl) dockerRegRestHandlerExtendedImpl := restHandler.NewDockerRegRestHandlerExtendedImpl(dockerRegistryConfigImpl, sugaredLogger, chartProviderServiceImpl, userServiceImpl, validate, enforcerImpl, teamServiceImpl, deleteServiceExtendedImpl, deleteServiceFullModeImpl) dockerRegRouterImpl := router.NewDockerRegRouterImpl(dockerRegRestHandlerExtendedImpl) - notificationSettingsRepositoryImpl := repository2.NewNotificationSettingsRepositoryImpl(db) notificationConfigBuilderImpl := notifier.NewNotificationConfigBuilderImpl(sugaredLogger) slackNotificationRepositoryImpl := repository2.NewSlackNotificationRepositoryImpl(db) webhookNotificationRepositoryImpl := repository2.NewWebhookNotificationRepositoryImpl(db) @@ -825,7 +826,7 @@ func InitializeApp() (*App, error) { k8sResourceHistoryServiceImpl := kubernetesResourceAuditLogs.Newk8sResourceHistoryServiceImpl(k8sResourceHistoryRepositoryImpl, sugaredLogger, appRepositoryImpl, environmentRepositoryImpl) ephemeralContainersRepositoryImpl := repository5.NewEphemeralContainersRepositoryImpl(db, transactionUtilImpl) ephemeralContainerServiceImpl := cluster.NewEphemeralContainerServiceImpl(ephemeralContainersRepositoryImpl, sugaredLogger) - terminalSessionHandlerImpl := terminal.NewTerminalSessionHandlerImpl(environmentServiceImpl, sugaredLogger, k8sServiceImpl, ephemeralContainerServiceImpl, argoApplicationConfigServiceImpl, clusterReadServiceImpl) + terminalSessionHandlerImpl := terminal.NewTerminalSessionHandlerImpl(environmentServiceImpl, sugaredLogger, k8sServiceImpl, ephemeralContainerServiceImpl, argoApplicationConfigServiceImpl, clusterReadServiceImpl, runnable) fluxApplicationServiceImpl := fluxApplication.NewFluxApplicationServiceImpl(sugaredLogger, helmAppReadServiceImpl, clusterServiceImplExtended, helmAppClientImpl, pumpImpl) k8sApplicationServiceImpl, err := application2.NewK8sApplicationServiceImpl(sugaredLogger, clusterServiceImplExtended, pumpImpl, helmAppServiceImpl, k8sServiceImpl, acdAuthConfig, k8sResourceHistoryServiceImpl, k8sCommonServiceImpl, terminalSessionHandlerImpl, ephemeralContainerServiceImpl, ephemeralContainersRepositoryImpl, fluxApplicationServiceImpl, clusterReadServiceImpl) if err != nil { @@ -833,7 +834,7 @@ func InitializeApp() (*App, error) { } argoApplicationServiceImpl := argoApplication.NewArgoApplicationServiceImpl(sugaredLogger, clusterRepositoryImpl, k8sServiceImpl, helmAppClientImpl, helmAppServiceImpl, k8sApplicationServiceImpl, argoApplicationConfigServiceImpl, deploymentConfigServiceImpl) argoApplicationReadServiceImpl := read22.NewArgoApplicationReadServiceImpl(sugaredLogger, clusterRepositoryImpl, k8sServiceImpl, helmAppClientImpl, helmAppServiceImpl) - argoApplicationServiceExtendedImpl := argoApplication.NewArgoApplicationServiceExtendedServiceImpl(acdAuthConfig, argoApplicationServiceImpl, argoClientWrapperServiceImpl, argoApplicationReadServiceImpl, clusterServiceImplExtended) + argoApplicationServiceExtendedImpl := argoApplication.NewArgoApplicationServiceExtendedServiceImpl(acdAuthConfig, argoApplicationServiceImpl, argoClientWrapperServiceImpl, argoApplicationReadServiceImpl, clusterServiceImplExtended, runnable) installedAppResourceServiceImpl := resource.NewInstalledAppResourceServiceImpl(sugaredLogger, installedAppRepositoryImpl, appStoreApplicationVersionRepositoryImpl, argoClientWrapperServiceImpl, acdAuthConfig, installedAppVersionHistoryRepositoryImpl, helmAppServiceImpl, helmAppReadServiceImpl, appStatusServiceImpl, k8sCommonServiceImpl, k8sApplicationServiceImpl, k8sServiceImpl, deploymentConfigServiceImpl, ociRegistryConfigRepositoryImpl, argoApplicationServiceExtendedImpl) chartGroupEntriesRepositoryImpl := repository28.NewChartGroupEntriesRepositoryImpl(db, sugaredLogger) chartGroupReposotoryImpl := repository28.NewChartGroupReposotoryImpl(db, sugaredLogger) @@ -855,7 +856,7 @@ func InitializeApp() (*App, error) { return nil, err } cdPipelineEventPublishServiceImpl := out.NewCDPipelineEventPublishServiceImpl(sugaredLogger, pubSubClientServiceImpl) - workflowStatusServiceImpl, err := status2.NewWorkflowStatusServiceImpl(sugaredLogger, workflowDagExecutorImpl, pipelineStatusTimelineServiceImpl, appServiceImpl, appStatusServiceImpl, acdConfig, appServiceConfig, pipelineStatusSyncDetailServiceImpl, argoClientWrapperServiceImpl, cdPipelineEventPublishServiceImpl, cdWorkflowRepositoryImpl, pipelineOverrideRepositoryImpl, installedAppVersionHistoryRepositoryImpl, appRepositoryImpl, environmentRepositoryImpl, installedAppRepositoryImpl, installedAppReadServiceImpl, pipelineStatusTimelineRepositoryImpl, pipelineRepositoryImpl, appListingServiceImpl, deploymentConfigServiceImpl, cdWorkflowRunnerServiceImpl) + workflowStatusServiceImpl, err := status2.NewWorkflowStatusServiceImpl(sugaredLogger, workflowDagExecutorImpl, pipelineStatusTimelineServiceImpl, appServiceImpl, appStatusServiceImpl, acdConfig, appServiceConfig, pipelineStatusSyncDetailServiceImpl, argoClientWrapperServiceImpl, cdPipelineEventPublishServiceImpl, cdWorkflowRepositoryImpl, pipelineOverrideRepositoryImpl, installedAppVersionHistoryRepositoryImpl, appRepositoryImpl, environmentRepositoryImpl, installedAppRepositoryImpl, installedAppReadServiceImpl, pipelineStatusTimelineRepositoryImpl, pipelineRepositoryImpl, appListingServiceImpl, deploymentConfigServiceImpl, cdWorkflowRunnerServiceImpl, deploymentEventHandlerImpl) if err != nil { return nil, err } @@ -895,7 +896,7 @@ func InitializeApp() (*App, error) { deploymentTemplateActionImpl := batch.NewDeploymentTemplateActionImpl(sugaredLogger, appRepositoryImpl, chartServiceImpl) deploymentActionImpl := batch.NewDeploymentActionImpl(pipelineBuilderImpl, sugaredLogger, appRepositoryImpl, environmentServiceImpl, appWorkflowRepositoryImpl, ciPipelineRepositoryImpl, pipelineRepositoryImpl, dataHolderActionImpl, deploymentTemplateActionImpl) workflowActionImpl := batch.NewWorkflowActionImpl(sugaredLogger, appRepositoryImpl, appWorkflowServiceImpl, buildActionImpl, deploymentActionImpl) - batchOperationRestHandlerImpl := restHandler.NewBatchOperationRestHandlerImpl(userServiceImpl, enforcerImpl, workflowActionImpl, teamServiceImpl, sugaredLogger, enforcerUtilImpl) + batchOperationRestHandlerImpl := restHandler.NewBatchOperationRestHandlerImpl(userServiceImpl, enforcerImpl, workflowActionImpl, teamServiceImpl, sugaredLogger, enforcerUtilImpl, runnable) batchOperationRouterImpl := router.NewBatchOperationRouterImpl(batchOperationRestHandlerImpl, sugaredLogger) chartGroupRestHandlerImpl := chartGroup2.NewChartGroupRestHandlerImpl(chartGroupServiceImpl, sugaredLogger, userServiceImpl, enforcerImpl, validate) chartGroupRouterImpl := chartGroup2.NewChartGroupRouterImpl(chartGroupRestHandlerImpl) @@ -939,7 +940,7 @@ func InitializeApp() (*App, error) { return nil, err } providerIdentifierServiceImpl := providerIdentifier.NewProviderIdentifierServiceImpl(sugaredLogger) - telemetryEventClientImplExtended, err := telemetry2.NewTelemetryEventClientImplExtended(sugaredLogger, httpClient, clusterServiceImplExtended, k8sServiceImpl, acdAuthConfig, environmentServiceImpl, userServiceImpl, appListingRepositoryImpl, posthogClient, serviceImpl, ciPipelineConfigReadServiceImpl, pipelineRepositoryImpl, gitProviderRepositoryImpl, attributesRepositoryImpl, ssoLoginServiceImpl, appRepositoryImpl, ciWorkflowRepositoryImpl, cdWorkflowRepositoryImpl, dockerArtifactStoreRepositoryImpl, gitMaterialReadServiceImpl, ciTemplateRepositoryImpl, chartRepositoryImpl, userAuditServiceImpl, ciBuildConfigServiceImpl, moduleRepositoryImpl, serverDataStoreServerDataStore, helmAppClientImpl, installedAppReadServiceImpl, userAttributesRepositoryImpl, providerIdentifierServiceImpl, cronLoggerImpl, gitOpsConfigReadServiceImpl, environmentVariables) + telemetryEventClientImplExtended, err := telemetry2.NewTelemetryEventClientImplExtended(sugaredLogger, httpClient, clusterServiceImplExtended, k8sServiceImpl, acdAuthConfig, environmentServiceImpl, userServiceImpl, appListingRepositoryImpl, posthogClient, serviceImpl, ciPipelineConfigReadServiceImpl, pipelineRepositoryImpl, gitProviderRepositoryImpl, attributesRepositoryImpl, ssoLoginServiceImpl, appRepositoryImpl, ciWorkflowRepositoryImpl, cdWorkflowRepositoryImpl, dockerArtifactStoreRepositoryImpl, gitMaterialReadServiceImpl, ciTemplateRepositoryImpl, chartRepositoryImpl, userAuditServiceImpl, ciBuildConfigServiceImpl, moduleRepositoryImpl, serverDataStoreServerDataStore, helmAppClientImpl, installedAppReadServiceImpl, userAttributesRepositoryImpl, providerIdentifierServiceImpl, cronLoggerImpl, gitOpsConfigReadServiceImpl, environmentVariables, globalPluginRepositoryImpl, cvePolicyRepositoryImpl, defaultAuthPolicyRepositoryImpl, rbacPolicyDataRepositoryImpl) if err != nil { return nil, err } @@ -958,7 +959,7 @@ func InitializeApp() (*App, error) { webhookListenerRouterImpl := router.NewWebhookListenerRouterImpl(webhookEventHandlerImpl) appFilteringRestHandlerImpl := appList.NewAppFilteringRestHandlerImpl(sugaredLogger, teamServiceImpl, enforcerImpl, userServiceImpl, clusterServiceImplExtended, environmentServiceImpl, teamReadServiceImpl) appFilteringRouterImpl := appList2.NewAppFilteringRouterImpl(appFilteringRestHandlerImpl) - resourceTreeServiceImpl := resourceTree.NewServiceImpl(sugaredLogger, appListingServiceImpl, appStatusServiceImpl, argoApplicationServiceExtendedImpl, cdApplicationStatusUpdateHandlerImpl, helmAppReadServiceImpl, helmAppServiceImpl, k8sApplicationServiceImpl, k8sCommonServiceImpl, environmentReadServiceImpl) + resourceTreeServiceImpl := resourceTree.NewServiceImpl(sugaredLogger, appListingServiceImpl, appStatusServiceImpl, argoApplicationServiceExtendedImpl, cdApplicationStatusUpdateHandlerImpl, helmAppReadServiceImpl, helmAppServiceImpl, k8sApplicationServiceImpl, k8sCommonServiceImpl, environmentReadServiceImpl, runnable) appListingRestHandlerImpl := appList.NewAppListingRestHandlerImpl(appListingServiceImpl, enforcerImpl, pipelineBuilderImpl, sugaredLogger, enforcerUtilImpl, deploymentGroupServiceImpl, userServiceImpl, k8sCommonServiceImpl, installedAppDBExtendedServiceImpl, installedAppResourceServiceImpl, pipelineRepositoryImpl, k8sApplicationServiceImpl, deploymentConfigServiceImpl, resourceTreeServiceImpl) appListingRouterImpl := appList2.NewAppListingRouterImpl(appListingRestHandlerImpl) appInfoRestHandlerImpl := appInfo.NewAppInfoRestHandlerImpl(sugaredLogger, appCrudOperationServiceImpl, userServiceImpl, validate, enforcerUtilImpl, enforcerImpl, helmAppServiceImpl, enforcerUtilHelmImpl, genericNoteServiceImpl, commonEnforcementUtilImpl) @@ -1029,7 +1030,7 @@ func InitializeApp() (*App, error) { if err != nil { return nil, err } - userTerminalAccessServiceImpl, err := clusterTerminalAccess.NewUserTerminalAccessServiceImpl(sugaredLogger, terminalAccessRepositoryImpl, userTerminalSessionConfig, k8sCommonServiceImpl, terminalSessionHandlerImpl, k8sCapacityServiceImpl, k8sServiceImpl, cronLoggerImpl) + userTerminalAccessServiceImpl, err := clusterTerminalAccess.NewUserTerminalAccessServiceImpl(sugaredLogger, terminalAccessRepositoryImpl, userTerminalSessionConfig, k8sCommonServiceImpl, terminalSessionHandlerImpl, k8sCapacityServiceImpl, k8sServiceImpl, cronLoggerImpl, runnable) if err != nil { return nil, err } @@ -1088,7 +1089,7 @@ func InitializeApp() (*App, error) { cdWorkflowServiceImpl := cd.NewCdWorkflowServiceImpl(sugaredLogger, cdWorkflowRepositoryImpl) cdWorkflowRunnerReadServiceImpl := read20.NewCdWorkflowRunnerReadServiceImpl(sugaredLogger, cdWorkflowRepositoryImpl) webhookServiceImpl := pipeline.NewWebhookServiceImpl(ciArtifactRepositoryImpl, sugaredLogger, ciPipelineRepositoryImpl, ciWorkflowRepositoryImpl, cdWorkflowCommonServiceImpl, workFlowStageStatusServiceImpl, ciServiceImpl) - workflowEventProcessorImpl, err := in.NewWorkflowEventProcessorImpl(sugaredLogger, pubSubClientServiceImpl, cdWorkflowServiceImpl, cdWorkflowReadServiceImpl, cdWorkflowRunnerServiceImpl, cdWorkflowRunnerReadServiceImpl, workflowDagExecutorImpl, ciHandlerImpl, cdHandlerImpl, eventSimpleFactoryImpl, eventRESTClientImpl, devtronAppsHandlerServiceImpl, deployedAppServiceImpl, webhookServiceImpl, validate, environmentVariables, cdWorkflowCommonServiceImpl, cdPipelineConfigServiceImpl, userDeploymentRequestServiceImpl, serviceImpl, pipelineRepositoryImpl, ciArtifactRepositoryImpl, cdWorkflowRepositoryImpl, deploymentConfigServiceImpl, handlerServiceImpl) + workflowEventProcessorImpl, err := in.NewWorkflowEventProcessorImpl(sugaredLogger, pubSubClientServiceImpl, cdWorkflowServiceImpl, cdWorkflowReadServiceImpl, cdWorkflowRunnerServiceImpl, cdWorkflowRunnerReadServiceImpl, workflowDagExecutorImpl, ciHandlerImpl, cdHandlerImpl, eventSimpleFactoryImpl, eventRESTClientImpl, devtronAppsHandlerServiceImpl, deployedAppServiceImpl, webhookServiceImpl, validate, environmentVariables, cdWorkflowCommonServiceImpl, cdPipelineConfigServiceImpl, userDeploymentRequestServiceImpl, serviceImpl, pipelineRepositoryImpl, ciArtifactRepositoryImpl, cdWorkflowRepositoryImpl, deploymentConfigServiceImpl, handlerServiceImpl, runnable) if err != nil { return nil, err } @@ -1100,6 +1101,6 @@ func InitializeApp() (*App, error) { if err != nil { return nil, err } - mainApp := NewApp(muxRouter, sugaredLogger, sseSSE, syncedEnforcer, db, sessionManager, posthogClient, loggingMiddlewareImpl, centralEventProcessor, pubSubClientServiceImpl, workflowEventProcessorImpl, casbinSyncedEnforcer) + mainApp := NewApp(muxRouter, sugaredLogger, sseSSE, syncedEnforcer, db, sessionManager, posthogClient, loggingMiddlewareImpl, centralEventProcessor, pubSubClientServiceImpl, workflowEventProcessorImpl, casbinSyncedEnforcer, userServiceImpl) return mainApp, nil }