Files
zeekayandHanzo Dev 1ab6e799c1 cd: the module is github.com/hanzoai/cd, where it is actually hosted
go.mod still declared `module github.com/hanzoai/deploy` after the repo was
renamed. A Go module path is not cosmetic — it is the resolvable location. So
`go get github.com/hanzoai/cd` could not work, and anything importing this
module had to spell a name that no longer exists at that address. The build log
of v3.6.0 is where it surfaced: the compiler was still emitting
github.com/hanzoai/deploy/cmd/cd/commands.

3541 import paths across 690 .go, 19 .proto, 4 .yaml, 3 .sh, 2 .mod, Procfile
and Makefile. Plus the 62 ghcr.io/hanzoai/deploy image references, moved to
ghcr.io/hanzoai/cd to match hanzo.yml's build target — a distinct string from
the module path, replaced separately so neither could clobber the other.

go mod tidy clean, CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./... clean.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 21:16:24 -07:00

319 lines
14 KiB
Go

package commands
import (
stderrors "errors"
"fmt"
"os"
"text/tabwriter"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/hanzoai/cd/cmd/cd/commands/headless"
"github.com/hanzoai/cd/cmd/cd/commands/utils"
cmdutil "github.com/hanzoai/cd/cmd/util"
"github.com/hanzoai/cd/common"
cdclient "github.com/hanzoai/cd/pkg/apiclient"
repocredspkg "github.com/hanzoai/cd/pkg/apiclient/repocreds"
appsv1 "github.com/hanzoai/cd/pkg/apis/application/v1alpha1"
"github.com/hanzoai/cd/util/cli"
"github.com/hanzoai/cd/util/errors"
"github.com/hanzoai/cd/util/git"
utilio "github.com/hanzoai/cd/util/io"
"github.com/hanzoai/cd/util/templates"
)
// NewRepoCredsCommand returns a new instance of an `cd repocreds` command
func NewRepoCredsCommand(clientOpts *cdclient.ClientOptions) *cobra.Command {
command := &cobra.Command{
Use: "repocreds",
Short: "Manage credential templates for repositories",
Example: templates.Examples(`
# Add credentials with user/pass authentication to use for all repositories under the specified URL
cd repocreds add URL --username USERNAME --password PASSWORD
# List all the configured repository credentials
cd repocreds list
# Remove credentials for the repositories with specified URL
cd repocreds rm URL
`),
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
os.Exit(1)
},
}
command.AddCommand(NewRepoCredsAddCommand(clientOpts))
command.AddCommand(NewRepoCredsListCommand(clientOpts))
command.AddCommand(NewRepoCredsRemoveCommand(clientOpts))
return command
}
// NewRepoCredsAddCommand returns a new instance of an `cd repocreds add` command
func NewRepoCredsAddCommand(clientOpts *cdclient.ClientOptions) *cobra.Command {
var (
repo appsv1.RepoCreds
upsert bool
sshPrivateKeyPath string
tlsClientCertPath string
tlsClientCertKeyPath string
githubAppPrivateKeyPath string
gcpServiceAccountKeyPath string
)
// For better readability and easier formatting
repocredsAddExamples := ` # Add credentials with user/pass authentication to use for all repositories under https://git.example.com/repos
cd repocreds add https://git.example.com/repos/ --username git --password secret
# Add credentials with bearer token authentication to use for all BitBucket Data Center repositories under https://bitbucket.example.com/scm
cd repocreds add https://bitbucket.example.com/scm/ --bearer-token secret-token
# Add credentials with SSH private key authentication to use for all repositories under ssh://[email protected]/repos
cd repocreds add ssh://[email protected]/repos/ --ssh-private-key-path ~/.ssh/id_rsa
# Add credentials with GitHub App authentication to use for all repositories under https://github.com/repos. github-app-installation-id is optional, if not provided, the installation id will be fetched from the GitHub API.
cd repocreds add https://github.com/repos/ --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem
# Add credentials with GitHub App authentication to use for all repositories under https://ghe.example.com/repos. github-app-installation-id is optional, if not provided, the installation id will be fetched from the GitHub API.
cd repocreds add https://ghe.example.com/repos/ --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
# Add credentials with helm oci registry so that these oci registry urls do not need to be added as repos individually.
cd repocreds add localhost:5000/myrepo --enable-oci --type helm
# Add credentials with GCP credentials for all repositories under https://source.developers.google.com/p/my-google-cloud-project/r/
cd repocreds add https://source.developers.google.com/p/my-google-cloud-project/r/ --gcp-service-account-key-path service-account-key.json
# Add credentials with Azure Service Principal to use for all repositories under https://dev.azure.com/my-devops-organization
cd repocreds add https://dev.azure.com/my-devops-organization --azure-service-principal-client-id 12345678-1234-1234-1234-123456789012 --azure-service-principal-client-secret test --azure-service-principal-tenant-id 12345678-1234-1234-1234-123456789012
# Add credentials with Azure Service Principal to use for all repositories under https://dev.azure.com/my-devops-organization when not using default Azure public cloud
cd repocreds add https://dev.azure.com/my-devops-organization --azure-service-principal-client-id 12345678-1234-1234-1234-123456789012 --azure-service-principal-client-secret test --azure-service-principal-tenant-id 12345678-1234-1234-1234-123456789012 --azure-active-directory-endpoint https://login.microsoftonline.de
`
command := &cobra.Command{
Use: "add REPOURL",
Short: "Add git repository connection parameters",
Example: repocredsAddExamples,
Run: func(c *cobra.Command, args []string) {
ctx := c.Context()
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
// Repository URL
repo.URL = args[0]
// Specifying ssh-private-key-path is only valid for SSH repositories
if sshPrivateKeyPath != "" {
if ok, _ := git.IsSSHURL(repo.URL); ok {
keyData, err := os.ReadFile(sshPrivateKeyPath)
if err != nil {
log.Fatal(err)
}
repo.SSHPrivateKey = string(keyData)
} else {
errors.Fatal(errors.ErrorGeneric, "--ssh-private-key-path is only supported for SSH repositories.")
}
}
// tls-client-cert-path and tls-client-cert-key-key-path must always be
// specified together
if (tlsClientCertPath != "" && tlsClientCertKeyPath == "") || (tlsClientCertPath == "" && tlsClientCertKeyPath != "") {
errors.Fatal(errors.ErrorGeneric, "--tls-client-cert-path and --tls-client-cert-key-path must be specified together")
}
// Specifying tls-client-cert-path is only valid for HTTPS repositories
if tlsClientCertPath != "" {
if git.IsHTTPSURL(repo.URL) {
tlsCertData, err := os.ReadFile(tlsClientCertPath)
errors.CheckError(err)
tlsCertKey, err := os.ReadFile(tlsClientCertKeyPath)
errors.CheckError(err)
repo.TLSClientCertData = string(tlsCertData)
repo.TLSClientCertKey = string(tlsCertKey)
} else {
err := stderrors.New("--tls-client-cert-path is only supported for HTTPS repositories")
errors.CheckError(err)
}
}
// Specifying github-app-private-key-path is only valid for HTTPS repositories
if githubAppPrivateKeyPath != "" {
if git.IsHTTPSURL(repo.URL) {
githubAppPrivateKey, err := os.ReadFile(githubAppPrivateKeyPath)
errors.CheckError(err)
repo.GithubAppPrivateKey = string(githubAppPrivateKey)
} else {
err := stderrors.New("--github-app-private-key-path is only supported for HTTPS repositories")
errors.CheckError(err)
}
}
// Specifying gcpServiceAccountKeyPath is only valid for HTTPS repositories
if gcpServiceAccountKeyPath != "" {
if git.IsHTTPSURL(repo.URL) {
gcpServiceAccountKey, err := os.ReadFile(gcpServiceAccountKeyPath)
errors.CheckError(err)
repo.GCPServiceAccountKey = string(gcpServiceAccountKey)
} else {
err := stderrors.New("--gcp-service-account-key-path is only supported for HTTPS repositories")
errors.CheckError(err)
}
}
conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoCredsClientOrDie()
defer utilio.Close(conn)
// If the user set a username, but didn't supply password via --password,
// then we prompt for it
if repo.Username != "" && repo.Password == "" {
repo.Password = cli.PromptPassword(repo.Password)
}
err := cmdutil.ValidateBearerTokenAndPasswordCombo(repo.BearerToken, repo.Password)
errors.CheckError(err)
err = cmdutil.ValidateBearerTokenForGitOnly(repo.BearerToken, repo.Type)
errors.CheckError(err)
err = cmdutil.ValidateBearerTokenForHTTPSRepoOnly(repo.BearerToken, git.IsHTTPSURL(repo.URL))
errors.CheckError(err)
err = cmdutil.ValidateInsecureOCIForceHTTP(repo.InsecureOCIForceHttp, repo.Type, repo.EnableOCI)
errors.CheckError(err)
repoCreateReq := repocredspkg.RepoCredsCreateRequest{
Creds: &repo,
Upsert: upsert,
}
createdRepo, err := repoIf.CreateRepositoryCredentials(ctx, &repoCreateReq)
errors.CheckError(err)
fmt.Printf("Repository credentials for '%s' added\n", createdRepo.URL)
},
}
command.Flags().StringVar(&repo.Username, "username", "", "username to the repository")
command.Flags().StringVar(&repo.Password, "password", "", "password to the repository")
command.Flags().StringVar(&repo.BearerToken, "bearer-token", "", "bearer token to the Git repository")
command.Flags().StringVar(&sshPrivateKeyPath, "ssh-private-key-path", "", "path to the private ssh key (e.g. ~/.ssh/id_rsa)")
command.Flags().StringVar(&tlsClientCertPath, "tls-client-cert-path", "", "path to the TLS client cert (must be PEM format)")
command.Flags().StringVar(&tlsClientCertKeyPath, "tls-client-cert-key-path", "", "path to the TLS client cert's key (must be PEM format)")
command.Flags().Int64Var(&repo.GithubAppId, "github-app-id", 0, "id of the GitHub Application")
command.Flags().Int64Var(&repo.GithubAppInstallationId, "github-app-installation-id", 0, "installation id of the GitHub Application (optional, will be auto-discovered if not provided)")
command.Flags().StringVar(&githubAppPrivateKeyPath, "github-app-private-key-path", "", "private key of the GitHub Application")
command.Flags().StringVar(&repo.GitHubAppEnterpriseBaseURL, "github-app-enterprise-base-url", "", "base url to use when using GitHub Enterprise (e.g. https://ghe.example.com/api/v3")
command.Flags().BoolVar(&upsert, "upsert", false, "Override an existing repository with the same name even if the spec differs")
command.Flags().BoolVar(&repo.EnableOCI, "enable-oci", false, "Specifies whether helm-oci support should be enabled for this repo")
command.Flags().BoolVar(&repo.InsecureOCIForceHttp, "insecure-oci-force-http", false, "Use http when accessing an OCI repository")
command.Flags().StringVar(&repo.Type, "type", common.DefaultRepoType, "type of the repository, \"git\" or \"helm\"")
command.Flags().StringVar(&gcpServiceAccountKeyPath, "gcp-service-account-key-path", "", "service account key for the Google Cloud Platform")
command.Flags().BoolVar(&repo.ForceHttpBasicAuth, "force-http-basic-auth", false, "whether to force basic auth when connecting via HTTP")
command.Flags().BoolVar(&repo.UseAzureWorkloadIdentity, "use-azure-workload-identity", false, "whether to use azure workload identity for authentication")
command.Flags().StringVar(&repo.Proxy, "proxy-url", "", "If provided, this URL will be used to connect via proxy")
command.Flags().StringVar(&repo.AzureServicePrincipalClientId, "azure-service-principal-client-id", "", "client id of the Azure Service Principal")
command.Flags().StringVar(&repo.AzureServicePrincipalClientSecret, "azure-service-principal-client-secret", "", "client secret of the Azure Service Principal")
command.Flags().StringVar(&repo.AzureServicePrincipalTenantId, "azure-service-principal-tenant-id", "", "tenant id of the Azure Service Principal")
command.Flags().StringVar(&repo.AzureActiveDirectoryEndpoint, "azure-active-directory-endpoint", "", "Active Directory endpoint when not using default Azure public cloud (e.g. https://login.microsoftonline.de)")
return command
}
// NewRepoCredsRemoveCommand returns a new instance of an `cd repocreds rm` command
func NewRepoCredsRemoveCommand(clientOpts *cdclient.ClientOptions) *cobra.Command {
command := &cobra.Command{
Use: "rm CREDSURL",
Short: "Remove repository credentials",
Example: templates.Examples(`
# Remove credentials for the repositories with URL https://git.example.com/repos
cd repocreds rm https://git.example.com/repos/
`),
Run: func(c *cobra.Command, args []string) {
ctx := c.Context()
if len(args) == 0 {
c.HelpFunc()(c, args)
os.Exit(1)
}
conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoCredsClientOrDie()
defer utilio.Close(conn)
promptUtil := utils.NewPrompt(clientOpts.PromptsEnabled)
for _, repoURL := range args {
canDelete := promptUtil.Confirm(fmt.Sprintf("Are you sure you want to remove '%s'? [y/n] ", repoURL))
if canDelete {
_, err := repoIf.DeleteRepositoryCredentials(ctx, &repocredspkg.RepoCredsDeleteRequest{Url: repoURL})
errors.CheckError(err)
fmt.Printf("Repository credentials for '%s' removed\n", repoURL)
} else {
fmt.Printf("The command to remove '%s' was cancelled.\n", repoURL)
}
}
},
}
return command
}
// Print the repository credentials as table
func printRepoCredsTable(repos []appsv1.RepoCreds) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprint(w, "URL PATTERN\tUSERNAME\tSSH_CREDS\tTLS_CREDS\n")
for _, r := range repos {
if r.Username == "" {
r.Username = "-"
}
fmt.Fprintf(w, "%s\t%s\t%v\t%v\n", r.URL, r.Username, r.SSHPrivateKey != "", r.TLSClientCertData != "")
}
_ = w.Flush()
}
// Print list of repo urls or url patterns for repository credentials
func printRepoCredsUrls(repos []appsv1.RepoCreds) {
for _, r := range repos {
fmt.Println(r.URL)
}
}
// NewRepoCredsListCommand returns a new instance of an `cd repo list` command
func NewRepoCredsListCommand(clientOpts *cdclient.ClientOptions) *cobra.Command {
var output string
command := &cobra.Command{
Use: "list",
Short: "List configured repository credentials",
Example: templates.Examples(`
# List all repo urls
cd repocreds list
# List all repo urls in json format
cd repocreds list -o json
# List all repo urls in yaml format
cd repocreds list -o yaml
# List all repo urls in url format
cd repocreds list -o url
`),
Run: func(c *cobra.Command, _ []string) {
ctx := c.Context()
conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoCredsClientOrDie()
defer utilio.Close(conn)
repos, err := repoIf.ListRepositoryCredentials(ctx, &repocredspkg.RepoCredsQuery{})
errors.CheckError(err)
switch output {
case "yaml", "json":
err := PrintResourceList(repos.Items, output, false)
errors.CheckError(err)
case "url":
printRepoCredsUrls(repos.Items)
case "wide", "":
printRepoCredsTable(repos.Items)
default:
errors.CheckError(fmt.Errorf("unknown output format: %s", output))
}
},
}
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|url")
return command
}