diff options
author | Dan Duvall <dduvall@wikimedia.org> | 2017-05-09 09:54:09 -0700 |
---|---|---|
committer | Dan Duvall <dduvall@wikimedia.org> | 2017-05-10 20:41:09 -0700 |
commit | fb363428ed0e4bcbe7aee83f70a34981f4f092c0 (patch) | |
tree | 869706acf2182cd41c5d7bf4b4f98b1635086362 | |
parent | d0ada7682fa323c52940c99113a8809aecfae06b (diff) | |
download | blubber-fb363428ed0e4bcbe7aee83f70a34981f4f092c0.tar.gz |
Decouple Docker compiler from apt/npm providers
Establish phases within Docker compiler to allow providers (apt, npm,
etc.) to inject their own run/copy instructions into to the Dockerfile
compilation process while leaving the compiler agnostic to the providers
themselves.
The instructions and phases are also generalized to leave room for
alternative compilers should they be needed in the future (e.g. aci
support via acbuild) but also as a general design constraint to leave
compiler implementation concerns out of providers.
-rw-r--r-- | build/instructions.go | 13 | ||||
-rw-r--r-- | build/phases.go | 14 | ||||
-rw-r--r-- | config/apt.go | 25 | ||||
-rw-r--r-- | config/common.go | 19 | ||||
-rw-r--r-- | config/config.go | 4 | ||||
-rw-r--r-- | config/npm.go | 39 | ||||
-rw-r--r-- | config/runs.go | 40 | ||||
-rw-r--r-- | docker/compiler.go | 29 |
8 files changed, 131 insertions, 52 deletions
diff --git a/build/instructions.go b/build/instructions.go new file mode 100644 index 0000000..2676a75 --- /dev/null +++ b/build/instructions.go @@ -0,0 +1,13 @@ +package build + +type InstructionType int + +const ( + Run InstructionType = iota + Copy +) + +type Instruction struct { + Type InstructionType + Arguments []string +} diff --git a/build/phases.go b/build/phases.go new file mode 100644 index 0000000..095263c --- /dev/null +++ b/build/phases.go @@ -0,0 +1,14 @@ +package build + +type Phase int + +const ( + PhasePrivileged Phase = iota + PhasePrivilegeDropped + PhasePreInstall + PhasePostInstall +) + +type PhaseCompileable interface { + InstructionsForPhase(phase Phase) []Instruction +} diff --git a/config/apt.go b/config/apt.go index f9e631f..65dad9c 100644 --- a/config/apt.go +++ b/config/apt.go @@ -1,8 +1,8 @@ package config import ( - "bytes" "strings" + "github.com/marxarelli/blubber/build" ) type AptConfig struct { @@ -13,16 +13,19 @@ func (apt *AptConfig) Merge(apt2 AptConfig) { apt.Packages = append(apt.Packages, apt2.Packages...) } -func (apt AptConfig) Commands() []string { - if len(apt.Packages) < 1 { - return []string{} +func (apt AptConfig) InstructionsForPhase(phase build.Phase) []build.Instruction { + if len(apt.Packages) > 0 { + switch phase { + case build.PhasePrivileged: + return []build.Instruction{ + {build.Run, []string{ + "apt-get update && apt-get install -y ", + strings.Join(apt.Packages, " "), + " && rm -rf /var/lib/apt/lists/*", + }}, + } + } } - buffer := new(bytes.Buffer) - - buffer.WriteString("apt-get update && apt-get install -y ") - buffer.WriteString(strings.Join(apt.Packages, " ")) - buffer.WriteString(" && rm -rf /var/lib/apt/lists/*") - - return []string{buffer.String()} + return []build.Instruction{} } diff --git a/config/common.go b/config/common.go index c4dc2db..1073b18 100644 --- a/config/common.go +++ b/config/common.go @@ -1,5 +1,9 @@ package config +import ( + "github.com/marxarelli/blubber/build" +) + type CommonConfig struct { Base string `yaml:"base"` Apt AptConfig `yaml:"apt"` @@ -24,3 +28,18 @@ func (cc1 *CommonConfig) Merge(cc2 CommonConfig) { cc1.EntryPoint = cc2.EntryPoint } } + + +func (cc *CommonConfig) PhaseCompileableConfig() []build.PhaseCompileable { + return []build.PhaseCompileable{cc.Apt, cc.Npm, cc.Runs} +} + +func (cc *CommonConfig) InstructionsForPhase(phase build.Phase) []build.Instruction { + instructions := []build.Instruction{} + + for _, phaseCompileable := range cc.PhaseCompileableConfig() { + instructions = append(instructions, phaseCompileable.InstructionsForPhase(phase)...) + } + + return instructions +} diff --git a/config/config.go b/config/config.go index dcf1adb..ee38ea8 100644 --- a/config/config.go +++ b/config/config.go @@ -4,7 +4,3 @@ type Config struct { CommonConfig `yaml:",inline"` Variants map[string]VariantConfig `yaml:"variants"` } - -type CommandCompileable interface { - Commands() []string -} diff --git a/config/npm.go b/config/npm.go index c4a52c1..78307b6 100644 --- a/config/npm.go +++ b/config/npm.go @@ -2,8 +2,12 @@ package config import ( "bytes" + "path" + "github.com/marxarelli/blubber/build" ) +const TempNpmInstallDir = "/tmp/node-deps/" + type NpmConfig struct { Install bool `yaml:"install"` Env string `yaml:"env"` @@ -17,18 +21,29 @@ func (npm *NpmConfig) Merge(npm2 NpmConfig) { } } -func (npm NpmConfig) Commands() []string { - if !npm.Install { - return []string{} - } - - buffer := new(bytes.Buffer) - - buffer.WriteString("npm install") - - if npm.Env == "production" { - buffer.WriteString(" --production && npm dedupe") +func (npm NpmConfig) InstructionsForPhase(phase build.Phase) []build.Instruction{ + if npm.Install { + switch phase { + case build.PhasePreInstall: + npmCmd := new(bytes.Buffer) + + npmCmd.WriteString("npm install") + + if npm.Env == "production" { + npmCmd.WriteString(" --production && npm dedupe") + } + + return []build.Instruction{ + {build.Run, []string{"mkdir -p ", TempNpmInstallDir}}, + {build.Copy, []string{"package.json", TempNpmInstallDir}}, + {build.Run, []string{"cd ", TempNpmInstallDir, " && ", npmCmd.String()}}, + } + case build.PhasePostInstall: + return []build.Instruction{ + {build.Run, []string{"mv ", path.Join(TempNpmInstallDir, "node_modules"), " ./"}}, + } + } } - return []string{buffer.String()} + return []build.Instruction{} } diff --git a/config/runs.go b/config/runs.go index 70f03a9..1d1f63a 100644 --- a/config/runs.go +++ b/config/runs.go @@ -2,7 +2,7 @@ package config import ( "strconv" - "strings" + "github.com/marxarelli/blubber/build" ) type RunsConfig struct { @@ -19,26 +19,32 @@ func (run *RunsConfig) Merge(run2 RunsConfig) { if run2.Gid != 0 { run.Gid = run2.Gid } } -func (run RunsConfig) Commands() []string { - cmds := []string{} +func (run RunsConfig) InstructionsForPhase(phase build.Phase) []build.Instruction { + ins := []build.Instruction{} - if run.In != "" { - cmds = append(cmds, strings.Join([]string{"mkdir -p", run.In}, " ")) - } - - if run.As != "" { - cmd := []string{ - "groupadd -o -g", strconv.Itoa(run.Gid), "-r", run.As, "&&", - "useradd -o -m -r -g", run.As, "-u", strconv.Itoa(run.Uid), run.As, + switch phase { + case build.PhasePrivileged: + if run.In != "" { + ins = append(ins, []build.Instruction{{build.Run, []string{"mkdir -p ", run.In}}}...) } - cmds = append(cmds, strings.Join(cmd, " ")) - - if run.In != "" { - owner := strings.Join([]string{run.As, ":", run.As}, "") - cmds = append(cmds, strings.Join([]string{"chown", owner, run.In}, " ")) + if run.As != "" { + ins = append(ins, []build.Instruction{ + {build.Run, []string{ + "groupadd -o -g ", strconv.Itoa(run.Gid), " -r ", run.As, " && ", + "useradd -o -m -r -g ", run.As, " -u ", strconv.Itoa(run.Uid), " ", run.As, + }}, + }...) + + if run.In != "" { + ins = append(ins, []build.Instruction{ + {build.Run, []string{ + "chown ", run.As, ":", run.As, " ", run.In, + }}, + }...) + } } } - return cmds + return ins } diff --git a/docker/compiler.go b/docker/compiler.go index f5114c4..6b08355 100644 --- a/docker/compiler.go +++ b/docker/compiler.go @@ -3,6 +3,7 @@ package docker import ( "bytes" "strings" + "github.com/marxarelli/blubber/build" "github.com/marxarelli/blubber/config" ) @@ -33,26 +34,27 @@ func CompileStage(buffer *bytes.Buffer, stage string, vcfg *config.VariantConfig Writeln(buffer, "FROM ", vcfg.Base, " AS ", stage) Writeln(buffer, "USER root") - Writeln(buffer, "WORKDIR /srv") - CompileToCommands(buffer, vcfg.Apt) - CompileToCommands(buffer, vcfg.Runs) + + CompilePhase(buffer, vcfg, build.PhasePrivileged) if vcfg.Runs.As != "" { Writeln(buffer, "USER ", vcfg.Runs.As) } + CompilePhase(buffer, vcfg, build.PhasePrivilegeDropped) + if vcfg.Runs.In != "" { Writeln(buffer, "WORKDIR ", vcfg.Runs.In) } + CompilePhase(buffer, vcfg, build.PhasePreInstall) + if vcfg.SharedVolume { Writeln(buffer, "VOLUME [\"", vcfg.Runs.In, "\"]") } else { Writeln(buffer, "COPY . \"", vcfg.Runs.In, "\"") } - CompileToCommands(buffer, vcfg.Npm) - // Artifact copying for _, artifact := range vcfg.Artifacts { Write(buffer, "COPY ") @@ -64,14 +66,25 @@ func CompileStage(buffer *bytes.Buffer, stage string, vcfg *config.VariantConfig Writeln(buffer, artifact.Source, " ", artifact.Destination) } + CompilePhase(buffer, vcfg, build.PhasePostInstall) + if len(vcfg.EntryPoint) > 0 { Writeln(buffer, "ENTRYPOINT [\"", strings.Join(vcfg.EntryPoint, "\", \""), "\"]") } } -func CompileToCommands(buffer *bytes.Buffer, compileable config.CommandCompileable) { - for _, command := range compileable.Commands() { - Writeln(buffer, "RUN ", command) +func CompilePhase(buffer *bytes.Buffer, vcfg *config.VariantConfig, phase build.Phase) { + for _, instruction := range vcfg.InstructionsForPhase(phase) { + CompileInstruction(buffer, instruction) + } +} + +func CompileInstruction(buffer *bytes.Buffer, instruction build.Instruction) { + switch instruction.Type { + case build.Run: + Writeln(buffer, append([]string{"RUN "}, instruction.Arguments...)...) + case build.Copy: + Writeln(buffer, "COPY \"", instruction.Arguments[0], "\" \"", instruction.Arguments[1], "\"") } } |