You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

228 lines
6.0 KiB

// 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...)
}