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

     1  package db
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"hash/fnv"
     7  
     8  	log "github.com/sirupsen/logrus"
     9  	"google.golang.org/grpc/codes"
    10  	"google.golang.org/grpc/status"
    11  	apiv1 "k8s.io/api/core/v1"
    12  
    13  	appsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    14  	appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    15  	"github.com/argoproj/argo-cd/v2/util/settings"
    16  )
    17  
    18  const (
    19  	// Prefix to use for naming repository secrets
    20  	repoSecretPrefix = "repo"
    21  	// Prefix to use for naming credential template secrets
    22  	credSecretPrefix = "creds"
    23  	// The name of the key storing the username in the secret
    24  	username = "username"
    25  	// The name of the key storing the password in the secret
    26  	password = "password"
    27  	// The name of the key storing the SSH private in the secret
    28  	sshPrivateKey = "sshPrivateKey"
    29  	// The name of the key storing the TLS client cert data in the secret
    30  	tlsClientCertData = "tlsClientCertData"
    31  	// The name of the key storing the TLS client cert key in the secret
    32  	tlsClientCertKey = "tlsClientCertKey"
    33  	// The name of the key storing the GitHub App private key in the secret
    34  	githubAppPrivateKey = "githubAppPrivateKey"
    35  	// The name of the key storing the service account to access Google Cloud Source repositories
    36  	gcpServiceAccountKey = "gcpServiceAccountKey"
    37  )
    38  
    39  // repositoryBackend defines the API for types that wish to provide interaction with repository storage
    40  type repositoryBackend interface {
    41  	CreateRepository(ctx context.Context, r *appsv1.Repository) (*appsv1.Repository, error)
    42  	GetRepository(ctx context.Context, repoURL string) (*appsv1.Repository, error)
    43  	ListRepositories(ctx context.Context, repoType *string) ([]*appsv1.Repository, error)
    44  	UpdateRepository(ctx context.Context, r *appsv1.Repository) (*appsv1.Repository, error)
    45  	DeleteRepository(ctx context.Context, repoURL string) error
    46  	RepositoryExists(ctx context.Context, repoURL string) (bool, error)
    47  
    48  	CreateRepoCreds(ctx context.Context, r *appsv1.RepoCreds) (*appsv1.RepoCreds, error)
    49  	GetRepoCreds(ctx context.Context, repoURL string) (*appsv1.RepoCreds, error)
    50  	ListRepoCreds(ctx context.Context) ([]string, error)
    51  	UpdateRepoCreds(ctx context.Context, r *appsv1.RepoCreds) (*appsv1.RepoCreds, error)
    52  	DeleteRepoCreds(ctx context.Context, name string) error
    53  	RepoCredsExists(ctx context.Context, repoURL string) (bool, error)
    54  
    55  	GetAllHelmRepoCreds(ctx context.Context) ([]*appsv1.RepoCreds, error)
    56  }
    57  
    58  func (db *db) CreateRepository(ctx context.Context, r *appsv1.Repository) (*appsv1.Repository, error) {
    59  	secretBackend := db.repoBackend()
    60  	legacyBackend := db.legacyRepoBackend()
    61  
    62  	secretExists, err := secretBackend.RepositoryExists(ctx, r.Repo)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	legacyExists, err := legacyBackend.RepositoryExists(ctx, r.Repo)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	if secretExists || legacyExists {
    72  		return nil, status.Errorf(codes.AlreadyExists, "repository %q already exists", r.Repo)
    73  	}
    74  
    75  	return secretBackend.CreateRepository(ctx, r)
    76  }
    77  
    78  func (db *db) GetRepository(ctx context.Context, repoURL string) (*appsv1.Repository, error) {
    79  	repository, err := db.getRepository(ctx, repoURL)
    80  	if err != nil {
    81  		return repository, fmt.Errorf("unable to get repository %q: %v", repoURL, err)
    82  	}
    83  
    84  	if err := db.enrichCredsToRepo(ctx, repository); err != nil {
    85  		return repository, fmt.Errorf("unable to enrich repository %q info with credentials: %v", repoURL, err)
    86  	}
    87  
    88  	return repository, err
    89  }
    90  
    91  func (db *db) GetProjectRepositories(ctx context.Context, project string) ([]*appsv1.Repository, error) {
    92  	informer, err := db.settingsMgr.GetSecretsInformer()
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	secrets, err := informer.GetIndexer().ByIndex(settings.ByProjectRepoIndexer, project)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	var res []*appv1.Repository
   101  	for i := range secrets {
   102  		repo, err := secretToRepository(secrets[i].(*apiv1.Secret))
   103  		if err != nil {
   104  			return nil, err
   105  		}
   106  		res = append(res, repo)
   107  	}
   108  	return res, nil
   109  }
   110  
   111  func (db *db) RepositoryExists(ctx context.Context, repoURL string) (bool, error) {
   112  	secretsBackend := db.repoBackend()
   113  	exists, err := secretsBackend.RepositoryExists(ctx, repoURL)
   114  	if exists || err != nil {
   115  		return exists, err
   116  	}
   117  
   118  	legacyBackend := db.legacyRepoBackend()
   119  	return legacyBackend.RepositoryExists(ctx, repoURL)
   120  }
   121  
   122  func (db *db) getRepository(ctx context.Context, repoURL string) (*appsv1.Repository, error) {
   123  	secretsBackend := db.repoBackend()
   124  	exists, err := secretsBackend.RepositoryExists(ctx, repoURL)
   125  	if err != nil {
   126  		return nil, fmt.Errorf("unable to check if repository %q exists from secrets backend: %v", repoURL, err)
   127  	} else if exists {
   128  		repository, err := secretsBackend.GetRepository(ctx, repoURL)
   129  		if err != nil {
   130  			return nil, fmt.Errorf("unable to get repository %q from secrets backend: %v", repoURL, err)
   131  		}
   132  		return repository, nil
   133  	}
   134  
   135  	legacyBackend := db.legacyRepoBackend()
   136  	exists, err = legacyBackend.RepositoryExists(ctx, repoURL)
   137  	if err != nil {
   138  		return nil, fmt.Errorf("unable to check if repository %q exists from legacy backend: %v", repoURL, err)
   139  	} else if exists {
   140  		repository, err := legacyBackend.GetRepository(ctx, repoURL)
   141  		if err != nil {
   142  			return nil, fmt.Errorf("unable to get repository %q from legacy backend: %v", repoURL, err)
   143  		}
   144  		return repository, nil
   145  	}
   146  
   147  	return &appsv1.Repository{Repo: repoURL}, nil
   148  }
   149  
   150  func (db *db) ListRepositories(ctx context.Context) ([]*appsv1.Repository, error) {
   151  	return db.listRepositories(ctx, nil)
   152  }
   153  
   154  func (db *db) listRepositories(ctx context.Context, repoType *string) ([]*appsv1.Repository, error) {
   155  	// TODO It would be nice to check for duplicates between secret and legacy repositories and make it so that
   156  	// 	repositories from secrets overlay repositories from legacys.
   157  
   158  	secretRepositories, err := db.repoBackend().ListRepositories(ctx, repoType)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	legacyRepositories, err := db.legacyRepoBackend().ListRepositories(ctx, repoType)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	repositories := append(secretRepositories, legacyRepositories...)
   169  	if err := db.enrichCredsToRepos(ctx, repositories); err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	return repositories, nil
   174  }
   175  
   176  // UpdateRepository updates a repository
   177  func (db *db) UpdateRepository(ctx context.Context, r *appsv1.Repository) (*appsv1.Repository, error) {
   178  	secretsBackend := db.repoBackend()
   179  	exists, err := secretsBackend.RepositoryExists(ctx, r.Repo)
   180  	if err != nil {
   181  		return nil, err
   182  	} else if exists {
   183  		return secretsBackend.UpdateRepository(ctx, r)
   184  	}
   185  
   186  	legacyBackend := db.legacyRepoBackend()
   187  	exists, err = legacyBackend.RepositoryExists(ctx, r.Repo)
   188  	if err != nil {
   189  		return nil, err
   190  	} else if exists {
   191  		return legacyBackend.UpdateRepository(ctx, r)
   192  	}
   193  
   194  	return nil, status.Errorf(codes.NotFound, "repo '%s' not found", r.Repo)
   195  }
   196  
   197  func (db *db) DeleteRepository(ctx context.Context, repoURL string) error {
   198  	secretsBackend := db.repoBackend()
   199  	exists, err := secretsBackend.RepositoryExists(ctx, repoURL)
   200  	if err != nil {
   201  		return err
   202  	} else if exists {
   203  		return secretsBackend.DeleteRepository(ctx, repoURL)
   204  	}
   205  
   206  	legacyBackend := db.legacyRepoBackend()
   207  	exists, err = legacyBackend.RepositoryExists(ctx, repoURL)
   208  	if err != nil {
   209  		return err
   210  	} else if exists {
   211  		return legacyBackend.DeleteRepository(ctx, repoURL)
   212  	}
   213  
   214  	return status.Errorf(codes.NotFound, "repo '%s' not found", repoURL)
   215  }
   216  
   217  // ListRepositoryCredentials returns a list of URLs that contain repo credential sets
   218  func (db *db) ListRepositoryCredentials(ctx context.Context) ([]string, error) {
   219  	// TODO It would be nice to check for duplicates between secret and legacy repositories and make it so that
   220  	// 	repositories from secrets overlay repositories from legacys.
   221  
   222  	secretRepoCreds, err := db.repoBackend().ListRepoCreds(ctx)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	legacyRepoCreds, err := db.legacyRepoBackend().ListRepoCreds(ctx)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  
   232  	return append(secretRepoCreds, legacyRepoCreds...), nil
   233  }
   234  
   235  // GetRepositoryCredentials retrieves a repository credential set
   236  func (db *db) GetRepositoryCredentials(ctx context.Context, repoURL string) (*appsv1.RepoCreds, error) {
   237  	secretsBackend := db.repoBackend()
   238  	exists, err := secretsBackend.RepoCredsExists(ctx, repoURL)
   239  	if err != nil {
   240  		return nil, fmt.Errorf("unable to check if repository credentials for %q exists from secrets backend: %w", repoURL, err)
   241  	} else if exists {
   242  		creds, err := secretsBackend.GetRepoCreds(ctx, repoURL)
   243  		if err != nil {
   244  			return nil, fmt.Errorf("unable to get repository credentials for %q from secrets backend: %w", repoURL, err)
   245  		}
   246  		return creds, nil
   247  	}
   248  
   249  	legacyBackend := db.legacyRepoBackend()
   250  	exists, err = legacyBackend.RepoCredsExists(ctx, repoURL)
   251  	if err != nil {
   252  		return nil, fmt.Errorf("unable to check if repository credentials for %q exists from legacy backend: %w", repoURL, err)
   253  	} else if exists {
   254  		creds, err := legacyBackend.GetRepoCreds(ctx, repoURL)
   255  		if err != nil {
   256  			return nil, fmt.Errorf("unable to get repository credentials for %q from legacy backend: %w", repoURL, err)
   257  		}
   258  		return creds, nil
   259  	}
   260  
   261  	return nil, nil
   262  }
   263  
   264  // GetAllHelmRepositoryCredentials retrieves all repository credentials
   265  func (db *db) GetAllHelmRepositoryCredentials(ctx context.Context) ([]*appsv1.RepoCreds, error) {
   266  	// TODO It would be nice to check for duplicates between secret and legacy repositories and make it so that
   267  	// 	repositories from secrets overlay repositories from legacys.
   268  
   269  	secretRepoCreds, err := db.repoBackend().GetAllHelmRepoCreds(ctx)
   270  	if err != nil {
   271  		return nil, fmt.Errorf("failed to get all Helm repo creds: %w", err)
   272  	}
   273  
   274  	legacyRepoCreds, err := db.legacyRepoBackend().GetAllHelmRepoCreds(ctx)
   275  	if err != nil {
   276  		return nil, fmt.Errorf("failed to get all legacy Helm repo creds: %w", err)
   277  	}
   278  
   279  	return append(secretRepoCreds, legacyRepoCreds...), nil
   280  }
   281  
   282  // CreateRepositoryCredentials creates a repository credential set
   283  func (db *db) CreateRepositoryCredentials(ctx context.Context, r *appsv1.RepoCreds) (*appsv1.RepoCreds, error) {
   284  	legacyBackend := db.legacyRepoBackend()
   285  	secretBackend := db.repoBackend()
   286  
   287  	secretExists, err := secretBackend.RepositoryExists(ctx, r.URL)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  	legacyExists, err := legacyBackend.RepositoryExists(ctx, r.URL)
   292  	if err != nil {
   293  		return nil, err
   294  	}
   295  
   296  	if secretExists || legacyExists {
   297  		return nil, status.Errorf(codes.AlreadyExists, "repository credentials %q already exists", r.URL)
   298  	}
   299  
   300  	return secretBackend.CreateRepoCreds(ctx, r)
   301  }
   302  
   303  // UpdateRepositoryCredentials updates a repository credential set
   304  func (db *db) UpdateRepositoryCredentials(ctx context.Context, r *appsv1.RepoCreds) (*appsv1.RepoCreds, error) {
   305  	secretsBackend := db.repoBackend()
   306  	exists, err := secretsBackend.RepoCredsExists(ctx, r.URL)
   307  	if err != nil {
   308  		return nil, err
   309  	} else if exists {
   310  		return secretsBackend.UpdateRepoCreds(ctx, r)
   311  	}
   312  
   313  	legacyBackend := db.legacyRepoBackend()
   314  	exists, err = legacyBackend.RepoCredsExists(ctx, r.URL)
   315  	if err != nil {
   316  		return nil, err
   317  	} else if exists {
   318  		return legacyBackend.UpdateRepoCreds(ctx, r)
   319  	}
   320  
   321  	return nil, status.Errorf(codes.NotFound, "repository credentials '%s' not found", r.URL)
   322  }
   323  
   324  // DeleteRepositoryCredentials deletes a repository credential set from config, and
   325  // also all the secrets which actually contained the credentials.
   326  func (db *db) DeleteRepositoryCredentials(ctx context.Context, name string) error {
   327  	secretsBackend := db.repoBackend()
   328  	exists, err := secretsBackend.RepoCredsExists(ctx, name)
   329  	if err != nil {
   330  		return err
   331  	} else if exists {
   332  		return secretsBackend.DeleteRepoCreds(ctx, name)
   333  	}
   334  
   335  	legacyBackend := db.legacyRepoBackend()
   336  	exists, err = legacyBackend.RepoCredsExists(ctx, name)
   337  	if err != nil {
   338  		return err
   339  	} else if exists {
   340  		return legacyBackend.DeleteRepoCreds(ctx, name)
   341  	}
   342  
   343  	return status.Errorf(codes.NotFound, "repository credentials '%s' not found", name)
   344  }
   345  
   346  func (db *db) enrichCredsToRepos(ctx context.Context, repositories []*appsv1.Repository) error {
   347  	for _, repository := range repositories {
   348  		if err := db.enrichCredsToRepo(ctx, repository); err != nil {
   349  			return err
   350  		}
   351  	}
   352  	return nil
   353  }
   354  
   355  func (db *db) repoBackend() repositoryBackend {
   356  	return &secretsRepositoryBackend{db: db}
   357  }
   358  
   359  func (db *db) legacyRepoBackend() repositoryBackend {
   360  	return &legacyRepositoryBackend{db: db}
   361  }
   362  
   363  func (db *db) enrichCredsToRepo(ctx context.Context, repository *appsv1.Repository) error {
   364  	if !repository.HasCredentials() {
   365  		creds, err := db.GetRepositoryCredentials(ctx, repository.Repo)
   366  		if err == nil {
   367  			if creds != nil {
   368  				repository.CopyCredentialsFrom(creds)
   369  				repository.InheritedCreds = true
   370  			}
   371  		} else {
   372  			return fmt.Errorf("failed to get repository credentials for %q: %w", repository.Repo, err)
   373  		}
   374  	} else {
   375  		log.Debugf("%s has credentials", repository.Repo)
   376  	}
   377  
   378  	return nil
   379  }
   380  
   381  // RepoURLToSecretName hashes repo URL to a secret name using a formula. This is used when
   382  // repositories are _imperatively_ created and need its credentials to be stored in a secret.
   383  // NOTE: this formula should not be considered stable and may change in future releases.
   384  // Do NOT rely on this formula as a means of secret lookup, only secret creation.
   385  func RepoURLToSecretName(prefix string, repo string) string {
   386  	h := fnv.New32a()
   387  	_, _ = h.Write([]byte(repo))
   388  	return fmt.Sprintf("%s-%v", prefix, h.Sum32())
   389  }