This repository has been archived on 2025-11-20. You can view files and clone it, but cannot push or open issues or pull requests.
Files
drone-runner-podman/command/exec.go
2019-12-06 16:10:27 -08:00

351 lines
7.9 KiB
Go

// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.
package command
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
"github.com/drone-runners/drone-runner-docker/command/internal"
"github.com/drone-runners/drone-runner-docker/engine"
"github.com/drone-runners/drone-runner-docker/engine/compiler"
"github.com/drone-runners/drone-runner-docker/engine/linter"
"github.com/drone-runners/drone-runner-docker/engine/resource"
"github.com/drone/drone-go/drone"
"github.com/drone/envsubst"
"github.com/drone/runner-go/environ"
"github.com/drone/runner-go/environ/provider"
"github.com/drone/runner-go/logger"
"github.com/drone/runner-go/manifest"
"github.com/drone/runner-go/pipeline"
"github.com/drone/runner-go/pipeline/runtime"
"github.com/drone/runner-go/pipeline/streamer/console"
"github.com/drone/runner-go/registry"
"github.com/drone/runner-go/secret"
"github.com/drone/signal"
"github.com/mattn/go-isatty"
"github.com/sirupsen/logrus"
"gopkg.in/alecthomas/kingpin.v2"
)
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
Resources compiler.Resources
Clone bool
Config string
Pretty bool
Procs int64
Debug bool
Trace bool
Dump bool
PublicKey string
PrivateKey string
}
func (c *execCommand) run(*kingpin.ParseContext) error {
rawsource, err := ioutil.ReadAll(c.Source)
if err != nil {
return err
}
envs := environ.Combine(
c.Environ,
environ.System(c.System),
environ.Repo(c.Repo),
environ.Build(c.Build),
environ.Stage(c.Stage),
environ.Link(c.Repo, c.Build, c.System),
c.Build.Params,
)
// string substitution function ensures that string
// replacement variables are escaped and quoted if they
// contain newlines.
subf := func(k string) string {
v := envs[k]
if strings.Contains(v, "\n") {
v = fmt.Sprintf("%q", v)
}
return v
}
// evaluates string replacement expressions and returns an
// update configuration.
config, err := envsubst.Eval(string(rawsource), subf)
if err != nil {
return err
}
// parse and lint the configuration.
manifest, err := manifest.ParseString(config)
if err != nil {
return err
}
// a configuration can contain multiple pipelines.
// get a specific pipeline resource for execution.
res, err := resource.Lookup(c.Stage.Name, manifest)
if err != nil {
return err
}
// lint the pipeline and return an error if any
// linting rules are broken
lint := linter.New()
err = lint.Lint(res, c.Repo)
if err != nil {
return err
}
// compile the pipeline to an intermediate representation.
comp := &compiler.Compiler{
Environ: provider.Static(c.Environ),
Labels: c.Labels,
Resources: c.Resources,
Privileged: append(c.Privileged, compiler.Privileged...),
Networks: c.Networks,
Volumes: c.Volumes,
Secret: secret.StaticVars(c.Secrets),
Registry: registry.Combine(
registry.File(c.Config),
),
}
// when running a build locally cloning is always
// disabled in favor of mounting the source code
// from the current working directory.
if c.Clone == false {
comp.Mount, _ = os.Getwd()
}
args := runtime.CompilerArgs{
Pipeline: res,
Manifest: manifest,
Build: c.Build,
Netrc: c.Netrc,
Repo: c.Repo,
Stage: c.Stage,
System: c.System,
}
spec := comp.Compile(nocontext, args).(*engine.Spec)
// 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 = runtime.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 = runtime.RunNever
continue E
}
}
}
}
// create a step object for each pipeline step.
for _, step := range spec.Steps {
if step.RunPolicy == runtime.RunNever {
continue
}
c.Stage.Steps = append(c.Stage.Steps, &drone.Step{
StageID: c.Stage.ID,
Number: len(c.Stage.Steps) + 1,
Name: step.Name,
Status: drone.StatusPending,
ErrIgnore: step.IgnoreErr,
})
}
// configures the pipeline timeout.
timeout := time.Duration(c.Repo.Timeout) * time.Minute
ctx, cancel := context.WithTimeout(nocontext, timeout)
defer cancel()
// listen for operating system signals and cancel execution
// when received.
ctx = signal.WithContextFunc(ctx, func() {
println("received signal, terminating process")
cancel()
})
state := &pipeline.State{
Build: c.Build,
Stage: c.Stage,
Repo: c.Repo,
System: c.System,
}
// enable debug logging
logrus.SetLevel(logrus.WarnLevel)
if c.Debug {
logrus.SetLevel(logrus.DebugLevel)
}
if c.Trace {
logrus.SetLevel(logrus.TraceLevel)
}
logger.Default = logger.Logrus(
logrus.NewEntry(
logrus.StandardLogger(),
),
)
engine, err := engine.NewEnv(engine.Opts{})
if err != nil {
return err
}
err = runtime.NewExecer(
pipeline.NopReporter(),
console.New(c.Pretty),
engine,
c.Procs,
).Exec(ctx, spec, state)
if c.Dump {
dump(state)
}
if err != nil {
return err
}
switch state.Stage.Status {
case drone.StatusError, drone.StatusFailing, drone.StatusKilled:
os.Exit(1)
}
return nil
}
func dump(v interface{}) {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.Encode(v)
}
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)
cmd.Arg("source", "source file location").
Default(".drone.yml").
FileVar(&c.Source)
cmd.Flag("clone", "enable cloning").
BoolVar(&c.Clone)
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("cpu-period", "container cpu period").
Int64Var(&c.Resources.CPUPeriod)
cmd.Flag("cpu-quota", "container cpu quota").
Int64Var(&c.Resources.CPUQuota)
cmd.Flag("cpu-set", "container cpu set").
StringsVar(&c.Resources.CPUSet)
cmd.Flag("cpu-shares", "container cpu shares").
Int64Var(&c.Resources.CPUShares)
cmd.Flag("memory", "container memory limit").
Int64Var(&c.Resources.Memory)
cmd.Flag("memory-swap", "container memory swap limit").
Int64Var(&c.Resources.MemorySwap)
cmd.Flag("public-key", "public key file path").
ExistingFileVar(&c.PublicKey)
cmd.Flag("private-key", "private key file path").
ExistingFileVar(&c.PrivateKey)
cmd.Flag("docker-config", "path to the docker config file").
StringVar(&c.Config)
cmd.Flag("debug", "enable debug logging").
BoolVar(&c.Debug)
cmd.Flag("trace", "enable trace logging").
BoolVar(&c.Trace)
cmd.Flag("dump", "dump the pipeline state to stdout").
BoolVar(&c.Dump)
cmd.Flag("pretty", "pretty print the output").
Default(
fmt.Sprint(
isatty.IsTerminal(
os.Stdout.Fd(),
),
),
).BoolVar(&c.Pretty)
// shared pipeline flags
c.Flags = internal.ParseFlags(cmd)
}