Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(reverse): dockerfilefromhistory default processor #472

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 130 additions & 66 deletions pkg/docker/dockerfile/reverse/reverse.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,72 +264,7 @@ func DockerfileFromHistory(apiClient *docker.Client, imageID string) (*Dockerfil
inst = instPrefixRun + runData
}
default:
//TODO: need to refactor
processed := false
//rawInst := rawLine
if strings.HasPrefix(rawInst, runInstArgsPrefix) {
parts := strings.SplitN(rawInst, " ", 2)
if len(parts) == 2 {
withArgs := strings.TrimSpace(parts[1])
argNumStr := parts[0][1:]
argNum, err := strconv.Atoi(argNumStr)
if err == nil {
if withArgsArray, err := shlex.Split(withArgs); err == nil {
if len(withArgsArray) > argNum {
rawInstParts := withArgsArray[argNum:]
processed = true
if len(rawInstParts) > 2 &&
rawInstParts[0] == defaultRunInstShell &&
rawInstParts[1] == "-c" {
isExecForm = false
rawInstParts = rawInstParts[2:]

inst = fmt.Sprintf("RUN %s", strings.Join(rawInstParts, " "))
inst = strings.TrimSpace(inst)
} else {
isExecForm = true

var outJson bytes.Buffer
encoder := json.NewEncoder(&outJson)
encoder.SetEscapeHTML(false)
err := encoder.Encode(rawInstParts)
if err == nil {
inst = fmt.Sprintf("RUN %s", outJson.String())
}
}
} else {
log.Infof("ReverseDockerfileFromHistory - RUN with ARGs - malformed - %v (%v)", rawInst, err)
}
} else {
log.Infof("ReverseDockerfileFromHistory - RUN with ARGs - malformed - %v (%v)", rawInst, err)
}

} else {
log.Infof("ReverseDockerfileFromHistory - RUN with ARGs - malformed number of ARGs - %v (%v)", rawInst, err)
}
} else {
log.Infof("ReverseDockerfileFromHistory - RUN with ARGs - unexpected number of parts - %v", rawInst)
}
}
//todo: RUN inst with ARGS for buildkit
if hasInstructionPrefix(rawInst) {
inst = rawInst
} else {
if !processed {
//default to RUN instruction in exec form
isExecForm = true
inst = instPrefixRun + rawInst
if outArray, err := shlex.Split(rawInst); err == nil {
var outJson bytes.Buffer
encoder := json.NewEncoder(&outJson)
encoder.SetEscapeHTML(false)
err := encoder.Encode(outArray)
if err == nil {
inst = fmt.Sprintf("RUN %s", outJson.String())
}
}
}
}
inst, isExecForm = processRawInst(rawInst)
}

//NOTE: Dockerfile instructions can be any case, but the instructions from history are always uppercase
Expand Down Expand Up @@ -645,6 +580,135 @@ func DockerfileFromHistory(apiClient *docker.Client, imageID string) (*Dockerfil
*/
}

// processRawInst handles the processing for non-standard instructions that
// are otherwise uncaught by the main processor. It returns both the processed
// instruction as well as its status as an exec format command.
func processRawInst(rawInst string) (string, bool) {
// Try to process as an uncaught standard instruction
inst, ief, err = processAsUncaughtInst(rawInst)
if err == nil {
return inst, ief
}

// Failing that, try to process as an args format RUN instruction
inst, ief, err := processAsArgsFormat(rawInst)
if err == nil {
return inst, ief
}

// Fallback: process as an exec format RUN instruction
return processAsExecRun(rawInst)
}

// processAsArgsFormat attempts to process the given instruction as an
// args style RUN, resulting in an encoded RUN string, a boolean that
// reflects if the output is in exec format, and an error.
//
// Provided there are no issues along the way, the error will be nil.
//
// If the resulting error is non-nil, the other returned values should
// not be used.
func processAsArgsFormat(rawInst string) (string, bool, error) {
if !strings.HasPrefix(rawInst, runInstArgsPrefix) {
return "", false, errors.New("not inst args format")
}

var inst string
var isExecForm bool

parts := strings.SplitN(rawInst, " ", 2)

if len(parts) != 2 {
log.Infof("ReverseDockerfileFromHistory - RUN with ARGs - unexpected number of parts - %v", rawInst)
return "", false, errors.New("inst args format - unexpected number of parts")
}

withArgs := strings.TrimSpace(parts[1])
argNumStr := parts[0][1:]
argNum, err := strconv.Atoi(argNumStr)
if err != nil {
log.Infof("ReverseDockerfileFromHistory - RUN with ARGs - malformed number of ARGs - %v (%v)", rawInst, err)
return "", false, err
}

withArgsArray, err := shlex.Split(withArgs)
if err != nil {
log.Infof("ReverseDockerfileFromHistory - RUN with ARGs - malformed - %v (%v)", rawInst, err)
return "", false, err
}

if len(withArgsArray) <= argNum {
log.Infof("ReverseDockerfileFromHistory - RUN with ARGs - malformed - %v (%v)", rawInst, err)
return "", false, errors.New("inst args format - not enough args")
}

rawInstParts := withArgsArray[argNum:]

if detectRawShellForm(rawInstParts) {
isExecForm = false
rawInstParts = rawInstParts[2:]

inst = fmt.Sprintf("RUN %s", strings.Join(rawInstParts, " "))
inst = strings.TrimSpace(inst)
} else {
isExecForm = true

exec, err := execify(rawInstParts)
if err == nil {
inst = exec
}
}

return inst, isExecForm, nil
}

// processAsUncaughtInst attempts to process the given instruction as an
// otherwise uncaught standard instruction. If the instruction is determined
// not to be an uncaught instruction, an error is returned.
func processAsUncaughtInst(rawInst string) (string, bool, error) {
if hasInstructionPrefix(rawInst) {
return rawInst, false, nil
}

return "", false, errors.New("not an uncaught instruction")
}

// processAsExecRun recieves a raw instruction, implicitly encodes it as
// a RUN instruction, and returns that encoded instruction in exec format
func processAsExecRun(rawInst string) (string, bool) {
inst := instPrefixRun + rawInst

if outArray, err := shlex.Split(rawInst); err == nil {
if exec, eErr := execify(outArray); eErr == nil {
inst = exec
}
}

return inst, true
}

// execify encodes an array of strings as an exec format RUN instruction
func execify(parts []string) (string, error) {
var outJson bytes.Buffer

encoder := json.NewEncoder(&outJson)
encoder.SetEscapeHTML(false)
err := encoder.Encode(parts)
if err != nil {
return "", err
}

return fmt.Sprintf("RUN %s", outJson.String()), nil
}

// detectRawShellForm determines if an array of strings can be treated as
// the arguments for a shell format RUN instruction
func detectRawShellForm(parts []string) bool {
return len(parts) > 2 &&
parts[0] == defaultRunInstShell &&
parts[1] == "-c"
}

// SaveDockerfileData saves the Dockerfile information to a file
func SaveDockerfileData(fatImageDockerfileLocation string, fatImageDockerfileLines []string) error {
var data bytes.Buffer
Expand Down
Loading