Skip to content

Commit 65775e5

Browse files
committed
Add functionality to search registries
podman search searches a registry for a matching image this adds the functionality to support that some registries respond to the v2 endpoint while others only respond to the v1 endpoint. This checks both endpoints for a result, and if none is given the user is informed. Signed-off-by: umohnani8 <umohnani@redhat.com>
1 parent 495da41 commit 65775e5

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

docker/docker_client.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import (
88
"io"
99
"io/ioutil"
1010
"net/http"
11+
"net/url"
1112
"path/filepath"
13+
"strconv"
1214
"strings"
1315
"time"
1416

@@ -221,6 +223,84 @@ func CheckAuth(ctx context.Context, sCtx *types.SystemContext, username, passwor
221223
}
222224
}
223225

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+
224304
// makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
225305
// 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/.
226306
func (c *dockerClient) makeRequest(ctx context.Context, method, path string, headers map[string][]string, stream io.Reader) (*http.Response, error) {

0 commit comments

Comments
 (0)