github.com/argoproj/argo-cd/v2@v2.10.9/applicationset/generators/scm_provider.go (about) 1 package generators 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "strings" 8 "time" 9 10 corev1 "k8s.io/api/core/v1" 11 "sigs.k8s.io/controller-runtime/pkg/client" 12 13 log "github.com/sirupsen/logrus" 14 15 "github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth" 16 "github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider" 17 "github.com/argoproj/argo-cd/v2/applicationset/utils" 18 "github.com/argoproj/argo-cd/v2/common" 19 argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" 20 ) 21 22 var _ Generator = (*SCMProviderGenerator)(nil) 23 24 const ( 25 DefaultSCMProviderRequeueAfterSeconds = 30 * time.Minute 26 ) 27 28 type SCMProviderGenerator struct { 29 client client.Client 30 // Testing hooks. 31 overrideProvider scm_provider.SCMProviderService 32 SCMAuthProviders 33 scmRootCAPath string 34 allowedSCMProviders []string 35 enableSCMProviders bool 36 } 37 38 type SCMAuthProviders struct { 39 GitHubApps github_app_auth.Credentials 40 } 41 42 func NewSCMProviderGenerator(client client.Client, providers SCMAuthProviders, scmRootCAPath string, allowedSCMProviders []string, enableSCMProviders bool) Generator { 43 return &SCMProviderGenerator{ 44 client: client, 45 SCMAuthProviders: providers, 46 scmRootCAPath: scmRootCAPath, 47 allowedSCMProviders: allowedSCMProviders, 48 enableSCMProviders: enableSCMProviders, 49 } 50 } 51 52 // Testing generator 53 func NewTestSCMProviderGenerator(overrideProvider scm_provider.SCMProviderService) Generator { 54 return &SCMProviderGenerator{overrideProvider: overrideProvider, enableSCMProviders: true} 55 } 56 57 func (g *SCMProviderGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration { 58 // Return a requeue default of 30 minutes, if no default is specified. 59 60 if appSetGenerator.SCMProvider.RequeueAfterSeconds != nil { 61 return time.Duration(*appSetGenerator.SCMProvider.RequeueAfterSeconds) * time.Second 62 } 63 64 return DefaultSCMProviderRequeueAfterSeconds 65 } 66 67 func (g *SCMProviderGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate { 68 return &appSetGenerator.SCMProvider.Template 69 } 70 71 var ErrSCMProvidersDisabled = errors.New("scm providers are disabled") 72 73 type ErrDisallowedSCMProvider struct { 74 Provider string 75 Allowed []string 76 } 77 78 func NewErrDisallowedSCMProvider(provider string, allowed []string) ErrDisallowedSCMProvider { 79 return ErrDisallowedSCMProvider{ 80 Provider: provider, 81 Allowed: allowed, 82 } 83 } 84 85 func (e ErrDisallowedSCMProvider) Error() string { 86 return fmt.Sprintf("scm provider %q not allowed, must use one of the following: %s", e.Provider, strings.Join(e.Allowed, ", ")) 87 } 88 89 func ScmProviderAllowed(applicationSetInfo *argoprojiov1alpha1.ApplicationSet, generator SCMGeneratorWithCustomApiUrl, allowedScmProviders []string) error { 90 url := generator.CustomApiUrl() 91 92 if url == "" || len(allowedScmProviders) == 0 { 93 return nil 94 } 95 96 for _, allowedScmProvider := range allowedScmProviders { 97 if url == allowedScmProvider { 98 return nil 99 } 100 } 101 102 log.WithFields(log.Fields{ 103 common.SecurityField: common.SecurityMedium, 104 "applicationset": applicationSetInfo.Name, 105 "appSetNamespace": applicationSetInfo.Namespace, 106 }).Debugf("attempted to use disallowed SCM %q, must use one of the following: %s", url, strings.Join(allowedScmProviders, ", ")) 107 108 return NewErrDisallowedSCMProvider(url, allowedScmProviders) 109 } 110 111 func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) { 112 if appSetGenerator == nil { 113 return nil, EmptyAppSetGeneratorError 114 } 115 116 if appSetGenerator.SCMProvider == nil { 117 return nil, EmptyAppSetGeneratorError 118 } 119 120 if !g.enableSCMProviders { 121 return nil, ErrSCMProvidersDisabled 122 } 123 124 // Create the SCM provider helper. 125 providerConfig := appSetGenerator.SCMProvider 126 127 if err := ScmProviderAllowed(applicationSetInfo, providerConfig, g.allowedSCMProviders); err != nil { 128 return nil, fmt.Errorf("scm provider not allowed: %w", err) 129 } 130 131 ctx := context.Background() 132 var provider scm_provider.SCMProviderService 133 if g.overrideProvider != nil { 134 provider = g.overrideProvider 135 } else if providerConfig.Github != nil { 136 var err error 137 provider, err = g.githubProvider(ctx, providerConfig.Github, applicationSetInfo) 138 if err != nil { 139 return nil, fmt.Errorf("scm provider: %w", err) 140 } 141 } else if providerConfig.Gitlab != nil { 142 token, err := g.getSecretRef(ctx, providerConfig.Gitlab.TokenRef, applicationSetInfo.Namespace) 143 if err != nil { 144 return nil, fmt.Errorf("error fetching Gitlab token: %v", err) 145 } 146 provider, err = scm_provider.NewGitlabProvider(ctx, providerConfig.Gitlab.Group, token, providerConfig.Gitlab.API, providerConfig.Gitlab.AllBranches, providerConfig.Gitlab.IncludeSubgroups, providerConfig.Gitlab.WillIncludeSharedProjects(), providerConfig.Gitlab.Insecure, g.scmRootCAPath, providerConfig.Gitlab.Topic) 147 if err != nil { 148 return nil, fmt.Errorf("error initializing Gitlab service: %v", err) 149 } 150 } else if providerConfig.Gitea != nil { 151 token, err := g.getSecretRef(ctx, providerConfig.Gitea.TokenRef, applicationSetInfo.Namespace) 152 if err != nil { 153 return nil, fmt.Errorf("error fetching Gitea token: %v", err) 154 } 155 provider, err = scm_provider.NewGiteaProvider(ctx, providerConfig.Gitea.Owner, token, providerConfig.Gitea.API, providerConfig.Gitea.AllBranches, providerConfig.Gitea.Insecure) 156 if err != nil { 157 return nil, fmt.Errorf("error initializing Gitea service: %v", err) 158 } 159 } else if providerConfig.BitbucketServer != nil { 160 providerConfig := providerConfig.BitbucketServer 161 var scmError error 162 if providerConfig.BasicAuth != nil { 163 password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace) 164 if err != nil { 165 return nil, fmt.Errorf("error fetching Secret token: %v", err) 166 } 167 provider, scmError = scm_provider.NewBitbucketServerProviderBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.AllBranches) 168 } else { 169 provider, scmError = scm_provider.NewBitbucketServerProviderNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.AllBranches) 170 } 171 if scmError != nil { 172 return nil, fmt.Errorf("error initializing Bitbucket Server service: %v", scmError) 173 } 174 } else if providerConfig.AzureDevOps != nil { 175 token, err := g.getSecretRef(ctx, providerConfig.AzureDevOps.AccessTokenRef, applicationSetInfo.Namespace) 176 if err != nil { 177 return nil, fmt.Errorf("error fetching Azure Devops access token: %v", err) 178 } 179 provider, err = scm_provider.NewAzureDevOpsProvider(ctx, token, providerConfig.AzureDevOps.Organization, providerConfig.AzureDevOps.API, providerConfig.AzureDevOps.TeamProject, providerConfig.AzureDevOps.AllBranches) 180 if err != nil { 181 return nil, fmt.Errorf("error initializing Azure Devops service: %v", err) 182 } 183 } else if providerConfig.Bitbucket != nil { 184 appPassword, err := g.getSecretRef(ctx, providerConfig.Bitbucket.AppPasswordRef, applicationSetInfo.Namespace) 185 if err != nil { 186 return nil, fmt.Errorf("error fetching Bitbucket cloud appPassword: %v", err) 187 } 188 provider, err = scm_provider.NewBitBucketCloudProvider(ctx, providerConfig.Bitbucket.Owner, providerConfig.Bitbucket.User, appPassword, providerConfig.Bitbucket.AllBranches) 189 if err != nil { 190 return nil, fmt.Errorf("error initializing Bitbucket cloud service: %v", err) 191 } 192 } else if providerConfig.AWSCodeCommit != nil { 193 var awsErr error 194 provider, awsErr = scm_provider.NewAWSCodeCommitProvider(ctx, providerConfig.AWSCodeCommit.TagFilters, providerConfig.AWSCodeCommit.Role, providerConfig.AWSCodeCommit.Region, providerConfig.AWSCodeCommit.AllBranches) 195 if awsErr != nil { 196 return nil, fmt.Errorf("error initializing AWS codecommit service: %v", awsErr) 197 } 198 } else { 199 return nil, fmt.Errorf("no SCM provider implementation configured") 200 } 201 202 // Find all the available repos. 203 repos, err := scm_provider.ListRepos(ctx, provider, providerConfig.Filters, providerConfig.CloneProtocol) 204 if err != nil { 205 return nil, fmt.Errorf("error listing repos: %v", err) 206 } 207 paramsArray := make([]map[string]interface{}, 0, len(repos)) 208 var shortSHALength int 209 var shortSHALength7 int 210 for _, repo := range repos { 211 shortSHALength = 8 212 if len(repo.SHA) < 8 { 213 shortSHALength = len(repo.SHA) 214 } 215 216 shortSHALength7 = 7 217 if len(repo.SHA) < 7 { 218 shortSHALength7 = len(repo.SHA) 219 } 220 221 params := map[string]interface{}{ 222 "organization": repo.Organization, 223 "repository": repo.Repository, 224 "url": repo.URL, 225 "branch": repo.Branch, 226 "sha": repo.SHA, 227 "short_sha": repo.SHA[:shortSHALength], 228 "short_sha_7": repo.SHA[:shortSHALength7], 229 "labels": strings.Join(repo.Labels, ","), 230 "branchNormalized": utils.SanitizeName(repo.Branch), 231 } 232 233 err := appendTemplatedValues(appSetGenerator.SCMProvider.Values, params, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions) 234 if err != nil { 235 return nil, fmt.Errorf("failed to append templated values: %w", err) 236 } 237 238 paramsArray = append(paramsArray, params) 239 } 240 return paramsArray, nil 241 } 242 243 func (g *SCMProviderGenerator) getSecretRef(ctx context.Context, ref *argoprojiov1alpha1.SecretRef, namespace string) (string, error) { 244 if ref == nil { 245 return "", nil 246 } 247 248 secret := &corev1.Secret{} 249 err := g.client.Get( 250 ctx, 251 client.ObjectKey{ 252 Name: ref.SecretName, 253 Namespace: namespace, 254 }, 255 secret) 256 if err != nil { 257 return "", fmt.Errorf("error fetching secret %s/%s: %v", namespace, ref.SecretName, err) 258 } 259 tokenBytes, ok := secret.Data[ref.Key] 260 if !ok { 261 return "", fmt.Errorf("key %q in secret %s/%s not found", ref.Key, namespace, ref.SecretName) 262 } 263 return string(tokenBytes), nil 264 } 265 266 func (g *SCMProviderGenerator) githubProvider(ctx context.Context, github *argoprojiov1alpha1.SCMProviderGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (scm_provider.SCMProviderService, error) { 267 if github.AppSecretName != "" { 268 auth, err := g.GitHubApps.GetAuthSecret(ctx, github.AppSecretName) 269 if err != nil { 270 return nil, fmt.Errorf("error fetching Github app secret: %v", err) 271 } 272 273 return scm_provider.NewGithubAppProviderFor( 274 *auth, 275 github.Organization, 276 github.API, 277 github.AllBranches, 278 ) 279 } 280 281 token, err := g.getSecretRef(ctx, github.TokenRef, applicationSetInfo.Namespace) 282 if err != nil { 283 return nil, fmt.Errorf("error fetching Github token: %v", err) 284 } 285 return scm_provider.NewGithubProvider(ctx, github.Organization, token, github.API, github.AllBranches) 286 }