Skip to content

Commit

Permalink
more build command report information about the target image
Browse files Browse the repository at this point in the history
  • Loading branch information
kcq committed Jul 21, 2019
1 parent f13a571 commit 835f296
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 64 deletions.
41 changes: 32 additions & 9 deletions internal/app/master/commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func OnBuild(

cmdReport := report.NewBuildCommand(cmdReportLocation)
cmdReport.State = report.CmdStateStarted
cmdReport.OriginalImage = imageRef
cmdReport.ImageReference = imageRef

fmt.Println("docker-slim[build]: state=started")
fmt.Printf("docker-slim[build]: info=params target=%v continue.mode=%v\n", imageRef, continueAfter.Mode)
Expand Down Expand Up @@ -118,10 +118,12 @@ func OnBuild(
strings.Join(imageInspector.DockerfileInfo.AllUsers, ","))
}

if len(imageInspector.DockerfileInfo.Layers) > 0 {
for idx, layerInfo := range imageInspector.DockerfileInfo.Layers {
fmt.Printf("docker-slim[build]: info=image.layers index=%v name='%v' tags='%v'\n",
idx, layerInfo.Name, strings.Join(layerInfo.Tags, ","))
if len(imageInspector.DockerfileInfo.ImageStack) > 0 {
cmdReport.ImageStack = imageInspector.DockerfileInfo.ImageStack

for idx, layerInfo := range imageInspector.DockerfileInfo.ImageStack {
fmt.Printf("docker-slim[build]: info=image.stack index=%v name='%v' id='%v'\n",
idx, layerInfo.FullName, layerInfo.ID)
}
}

Expand Down Expand Up @@ -282,15 +284,36 @@ func OnBuild(

if err == nil {
cmdReport.MinifiedBy = float64(imageInspector.ImageInfo.VirtualSize) / float64(newImageInspector.ImageInfo.VirtualSize)
cmdReport.OriginalImageSize = imageInspector.ImageInfo.VirtualSize
cmdReport.OriginalImageSizeHuman = humanize.Bytes(uint64(imageInspector.ImageInfo.VirtualSize))

cmdReport.SourceImage = report.ImageMetadata{
AllNames: imageInspector.ImageRecordInfo.RepoTags,
ID: imageInspector.ImageRecordInfo.ID,
Size: imageInspector.ImageInfo.VirtualSize,
SizeHuman: humanize.Bytes(uint64(imageInspector.ImageInfo.VirtualSize)),
CreateTime: imageInspector.ImageInfo.Created.UTC().Format(time.RFC3339),
Author: imageInspector.ImageInfo.Author,
DockerVersion: imageInspector.ImageInfo.DockerVersion,
Architecture: imageInspector.ImageInfo.Architecture,
User: imageInspector.ImageInfo.Config.User,
}

if len(imageInspector.ImageRecordInfo.RepoTags) > 0 {
cmdReport.SourceImage.Name = imageInspector.ImageRecordInfo.RepoTags[0]
}

if len(imageInspector.ImageInfo.Config.ExposedPorts) > 0 {
for k := range imageInspector.ImageInfo.Config.ExposedPorts {
cmdReport.SourceImage.ExposedPorts = append(cmdReport.SourceImage.ExposedPorts, string(k))
}
}

cmdReport.MinifiedImageSize = newImageInspector.ImageInfo.VirtualSize
cmdReport.MinifiedImageSizeHuman = humanize.Bytes(uint64(newImageInspector.ImageInfo.VirtualSize))

fmt.Printf("docker-slim[build]: info=results status='MINIFIED BY %.2fX [%v (%v) => %v (%v)]'\n",
cmdReport.MinifiedBy,
cmdReport.OriginalImageSize,
cmdReport.OriginalImageSizeHuman,
cmdReport.SourceImage.Size,
cmdReport.SourceImage.SizeHuman,
cmdReport.MinifiedImageSize,
cmdReport.MinifiedImageSizeHuman)
} else {
Expand Down
175 changes: 135 additions & 40 deletions internal/app/master/docker/dockerfile/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,53 @@ import (
"path/filepath"
"strconv"
"strings"
"time"

log "github.com/Sirupsen/logrus"
"github.com/cloudimmunity/go-dockerclientx"
)
"github.com/dustin/go-humanize"

type Layer struct {
Name string
Tags []string
}
v "github.com/docker-slim/docker-slim/pkg/version"
)

// Info represents the reverse engineered Dockerfile info
type Info struct {
Lines []string
AllUsers []string
ExeUser string
ExposedPorts []string
Layers []Layer
ImageStack []*ImageInfo
}

type ImageInfo struct {
IsTopImage bool `json:"is_top_image"`
ID string `json:"id"`
FullName string `json:"full_name"`
RepoName string `json:"repo_name"`
VersionTag string `json:"version_tag"`
RawTags []string `json:"raw_tags,omitempty"`
CreateTime string `json:"create_time"`
NewSize int64 `json:"new_size"`
NewSizeHuman string `json:"new_size_human"`
BaseImageID string `json:"base_image_id,omitempty"`
Instructions []*InstructionInfo `json:"instructions"`
}

type imageInst struct {
instCmd string
instComment string
instType string
instTime int64
layerImageID string
imageName string
shortTags []string
fullTags []string
type InstructionInfo struct {
Type string `json:"type"`
Time string `json:"time"`
IsNop bool `json:"is_nop"`
IsLocal bool `json:"is_local"`
IntermediateImageID string `json:"intermediate_image_id,omitempty"`
Size int64 `json:"size"`
SizeHuman string `json:"size_human,omitempty"`
CommandSnippet string `json:"command_snippet"`
command string
SystemCommands []string `json:"system_commands,omitempty"`
Comment string `json:"comment,omitempty"`
instPosition string
imageFullName string
RawTags []string `json:"raw_tags,omitempty"`
}

// ReverseDockerfileFromHistory recreates Dockerfile information from container image history
Expand All @@ -49,17 +68,26 @@ func ReverseDockerfileFromHistory(apiClient *docker.Client, imageID string) (*In

log.Debugf("\n\nIMAGE HISTORY =>\n%#v\n\n", imageHistory)

var fatImageDockerInstructions []imageInst
var fatImageDockerInstructions []InstructionInfo
var currentImageInfo *ImageInfo
var prevImageID string

imageLayerCount := len(imageHistory)
imageLayerStart := imageLayerCount - 1
startNewImage := true
if imageLayerCount > 0 {
for idx := imageLayerStart; idx >= 0; idx-- {
isNop := false

nopPrefix := "/bin/sh -c #(nop) "
execPrefix := "/bin/sh -c "
rawLine := imageHistory[idx].CreatedBy
var inst string

if strings.Contains(rawLine, "(nop)") {
isNop = true
}

switch {
case len(rawLine) == 0:
inst = "FROM scratch"
Expand Down Expand Up @@ -116,60 +144,124 @@ func ReverseDockerfileFromHistory(apiClient *docker.Client, imageID string) (*In
}
}

instInfo := imageInst{
instCmd: inst,
instTime: imageHistory[idx].Created,
layerImageID: imageHistory[idx].ID,
instComment: imageHistory[idx].Comment,
instInfo := InstructionInfo{
IsNop: isNop,
command: cleanInst,
Time: time.Unix(imageHistory[idx].Created, 0).UTC().Format(time.RFC3339),
Comment: imageHistory[idx].Comment,
RawTags: imageHistory[idx].Tags,
Size: imageHistory[idx].Size,
}

instParts := strings.SplitN(cleanInst, " ", 2)
if len(instParts) == 2 {
instInfo.Type = instParts[0]
}

if instInfo.Type == "RUN" {
var cmdParts []string
cmds := strings.Replace(instParts[1], "\\", "", -1)
if strings.Contains(cmds, "&&") {
cmdParts = strings.Split(cmds, "&&")
} else {
cmdParts = strings.Split(cmds, ";")
}

for _, cmd := range cmdParts {
cmd = strings.TrimSpace(cmd)
cmd = strings.Replace(cmd, "\t", "", -1)
cmd = strings.Replace(cmd, "\n", "", -1)
instInfo.SystemCommands = append(instInfo.SystemCommands, cmd)
}
}

if instInfo.Type == "WORKDIR" {
instInfo.SystemCommands = append(instInfo.SystemCommands, fmt.Sprintf("mkdir -p %s", instParts[1]))
}

if len(instInfo.command) > 44 {
instInfo.CommandSnippet = fmt.Sprintf("%s...", instInfo.command[0:44])
} else {
instInfo.CommandSnippet = instInfo.command
}

if instInfo.Size > 0 {
instInfo.SizeHuman = humanize.Bytes(uint64(instInfo.Size))
}

if imageHistory[idx].ID != "<missing>" {
instInfo.IsLocal = true
instInfo.IntermediateImageID = imageHistory[idx].ID
}

instType := "intermediate"
if startNewImage {
startNewImage = false
currentImageInfo = &ImageInfo{
BaseImageID: prevImageID,
NewSize: 0,
}
}

currentImageInfo.NewSize += imageHistory[idx].Size
currentImageInfo.Instructions = append(currentImageInfo.Instructions, &instInfo)

instPosition := "intermediate"
if idx == imageLayerStart {
instType = "first"
instPosition = "first" //first instruction in the list
}

if len(imageHistory[idx].Tags) > 0 {
instType = "last"
instPosition = "last" //last in an image

if tagInfo := strings.Split(imageHistory[idx].Tags[0], ":"); len(tagInfo) > 1 {
instInfo.imageName = tagInfo[0]
}
currentImageInfo.ID = imageHistory[idx].ID
prevImageID = currentImageInfo.ID

instInfo.fullTags = imageHistory[idx].Tags
currentImageInfo.CreateTime = instInfo.Time
currentImageInfo.RawTags = imageHistory[idx].Tags

for _, fullTag := range instInfo.fullTags {
if tagInfo := strings.Split(fullTag, ":"); len(tagInfo) > 1 {
instInfo.shortTags = append(instInfo.shortTags, tagInfo[1])
}
instInfo.imageFullName = imageHistory[idx].Tags[0]
currentImageInfo.FullName = imageHistory[idx].Tags[0]

if tagInfo := strings.Split(imageHistory[idx].Tags[0], ":"); len(tagInfo) > 1 {
currentImageInfo.RepoName = tagInfo[0]
currentImageInfo.VersionTag = tagInfo[1]
}

out.Layers = append(out.Layers, Layer{Name: instInfo.imageName, Tags: instInfo.shortTags})
currentImageInfo.NewSizeHuman = humanize.Bytes(uint64(currentImageInfo.NewSize))

out.ImageStack = append(out.ImageStack, currentImageInfo)
startNewImage = true
}

instInfo.instType = instType
instInfo.instPosition = instPosition

fatImageDockerInstructions = append(fatImageDockerInstructions, instInfo)
}

if currentImageInfo != nil {
currentImageInfo.IsTopImage = true
}
}

for idx, instInfo := range fatImageDockerInstructions {
if instInfo.instType == "first" {
if instInfo.instPosition == "first" {
out.Lines = append(out.Lines, "# new image")
}

out.Lines = append(out.Lines, instInfo.instCmd)
if instInfo.instType == "last" {
out.Lines = append(out.Lines, instInfo.command)
if instInfo.instPosition == "last" {
commentText := fmt.Sprintf("# end of image: %s (id: %s tags: %s)",
instInfo.imageName, instInfo.layerImageID, strings.Join(instInfo.shortTags, ","))
instInfo.imageFullName, instInfo.IntermediateImageID, strings.Join(instInfo.RawTags, ","))

out.Lines = append(out.Lines, commentText)
out.Lines = append(out.Lines, "")
if idx < (len(fatImageDockerInstructions) - 1) {
out.Lines = append(out.Lines, "# new image")
}
}

if instInfo.instComment != "" {
out.Lines = append(out.Lines, "# "+instInfo.instComment)
if instInfo.Comment != "" {
out.Lines = append(out.Lines, "# "+instInfo.Comment)
}

//TODO: use time diff to separate each instruction
Expand Down Expand Up @@ -228,6 +320,9 @@ func GenerateFromInfo(location string,
var dfData bytes.Buffer
dfData.WriteString("FROM scratch\n")

dsInfoLabel := fmt.Sprintf("LABEL docker-slim.version=\"%s\"\n", v.Current())
dfData.WriteString(dsInfoLabel)

if len(volumes) > 0 {
var volumeList []string
for volumeName := range volumes {
Expand Down
Loading

0 comments on commit 835f296

Please sign in to comment.