-
Notifications
You must be signed in to change notification settings - Fork 0
/
repo.go
378 lines (334 loc) · 14.1 KB
/
repo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
package commands
import (
"fmt"
"os"
"strconv"
"text/tabwriter"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/dcoppa/argo-cd/v2/cmd/argocd/commands/headless"
cmdutil "github.com/dcoppa/argo-cd/v2/cmd/util"
argocdclient "github.com/dcoppa/argo-cd/v2/pkg/apiclient"
repositorypkg "github.com/dcoppa/argo-cd/v2/pkg/apiclient/repository"
appsv1 "github.com/dcoppa/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/dcoppa/argo-cd/v2/util/cli"
"github.com/dcoppa/argo-cd/v2/util/errors"
"github.com/dcoppa/argo-cd/v2/util/git"
"github.com/dcoppa/argo-cd/v2/util/io"
)
// NewRepoCommand returns a new instance of an `argocd repo` command
func NewRepoCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "repo",
Short: "Manage repository connection parameters",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
os.Exit(1)
},
Example: `
# Add git repository connection parameters
argocd repo add git@git.example.com:repos/repo
# Get a Configured Repository by URL
argocd repo get https://github.com/yourusername/your-repo.git
# List Configured Repositories
argocd repo list
# Remove Repository Credentials
argocd repo rm https://github.com/yourusername/your-repo.git
`,
}
command.AddCommand(NewRepoAddCommand(clientOpts))
command.AddCommand(NewRepoGetCommand(clientOpts))
command.AddCommand(NewRepoListCommand(clientOpts))
command.AddCommand(NewRepoRemoveCommand(clientOpts))
return command
}
// NewRepoAddCommand returns a new instance of an `argocd repo add` command
func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
repoOpts cmdutil.RepoOptions
)
// For better readability and easier formatting
var repoAddExamples = ` # Add a Git repository via SSH using a private key for authentication, ignoring the server's host key:
argocd repo add git@git.example.com:repos/repo --insecure-ignore-host-key --ssh-private-key-path ~/id_rsa
# Add a Git repository via SSH on a non-default port - need to use ssh:// style URLs here
argocd repo add ssh://git@git.example.com:2222/repos/repo --ssh-private-key-path ~/id_rsa
# Add a private Git repository via HTTPS using username/password and TLS client certificates:
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
# Add a private Git repository via HTTPS using username/password without verifying the server's TLS certificate
argocd repo add https://git.example.com/repos/repo --username git --password secret --insecure-skip-server-verification
# Add a public Helm repository named 'stable' via HTTPS
argocd repo add https://charts.helm.sh/stable --type helm --name stable
# Add a private Helm repository named 'stable' via HTTPS
argocd repo add https://charts.helm.sh/stable --type helm --name stable --username test --password test
# Add a private Helm OCI-based repository named 'stable' via HTTPS
argocd repo add helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type helm --name stable --enable-oci --username test --password test
# Add a private Git repository on GitHub.com via GitHub App
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
# Add a private Git repository on GitHub Enterprise via GitHub App
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
# Add a private Git repository on Google Cloud Sources via GCP service account credentials
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
`
var command = &cobra.Command{
Use: "add REPOURL",
Short: "Add git repository connection parameters",
Example: repoAddExamples,
Run: func(c *cobra.Command, args []string) {
ctx := c.Context()
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
// Repository URL
repoOpts.Repo.Repo = args[0]
// Specifying ssh-private-key-path is only valid for SSH repositories
if repoOpts.SshPrivateKeyPath != "" {
if ok, _ := git.IsSSHURL(repoOpts.Repo.Repo); ok {
keyData, err := os.ReadFile(repoOpts.SshPrivateKeyPath)
if err != nil {
log.Fatal(err)
}
repoOpts.Repo.SSHPrivateKey = string(keyData)
} else {
err := fmt.Errorf("--ssh-private-key-path is only supported for SSH repositories.")
errors.CheckError(err)
}
}
// tls-client-cert-path and tls-client-cert-key-key-path must always be
// specified together
if (repoOpts.TlsClientCertPath != "" && repoOpts.TlsClientCertKeyPath == "") || (repoOpts.TlsClientCertPath == "" && repoOpts.TlsClientCertKeyPath != "") {
err := fmt.Errorf("--tls-client-cert-path and --tls-client-cert-key-path must be specified together")
errors.CheckError(err)
}
// Specifying tls-client-cert-path is only valid for HTTPS repositories
if repoOpts.TlsClientCertPath != "" {
if git.IsHTTPSURL(repoOpts.Repo.Repo) {
tlsCertData, err := os.ReadFile(repoOpts.TlsClientCertPath)
errors.CheckError(err)
tlsCertKey, err := os.ReadFile(repoOpts.TlsClientCertKeyPath)
errors.CheckError(err)
repoOpts.Repo.TLSClientCertData = string(tlsCertData)
repoOpts.Repo.TLSClientCertKey = string(tlsCertKey)
} else {
err := fmt.Errorf("--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 repoOpts.GithubAppPrivateKeyPath != "" {
if git.IsHTTPSURL(repoOpts.Repo.Repo) {
githubAppPrivateKey, err := os.ReadFile(repoOpts.GithubAppPrivateKeyPath)
errors.CheckError(err)
repoOpts.Repo.GithubAppPrivateKey = string(githubAppPrivateKey)
} else {
err := fmt.Errorf("--github-app-private-key-path is only supported for HTTPS repositories")
errors.CheckError(err)
}
}
if repoOpts.GCPServiceAccountKeyPath != "" {
if git.IsHTTPSURL(repoOpts.Repo.Repo) {
gcpServiceAccountKey, err := os.ReadFile(repoOpts.GCPServiceAccountKeyPath)
errors.CheckError(err)
repoOpts.Repo.GCPServiceAccountKey = string(gcpServiceAccountKey)
} else {
err := fmt.Errorf("--gcp-service-account-key-path is only supported for HTTPS repositories")
errors.CheckError(err)
}
}
// Set repository connection properties only when creating repository, not
// when creating repository credentials.
// InsecureIgnoreHostKey is deprecated and only here for backwards compat
repoOpts.Repo.InsecureIgnoreHostKey = repoOpts.InsecureIgnoreHostKey
repoOpts.Repo.Insecure = repoOpts.InsecureSkipServerVerification
repoOpts.Repo.EnableLFS = repoOpts.EnableLfs
repoOpts.Repo.EnableOCI = repoOpts.EnableOci
repoOpts.Repo.GithubAppId = repoOpts.GithubAppId
repoOpts.Repo.GithubAppInstallationId = repoOpts.GithubAppInstallationId
repoOpts.Repo.GitHubAppEnterpriseBaseURL = repoOpts.GitHubAppEnterpriseBaseURL
repoOpts.Repo.Proxy = repoOpts.Proxy
repoOpts.Repo.ForceHttpBasicAuth = repoOpts.ForceHttpBasicAuth
if repoOpts.Repo.Type == "helm" && repoOpts.Repo.Name == "" {
errors.CheckError(fmt.Errorf("Must specify --name for repos of type 'helm'"))
}
conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoClientOrDie()
defer io.Close(conn)
// If the user set a username, but didn't supply password via --password,
// then we prompt for it
if repoOpts.Repo.Username != "" && repoOpts.Repo.Password == "" {
repoOpts.Repo.Password = cli.PromptPassword(repoOpts.Repo.Password)
}
// We let the server check access to the repository before adding it. If
// it is a private repo, but we cannot access with with the credentials
// that were supplied, we bail out.
//
// Skip validation if we are just adding credentials template, chances
// are high that we do not have the given URL pointing to a valid Git
// repo anyway.
repoAccessReq := repositorypkg.RepoAccessQuery{
Repo: repoOpts.Repo.Repo,
Type: repoOpts.Repo.Type,
Name: repoOpts.Repo.Name,
Username: repoOpts.Repo.Username,
Password: repoOpts.Repo.Password,
SshPrivateKey: repoOpts.Repo.SSHPrivateKey,
TlsClientCertData: repoOpts.Repo.TLSClientCertData,
TlsClientCertKey: repoOpts.Repo.TLSClientCertKey,
Insecure: repoOpts.Repo.IsInsecure(),
EnableOci: repoOpts.Repo.EnableOCI,
GithubAppPrivateKey: repoOpts.Repo.GithubAppPrivateKey,
GithubAppID: repoOpts.Repo.GithubAppId,
GithubAppInstallationID: repoOpts.Repo.GithubAppInstallationId,
GithubAppEnterpriseBaseUrl: repoOpts.Repo.GitHubAppEnterpriseBaseURL,
Proxy: repoOpts.Proxy,
Project: repoOpts.Repo.Project,
GcpServiceAccountKey: repoOpts.Repo.GCPServiceAccountKey,
ForceHttpBasicAuth: repoOpts.Repo.ForceHttpBasicAuth,
}
_, err := repoIf.ValidateAccess(ctx, &repoAccessReq)
errors.CheckError(err)
repoCreateReq := repositorypkg.RepoCreateRequest{
Repo: &repoOpts.Repo,
Upsert: repoOpts.Upsert,
}
createdRepo, err := repoIf.CreateRepository(ctx, &repoCreateReq)
errors.CheckError(err)
fmt.Printf("Repository '%s' added\n", createdRepo.Repo)
},
}
command.Flags().BoolVar(&repoOpts.Upsert, "upsert", false, "Override an existing repository with the same name even if the spec differs")
cmdutil.AddRepoFlags(command, &repoOpts)
return command
}
// NewRepoRemoveCommand returns a new instance of an `argocd repo remove` command
func NewRepoRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "rm REPO",
Short: "Remove repository credentials",
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).NewRepoClientOrDie()
defer io.Close(conn)
for _, repoURL := range args {
_, err := repoIf.DeleteRepository(ctx, &repositorypkg.RepoQuery{Repo: repoURL})
errors.CheckError(err)
fmt.Printf("Repository '%s' removed\n", repoURL)
}
},
}
return command
}
// Print table of repo info
func printRepoTable(repos appsv1.Repositories) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
_, _ = fmt.Fprintf(w, "TYPE\tNAME\tREPO\tINSECURE\tOCI\tLFS\tCREDS\tSTATUS\tMESSAGE\tPROJECT\n")
for _, r := range repos {
var hasCreds string
if r.InheritedCreds {
hasCreds = "inherited"
} else {
hasCreds = strconv.FormatBool(r.HasCredentials())
}
_, _ = 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)
}
_ = w.Flush()
}
// Print list of repo urls or url patterns for repository credentials
func printRepoUrls(repos appsv1.Repositories) {
for _, r := range repos {
fmt.Println(r.Repo)
}
}
// NewRepoListCommand returns a new instance of an `argocd repo list` command
func NewRepoListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
output string
refresh string
)
var command = &cobra.Command{
Use: "list",
Short: "List configured repositories",
Run: func(c *cobra.Command, args []string) {
ctx := c.Context()
conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoClientOrDie()
defer io.Close(conn)
forceRefresh := false
switch refresh {
case "":
case "hard":
forceRefresh = true
default:
err := fmt.Errorf("--refresh must be one of: 'hard'")
errors.CheckError(err)
}
repos, err := repoIf.ListRepositories(ctx, &repositorypkg.RepoQuery{ForceRefresh: forceRefresh})
errors.CheckError(err)
switch output {
case "yaml", "json":
err := PrintResourceList(repos.Items, output, false)
errors.CheckError(err)
case "url":
printRepoUrls(repos.Items)
// wide is the default
case "wide", "":
printRepoTable(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")
command.Flags().StringVar(&refresh, "refresh", "", "Force a cache refresh on connection status , must be one of: 'hard'")
return command
}
// NewRepoGetCommand returns a new instance of an `argocd repo get` command
func NewRepoGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
output string
refresh string
)
var command = &cobra.Command{
Use: "get",
Short: "Get a configured repository by URL",
Run: func(c *cobra.Command, args []string) {
ctx := c.Context()
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
// Repository URL
repoURL := args[0]
conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoClientOrDie()
defer io.Close(conn)
forceRefresh := false
switch refresh {
case "":
case "hard":
forceRefresh = true
default:
err := fmt.Errorf("--refresh must be one of: 'hard'")
errors.CheckError(err)
}
repo, err := repoIf.Get(ctx, &repositorypkg.RepoQuery{Repo: repoURL, ForceRefresh: forceRefresh})
errors.CheckError(err)
switch output {
case "yaml", "json":
err := PrintResource(repo, output)
errors.CheckError(err)
case "url":
fmt.Println(repo.Repo)
// wide is the default
case "wide", "":
printRepoTable(appsv1.Repositories{repo})
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")
command.Flags().StringVar(&refresh, "refresh", "", "Force a cache refresh on connection status , must be one of: 'hard'")
return command
}