diff --git a/command/compile.go b/command/compile.go index adbafbb..7ec6d31 100644 --- a/command/compile.go +++ b/command/compile.go @@ -26,9 +26,13 @@ import ( type compileCommand struct { *internal.Flags - Source *os.File - Environ map[string]string - Secrets map[string]string + Source *os.File + Privileged []string + Networks []string + Volumes map[string]string + Environ map[string]string + Labels map[string]string + Secrets map[string]string } func (c *compileCommand) run(*kingpin.ParseContext) error { @@ -89,15 +93,19 @@ func (c *compileCommand) run(*kingpin.ParseContext) error { // compile the pipeline to an intermediate representation. comp := &compiler.Compiler{ - Pipeline: resource, - Manifest: manifest, - Build: c.Build, - Netrc: c.Netrc, - Repo: c.Repo, - Stage: c.Stage, - System: c.System, - Environ: c.Environ, - Secret: secret.StaticVars(c.Secrets), + Pipeline: resource, + Manifest: manifest, + Build: c.Build, + Netrc: c.Netrc, + Repo: c.Repo, + Stage: c.Stage, + System: c.System, + Environ: c.Environ, + Labels: c.Labels, + Privileged: append(c.Privileged, compiler.Privileged...), + Networks: c.Networks, + Volumes: c.Volumes, + Secret: secret.StaticVars(c.Secrets), } spec := comp.Compile(nocontext) @@ -113,6 +121,8 @@ func registerCompile(app *kingpin.Application) { c := new(compileCommand) c.Environ = map[string]string{} c.Secrets = map[string]string{} + c.Labels = map[string]string{} + c.Volumes = map[string]string{} cmd := app.Command("compile", "compile the yaml file"). Action(c.run) @@ -127,6 +137,18 @@ func registerCompile(app *kingpin.Application) { cmd.Flag("environ", "environment variables"). StringMapVar(&c.Environ) + cmd.Flag("labels", "container labels"). + StringMapVar(&c.Labels) + + cmd.Flag("networks", "container networks"). + StringsVar(&c.Networks) + + cmd.Flag("volumes", "container volumes"). + StringMapVar(&c.Volumes) + + cmd.Flag("privileged", "privileged docker images"). + StringsVar(&c.Privileged) + // shared pipeline flags c.Flags = internal.ParseFlags(cmd) } diff --git a/command/exec.go b/command/exec.go index 192f818..d6ff8a1 100644 --- a/command/exec.go +++ b/command/exec.go @@ -38,7 +38,13 @@ type execCommand struct { *internal.Flags Source *os.File + Include []string + Exclude []string + Privileged []string + Networks []string + Volumes map[string]string Environ map[string]string + Labels map[string]string Secrets map[string]string Pretty bool Procs int64 @@ -107,18 +113,56 @@ func (c *execCommand) run(*kingpin.ParseContext) error { // compile the pipeline to an intermediate representation. comp := &compiler.Compiler{ - Pipeline: resource, - Manifest: manifest, - Build: c.Build, - Netrc: c.Netrc, - Repo: c.Repo, - Stage: c.Stage, - System: c.System, - Environ: c.Environ, - Secret: secret.StaticVars(c.Secrets), + Pipeline: resource, + Manifest: manifest, + Build: c.Build, + Netrc: c.Netrc, + Repo: c.Repo, + Stage: c.Stage, + System: c.System, + Environ: c.Environ, + Labels: c.Labels, + Privileged: append(c.Privileged, compiler.Privileged...), + Networks: c.Networks, + Volumes: c.Volumes, + Secret: secret.StaticVars(c.Secrets), } spec := comp.Compile(nocontext) + // include only steps that are in the include list, + // if the list in non-empty. + if len(c.Include) > 0 { + I: + for _, step := range spec.Steps { + if step.Name == "clone" { + continue + } + for _, name := range c.Include { + if step.Name == name { + continue I + } + } + step.RunPolicy = engine.RunNever + } + } + + // exclude steps that are in the exclude list, + // if the list in non-empty. + if len(c.Exclude) > 0 { + E: + for _, step := range spec.Steps { + if step.Name == "clone" { + continue + } + for _, name := range c.Exclude { + if step.Name == name { + step.RunPolicy = engine.RunNever + continue E + } + } + } + } + // create a step object for each pipeline step. for _, step := range spec.Steps { if step.RunPolicy == engine.RunNever { @@ -201,6 +245,8 @@ func registerExec(app *kingpin.Application) { c := new(execCommand) c.Environ = map[string]string{} c.Secrets = map[string]string{} + c.Labels = map[string]string{} + c.Volumes = map[string]string{} cmd := app.Command("exec", "executes a pipeline"). Action(c.run) @@ -212,9 +258,27 @@ func registerExec(app *kingpin.Application) { cmd.Flag("secrets", "secret parameters"). StringMapVar(&c.Secrets) + cmd.Flag("include", "include pipeline steps"). + StringsVar(&c.Include) + + cmd.Flag("exclude", "exclude pipeline steps"). + StringsVar(&c.Exclude) + cmd.Flag("environ", "environment variables"). StringMapVar(&c.Environ) + cmd.Flag("labels", "container labels"). + StringMapVar(&c.Labels) + + cmd.Flag("networks", "container networks"). + StringsVar(&c.Networks) + + cmd.Flag("volumes", "container volumes"). + StringMapVar(&c.Volumes) + + cmd.Flag("privileged", "privileged docker images"). + StringsVar(&c.Privileged) + cmd.Flag("public-key", "public key file path"). ExistingFileVar(&c.PublicKey) diff --git a/engine/compiler/compiler.go b/engine/compiler/compiler.go index 1549568..97478eb 100644 --- a/engine/compiler/compiler.go +++ b/engine/compiler/compiler.go @@ -25,6 +25,17 @@ import ( // random generator function var random = uniuri.New +// Privileged provides a list of plugins that execute +// with privileged capabilities in order to run Docker +// in Docker. +var Privileged = []string{ + "plugins/docker", + "plugins/acr", + "plugins/ecr", + "plugins/gcr", + "plugins/heroku", +} + // Compiler compiles the Yaml configuration file to an // intermediate representation optimized for simple execution. type Compiler struct { @@ -61,6 +72,22 @@ type Compiler struct { // should be added to each pipeline step by default. Environ map[string]string + // Labels provides a set of labels that should be added + // to each container by default. + Labels map[string]string + + // Privileged provides a list of docker images that + // are always privileged. + Privileged []string + + // Networks provides a set of networks that should be + // attached to each pipeline container. + Networks []string + + // Volumes provides a set of volumes that should be + // mounted to each pipeline container. + Volumes map[string]string + // Netrc provides netrc parameters that can be used by the // default clone step to authenticate to the remote // repository. @@ -86,15 +113,21 @@ func (c *Compiler) Compile(ctx context.Context) *engine.Spec { // create the workspace volume volume := &engine.VolumeEmptyDir{ - ID: random(), - Name: mount.Name, - Labels: createLabels(c.Repo, c.Build, c.Stage, nil), + ID: random(), + Name: mount.Name, + Labels: environ.Combine( + c.Labels, + createLabels(c.Repo, c.Build, c.Stage), + ), } spec := &engine.Spec{ Network: engine.Network{ - ID: random(), - Labels: createLabels(c.Repo, c.Build, c.Stage, nil), + ID: random(), + Labels: environ.Combine( + c.Labels, + createLabels(c.Repo, c.Build, c.Stage), + ), }, Platform: engine.Platform{ OS: c.Pipeline.Platform.OS, @@ -167,7 +200,10 @@ func (c *Compiler) Compile(ctx context.Context) *engine.Spec { step.ID = random() step.Envs = environ.Combine(envs, step.Envs) step.WorkingDir = full - step.Labels = createLabels(c.Repo, c.Build, c.Stage, nil) + step.Labels = environ.Combine( + c.Labels, + createLabels(c.Repo, c.Build, c.Stage), + ) step.Volumes = append(step.Volumes, mount) spec.Steps = append(spec.Steps, step) } @@ -178,7 +214,10 @@ func (c *Compiler) Compile(ctx context.Context) *engine.Spec { dst.Detach = true dst.Envs = environ.Combine(envs, dst.Envs) dst.Volumes = append(dst.Volumes, mount) - dst.Labels = createLabels(c.Repo, c.Build, c.Stage, nil) + dst.Labels = environ.Combine( + c.Labels, + createLabels(c.Repo, c.Build, c.Stage), + ) setupScript(src, dst, os) setupWorkdir(src, dst, full) spec.Steps = append(spec.Steps, dst) @@ -195,7 +234,10 @@ func (c *Compiler) Compile(ctx context.Context) *engine.Spec { dst := createStep(c.Pipeline, src) dst.Envs = environ.Combine(envs, dst.Envs) dst.Volumes = append(dst.Volumes, mount) - dst.Labels = createLabels(c.Repo, c.Build, c.Stage, nil) + dst.Labels = environ.Combine( + c.Labels, + createLabels(c.Repo, c.Build, c.Stage), + ) setupScript(src, dst, full) setupWorkdir(src, dst, full) spec.Steps = append(spec.Steps, dst) @@ -205,6 +247,13 @@ func (c *Compiler) Compile(ctx context.Context) *engine.Spec { if !src.When.Match(match) { dst.RunPolicy = engine.RunNever } + + // if the pipeline step has an approved image, it is + // automatically defaulted to run with escalalated + // privileges. + if c.isPrivileged(src) { + dst.Privileged = true + } } if isGraph(spec) == false { @@ -245,9 +294,59 @@ func (c *Compiler) Compile(ctx context.Context) *engine.Spec { } } + // append global networks to the steps. + for _, step := range spec.Steps { + step.Networks = append(step.Networks, c.Networks...) + } + + // append global volumes to the steps. + for k, v := range c.Volumes { + id := random() + volume := &engine.Volume{ + HostPath: &engine.VolumeHostPath{ + ID: id, + Name: id, + Path: k, + }, + } + spec.Volumes = append(spec.Volumes, volume) + for _, step := range spec.Steps { + mount := &engine.VolumeMount{ + Name: id, + Path: v, + } + step.Volumes = append(step.Volumes, mount) + } + } + return spec } +func (c *Compiler) isPrivileged(step *resource.Step) bool { + // privileged-by-default containers are only + // enabled for plugins steps that do not define + // commands, command, or entrypoint. + if len(step.Commands) > 0 { + return false + } + if len(step.Command) > 0 { + return false + } + if len(step.Entrypoint) > 0 { + return false + } + // if the container image matches any image + // in the whitelist, return true. + for _, img := range c.Privileged { + a := img + b := step.Image + if image.Match(a, b) { + return true + } + } + return false +} + // helper function attempts to find and return the named secret. // from the secret provider. func (c *Compiler) findSecret(ctx context.Context, name string) (s string, ok bool) { diff --git a/engine/compiler/label.go b/engine/compiler/label.go index b1666b7..53c5cbe 100644 --- a/engine/compiler/label.go +++ b/engine/compiler/label.go @@ -15,9 +15,8 @@ func createLabels( repo *drone.Repo, build *drone.Build, stage *drone.Stage, - step *drone.Step, ) map[string]string { - labels := map[string]string{ + return map[string]string{ "io.drone": "true", "io.drone.build.number": fmt.Sprint(build.Number), "io.drone.repo.namespace": repo.Namespace, @@ -29,9 +28,6 @@ func createLabels( "io.drone.created": fmt.Sprint(time.Now().Unix()), "io.drone.protected": "false", } - if step != nil { - labels["io.drone.step.name"] = step.Name - labels["io.drone.step.number"] = fmt.Sprint(step.Number) - } - return labels + // labels["io.drone.step.name"] = step.Name + // labels["io.drone.step.number"] = fmt.Sprint(step.Number) }