// 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 ( "fmt" "sort" "strconv" "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 // 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]) if len(run.Arguments) > numInnerArgs { command += " " + strings.Join(quoteAll(run.Arguments[numInnerArgs:]), " ") } 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 // 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)) for i, run := range runAll.Runs { commands[i] = run.Compile()[0] } 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 // source file/directory paths Destination string // destination path } // Compile quotes the defined source files/directories and destination. // func (copy Copy) Compile() []string { dest := copy.Destination // If there is more than 1 file being copied, the destination must be a // directory ending with "/" if len(copy.Sources) > 1 && !strings.HasSuffix(copy.Destination, "/") { dest = dest + "/" } return append(quoteAll(copy.Sources), quote(dest)) } // CopyAs is a concrete build instruction for copying source // files/directories and setting their ownership to the given UID/GID. // // While it can technically wrap any build.Instruction, it is meant to be used // with build.Copy and build.CopyFrom to enforce file/directory ownership. // type CopyAs struct { UID uint // owner UID GID uint // owner GID Instruction } // Compile returns the variant name unquoted and all quoted CopyAs instruction // fields. // func (ca CopyAs) Compile() []string { return append([]string{fmt.Sprintf("%d:%d", ca.UID, ca.GID)}, ca.Instruction.Compile()...) } // CopyFrom is a concrete build instruction for copying source // files/directories from one variant image to another. // type CopyFrom struct { 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()...) } // EntryPoint is a build instruction for declaring a container's default // runtime process. type EntryPoint struct { Command []string // command and arguments } // Compile returns the quoted entrypoint command and arguments. // func (ep EntryPoint) Compile() []string { return quoteAll(ep.Command) } // Env is a concrete build instruction for declaring a container's runtime // environment variables. // type Env struct { 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 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 // number of meta-data 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 (label Label) Compile() []string { return compileSortedKeyValues(label.Definitions) } // User is a build instruction for setting which user will run future // commands. // type User struct { Name string // user name } // Compile returns the quoted user name. // func (user User) Compile() []string { return []string{quote(user.Name)} } // WorkingDirectory is a build instruction for defining the working directory // for future command and entrypoint instructions. // type WorkingDirectory struct { Path string // working directory path } // Compile returns the quoted working directory path. // func (wd WorkingDirectory) Compile() []string { return []string{quote(wd.Path)} } func compileSortedKeyValues(keyValues map[string]string) []string { defs := make([]string, 0, len(keyValues)) names := make([]string, 0, len(keyValues)) for name := range keyValues { names = append(names, name) } sort.Strings(names) for _, name := range names { defs = append(defs, name+"="+quote(keyValues[name])) } return defs } func quote(arg string) string { return strconv.Quote(arg) } func quoteAll(arguments []string) []string { quoted := make([]string, len(arguments)) for i, arg := range arguments { quoted[i] = quote(arg) } return quoted } func sprintf(format string, arguments []string) string { args := make([]interface{}, len(arguments)) for i, v := range arguments { args[i] = quote(v) } return fmt.Sprintf(format, args...) }