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