1/*
2Copyright The Helm Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package main
18
19import (
20	"io"
21	"strings"
22
23	"github.com/gosuri/uitable"
24	"github.com/pkg/errors"
25	"github.com/spf13/cobra"
26
27	"helm.sh/helm/v3/cmd/helm/require"
28	"helm.sh/helm/v3/pkg/cli/output"
29	"helm.sh/helm/v3/pkg/repo"
30)
31
32func newRepoListCmd(out io.Writer) *cobra.Command {
33	var outfmt output.Format
34	cmd := &cobra.Command{
35		Use:               "list",
36		Aliases:           []string{"ls"},
37		Short:             "list chart repositories",
38		Args:              require.NoArgs,
39		ValidArgsFunction: noCompletions,
40		RunE: func(cmd *cobra.Command, args []string) error {
41			f, err := repo.LoadFile(settings.RepositoryConfig)
42			if isNotExist(err) || (len(f.Repositories) == 0 && !(outfmt == output.JSON || outfmt == output.YAML)) {
43				return errors.New("no repositories to show")
44			}
45
46			return outfmt.Write(out, &repoListWriter{f.Repositories})
47		},
48	}
49
50	bindOutputFlag(cmd, &outfmt)
51
52	return cmd
53}
54
55type repositoryElement struct {
56	Name string `json:"name"`
57	URL  string `json:"url"`
58}
59
60type repoListWriter struct {
61	repos []*repo.Entry
62}
63
64func (r *repoListWriter) WriteTable(out io.Writer) error {
65	table := uitable.New()
66	table.AddRow("NAME", "URL")
67	for _, re := range r.repos {
68		table.AddRow(re.Name, re.URL)
69	}
70	return output.EncodeTable(out, table)
71}
72
73func (r *repoListWriter) WriteJSON(out io.Writer) error {
74	return r.encodeByFormat(out, output.JSON)
75}
76
77func (r *repoListWriter) WriteYAML(out io.Writer) error {
78	return r.encodeByFormat(out, output.YAML)
79}
80
81func (r *repoListWriter) encodeByFormat(out io.Writer, format output.Format) error {
82	// Initialize the array so no results returns an empty array instead of null
83	repolist := make([]repositoryElement, 0, len(r.repos))
84
85	for _, re := range r.repos {
86		repolist = append(repolist, repositoryElement{Name: re.Name, URL: re.URL})
87	}
88
89	switch format {
90	case output.JSON:
91		return output.EncodeJSON(out, repolist)
92	case output.YAML:
93		return output.EncodeYAML(out, repolist)
94	}
95
96	// Because this is a non-exported function and only called internally by
97	// WriteJSON and WriteYAML, we shouldn't get invalid types
98	return nil
99}
100
101// Returns all repos from repos, except those with names matching ignoredRepoNames
102// Inspired by https://stackoverflow.com/a/28701031/893211
103func filterRepos(repos []*repo.Entry, ignoredRepoNames []string) []*repo.Entry {
104	// if ignoredRepoNames is nil, just return repo
105	if ignoredRepoNames == nil {
106		return repos
107	}
108
109	filteredRepos := make([]*repo.Entry, 0)
110
111	ignored := make(map[string]bool, len(ignoredRepoNames))
112	for _, repoName := range ignoredRepoNames {
113		ignored[repoName] = true
114	}
115
116	for _, repo := range repos {
117		if _, removed := ignored[repo.Name]; !removed {
118			filteredRepos = append(filteredRepos, repo)
119		}
120	}
121
122	return filteredRepos
123}
124
125// Provide dynamic auto-completion for repo names
126func compListRepos(prefix string, ignoredRepoNames []string) []string {
127	var rNames []string
128
129	f, err := repo.LoadFile(settings.RepositoryConfig)
130	if err == nil && len(f.Repositories) > 0 {
131		filteredRepos := filterRepos(f.Repositories, ignoredRepoNames)
132		for _, repo := range filteredRepos {
133			if strings.HasPrefix(repo.Name, prefix) {
134				rNames = append(rNames, repo.Name)
135			}
136		}
137	}
138	return rNames
139}
140