377 lines
8.7 KiB
Go
377 lines
8.7 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"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/drone-runners/drone-runner-podman/command/internal"
|
|
"github.com/drone-runners/drone-runner-podman/engine"
|
|
"github.com/drone-runners/drone-runner-podman/engine/compiler"
|
|
"github.com/drone-runners/drone-runner-podman/engine/linter"
|
|
"github.com/drone-runners/drone-runner-podman/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
|
|
Tmate compiler.Tmate
|
|
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 := io.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,
|
|
Tmate: c.Tmate,
|
|
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.ErrPolicy == runtime.ErrIgnore,
|
|
})
|
|
}
|
|
|
|
// 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(context.Background(), engine.Opts{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = runtime.NewExecer(
|
|
pipeline.NopReporter(),
|
|
console.New(c.Pretty),
|
|
pipeline.NopUploader(),
|
|
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 podman 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("podman-config", "path to the podman config file").
|
|
StringVar(&c.Config)
|
|
|
|
cmd.Flag("tmate-image", "tmate podman image").
|
|
Default("drone/drone-runner-podman:1").
|
|
StringVar(&c.Tmate.Image)
|
|
|
|
cmd.Flag("tmate-enabled", "tmate enabled").
|
|
BoolVar(&c.Tmate.Enabled)
|
|
|
|
cmd.Flag("tmate-server-host", "tmate server host").
|
|
StringVar(&c.Tmate.Server)
|
|
|
|
cmd.Flag("tmate-server-port", "tmate server port").
|
|
StringVar(&c.Tmate.Port)
|
|
|
|
cmd.Flag("tmate-server-rsa-fingerprint", "tmate server rsa fingerprint").
|
|
StringVar(&c.Tmate.RSA)
|
|
|
|
cmd.Flag("tmate-server-ed25519-fingerprint", "tmate server rsa fingerprint").
|
|
StringVar(&c.Tmate.ED25519)
|
|
|
|
cmd.Flag("tmate-authorized-keys", "tmate authorized keys").
|
|
StringVar(&c.Tmate.AuthorizedKeys)
|
|
|
|
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)
|
|
}
|