|
8 | 8 | "io" |
9 | 9 | "io/ioutil" |
10 | 10 | "net/http" |
| 11 | + "net/url" |
11 | 12 | "path/filepath" |
| 13 | + "strconv" |
12 | 14 | "strings" |
13 | 15 | "time" |
14 | 16 |
|
@@ -221,6 +223,84 @@ func CheckAuth(ctx context.Context, sCtx *types.SystemContext, username, passwor |
221 | 223 | } |
222 | 224 | } |
223 | 225 |
|
| 226 | +// SearchResult holds the information of each matching image |
| 227 | +// It matches the output returned by the v1 endpoint |
| 228 | +type SearchResult struct { |
| 229 | + Name string `json:"name"` |
| 230 | + Description string `json:"description"` |
| 231 | + // StarCount states the number of stars the image has |
| 232 | + StarCount int `json:"star_count"` |
| 233 | + IsTrusted bool `json:"is_trusted"` |
| 234 | + // IsAutomated states whether the image is an automated build |
| 235 | + IsAutomated bool `json:"is_automated"` |
| 236 | + // IsOfficial states whether the image is an official build |
| 237 | + IsOfficial bool `json:"is_official"` |
| 238 | +} |
| 239 | + |
| 240 | +// SearchRegistry queries a registry for images that contain "image" in their name |
| 241 | +// The limit is the max number of results desired |
| 242 | +// Note: The limit value doesn't work with all registries |
| 243 | +// for example registry.access.redhat.com returns all the results without limiting it to the limit value |
| 244 | +func SearchRegistry(ctx context.Context, sCtx *types.SystemContext, registry, image string, limit int) ([]SearchResult, error) { |
| 245 | + type V2Results struct { |
| 246 | + // Repositories holds the results returned by the /v2/_catalog endpoint |
| 247 | + Repositories []string `json:"repositories"` |
| 248 | + } |
| 249 | + type V1Results struct { |
| 250 | + // Results holds the results returned by the /v1/search endpoint |
| 251 | + Results []SearchResult `json:"results"` |
| 252 | + } |
| 253 | + v2Res := &V2Results{} |
| 254 | + v1Res := &V1Results{} |
| 255 | + |
| 256 | + client, err := newDockerClientWithDetails(sCtx, registry, "", "", "", nil, "") |
| 257 | + if err != nil { |
| 258 | + return nil, errors.Wrapf(err, "error creating new docker client") |
| 259 | + } |
| 260 | + |
| 261 | + logrus.Debugf("trying to talk to v2 search endpoint\n") |
| 262 | + resp, err := client.makeRequest(ctx, "GET", "/v2/_catalog", nil, nil) |
| 263 | + if err == nil && resp.StatusCode == http.StatusOK { |
| 264 | + if err := json.NewDecoder(resp.Body).Decode(v2Res); err != nil { |
| 265 | + return nil, err |
| 266 | + } |
| 267 | + searchRes := []SearchResult{} |
| 268 | + for _, repo := range v2Res.Repositories { |
| 269 | + if strings.Contains(repo, image) { |
| 270 | + res := SearchResult{ |
| 271 | + Name: repo, |
| 272 | + } |
| 273 | + searchRes = append(searchRes, res) |
| 274 | + } |
| 275 | + } |
| 276 | + return searchRes, nil |
| 277 | + } |
| 278 | + defer resp.Body.Close() |
| 279 | + logrus.Errorf("error getting search results from v2 endpoint %q, status code %q: %v", registry, resp.StatusCode, err) |
| 280 | + |
| 281 | + // set up the query values for the v1 endpoint |
| 282 | + u := url.URL{ |
| 283 | + Path: "/v1/search", |
| 284 | + } |
| 285 | + q := u.Query() |
| 286 | + q.Set("q", image) |
| 287 | + q.Set("n", strconv.Itoa(limit)) |
| 288 | + u.RawQuery = q.Encode() |
| 289 | + |
| 290 | + logrus.Debugf("trying to talk to v1 search endpoint\n") |
| 291 | + resp, err = client.makeRequest(ctx, "GET", u.String(), nil, nil) |
| 292 | + if err == nil || resp.StatusCode == http.StatusOK { |
| 293 | + if err := json.NewDecoder(resp.Body).Decode(v1Res); err != nil { |
| 294 | + return nil, err |
| 295 | + } |
| 296 | + return v1Res.Results, nil |
| 297 | + } |
| 298 | + defer resp.Body.Close() |
| 299 | + logrus.Errorf("error getting search results from v2 endpoint %q, status code %q: %v", registry, resp.StatusCode, err) |
| 300 | + |
| 301 | + return nil, errors.Wrapf(err, "couldn't search registry %q", registry) |
| 302 | +} |
| 303 | + |
224 | 304 | // makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client. |
225 | 305 | // The host name and schema is taken from the client or autodetected, and the path is relative to it, i.e. the path usually starts with /v2/. |
226 | 306 | func (c *dockerClient) makeRequest(ctx context.Context, method, path string, headers map[string][]string, stream io.Reader) (*http.Response, error) { |
|
0 commit comments