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  }