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 }