@@ -7,34 +7,53 @@ import (
7
7
"path/filepath"
8
8
"strconv"
9
9
"strings"
10
+ "time"
10
11
11
12
log "github.com/Sirupsen/logrus"
12
13
"github.com/cloudimmunity/go-dockerclientx"
13
- )
14
+ "github.com/dustin/go-humanize"
14
15
15
- type Layer struct {
16
- Name string
17
- Tags []string
18
- }
16
+ v "github.com/docker-slim/docker-slim/pkg/version"
17
+ )
19
18
20
19
// Info represents the reverse engineered Dockerfile info
21
20
type Info struct {
22
21
Lines []string
23
22
AllUsers []string
24
23
ExeUser string
25
24
ExposedPorts []string
26
- Layers []Layer
25
+ ImageStack []* ImageInfo
26
+ }
27
+
28
+ type ImageInfo struct {
29
+ IsTopImage bool `json:"is_top_image"`
30
+ ID string `json:"id"`
31
+ FullName string `json:"full_name"`
32
+ RepoName string `json:"repo_name"`
33
+ VersionTag string `json:"version_tag"`
34
+ RawTags []string `json:"raw_tags,omitempty"`
35
+ CreateTime string `json:"create_time"`
36
+ NewSize int64 `json:"new_size"`
37
+ NewSizeHuman string `json:"new_size_human"`
38
+ BaseImageID string `json:"base_image_id,omitempty"`
39
+ Instructions []* InstructionInfo `json:"instructions"`
27
40
}
28
41
29
- type imageInst struct {
30
- instCmd string
31
- instComment string
32
- instType string
33
- instTime int64
34
- layerImageID string
35
- imageName string
36
- shortTags []string
37
- fullTags []string
42
+ type InstructionInfo struct {
43
+ Type string `json:"type"`
44
+ Time string `json:"time"`
45
+ IsNop bool `json:"is_nop"`
46
+ IsLocal bool `json:"is_local"`
47
+ IntermediateImageID string `json:"intermediate_image_id,omitempty"`
48
+ Size int64 `json:"size"`
49
+ SizeHuman string `json:"size_human,omitempty"`
50
+ CommandSnippet string `json:"command_snippet"`
51
+ command string
52
+ SystemCommands []string `json:"system_commands,omitempty"`
53
+ Comment string `json:"comment,omitempty"`
54
+ instPosition string
55
+ imageFullName string
56
+ RawTags []string `json:"raw_tags,omitempty"`
38
57
}
39
58
40
59
// ReverseDockerfileFromHistory recreates Dockerfile information from container image history
@@ -49,17 +68,26 @@ func ReverseDockerfileFromHistory(apiClient *docker.Client, imageID string) (*In
49
68
50
69
log .Debugf ("\n \n IMAGE HISTORY =>\n %#v\n \n " , imageHistory )
51
70
52
- var fatImageDockerInstructions []imageInst
71
+ var fatImageDockerInstructions []InstructionInfo
72
+ var currentImageInfo * ImageInfo
73
+ var prevImageID string
53
74
54
75
imageLayerCount := len (imageHistory )
55
76
imageLayerStart := imageLayerCount - 1
77
+ startNewImage := true
56
78
if imageLayerCount > 0 {
57
79
for idx := imageLayerStart ; idx >= 0 ; idx -- {
80
+ isNop := false
81
+
58
82
nopPrefix := "/bin/sh -c #(nop) "
59
83
execPrefix := "/bin/sh -c "
60
84
rawLine := imageHistory [idx ].CreatedBy
61
85
var inst string
62
86
87
+ if strings .Contains (rawLine , "(nop)" ) {
88
+ isNop = true
89
+ }
90
+
63
91
switch {
64
92
case len (rawLine ) == 0 :
65
93
inst = "FROM scratch"
@@ -116,60 +144,124 @@ func ReverseDockerfileFromHistory(apiClient *docker.Client, imageID string) (*In
116
144
}
117
145
}
118
146
119
- instInfo := imageInst {
120
- instCmd : inst ,
121
- instTime : imageHistory [idx ].Created ,
122
- layerImageID : imageHistory [idx ].ID ,
123
- instComment : imageHistory [idx ].Comment ,
147
+ instInfo := InstructionInfo {
148
+ IsNop : isNop ,
149
+ command : cleanInst ,
150
+ Time : time .Unix (imageHistory [idx ].Created , 0 ).UTC ().Format (time .RFC3339 ),
151
+ Comment : imageHistory [idx ].Comment ,
152
+ RawTags : imageHistory [idx ].Tags ,
153
+ Size : imageHistory [idx ].Size ,
154
+ }
155
+
156
+ instParts := strings .SplitN (cleanInst , " " , 2 )
157
+ if len (instParts ) == 2 {
158
+ instInfo .Type = instParts [0 ]
159
+ }
160
+
161
+ if instInfo .Type == "RUN" {
162
+ var cmdParts []string
163
+ cmds := strings .Replace (instParts [1 ], "\\ " , "" , - 1 )
164
+ if strings .Contains (cmds , "&&" ) {
165
+ cmdParts = strings .Split (cmds , "&&" )
166
+ } else {
167
+ cmdParts = strings .Split (cmds , ";" )
168
+ }
169
+
170
+ for _ , cmd := range cmdParts {
171
+ cmd = strings .TrimSpace (cmd )
172
+ cmd = strings .Replace (cmd , "\t " , "" , - 1 )
173
+ cmd = strings .Replace (cmd , "\n " , "" , - 1 )
174
+ instInfo .SystemCommands = append (instInfo .SystemCommands , cmd )
175
+ }
176
+ }
177
+
178
+ if instInfo .Type == "WORKDIR" {
179
+ instInfo .SystemCommands = append (instInfo .SystemCommands , fmt .Sprintf ("mkdir -p %s" , instParts [1 ]))
180
+ }
181
+
182
+ if len (instInfo .command ) > 44 {
183
+ instInfo .CommandSnippet = fmt .Sprintf ("%s..." , instInfo .command [0 :44 ])
184
+ } else {
185
+ instInfo .CommandSnippet = instInfo .command
186
+ }
187
+
188
+ if instInfo .Size > 0 {
189
+ instInfo .SizeHuman = humanize .Bytes (uint64 (instInfo .Size ))
190
+ }
191
+
192
+ if imageHistory [idx ].ID != "<missing>" {
193
+ instInfo .IsLocal = true
194
+ instInfo .IntermediateImageID = imageHistory [idx ].ID
124
195
}
125
196
126
- instType := "intermediate"
197
+ if startNewImage {
198
+ startNewImage = false
199
+ currentImageInfo = & ImageInfo {
200
+ BaseImageID : prevImageID ,
201
+ NewSize : 0 ,
202
+ }
203
+ }
204
+
205
+ currentImageInfo .NewSize += imageHistory [idx ].Size
206
+ currentImageInfo .Instructions = append (currentImageInfo .Instructions , & instInfo )
207
+
208
+ instPosition := "intermediate"
127
209
if idx == imageLayerStart {
128
- instType = "first"
210
+ instPosition = "first" //first instruction in the list
129
211
}
130
212
131
213
if len (imageHistory [idx ].Tags ) > 0 {
132
- instType = "last"
214
+ instPosition = "last" //last in an image
133
215
134
- if tagInfo := strings .Split (imageHistory [idx ].Tags [0 ], ":" ); len (tagInfo ) > 1 {
135
- instInfo .imageName = tagInfo [0 ]
136
- }
216
+ currentImageInfo .ID = imageHistory [idx ].ID
217
+ prevImageID = currentImageInfo .ID
137
218
138
- instInfo .fullTags = imageHistory [idx ].Tags
219
+ currentImageInfo .CreateTime = instInfo .Time
220
+ currentImageInfo .RawTags = imageHistory [idx ].Tags
139
221
140
- for _ , fullTag := range instInfo .fullTags {
141
- if tagInfo := strings .Split (fullTag , ":" ); len (tagInfo ) > 1 {
142
- instInfo .shortTags = append (instInfo .shortTags , tagInfo [1 ])
143
- }
222
+ instInfo .imageFullName = imageHistory [idx ].Tags [0 ]
223
+ currentImageInfo .FullName = imageHistory [idx ].Tags [0 ]
224
+
225
+ if tagInfo := strings .Split (imageHistory [idx ].Tags [0 ], ":" ); len (tagInfo ) > 1 {
226
+ currentImageInfo .RepoName = tagInfo [0 ]
227
+ currentImageInfo .VersionTag = tagInfo [1 ]
144
228
}
145
229
146
- out .Layers = append (out .Layers , Layer {Name : instInfo .imageName , Tags : instInfo .shortTags })
230
+ currentImageInfo .NewSizeHuman = humanize .Bytes (uint64 (currentImageInfo .NewSize ))
231
+
232
+ out .ImageStack = append (out .ImageStack , currentImageInfo )
233
+ startNewImage = true
147
234
}
148
235
149
- instInfo .instType = instType
236
+ instInfo .instPosition = instPosition
150
237
151
238
fatImageDockerInstructions = append (fatImageDockerInstructions , instInfo )
152
239
}
240
+
241
+ if currentImageInfo != nil {
242
+ currentImageInfo .IsTopImage = true
243
+ }
153
244
}
154
245
155
246
for idx , instInfo := range fatImageDockerInstructions {
156
- if instInfo .instType == "first" {
247
+ if instInfo .instPosition == "first" {
157
248
out .Lines = append (out .Lines , "# new image" )
158
249
}
159
250
160
- out .Lines = append (out .Lines , instInfo .instCmd )
161
- if instInfo .instType == "last" {
251
+ out .Lines = append (out .Lines , instInfo .command )
252
+ if instInfo .instPosition == "last" {
162
253
commentText := fmt .Sprintf ("# end of image: %s (id: %s tags: %s)" ,
163
- instInfo .imageName , instInfo .layerImageID , strings .Join (instInfo .shortTags , "," ))
254
+ instInfo .imageFullName , instInfo .IntermediateImageID , strings .Join (instInfo .RawTags , "," ))
255
+
164
256
out .Lines = append (out .Lines , commentText )
165
257
out .Lines = append (out .Lines , "" )
166
258
if idx < (len (fatImageDockerInstructions ) - 1 ) {
167
259
out .Lines = append (out .Lines , "# new image" )
168
260
}
169
261
}
170
262
171
- if instInfo .instComment != "" {
172
- out .Lines = append (out .Lines , "# " + instInfo .instComment )
263
+ if instInfo .Comment != "" {
264
+ out .Lines = append (out .Lines , "# " + instInfo .Comment )
173
265
}
174
266
175
267
//TODO: use time diff to separate each instruction
@@ -228,6 +320,9 @@ func GenerateFromInfo(location string,
228
320
var dfData bytes.Buffer
229
321
dfData .WriteString ("FROM scratch\n " )
230
322
323
+ dsInfoLabel := fmt .Sprintf ("LABEL docker-slim.version=\" %s\" \n " , v .Current ())
324
+ dfData .WriteString (dsInfoLabel )
325
+
231
326
if len (volumes ) > 0 {
232
327
var volumeList []string
233
328
for volumeName := range volumes {
0 commit comments