diff options
author | Dan Duvall <dduvall@wikimedia.org> | 2017-10-24 18:04:47 -0700 |
---|---|---|
committer | Dan Duvall <dduvall@wikimedia.org> | 2017-11-06 13:25:20 -0800 |
commit | 515d9f26eb616a9c3a0b70ba6eca88570a15b0ef (patch) | |
tree | 0f8d3b0a6381f4ea2c43ce063f6af9990a2abb2f | |
parent | 5d4cc80edf4fdba60069a7ef1e450117195fb987 (diff) | |
download | blubber-515d9f26eb616a9c3a0b70ba6eca88570a15b0ef.tar.gz |
Documented all exported types, functions, and interfaces
Summary:
Wrote inline documentation for all the things.
Fixes T168000
Test Plan: Execute `godoc -http=:6060` and proofread all the things.
Reviewers: thcipriani, hashar, #release-engineering-team, demon
Reviewed By: thcipriani, #release-engineering-team, demon
Tags: #release-engineering-team
Maniphest Tasks: T168000
Differential Revision: https://phabricator.wikimedia.org/D841
-rw-r--r-- | build/instructions.go | 72 | ||||
-rw-r--r-- | build/phases.go | 19 | ||||
-rw-r--r-- | config/apt.go | 13 | ||||
-rw-r--r-- | config/artifacts.go | 24 | ||||
-rw-r--r-- | config/common.go | 24 | ||||
-rw-r--r-- | config/config.go | 6 | ||||
-rw-r--r-- | config/flag.go | 9 | ||||
-rw-r--r-- | config/node.go | 31 | ||||
-rw-r--r-- | config/reader.go | 6 | ||||
-rw-r--r-- | config/runs.go | 37 | ||||
-rw-r--r-- | config/variant.go | 24 | ||||
-rw-r--r-- | docker/compiler.go | 9 | ||||
-rw-r--r-- | docker/instructions.go | 34 | ||||
-rw-r--r-- | main.go | 2 |
14 files changed, 271 insertions, 39 deletions
diff --git a/build/instructions.go b/build/instructions.go index 41c959d..3035b9f 100644 --- a/build/instructions.go +++ b/build/instructions.go @@ -1,3 +1,7 @@ +// Package build defines types and interfaces that could potentially be +// compiled to various external build-tool scripts but share a general +// internal abstraction and rules for escaping. +// package build import ( @@ -7,15 +11,28 @@ import ( "strings" ) +// Instruction defines a common interface that all concrete build types must +// implement. +// type Instruction interface { Compile() []string } +// Run is a concrete build instruction for passing any number of arguments to +// a shell command. +// +// The command string may contain inner argument placeholders using the "%s" +// format verb and will be appended with the quoted values of any arguments +// that remain after interpolation of the command string. +// type Run struct { - Command string - Arguments []string + Command string // command string (e.g. "useradd -d %s -u %s") + Arguments []string // command arguments both inner and final (e.g. ["/home/user", "123", "user"]) } +// Compile quotes all arguments, interpolates the command string with inner +// arguments, and appends the final arguments. +// func (run Run) Compile() []string { numInnerArgs := strings.Count(run.Command, `%`) - strings.Count(run.Command, `%%`) command := sprintf(run.Command, run.Arguments[0:numInnerArgs]) @@ -27,10 +44,16 @@ func (run Run) Compile() []string { return []string{command} } +// RunAll is a concrete build instruction for declaring multiple Run +// instructions that will be executed together in a `cmd1 && cmd2` chain. +// type RunAll struct { - Runs []Run + Runs []Run // multiple Run instructions to be executed together } +// Compile concatenates all individually compiled Run instructions into a +// single command. +// func (runAll RunAll) Compile() []string { commands := make([]string, len(runAll.Runs)) @@ -41,47 +64,72 @@ func (runAll RunAll) Compile() []string { return []string{strings.Join(commands, " && ")} } +// Copy is a concrete build instruction for copying source files/directories +// from the build host into the image. +// type Copy struct { - Sources []string - Destination string + Sources []string // source file/directory paths + Destination string // destination path } +// Compile quotes the defined source files/directories and destination. +// func (copy Copy) Compile() []string { return append(quoteAll(copy.Sources), quote(copy.Destination)) } +// CopyFrom is a concrete build instruction for copying source +// files/directories from one variant image to another. +// type CopyFrom struct { - From string + From string // source variant name Copy } +// Compile returns the variant name unquoted and all quoted Copy instruction +// fields. +// func (cf CopyFrom) Compile() []string { return append([]string{cf.From}, cf.Copy.Compile()...) } +// Env is a concrete build instruction for declaring a container's runtime +// environment variables. +// type Env struct { - Definitions map[string]string + Definitions map[string]string // number of key/value pairs } +// Compile returns the key/value pairs as a number of `key="value"` strings +// where the values are properly quoted and the slice is ordered by the keys. +// func (env Env) Compile() []string { return compileSortedKeyValues(env.Definitions) } -// Label represents a number of meta-data key/value pairs +// Label is a concrete build instruction for declaring a number of meta-data +// key/value pairs to be included in the image. +// type Label struct { - Definitions map[string]string + Definitions map[string]string // number of meta-data key/value pairs } -// Compile returns the label key/value pairs as a number of `key="value"` -// strings where the values are properly quoted +// Compile returns the key/value pairs as a number of `key="value"` strings +// where the values are properly quoted and the slice is ordered by the keys. +// func (label Label) Compile() []string { return compileSortedKeyValues(label.Definitions) } +// Volume is a concrete build instruction for defining a volume mount point +// within the container. +// type Volume struct { - Path string + Path string // volume/mount path } +// Compile returns the quoted volume path. +// func (vol Volume) Compile() []string { return []string{quote(vol.Path)} } diff --git a/build/phases.go b/build/phases.go index 02e84e4..3480927 100644 --- a/build/phases.go +++ b/build/phases.go @@ -1,15 +1,24 @@ package build +// Phase enum type type Phase int +// Distinct build phases that each compiler implementation should pass to +// PhaseCompileable configuration (in the order they are defined here) to +// allow for dependency injection during compilation. +// const ( - PhasePrivileged Phase = iota - PhasePrivilegeDropped - PhasePreInstall - PhaseInstall - PhasePostInstall + PhasePrivileged Phase = iota // first, copies/execution done as root + PhasePrivilegeDropped // second, copies/execution done as unprivileged user from here on + PhasePreInstall // third, before application files and artifacts are copied + PhaseInstall // fourth, application files and artifacts are copied + PhasePostInstall // fifth, after application files and artifacts are copied ) +// PhaseCompileable defines and interface that all configuration types should +// implement if they want to inject build instructions into any of the defined +// build phases. +// type PhaseCompileable interface { InstructionsForPhase(phase Phase) []Instruction } diff --git a/config/apt.go b/config/apt.go index e1f8f53..3a7a688 100644 --- a/config/apt.go +++ b/config/apt.go @@ -4,14 +4,27 @@ import ( "phabricator.wikimedia.org/source/blubber/build" ) +// AptConfig represents configuration pertaining to package installation from +// existing APT sources. +// type AptConfig struct { Packages []string `yaml:"packages"` } +// Merge takes another AptConfig and combines the packages declared within +// with the packages of this AptConfig. +// func (apt *AptConfig) Merge(apt2 AptConfig) { apt.Packages = append(apt.Packages, apt2.Packages...) } +// InstructionsForPhase injects build instructions that will install the +// declared packages during the privileged phase. +// +// PhasePrivileged +// +// Updates the APT cache, installs configured packages, and cleans up. +// func (apt AptConfig) InstructionsForPhase(phase build.Phase) []build.Instruction { if len(apt.Packages) > 0 { switch phase { diff --git a/config/artifacts.go b/config/artifacts.go index 654f10f..bd1bdeb 100644 --- a/config/artifacts.go +++ b/config/artifacts.go @@ -4,12 +4,30 @@ import ( "phabricator.wikimedia.org/source/blubber/build" ) +// ArtifactsConfig declares files and directories to be copied from one +// variant's container to another during the build. +// +// The most common use of such "multi-stage" builds is to compile and test +// software using one variant image that contains a comprehensive set of +// development dependencies, and copy the software binaries or production only +// source files over into a smaller image that contains only production +// dependencies. For a shorthand configuration of this precise pattern, use +// VariantConfig.Copies. +// type ArtifactsConfig struct { - From string `yaml:"from"` - Source string `yaml:"source"` - Destination string `yaml:"destination"` + From string `yaml:"from"` // source variant from which to copy + Source string `yaml:"source"` // source path within variant from which to copy + Destination string `yaml:"destination"` // destination path within current variant } +// InstructionsForPhase injects instructions into the given build phase that +// copy configured artifacts. +// +// PhasePostInstall +// +// Injects build.CopyFrom instructions for the configured source and +// destination paths. +// func (ac ArtifactsConfig) InstructionsForPhase(phase build.Phase) []build.Instruction { switch phase { case build.PhasePostInstall: diff --git a/config/common.go b/config/common.go index dae0013..11357d8 100644 --- a/config/common.go +++ b/config/common.go @@ -4,15 +4,20 @@ import ( "phabricator.wikimedia.org/source/blubber/build" ) +// CommonConfig holds the configuration fields common to both the root config +// and each configured variant. +// type CommonConfig struct { - Base string `yaml:"base"` - Apt AptConfig `yaml:"apt"` - Node NodeConfig `yaml:"node"` - Runs RunsConfig `yaml:"runs"` - SharedVolume Flag `yaml:"sharedvolume"` - EntryPoint []string `yaml:"entrypoint"` + Base string `yaml:"base"` // name/path to base image + Apt AptConfig `yaml:"apt"` // APT related configuration + Node NodeConfig `yaml:"node"` // Node related configuration + Runs RunsConfig `yaml:"runs"` // runtime environment configuration + SharedVolume Flag `yaml:"sharedvolume"` // define a volume instead of copying in source files + EntryPoint []string `yaml:"entrypoint"` // entry-point executable } +// Merge takes another CommonConfig and merges its fields this one's. +// func (cc1 *CommonConfig) Merge(cc2 CommonConfig) { if cc2.Base != "" { cc1.Base = cc2.Base @@ -28,10 +33,17 @@ func (cc1 *CommonConfig) Merge(cc2 CommonConfig) { } } +// PhaseCompileableConfig returns all fields that implement +// build.PhaseCompileable in the order that their instructions should be +// injected. +// func (cc *CommonConfig) PhaseCompileableConfig() []build.PhaseCompileable { return []build.PhaseCompileable{cc.Apt, cc.Node, cc.Runs} } +// InstructionsForPhase injects instructions into the given build phase for +// each member field that supports it. +// func (cc *CommonConfig) InstructionsForPhase(phase build.Phase) []build.Instruction { instructions := []build.Instruction{} diff --git a/config/config.go b/config/config.go index 6f4283f..3360012 100644 --- a/config/config.go +++ b/config/config.go @@ -1,5 +1,11 @@ +// Package config provides the internal representation of Blubber +// configuration parsed from YAML. Each configuration type may implement +// its own hooks for injecting build instructions into the compiler. +// package config +// Config holds the root fields of a Blubber configuration. +// type Config struct { CommonConfig `yaml:",inline"` Variants map[string]VariantConfig `yaml:"variants"` diff --git a/config/flag.go b/config/flag.go index b841819..6fb9e72 100644 --- a/config/flag.go +++ b/config/flag.go @@ -1,10 +1,16 @@ package config +// Flag represents a nullable boolean value that is considered null until +// either parsed from YAML or merged in from another Flag value. +// type Flag struct { True bool set bool } +// UnmarshalYAML implements yaml.Unmarshaler to parse the underlying boolean +// value and detect that the Flag should no longer be considered null. +// func (flag *Flag) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := unmarshal(&flag.True); err != nil { return err @@ -15,6 +21,9 @@ func (flag *Flag) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } +// Merge takes another flag and, if set, merged its boolean value into this +// one. +// func (flag *Flag) Merge(flag2 Flag) { if flag2.set { flag.True = flag2.True diff --git a/config/node.go b/config/node.go index 90859e8..561c915 100644 --- a/config/node.go +++ b/config/node.go @@ -5,11 +5,17 @@ import ( "phabricator.wikimedia.org/source/blubber/build" ) +// NodeConfig holds configuration fields related to the Node environment and +// whether/how to install NPM packages. +// type NodeConfig struct { - Dependencies Flag `yaml:"dependencies"` - Env string `yaml:"env"` + Dependencies Flag `yaml:"dependencies"` // install dependencies declared in package.json + Env string `yaml:"env"` // environment name ("production" install) } +// Merge takes another NodeConfig and merges its fields into this one's, +// overwriting both the environment and dependencies flag. +// func (nc *NodeConfig) Merge(nc2 NodeConfig) { nc.Dependencies.Merge(nc2.Dependencies) @@ -18,6 +24,27 @@ func (nc *NodeConfig) Merge(nc2 NodeConfig) { } } +// InstructionsForPhase injects instructions into the build related to Node +// dependency installation and setting of the NODE_ENV, NODE_PATH, and PATH +// environment variables. +// +// PhasePreInstall +// +// Installs Node package dependencies declared in package.json into the shared +// library directory (/opt/lib). Only production related packages are install +// if NodeConfig.Env is set to "production" in which case `npm dedupe` is also +// run. Installing dependencies during the build.PhasePreInstall phase allows +// a compiler implementation (e.g. Docker) to produce cache-efficient output +// so only changes to package.json will invalidate these steps of the image +// build. +// +// PhasePostInstall +// +// Injects build.Env instructions for NODE_ENV, NODE_PATH, and PATH, setting +// the environment according to the configuration, ensuring that packages +// installed during build.PhasePreInstall are found by Node, and that any +// installed binaries are found by shells. +// func (nc NodeConfig) InstructionsForPhase(phase build.Phase) []build.Instruction { switch phase { case build.PhasePreInstall: diff --git a/config/reader.go b/config/reader.go index 0f4cc12..7aa060d 100644 --- a/config/reader.go +++ b/config/reader.go @@ -35,6 +35,7 @@ func expandIncludes(config *Config, name string, included map[string]bool) ([]st // ExpandVariant merges a named variant with a config. It also attempts to // recursively expand any included variants in the expanded variant. +// func ExpandVariant(config *Config, name string) (*VariantConfig, error) { expanded := new(VariantConfig) expanded.CommonConfig.Merge(config.CommonConfig) @@ -53,6 +54,8 @@ func ExpandVariant(config *Config, name string) (*VariantConfig, error) { return expanded, nil } +// ReadConfig unmarshals the given YAML bytes into a Config struct. +// func ReadConfig(data []byte) (*Config, error) { var config Config @@ -61,6 +64,9 @@ func ReadConfig(data []byte) (*Config, error) { return &config, err } +// ReadConfigFile unmarshals the given YAML file contents into a Config +// struct. +// func ReadConfigFile(path string) (*Config, error) { data, err := ioutil.ReadFile(path) diff --git a/config/runs.go b/config/runs.go index 6a176c3..ef9f4ea 100644 --- a/config/runs.go +++ b/config/runs.go @@ -6,16 +6,26 @@ import ( "phabricator.wikimedia.org/source/blubber/build" ) +// LocalLibPrefix declares the shared directory into which application level +// dependencies will be installed. +// const LocalLibPrefix = "/opt/lib" +// RunsConfig holds configuration fields related to the application's +// runtime environment. +// type RunsConfig struct { - In string `yaml:"in"` - As string `yaml:"as"` - Uid int `yaml:"uid"` - Gid int `yaml:"gid"` - Environment map[string]string `yaml:"environment"` + In string `yaml:"in"` // working directory + As string `yaml:"as"` // unprivileged user + Uid int `yaml:"uid"` // unprivileged UID + Gid int `yaml:"gid"` // unprivileged GID + Environment map[string]string `yaml:"environment"` // environment variables } +// Merge takes another RunsConfig and overwrites this struct's fields. All +// fields except Environment are overwritten if set. The latter is an additive +// merge. +// func (run *RunsConfig) Merge(run2 RunsConfig) { if run2.In != "" { run.In = run2.In @@ -39,6 +49,9 @@ func (run *RunsConfig) Merge(run2 RunsConfig) { } } +// Home returns the home directory for the configured user, or /root if no +// user is set. +// func (run RunsConfig) Home() string { if run.As == "" { return "/root" @@ -47,6 +60,20 @@ func (run RunsConfig) Home() string { } } +// InstructionsForPhase injects build instructions related to the runtime +// configuration. +// +// PhasePrivileged +// +// Creates LocalLibPrefix directory and unprivileged user home directory, +// creates the unprivileged user and its group, and sets up directory +// permissions. +// +// PhasePrivilegeDropped +// +// Injects build.Env instructions for the user home directory and all +// names/values defined by RunsConfig.Environment. +// func (run RunsConfig) InstructionsForPhase(phase build.Phase) []build.Instruction { ins := []build.Instruction{} diff --git a/config/variant.go b/config/variant.go index 8e53b70..60fa325 100644 --- a/config/variant.go +++ b/config/variant.go @@ -4,19 +4,34 @@ import ( "phabricator.wikimedia.org/source/blubber/build" ) +// VariantConfig holds configuration fields for each defined build variant. +// type VariantConfig struct { - Includes []string `yaml:"includes"` - Copies string `yaml:"copies"` - Artifacts []ArtifactsConfig `yaml:"artifacts"` + Includes []string `yaml:"includes"` // references to one or more + Copies string `yaml:"copies"` // copy standard artifacts from another variant + Artifacts []ArtifactsConfig `yaml:"artifacts"` // non-standard artifact configuration CommonConfig `yaml:",inline"` } +// Merge takes another VariantConfig and overwrites this struct's fields. +// Artifacts are merged additively. +// func (vc *VariantConfig) Merge(vc2 VariantConfig) { vc.Copies = vc2.Copies vc.Artifacts = append(vc.Artifacts, vc2.Artifacts...) vc.CommonConfig.Merge(vc2.CommonConfig) } +// InstructionsForPhase injects build instructions related to artifact +// copying, volume definition or copying of application files, and all common +// configuration. +// +// PhaseInstall +// +// If VariantConfig.Copies is not set, either copy in application files or +// define a shared volume. Otherwise, delegate to +// ArtifactsConfig.InstructionsForPhase. +// func (vc *VariantConfig) InstructionsForPhase(phase build.Phase) []build.Instruction { instructions := vc.CommonConfig.InstructionsForPhase(phase) ainstructions := []build.Instruction{} @@ -41,6 +56,9 @@ func (vc *VariantConfig) InstructionsForPhase(phase build.Phase) []build.Instruc return instructions } +// VariantDependencies returns all unique names of other variants that are +// referenced in the VariantConfig.Artifacts configuration. +// func (vc *VariantConfig) VariantDependencies() []string { // get unique set of variant dependencies based on artifacts existing := map[string]bool{} diff --git a/docker/compiler.go b/docker/compiler.go index 0a2120b..2823777 100644 --- a/docker/compiler.go +++ b/docker/compiler.go @@ -1,3 +1,6 @@ +// Package docker implements a compiler for turning Blubber configuration into +// a valid single- or multi-stage Dockerfile. +// package docker import ( @@ -9,7 +12,11 @@ import ( "phabricator.wikimedia.org/source/blubber/meta" ) -// Compile blubber yaml file into Dockerfile +// Compile takes a parsed config.Config and a configured variant name and +// returns the bytes of a resulting Dockerfile. In the case where artifacts +// are defined or the shorthand "copies" configured is set, a multi-stage +// Dockerfile will be returned. +// func Compile(cfg *config.Config, variant string) (*bytes.Buffer, error) { buffer := new(bytes.Buffer) diff --git a/docker/instructions.go b/docker/instructions.go index eba6731..f362814 100644 --- a/docker/instructions.go +++ b/docker/instructions.go @@ -8,6 +8,11 @@ import ( "phabricator.wikimedia.org/source/blubber/build" ) +// NewDockerInstruction 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 NewDockerInstruction(instruction build.Instruction) (DockerInstruction, error) { switch instruction.(type) { case build.Run, build.RunAll: @@ -39,6 +44,8 @@ func NewDockerInstruction(instruction build.Instruction) (DockerInstruction, err return nil, errors.New("Unable to create DockerInstruction") } +// DockerInstruction defines an interface for instruction compilation. +// type DockerInstruction interface { Compile() string Arguments() []string @@ -52,24 +59,36 @@ func (di abstractDockerInstruction) Arguments() []string { return di.arguments } +// DockerRun compiles into a RUN instruction. +// type DockerRun struct{ abstractDockerInstruction } +// Compile compiles RUN instructions. +// func (dr DockerRun) Compile() string { return fmt.Sprintf( "RUN %s\n", join(dr.arguments, "")) } +// DockerCopy compiles into a COPY instruction. +// type DockerCopy struct{ abstractDockerInstruction } +// Compile compiles COPY instructions. +// func (dc DockerCopy) Compile() string { return fmt.Sprintf( "COPY [%s]\n", join(dc.arguments, ", ")) } +// DockerCopyFrom compiles into a COPY --from instruction. +// type DockerCopyFrom struct{ abstractDockerInstruction } +// Compile compiles COPY --from instructions. +// func (dcf DockerCopyFrom) Compile() string { return fmt.Sprintf( "COPY --from=%s [%s]\n", @@ -77,26 +96,37 @@ func (dcf DockerCopyFrom) Compile() string { join(dcf.arguments[1:], ", ")) } +// DockerEnv compiles into a ENV instruction. +// type DockerEnv struct{ abstractDockerInstruction } +// Compile compiles ENV instructions. +// func (de DockerEnv) Compile() string { return fmt.Sprintf( "ENV %s\n", join(de.arguments, " ")) } -// DockerLabel represents a concrete LABEL instruction +// DockerLabel compiles into a LABEL instruction. +// type DockerLabel struct{ abstractDockerInstruction } -// Compile returns multiple key="value" arguments as a single LABEL string +// Compile returns multiple key="value" arguments as a single LABEL +// instruction. +// func (dl DockerLabel) Compile() string { return fmt.Sprintf( "LABEL %s\n", join(dl.arguments, " ")) } +// DockerVolume compiles into a VOLUME instruction. +// type DockerVolume struct{ abstractDockerInstruction } +// Compile compiles VOLUME instructions. +// func (dv DockerVolume) Compile() string { return fmt.Sprintf( "VOLUME [%s]\n", @@ -1,3 +1,5 @@ +// Package main provides the command line interface. +// package main import ( |