github.com/argoproj/argo-cd/v3@v3.2.1/util/db/repository.go (about)

     1  package db
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"hash/fnv"
     7  
     8  	corev1 "k8s.io/api/core/v1"
     9  	"k8s.io/utils/ptr"
    10  
    11  	log "github.com/sirupsen/logrus"
    12  	"google.golang.org/grpc/codes"
    13  	"google.golang.org/grpc/status"
    14  
    15  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    16  	"github.com/argoproj/argo-cd/v3/util/settings"
    17  )
    18  
    19  const (
    20  	// Prefix to use for naming repository secrets
    21  	repoSecretPrefix = "repo"
    22  	// Prefix to use for naming credential template secrets
    23  	credSecretPrefix = "creds"
    24  	// The name of the key storing the username in the secret
    25  	username = "username"
    26  	// The name of the key storing the password in the secret
    27  	password = "password"
    28  	// The name of the project storing the project in the secret
    29  	project = "project"
    30  	// The name of the key storing the SSH private in the secret
    31  	sshPrivateKey = "sshPrivateKey"
    32  )
    33  
    34  // repositoryBackend defines the API for types that wish to provide interaction with repository storage
    35  type repositoryBackend interface {
    36  	CreateRepository(ctx context.Context, r *v1alpha1.Repository) (*v1alpha1.Repository, error)
    37  	GetRepository(ctx context.Context, repoURL, project string) (*v1alpha1.Repository, error)
    38  	ListRepositories(ctx context.Context, repoType *string) ([]*v1alpha1.Repository, error)
    39  	UpdateRepository(ctx context.Context, r *v1alpha1.Repository) (*v1alpha1.Repository, error)
    40  	DeleteRepository(ctx context.Context, repoURL, project string) error
    41  	RepositoryExists(ctx context.Context, repoURL, project string, allowFallback bool) (bool, error)
    42  
    43  	CreateRepoCreds(ctx context.Context, r *v1alpha1.RepoCreds) (*v1alpha1.RepoCreds, error)
    44  	GetRepoCreds(ctx context.Context, repoURL string) (*v1alpha1.RepoCreds, error)
    45  	ListRepoCreds(ctx context.Context) ([]string, error)
    46  	UpdateRepoCreds(ctx context.Context, r *v1alpha1.RepoCreds) (*v1alpha1.RepoCreds, error)
    47  	DeleteRepoCreds(ctx context.Context, name string) error
    48  	RepoCredsExists(ctx context.Context, repoURL string) (bool, error)
    49  
    50  	GetAllHelmRepoCreds(ctx context.Context) ([]*v1alpha1.RepoCreds, error)
    51  	GetAllOCIRepoCreds(ctx context.Context) ([]*v1alpha1.RepoCreds, error)
    52  }
    53  
    54  func (db *db) CreateRepository(ctx context.Context, r *v1alpha1.Repository) (*v1alpha1.Repository, error) {
    55  	secretBackend := db.repoBackend()
    56  
    57  	secretExists, err := secretBackend.RepositoryExists(ctx, r.Repo, r.Project, false)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	if secretExists {
    63  		return nil, status.Errorf(codes.AlreadyExists, "repository %q already exists", r.Repo)
    64  	}
    65  
    66  	return secretBackend.CreateRepository(ctx, r)
    67  }
    68  
    69  func (db *db) CreateWriteRepository(ctx context.Context, r *v1alpha1.Repository) (*v1alpha1.Repository, error) {
    70  	secretBackend := db.repoWriteBackend()
    71  	secretExists, err := secretBackend.RepositoryExists(ctx, r.Repo, r.Project, false)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	if secretExists {
    77  		return nil, status.Errorf(codes.AlreadyExists, "repository %q already exists", r.Repo)
    78  	}
    79  
    80  	return secretBackend.CreateRepository(ctx, r)
    81  }
    82  
    83  func (db *db) GetRepository(ctx context.Context, repoURL, project string) (*v1alpha1.Repository, error) {
    84  	repository, err := db.getRepository(ctx, repoURL, project)
    85  	if err != nil {
    86  		return repository, fmt.Errorf("unable to get repository %q: %w", repoURL, err)
    87  	}
    88  
    89  	if err := db.enrichCredsToRepo(ctx, repository); err != nil {
    90  		return repository, fmt.Errorf("unable to enrich repository %q info with credentials: %w", repoURL, err)
    91  	}
    92  
    93  	return repository, err
    94  }
    95  
    96  func (db *db) GetWriteRepository(ctx context.Context, repoURL, project string) (*v1alpha1.Repository, error) {
    97  	repository, err := db.repoWriteBackend().GetRepository(ctx, repoURL, project)
    98  	if err != nil {
    99  		return repository, fmt.Errorf("unable to get write repository %q: %w", repoURL, err)
   100  	}
   101  
   102  	if err := db.enrichWriteCredsToRepo(ctx, repository); err != nil {
   103  		return repository, fmt.Errorf("unable to enrich write repository %q info with credentials: %w", repoURL, err)
   104  	}
   105  
   106  	return repository, err
   107  }
   108  
   109  func (db *db) GetProjectRepositories(project string) ([]*v1alpha1.Repository, error) {
   110  	return db.getRepositories(settings.ByProjectRepoIndexer, project)
   111  }
   112  
   113  func (db *db) GetProjectWriteRepositories(project string) ([]*v1alpha1.Repository, error) {
   114  	return db.getRepositories(settings.ByProjectRepoWriteIndexer, project)
   115  }
   116  
   117  func (db *db) getRepositories(indexer, project string) ([]*v1alpha1.Repository, error) {
   118  	informer, err := db.settingsMgr.GetSecretsInformer()
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	secrets, err := informer.GetIndexer().ByIndex(indexer, project)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	var res []*v1alpha1.Repository
   127  	for i := range secrets {
   128  		repo, err := secretToRepository(secrets[i].(*corev1.Secret))
   129  		if err != nil {
   130  			return nil, err
   131  		}
   132  		res = append(res, repo)
   133  	}
   134  	return res, nil
   135  }
   136  
   137  func (db *db) RepositoryExists(ctx context.Context, repoURL, project string) (bool, error) {
   138  	secretsBackend := db.repoBackend()
   139  	return secretsBackend.RepositoryExists(ctx, repoURL, project, true)
   140  }
   141  
   142  func (db *db) WriteRepositoryExists(ctx context.Context, repoURL, project string) (bool, error) {
   143  	secretsBackend := db.repoWriteBackend()
   144  	return secretsBackend.RepositoryExists(ctx, repoURL, project, true)
   145  }
   146  
   147  func (db *db) getRepository(ctx context.Context, repoURL, project string) (*v1alpha1.Repository, error) {
   148  	secretsBackend := db.repoBackend()
   149  	exists, err := secretsBackend.RepositoryExists(ctx, repoURL, project, true)
   150  	if err != nil {
   151  		return nil, fmt.Errorf("unable to check if repository %q exists from secrets backend: %w", repoURL, err)
   152  	} else if exists {
   153  		repository, err := secretsBackend.GetRepository(ctx, repoURL, project)
   154  		if err != nil {
   155  			return nil, fmt.Errorf("unable to get repository %q from secrets backend: %w", repoURL, err)
   156  		}
   157  		return repository, nil
   158  	}
   159  
   160  	return &v1alpha1.Repository{Repo: repoURL}, nil
   161  }
   162  
   163  func (db *db) ListRepositories(ctx context.Context) ([]*v1alpha1.Repository, error) {
   164  	return db.listRepositories(ctx, nil, false)
   165  }
   166  
   167  func (db *db) ListWriteRepositories(ctx context.Context) ([]*v1alpha1.Repository, error) {
   168  	return db.listRepositories(ctx, nil, true)
   169  }
   170  
   171  func (db *db) listRepositories(ctx context.Context, repoType *string, writeCreds bool) ([]*v1alpha1.Repository, error) {
   172  	var backend repositoryBackend
   173  	if writeCreds {
   174  		backend = db.repoWriteBackend()
   175  	} else {
   176  		backend = db.repoBackend()
   177  	}
   178  	repositories, err := backend.ListRepositories(ctx, repoType)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  	err = db.enrichCredsToRepos(ctx, repositories)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	return repositories, nil
   188  }
   189  
   190  func (db *db) ListOCIRepositories(ctx context.Context) ([]*v1alpha1.Repository, error) {
   191  	var result []*v1alpha1.Repository
   192  	repos, err := db.listRepositories(ctx, ptr.To("oci"), false)
   193  	if err != nil {
   194  		return nil, fmt.Errorf("failed to list OCI repositories: %w", err)
   195  	}
   196  	result = append(result, v1alpha1.Repositories(repos).Filter(func(r *v1alpha1.Repository) bool {
   197  		return r.Type == "oci"
   198  	})...)
   199  	return result, nil
   200  }
   201  
   202  // UpdateRepository updates a repository
   203  func (db *db) UpdateRepository(ctx context.Context, r *v1alpha1.Repository) (*v1alpha1.Repository, error) {
   204  	secretsBackend := db.repoBackend()
   205  	exists, err := secretsBackend.RepositoryExists(ctx, r.Repo, r.Project, false)
   206  	if err != nil {
   207  		return nil, err
   208  	} else if exists {
   209  		return secretsBackend.UpdateRepository(ctx, r)
   210  	}
   211  
   212  	return nil, status.Errorf(codes.NotFound, "repo '%s' not found", r.Repo)
   213  }
   214  
   215  func (db *db) UpdateWriteRepository(ctx context.Context, r *v1alpha1.Repository) (*v1alpha1.Repository, error) {
   216  	secretBackend := db.repoWriteBackend()
   217  	exists, err := secretBackend.RepositoryExists(ctx, r.Repo, r.Project, false)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  
   222  	if !exists {
   223  		return nil, status.Errorf(codes.NotFound, "repo '%s' not found", r.Repo)
   224  	}
   225  
   226  	return secretBackend.UpdateRepository(ctx, r)
   227  }
   228  
   229  func (db *db) DeleteRepository(ctx context.Context, repoURL, project string) error {
   230  	secretsBackend := db.repoBackend()
   231  	exists, err := secretsBackend.RepositoryExists(ctx, repoURL, project, false)
   232  	if err != nil {
   233  		return err
   234  	} else if exists {
   235  		return secretsBackend.DeleteRepository(ctx, repoURL, project)
   236  	}
   237  
   238  	return status.Errorf(codes.NotFound, "repo '%s' not found", repoURL)
   239  }
   240  
   241  func (db *db) DeleteWriteRepository(ctx context.Context, repoURL, project string) error {
   242  	secretsBackend := db.repoWriteBackend()
   243  	exists, err := secretsBackend.RepositoryExists(ctx, repoURL, project, false)
   244  	if err != nil {
   245  		return err
   246  	}
   247  
   248  	if !exists {
   249  		return status.Errorf(codes.NotFound, "repo '%s' not found", repoURL)
   250  	}
   251  
   252  	return secretsBackend.DeleteRepository(ctx, repoURL, project)
   253  }
   254  
   255  // ListRepositoryCredentials returns a list of URLs that contain repo credential sets
   256  func (db *db) ListRepositoryCredentials(ctx context.Context) ([]string, error) {
   257  	secretRepoCreds, err := db.repoBackend().ListRepoCreds(ctx)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  
   262  	return secretRepoCreds, nil
   263  }
   264  
   265  // ListWriteRepositoryCredentials returns a list of URLs that contain repo write credential sets
   266  func (db *db) ListWriteRepositoryCredentials(ctx context.Context) ([]string, error) {
   267  	secretRepoCreds, err := db.repoWriteBackend().ListRepoCreds(ctx)
   268  	if err != nil {
   269  		return nil, err
   270  	}
   271  	return secretRepoCreds, nil
   272  }
   273  
   274  // GetRepositoryCredentials retrieves a repository credential set
   275  func (db *db) GetRepositoryCredentials(ctx context.Context, repoURL string) (*v1alpha1.RepoCreds, error) {
   276  	secretsBackend := db.repoBackend()
   277  	exists, err := secretsBackend.RepoCredsExists(ctx, repoURL)
   278  	if err != nil {
   279  		return nil, fmt.Errorf("unable to check if repository credentials for %q exists from secrets backend: %w", repoURL, err)
   280  	} else if exists {
   281  		creds, err := secretsBackend.GetRepoCreds(ctx, repoURL)
   282  		if err != nil {
   283  			return nil, fmt.Errorf("unable to get repository credentials for %q from secrets backend: %w", repoURL, err)
   284  		}
   285  		return creds, nil
   286  	}
   287  
   288  	return nil, nil
   289  }
   290  
   291  // GetWriteRepositoryCredentials retrieves a repository write credential set
   292  func (db *db) GetWriteRepositoryCredentials(ctx context.Context, repoURL string) (*v1alpha1.RepoCreds, error) {
   293  	secretBackend := db.repoWriteBackend()
   294  	creds, err := secretBackend.GetRepoCreds(ctx, repoURL)
   295  	if err != nil {
   296  		if creds == nil {
   297  			return nil, fmt.Errorf("unable to check if repo write credentials for %q exists from secrets backend: %w", repoURL, err)
   298  		}
   299  		return nil, fmt.Errorf("unable to get repo write credentials for %q from secrets backend: %w", repoURL, err)
   300  	}
   301  	if creds == nil { // to cover for not found. In that case both creds and err are nil
   302  		return nil, nil
   303  	}
   304  
   305  	return creds, nil
   306  }
   307  
   308  // GetAllHelmRepositoryCredentials retrieves all repository credentials
   309  func (db *db) GetAllHelmRepositoryCredentials(ctx context.Context) ([]*v1alpha1.RepoCreds, error) {
   310  	secretRepoCreds, err := db.repoBackend().GetAllHelmRepoCreds(ctx)
   311  	if err != nil {
   312  		return nil, fmt.Errorf("failed to get all Helm repo creds: %w", err)
   313  	}
   314  
   315  	return secretRepoCreds, nil
   316  }
   317  
   318  // GetAllOCIRepositoryCredentials retrieves all repository credentials
   319  func (db *db) GetAllOCIRepositoryCredentials(ctx context.Context) ([]*v1alpha1.RepoCreds, error) {
   320  	secretRepoCreds, err := db.repoBackend().GetAllOCIRepoCreds(ctx)
   321  	if err != nil {
   322  		return nil, fmt.Errorf("failed to get all Helm repo creds: %w", err)
   323  	}
   324  
   325  	return secretRepoCreds, nil
   326  }
   327  
   328  // CreateRepositoryCredentials creates a repository credential set
   329  func (db *db) CreateRepositoryCredentials(ctx context.Context, r *v1alpha1.RepoCreds) (*v1alpha1.RepoCreds, error) {
   330  	secretBackend := db.repoBackend()
   331  
   332  	secretExists, err := secretBackend.RepositoryExists(ctx, r.URL, "", false)
   333  	if err != nil {
   334  		return nil, err
   335  	}
   336  
   337  	if secretExists {
   338  		return nil, status.Errorf(codes.AlreadyExists, "repository credentials %q already exists", r.URL)
   339  	}
   340  
   341  	return secretBackend.CreateRepoCreds(ctx, r)
   342  }
   343  
   344  // CreateWriteRepositoryCredentials creates a repository write credential set
   345  func (db *db) CreateWriteRepositoryCredentials(ctx context.Context, r *v1alpha1.RepoCreds) (*v1alpha1.RepoCreds, error) {
   346  	secretBackend := db.repoWriteBackend()
   347  	secretExists, err := secretBackend.RepoCredsExists(ctx, r.URL)
   348  	if err != nil {
   349  		return nil, err
   350  	}
   351  
   352  	if secretExists {
   353  		return nil, status.Errorf(codes.AlreadyExists, "write repository credentials %q already exists", r.URL)
   354  	}
   355  
   356  	return secretBackend.CreateRepoCreds(ctx, r)
   357  }
   358  
   359  // UpdateRepositoryCredentials updates a repository credential set
   360  func (db *db) UpdateRepositoryCredentials(ctx context.Context, r *v1alpha1.RepoCreds) (*v1alpha1.RepoCreds, error) {
   361  	secretsBackend := db.repoBackend()
   362  	exists, err := secretsBackend.RepoCredsExists(ctx, r.URL)
   363  	if err != nil {
   364  		return nil, err
   365  	} else if exists {
   366  		return secretsBackend.UpdateRepoCreds(ctx, r)
   367  	}
   368  
   369  	return nil, status.Errorf(codes.NotFound, "repository credentials '%s' not found", r.URL)
   370  }
   371  
   372  // UpdateWriteRepositoryCredentials updates a repository write credential set
   373  func (db *db) UpdateWriteRepositoryCredentials(ctx context.Context, r *v1alpha1.RepoCreds) (*v1alpha1.RepoCreds, error) {
   374  	secretBackend := db.repoWriteBackend()
   375  	exists, err := secretBackend.RepoCredsExists(ctx, r.URL)
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  
   380  	if !exists {
   381  		return nil, status.Errorf(codes.NotFound, "write repository credentials '%s' not found", r.URL)
   382  	}
   383  
   384  	return secretBackend.UpdateRepoCreds(ctx, r)
   385  }
   386  
   387  // DeleteRepositoryCredentials deletes a repository credential set from config, and
   388  // also all the secrets which actually contained the credentials.
   389  func (db *db) DeleteRepositoryCredentials(ctx context.Context, name string) error {
   390  	secretsBackend := db.repoBackend()
   391  	exists, err := secretsBackend.RepoCredsExists(ctx, name)
   392  	if err != nil {
   393  		return err
   394  	} else if exists {
   395  		return secretsBackend.DeleteRepoCreds(ctx, name)
   396  	}
   397  
   398  	return status.Errorf(codes.NotFound, "repository credentials '%s' not found", name)
   399  }
   400  
   401  // DeleteWriteRepositoryCredentials deletes a repository write credential set from config, and
   402  // also all the secrets which actually contained the credentials.
   403  func (db *db) DeleteWriteRepositoryCredentials(ctx context.Context, name string) error {
   404  	secretBackend := db.repoWriteBackend()
   405  	exists, err := secretBackend.RepoCredsExists(ctx, name)
   406  	if err != nil {
   407  		return err
   408  	} else if exists {
   409  		return secretBackend.DeleteRepoCreds(ctx, name)
   410  	}
   411  	return status.Errorf(codes.NotFound, "write repository credentials '%s' not found", name)
   412  }
   413  
   414  func (db *db) enrichCredsToRepos(ctx context.Context, repositories []*v1alpha1.Repository) error {
   415  	for _, repository := range repositories {
   416  		if err := db.enrichCredsToRepo(ctx, repository); err != nil {
   417  			return err
   418  		}
   419  	}
   420  	return nil
   421  }
   422  
   423  func (db *db) repoBackend() repositoryBackend {
   424  	return &secretsRepositoryBackend{db: db}
   425  }
   426  
   427  func (db *db) repoWriteBackend() repositoryBackend {
   428  	return &secretsRepositoryBackend{db: db, writeCreds: true}
   429  }
   430  
   431  func (db *db) enrichCredsToRepo(ctx context.Context, repository *v1alpha1.Repository) error {
   432  	if !repository.HasCredentials() {
   433  		creds, err := db.GetRepositoryCredentials(ctx, repository.Repo)
   434  		if err != nil {
   435  			return fmt.Errorf("failed to get repository credentials for %q: %w", repository.Repo, err)
   436  		}
   437  		if creds != nil {
   438  			repository.CopyCredentialsFrom(creds)
   439  			repository.InheritedCreds = true
   440  		}
   441  	} else {
   442  		log.Debugf("%s has credentials", repository.Repo)
   443  	}
   444  
   445  	return nil
   446  }
   447  
   448  func (db *db) enrichWriteCredsToRepo(ctx context.Context, repository *v1alpha1.Repository) error {
   449  	if !repository.HasCredentials() {
   450  		creds, err := db.GetWriteRepositoryCredentials(ctx, repository.Repo)
   451  		if err != nil {
   452  			return fmt.Errorf("failed to get repository credentials for %q: %w", repository.Repo, err)
   453  		}
   454  		if creds != nil {
   455  			repository.CopyCredentialsFrom(creds)
   456  			repository.InheritedCreds = true
   457  		}
   458  	} else {
   459  		log.Debugf("%s has credentials", repository.Repo)
   460  	}
   461  
   462  	return nil
   463  }
   464  
   465  // RepoURLToSecretName hashes repo URL to a secret name using a formula. This is used when
   466  // repositories are _imperatively_ created and need its credentials to be stored in a secret.
   467  // NOTE: this formula should not be considered stable and may change in future releases.
   468  // Do NOT rely on this formula as a means of secret lookup, only secret creation.
   469  func RepoURLToSecretName(prefix string, repo string, project string) string {
   470  	h := fnv.New32a()
   471  	_, _ = h.Write([]byte(repo))
   472  	_, _ = h.Write([]byte(project))
   473  	return fmt.Sprintf("%s-%v", prefix, h.Sum32())
   474  }