package docker
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"gerrit.wikimedia.org/r/blubber/build"
|
|
)
|
|
|
|
// NewInstruction takes a general internal build.Instruction and returns
|
|
// a corresponding compilable Docker specific instruction. The given internal
|
|
// instruction is partially compiled at this point by calling Compile() which
|
|
// applies its own logic for escaping arguments, etc.
|
|
//
|
|
func NewInstruction(bi build.Instruction) (Instruction, error) {
|
|
i := instruction{arguments: bi.Compile()}
|
|
|
|
switch bi.(type) {
|
|
case build.Run, build.RunAll:
|
|
i.name = "RUN"
|
|
|
|
case build.Copy, build.CopyAs, build.CopyFrom:
|
|
i.name = "COPY"
|
|
i.array = true
|
|
|
|
switch bi.(type) {
|
|
case build.CopyAs:
|
|
switch bi.(build.CopyAs).Instruction.(type) {
|
|
case build.Copy:
|
|
i.flags = []string{"chown"}
|
|
case build.CopyFrom:
|
|
i.flags = []string{"chown", "from"}
|
|
}
|
|
case build.CopyFrom:
|
|
i.flags = []string{"from"}
|
|
}
|
|
|
|
case build.EntryPoint:
|
|
i.name = "ENTRYPOINT"
|
|
i.array = true
|
|
|
|
case build.Env:
|
|
i.name = "ENV"
|
|
i.separator = " "
|
|
|
|
case build.Label:
|
|
i.name = "LABEL"
|
|
i.separator = " "
|
|
|
|
case build.User:
|
|
i.name = "USER"
|
|
|
|
case build.WorkingDirectory:
|
|
i.name = "WORKDIR"
|
|
}
|
|
|
|
if i.name == "" {
|
|
return nil, errors.New("Unable to create Instruction")
|
|
}
|
|
|
|
return i, nil
|
|
}
|
|
|
|
// Instruction defines an interface for instruction compilation.
|
|
//
|
|
type Instruction interface {
|
|
Compile() string
|
|
}
|
|
|
|
type instruction struct {
|
|
name string // name (e.g. "RUN")
|
|
flags []string // flags (e.g. "chown")
|
|
arguments []string // quoted arguments
|
|
separator string // argument separator
|
|
array bool // format arguments as array (enforces ", " separator)
|
|
}
|
|
|
|
// Compile returns a valid Dockerfile line for the instruction.
|
|
//
|
|
// Output is in the format "<name> <flags> <arguments>", e.g.
|
|
// "COPY --chown=123:223 ["foo", "bar"]" and flag values are taken from the
|
|
// beginning of the arguments slice.
|
|
//
|
|
func (ins instruction) Compile() string {
|
|
format := ins.name + " "
|
|
numFlags := len(ins.flags)
|
|
args := make([]interface{}, numFlags+1)
|
|
|
|
for i, option := range ins.flags {
|
|
format += "--" + option + "=%s "
|
|
args[i] = ins.arguments[i]
|
|
}
|
|
|
|
separator := ins.separator
|
|
|
|
if ins.array {
|
|
separator = ", "
|
|
format += "[%s]"
|
|
} else {
|
|
format += "%s"
|
|
}
|
|
|
|
format += "\n"
|
|
args[numFlags] = join(ins.arguments[numFlags:], separator)
|
|
|
|
return fmt.Sprintf(format, args...)
|
|
}
|
|
|
|
func join(arguments []string, delimiter string) string {
|
|
return removeNewlines(strings.Join(arguments, delimiter))
|
|
}
|
|
|
|
func removeNewlines(instructions string) string {
|
|
out := strings.Replace(instructions, "\n", "\\n", -1)
|
|
return out
|
|
}
|