1
1
package extract
2
2
3
3
import (
4
+ "archive/tar"
4
5
"context"
5
6
"fmt"
7
+ "io"
6
8
"log"
7
9
"os"
8
10
"path/filepath"
11
+ "strings"
9
12
10
13
"github.com/awslabs/aws-lambda-container-image-converter/img2lambda/types"
11
14
"github.com/containers/image/image"
12
15
"github.com/containers/image/pkg/blobinfocache"
13
16
"github.com/containers/image/transports/alltransports"
14
17
imgtypes "github.com/containers/image/types"
18
+ zglob "github.com/mattn/go-zglob"
19
+ "github.com/mholt/archiver"
15
20
"github.com/pkg/errors"
16
21
)
17
22
@@ -25,6 +30,10 @@ func RepackImage(imageName string, layerOutputDir string) (layers []types.Lambda
25
30
return nil , err
26
31
}
27
32
33
+ return repackImage (ref , imageName , layerOutputDir )
34
+ }
35
+
36
+ func repackImage (ref imgtypes.ImageReference , imageName string , layerOutputDir string ) (layers []types.LambdaLayer , retErr error ) {
28
37
sys := & imgtypes.SystemContext {}
29
38
30
39
ctx := context .Background ()
@@ -88,3 +97,125 @@ func RepackImage(imageName string, layerOutputDir string) (layers []types.Lambda
88
97
89
98
return layers , nil
90
99
}
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