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 " ", 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 }