88 "io"
99 "io/ioutil"
1010 "net/http"
11+ "net/url"
1112 "path/filepath"
13+ "strconv"
1214 "strings"
1315 "time"
1416
@@ -24,8 +26,9 @@ import (
2426)
2527
2628const (
27- dockerHostname = "docker.io"
28- dockerRegistry = "registry-1.docker.io"
29+ dockerHostname = "docker.io"
30+ dockerV1Hostname = "index.docker.io"
31+ dockerRegistry = "registry-1.docker.io"
2932
3033 systemPerHostCertDirPath = "/etc/docker/certs.d"
3134
@@ -221,6 +224,100 @@ func CheckAuth(ctx context.Context, sCtx *types.SystemContext, username, passwor
221224 }
222225}
223226
227+ // SearchResult holds the information of each matching image
228+ // It matches the output returned by the v1 endpoint
229+ type SearchResult struct {
230+ Name string `json:"name"`
231+ Description string `json:"description"`
232+ // StarCount states the number of stars the image has
233+ StarCount int `json:"star_count"`
234+ IsTrusted bool `json:"is_trusted"`
235+ // IsAutomated states whether the image is an automated build
236+ IsAutomated bool `json:"is_automated"`
237+ // IsOfficial states whether the image is an official build
238+ IsOfficial bool `json:"is_official"`
239+ }
240+
241+ // SearchRegistry queries a registry for images that contain "image" in their name
242+ // The limit is the max number of results desired
243+ // Note: The limit value doesn't work with all registries
244+ // for example registry.access.redhat.com returns all the results without limiting it to the limit value
245+ func SearchRegistry (ctx context.Context , sCtx * types.SystemContext , registry , image string , limit int ) ([]SearchResult , error ) {
246+ type V2Results struct {
247+ // Repositories holds the results returned by the /v2/_catalog endpoint
248+ Repositories []string `json:"repositories"`
249+ }
250+ type V1Results struct {
251+ // Results holds the results returned by the /v1/search endpoint
252+ Results []SearchResult `json:"results"`
253+ }
254+ v2Res := & V2Results {}
255+ v1Res := & V1Results {}
256+
257+ // The /v2/_catalog endpoint has been disabled for docker.io therefore the call made to that endpoint will fail
258+ // So using the v1 hostname for docker.io for simplicity of implementation and the fact that it returns search results
259+ if registry == dockerHostname {
260+ registry = dockerV1Hostname
261+ }
262+
263+ client , err := newDockerClientWithDetails (sCtx , registry , "" , "" , "" , nil , "" )
264+ if err != nil {
265+ return nil , errors .Wrapf (err , "error creating new docker client" )
266+ }
267+
268+ logrus .Debugf ("trying to talk to v2 search endpoint\n " )
269+ resp , err := client .makeRequest (ctx , "GET" , "/v2/_catalog" , nil , nil )
270+ if err != nil {
271+ logrus .Debugf ("error getting search results from v2 endpoint %q: %v" , registry , err )
272+ } else {
273+ defer resp .Body .Close ()
274+ if resp .StatusCode != http .StatusOK {
275+ logrus .Debugf ("error getting search results from v2 endpoint %q, status code %q" , registry , resp .StatusCode )
276+ } else {
277+ if err := json .NewDecoder (resp .Body ).Decode (v2Res ); err != nil {
278+ return nil , err
279+ }
280+ searchRes := []SearchResult {}
281+ for _ , repo := range v2Res .Repositories {
282+ if strings .Contains (repo , image ) {
283+ res := SearchResult {
284+ Name : repo ,
285+ }
286+ searchRes = append (searchRes , res )
287+ }
288+ }
289+ return searchRes , nil
290+ }
291+ }
292+
293+ // set up the query values for the v1 endpoint
294+ u := url.URL {
295+ Path : "/v1/search" ,
296+ }
297+ q := u .Query ()
298+ q .Set ("q" , image )
299+ q .Set ("n" , strconv .Itoa (limit ))
300+ u .RawQuery = q .Encode ()
301+
302+ logrus .Debugf ("trying to talk to v1 search endpoint\n " )
303+ resp , err = client .makeRequest (ctx , "GET" , u .String (), nil , nil )
304+ if err != nil {
305+ logrus .Debugf ("error getting search results from v1 endpoint %q: %v" , registry , err )
306+ } else {
307+ defer resp .Body .Close ()
308+ if resp .StatusCode != http .StatusOK {
309+ logrus .Debugf ("error getting search results from v1 endpoint %q, status code %q" , registry , resp .StatusCode )
310+ } else {
311+ if err := json .NewDecoder (resp .Body ).Decode (v1Res ); err != nil {
312+ return nil , err
313+ }
314+ return v1Res .Results , nil
315+ }
316+ }
317+
318+ return nil , errors .Wrapf (err , "couldn't search registry %q" , registry )
319+ }
320+
224321// makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
225322// 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/.
226323func (c * dockerClient ) makeRequest (ctx context.Context , method , path string , headers map [string ][]string , stream io.Reader ) (* http.Response , error ) {
0 commit comments