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  }