mirror of
https://github.com/drone/drone-cli.git
synced 2025-02-22 09:31:18 +01:00
367 lines
8.1 KiB
Go
367 lines
8.1 KiB
Go
package exec
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/drone/envsubst"
|
|
|
|
"github.com/drone/drone-runtime/engine"
|
|
"github.com/drone/drone-runtime/engine/docker"
|
|
"github.com/drone/drone-runtime/runtime"
|
|
"github.com/drone/drone-runtime/runtime/term"
|
|
"github.com/drone/drone-yaml/yaml"
|
|
"github.com/drone/drone-yaml/yaml/compiler"
|
|
"github.com/drone/drone-yaml/yaml/compiler/transform"
|
|
"github.com/drone/drone-yaml/yaml/converter"
|
|
"github.com/drone/drone-yaml/yaml/linter"
|
|
"github.com/drone/signal"
|
|
|
|
"github.com/joho/godotenv"
|
|
"github.com/mattn/go-colorable"
|
|
"github.com/mattn/go-isatty"
|
|
"github.com/urfave/cli"
|
|
)
|
|
|
|
var tty = isatty.IsTerminal(os.Stdout.Fd())
|
|
|
|
// Command exports the exec command.
|
|
var Command = cli.Command{
|
|
Name: "exec",
|
|
Usage: "execute a local build",
|
|
ArgsUsage: "[path/to/.drone.yml]",
|
|
Action: func(c *cli.Context) {
|
|
if err := exec(c); err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
},
|
|
Flags: []cli.Flag{
|
|
cli.StringFlag{
|
|
Name: "pipeline",
|
|
Usage: "Name of the pipeline to execute",
|
|
},
|
|
cli.StringSliceFlag{
|
|
Name: "include",
|
|
Usage: "Name of steps to include",
|
|
},
|
|
cli.StringSliceFlag{
|
|
Name: "exclude",
|
|
Usage: "Name of steps to exclude",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "resume-at",
|
|
Usage: "Name of start to resume at",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "clone",
|
|
Usage: "enable the clone step",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "trusted",
|
|
Usage: "build is trusted",
|
|
},
|
|
cli.DurationFlag{
|
|
Name: "timeout",
|
|
Usage: "build timeout",
|
|
Value: time.Hour,
|
|
},
|
|
cli.StringSliceFlag{
|
|
Name: "volume",
|
|
Usage: "build volumes",
|
|
},
|
|
cli.StringSliceFlag{
|
|
Name: "network",
|
|
Usage: "external networks",
|
|
},
|
|
cli.StringSliceFlag{
|
|
Name: "registry",
|
|
Usage: "registry",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "secret-file",
|
|
Usage: "secret file, define values that can be used with from_secret",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "env-file",
|
|
Usage: "env file",
|
|
},
|
|
cli.StringSliceFlag{
|
|
Name: "privileged",
|
|
Usage: "privileged plugins",
|
|
Value: &cli.StringSlice{
|
|
"plugins/docker",
|
|
"plugins/acr",
|
|
"plugins/ecr",
|
|
"plugins/gcr",
|
|
"plugins/heroku",
|
|
},
|
|
},
|
|
|
|
//
|
|
// netrc parameters
|
|
//
|
|
cli.StringFlag{
|
|
Name: "netrc-username",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "netrc-password",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "netrc-machine",
|
|
},
|
|
|
|
//
|
|
// trigger parameters
|
|
//
|
|
|
|
cli.StringFlag{
|
|
Name: "branch",
|
|
Usage: "branch name",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "event",
|
|
Usage: "build event name (push, pull_request, etc)",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "instance",
|
|
Usage: "instance hostname (e.g. drone.company.com)",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "ref",
|
|
Usage: "git reference",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "sha",
|
|
Usage: "git sha",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "repo",
|
|
Usage: "git repository name (e.g. octocat/hello-world)",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "deploy-to",
|
|
Usage: "deployment target (e.g. production)",
|
|
},
|
|
},
|
|
}
|
|
|
|
func exec(c *cli.Context) error {
|
|
file := c.Args().First()
|
|
if file == "" {
|
|
file = ".drone.yml"
|
|
}
|
|
|
|
data, err := ioutil.ReadFile(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
environ := getEnv(c)
|
|
dataS, err := envsubst.Eval(string(data), func(name string) string {
|
|
return environ[name]
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// this code is temporarily in place to detect and convert
|
|
// the legacy yaml configuration file to the new format.
|
|
dataS, err = converter.ConvertString(dataS, converter.Metadata{
|
|
Filename: file,
|
|
Ref: c.String("ref"),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
manifest, err := yaml.ParseString(dataS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var pipeline *yaml.Pipeline
|
|
filter := c.String("pipeline")
|
|
for _, resource := range manifest.Resources {
|
|
v, ok := resource.(*yaml.Pipeline)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if v.Type != "" && v.Type != "docker" {
|
|
return fmt.Errorf("pipeline type (%s) is not supported with 'drone exec'", v.Type)
|
|
}
|
|
|
|
if filter == "" || filter == v.Name {
|
|
pipeline = v
|
|
break
|
|
}
|
|
}
|
|
if pipeline == nil {
|
|
return errors.New("cannot find pipeline")
|
|
}
|
|
|
|
trusted := c.Bool("trusted")
|
|
err = linter.Lint(pipeline, trusted)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// the user has the option to disable the git clone
|
|
// if the pipeline is being executed on the local
|
|
// codebase.
|
|
if c.Bool("clone") == false {
|
|
pipeline.Clone.Disable = true
|
|
}
|
|
|
|
comp := new(compiler.Compiler)
|
|
comp.PrivilegedFunc = compiler.DindFunc(
|
|
c.StringSlice("privileged"),
|
|
)
|
|
comp.SkipFunc = compiler.SkipFunc(
|
|
compiler.SkipData{
|
|
Branch: environ["DRONE_BRANCH"],
|
|
Event: environ["DRONE_EVENT"],
|
|
Instance: environ["DRONE_SYSTEM_HOST"],
|
|
Ref: environ["DRONE_COMMIT_REF"],
|
|
Repo: environ["DRONE_REPO"],
|
|
Target: environ["DRONE_DEPLOY_TO"],
|
|
},
|
|
)
|
|
transforms := []func(*engine.Spec){
|
|
transform.Include(
|
|
c.StringSlice("include"),
|
|
),
|
|
transform.Exclude(
|
|
c.StringSlice("exclude"),
|
|
),
|
|
transform.ResumeAt(
|
|
c.String("resume-at"),
|
|
),
|
|
transform.WithAuths(
|
|
toRegistry(
|
|
c.StringSlice("registry"),
|
|
),
|
|
),
|
|
transform.WithEnviron(
|
|
readParams(
|
|
c.String("env-file"),
|
|
),
|
|
),
|
|
transform.WithEnviron(environ),
|
|
transform.WithLables(nil),
|
|
transform.WithLimits(0, 0),
|
|
transform.WithNetrc(
|
|
c.String("netrc-machine"),
|
|
c.String("netrc-username"),
|
|
c.String("netrc-password"),
|
|
),
|
|
transform.WithNetworks(
|
|
c.StringSlice("network"),
|
|
),
|
|
transform.WithProxy(),
|
|
transform.WithSecrets(
|
|
readParams(
|
|
c.String("secret-file"),
|
|
),
|
|
),
|
|
transform.WithVolumeSlice(
|
|
c.StringSlice("volume"),
|
|
),
|
|
}
|
|
if c.Bool("clone") == false {
|
|
pwd, _ := os.Getwd()
|
|
comp.WorkspaceMountFunc = compiler.MountHostWorkspace
|
|
comp.WorkspaceFunc = compiler.CreateHostWorkspace(pwd)
|
|
}
|
|
comp.TransformFunc = transform.Combine(transforms...)
|
|
ir := comp.Compile(pipeline)
|
|
|
|
ctx, cancel := context.WithTimeout(
|
|
context.Background(),
|
|
c.Duration("timeout"),
|
|
)
|
|
ctx = signal.WithContext(ctx)
|
|
defer cancel()
|
|
|
|
// creates a docker-based engine. eventually we will
|
|
// include the kubernetes and vmware fusion engines.
|
|
engine, err := docker.NewEnv()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// creates a hook to print the step output to stdout,
|
|
// with per-step color coding if a tty.
|
|
hooks := &runtime.Hook{}
|
|
hooks.BeforeEach = func(s *runtime.State) error {
|
|
s.Step.Envs["CI_BUILD_STATUS"] = "success"
|
|
s.Step.Envs["CI_BUILD_STARTED"] = strconv.FormatInt(s.Runtime.Time, 10)
|
|
s.Step.Envs["CI_BUILD_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10)
|
|
s.Step.Envs["DRONE_BUILD_STATUS"] = "success"
|
|
s.Step.Envs["DRONE_BUILD_STARTED"] = strconv.FormatInt(s.Runtime.Time, 10)
|
|
s.Step.Envs["DRONE_BUILD_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10)
|
|
|
|
s.Step.Envs["CI_JOB_STATUS"] = "success"
|
|
s.Step.Envs["CI_JOB_STARTED"] = strconv.FormatInt(s.Runtime.Time, 10)
|
|
s.Step.Envs["CI_JOB_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10)
|
|
s.Step.Envs["DRONE_JOB_STATUS"] = "success"
|
|
s.Step.Envs["DRONE_JOB_STARTED"] = strconv.FormatInt(s.Runtime.Time, 10)
|
|
s.Step.Envs["DRONE_JOB_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10)
|
|
|
|
if s.Runtime.Error != nil {
|
|
s.Step.Envs["CI_BUILD_STATUS"] = "failure"
|
|
s.Step.Envs["CI_JOB_STATUS"] = "failure"
|
|
s.Step.Envs["DRONE_BUILD_STATUS"] = "failure"
|
|
s.Step.Envs["DRONE_JOB_STATUS"] = "failure"
|
|
}
|
|
return nil
|
|
}
|
|
|
|
hooks.GotLine = term.WriteLine(os.Stdout)
|
|
if tty {
|
|
hooks.GotLine = term.WriteLinePretty(
|
|
colorable.NewColorableStdout(),
|
|
)
|
|
}
|
|
|
|
return runtime.New(
|
|
runtime.WithEngine(engine),
|
|
runtime.WithConfig(ir),
|
|
runtime.WithHooks(hooks),
|
|
).Run(ctx)
|
|
}
|
|
|
|
// helper function converts a slice of urls to a slice
|
|
// of docker registry credentials.
|
|
func toRegistry(items []string) []*engine.DockerAuth {
|
|
auths := []*engine.DockerAuth{}
|
|
for _, item := range items {
|
|
uri, err := url.Parse(item)
|
|
if err != nil {
|
|
continue // skip invalid
|
|
}
|
|
user := uri.User.Username()
|
|
pass, _ := uri.User.Password()
|
|
uri.User = nil
|
|
auths = append(auths, &engine.DockerAuth{
|
|
Address: uri.String(),
|
|
Username: user,
|
|
Password: pass,
|
|
})
|
|
}
|
|
return auths
|
|
}
|
|
|
|
// helper function reads secrets from a key-value file.
|
|
func readParams(path string) map[string]string {
|
|
data, _ := godotenv.Read(path)
|
|
return data
|
|
}
|