Repository: helm/helm
Pull request #9119 (2020-12-12 08:20:28)

Added flag --list-repo
The flag makes helm print the chart repo URL to use with helm repo add.
Issue: #7419

Signed-off-by: Dmitriy Ermakov <demonihin@gmail.com>

<!--  Thanks for sending a pull request!  Here are some tips for you:
1. Make sure to read the Contributing Guide before submitting your PR: https://github.com/helm/helm/blob/master/CONTRIBUTING.md
2. If this PR closes another issue, add 'closes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'refs #<issue number>' will not close the issue, but help provide the reviewer more context.-->

**What this PR does / why we need it**:
This PR allows printing of charts repo URLs. The printed URLs could be used in helm repo add <name> <URL>.
The PR is a possible solution to --list-repo flag which mentioned in #7419.


**If applicable**:
- [ ] this PR contains documentation
- [x] this PR contains unit tests
- [ ] this PR has been tested for backwards compatibility



Patch:
diff --git a/cmd/helm/search_hub.go b/cmd/helm/search_hub.go
index 82b555788..b8887efd5 100644
--- a/cmd/helm/search_hub.go
+++ b/cmd/helm/search_hub.go
@@ -53,6 +53,7 @@ type searchHubOptions struct {
 	searchEndpoint string
 	maxColWidth    uint
 	outputFormat   output.Format
+	listRepoURL    bool
 }
 
 func newSearchHubCmd(out io.Writer) *cobra.Command {
@@ -70,6 +71,8 @@ func newSearchHubCmd(out io.Writer) *cobra.Command {
 	f := cmd.Flags()
 	f.StringVar(&o.searchEndpoint, "endpoint", "https://hub.helm.sh", "Hub instance to query for charts")
 	f.UintVar(&o.maxColWidth, "max-col-width", 50, "maximum column width for output table")
+	f.BoolVar(&o.listRepoURL, "list-repo-url", false, "print charts repository URL")
+
 	bindOutputFlag(cmd, &o.outputFormat)
 
 	return cmd
@@ -88,22 +91,29 @@ func (o *searchHubOptions) run(out io.Writer, args []string) error {
 		return fmt.Errorf("unable to perform search against %q", o.searchEndpoint)
 	}
 
-	return o.outputFormat.Write(out, newHubSearchWriter(results, o.searchEndpoint, o.maxColWidth))
+	return o.outputFormat.Write(out, newHubSearchWriter(results, o.searchEndpoint, o.maxColWidth, o.listRepoURL))
+}
+
+type hubChartRepo struct {
+	URL  string `json:"url"`
+	Name string `json:"name"`
 }
 
 type hubChartElement struct {
-	URL         string `json:"url"`
-	Version     string `json:"version"`
-	AppVersion  string `json:"app_version"`
-	Description string `json:"description"`
+	URL         string       `json:"url"`
+	Version     string       `json:"version"`
+	AppVersion  string       `json:"app_version"`
+	Description string       `json:"description"`
+	Repository  hubChartRepo `json:"repository"`
 }
 
 type hubSearchWriter struct {
 	elements    []hubChartElement
 	columnWidth uint
+	listRepoURL bool
 }
 
-func newHubSearchWriter(results []monocular.SearchResult, endpoint string, columnWidth uint) *hubSearchWriter {
+func newHubSearchWriter(results []monocular.SearchResult, endpoint string, columnWidth uint, listRepoURL bool) *hubSearchWriter {
 	var elements []hubChartElement
 	for _, r := range results {
 		// Backwards compatibility for Monocular
@@ -114,9 +124,9 @@ func newHubSearchWriter(results []monocular.SearchResult, endpoint string, colum
 			url = r.ArtifactHub.PackageURL
 		}
 
-		elements = append(elements, hubChartElement{url, r.Relationships.LatestChartVersion.Data.Version, r.Relationships.LatestChartVersion.Data.AppVersion, r.Attributes.Description})
+		elements = append(elements, hubChartElement{url, r.Relationships.LatestChartVersion.Data.Version, r.Relationships.LatestChartVersion.Data.AppVersion, r.Attributes.Description, hubChartRepo{URL: r.Attributes.Repo.URL, Name: r.Attributes.Repo.Name}})
 	}
-	return &hubSearchWriter{elements, columnWidth}
+	return &hubSearchWriter{elements, columnWidth, listRepoURL}
 }
 
 func (h *hubSearchWriter) WriteTable(out io.Writer) error {
@@ -129,9 +139,19 @@ func (h *hubSearchWriter) WriteTable(out io.Writer) error {
 	}
 	table := uitable.New()
 	table.MaxColWidth = h.columnWidth
-	table.AddRow("URL", "CHART VERSION", "APP VERSION", "DESCRIPTION")
+
+	if h.listRepoURL {
+		table.AddRow("URL", "CHART VERSION", "APP VERSION", "DESCRIPTION", "REPO URL")
+	} else {
+		table.AddRow("URL", "CHART VERSION", "APP VERSION", "DESCRIPTION")
+	}
+
 	for _, r := range h.elements {
-		table.AddRow(r.URL, r.Version, r.AppVersion, r.Description)
+		if h.listRepoURL {
+			table.AddRow(r.URL, r.Version, r.AppVersion, r.Description, r.Repository.URL)
+		} else {
+			table.AddRow(r.URL, r.Version, r.AppVersion, r.Description)
+		}
 	}
 	return output.EncodeTable(out, table)
 }
@@ -149,7 +169,7 @@ func (h *hubSearchWriter) encodeByFormat(out io.Writer, format output.Format) er
 	chartList := make([]hubChartElement, 0, len(h.elements))
 
 	for _, r := range h.elements {
-		chartList = append(chartList, hubChartElement{r.URL, r.Version, r.AppVersion, r.Description})
+		chartList = append(chartList, hubChartElement{r.URL, r.Version, r.AppVersion, r.Description, r.Repository})
 	}
 
 	switch format {


Test patch:
diff --git a/cmd/helm/search_hub_test.go b/cmd/helm/search_hub_test.go
index ae51b6a3e..7df54ea8f 100644
--- a/cmd/helm/search_hub_test.go
+++ b/cmd/helm/search_hub_test.go
@@ -33,6 +33,8 @@ func TestSearchHubCmd(t *testing.T) {
 	defer ts.Close()
 
 	// The expected output has the URL to the mocked search service in it
+	// Trailing spaces are necessary to preserve in "expected" as the uitable package adds
+	// them during printing.
 	var expected = fmt.Sprintf(`URL                                             	CHART VERSION	APP VERSION	DESCRIPTION                                   
 %s/charts/stable/phpmyadmin 	3.0.0        	4.9.0-1    	phpMyAdmin is an mysql administration frontend
 %s/charts/bitnami/phpmyadmin	3.0.0        	4.9.0-1    	phpMyAdmin is an mysql administration frontend
@@ -51,6 +53,36 @@ func TestSearchHubCmd(t *testing.T) {
 	}
 }
 
+func TestSearchHubListRepoCmd(t *testing.T) {
+
+	// Setup a mock search service
+	var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}`
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprintln(w, searchResult)
+	}))
+	defer ts.Close()
+
+	// The expected output has the URL to the mocked search service in it
+	// Trailing spaces are necessary to preserve in "expected" as the uitable package adds
+	// them during printing.
+	var expected = fmt.Sprintf(`URL                                             	CHART VERSION	APP VERSION	DESCRIPTION                                   	REPO URL                     
+%s/charts/stable/phpmyadmin 	3.0.0        	4.9.0-1    	phpMyAdmin is an mysql administration frontend	https://charts.helm.sh/stable
+%s/charts/bitnami/phpmyadmin	3.0.0        	4.9.0-1    	phpMyAdmin is an mysql administration frontend	https://charts.bitnami.com   
+`, ts.URL, ts.URL)
+
+	testcmd := "search hub --list-repo-url --endpoint " + ts.URL + " maria"
+	storage := storageFixture()
+	_, out, err := executeActionCommandC(storage, testcmd)
+	if err != nil {
+		t.Errorf("unexpected error, %s", err)
+	}
+	if out != expected {
+		t.Error("expected and actual output did not match")
+		t.Log(out)
+		t.Log(expected)
+	}
+}
+
 func TestSearchHubOutputCompletion(t *testing.T) {
 	outputFlagCompletionTest(t, "search hub")
 }