10
10
import tarfile
11
11
import tempfile
12
12
import threading
13
- from typing import List
13
+ from typing import List , Optional , Union
14
14
15
- import docker
15
+ import docker as docker_library
16
16
17
17
from docker_squash .errors import SquashError , SquashUnnecessaryError
18
18
19
19
20
20
class Chdir (object ):
21
21
"""Context manager for changing the current working directory"""
22
22
23
- def __init__ (self , newPath ):
24
- self .newPath = os .path .expanduser (newPath )
23
+ def __init__ (self , new_path ):
24
+ self .newPath = os .path .expanduser (new_path )
25
25
26
26
def __enter__ (self ):
27
27
self .savedPath = os .getcwd ()
@@ -43,18 +43,26 @@ class Image(object):
43
43
""" Image format version """
44
44
45
45
def __init__ (
46
- self , log , docker , image , from_layer , tmp_dir = None , tag = None , comment = ""
46
+ self ,
47
+ log ,
48
+ docker ,
49
+ image ,
50
+ from_layer ,
51
+ tmp_dir : Optional [str ] = None ,
52
+ tag : Optional [str ] = None ,
53
+ comment : Optional [str ] = "" ,
47
54
):
48
- self .log = log
55
+ self .log : logging . Logger = log
49
56
self .debug = self .log .isEnabledFor (logging .DEBUG )
50
57
self .docker = docker
51
- self .image = image
52
- self .from_layer = from_layer
53
- self .tag = tag
54
- self .comment = comment
58
+ self .image : str = image
59
+ self .from_layer : str = from_layer
60
+ self .tag : str = tag
61
+ self .comment : str = comment
55
62
self .image_name = None
56
63
self .image_tag = None
57
64
self .squash_id = None
65
+ self .oci_format = False
58
66
59
67
# Workaround for https://play.golang.org/p/sCsWMXYxqy
60
68
#
@@ -68,7 +76,7 @@ def __init__(
68
76
)
69
77
""" Date used in metadata, already formatted using the `%Y-%m-%dT%H:%M:%S.%fZ` format """
70
78
71
- self .tmp_dir = tmp_dir
79
+ self .tmp_dir : str = tmp_dir
72
80
""" Main temporary directory to save all working files. This is the root directory for all other temporary files. """
73
81
74
82
def squash (self ):
@@ -95,11 +103,11 @@ def _initialize_directories(self):
95
103
raise SquashError ("Preparing temporary directory failed" )
96
104
97
105
# Temporary location on the disk of the old, unpacked *image*
98
- self .old_image_dir = os .path .join (self .tmp_dir , "old" )
106
+ self .old_image_dir : str = os .path .join (self .tmp_dir , "old" )
99
107
# Temporary location on the disk of the new, unpacked, squashed *image*
100
- self .new_image_dir = os .path .join (self .tmp_dir , "new" )
108
+ self .new_image_dir : str = os .path .join (self .tmp_dir , "new" )
101
109
# Temporary location on the disk of the squashed *layer*
102
- self .squashed_dir = os .path .join (self .new_image_dir , "squashed" )
110
+ self .squashed_dir : str = os .path .join (self .new_image_dir , "squashed" )
103
111
104
112
for d in self .old_image_dir , self .new_image_dir :
105
113
os .makedirs (d )
@@ -115,14 +123,12 @@ def _squash_id(self, layer):
115
123
squash_id = self .docker .inspect_image (layer )["Id" ]
116
124
except Exception :
117
125
raise SquashError (
118
- "Could not get the layer ID to squash, please check provided 'layer' argument: %s"
119
- % layer
126
+ f"Could not get the layer ID to squash, please check provided 'layer' argument: { layer } "
120
127
)
121
128
122
129
if squash_id not in self .old_image_layers :
123
130
raise SquashError (
124
- "Couldn't find the provided layer (%s) in the %s image"
125
- % (layer , self .image )
131
+ f"Couldn't find the provided layer ({ layer } ) in the { self .image } image"
126
132
)
127
133
128
134
self .log .debug ("Layer ID to squash from: %s" % squash_id )
@@ -138,16 +144,14 @@ def _validate_number_of_layers(self, number_of_layers):
138
144
# Only positive numbers are correct
139
145
if number_of_layers <= 0 :
140
146
raise SquashError (
141
- "Number of layers to squash cannot be less or equal 0, provided: %s"
142
- % number_of_layers
147
+ f"Number of layers to squash cannot be less or equal 0, provided: { number_of_layers } "
143
148
)
144
149
145
150
# Do not squash if provided number of layer to squash is bigger
146
151
# than number of actual layers in the image
147
152
if number_of_layers > len (self .old_image_layers ):
148
153
raise SquashError (
149
- "Cannot squash %s layers, the %s image contains only %s layers"
150
- % (number_of_layers , self .image , len (self .old_image_layers ))
154
+ f"Cannot squash { number_of_layers } layers, the { self .image } image contains only { len (self .old_image_layers )} layers"
151
155
)
152
156
153
157
def _before_squashing (self ):
@@ -164,17 +168,14 @@ def _before_squashing(self):
164
168
self .old_image_id = self .docker .inspect_image (self .image )["Id" ]
165
169
except SquashError :
166
170
raise SquashError (
167
- "Could not get the image ID to squash, please check provided 'image' argument: %s"
168
- % self .image
171
+ f"Could not get the image ID to squash, please check provided 'image' argument: { self .image } "
169
172
)
170
173
171
174
self .old_image_layers = []
172
175
173
176
# Read all layers in the image
174
177
self ._read_layers (self .old_image_layers , self .old_image_id )
175
-
176
178
self .old_image_layers .reverse ()
177
-
178
179
self .log .info ("Old image has %s layers" , len (self .old_image_layers ))
179
180
self .log .debug ("Old layers: %s" , self .old_image_layers )
180
181
@@ -193,8 +194,7 @@ def _before_squashing(self):
193
194
194
195
if not squash_id :
195
196
raise SquashError (
196
- "The %s layer could not be found in the %s image"
197
- % (self .from_layer , self .image )
197
+ f"The { self .from_layer } layer could not be found in the { self .image } image"
198
198
)
199
199
200
200
number_of_layers = (
@@ -212,7 +212,7 @@ def _before_squashing(self):
212
212
213
213
if len (self .layers_to_squash ) < 1 :
214
214
raise SquashError (
215
- "Invalid number of layers to squash: %s" % len (self .layers_to_squash )
215
+ f "Invalid number of layers to squash: { len (self .layers_to_squash )} "
216
216
)
217
217
218
218
if len (self .layers_to_squash ) == 1 :
@@ -233,6 +233,7 @@ def _before_squashing(self):
233
233
234
234
def _after_squashing (self ):
235
235
self .log .debug ("Removing from disk already squashed layers..." )
236
+ self .log .debug ("Cleaning up %s temporary directory" % self .old_image_dir )
236
237
shutil .rmtree (self .old_image_dir , ignore_errors = True )
237
238
238
239
self .size_after = self ._dir_size (self .new_image_dir )
@@ -281,29 +282,28 @@ def load_squashed_image(self):
281
282
% (self .image_name , self .image_tag )
282
283
)
283
284
284
- def _files_in_layers (self , layers , directory ):
285
+ def _files_in_layers (self , layers ):
285
286
"""
286
287
Prepare a list of files in all layers
287
288
"""
288
289
files = {}
289
290
290
291
for layer in layers :
291
292
self .log .debug ("Generating list of files in layer '%s'..." % layer )
292
- tar_file = os . path . join ( directory , layer , "layer.tar" )
293
+ tar_file = self . _extract_tar_name ( layer )
293
294
with tarfile .open (tar_file , "r" , format = tarfile .PAX_FORMAT ) as tar :
294
295
files [layer ] = [self ._normalize_path (x ) for x in tar .getnames ()]
295
296
self .log .debug ("Done, found %s files" % len (files [layer ]))
296
297
297
298
return files
298
299
299
- def _prepare_tmp_directory (self , tmp_dir ) :
300
+ def _prepare_tmp_directory (self , tmp_dir : str ) -> str :
300
301
"""Creates temporary directory that is used to work on layers"""
301
302
302
303
if tmp_dir :
303
304
if os .path .exists (tmp_dir ):
304
305
raise SquashError (
305
- "The '%s' directory already exists, please remove it before you proceed"
306
- % tmp_dir
306
+ f"The '{ tmp_dir } ' directory already exists, please remove it before you proceed"
307
307
)
308
308
os .makedirs (tmp_dir )
309
309
else :
@@ -374,9 +374,9 @@ def _save_image(self, image_id, directory):
374
374
try :
375
375
image = self .docker .get_image (image_id )
376
376
377
- if int (docker .__version__ .split ("." )[0 ]) < 3 :
377
+ if int (docker_library .__version__ .split ("." )[0 ]) < 3 :
378
378
# Docker library prior to 3.0.0 returned the requests
379
- # object directly which cold be used to read from
379
+ # object directly which could be used to read from
380
380
self .log .debug (
381
381
"Extracting image using HTTPResponse object directly"
382
382
)
@@ -408,10 +408,10 @@ def _save_image(self, image_id, directory):
408
408
except Exception as e :
409
409
self .log .exception (e )
410
410
self .log .warning (
411
- "An error occured while saving the %s image, retrying..." % image_id
411
+ f "An error occurred while saving the { image_id } image, retrying..."
412
412
)
413
413
414
- raise SquashError ("Couldn't save %s image!" % image_id )
414
+ raise SquashError (f "Couldn't save { image_id } image!" )
415
415
416
416
def _unpack (self , tar_file , directory ):
417
417
"""Unpacks tar archive to selected directory"""
@@ -500,7 +500,7 @@ def _read_old_metadata(self, old_json_file):
500
500
501
501
return metadata
502
502
503
- def _move_layers (self , layers , src , dest ):
503
+ def _move_layers (self , layers , src : str , dest : str ):
504
504
"""
505
505
This moves all the layers that should be copied as-is.
506
506
In other words - all layers that are not meant to be squashed will be
@@ -530,7 +530,7 @@ def _marker_files(self, tar, members):
530
530
"""
531
531
Searches for marker files in the specified archive.
532
532
533
- Docker marker files are files taht have the .wh. prefix in the name.
533
+ Docker marker files are files that have the .wh. prefix in the name.
534
534
These files mark the corresponding file to be removed (hidden) when
535
535
we start a container from the image.
536
536
"""
@@ -609,7 +609,9 @@ def _add_markers(self, markers, tar, files_in_layers, added_symlinks):
609
609
else :
610
610
self .log .debug ("Skipping '%s' marker file..." % marker .name )
611
611
612
- def _normalize_path (self , path ):
612
+ def _normalize_path (
613
+ self , path : Union [str , pathlib .Path ]
614
+ ) -> Union [str , pathlib .Path ]:
613
615
return os .path .normpath (os .path .join ("/" , path ))
614
616
615
617
def _add_hardlinks (self , squashed_tar , squashed_files , to_skip , skipped_hard_links ):
@@ -743,17 +745,15 @@ def _add_symlinks(self, squashed_tar, squashed_files, to_skip, skipped_sym_links
743
745
744
746
return added_symlinks
745
747
746
- def _squash_layers (self , layers_to_squash , layers_to_move ):
747
- self .log .info ("Starting squashing..." )
748
+ def _squash_layers (self , layers_to_squash : List [ str ] , layers_to_move : List [ str ] ):
749
+ self .log .info (f "Starting squashing for { self . squashed_tar } ..." )
748
750
749
751
# Reverse the layers to squash - we begin with the newest one
750
752
# to make the tar lighter
751
753
layers_to_squash .reverse ()
752
754
753
755
# Find all files in layers that we don't squash
754
- files_in_layers_to_move = self ._files_in_layers (
755
- layers_to_move , self .old_image_dir
756
- )
756
+ files_in_layers_to_move = self ._files_in_layers (layers_to_move )
757
757
758
758
with tarfile .open (
759
759
self .squashed_tar , "w" , format = tarfile .PAX_FORMAT
@@ -770,8 +770,7 @@ def _squash_layers(self, layers_to_squash, layers_to_move):
770
770
reading_layers : List [tarfile .TarFile ] = []
771
771
772
772
for layer_id in layers_to_squash :
773
- layer_tar_file = os .path .join (self .old_image_dir , layer_id , "layer.tar" )
774
-
773
+ layer_tar_file = self ._extract_tar_name (layer_id )
775
774
self .log .info ("Squashing file '%s'..." % layer_tar_file )
776
775
777
776
# Open the exiting layer to squash
@@ -1028,3 +1027,9 @@ def _path_hierarchy(self, path):
1028
1027
return itertools .accumulate (
1029
1028
path .parts [:- 1 ], func = lambda head , tail : str (path .__class__ (head , tail ))
1030
1029
)
1030
+
1031
+ def _extract_tar_name (self , path : str ) -> str :
1032
+ if self .oci_format :
1033
+ return os .path .join (self .old_image_dir , path )
1034
+ else :
1035
+ return os .path .join (self .old_image_dir , path , "layer.tar" )
0 commit comments