11package synclister
22
33import (
4+ "context"
45 "fmt"
6+ "net/http"
57 "net/url"
68 "sort"
9+ "strconv"
710 "strings"
811 "time"
912
@@ -17,24 +20,28 @@ import (
1720)
1821
1922type SyncLister struct {
20- logger * zap.SugaredLogger
21- driver * metalgo.Driver
22- config * api.Config
23- s3 * s3.S3
24- collector * metrics.Collector
23+ logger * zap.SugaredLogger
24+ driver * metalgo.Driver
25+ config * api.Config
26+ s3 * s3.S3
27+ stop context.Context
28+ imageCollector * metrics.ImageCollector
29+ httpClient * http.Client
2530}
2631
27- func NewSyncLister (logger * zap.SugaredLogger , driver * metalgo.Driver , s3 * s3.S3 , collector * metrics.Collector , config * api.Config ) * SyncLister {
32+ func NewSyncLister (logger * zap.SugaredLogger , driver * metalgo.Driver , s3 * s3.S3 , imageCollector * metrics.ImageCollector , config * api.Config , stop context. Context ) * SyncLister {
2833 return & SyncLister {
29- logger : logger ,
30- driver : driver ,
31- config : config ,
32- s3 : s3 ,
33- collector : collector ,
34+ logger : logger ,
35+ driver : driver ,
36+ config : config ,
37+ s3 : s3 ,
38+ stop : stop ,
39+ imageCollector : imageCollector ,
40+ httpClient : http .DefaultClient ,
3441 }
3542}
3643
37- func (s * SyncLister ) DetermineSyncList () ([]api.OS , error ) {
44+ func (s * SyncLister ) DetermineImageSyncList () ([]api.OS , error ) {
3845 s3Images , err := s .retrieveImagesFromS3 ()
3946 if err != nil {
4047 return nil , errors .Wrap (err , "error listing images in s3" )
@@ -45,21 +52,13 @@ func (s *SyncLister) DetermineSyncList() ([]api.OS, error) {
4552 return nil , errors .Wrap (err , "error listing images" )
4653 }
4754
48- s .collector .SetMetalAPIImageCount (len (resp .Image ))
55+ s .imageCollector .SetMetalAPIImageCount (len (resp .Image ))
4956
5057 expirationGraceDays := 24 * time .Hour * time .Duration (s .config .ExpirationGraceDays )
5158
5259 images := api.OSImagesByOS {}
5360 for _ , img := range resp .Image {
54- skip := false
55- for _ , exclude := range s .config .ExcludePaths {
56- if strings .Contains (img .URL , exclude ) {
57- skip = true
58- break
59- }
60- }
61-
62- if skip {
61+ if s .isExcluded (img .URL ) {
6362 s .logger .Debugw ("skipping image with exclude URL" , "id" , * img .ID )
6463 continue
6564 }
@@ -106,12 +105,13 @@ func (s *SyncLister) DetermineSyncList() ([]api.OS, error) {
106105 }
107106
108107 imageVersions = append (imageVersions , api.OS {
109- Name : os ,
110- Version : ver ,
111- ApiRef : * img ,
112- BucketKey : bucketKey ,
113- ImageRef : s3Image ,
114- MD5Ref : s3MD5 ,
108+ Name : os ,
109+ Version : ver ,
110+ ApiRef : * img ,
111+ BucketKey : bucketKey ,
112+ BucketName : s .config .ImageBucket ,
113+ ImageRef : s3Image ,
114+ MD5Ref : s3MD5 ,
115115 })
116116
117117 versions [majorMinor ] = imageVersions
@@ -121,12 +121,13 @@ func (s *SyncLister) DetermineSyncList() ([]api.OS, error) {
121121 var sizeCount int64
122122 var syncImages []api.OS
123123 for _ , versions := range images {
124- for _ , images := range versions {
125- sort .Slice (images , func (i , j int ) bool {
126- return images [i ].Version .GreaterThan (images [j ].Version )
124+ for _ , versionedImages := range versions {
125+ versionedImages := versionedImages
126+ sort .Slice (versionedImages , func (i , j int ) bool {
127+ return versionedImages [i ].Version .GreaterThan (versionedImages [j ].Version )
127128 })
128129 amount := 0
129- for _ , img := range images {
130+ for _ , img := range versionedImages {
130131 if s .config .MaxImagesPerName > 0 && amount >= s .config .MaxImagesPerName {
131132 break
132133 }
@@ -151,11 +152,148 @@ func (s *SyncLister) DetermineSyncList() ([]api.OS, error) {
151152 }
152153 }
153154
154- s .collector .SetUnsyncedImageCount (len (resp .Image ) - len (syncImages ))
155+ s .imageCollector .SetUnsyncedImageCount (len (resp .Image ) - len (syncImages ))
155156
156157 return syncImages , nil
157158}
158159
160+ func (s * SyncLister ) isExcluded (url string ) bool {
161+ for _ , exclude := range s .config .ExcludePaths {
162+ if strings .Contains (url , exclude ) {
163+ return true
164+ }
165+ }
166+
167+ return false
168+ }
169+
170+ func (s * SyncLister ) DetermineKernelSyncList () ([]api.Kernel , error ) {
171+ resp , err := s .driver .PartitionList ()
172+ if err != nil {
173+ return nil , errors .Wrap (err , "error listing partitions" )
174+ }
175+
176+ var result []api.Kernel
177+ urls := map [string ]bool {}
178+
179+ for _ , p := range resp .Partition {
180+ if p .Bootconfig == nil {
181+ continue
182+ }
183+
184+ kernelURL := p .Bootconfig .Kernelurl
185+
186+ if urls [kernelURL ] {
187+ continue
188+ }
189+
190+ if s .isExcluded (kernelURL ) {
191+ s .logger .Debugw ("skipping kernel with exclude URL" , "url" , kernelURL )
192+ continue
193+ }
194+
195+ u , err := url .Parse (kernelURL )
196+ if err != nil {
197+ s .logger .Errorw ("kernel url is invalid, skipping" , "error" , err )
198+ continue
199+ }
200+
201+ size , err := retrieveContentLength (s .stop , s .httpClient , u .String ())
202+ if err != nil {
203+ s .logger .Warnw ("unable to determine kernel download size" , "error" , err )
204+ }
205+
206+ result = append (result , api.Kernel {
207+ SubPath : strings .TrimPrefix (u .Path , "/" ),
208+ URL : kernelURL ,
209+ Size : size ,
210+ })
211+ urls [kernelURL ] = true
212+ }
213+
214+ return result , nil
215+ }
216+
217+ func (s * SyncLister ) DetermineBootImageSyncList () ([]api.BootImage , error ) {
218+ resp , err := s .driver .PartitionList ()
219+ if err != nil {
220+ return nil , errors .Wrap (err , "error listing partitions" )
221+ }
222+
223+ var result []api.BootImage
224+ urls := map [string ]bool {}
225+
226+ for _ , p := range resp .Partition {
227+ if p .Bootconfig == nil {
228+ continue
229+ }
230+
231+ bootImageURL := p .Bootconfig .Imageurl
232+
233+ if urls [bootImageURL ] {
234+ continue
235+ }
236+
237+ if s .isExcluded (bootImageURL ) {
238+ s .logger .Debugw ("skipping boot image with exclude URL" , "url" , bootImageURL )
239+ continue
240+ }
241+
242+ u , err := url .Parse (bootImageURL )
243+ if err != nil {
244+ s .logger .Errorw ("boot image url is invalid, skipping" , "error" , err )
245+ continue
246+ }
247+
248+ size , err := retrieveContentLength (s .stop , s .httpClient , u .String ())
249+ if err != nil {
250+ s .logger .Warnw ("unable to determine boot image download size" , "error" , err )
251+ }
252+
253+ md5URL := u .String () + ".md5"
254+ _ , err = retrieveContentLength (s .stop , s .httpClient , md5URL )
255+ if err != nil {
256+ s .logger .Errorw ("boot image md5 does not exist, skipping" , "url" , md5URL , "error" , err )
257+ continue
258+ }
259+
260+ result = append (result , api.BootImage {
261+ SubPath : strings .TrimPrefix (u .Path , "/" ),
262+ URL : bootImageURL ,
263+ Size : size ,
264+ })
265+ urls [bootImageURL ] = true
266+ }
267+
268+ return result , nil
269+ }
270+
271+ func retrieveContentLength (ctx context.Context , c * http.Client , url string ) (int64 , error ) {
272+ req , err := http .NewRequest (http .MethodHead , url , nil )
273+ if err != nil {
274+ return 0 , errors .Wrap (err , "unable to create head request" )
275+ }
276+
277+ req = req .WithContext (ctx )
278+
279+ resp , err := c .Do (req )
280+ if err != nil {
281+ return 0 , err
282+ }
283+ defer resp .Body .Close ()
284+
285+ if resp .StatusCode != http .StatusOK {
286+ return 0 , fmt .Errorf ("head request to url did not return OK: %s" , url )
287+ }
288+
289+ size , err := strconv .Atoi (resp .Header .Get ("Content-Length" ))
290+ if err != nil {
291+ return 0 , errors .Wrap (err , "content-length header value could not be converted to integer" )
292+ }
293+
294+ return int64 (size ), nil
295+ }
296+
159297func (s * SyncLister ) reduce (images []api.OS , sizeCount int64 ) ([]api.OS , int64 , error ) {
160298 groups := map [string ][]api.OS {}
161299 for _ , img := range images {
0 commit comments