11package extract
22
33import (
4+ "archive/tar"
45 "context"
56 "fmt"
7+ "io"
68 "log"
79 "os"
810 "path/filepath"
11+ "strings"
912
1013 "github.com/awslabs/aws-lambda-container-image-converter/img2lambda/types"
1114 "github.com/containers/image/image"
1215 "github.com/containers/image/pkg/blobinfocache"
1316 "github.com/containers/image/transports/alltransports"
1417 imgtypes "github.com/containers/image/types"
18+ zglob "github.com/mattn/go-zglob"
19+ "github.com/mholt/archiver"
1520 "github.com/pkg/errors"
1621)
1722
@@ -25,6 +30,10 @@ func RepackImage(imageName string, layerOutputDir string) (layers []types.Lambda
2530 return nil , err
2631 }
2732
33+ return repackImage (ref , imageName , layerOutputDir )
34+ }
35+
36+ func repackImage (ref imgtypes.ImageReference , imageName string , layerOutputDir string ) (layers []types.LambdaLayer , retErr error ) {
2837 sys := & imgtypes.SystemContext {}
2938
3039 ctx := context .Background ()
@@ -88,3 +97,125 @@ func RepackImage(imageName string, layerOutputDir string) (layers []types.Lambda
8897
8998 return layers , nil
9099}
100+
101+ // Converts container image layer archive (tar) to Lambda layer archive (zip).
102+ // Filters files from the source and only writes a new archive if at least
103+ // one file in the source matches the filter (i.e. does not create empty archives).
104+ func repackLayer (outputFilename string , layerContents io.Reader ) (created bool , retError error ) {
105+ t := archiver .NewTar ()
106+
107+ err := t .Open (layerContents , 0 )
108+ if err != nil {
109+ return false , fmt .Errorf ("opening layer tar: %v" , err )
110+ }
111+ defer t .Close ()
112+
113+ // Walk the files in the tar
114+ var z * archiver.Zip
115+ var out * os.File
116+ defer func () {
117+ if z != nil {
118+ if err := z .Close (); err != nil {
119+ retError = errors .Wrapf (err , " (zip close error: %v)" , err )
120+ }
121+ }
122+ if out != nil {
123+ if err := out .Close (); err != nil {
124+ retError = errors .Wrapf (err , " (file close error: %v)" , err )
125+ }
126+ }
127+ }()
128+
129+ for {
130+ // Get next file in tar
131+ f , err := t .Read ()
132+ if err == io .EOF {
133+ break
134+ }
135+
136+ if err != nil {
137+ return false , fmt .Errorf ("opening next file in layer tar: %v" , err )
138+ }
139+
140+ // Determine if this file should be repacked
141+ repack , err := shouldRepackLayerFile (f )
142+ if err != nil {
143+ return false , fmt .Errorf ("filtering file in layer tar: %v" , err )
144+ }
145+ if repack {
146+ if z == nil {
147+ z , out , err = startZipFile (outputFilename )
148+ if err != nil {
149+ return false , fmt .Errorf ("starting zip file: %v" , err )
150+ }
151+ }
152+
153+ err = repackLayerFile (f , z )
154+ }
155+
156+ if err != nil {
157+ return false , fmt .Errorf ("walking %s in layer tar: %v" , f .Name (), err )
158+ }
159+ }
160+
161+ return (z != nil ), nil
162+ }
163+
164+ func startZipFile (destination string ) (zip * archiver.Zip , zipFile * os.File , err error ) {
165+ z := archiver .NewZip ()
166+
167+ out , err := os .Create (destination )
168+ if err != nil {
169+ return nil , nil , fmt .Errorf ("creating %s: %v" , destination , err )
170+ }
171+
172+ err = z .Create (out )
173+ if err != nil {
174+ return nil , nil , fmt .Errorf ("creating zip: %v" , err )
175+ }
176+
177+ return z , out , nil
178+ }
179+
180+ func shouldRepackLayerFile (f archiver.File ) (should bool , err error ) {
181+ header , ok := f .Header .(* tar.Header )
182+ if ! ok {
183+ return false , fmt .Errorf ("expected header to be *tar.Header but was %T" , f .Header )
184+ }
185+
186+ if f .IsDir () || header .Typeflag == tar .TypeDir {
187+ return false , nil
188+ }
189+
190+ // Ignore whiteout files
191+ if strings .HasPrefix (f .Name (), ".wh." ) {
192+ return false , nil
193+ }
194+
195+ // Only extract files that can be used for Lambda custom runtimes
196+ return zglob .Match ("opt/**/**" , header .Name )
197+ }
198+
199+ func repackLayerFile (f archiver.File , z * archiver.Zip ) error {
200+ hdr , ok := f .Header .(* tar.Header )
201+ if ! ok {
202+ return fmt .Errorf ("expected header to be *tar.Header but was %T" , f .Header )
203+ }
204+
205+ filename := strings .TrimPrefix (filepath .ToSlash (hdr .Name ), "opt/" )
206+
207+ switch hdr .Typeflag {
208+ case tar .TypeReg , tar .TypeRegA , tar .TypeChar , tar .TypeBlock , tar .TypeFifo , tar .TypeSymlink , tar .TypeLink :
209+ return z .Write (archiver.File {
210+ FileInfo : archiver.FileInfo {
211+ FileInfo : f .FileInfo ,
212+ CustomName : filename ,
213+ },
214+ ReadCloser : f ,
215+ })
216+ case tar .TypeXGlobalHeader :
217+ return nil // ignore
218+ default :
219+ return fmt .Errorf ("%s: unknown type flag: %c" , hdr .Name , hdr .Typeflag )
220+ }
221+ }
0 commit comments