github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd/commands/repo.go (about) 1 package commands 2 3 import ( 4 stderrors "errors" 5 "fmt" 6 "os" 7 "strconv" 8 "text/tabwriter" 9 10 log "github.com/sirupsen/logrus" 11 "github.com/spf13/cobra" 12 13 "github.com/argoproj/argo-cd/v3/cmd/argocd/commands/headless" 14 "github.com/argoproj/argo-cd/v3/cmd/argocd/commands/utils" 15 cmdutil "github.com/argoproj/argo-cd/v3/cmd/util" 16 argocdclient "github.com/argoproj/argo-cd/v3/pkg/apiclient" 17 repositorypkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/repository" 18 appsv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 19 "github.com/argoproj/argo-cd/v3/util/cli" 20 "github.com/argoproj/argo-cd/v3/util/errors" 21 "github.com/argoproj/argo-cd/v3/util/git" 22 utilio "github.com/argoproj/argo-cd/v3/util/io" 23 ) 24 25 // NewRepoCommand returns a new instance of an `argocd repo` command 26 func NewRepoCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 27 command := &cobra.Command{ 28 Use: "repo", 29 Short: "Manage repository connection parameters", 30 Run: func(c *cobra.Command, args []string) { 31 c.HelpFunc()(c, args) 32 os.Exit(1) 33 }, 34 Example: ` 35 # Add git repository connection parameters 36 argocd repo add git@git.example.com:repos/repo 37 38 # Get a Configured Repository by URL 39 argocd repo get https://github.com/yourusername/your-repo.git 40 41 # List Configured Repositories 42 argocd repo list 43 44 # Remove Configured Repositories 45 argocd repo rm https://github.com/yourusername/your-repo.git 46 `, 47 } 48 49 command.AddCommand(NewRepoAddCommand(clientOpts)) 50 command.AddCommand(NewRepoGetCommand(clientOpts)) 51 command.AddCommand(NewRepoListCommand(clientOpts)) 52 command.AddCommand(NewRepoRemoveCommand(clientOpts)) 53 return command 54 } 55 56 // NewRepoAddCommand returns a new instance of an `argocd repo add` command 57 func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 58 var repoOpts cmdutil.RepoOptions 59 60 // For better readability and easier formatting 61 repoAddExamples := ` # Add a Git repository via SSH using a private key for authentication, ignoring the server's host key: 62 argocd repo add git@git.example.com:repos/repo --insecure-ignore-host-key --ssh-private-key-path ~/id_rsa 63 64 # Add a Git repository via SSH on a non-default port - need to use ssh:// style URLs here 65 argocd repo add ssh://git@git.example.com:2222/repos/repo --ssh-private-key-path ~/id_rsa 66 67 # Add a Git repository via SSH using socks5 proxy with no proxy credentials 68 argocd repo add ssh://git@github.com/argoproj/argocd-example-apps --ssh-private-key-path ~/id_rsa --proxy socks5://your.proxy.server.ip:1080 69 70 # Add a Git repository via SSH using socks5 proxy with proxy credentials 71 argocd repo add ssh://git@github.com/argoproj/argocd-example-apps --ssh-private-key-path ~/id_rsa --proxy socks5://username:password@your.proxy.server.ip:1080 72 73 # Add a private Git repository via HTTPS using username/password and TLS client certificates: 74 argocd repo add https://git.example.com/repos/repo --username git --password secret --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key 75 76 # Add a private Git repository via HTTPS using username/password without verifying the server's TLS certificate 77 argocd repo add https://git.example.com/repos/repo --username git --password secret --insecure-skip-server-verification 78 79 # Add a public Helm repository named 'stable' via HTTPS 80 argocd repo add https://charts.helm.sh/stable --type helm --name stable 81 82 # Add a private Helm repository named 'stable' via HTTPS 83 argocd repo add https://charts.helm.sh/stable --type helm --name stable --username test --password test 84 85 # Add a private Helm OCI-based repository named 'stable' via HTTPS 86 argocd repo add helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type helm --name stable --enable-oci --username test --password test 87 88 # Add a private HTTPS OCI repository named 'stable' 89 argocd repo add oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test 90 91 # Add a private OCI repository named 'stable' without verifying the server's TLS certificate 92 argocd repo add oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test --insecure-skip-server-verification 93 94 # Add a private HTTP OCI repository named 'stable' 95 argocd repo add oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test --insecure-oci-force-http 96 97 # Add a private Git repository on GitHub.com via GitHub App 98 argocd repo add https://git.example.com/repos/repo --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem 99 100 # Add a private Git repository on GitHub Enterprise via GitHub App 101 argocd repo add https://ghe.example.com/repos/repo --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem --github-app-enterprise-base-url https://ghe.example.com/api/v3 102 103 # Add a private Git repository on Google Cloud Sources via GCP service account credentials 104 argocd repo add https://source.developers.google.com/p/my-google-cloud-project/r/my-repo --gcp-service-account-key-path service-account-key.json 105 ` 106 107 command := &cobra.Command{ 108 Use: "add REPOURL", 109 Short: "Add git, oci or helm repository connection parameters", 110 Example: repoAddExamples, 111 Run: func(c *cobra.Command, args []string) { 112 ctx := c.Context() 113 114 if len(args) != 1 { 115 c.HelpFunc()(c, args) 116 os.Exit(1) 117 } 118 119 // Repository URL 120 repoOpts.Repo.Repo = args[0] 121 122 // Specifying ssh-private-key-path is only valid for SSH repositories 123 if repoOpts.SshPrivateKeyPath != "" { 124 if ok, _ := git.IsSSHURL(repoOpts.Repo.Repo); ok { 125 keyData, err := os.ReadFile(repoOpts.SshPrivateKeyPath) 126 if err != nil { 127 log.Fatal(err) 128 } 129 repoOpts.Repo.SSHPrivateKey = string(keyData) 130 } else { 131 errors.Fatal(errors.ErrorGeneric, "--ssh-private-key-path is only supported for SSH repositories.") 132 } 133 } 134 135 // tls-client-cert-path and tls-client-cert-key-key-path must always be 136 // specified together 137 if (repoOpts.TlsClientCertPath != "" && repoOpts.TlsClientCertKeyPath == "") || (repoOpts.TlsClientCertPath == "" && repoOpts.TlsClientCertKeyPath != "") { 138 err := stderrors.New("--tls-client-cert-path and --tls-client-cert-key-path must be specified together") 139 errors.CheckError(err) 140 } 141 142 // Specifying tls-client-cert-path is only valid for HTTPS repositories 143 if repoOpts.TlsClientCertPath != "" { 144 if git.IsHTTPSURL(repoOpts.Repo.Repo) { 145 tlsCertData, err := os.ReadFile(repoOpts.TlsClientCertPath) 146 errors.CheckError(err) 147 tlsCertKey, err := os.ReadFile(repoOpts.TlsClientCertKeyPath) 148 errors.CheckError(err) 149 repoOpts.Repo.TLSClientCertData = string(tlsCertData) 150 repoOpts.Repo.TLSClientCertKey = string(tlsCertKey) 151 } else { 152 err := stderrors.New("--tls-client-cert-path is only supported for HTTPS repositories") 153 errors.CheckError(err) 154 } 155 } 156 157 // Specifying github-app-private-key-path is only valid for HTTPS repositories 158 if repoOpts.GithubAppPrivateKeyPath != "" { 159 if git.IsHTTPSURL(repoOpts.Repo.Repo) { 160 githubAppPrivateKey, err := os.ReadFile(repoOpts.GithubAppPrivateKeyPath) 161 errors.CheckError(err) 162 repoOpts.Repo.GithubAppPrivateKey = string(githubAppPrivateKey) 163 } else { 164 err := stderrors.New("--github-app-private-key-path is only supported for HTTPS repositories") 165 errors.CheckError(err) 166 } 167 } 168 169 if repoOpts.GCPServiceAccountKeyPath != "" { 170 if git.IsHTTPSURL(repoOpts.Repo.Repo) { 171 gcpServiceAccountKey, err := os.ReadFile(repoOpts.GCPServiceAccountKeyPath) 172 errors.CheckError(err) 173 repoOpts.Repo.GCPServiceAccountKey = string(gcpServiceAccountKey) 174 } else { 175 err := stderrors.New("--gcp-service-account-key-path is only supported for HTTPS repositories") 176 errors.CheckError(err) 177 } 178 } 179 180 // Set repository connection properties only when creating repository, not 181 // when creating repository credentials. 182 // InsecureIgnoreHostKey is deprecated and only here for backwards compat 183 repoOpts.Repo.InsecureIgnoreHostKey = repoOpts.InsecureIgnoreHostKey 184 repoOpts.Repo.Insecure = repoOpts.InsecureSkipServerVerification 185 repoOpts.Repo.EnableLFS = repoOpts.EnableLfs 186 repoOpts.Repo.EnableOCI = repoOpts.EnableOci 187 repoOpts.Repo.GithubAppId = repoOpts.GithubAppId 188 repoOpts.Repo.GithubAppInstallationId = repoOpts.GithubAppInstallationId 189 repoOpts.Repo.GitHubAppEnterpriseBaseURL = repoOpts.GitHubAppEnterpriseBaseURL 190 repoOpts.Repo.Proxy = repoOpts.Proxy 191 repoOpts.Repo.NoProxy = repoOpts.NoProxy 192 repoOpts.Repo.ForceHttpBasicAuth = repoOpts.ForceHttpBasicAuth 193 repoOpts.Repo.UseAzureWorkloadIdentity = repoOpts.UseAzureWorkloadIdentity 194 195 if repoOpts.Repo.Type == "helm" && repoOpts.Repo.Name == "" { 196 errors.Fatal(errors.ErrorGeneric, "Must specify --name for repos of type 'helm'") 197 } 198 199 if repoOpts.Repo.Type == "oci" && repoOpts.InsecureOCIForceHTTP { 200 repoOpts.Repo.InsecureOCIForceHttp = repoOpts.InsecureOCIForceHTTP 201 } 202 203 conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoClientOrDie() 204 defer utilio.Close(conn) 205 206 // If the user set a username, but didn't supply password via --password, 207 // then we prompt for it 208 if repoOpts.Repo.Username != "" && repoOpts.Repo.Password == "" { 209 repoOpts.Repo.Password = cli.PromptPassword(repoOpts.Repo.Password) 210 } 211 212 err := cmdutil.ValidateBearerTokenAndPasswordCombo(repoOpts.Repo.BearerToken, repoOpts.Repo.Password) 213 errors.CheckError(err) 214 err = cmdutil.ValidateBearerTokenForGitOnly(repoOpts.Repo.BearerToken, repoOpts.Repo.Type) 215 errors.CheckError(err) 216 err = cmdutil.ValidateBearerTokenForHTTPSRepoOnly(repoOpts.Repo.BearerToken, git.IsHTTPSURL(repoOpts.Repo.Repo)) 217 errors.CheckError(err) 218 219 // We let the server check access to the repository before adding it. If 220 // it is a private repo, but we cannot access with the credentials 221 // that were supplied, we bail out. 222 // 223 // Skip validation if we are just adding credentials template, chances 224 // are high that we do not have the given URL pointing to a valid Git 225 // repo anyway. 226 repoAccessReq := repositorypkg.RepoAccessQuery{ 227 Repo: repoOpts.Repo.Repo, 228 Type: repoOpts.Repo.Type, 229 Name: repoOpts.Repo.Name, 230 Username: repoOpts.Repo.Username, 231 Password: repoOpts.Repo.Password, 232 BearerToken: repoOpts.Repo.BearerToken, 233 SshPrivateKey: repoOpts.Repo.SSHPrivateKey, 234 TlsClientCertData: repoOpts.Repo.TLSClientCertData, 235 TlsClientCertKey: repoOpts.Repo.TLSClientCertKey, 236 Insecure: repoOpts.Repo.IsInsecure(), 237 EnableOci: repoOpts.Repo.EnableOCI, 238 GithubAppPrivateKey: repoOpts.Repo.GithubAppPrivateKey, 239 GithubAppID: repoOpts.Repo.GithubAppId, 240 GithubAppInstallationID: repoOpts.Repo.GithubAppInstallationId, 241 GithubAppEnterpriseBaseUrl: repoOpts.Repo.GitHubAppEnterpriseBaseURL, 242 Proxy: repoOpts.Proxy, 243 Project: repoOpts.Repo.Project, 244 GcpServiceAccountKey: repoOpts.Repo.GCPServiceAccountKey, 245 ForceHttpBasicAuth: repoOpts.Repo.ForceHttpBasicAuth, 246 UseAzureWorkloadIdentity: repoOpts.Repo.UseAzureWorkloadIdentity, 247 InsecureOciForceHttp: repoOpts.Repo.InsecureOCIForceHttp, 248 } 249 _, err = repoIf.ValidateAccess(ctx, &repoAccessReq) 250 errors.CheckError(err) 251 252 repoCreateReq := repositorypkg.RepoCreateRequest{ 253 Repo: &repoOpts.Repo, 254 Upsert: repoOpts.Upsert, 255 } 256 257 createdRepo, err := repoIf.CreateRepository(ctx, &repoCreateReq) 258 errors.CheckError(err) 259 fmt.Printf("Repository '%s' added\n", createdRepo.Repo) 260 }, 261 } 262 command.Flags().BoolVar(&repoOpts.Upsert, "upsert", false, "Override an existing repository with the same name even if the spec differs") 263 cmdutil.AddRepoFlags(command, &repoOpts) 264 return command 265 } 266 267 // NewRepoRemoveCommand returns a new instance of an `argocd repo rm` command 268 func NewRepoRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 269 var project string 270 command := &cobra.Command{ 271 Use: "rm REPO ...", 272 Short: "Remove configured repositories", 273 Example: ` 274 # Remove a single repository 275 argocd repo rm https://github.com/yourusername/your-repo.git 276 277 # Remove multiple repositories 278 argocd repo rm https://github.com/yourusername/your-repo.git https://git.example.com/repo2.git 279 280 # Remove repositories for a specific project 281 argocd repo rm https://github.com/yourusername/your-repo.git --project myproject 282 283 # Remove repository using SSH URL 284 argocd repo rm git@github.com:yourusername/your-repo.git 285 `, 286 Run: func(c *cobra.Command, args []string) { 287 ctx := c.Context() 288 289 if len(args) == 0 { 290 c.HelpFunc()(c, args) 291 os.Exit(1) 292 } 293 conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoClientOrDie() 294 defer utilio.Close(conn) 295 296 promptUtil := utils.NewPrompt(clientOpts.PromptsEnabled) 297 for _, repoURL := range args { 298 canDelete := promptUtil.Confirm(fmt.Sprintf("Are you sure you want to delete repository '%s'? [y/n]", repoURL)) 299 if canDelete { 300 _, err := repoIf.DeleteRepository(ctx, &repositorypkg.RepoQuery{Repo: repoURL, AppProject: project}) 301 errors.CheckError(err) 302 fmt.Printf("Repository '%s' removed\n", repoURL) 303 } else { 304 fmt.Printf("The command to delete '%s' was cancelled.\n", repoURL) 305 } 306 } 307 }, 308 } 309 command.Flags().StringVar(&project, "project", "", "project of the repository") 310 return command 311 } 312 313 // Print table of repo info 314 func printRepoTable(repos appsv1.Repositories) { 315 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 316 _, _ = fmt.Fprintf(w, "TYPE\tNAME\tREPO\tINSECURE\tOCI\tLFS\tCREDS\tSTATUS\tMESSAGE\tPROJECT\n") 317 for _, r := range repos { 318 var hasCreds string 319 if r.InheritedCreds { 320 hasCreds = "inherited" 321 } else { 322 hasCreds = strconv.FormatBool(r.HasCredentials()) 323 } 324 325 _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%v\t%v\t%s\t%s\t%s\t%s\n", r.Type, r.Name, r.Repo, r.IsInsecure(), r.EnableOCI, r.EnableLFS, hasCreds, r.ConnectionState.Status, r.ConnectionState.Message, r.Project) 326 } 327 _ = w.Flush() 328 } 329 330 // Print list of repo urls or url patterns for repository credentials 331 func printRepoUrls(repos appsv1.Repositories) { 332 for _, r := range repos { 333 fmt.Println(r.Repo) 334 } 335 } 336 337 // NewRepoListCommand returns a new instance of an `argocd repo list` command 338 func NewRepoListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 339 var ( 340 output string 341 refresh string 342 ) 343 command := &cobra.Command{ 344 Use: "list", 345 Short: "List configured repositories", 346 Example: ` 347 # List all repositories 348 argocd repo list 349 350 # List repositories in wide format 351 argocd repo list -o wide 352 353 # List repositories in YAML format 354 argocd repo list -o yaml 355 356 # List repositories in JSON format 357 argocd repo list -o json 358 359 # List urls of repositories 360 argocd repo list -o url 361 362 # Force refresh of cached repository connection status 363 argocd repo list --refresh hard 364 `, 365 Run: func(c *cobra.Command, _ []string) { 366 ctx := c.Context() 367 368 conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoClientOrDie() 369 defer utilio.Close(conn) 370 forceRefresh := false 371 372 switch refresh { 373 case "": 374 case "hard": 375 forceRefresh = true 376 default: 377 err := fmt.Errorf("unknown refresh value: %s. Supported values: hard", refresh) 378 errors.CheckError(err) 379 } 380 381 repos, err := repoIf.ListRepositories(ctx, &repositorypkg.RepoQuery{ForceRefresh: forceRefresh}) 382 errors.CheckError(err) 383 384 switch output { 385 case "yaml", "json": 386 err := PrintResourceList(repos.Items, output, false) 387 errors.CheckError(err) 388 case "url": 389 printRepoUrls(repos.Items) 390 // wide is the default 391 case "wide", "": 392 printRepoTable(repos.Items) 393 default: 394 errors.CheckError(fmt.Errorf("unknown output format: %s. Supported formats: yaml|json|url|wide", output)) 395 } 396 }, 397 } 398 command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. Supported formats: yaml|json|url|wide") 399 command.Flags().StringVar(&refresh, "refresh", "", "Force a cache refresh on connection status. Supported values: hard") 400 return command 401 } 402 403 // NewRepoGetCommand returns a new instance of an `argocd repo get` command 404 func NewRepoGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 405 var ( 406 output string 407 refresh string 408 project string 409 ) 410 411 // For better readability and easier formatting 412 repoGetExamples := ` 413 # Get Git or Helm repository details in wide format (default, '-o wide') 414 argocd repo get https://git.example.com/repos/repo 415 416 # Get repository details in YAML format 417 argocd repo get https://git.example.com/repos/repo -o yaml 418 419 # Get repository details in JSON format 420 argocd repo get https://git.example.com/repos/repo -o json 421 422 # Get repository URL 423 argocd repo get https://git.example.com/repos/repo -o url 424 ` 425 426 command := &cobra.Command{ 427 Use: "get REPO", 428 Short: "Get a configured repository by URL", 429 Example: repoGetExamples, 430 Run: func(c *cobra.Command, args []string) { 431 ctx := c.Context() 432 433 if len(args) != 1 { 434 c.HelpFunc()(c, args) 435 os.Exit(1) 436 } 437 438 // Repository URL 439 repoURL := args[0] 440 conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoClientOrDie() 441 defer utilio.Close(conn) 442 forceRefresh := false 443 switch refresh { 444 case "": 445 case "hard": 446 forceRefresh = true 447 default: 448 err := fmt.Errorf("unknown refresh value: %s. Supported values: hard", refresh) 449 errors.CheckError(err) 450 } 451 repo, err := repoIf.Get(ctx, &repositorypkg.RepoQuery{Repo: repoURL, ForceRefresh: forceRefresh, AppProject: project}) 452 errors.CheckError(err) 453 454 switch output { 455 case "yaml", "json": 456 err := PrintResource(repo, output) 457 errors.CheckError(err) 458 case "url": 459 fmt.Println(repo.Repo) 460 // wide is the default 461 case "wide", "": 462 printRepoTable(appsv1.Repositories{repo}) 463 default: 464 errors.CheckError(fmt.Errorf("unknown output format: %s. Supported formats: yaml|json|url|wide", output)) 465 } 466 }, 467 } 468 469 command.Flags().StringVar(&project, "project", "", "project of the repository") 470 command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|url") 471 command.Flags().StringVar(&refresh, "refresh", "", "Force a cache refresh on connection status. Supported values: hard") 472 return command 473 }