github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/libpod/image/search.go (about) 1 package image 2 3 import ( 4 "context" 5 "strconv" 6 "strings" 7 "sync" 8 9 "github.com/containers/image/v5/docker" 10 "github.com/containers/image/v5/types" 11 sysreg "github.com/containers/libpod/pkg/registries" 12 "github.com/pkg/errors" 13 "github.com/sirupsen/logrus" 14 "golang.org/x/sync/semaphore" 15 ) 16 17 const ( 18 descriptionTruncLength = 44 19 maxQueries = 25 20 maxParallelSearches = int64(6) 21 ) 22 23 // SearchResult is holding image-search related data. 24 type SearchResult struct { 25 // Index is the image index (e.g., "docker.io" or "quay.io") 26 Index string 27 // Name is the canoncical name of the image (e.g., "docker.io/library/alpine"). 28 Name string 29 // Description of the image. 30 Description string 31 // Stars is the number of stars of the image. 32 Stars int 33 // Official indicates if it's an official image. 34 Official string 35 // Automated indicates if the image was created by an automated build. 36 Automated string 37 } 38 39 // SearchOptions are used to control the behaviour of SearchImages. 40 type SearchOptions struct { 41 // Filter allows to filter the results. 42 Filter SearchFilter 43 // Limit limits the number of queries per index (default: 25). Must be 44 // greater than 0 to overwrite the default value. 45 Limit int 46 // NoTrunc avoids the output to be truncated. 47 NoTrunc bool 48 // Authfile is the path to the authentication file. 49 Authfile string 50 // InsecureSkipTLSVerify allows to skip TLS verification. 51 InsecureSkipTLSVerify types.OptionalBool 52 } 53 54 // SearchFilter allows filtering the results of SearchImages. 55 type SearchFilter struct { 56 // Stars describes the minimal amount of starts of an image. 57 Stars int 58 // IsAutomated decides if only images from automated builds are displayed. 59 IsAutomated types.OptionalBool 60 // IsOfficial decides if only official images are displayed. 61 IsOfficial types.OptionalBool 62 } 63 64 // SearchImages searches images based on term and the specified SearchOptions 65 // in all registries. 66 func SearchImages(term string, options SearchOptions) ([]SearchResult, error) { 67 // Check if search term has a registry in it 68 registry, err := sysreg.GetRegistry(term) 69 if err != nil { 70 return nil, errors.Wrapf(err, "error getting registry from %q", term) 71 } 72 if registry != "" { 73 term = term[len(registry)+1:] 74 } 75 76 registries, err := getRegistries(registry) 77 if err != nil { 78 return nil, err 79 } 80 81 // searchOutputData is used as a return value for searching in parallel. 82 type searchOutputData struct { 83 data []SearchResult 84 err error 85 } 86 87 // Let's follow Firefox by limiting parallel downloads to 6. 88 sem := semaphore.NewWeighted(maxParallelSearches) 89 wg := sync.WaitGroup{} 90 wg.Add(len(registries)) 91 data := make([]searchOutputData, len(registries)) 92 93 searchImageInRegistryHelper := func(index int, registry string) { 94 defer sem.Release(1) 95 defer wg.Done() 96 searchOutput, err := searchImageInRegistry(term, registry, options) 97 data[index] = searchOutputData{data: searchOutput, err: err} 98 } 99 100 ctx := context.Background() 101 for i := range registries { 102 if err := sem.Acquire(ctx, 1); err != nil { 103 return nil, err 104 } 105 go searchImageInRegistryHelper(i, registries[i]) 106 } 107 108 wg.Wait() 109 results := []SearchResult{} 110 for _, d := range data { 111 if d.err != nil { 112 return nil, d.err 113 } 114 results = append(results, d.data...) 115 } 116 return results, nil 117 } 118 119 // getRegistries returns the list of registries to search, depending on an optional registry specification 120 func getRegistries(registry string) ([]string, error) { 121 var registries []string 122 if registry != "" { 123 registries = append(registries, registry) 124 } else { 125 var err error 126 registries, err = sysreg.GetRegistries() 127 if err != nil { 128 return nil, errors.Wrapf(err, "error getting registries to search") 129 } 130 } 131 return registries, nil 132 } 133 134 func searchImageInRegistry(term string, registry string, options SearchOptions) ([]SearchResult, error) { 135 // Max number of queries by default is 25 136 limit := maxQueries 137 if options.Limit > 0 { 138 limit = options.Limit 139 } 140 141 sc := GetSystemContext("", options.Authfile, false) 142 sc.DockerInsecureSkipTLSVerify = options.InsecureSkipTLSVerify 143 // FIXME: Set this more globally. Probably no reason not to have it in 144 // every types.SystemContext, and to compute the value just once in one 145 // place. 146 sc.SystemRegistriesConfPath = sysreg.SystemRegistriesConfPath() 147 results, err := docker.SearchRegistry(context.TODO(), sc, registry, term, limit) 148 if err != nil { 149 logrus.Errorf("error searching registry %q: %v", registry, err) 150 return []SearchResult{}, nil 151 } 152 index := registry 153 arr := strings.Split(registry, ".") 154 if len(arr) > 2 { 155 index = strings.Join(arr[len(arr)-2:], ".") 156 } 157 158 // limit is the number of results to output 159 // if the total number of results is less than the limit, output all 160 // if the limit has been set by the user, output those number of queries 161 limit = maxQueries 162 if len(results) < limit { 163 limit = len(results) 164 } 165 if options.Limit != 0 { 166 limit = len(results) 167 if options.Limit < len(results) { 168 limit = options.Limit 169 } 170 } 171 172 paramsArr := []SearchResult{} 173 for i := 0; i < limit; i++ { 174 // Check whether query matches filters 175 if !(options.Filter.matchesAutomatedFilter(results[i]) && options.Filter.matchesOfficialFilter(results[i]) && options.Filter.matchesStarFilter(results[i])) { 176 continue 177 } 178 official := "" 179 if results[i].IsOfficial { 180 official = "[OK]" 181 } 182 automated := "" 183 if results[i].IsAutomated { 184 automated = "[OK]" 185 } 186 description := strings.Replace(results[i].Description, "\n", " ", -1) 187 if len(description) > 44 && !options.NoTrunc { 188 description = description[:descriptionTruncLength] + "..." 189 } 190 name := registry + "/" + results[i].Name 191 if index == "docker.io" && !strings.Contains(results[i].Name, "/") { 192 name = index + "/library/" + results[i].Name 193 } 194 params := SearchResult{ 195 Index: index, 196 Name: name, 197 Description: description, 198 Official: official, 199 Automated: automated, 200 Stars: results[i].StarCount, 201 } 202 paramsArr = append(paramsArr, params) 203 } 204 return paramsArr, nil 205 } 206 207 // ParseSearchFilter turns the filter into a SearchFilter that can be used for 208 // searching images. 209 func ParseSearchFilter(filter []string) (*SearchFilter, error) { 210 sFilter := new(SearchFilter) 211 for _, f := range filter { 212 arr := strings.Split(f, "=") 213 switch arr[0] { 214 case "stars": 215 if len(arr) < 2 { 216 return nil, errors.Errorf("invalid `stars` filter %q, should be stars=<value>", filter) 217 } 218 stars, err := strconv.Atoi(arr[1]) 219 if err != nil { 220 return nil, errors.Wrapf(err, "incorrect value type for stars filter") 221 } 222 sFilter.Stars = stars 223 case "is-automated": 224 if len(arr) == 2 && arr[1] == "false" { 225 sFilter.IsAutomated = types.OptionalBoolFalse 226 } else { 227 sFilter.IsAutomated = types.OptionalBoolTrue 228 } 229 case "is-official": 230 if len(arr) == 2 && arr[1] == "false" { 231 sFilter.IsOfficial = types.OptionalBoolFalse 232 } else { 233 sFilter.IsOfficial = types.OptionalBoolTrue 234 } 235 default: 236 return nil, errors.Errorf("invalid filter type %q", f) 237 } 238 } 239 return sFilter, nil 240 } 241 242 func (f *SearchFilter) matchesStarFilter(result docker.SearchResult) bool { 243 return result.StarCount >= f.Stars 244 } 245 246 func (f *SearchFilter) matchesAutomatedFilter(result docker.SearchResult) bool { 247 if f.IsAutomated != types.OptionalBoolUndefined { 248 return result.IsAutomated == (f.IsAutomated == types.OptionalBoolTrue) 249 } 250 return true 251 } 252 253 func (f *SearchFilter) matchesOfficialFilter(result docker.SearchResult) bool { 254 if f.IsOfficial != types.OptionalBoolUndefined { 255 return result.IsOfficial == (f.IsOfficial == types.OptionalBoolTrue) 256 } 257 return true 258 }