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 }