github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/cmd/helm/search_hub.go (about) 1 /* 2 Copyright The Helm Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "fmt" 21 "io" 22 "strings" 23 24 "github.com/gosuri/uitable" 25 "github.com/pkg/errors" 26 "github.com/spf13/cobra" 27 28 "github.com/stefanmcshane/helm/internal/monocular" 29 "github.com/stefanmcshane/helm/pkg/cli/output" 30 ) 31 32 const searchHubDesc = ` 33 Search for Helm charts in the Artifact Hub or your own hub instance. 34 35 Artifact Hub is a web-based application that enables finding, installing, and 36 publishing packages and configurations for CNCF projects, including publicly 37 available distributed charts Helm charts. It is a Cloud Native Computing 38 Foundation sandbox project. You can browse the hub at https://artifacthub.io/ 39 40 The [KEYWORD] argument accepts either a keyword string, or quoted string of rich 41 query options. For rich query options documentation, see 42 https://artifacthub.github.io/hub/api/?urls.primaryName=Monocular%20compatible%20search%20API#/Monocular/get_api_chartsvc_v1_charts_search 43 44 Previous versions of Helm used an instance of Monocular as the default 45 'endpoint', so for backwards compatibility Artifact Hub is compatible with the 46 Monocular search API. Similarly, when setting the 'endpoint' flag, the specified 47 endpoint must also be implement a Monocular compatible search API endpoint. 48 Note that when specifying a Monocular instance as the 'endpoint', rich queries 49 are not supported. For API details, see https://github.com/helm/monocular 50 ` 51 52 type searchHubOptions struct { 53 searchEndpoint string 54 maxColWidth uint 55 outputFormat output.Format 56 listRepoURL bool 57 } 58 59 func newSearchHubCmd(out io.Writer) *cobra.Command { 60 o := &searchHubOptions{} 61 62 cmd := &cobra.Command{ 63 Use: "hub [KEYWORD]", 64 Short: "search for charts in the Artifact Hub or your own hub instance", 65 Long: searchHubDesc, 66 RunE: func(cmd *cobra.Command, args []string) error { 67 return o.run(out, args) 68 }, 69 } 70 71 f := cmd.Flags() 72 f.StringVar(&o.searchEndpoint, "endpoint", "https://hub.helm.sh", "Hub instance to query for charts") 73 f.UintVar(&o.maxColWidth, "max-col-width", 50, "maximum column width for output table") 74 f.BoolVar(&o.listRepoURL, "list-repo-url", false, "print charts repository URL") 75 76 bindOutputFlag(cmd, &o.outputFormat) 77 78 return cmd 79 } 80 81 func (o *searchHubOptions) run(out io.Writer, args []string) error { 82 c, err := monocular.New(o.searchEndpoint) 83 if err != nil { 84 return errors.Wrap(err, fmt.Sprintf("unable to create connection to %q", o.searchEndpoint)) 85 } 86 87 q := strings.Join(args, " ") 88 results, err := c.Search(q) 89 if err != nil { 90 debug("%s", err) 91 return fmt.Errorf("unable to perform search against %q", o.searchEndpoint) 92 } 93 94 return o.outputFormat.Write(out, newHubSearchWriter(results, o.searchEndpoint, o.maxColWidth, o.listRepoURL)) 95 } 96 97 type hubChartRepo struct { 98 URL string `json:"url"` 99 Name string `json:"name"` 100 } 101 102 type hubChartElement struct { 103 URL string `json:"url"` 104 Version string `json:"version"` 105 AppVersion string `json:"app_version"` 106 Description string `json:"description"` 107 Repository hubChartRepo `json:"repository"` 108 } 109 110 type hubSearchWriter struct { 111 elements []hubChartElement 112 columnWidth uint 113 listRepoURL bool 114 } 115 116 func newHubSearchWriter(results []monocular.SearchResult, endpoint string, columnWidth uint, listRepoURL bool) *hubSearchWriter { 117 var elements []hubChartElement 118 for _, r := range results { 119 // Backwards compatibility for Monocular 120 url := endpoint + "/charts/" + r.ID 121 122 // Check for artifactHub compatibility 123 if r.ArtifactHub.PackageURL != "" { 124 url = r.ArtifactHub.PackageURL 125 } 126 127 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}}) 128 } 129 return &hubSearchWriter{elements, columnWidth, listRepoURL} 130 } 131 132 func (h *hubSearchWriter) WriteTable(out io.Writer) error { 133 if len(h.elements) == 0 { 134 _, err := out.Write([]byte("No results found\n")) 135 if err != nil { 136 return fmt.Errorf("unable to write results: %s", err) 137 } 138 return nil 139 } 140 table := uitable.New() 141 table.MaxColWidth = h.columnWidth 142 143 if h.listRepoURL { 144 table.AddRow("URL", "CHART VERSION", "APP VERSION", "DESCRIPTION", "REPO URL") 145 } else { 146 table.AddRow("URL", "CHART VERSION", "APP VERSION", "DESCRIPTION") 147 } 148 149 for _, r := range h.elements { 150 if h.listRepoURL { 151 table.AddRow(r.URL, r.Version, r.AppVersion, r.Description, r.Repository.URL) 152 } else { 153 table.AddRow(r.URL, r.Version, r.AppVersion, r.Description) 154 } 155 } 156 return output.EncodeTable(out, table) 157 } 158 159 func (h *hubSearchWriter) WriteJSON(out io.Writer) error { 160 return h.encodeByFormat(out, output.JSON) 161 } 162 163 func (h *hubSearchWriter) WriteYAML(out io.Writer) error { 164 return h.encodeByFormat(out, output.YAML) 165 } 166 167 func (h *hubSearchWriter) encodeByFormat(out io.Writer, format output.Format) error { 168 // Initialize the array so no results returns an empty array instead of null 169 chartList := make([]hubChartElement, 0, len(h.elements)) 170 171 for _, r := range h.elements { 172 chartList = append(chartList, hubChartElement{r.URL, r.Version, r.AppVersion, r.Description, r.Repository}) 173 } 174 175 switch format { 176 case output.JSON: 177 return output.EncodeJSON(out, chartList) 178 case output.YAML: 179 return output.EncodeYAML(out, chartList) 180 } 181 182 // Because this is a non-exported function and only called internally by 183 // WriteJSON and WriteYAML, we shouldn't get invalid types 184 return nil 185 }