Skip to content

fix: inconsistency in saving empty GitOps repository #6612

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions client/argocdServer/ArgoClientWrapperService.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ import (
)

type ACDConfig struct {
ArgoCDAutoSyncEnabled bool `env:"ARGO_AUTO_SYNC_ENABLED" envDefault:"true" description:"If enabled all argocd application will have auto sync enabled"` // will gradually switch this flag to false in enterprise
RegisterRepoMaxRetryCount int `env:"ARGO_REPO_REGISTER_RETRY_COUNT" envDefault:"3" description:"Argo app registration in argo retries on deployment"`
RegisterRepoMaxRetryDelay int `env:"ARGO_REPO_REGISTER_RETRY_DELAY" envDefault:"10" description:"Argo app registration in argo cd on deployment delay between retry"`
ArgoCDAutoSyncEnabled bool `env:"ARGO_AUTO_SYNC_ENABLED" envDefault:"true" description:"If enabled all argocd application will have auto sync enabled" example:"true" deprecated:"false"` // will gradually switch this flag to false in enterprise
RegisterRepoMaxRetryCount int `env:"ARGO_REPO_REGISTER_RETRY_COUNT" envDefault:"4" description:"Retry count for registering a GitOps repository to ArgoCD" example:"3" deprecated:"false"`
RegisterRepoMaxRetryDelay int `env:"ARGO_REPO_REGISTER_RETRY_DELAY" envDefault:"5" description:"Delay (in Seconds) between the retries for registering a GitOps repository to ArgoCD" example:"5" deprecated:"false"`
}

func (config *ACDConfig) IsManualSyncEnabled() bool {
Expand Down Expand Up @@ -105,7 +105,7 @@ type ApplicationClientWrapper interface {
// IsArgoAppPatchRequired decides weather the v1alpha1.ApplicationSource requires to be updated
IsArgoAppPatchRequired(argoAppSpec *v1alpha1.ApplicationSource, currentGitRepoUrl, currentTargetRevision, currentChartPath string) bool

// GetGitOpsRepoName returns the GitOps repository name, configured for the argoCd app
// GetGitOpsRepoNameForApplication returns the GitOps repository name, configured for the argoCd app
GetGitOpsRepoNameForApplication(ctx context.Context, appName string) (gitOpsRepoName string, err error)

GetGitOpsRepoURLForApplication(ctx context.Context, appName string) (gitOpsRepoURL string, err error)
Expand All @@ -127,6 +127,7 @@ type RepoCredsClientWrapper interface {
type CertificateClientWrapper interface {
CreateCertificate(ctx context.Context, query *certificate.RepositoryCertificateCreateRequest) (*v1alpha1.RepositoryCertificateList, error)
DeleteCertificate(ctx context.Context, query *certificate.RepositoryCertificateQuery, opts ...grpc.CallOption) (*v1alpha1.RepositoryCertificateList, error)
CertificateClientWrapperEnt
}

type ClusterClientWrapper interface {
Expand All @@ -147,7 +148,7 @@ type ArgoClientWrapperServiceImpl struct {
repositoryService repository.ServiceClient
clusterClient cluster.ServiceClient
repoCredsClient repocreds2.ServiceClient
CertificateClient certificate2.ServiceClient
certificateClient certificate2.ServiceClient
logger *zap.SugaredLogger
ACDConfig *ACDConfig
gitOpsConfigReadService config.GitOpsConfigReadService
Expand Down Expand Up @@ -176,7 +177,7 @@ func NewArgoClientWrapperServiceImpl(
repositoryService: repositoryService,
clusterClient: clusterClient,
repoCredsClient: repocredsClient,
CertificateClient: CertificateClient,
certificateClient: CertificateClient,
logger: logger,
ACDConfig: ACDConfig,
gitOpsConfigReadService: gitOpsConfigReadService,
Expand Down Expand Up @@ -521,7 +522,7 @@ func (impl *ArgoClientWrapperServiceImpl) createRepoInArgoCd(ctx context.Context
}
repo, err := impl.repositoryService.Create(ctx, grpcConfig, &repository2.RepoCreateRequest{Repo: repo, Upsert: true})
if err != nil {
impl.logger.Errorw("error in creating argo Repository", "err", err)
impl.logger.Errorw("error in creating argo Repository", "url", gitOpsRepoUrl, "err", err)
return err
}
return nil
Expand All @@ -543,15 +544,18 @@ func (impl *ArgoClientWrapperServiceImpl) handleArgoRepoCreationError(ctx contex
}
}
if isEmptyRepoError {
// - found empty repository, create some file in repository
impl.logger.Infow("handling for empty repo", "url", gitOpsRepoUrl)
// - found empty repository (there is no origin/HEAD)
// - create new commit on HEAD (default branch) with a README file
// - then register the repository in ArgoCD
gitOpsRepoName := impl.gitOpsConfigReadService.GetGitOpsRepoNameFromUrl(gitOpsRepoUrl)
err := impl.gitOperationService.CreateReadmeInGitRepo(ctx, gitOpsRepoName, targetRevision, userId)
err := impl.gitOperationService.CreateFirstCommitOnHead(ctx, gitOpsRepoName, userId)
if err != nil {
impl.logger.Errorw("error in creating file in git repo", "err", err)
return err
}
}
// try to register with after creating readme file
// try to register with after commiting a file to origin/HEAD
return impl.createRepoInArgoCd(ctx, grpcConfig, gitOpsRepoUrl)
}

Expand Down Expand Up @@ -586,13 +590,13 @@ func (impl *ArgoClientWrapperServiceImpl) CreateCertificate(ctx context.Context,
if err != nil {
return nil, err
}
return impl.CertificateClient.CreateCertificate(ctx, grpcConfig, query)
return impl.certificateClient.CreateCertificate(ctx, grpcConfig, query)
}

func (impl *ArgoClientWrapperServiceImpl) DeleteCertificate(ctx context.Context, query *certificate.RepositoryCertificateQuery, opts ...grpc.CallOption) (*v1alpha1.RepositoryCertificateList, error) {
grpcConfig, err := impl.acdConfigGetter.GetGRPCConfig()
if err != nil {
return nil, err
}
return impl.CertificateClient.DeleteCertificate(ctx, grpcConfig, query, opts...)
return impl.certificateClient.DeleteCertificate(ctx, grpcConfig, query, opts...)
}
20 changes: 20 additions & 0 deletions client/argocdServer/ArgoClientWrapperService_ent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 argocdServer

type CertificateClientWrapperEnt interface {
}
3 changes: 2 additions & 1 deletion client/argocdServer/bean/bean.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ const RegisterRepoMaxRetryCount = 3
var EmptyRepoErrorList = []string{"failed to get index: 404 Not Found", "remote repository is empty"}

// ArgoRepoSyncDelayErr - This error occurs inconsistently; ArgoCD requires 80-120s after last commit for create repository operation
const ArgoRepoSyncDelayErr = "Unable to resolve 'HEAD' to a commit SHA"
// Error message reference: https://github.com/argoproj/argo-cd/blob/master/util/git/client.go#L718
const ArgoRepoSyncDelayErr = "unable to resolve 'HEAD' to a commit SHA"

const (
Degraded = "Degraded"
Expand Down
1 change: 0 additions & 1 deletion client/argocdServer/k8sClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,6 @@ func (impl ArgoK8sClientImpl) DeleteArgoApplication(ctx context.Context, k8sConf
patchType := types.MergePatchType
patchJSON := ""

//TODO: ayush test cascade delete
if cascadeDelete {
patchJSON = `{"metadata": {"finalizers": ["resources-finalizer.argocd.argoproj.io"]}}`
} else {
Expand Down
2 changes: 1 addition & 1 deletion env_gen.json

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions env_gen.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,11 @@
| APP_SYNC_JOB_RESOURCES_OBJ | string | | To pass the resource of app sync | | false |
| APP_SYNC_SERVICE_ACCOUNT | string |chart-sync | Service account to be used in app sync Job | | false |
| APP_SYNC_SHUTDOWN_WAIT_DURATION | int |120 | | | false |
| ARGO_AUTO_SYNC_ENABLED | bool |true | If enabled all argocd application will have auto sync enabled | | false |
| ARGO_AUTO_SYNC_ENABLED | bool |true | If enabled all argocd application will have auto sync enabled | true | false |
| ARGO_GIT_COMMIT_RETRY_COUNT_ON_CONFLICT | int |3 | retry argocd app manual sync if the timeline is stuck in ARGOCD_SYNC_INITIATED state for more than this defined time (in mins) | | false |
| ARGO_GIT_COMMIT_RETRY_DELAY_ON_CONFLICT | int |1 | Delay on retrying the maifest commit the on gitops | | false |
| ARGO_REPO_REGISTER_RETRY_COUNT | int |3 | Argo app registration in argo retries on deployment | | false |
| ARGO_REPO_REGISTER_RETRY_DELAY | int |10 | Argo app registration in argo cd on deployment delay between retry | | false |
| ARGO_REPO_REGISTER_RETRY_COUNT | int |4 | Retry count for registering a GitOps repository to ArgoCD | 3 | false |
| ARGO_REPO_REGISTER_RETRY_DELAY | int |5 | Delay (in Seconds) between the retries for registering a GitOps repository to ArgoCD | 5 | false |
| ASYNC_BUILDX_CACHE_EXPORT | bool |false | To enable async container image cache export | | false |
| BATCH_SIZE | int |5 | 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. | | false |
| BLOB_STORAGE_ENABLED | bool |false | | | false |
Expand Down Expand Up @@ -185,6 +185,9 @@
| FEATURE_RESTART_WORKLOAD_BATCH_SIZE | int |1 | restart workload retrieval batch size | | false |
| FEATURE_RESTART_WORKLOAD_WORKER_POOL_SIZE | int |5 | restart workload retrieval pool size | | false |
| FORCE_SECURITY_SCANNING | bool |false | By enabling this no one can disable image scaning on ci-pipeline from UI | | false |
| GITHUB_ORG_NAME | string | | | | false |
| GITHUB_TOKEN | string | | | | false |
| GITHUB_USERNAME | string | | | | false |
| GITOPS_REPO_PREFIX | string | | Prefix for Gitops repo being creation for argocd application | | false |
| GO_RUNTIME_ENV | string |production | | | false |
| GRAFANA_HOST | string |localhost | Host URL for the grafana dashboard | | false |
Expand Down
8 changes: 8 additions & 0 deletions internal/sql/repository/GitOpsConfigRepository.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type GitOpsConfigRepository interface {
GetAllGitOpsConfig() ([]*GitOpsConfig, error)
GetAllGitOpsConfigCount() (int, error)
GetGitOpsConfigByProvider(provider string) (*GitOpsConfig, error)
CheckIfGitOpsProviderExist(provider string) (bool, error)
GetGitOpsConfigActive() (*GitOpsConfig, error)
GetConnection() *pg.DB
GetEmailIdFromActiveGitOpsConfig() (string, error)
Expand Down Expand Up @@ -105,6 +106,13 @@ func (impl *GitOpsConfigRepositoryImpl) GetGitOpsConfigByProvider(provider strin
return &model, err
}

func (impl *GitOpsConfigRepositoryImpl) CheckIfGitOpsProviderExist(provider string) (bool, error) {
found, err := impl.dbConnection.Model((*GitOpsConfig)(nil)).
Where("provider = ?", provider).
Exists()
return found, err
}

func (impl *GitOpsConfigRepositoryImpl) GetGitOpsConfigActive() (*GitOpsConfig, error) {
var model GitOpsConfig
err := impl.dbConnection.Model(&model).Where("active = ?", true).Limit(1).Select()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@ import (
"github.com/google/go-github/github"
"github.com/microsoft/azure-devops-go-api/azuredevops"
"github.com/xanzy/go-gitlab"
"strconv"

//"github.com/xanzy/go-gitlab"
"net/http"
"strconv"
"strings"
)

type InstalledAppGitOpsService interface {
Expand Down Expand Up @@ -176,7 +175,7 @@ func (impl *FullModeDeploymentServiceImpl) parseGitRepoErrorResponse(err error)
impl.Logger.Errorw("no content found while updating git repo gitlab, do auto fix", "error", err)
noTargetFound = true
}
if err.Error() == git.BITBUCKET_REPO_NOT_FOUND_ERROR {
if strings.Contains(err.Error(), git.BitbucketRepoNotFoundError.Error()) {
impl.Logger.Errorw("no content found while updating git repo bitbucket, do auto fix", "error", err)
noTargetFound = true
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/deployment/gitOps/adapter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func GetGitOpsConfigBean(model *repository.GitOpsConfig) *apiGitOpsBean.GitOpsCo
BitBucketWorkspaceId: model.BitBucketWorkspaceId,
BitBucketProjectKey: model.BitBucketProjectKey,
AllowCustomRepository: model.AllowCustomRepository,
EnableTLSVerification: true,
EnableTLSVerification: model.EnableTLSVerification,
TLSConfig: &apiBean.TLSConfig{
CaData: model.CaCert,
TLSCertData: model.TlsCert,
Expand Down
35 changes: 13 additions & 22 deletions pkg/deployment/gitOps/config/GitOpsConfigReadService.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ package config
import (
"errors"
"fmt"
bean3 "github.com/devtron-labs/devtron/api/bean"
bean2 "github.com/devtron-labs/devtron/api/bean/gitOps"
"github.com/devtron-labs/devtron/internal/constants"
"github.com/devtron-labs/devtron/internal/sql/repository"
internalUtil "github.com/devtron-labs/devtron/internal/util"
"github.com/devtron-labs/devtron/pkg/auth/user"
"github.com/devtron-labs/devtron/pkg/deployment/gitOps/adapter"
"github.com/devtron-labs/devtron/pkg/deployment/gitOps/config/bean"
gitAdapter "github.com/devtron-labs/devtron/pkg/deployment/gitOps/git/adapter"
gitBean "github.com/devtron-labs/devtron/pkg/deployment/gitOps/git/bean"
moduleBean "github.com/devtron-labs/devtron/pkg/module/bean"
moduleRead "github.com/devtron-labs/devtron/pkg/module/read"
moduleErr "github.com/devtron-labs/devtron/pkg/module/read/error"
Expand All @@ -49,6 +50,7 @@ type GitOpsConfigReadService interface {
GetConfiguredGitOpsCount() (int, error)
GetGitOpsProviderByRepoURL(gitRepoUrl string) (*bean2.GitOpsConfigDto, error)
GetGitOpsById(id int) (*bean2.GitOpsConfigDto, error)
GetGitConfig() (*gitBean.GitConfig, error)
}

type GitOpsConfigReadServiceImpl struct {
Expand Down Expand Up @@ -211,25 +213,14 @@ func (impl *GitOpsConfigReadServiceImpl) GetGitOpsById(id int) (*bean2.GitOpsCon
impl.logger.Errorw("error, GetGitOpsConfigById", "id", id, "err", err)
return nil, err
}
config := &bean2.GitOpsConfigDto{
Id: model.Id,
Provider: model.Provider,
GitHubOrgId: model.GitHubOrgId,
GitLabGroupId: model.GitLabGroupId,
Active: model.Active,
Token: model.Token,
Host: model.Host,
Username: model.Username,
UserId: model.CreatedBy,
AzureProjectName: model.AzureProject,
BitBucketWorkspaceId: model.BitBucketWorkspaceId,
BitBucketProjectKey: model.BitBucketProjectKey,
AllowCustomRepository: model.AllowCustomRepository,
TLSConfig: &bean3.TLSConfig{
CaData: model.CaCert,
TLSCertData: model.TlsCert,
TLSKeyData: model.TlsKey,
},
}
return config, err
return adapter.GetGitOpsConfigBean(model), err
}

func (impl *GitOpsConfigReadServiceImpl) GetGitConfig() (*gitBean.GitConfig, error) {
gitOpsConfig, err := impl.GetGitOpsConfigActive()
if err != nil {
impl.logger.Errorw("error while fetching gitops config", "err", err)
return nil, err
}
return gitAdapter.ConvertGitOpsConfigToGitConfig(gitOpsConfig), err
}
62 changes: 42 additions & 20 deletions pkg/deployment/gitOps/git/GitFactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ package git

import (
"crypto/tls"
"errors"
"fmt"
"github.com/devtron-labs/devtron/api/bean/gitOps"
"github.com/devtron-labs/devtron/pkg/deployment/gitOps/config"
"github.com/devtron-labs/devtron/pkg/deployment/gitOps/git/adapter"
"github.com/devtron-labs/devtron/util"
"github.com/go-pg/pg"
"github.com/xanzy/go-gitlab"
"go.uber.org/zap"
"time"
Expand All @@ -34,24 +36,27 @@ type GitFactory struct {
logger *zap.SugaredLogger
}

func (factory *GitFactory) Reload(gitOpsConfigReadService config.GitOpsConfigReadService) error {
var err error
func (factory *GitFactory) Reload(gitOpsConfigReadService config.GitOpsConfigReadService) (err error) {
start := time.Now()
defer func() {
util.TriggerGitOpsMetrics("Reload", "GitService", start, err)
}()
factory.logger.Infow("reloading gitops details")
cfg, err := GetGitConfig(gitOpsConfigReadService)
if err != nil {
gitConfig, err := gitOpsConfigReadService.GetGitConfig()
if err != nil && !errors.Is(err, pg.ErrNoRows) {
factory.logger.Errorw("error in getting gitops config", "err", err)
return err
} else if errors.Is(err, pg.ErrNoRows) || gitConfig == nil {
factory.logger.Warn("no gitops config found, gitops will not work")
return nil
}
factory.GitOpsHelper.SetAuth(cfg.GetAuth())
client, err := NewGitOpsClient(cfg, factory.logger, factory.GitOpsHelper)
factory.GitOpsHelper.SetAuth(gitConfig.GetAuth())
client, err := NewGitOpsClient(gitConfig, factory.logger, factory.GitOpsHelper)
if err != nil {
return err
}
factory.Client = client
factory.logger.Infow(" gitops details reload success")
factory.logger.Infow("gitops details reload success")
return nil
}

Expand All @@ -78,7 +83,7 @@ func (factory *GitFactory) GetGitLabGroupPath(gitOpsConfig *gitOps.GitOpsConfigD
}
group, _, err := gitLabClient.Groups.GetGroup(gitOpsConfig.GitLabGroupId, &gitlab.GetGroupOptions{})
if err != nil {
factory.logger.Errorw("error in fetching gitlab group name", "err", err, "gitLab groupID", gitOpsConfig.GitLabGroupId)
factory.logger.Errorw("error in fetching gitlab group name", "gitLab groupID", gitOpsConfig.GitLabGroupId, "err", err)
return "", err
}
if group == nil {
Expand All @@ -96,10 +101,14 @@ func (factory *GitFactory) NewClientForValidation(gitOpsConfig *gitOps.GitOpsCon
}()
cfg := adapter.ConvertGitOpsConfigToGitConfig(gitOpsConfig)
//factory.GitOpsHelper.SetAuth(cfg.GetAuth())
gitOpsHelper := NewGitOpsHelperImpl(cfg.GetAuth(), factory.logger, cfg.GetTLSConfig(), gitOpsConfig.EnableTLSVerification)

gitOpsHelper, err := NewGitOpsHelperImpl(cfg.GetAuth(), factory.logger, cfg.GetTLSConfig(), gitOpsConfig.EnableTLSVerification)
if err != nil {
factory.logger.Errorw("error in creating gitOps helper", "gitProvider", cfg.GitProvider, "err", err)
return nil, gitOpsHelper, err
}
client, err := NewGitOpsClient(cfg, factory.logger, gitOpsHelper)
if err != nil {
factory.logger.Errorw("error in creating gitOps client", "gitProvider", cfg.GitProvider, "err", err)
return client, gitOpsHelper, err
}

Expand All @@ -109,18 +118,31 @@ func (factory *GitFactory) NewClientForValidation(gitOpsConfig *gitOps.GitOpsCon
}

func NewGitFactory(logger *zap.SugaredLogger, gitOpsConfigReadService config.GitOpsConfigReadService) (*GitFactory, error) {
cfg, err := GetGitConfig(gitOpsConfigReadService)
gitFactory := &GitFactory{
logger: logger,
}
gitConfig, err := gitOpsConfigReadService.GetGitConfig()
if err != nil && !errors.Is(err, pg.ErrNoRows) {
logger.Errorw("error in getting gitops config", "err", err)
return gitFactory, err
} else if errors.Is(err, pg.ErrNoRows) || gitConfig == nil {
logger.Warn("no gitops config found, gitops will not work")
return gitFactory, nil
}
gitOpsHelper, err := NewGitOpsHelperImpl(gitConfig.GetAuth(), logger, gitConfig.GetTLSConfig(), gitConfig.EnableTLSVerification)
if err != nil {
return nil, err
logger.Errorw("error in creating gitOps helper", "gitProvider", gitConfig.GitProvider, "err", err)
// error handling is skipped intentionally here, otherwise orchestration will not work
}
gitOpsHelper := NewGitOpsHelperImpl(cfg.GetAuth(), logger, cfg.GetTLSConfig(), cfg.EnableTLSVerification)
client, err := NewGitOpsClient(cfg, logger, gitOpsHelper)
gitFactory.GitOpsHelper = gitOpsHelper
client, err := NewGitOpsClient(gitConfig, logger, gitOpsHelper)
if err != nil {
logger.Errorw("error in creating gitOps client", "err", err, "gitProvider", cfg.GitProvider)
logger.Errorw("error in creating gitOps client", "gitProvider", gitConfig.GitProvider, "err", err)
// error handling is skipped intentionally here, otherwise orchestration will not work
}
if client == nil {
client = &UnimplementedGitOpsClient{}
}
return &GitFactory{
Client: client,
logger: logger,
GitOpsHelper: gitOpsHelper,
}, nil
gitFactory.Client = client
return gitFactory, nil
}
Loading