github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/internal/monocular/search.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 monocular 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "net/http" 23 "net/url" 24 "path" 25 "time" 26 27 "github.com/stefanmcshane/helm/internal/version" 28 "github.com/stefanmcshane/helm/pkg/chart" 29 ) 30 31 // SearchPath is the url path to the search API in monocular. 32 const SearchPath = "api/chartsvc/v1/charts/search" 33 34 // The structs below represent the structure of the response from the monocular 35 // search API. The structs were not imported from monocular because monocular 36 // imports from Helm v2 (avoiding circular version dependency) and the mappings 37 // are slightly different (monocular search results do not directly reflect 38 // the struct definitions). 39 40 // SearchResult represents an individual chart result 41 type SearchResult struct { 42 ID string `json:"id"` 43 ArtifactHub ArtifactHub `json:"artifactHub"` 44 Type string `json:"type"` 45 Attributes Chart `json:"attributes"` 46 Links Links `json:"links"` 47 Relationships Relationships `json:"relationships"` 48 } 49 50 // ArtifactHub represents data specific to Artifact Hub instances 51 type ArtifactHub struct { 52 PackageURL string `json:"packageUrl"` 53 } 54 55 // Chart is the attributes for the chart 56 type Chart struct { 57 Name string `json:"name"` 58 Repo Repo `json:"repo"` 59 Description string `json:"description"` 60 Home string `json:"home"` 61 Keywords []string `json:"keywords"` 62 Maintainers []chart.Maintainer `json:"maintainers"` 63 Sources []string `json:"sources"` 64 Icon string `json:"icon"` 65 } 66 67 // Repo contains the name in monocular the url for the repository 68 type Repo struct { 69 Name string `json:"name"` 70 URL string `json:"url"` 71 } 72 73 // Links provides a set of links relative to the chartsvc base 74 type Links struct { 75 Self string `json:"self"` 76 } 77 78 // Relationships provides information on the latest version of the chart 79 type Relationships struct { 80 LatestChartVersion LatestChartVersion `json:"latestChartVersion"` 81 } 82 83 // LatestChartVersion provides the details on the latest version of the chart 84 type LatestChartVersion struct { 85 Data ChartVersion `json:"data"` 86 Links Links `json:"links"` 87 } 88 89 // ChartVersion provides the specific data on the chart version 90 type ChartVersion struct { 91 Version string `json:"version"` 92 AppVersion string `json:"app_version"` 93 Created time.Time `json:"created"` 94 Digest string `json:"digest"` 95 Urls []string `json:"urls"` 96 Readme string `json:"readme"` 97 Values string `json:"values"` 98 } 99 100 // Search performs a search against the monocular search API 101 func (c *Client) Search(term string) ([]SearchResult, error) { 102 103 // Create the URL to the search endpoint 104 // Note, this is currently an internal API for the Hub. This should be 105 // formatted without showing how monocular operates. 106 p, err := url.Parse(c.BaseURL) 107 if err != nil { 108 return nil, err 109 } 110 111 // Set the path to the monocular API endpoint for search 112 p.Path = path.Join(p.Path, SearchPath) 113 114 p.RawQuery = "q=" + url.QueryEscape(term) 115 116 // Create request 117 req, err := http.NewRequest("GET", p.String(), nil) 118 if err != nil { 119 return nil, err 120 } 121 122 // Set the user agent so that monocular can identify where the request 123 // is coming from 124 req.Header.Set("User-Agent", version.GetUserAgent()) 125 126 res, err := http.DefaultClient.Do(req) 127 if err != nil { 128 return nil, err 129 } 130 defer res.Body.Close() 131 132 if res.StatusCode != 200 { 133 return nil, fmt.Errorf("failed to fetch %s : %s", p.String(), res.Status) 134 } 135 136 result := &searchResponse{} 137 138 json.NewDecoder(res.Body).Decode(result) 139 140 return result.Data, nil 141 } 142 143 type searchResponse struct { 144 Data []SearchResult `json:"data"` 145 }