summaryrefslogtreecommitdiff
path: root/docker/instructions.go
blob: 8f5c63c432bda9bfa14db3503112258422372090 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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.Volume:
		i.name = "VOLUME"
		i.array = true

	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
}