github.com/argoproj/argo-cd/v2@v2.10.9/applicationset/generators/pull_request.go (about) 1 package generators 2 3 import ( 4 "context" 5 "fmt" 6 "strconv" 7 "time" 8 9 corev1 "k8s.io/api/core/v1" 10 "sigs.k8s.io/controller-runtime/pkg/client" 11 12 "github.com/gosimple/slug" 13 14 pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request" 15 argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" 16 ) 17 18 var _ Generator = (*PullRequestGenerator)(nil) 19 20 const ( 21 DefaultPullRequestRequeueAfterSeconds = 30 * time.Minute 22 ) 23 24 type PullRequestGenerator struct { 25 client client.Client 26 selectServiceProviderFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) 27 auth SCMAuthProviders 28 scmRootCAPath string 29 allowedSCMProviders []string 30 enableSCMProviders bool 31 } 32 33 func NewPullRequestGenerator(client client.Client, auth SCMAuthProviders, scmRootCAPath string, allowedScmProviders []string, enableSCMProviders bool) Generator { 34 g := &PullRequestGenerator{ 35 client: client, 36 auth: auth, 37 scmRootCAPath: scmRootCAPath, 38 allowedSCMProviders: allowedScmProviders, 39 enableSCMProviders: enableSCMProviders, 40 } 41 g.selectServiceProviderFunc = g.selectServiceProvider 42 return g 43 } 44 45 func (g *PullRequestGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration { 46 // Return a requeue default of 30 minutes, if no default is specified. 47 48 if appSetGenerator.PullRequest.RequeueAfterSeconds != nil { 49 return time.Duration(*appSetGenerator.PullRequest.RequeueAfterSeconds) * time.Second 50 } 51 52 return DefaultPullRequestRequeueAfterSeconds 53 } 54 55 func (g *PullRequestGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate { 56 return &appSetGenerator.PullRequest.Template 57 } 58 59 func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) { 60 if appSetGenerator == nil { 61 return nil, EmptyAppSetGeneratorError 62 } 63 64 if appSetGenerator.PullRequest == nil { 65 return nil, EmptyAppSetGeneratorError 66 } 67 68 ctx := context.Background() 69 svc, err := g.selectServiceProviderFunc(ctx, appSetGenerator.PullRequest, applicationSetInfo) 70 if err != nil { 71 return nil, fmt.Errorf("failed to select pull request service provider: %w", err) 72 } 73 74 pulls, err := pullrequest.ListPullRequests(ctx, svc, appSetGenerator.PullRequest.Filters) 75 if err != nil { 76 return nil, fmt.Errorf("error listing repos: %v", err) 77 } 78 params := make([]map[string]interface{}, 0, len(pulls)) 79 80 // In order to follow the DNS label standard as defined in RFC 1123, 81 // we need to limit the 'branch' to 50 to give room to append/suffix-ing it 82 // with 13 more characters. Also, there is the need to clean it as recommended 83 // here https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names 84 slug.MaxLength = 50 85 86 // Converting underscores to dashes 87 slug.CustomSub = map[string]string{ 88 "_": "-", 89 } 90 91 var shortSHALength int 92 var shortSHALength7 int 93 for _, pull := range pulls { 94 shortSHALength = 8 95 if len(pull.HeadSHA) < 8 { 96 shortSHALength = len(pull.HeadSHA) 97 } 98 99 shortSHALength7 = 7 100 if len(pull.HeadSHA) < 7 { 101 shortSHALength7 = len(pull.HeadSHA) 102 } 103 104 paramMap := map[string]interface{}{ 105 "number": strconv.Itoa(pull.Number), 106 "branch": pull.Branch, 107 "branch_slug": slug.Make(pull.Branch), 108 "target_branch": pull.TargetBranch, 109 "target_branch_slug": slug.Make(pull.TargetBranch), 110 "head_sha": pull.HeadSHA, 111 "head_short_sha": pull.HeadSHA[:shortSHALength], 112 "head_short_sha_7": pull.HeadSHA[:shortSHALength7], 113 } 114 115 // PR lables will only be supported for Go Template appsets, since fasttemplate will be deprecated. 116 if applicationSetInfo != nil && applicationSetInfo.Spec.GoTemplate { 117 paramMap["labels"] = pull.Labels 118 } 119 params = append(params, paramMap) 120 } 121 return params, nil 122 } 123 124 // selectServiceProvider selects the provider to get pull requests from the configuration 125 func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, generatorConfig *argoprojiov1alpha1.PullRequestGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) { 126 if !g.enableSCMProviders { 127 return nil, ErrSCMProvidersDisabled 128 } 129 if err := ScmProviderAllowed(applicationSetInfo, generatorConfig, g.allowedSCMProviders); err != nil { 130 return nil, fmt.Errorf("scm provider not allowed: %w", err) 131 } 132 133 if generatorConfig.Github != nil { 134 return g.github(ctx, generatorConfig.Github, applicationSetInfo) 135 } 136 if generatorConfig.GitLab != nil { 137 providerConfig := generatorConfig.GitLab 138 token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace) 139 if err != nil { 140 return nil, fmt.Errorf("error fetching Secret token: %v", err) 141 } 142 return pullrequest.NewGitLabService(ctx, token, providerConfig.API, providerConfig.Project, providerConfig.Labels, providerConfig.PullRequestState, g.scmRootCAPath, providerConfig.Insecure) 143 } 144 if generatorConfig.Gitea != nil { 145 providerConfig := generatorConfig.Gitea 146 token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace) 147 if err != nil { 148 return nil, fmt.Errorf("error fetching Secret token: %v", err) 149 } 150 return pullrequest.NewGiteaService(ctx, token, providerConfig.API, providerConfig.Owner, providerConfig.Repo, providerConfig.Insecure) 151 } 152 if generatorConfig.BitbucketServer != nil { 153 providerConfig := generatorConfig.BitbucketServer 154 if providerConfig.BasicAuth != nil { 155 password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace) 156 if err != nil { 157 return nil, fmt.Errorf("error fetching Secret token: %v", err) 158 } 159 return pullrequest.NewBitbucketServiceBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.Repo) 160 } else { 161 return pullrequest.NewBitbucketServiceNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.Repo) 162 } 163 } 164 if generatorConfig.Bitbucket != nil { 165 providerConfig := generatorConfig.Bitbucket 166 if providerConfig.BearerToken != nil { 167 appToken, err := g.getSecretRef(ctx, providerConfig.BearerToken.TokenRef, applicationSetInfo.Namespace) 168 if err != nil { 169 return nil, fmt.Errorf("error fetching Secret Bearer token: %v", err) 170 } 171 return pullrequest.NewBitbucketCloudServiceBearerToken(providerConfig.API, appToken, providerConfig.Owner, providerConfig.Repo) 172 } else if providerConfig.BasicAuth != nil { 173 password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace) 174 if err != nil { 175 return nil, fmt.Errorf("error fetching Secret token: %v", err) 176 } 177 return pullrequest.NewBitbucketCloudServiceBasicAuth(providerConfig.API, providerConfig.BasicAuth.Username, password, providerConfig.Owner, providerConfig.Repo) 178 } else { 179 return pullrequest.NewBitbucketCloudServiceNoAuth(providerConfig.API, providerConfig.Owner, providerConfig.Repo) 180 } 181 } 182 if generatorConfig.AzureDevOps != nil { 183 providerConfig := generatorConfig.AzureDevOps 184 token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace) 185 if err != nil { 186 return nil, fmt.Errorf("error fetching Secret token: %v", err) 187 } 188 return pullrequest.NewAzureDevOpsService(ctx, token, providerConfig.API, providerConfig.Organization, providerConfig.Project, providerConfig.Repo, providerConfig.Labels) 189 } 190 return nil, fmt.Errorf("no Pull Request provider implementation configured") 191 } 192 193 func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alpha1.PullRequestGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) { 194 // use an app if it was configured 195 if cfg.AppSecretName != "" { 196 auth, err := g.auth.GitHubApps.GetAuthSecret(ctx, cfg.AppSecretName) 197 if err != nil { 198 return nil, fmt.Errorf("error getting GitHub App secret: %v", err) 199 } 200 return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels) 201 } 202 203 // always default to token, even if not set (public access) 204 token, err := g.getSecretRef(ctx, cfg.TokenRef, applicationSetInfo.Namespace) 205 if err != nil { 206 return nil, fmt.Errorf("error fetching Secret token: %v", err) 207 } 208 return pullrequest.NewGithubService(ctx, token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels) 209 } 210 211 // getSecretRef gets the value of the key for the specified Secret resource. 212 func (g *PullRequestGenerator) getSecretRef(ctx context.Context, ref *argoprojiov1alpha1.SecretRef, namespace string) (string, error) { 213 if ref == nil { 214 return "", nil 215 } 216 217 secret := &corev1.Secret{} 218 err := g.client.Get( 219 ctx, 220 client.ObjectKey{ 221 Name: ref.SecretName, 222 Namespace: namespace, 223 }, 224 secret) 225 if err != nil { 226 return "", fmt.Errorf("error fetching secret %s/%s: %v", namespace, ref.SecretName, err) 227 } 228 tokenBytes, ok := secret.Data[ref.Key] 229 if !ok { 230 return "", fmt.Errorf("key %q in secret %s/%s not found", ref.Key, namespace, ref.SecretName) 231 } 232 return string(tokenBytes), nil 233 }