initial commit [ci skip]

This commit is contained in:
Brad Rydzewski
2019-10-10 19:01:58 -07:00
parent 56c135e4ae
commit 43bbf6e78c
95 changed files with 6579 additions and 1 deletions

48
.drone.yml Normal file
View File

@@ -0,0 +1,48 @@
kind: pipeline
name: default
platform:
os: linux
arch: amd64
steps:
- name: test
image: golang:1.12
commands:
- go test -cover ./...
volumes:
- name: go
path: /go
- name: build
image: golang:1.12
commands:
- sh scripts/build.sh
volumes:
- name: go
path: /go
when:
event:
- push
- tag
- name: publish
image: plugins/docker
pull: if-not-exists
settings:
repo: drone/drone-runner-docker
auto_tag: true
auto_tag_suffix: linux-amd64
dockerfile: docker/Dockerfile.linux.amd64
username:
from_secret: docker_username
password:
from_secret: docker_password
when:
ref:
- refs/heads/master
- refs/tags/*
volumes:
- name: go
temp: {}

73
.github/code_of_conduct.md vendored Normal file
View File

@@ -0,0 +1,73 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance, race,
religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at conduct@drone.io. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org

0
.github/issue_template.md vendored Normal file
View File

0
.github/pull_request_template.md vendored Normal file
View File

36
.github/security.md vendored Normal file
View File

@@ -0,0 +1,36 @@
# Security Policies and Procedures
This document outlines security procedures and general policies for this project.
* [Reporting a Bug](#reporting-a-bug)
* [Disclosure Policy](#disclosure-policy)
* [Comments on this Policy](#comments-on-this-policy)
## Reporting a Bug
Report security bugs by emailing the lead maintainer at security@drone.io.
The lead maintainer will acknowledge your email within 48 hours, and will send a
more detailed response within 48 hours indicating the next steps in handling
your report. After the initial reply to your report, the security team will
endeavor to keep you informed of the progress towards a fix and full
announcement, and may ask for additional information or guidance.
Report security bugs in third-party packages to the person or team maintaining
the module.
## Disclosure Policy
When the security team receives a security bug report, they will assign it to a
primary handler. This person will coordinate the fix and release process,
involving the following steps:
* Confirm the problem and determine the affected versions.
* Audit code to find any potential similar problems.
* Prepare fixes for all releases still under maintenance. These fixes will be
released as fast as possible.
## Comments on this Policy
If you have suggestions on how this process could be improved please submit a
pull request.

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
drone-runner-docker
drone-runner-docker.exe
release/*
.env
NOTES*
engine/compiler2

14
BUILDING.md Normal file
View File

@@ -0,0 +1,14 @@
1. Install go 1.13 or higher
2. Test
go test ./...
3. Build binaries
sh scripts/build_all.sh
4. Build images
docker build -t drone/drone-runner-docker:latest-linux-amd64 -f docker/Dockerfile.linux.amd64 .
docker build -t drone/drone-runner-docker:latest-linux-arm64 -f docker/Dockerfile.linux.arm64 .
docker build -t drone/drone-runner-docker:latest-linux-arm -f docker/Dockerfile.linux.arm .

5
CHANGELOG.md Normal file
View File

@@ -0,0 +1,5 @@
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added

2
LICENSE.md Normal file
View File

@@ -0,0 +1,2 @@
[Polyform-Small-Business-1.0.0](https://polyformproject.org/licenses/small-business/1.0.0) OR
[Polyform-Free-Trial-1.0.0](https://polyformproject.org/licenses/free-trial/1.0.0)

View File

@@ -1,2 +1,12 @@
# drone-runner-docker # drone-runner-docker
Drone pipeline runner that executes builds inside Docker containers
The `docker` runner executes pipelines inside Docker containers. This runner is intended for linux workloads that are suitable for execution inside containers. This requires Drone server `1.6.0` or higher.
Documentation:<br/>
https://docker-runner.docs.drone.io
Technical Support:<br/>
https://discourse.drone.io
Issue Tracker and Roadmap:<br/>
https://trello.com/b/ttae5E5o/drone

32
command/command.go Normal file
View File

@@ -0,0 +1,32 @@
// 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"
"os"
"github.com/drone-runners/drone-runner-docker/command/daemon"
"gopkg.in/alecthomas/kingpin.v2"
)
// program version
var version = "0.0.0"
// empty context
var nocontext = context.Background()
// Command parses the command line arguments and then executes a
// subcommand program.
func Command() {
app := kingpin.New("drone", "drone exec runner")
registerCompile(app)
registerExec(app)
daemon.Register(app)
kingpin.Version(version)
kingpin.MustParse(app.Parse(os.Args[1:]))
}

122
command/compile.go Normal file
View File

@@ -0,0 +1,122 @@
// 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 (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/drone-runners/drone-runner-docker/command/internal"
"github.com/drone-runners/drone-runner-docker/engine/compiler"
"github.com/drone-runners/drone-runner-docker/engine/resource"
"github.com/drone/envsubst"
"github.com/drone/runner-go/environ"
"github.com/drone/runner-go/manifest"
"github.com/drone/runner-go/secret"
"gopkg.in/alecthomas/kingpin.v2"
)
type compileCommand struct {
*internal.Flags
Source *os.File
Environ map[string]string
Secrets map[string]string
}
func (c *compileCommand) 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.
resource, err := resource.Lookup(c.Stage.Name, manifest)
if err != nil {
return err
}
// 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),
}
spec := comp.Compile(nocontext)
// encode the pipeline in json format and print to the
// console for inspection.
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.Encode(spec)
return nil
}
func registerCompile(app *kingpin.Application) {
c := new(compileCommand)
c.Environ = map[string]string{}
c.Secrets = map[string]string{}
cmd := app.Command("compile", "compile the yaml file").
Action(c.run)
cmd.Flag("source", "source file location").
Default(".drone.yml").
FileVar(&c.Source)
cmd.Flag("secrets", "secret parameters").
StringMapVar(&c.Secrets)
cmd.Flag("environ", "environment variables").
StringMapVar(&c.Environ)
// shared pipeline flags
c.Flags = internal.ParseFlags(cmd)
}

94
command/daemon/config.go Normal file
View File

@@ -0,0 +1,94 @@
// 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 daemon
import (
"fmt"
"os"
"github.com/kelseyhightower/envconfig"
)
// Config stores the system configuration.
type Config struct {
Debug bool `envconfig:"DRONE_DEBUG"`
Trace bool `envconfig:"DRONE_TRACE"`
Logger struct {
File string `envconfig:"DRONE_LOG_FILE"`
MaxAge int `envconfig:"DRONE_LOG_FILE_MAX_AGE" default:"1"`
MaxBackups int `envconfig:"DRONE_LOG_FILE_MAX_BACKUPS" default:"1"`
MaxSize int `envconfig:"DRONE_LOG_FILE_MAX_SIZE" default:"100"`
}
Client struct {
Address string `ignored:"true"`
Proto string `envconfig:"DRONE_RPC_PROTO" default:"http"`
Host string `envconfig:"DRONE_RPC_HOST" required:"true"`
Secret string `envconfig:"DRONE_RPC_SECRET" required:"true"`
SkipVerify bool `envconfig:"DRONE_RPC_SKIP_VERIFY"`
Dump bool `envconfig:"DRONE_RPC_DUMP_HTTP"`
DumpBody bool `envconfig:"DRONE_RPC_DUMP_HTTP_BODY"`
}
Dashboard struct {
Disabled bool `envconfig:"DRONE_UI_DISABLE"`
Username string `envconfig:"DRONE_UI_USERNAME"`
Password string `envconfig:"DRONE_UI_PASSWORD"`
Realm string `envconfig:"DRONE_UI_REALM" default:"MyRealm"`
}
Server struct {
Proto string `envconfig:"DRONE_SERVER_PROTO"`
Host string `envconfig:"DRONE_SERVER_HOST"`
Port string `envconfig:"DRONE_SERVER_PORT" default:":3000"`
Acme bool `envconfig:"DRONE_SERVER_ACME"`
}
Keypair struct {
Public string `envconfig:"DRONE_PUBLIC_KEY_FILE"`
Private string `envconfig:"DRONE_PRIVATE_KEY_FILE"`
}
Runner struct {
Name string `envconfig:"DRONE_RUNNER_NAME"`
Capacity int `envconfig:"DRONE_RUNNER_CAPACITY" default:"10"`
Procs int64 `envconfig:"DRONE_RUNNER_MAX_PROCS"`
Labels map[string]string `envconfig:"DRONE_RUNNER_LABELS"`
Environ map[string]string `envconfig:"DRONE_RUNNER_ENVIRON"`
}
Limit struct {
Repos []string `envconfig:"DRONE_LIMIT_REPOS"`
Events []string `envconfig:"DRONE_LIMIT_EVENTS"`
Trusted bool `envconfig:"DRONE_LIMIT_TRUSTED"`
}
Secret struct {
Endpoint string `envconfig:"DRONE_SECRET_PLUGIN_ENDPOINT"`
Token string `envconfig:"DRONE_SECRET_PLUGIN_TOKEN"`
SkipVerify bool `envconfig:"DRONE_SECRET_PLUGIN_SKIP_VERIFY"`
}
}
func fromEnviron() (Config, error) {
var config Config
err := envconfig.Process("", &config)
if err != nil {
return config, err
}
if config.Runner.Name == "" {
config.Runner.Name, _ = os.Hostname()
}
if config.Dashboard.Password == "" {
config.Dashboard.Disabled = true
}
config.Client.Address = fmt.Sprintf(
"%s://%s",
config.Client.Proto,
config.Client.Host,
)
return config, nil
}

204
command/daemon/daemon.go Normal file
View File

@@ -0,0 +1,204 @@
// 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 daemon
import (
"context"
"time"
"github.com/drone-runners/drone-runner-docker/engine"
"github.com/drone-runners/drone-runner-docker/engine/resource"
"github.com/drone-runners/drone-runner-docker/internal/match"
"github.com/drone-runners/drone-runner-docker/runtime"
"github.com/drone/runner-go/client"
"github.com/drone/runner-go/handler/router"
"github.com/drone/runner-go/logger"
loghistory "github.com/drone/runner-go/logger/history"
"github.com/drone/runner-go/pipeline/history"
"github.com/drone/runner-go/pipeline/remote"
"github.com/drone/runner-go/secret"
"github.com/drone/runner-go/server"
"github.com/drone/signal"
"github.com/joho/godotenv"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
"gopkg.in/alecthomas/kingpin.v2"
)
// empty context.
var nocontext = context.Background()
type daemonCommand struct {
envfile string
}
func (c *daemonCommand) run(*kingpin.ParseContext) error {
// load environment variables from file.
godotenv.Load(c.envfile)
// load the configuration from the environment
config, err := fromEnviron()
if err != nil {
return err
}
// setup the global logrus logger.
setupLogger(config)
cli := client.New(
config.Client.Address,
config.Client.Secret,
config.Client.SkipVerify,
)
if config.Client.Dump {
cli.Dumper = logger.StandardDumper(
config.Client.DumpBody,
)
}
cli.Logger = logger.Logrus(
logrus.NewEntry(
logrus.StandardLogger(),
),
)
engine, err := engine.New(config.Keypair.Public, config.Keypair.Private)
if err != nil {
return err
}
remote := remote.New(cli)
tracer := history.New(remote)
hook := loghistory.New()
logrus.AddHook(hook)
poller := &runtime.Poller{
Client: cli,
Runner: &runtime.Runner{
Client: cli,
Environ: config.Runner.Environ,
Machine: config.Runner.Name,
Reporter: tracer,
Match: match.Func(
config.Limit.Repos,
config.Limit.Events,
config.Limit.Trusted,
),
Secret: secret.External(
config.Secret.Endpoint,
config.Secret.Token,
config.Secret.SkipVerify,
),
Execer: runtime.NewExecer(
tracer,
remote,
engine,
config.Runner.Procs,
),
},
Filter: &client.Filter{
Kind: resource.Kind,
Type: resource.Type,
Labels: config.Runner.Labels,
},
}
ctx, cancel := context.WithCancel(nocontext)
defer cancel()
// listen for termination signals to gracefully shutdown
// the runner daemon.
ctx = signal.WithContextFunc(ctx, func() {
println("received signal, terminating process")
cancel()
})
var g errgroup.Group
server := server.Server{
Addr: config.Server.Port,
Handler: router.New(tracer, hook, router.Config{
Username: config.Dashboard.Username,
Password: config.Dashboard.Password,
Realm: config.Dashboard.Realm,
}),
}
logrus.WithField("addr", config.Server.Port).
Infoln("starting the server")
g.Go(func() error {
return server.ListenAndServe(ctx)
})
// Ping the server and block until a successful connection
// to the server has been established.
for {
err := cli.Ping(ctx, config.Runner.Name)
select {
case <-ctx.Done():
return nil
default:
}
if ctx.Err() != nil {
break
}
if err != nil {
logrus.WithError(err).
Errorln("cannot ping the remote server")
time.Sleep(time.Second)
} else {
logrus.Infoln("successfully pinged the remote server")
break
}
}
g.Go(func() error {
logrus.WithField("capacity", config.Runner.Capacity).
WithField("endpoint", config.Client.Address).
WithField("kind", resource.Kind).
WithField("type", resource.Type).
Infoln("polling the remote server")
poller.Poll(ctx, config.Runner.Capacity)
return nil
})
err = g.Wait()
if err != nil {
logrus.WithError(err).
Errorln("shutting down the server")
}
return err
}
// helper function configures the global logger from
// the loaded configuration.
func setupLogger(config Config) {
logger.Default = logger.Logrus(
logrus.NewEntry(
logrus.StandardLogger(),
),
)
if config.Debug {
logrus.SetLevel(logrus.DebugLevel)
}
if config.Trace {
logrus.SetLevel(logrus.TraceLevel)
}
}
// Register the daemon command.
func Register(app *kingpin.Application) {
c := new(daemonCommand)
cmd := app.Command("daemon", "starts the runner daemon").
Default().
Action(c.run)
cmd.Arg("envfile", "load the environment variable file").
Default("").
StringVar(&c.envfile)
}

234
command/exec.go Normal file
View File

@@ -0,0 +1,234 @@
// 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/resource"
"github.com/drone-runners/drone-runner-docker/runtime"
"github.com/drone/drone-go/drone"
"github.com/drone/envsubst"
"github.com/drone/runner-go/environ"
"github.com/drone/runner-go/logger"
"github.com/drone/runner-go/manifest"
"github.com/drone/runner-go/pipeline"
"github.com/drone/runner-go/pipeline/console"
"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
Environ map[string]string
Secrets map[string]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.
resource, err := resource.Lookup(c.Stage.Name, manifest)
if err != nil {
return err
}
// 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),
}
spec := comp.Compile(nocontext)
// create a step object for each pipeline step.
for _, step := range spec.Steps {
if step.RunPolicy == engine.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.New(c.PublicKey, c.PrivateKey)
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{}
cmd := app.Command("exec", "executes a pipeline").
Action(c.run)
cmd.Arg("source", "source file location").
Default(".drone.yml").
FileVar(&c.Source)
cmd.Flag("secrets", "secret parameters").
StringMapVar(&c.Secrets)
cmd.Flag("environ", "environment variables").
StringMapVar(&c.Environ)
cmd.Flag("public-key", "public key file path").
ExistingFileVar(&c.PublicKey)
cmd.Flag("private-key", "private key file path").
ExistingFileVar(&c.PrivateKey)
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)
}

103
command/internal/flags.go Normal file
View File

@@ -0,0 +1,103 @@
// 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 internal
import (
"fmt"
"time"
"github.com/drone/drone-go/drone"
"gopkg.in/alecthomas/kingpin.v2"
)
// Flags maps
type Flags struct {
Build *drone.Build
Netrc *drone.Netrc
Repo *drone.Repo
Stage *drone.Stage
System *drone.System
}
// ParseFlags parses the flags from the command args.
func ParseFlags(cmd *kingpin.CmdClause) *Flags {
f := &Flags{
Build: &drone.Build{},
Netrc: &drone.Netrc{},
Repo: &drone.Repo{},
Stage: &drone.Stage{},
System: &drone.System{},
}
now := fmt.Sprint(
time.Now().Unix(),
)
cmd.Flag("repo-id", "repo id").Default("1").Int64Var(&f.Repo.ID)
cmd.Flag("repo-namespace", "repo namespace").Default("").StringVar(&f.Repo.Namespace)
cmd.Flag("repo-name", "repo name").Default("").StringVar(&f.Repo.Name)
cmd.Flag("repo-slug", "repo slug").Default("").StringVar(&f.Repo.Slug)
cmd.Flag("repo-http", "repo http clone url").Default("").StringVar(&f.Repo.HTTPURL)
cmd.Flag("repo-ssh", "repo ssh clone url").Default("").StringVar(&f.Repo.SSHURL)
cmd.Flag("repo-link", "repo link").Default("").StringVar(&f.Repo.Link)
cmd.Flag("repo-branch", "repo branch").Default("").StringVar(&f.Repo.Branch)
cmd.Flag("repo-private", "repo private").Default("false").BoolVar(&f.Repo.Private)
cmd.Flag("repo-visibility", "repo visibility").Default("").StringVar(&f.Repo.Visibility)
cmd.Flag("repo-trusted", "repo trusted").Default("false").BoolVar(&f.Repo.Trusted)
cmd.Flag("repo-protected", "repo protected").Default("false").BoolVar(&f.Repo.Protected)
cmd.Flag("repo-timeout", "repo timeout in minutes").Default("60").Int64Var(&f.Repo.Timeout)
cmd.Flag("repo-created", "repo created").Default(now).Int64Var(&f.Repo.Created)
cmd.Flag("repo-updated", "repo updated").Default(now).Int64Var(&f.Repo.Updated)
cmd.Flag("build-id", "build id").Default("1").Int64Var(&f.Build.ID)
cmd.Flag("build-number", "build number").Default("1").Int64Var(&f.Build.Number)
cmd.Flag("build-parent", "build parent").Default("0").Int64Var(&f.Build.Parent)
cmd.Flag("build-event", "build event").Default("push").StringVar(&f.Build.Event)
cmd.Flag("build-action", "build action").Default("").StringVar(&f.Build.Action)
cmd.Flag("build-cron", "build cron trigger").Default("").StringVar(&f.Build.Cron)
cmd.Flag("build-target", "build deploy target").Default("").StringVar(&f.Build.Deploy)
cmd.Flag("build-created", "build created").Default(now).Int64Var(&f.Build.Created)
cmd.Flag("build-updated", "build updated").Default(now).Int64Var(&f.Build.Updated)
cmd.Flag("commit-sender", "commit sender").Default("").StringVar(&f.Build.Sender)
cmd.Flag("commit-link", "commit link").Default("").StringVar(&f.Build.Link)
cmd.Flag("commit-title", "commit title").Default("").StringVar(&f.Build.Title)
cmd.Flag("commit-message", "commit message").Default("").StringVar(&f.Build.Message)
cmd.Flag("commit-before", "commit before").Default("").StringVar(&f.Build.Before)
cmd.Flag("commit-after", "commit after").Default("").StringVar(&f.Build.After)
cmd.Flag("commit-ref", "commit ref").Default("").StringVar(&f.Build.Ref)
cmd.Flag("commit-fork", "commit fork").Default("").StringVar(&f.Build.Fork)
cmd.Flag("commit-source", "commit source branch").Default("").StringVar(&f.Build.Source)
cmd.Flag("commit-target", "commit target branch").Default("").StringVar(&f.Build.Target)
cmd.Flag("author-login", "commit author login").Default("").StringVar(&f.Build.Author)
cmd.Flag("author-name", "commit author name").Default("").StringVar(&f.Build.AuthorName)
cmd.Flag("author-email", "commit author email").Default("").StringVar(&f.Build.AuthorEmail)
cmd.Flag("author-avatar", "commit author avatar").Default("").StringVar(&f.Build.AuthorAvatar)
cmd.Flag("stage-id", "stage id").Default("1").Int64Var(&f.Stage.ID)
cmd.Flag("stage-number", "stage number").Default("1").IntVar(&f.Stage.Number)
cmd.Flag("stage-kind", "stage kind").Default("").StringVar(&f.Stage.Kind)
cmd.Flag("stage-type", "stage type").Default("").StringVar(&f.Stage.Type)
cmd.Flag("stage-name", "stage name").Default("default").StringVar(&f.Stage.Name)
cmd.Flag("stage-os", "stage os").Default("").StringVar(&f.Stage.OS)
cmd.Flag("stage-arch", "stage arch").Default("").StringVar(&f.Stage.Arch)
cmd.Flag("stage-variant", "stage variant").Default("").StringVar(&f.Stage.Variant)
cmd.Flag("stage-kernel", "stage kernel").Default("").StringVar(&f.Stage.Kernel)
cmd.Flag("stage-created", "stage created").Default(now).Int64Var(&f.Stage.Created)
cmd.Flag("stage-updated", "stage updated").Default(now).Int64Var(&f.Stage.Updated)
cmd.Flag("netrc-username", "netrc username").Default("").StringVar(&f.Netrc.Login)
cmd.Flag("netrc-password", "netrc password").Default("").StringVar(&f.Netrc.Password)
cmd.Flag("netrc-machine", "netrc machine").Default("").StringVar(&f.Netrc.Machine)
cmd.Flag("system-host", "server host").Default("").StringVar(&f.System.Host)
cmd.Flag("system-proto", "server proto").Default("").StringVar(&f.System.Proto)
cmd.Flag("system-link", "server link").Default("").StringVar(&f.System.Link)
cmd.Flag("system-version", "server version").Default("").StringVar(&f.System.Version)
return f
}

View File

@@ -0,0 +1,9 @@
# escape=`
FROM mcr.microsoft.com/windows/nanoserver:1809
USER ContainerAdministrator
EXPOSE 3000
ENV GODEBUG=netdns=go
ADD release/windows/amd64/drone-runner-docker.exe C:/drone-runner-docker.exe
ENTRYPOINT [ "C:\\drone-runner-docker.exe" ]

View File

@@ -0,0 +1,9 @@
# escape=`
FROM mcr.microsoft.com/windows/nanoserver:1903
USER ContainerAdministrator
EXPOSE 3000
ENV GODEBUG=netdns=go
ADD release/windows/amd64/drone-runner-docker.exe C:/drone-runner-docker.exe
ENTRYPOINT [ "C:\\drone-runner-docker.exe" ]

View File

@@ -0,0 +1,12 @@
FROM alpine:3.6 as alpine
RUN apk add -U --no-cache ca-certificates
FROM alpine:3.6
EXPOSE 3000
ENV GODEBUG netdns=go
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ADD release/linux/amd64/drone-runner-docker /bin/
ENTRYPOINT ["/bin/drone-runner-docker"]

View File

@@ -0,0 +1,12 @@
FROM alpine:3.6 as alpine
RUN apk add -U --no-cache ca-certificates
FROM alpine:3.6
EXPOSE 3000
ENV GODEBUG netdns=go
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ADD release/linux/arm/drone-runner-docker /bin/
ENTRYPOINT ["/bin/drone-runner-docker"]

View File

@@ -0,0 +1,12 @@
FROM alpine:3.6 as alpine
RUN apk add -U --no-cache ca-certificates
FROM alpine:3.6
EXPOSE 3000
ENV GODEBUG netdns=go
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ADD release/linux/arm64/drone-runner-docker /bin/
ENTRYPOINT ["/bin/drone-runner-docker"]

53
engine/compiler/clone.go Normal file
View File

@@ -0,0 +1,53 @@
// 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 compiler
import (
"strconv"
"github.com/drone-runners/drone-runner-docker/engine"
"github.com/drone-runners/drone-runner-docker/engine/resource"
"github.com/drone/runner-go/manifest"
)
// default name of the clone step.
const cloneStepName = "clone"
// helper function returns the clone image based on the
// target operating system.
func cloneImage(platform manifest.Platform) string {
switch platform.OS {
case "windows":
return "drone/git:latest"
default:
return "drone/git:1"
}
}
// helper function configures the clone depth parameter,
// specific to the clone plugin.
func cloneParams(src manifest.Clone) map[string]string {
dst := map[string]string{}
if depth := src.Depth; depth > 0 {
dst["PLUGIN_DEPTH"] = strconv.Itoa(depth)
}
if skipVerify := src.SkipVerify; skipVerify {
dst["GIT_SSL_NO_VERIFY"] = "true"
dst["PLUGIN_SKIP_VERIFY"] = "true"
}
return dst
}
// helper function creates a default container configuration
// for the clone stage. The clone stage is automatically
// added to each pipeline.
func createClone(src *resource.Pipeline) *engine.Step {
return &engine.Step{
Name: cloneStepName,
Image: cloneImage(src.Platform),
RunPolicy: engine.RunAlways,
Envs: cloneParams(src.Clone),
}
}

View File

@@ -0,0 +1,126 @@
// 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 compiler
import (
"testing"
"github.com/dchest/uniuri"
"github.com/drone-runners/drone-runner-docker/engine"
"github.com/drone-runners/drone-runner-docker/engine/resource"
"github.com/drone/drone-go/drone"
"github.com/drone/runner-go/manifest"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)
func TestClone(t *testing.T) {
random = notRandom
defer func() {
random = uniuri.New
}()
c := &Compiler{
Repo: &drone.Repo{},
Build: &drone.Build{},
Stage: &drone.Stage{},
System: &drone.System{},
Netrc: &drone.Netrc{},
Manifest: &manifest.Manifest{},
Pipeline: &resource.Pipeline{},
}
want := []*engine.Step{
{
ID: "random",
Image: "drone/git:1",
Name: "clone",
RunPolicy: engine.RunAlways,
WorkingDir: "/drone/src",
},
}
got := c.Compile(nil)
ignore := cmpopts.IgnoreFields(engine.Step{}, "Envs")
if diff := cmp.Diff(got.Steps, want, ignore); len(diff) != 0 {
t.Errorf(diff)
}
}
func TestCloneDisable(t *testing.T) {
c := &Compiler{
Repo: &drone.Repo{},
Build: &drone.Build{},
Stage: &drone.Stage{},
System: &drone.System{},
Netrc: &drone.Netrc{},
Manifest: &manifest.Manifest{},
Pipeline: &resource.Pipeline{Clone: manifest.Clone{Disable: true}},
}
got := c.Compile(nil)
if len(got.Steps) != 0 {
t.Errorf("Expect no clone step added when disabled")
}
}
func TestCloneCreate(t *testing.T) {
want := &engine.Step{
Name: "clone",
Image: "drone/git:1",
RunPolicy: engine.RunAlways,
Envs: map[string]string{"PLUGIN_DEPTH": "50"},
}
src := &resource.Pipeline{Clone: manifest.Clone{Depth: 50}}
got := createClone(src)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
func TestCloneImage(t *testing.T) {
tests := []struct {
in manifest.Platform
out string
}{
{
in: manifest.Platform{},
out: "drone/git:1",
},
{
in: manifest.Platform{OS: "linux"},
out: "drone/git:1",
},
{
in: manifest.Platform{OS: "windows"},
out: "drone/git:latest",
},
}
for _, test := range tests {
got, want := cloneImage(test.in), test.out
if got != want {
t.Errorf("Want clone image %q, got %q", want, got)
}
}
}
func TestCloneParams(t *testing.T) {
params := cloneParams(manifest.Clone{})
if len(params) != 0 {
t.Errorf("Expect empty clone parameters")
}
params = cloneParams(manifest.Clone{Depth: 0})
if len(params) != 0 {
t.Errorf("Expect zero depth ignored")
}
params = cloneParams(manifest.Clone{Depth: 50, SkipVerify: true})
if params["PLUGIN_DEPTH"] != "50" {
t.Errorf("Expect clone depth 50")
}
if params["GIT_SSL_NO_VERIFY"] != "true" {
t.Errorf("Expect GIT_SSL_NO_VERIFY is true")
}
if params["PLUGIN_SKIP_VERIFY"] != "true" {
t.Errorf("Expect PLUGIN_SKIP_VERIFY is true")
}
}

238
engine/compiler/compiler.go Normal file
View File

@@ -0,0 +1,238 @@
// 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 compiler
import (
"context"
"fmt"
"github.com/drone-runners/drone-runner-docker/engine"
"github.com/drone-runners/drone-runner-docker/engine/resource"
"github.com/drone/drone-go/drone"
"github.com/drone/runner-go/clone"
"github.com/drone/runner-go/environ"
"github.com/drone/runner-go/manifest"
"github.com/drone/runner-go/secret"
"github.com/dchest/uniuri"
)
// random generator function
var random = uniuri.New
// Compiler compiles the Yaml configuration file to an
// intermediate representation optimized for simple execution.
type Compiler struct {
// Manifest provides the parsed manifest.
Manifest *manifest.Manifest
// Pipeline provides the parsed pipeline. This pipeline is
// the compiler source and is converted to the intermediate
// representation by the Compile method.
Pipeline *resource.Pipeline
// Build provides the compiler with stage information that
// is converted to environment variable format and passed to
// each pipeline step. It is also used to clone the commit.
Build *drone.Build
// Stage provides the compiler with stage information that
// is converted to environment variable format and passed to
// each pipeline step.
Stage *drone.Stage
// Repo provides the compiler with repo information. This
// repo information is converted to environment variable
// format and passed to each pipeline step. It is also used
// to clone the repository.
Repo *drone.Repo
// System provides the compiler with system information that
// is converted to environment variable format and passed to
// each pipeline step.
System *drone.System
// Environ provides a set of environment variables that
// should be added to each pipeline step by default.
Environ map[string]string
// Netrc provides netrc parameters that can be used by the
// default clone step to authenticate to the remote
// repository.
Netrc *drone.Netrc
// Secret returns a named secret value that can be injected
// into the pipeline step.
Secret secret.Provider
}
// Compile compiles the configuration file.
func (c *Compiler) Compile(ctx context.Context) *engine.Spec {
os := c.Pipeline.Platform.OS
// create the workspace paths
base, path, full := createWorkspace(c.Pipeline)
// create the workspace mount
mount := &engine.VolumeMount{
Name: "_workspace",
Path: base,
}
// create the workspace volume
volume := &engine.VolumeEmptyDir{
ID: random(),
Name: mount.Name,
}
spec := &engine.Spec{
Network: engine.Network{
ID: random(),
},
Platform: engine.Platform{
OS: c.Pipeline.Platform.OS,
Arch: c.Pipeline.Platform.Arch,
Variant: c.Pipeline.Platform.Variant,
Version: c.Pipeline.Platform.Version,
},
Volumes: []*engine.Volume{
{EmptyDir: volume},
},
}
// create the default environment variables.
envs := environ.Combine(
c.Environ,
c.Build.Params,
environ.Proxy(),
environ.System(c.System),
environ.Repo(c.Repo),
environ.Build(c.Build),
environ.Stage(c.Stage),
environ.Link(c.Repo, c.Build, c.System),
clone.Environ(clone.Config{
SkipVerify: c.Pipeline.Clone.SkipVerify,
Trace: c.Pipeline.Clone.Trace,
User: clone.User{
Name: c.Build.AuthorName,
Email: c.Build.AuthorEmail,
},
}),
)
// create docker reference variables
envs["DRONE_DOCKER_VOLUME_ID"] = volume.ID
envs["DRONE_DOCKER_NETWORK_ID"] = spec.Network.ID
// create the workspace variables
envs["DRONE_WORKSPACE"] = full
envs["DRONE_WORKSPACE_BASE"] = base
envs["DRONE_WORKSPACE_PATH"] = path
// create the netrc environment variables
if c.Netrc != nil && c.Netrc.Machine != "" {
envs["DRONE_NETRC_MACHINE"] = c.Netrc.Machine
envs["DRONE_NETRC_USERNAME"] = c.Netrc.Login
envs["DRONE_NETRC_PASSWORD"] = c.Netrc.Password
envs["DRONE_NETRC_FILE"] = fmt.Sprintf(
"machine %s login %s password %s",
c.Netrc.Machine,
c.Netrc.Login,
c.Netrc.Password,
)
}
match := manifest.Match{
Action: c.Build.Action,
Cron: c.Build.Cron,
Ref: c.Build.Ref,
Repo: c.Repo.Slug,
Instance: c.System.Host,
Target: c.Build.Deploy,
Event: c.Build.Event,
Branch: c.Build.Target,
}
// create the clone step
if c.Pipeline.Clone.Disable == false {
step := createClone(c.Pipeline)
step.ID = random()
step.Envs = environ.Combine(envs, step.Envs)
step.WorkingDir = full
step.Volumes = append(step.Volumes, mount)
spec.Steps = append(spec.Steps, step)
}
// create steps
for _, src := range c.Pipeline.Services {
dst := createStep(c.Pipeline, src)
dst.Detach = true
dst.Envs = environ.Combine(envs, dst.Envs)
dst.Volumes = append(dst.Volumes, mount)
setupScript(src, dst, os)
setupWorkdir(src, dst, full)
spec.Steps = append(spec.Steps, dst)
// if the pipeline step has unmet conditions the step is
// automatically skipped.
if !src.When.Match(match) {
dst.RunPolicy = engine.RunNever
}
}
// create steps
for _, src := range c.Pipeline.Steps {
dst := createStep(c.Pipeline, src)
dst.Envs = environ.Combine(envs, dst.Envs)
dst.Volumes = append(dst.Volumes, mount)
setupScript(src, dst, full)
setupWorkdir(src, dst, full)
spec.Steps = append(spec.Steps, dst)
// if the pipeline step has unmet conditions the step is
// automatically skipped.
if !src.When.Match(match) {
dst.RunPolicy = engine.RunNever
}
}
if isGraph(spec) == false {
configureSerial(spec)
} else if c.Pipeline.Clone.Disable == false {
configureCloneDeps(spec)
} else if c.Pipeline.Clone.Disable == true {
removeCloneDeps(spec)
}
for _, step := range spec.Steps {
for _, s := range step.Secrets {
secret, ok := c.findSecret(ctx, s.Name)
if ok {
s.Data = []byte(secret)
}
}
}
return spec
}
// 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) {
if name == "" {
return
}
found, _ := c.Secret.Find(ctx, &secret.Request{
Name: name,
Build: c.Build,
Repo: c.Repo,
Conf: c.Manifest,
})
if found == nil {
return
}
return found.Data, true
}

View File

@@ -0,0 +1,186 @@
// 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.
// +build !windows
package compiler
import (
"context"
"encoding/json"
"io/ioutil"
"os"
"testing"
"github.com/dchest/uniuri"
"github.com/drone-runners/drone-runner-docker/engine"
"github.com/drone-runners/drone-runner-docker/engine/resource"
"github.com/drone/drone-go/drone"
"github.com/drone/runner-go/manifest"
"github.com/drone/runner-go/secret"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)
var nocontext = context.Background()
// dummy function that returns a non-random string for testing.
// it is used in place of the random function.
func notRandom() string {
return "random"
}
// This test verifies the pipeline dependency graph. When no
// dependency graph is defined, a default dependency graph is
// automatically defined to run steps serially.
func TestCompile_Serial(t *testing.T) {
testCompile(t, "testdata/serial.yml", "testdata/serial.json")
}
// This test verifies the pipeline dependency graph. It also
// verifies that pipeline steps with no dependencies depend on
// the initial clone step.
func TestCompile_Graph(t *testing.T) {
testCompile(t, "testdata/graph.yml", "testdata/graph.json")
}
// This test verifies no clone step exists in the pipeline if
// cloning is disabled.
func TestCompile_CloneDisabled_Serial(t *testing.T) {
testCompile(t, "testdata/noclone_serial.yml", "testdata/noclone_serial.json")
}
// This test verifies no clone step exists in the pipeline if
// cloning is disabled. It also verifies no pipeline steps
// depend on a clone step.
func TestCompile_CloneDisabled_Graph(t *testing.T) {
testCompile(t, "testdata/noclone_graph.yml", "testdata/noclone_graph.json")
}
// This test verifies that steps are disabled if conditions
// defined in the when block are not satisfied.
func TestCompile_Match(t *testing.T) {
ir := testCompile(t, "testdata/match.yml", "testdata/match.json")
if ir.Steps[0].RunPolicy != engine.RunOnSuccess {
t.Errorf("Expect run on success")
}
if ir.Steps[1].RunPolicy != engine.RunNever {
t.Errorf("Expect run never")
}
}
// This test verifies that steps configured to run on both
// success or failure are configured to always run.
func TestCompile_RunAlways(t *testing.T) {
ir := testCompile(t, "testdata/run_always.yml", "testdata/run_always.json")
if ir.Steps[0].RunPolicy != engine.RunAlways {
t.Errorf("Expect run always")
}
}
// This test verifies that steps configured to run on failure
// are configured to run on failure.
func TestCompile_RunFaiure(t *testing.T) {
ir := testCompile(t, "testdata/run_failure.yml", "testdata/run_failure.json")
if ir.Steps[0].RunPolicy != engine.RunOnFailure {
t.Errorf("Expect run on failure")
}
}
// This test verifies that secrets defined in the yaml are
// requested and stored in the intermediate representation
// at compile time.
func TestCompile_Secrets(t *testing.T) {
manifest, _ := manifest.ParseFile("testdata/secret.yml")
compiler := Compiler{}
compiler.Build = &drone.Build{}
compiler.Repo = &drone.Repo{}
compiler.Stage = &drone.Stage{}
compiler.System = &drone.System{}
compiler.Netrc = &drone.Netrc{}
compiler.Manifest = manifest
compiler.Pipeline = manifest.Resources[0].(*resource.Pipeline)
compiler.Secret = secret.StaticVars(map[string]string{
"token": "3DA541559918A808C2402BBA5012F6C60B27661C",
"password": "password",
"my_username": "octocat",
})
ir := compiler.Compile(nocontext)
got := ir.Steps[0].Secrets
want := []*engine.Secret{
{
Name: "my_password",
Env: "PASSWORD",
Data: nil, // secret not found, data nil
Mask: true,
},
{
Name: "my_username",
Env: "USERNAME",
Data: []byte("octocat"), // secret found
Mask: true,
},
}
if diff := cmp.Diff(got, want); len(diff) != 0 {
// TODO(bradrydzewski) ordering is not guaranteed. this
// unit tests needs to be adjusted accordingly.
t.Skipf(diff)
}
}
// helper function parses and compiles the source file and then
// compares to a golden json file.
func testCompile(t *testing.T, source, golden string) *engine.Spec {
// replace the default random function with one that
// is deterministic, for testing purposes.
random = notRandom
// restore the default random function and the previously
// specified temporary directory
defer func() {
random = uniuri.New
}()
manifest, err := manifest.ParseFile(source)
if err != nil {
t.Error(err)
return nil
}
compiler := Compiler{}
compiler.Build = &drone.Build{Target: "master"}
compiler.Repo = &drone.Repo{}
compiler.Stage = &drone.Stage{}
compiler.System = &drone.System{}
compiler.Netrc = &drone.Netrc{Machine: "github.com", Login: "octocat", Password: "correct-horse-battery-staple"}
compiler.Manifest = manifest
compiler.Pipeline = manifest.Resources[0].(*resource.Pipeline)
got := compiler.Compile(nocontext)
raw, err := ioutil.ReadFile(golden)
if err != nil {
t.Error(err)
}
want := new(engine.Spec)
err = json.Unmarshal(raw, want)
if err != nil {
t.Error(err)
}
ignore := cmpopts.IgnoreFields(engine.Step{}, "Envs", "Secrets")
unexported := cmpopts.IgnoreUnexported(engine.Spec{})
if diff := cmp.Diff(got, want, ignore, unexported); len(diff) != 0 {
t.Errorf(diff)
}
return got
}
func dump(v interface{}) {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.Encode(v)
}

View File

@@ -0,0 +1,56 @@
// 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 encoder
import (
"encoding/base64"
"strconv"
"strings"
"github.com/buildkite/yaml"
json "github.com/ghodss/yaml"
)
// Encode encodes an interface value as a string. This function
// assumes all types were unmarshaled by the yaml.v2 library.
// The yaml.v2 package only supports a subset of primative types.
func Encode(v interface{}) string {
switch v := v.(type) {
case string:
return v
case bool:
return strconv.FormatBool(v)
case int:
return strconv.Itoa(v)
case float64:
return strconv.FormatFloat(v, 'g', -1, 64)
case []byte:
return base64.StdEncoding.EncodeToString(v)
case []interface{}:
return encodeSlice(v)
default:
return encodeMap(v)
}
}
// helper function encodes a parameter in map format.
func encodeMap(v interface{}) string {
yml, _ := yaml.Marshal(v)
out, _ := json.YAMLToJSON(yml)
return string(out)
}
// helper function encodes a parameter in slice format.
func encodeSlice(v interface{}) string {
out, _ := yaml.Marshal(v)
in := []string{}
err := yaml.Unmarshal(out, &in)
if err == nil {
return strings.Join(in, ",")
}
out, _ = json.YAMLToJSON(out)
return string(out)
}

View File

@@ -0,0 +1,63 @@
// 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 encoder
import "testing"
func TestEncode(t *testing.T) {
testdatum := []struct {
data interface{}
text string
}{
{
data: "foo",
text: "foo",
},
{
data: true,
text: "true",
},
{
data: 42,
text: "42",
},
{
data: float64(42.424242),
text: "42.424242",
},
{
data: []interface{}{"foo", "bar", "baz"},
text: "foo,bar,baz",
},
{
data: []interface{}{1, 1, 2, 3, 5, 8},
text: "1,1,2,3,5,8",
},
{
data: []byte("foo"),
text: "Zm9v",
},
{
data: []interface{}{
struct {
Name string `json:"name"`
}{
Name: "john",
},
},
text: `[{"name":"john"}]`,
},
{
data: map[interface{}]interface{}{"foo": "bar"},
text: `{"foo":"bar"}`,
},
}
for _, testdata := range testdatum {
if got, want := Encode(testdata.data), testdata.text; got != want {
t.Errorf("Want interface{} encoded to %q, got %q", want, got)
}
}
}

View File

@@ -0,0 +1,71 @@
// 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 image
import "github.com/docker/distribution/reference"
// Trim returns the short image name without tag.
func Trim(name string) string {
ref, err := reference.ParseAnyReference(name)
if err != nil {
return name
}
named, err := reference.ParseNamed(ref.String())
if err != nil {
return name
}
named = reference.TrimNamed(named)
return reference.FamiliarName(named)
}
// Expand returns the fully qualified image name.
func Expand(name string) string {
ref, err := reference.ParseAnyReference(name)
if err != nil {
return name
}
named, err := reference.ParseNamed(ref.String())
if err != nil {
return name
}
named = reference.TagNameOnly(named)
return named.String()
}
// Match returns true if the image name matches
// an image in the list. Note the image tag is not used
// in the matching logic.
func Match(from string, to ...string) bool {
from = Trim(from)
for _, match := range to {
if from == Trim(match) {
return true
}
}
return false
}
// MatchTag returns true if the image name matches
// an image in the list, including the tag.
func MatchTag(a, b string) bool {
return Expand(a) == Expand(b)
}
// MatchHostname returns true if the image hostname
// matches the specified hostname.
func MatchHostname(image, hostname string) bool {
ref, err := reference.ParseAnyReference(image)
if err != nil {
return false
}
named, err := reference.ParseNamed(ref.String())
if err != nil {
return false
}
if hostname == "index.docker.io" {
hostname = "docker.io"
}
return reference.Domain(named) == hostname
}

View File

@@ -0,0 +1,299 @@
// 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 image
import "testing"
func Test_trimImage(t *testing.T) {
testdata := []struct {
from string
want string
}{
{
from: "golang",
want: "golang",
},
{
from: "golang:latest",
want: "golang",
},
{
from: "golang:1.0.0",
want: "golang",
},
{
from: "library/golang",
want: "golang",
},
{
from: "library/golang:latest",
want: "golang",
},
{
from: "library/golang:1.0.0",
want: "golang",
},
{
from: "index.docker.io/library/golang:1.0.0",
want: "golang",
},
{
from: "docker.io/library/golang:1.0.0",
want: "golang",
},
{
from: "gcr.io/library/golang:1.0.0",
want: "gcr.io/library/golang",
},
// error cases, return input unmodified
{
from: "foo/bar?baz:boo",
want: "foo/bar?baz:boo",
},
}
for _, test := range testdata {
got, want := Trim(test.from), test.want
if got != want {
t.Errorf("Want image %q trimmed to %q, got %q", test.from, want, got)
}
}
}
func Test_expandImage(t *testing.T) {
testdata := []struct {
from string
want string
}{
{
from: "golang",
want: "docker.io/library/golang:latest",
},
{
from: "golang:latest",
want: "docker.io/library/golang:latest",
},
{
from: "golang:1.0.0",
want: "docker.io/library/golang:1.0.0",
},
{
from: "library/golang",
want: "docker.io/library/golang:latest",
},
{
from: "library/golang:latest",
want: "docker.io/library/golang:latest",
},
{
from: "library/golang:1.0.0",
want: "docker.io/library/golang:1.0.0",
},
{
from: "index.docker.io/library/golang:1.0.0",
want: "docker.io/library/golang:1.0.0",
},
{
from: "gcr.io/golang",
want: "gcr.io/golang:latest",
},
{
from: "gcr.io/golang:1.0.0",
want: "gcr.io/golang:1.0.0",
},
// error cases, return input unmodified
{
from: "foo/bar?baz:boo",
want: "foo/bar?baz:boo",
},
}
for _, test := range testdata {
got, want := Expand(test.from), test.want
if got != want {
t.Errorf("Want image %q expanded to %q, got %q", test.from, want, got)
}
}
}
func Test_matchImage(t *testing.T) {
testdata := []struct {
from, to string
want bool
}{
{
from: "golang",
to: "golang",
want: true,
},
{
from: "golang:latest",
to: "golang",
want: true,
},
{
from: "library/golang:latest",
to: "golang",
want: true,
},
{
from: "index.docker.io/library/golang:1.0.0",
to: "golang",
want: true,
},
{
from: "golang",
to: "golang:latest",
want: true,
},
{
from: "library/golang:latest",
to: "library/golang",
want: true,
},
{
from: "gcr.io/golang",
to: "gcr.io/golang",
want: true,
},
{
from: "gcr.io/golang:1.0.0",
to: "gcr.io/golang",
want: true,
},
{
from: "gcr.io/golang:latest",
to: "gcr.io/golang",
want: true,
},
{
from: "gcr.io/golang",
to: "gcr.io/golang:latest",
want: true,
},
{
from: "golang",
to: "library/golang",
want: true,
},
{
from: "golang",
to: "gcr.io/project/golang",
want: false,
},
{
from: "golang",
to: "gcr.io/library/golang",
want: false,
},
{
from: "golang",
to: "gcr.io/golang",
want: false,
},
}
for _, test := range testdata {
got, want := Match(test.from, test.to), test.want
if got != want {
t.Errorf("Want image %q matching %q is %v", test.from, test.to, want)
}
}
}
func Test_matchHostname(t *testing.T) {
testdata := []struct {
image, hostname string
want bool
}{
{
image: "golang",
hostname: "docker.io",
want: true,
},
{
image: "golang:latest",
hostname: "docker.io",
want: true,
},
{
image: "golang:latest",
hostname: "index.docker.io",
want: true,
},
{
image: "library/golang:latest",
hostname: "docker.io",
want: true,
},
{
image: "docker.io/library/golang:1.0.0",
hostname: "docker.io",
want: true,
},
{
image: "gcr.io/golang",
hostname: "docker.io",
want: false,
},
{
image: "gcr.io/golang:1.0.0",
hostname: "gcr.io",
want: true,
},
{
image: "1.2.3.4:8000/golang:1.0.0",
hostname: "1.2.3.4:8000",
want: true,
},
{
image: "*&^%",
hostname: "1.2.3.4:8000",
want: false,
},
}
for _, test := range testdata {
got, want := MatchHostname(test.image, test.hostname), test.want
if got != want {
t.Errorf("Want image %q matching hostname %q is %v", test.image, test.hostname, want)
}
}
}
func Test_matchTag(t *testing.T) {
testdata := []struct {
a, b string
want bool
}{
{
a: "golang:1.0",
b: "golang:1.0",
want: true,
},
{
a: "golang",
b: "golang:latest",
want: true,
},
{
a: "docker.io/library/golang",
b: "golang:latest",
want: true,
},
{
a: "golang",
b: "golang:1.0",
want: false,
},
{
a: "golang:1.0",
b: "golang:2.0",
want: false,
},
}
for _, test := range testdata {
got, want := MatchTag(test.a, test.b), test.want
if got != want {
t.Errorf("Want image %q matching image tag %q is %v", test.a, test.b, want)
}
}
}

85
engine/compiler/os.go Normal file
View File

@@ -0,0 +1,85 @@
// 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 compiler
import (
"fmt"
"strings"
"github.com/drone/runner-go/shell/bash"
"github.com/drone/runner-go/shell/powershell"
)
// helper function returns the base temporary directory based
// on the target platform.
func tempdir(os string) string {
dir := fmt.Sprintf("drone-%s", random())
switch os {
case "windows":
return join(os, "C:\\Windows\\Temp", dir)
default:
return join(os, "/tmp", dir)
}
}
// helper function joins the file paths.
func join(os string, paths ...string) string {
switch os {
case "windows":
return strings.Join(paths, "\\")
default:
return strings.Join(paths, "/")
}
}
// helper function returns the shell extension based on the
// target platform.
func getExt(os, file string) (s string) {
switch os {
case "windows":
return file + ".ps1"
default:
return file
}
}
//
// TODO(bradrydzewski) can we remove the below functions?
//
// helper function returns the shell command and arguments
// based on the target platform to invoke the script
func getCommand(os, script string) (string, []string) {
cmd, args := bash.Command()
switch os {
case "windows":
cmd, args = powershell.Command()
}
return cmd, append(args, script)
}
// helper function returns the netrc file name based on the
// target platform.
func getNetrc(os string) string {
switch os {
case "windows":
return "_netrc"
default:
return ".netrc"
}
}
// helper function generates and returns a shell script to
// execute the provided shell commands. The shell scripting
// language (bash vs pwoershell) is determined by the operating
// system.
func genScript(os string, commands []string) string {
switch os {
case "windows":
return powershell.Script(commands)
default:
return bash.Script(commands)
}
}

128
engine/compiler/os_test.go Normal file
View File

@@ -0,0 +1,128 @@
// 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 compiler
import (
"reflect"
"testing"
"github.com/drone/runner-go/shell/bash"
"github.com/drone/runner-go/shell/powershell"
"github.com/dchest/uniuri"
)
func Test_tempdir(t *testing.T) {
// replace the default random function with one that
// is deterministic, for testing purposes.
random = notRandom
// restore the default random function and the previously
// specified temporary directory
defer func() {
random = uniuri.New
}()
tests := []struct {
os string
path string
}{
{os: "windows", path: "C:\\Windows\\Temp\\drone-random"},
{os: "linux", path: "/tmp/drone-random"},
{os: "openbsd", path: "/tmp/drone-random"},
{os: "netbsd", path: "/tmp/drone-random"},
{os: "freebsd", path: "/tmp/drone-random"},
}
for _, test := range tests {
if got, want := tempdir(test.os), test.path; got != want {
t.Errorf("Want tempdir %s, got %s", want, got)
}
}
}
func Test_join(t *testing.T) {
tests := []struct {
os string
a []string
b string
}{
{os: "windows", a: []string{"C:", "Windows", "Temp"}, b: "C:\\Windows\\Temp"},
{os: "linux", a: []string{"/tmp", "foo", "bar"}, b: "/tmp/foo/bar"},
}
for _, test := range tests {
if got, want := join(test.os, test.a...), test.b; got != want {
t.Errorf("Want %s, got %s", want, got)
}
}
}
func Test_getExt(t *testing.T) {
tests := []struct {
os string
a string
b string
}{
{os: "windows", a: "clone", b: "clone.ps1"},
{os: "linux", a: "clone", b: "clone"},
}
for _, test := range tests {
if got, want := getExt(test.os, test.a), test.b; got != want {
t.Errorf("Want %s, got %s", want, got)
}
}
}
func Test_getCommand(t *testing.T) {
cmd, args := getCommand("linux", "clone.sh")
if got, want := cmd, "/bin/sh"; got != want {
t.Errorf("Want command %s, got %s", want, got)
}
if !reflect.DeepEqual(args, []string{"-e", "clone.sh"}) {
t.Errorf("Unexpected args %v", args)
}
cmd, args = getCommand("windows", "clone.ps1")
if got, want := cmd, "powershell"; got != want {
t.Errorf("Want command %s, got %s", want, got)
}
if !reflect.DeepEqual(args, []string{"-noprofile", "-noninteractive", "-command", "clone.ps1"}) {
t.Errorf("Unexpected args %v", args)
}
}
func Test_getNetrc(t *testing.T) {
tests := []struct {
os string
name string
}{
{os: "windows", name: "_netrc"},
{os: "linux", name: ".netrc"},
{os: "openbsd", name: ".netrc"},
{os: "netbsd", name: ".netrc"},
{os: "freebsd", name: ".netrc"},
}
for _, test := range tests {
if got, want := getNetrc(test.os), test.name; got != want {
t.Errorf("Want %s on %s, got %s", want, test.os, got)
}
}
}
func Test_getScript(t *testing.T) {
commands := []string{"go build"}
a := genScript("windows", commands)
b := powershell.Script(commands)
if !reflect.DeepEqual(a, b) {
t.Errorf("Generated windows linux script")
}
a = genScript("linux", commands)
b = bash.Script(commands)
if !reflect.DeepEqual(a, b) {
t.Errorf("Generated invalid linux script")
}
}

35
engine/compiler/script.go Normal file
View File

@@ -0,0 +1,35 @@
// 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 compiler
import (
"github.com/drone-runners/drone-runner-docker/engine"
"github.com/drone-runners/drone-runner-docker/engine/compiler/shell"
"github.com/drone-runners/drone-runner-docker/engine/compiler/shell/powershell"
"github.com/drone-runners/drone-runner-docker/engine/resource"
)
func setupScript(src *resource.Step, dst *engine.Step, os string) {
if len(src.Commands) > 0 {
switch os {
case "windows":
setupScriptWindows(src, dst)
default:
setupScriptPosix(src, dst)
}
}
}
func setupScriptWindows(src *resource.Step, dst *engine.Step) {
dst.Entrypoint = []string{"powershell", "-noprofile", "-noninteractive", "-command"}
dst.Command = []string{"echo $DRONE_SCRIPT | iex"}
dst.Envs["DRONE_SCRIPT"] = powershell.Script(src.Commands)
}
func setupScriptPosix(src *resource.Step, dst *engine.Step) {
dst.Entrypoint = []string{"/bin/sh", "-c"}
dst.Command = []string{"echo $DRONE_SCRIPT | /bin/sh -e"}
dst.Envs["DRONE_SCRIPT"] = shell.Script(src.Commands)
}

View File

@@ -0,0 +1,5 @@
// 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 compiler

View File

@@ -0,0 +1,59 @@
// 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 powershell provides functions for converting shell
// commands to powershell scripts.
package powershell
import (
"bytes"
"fmt"
"strings"
)
// Script converts a slice of individual shell commands to
// a powershell script.
func Script(commands []string) string {
buf := new(bytes.Buffer)
fmt.Fprintln(buf)
fmt.Fprintf(buf, optionScript)
fmt.Fprintln(buf)
for _, command := range commands {
escaped := fmt.Sprintf("%q", "+ "+command)
escaped = strings.Replace(escaped, "$", "`$", -1)
buf.WriteString(fmt.Sprintf(
traceScript,
escaped,
command,
))
}
return buf.String()
}
// optionScript is a helper script this is added to the build
// to set shell options, in this case, to exit on error.
const optionScript = `
if ($Env:DRONE_NETRC_MACHINE) {
@"
machine $Env:DRONE_NETRC_MACHINE
login $Env:DRONE_NETRC_USERNAME
password $Env:DRONE_NETRC_PASSWORD
"@ > (Join-Path $Env:USERPROFILE '_netrc');
}
[Environment]::SetEnvironmentVariable("DRONE_NETRC_USERNAME", $null);
[Environment]::SetEnvironmentVariable("DRONE_NETRC_PASSWORD", $null);
[Environment]::SetEnvironmentVariable("DRONE_NETRC_USERNAME", $null);
[Environment]::SetEnvironmentVariable("DRONE_NETRC_PASSWORD", $null);
[Environment]::SetEnvironmentVariable("DRONE_SCRIPT", $null);
$erroractionpreference = "stop"
`
// traceScript is a helper script that is added to
// the build script to trace a command.
const traceScript = `
echo %s
%s
if ($LastExitCode -ne 0) { exit $LastExitCode }
`

View File

@@ -0,0 +1,5 @@
// 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 powershell

View File

@@ -0,0 +1,56 @@
// 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 shell provides functions for converting shell commands
// to posix shell scripts.
package shell
import (
"bytes"
"fmt"
"strings"
)
// Script converts a slice of individual shell commands to
// a posix-compliant shell script.
func Script(commands []string) string {
buf := new(bytes.Buffer)
fmt.Fprintln(buf)
fmt.Fprintf(buf, optionScript)
fmt.Fprintln(buf)
for _, command := range commands {
escaped := fmt.Sprintf("%q", command)
escaped = strings.Replace(escaped, "$", `\$`, -1)
buf.WriteString(fmt.Sprintf(
traceScript,
escaped,
command,
))
}
return buf.String()
}
// optionScript is a helper script this is added to the build
// to set shell options, in this case, to exit on error.
const optionScript = `
if [[ ! -z "${DRONE_NETRC_FILE}" ]]; then
echo $DRONE_NETRC_FILE > $HOME/.netrc
EOF
fi
unset DRONE_SCRIPT
unset DRONE_NETRC_MACHINE
unset DRONE_NETRC_USERNAME
unset DRONE_NETRC_PASSWORD
unset DRONE_NETRC_FILE
set -e
`
// traceScript is a helper script that is added to
// the build script to trace a command.
const traceScript = `
echo + %s
%s
`

View File

@@ -0,0 +1,5 @@
// 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 shell

108
engine/compiler/step.go Normal file
View File

@@ -0,0 +1,108 @@
// 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 compiler
import (
"strings"
"github.com/drone-runners/drone-runner-docker/engine"
"github.com/drone-runners/drone-runner-docker/engine/compiler/encoder"
"github.com/drone-runners/drone-runner-docker/engine/compiler/image"
"github.com/drone-runners/drone-runner-docker/engine/resource"
)
func createStep(spec *resource.Pipeline, src *resource.Step) *engine.Step {
dst := &engine.Step{
ID: random(),
Name: src.Name,
Image: image.Expand(src.Image),
Command: src.Command,
Entrypoint: src.Entrypoint,
Detach: src.Detach,
DependsOn: src.DependsOn,
DNS: src.DNS,
DNSSearch: src.DNSSearch,
Envs: convertStaticEnv(src.Environment),
ExtraHosts: src.ExtraHosts,
IgnoreErr: strings.EqualFold(src.Failure, "ignore"),
IgnoreStderr: false,
IgnoreStdout: false,
Network: src.Network,
Privileged: src.Privileged,
Pull: convertPullPolicy(src.Pull),
User: src.User,
Secrets: convertSecretEnv(src.Environment),
WorkingDir: src.WorkingDir,
//
//
//
Networks: nil, // set in compiler.go
Files: nil, // set below
Volumes: nil, // set below
// Devices: nil, // TODO
// Resources: toResources(src), // TODO
}
// appends the volumes to the container def.
for _, vol := range src.Volumes {
dst.Volumes = append(dst.Volumes, &engine.VolumeMount{
Name: vol.Name,
Path: vol.MountPath,
})
}
// appends the settings variables to the
// container definition.
for key, value := range src.Settings {
// fix https://github.com/drone/drone-yaml/issues/13
if value == nil {
continue
}
// all settings are passed to the plugin env
// variables, prefixed with PLUGIN_
key = "PLUGIN_" + strings.ToUpper(key)
// if the setting parameter is sources from the
// secret we create a secret enviornment variable.
if value.Secret != "" {
dst.Secrets = append(dst.Secrets, &engine.Secret{
Name: value.Secret,
Mask: true,
Env: key,
})
} else {
// else if the setting parameter is opaque
// we inject as a string-encoded environment
// variable.
dst.Envs[key] = encoder.Encode(value.Value)
}
}
// // if the step specifies shell commands we generate a
// // script. The script is copied to the container at
// // runtime (or mounted as a config map) and then executed
// // as the entrypoint.
// if len(src.Commands) > 0 {
// switch spec.Platform.OS {
// case "windows":
// setupScriptWin(spec, dst, src)
// default:
// setupScript(spec, dst, src)
// }
// }
// set the pipeline step run policy. steps run on
// success by default, but may be optionally configured
// to run on failure.
if isRunAlways(src) {
dst.RunPolicy = engine.RunAlways
} else if isRunOnFailure(src) {
dst.RunPolicy = engine.RunOnFailure
}
return dst
}

104
engine/compiler/testdata/graph.json vendored Normal file
View File

@@ -0,0 +1,104 @@
{
"platform": {},
"token": "3DA541559918A808C2402BBA5012F6C60B27661C",
"server": {
"name": "drone-temp-random",
"image": "docker-18-04",
"region": "nyc1",
"size": "s-1vcpu-1gb",
"user": "root"
},
"root": "/tmp/drone-random",
"files": [
{
"path": "/tmp/drone-random/home",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/home/drone",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/drone",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/drone/src",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/opt",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/home/drone/.netrc",
"mode": 384,
"data": "bWFjaGluZSBnaXRodWIuY29tIGxvZ2luIG9jdG9jYXQgcGFzc3dvcmQgY29ycmVjdC1ob3JzZS1iYXR0ZXJ5LXN0YXBsZQ=="
}
],
"steps": [
{
"args": [
"-e",
"/tmp/drone-random/opt/clone"
],
"command": "/bin/sh",
"files": [
{
"path": "/tmp/drone-random/opt/clone",
"mode": 448,
"data": "CnNldCAtZQoKZWNobyArICJnaXQgaW5pdCIKZ2l0IGluaXQKCmVjaG8gKyAiZ2l0IHJlbW90ZSBhZGQgb3JpZ2luICIKZ2l0IHJlbW90ZSBhZGQgb3JpZ2luIAoKZWNobyArICJnaXQgZmV0Y2ggIG9yaWdpbiArcmVmcy9oZWFkcy9tYXN0ZXI6IgpnaXQgZmV0Y2ggIG9yaWdpbiArcmVmcy9oZWFkcy9tYXN0ZXI6CgplY2hvICsgImdpdCBjaGVja291dCAgLWIgbWFzdGVyIgpnaXQgY2hlY2tvdXQgIC1iIG1hc3Rlcgo="
}
],
"secrets": [],
"name": "clone",
"run_policy": 2,
"working_dir": "/tmp/drone-random/drone/src"
},
{
"args": [
"-e",
"/tmp/drone-random/opt/build"
],
"command": "/bin/sh",
"depends_on": [
"clone"
],
"files": [
{
"path": "/tmp/drone-random/opt/build",
"mode": 448,
"data": "CnNldCAtZQoKZWNobyArICJnbyBidWlsZCIKZ28gYnVpbGQK"
}
],
"secrets": [],
"name": "build",
"working_dir": "/tmp/drone-random/drone/src"
},
{
"args": [
"-e",
"/tmp/drone-random/opt/test"
],
"command": "/bin/sh",
"depends_on": [
"build"
],
"files": [
{
"path": "/tmp/drone-random/opt/test",
"mode": 448,
"data": "CnNldCAtZQoKZWNobyArICJnbyB0ZXN0IgpnbyB0ZXN0Cg=="
}
],
"secrets": [],
"name": "test",
"working_dir": "/tmp/drone-random/drone/src"
}
]
}

20
engine/compiler/testdata/graph.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
kind: pipeline
type: docker
name: default
server:
image: docker-18-04
region: nyc1
size: s-1vcpu-1gb
token: 3DA541559918A808C2402BBA5012F6C60B27661C
steps:
- name: build
commands:
- go build
- name: test
commands:
- go test
depends_on: [ build ]

82
engine/compiler/testdata/match.json vendored Normal file
View File

@@ -0,0 +1,82 @@
{
"platform": {},
"token": "3DA541559918A808C2402BBA5012F6C60B27661C",
"server": {
"name": "drone-temp-random",
"image": "docker-18-04",
"region": "nyc1",
"size": "s-1vcpu-1gb",
"user": "root"
},
"root": "/tmp/drone-random",
"files": [
{
"path": "/tmp/drone-random/home",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/home/drone",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/drone",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/drone/src",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/opt",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/home/drone/.netrc",
"mode": 384,
"data": "bWFjaGluZSBnaXRodWIuY29tIGxvZ2luIG9jdG9jYXQgcGFzc3dvcmQgY29ycmVjdC1ob3JzZS1iYXR0ZXJ5LXN0YXBsZQ=="
}
],
"steps": [
{
"args": [
"-e",
"/tmp/drone-random/opt/build"
],
"command": "/bin/sh",
"files": [
{
"path": "/tmp/drone-random/opt/build",
"mode": 448,
"data": "CnNldCAtZQoKZWNobyArICJnbyBidWlsZCIKZ28gYnVpbGQK"
}
],
"name": "build",
"working_dir": "/tmp/drone-random/drone/src"
},
{
"args": [
"-e",
"/tmp/drone-random/opt/test"
],
"command": "/bin/sh",
"depends_on": [
"build"
],
"files": [
{
"path": "/tmp/drone-random/opt/test",
"mode": 448,
"data": "CnNldCAtZQoKZWNobyArICJnbyB0ZXN0IgpnbyB0ZXN0Cg=="
}
],
"name": "test",
"run_policy": 3,
"working_dir": "/tmp/drone-random/drone/src"
}
]
}

26
engine/compiler/testdata/match.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
kind: pipeline
type: docker
name: default
clone:
disable: true
server:
image: docker-18-04
region: nyc1
size: s-1vcpu-1gb
token: 3DA541559918A808C2402BBA5012F6C60B27661C
steps:
- name: build
commands:
- go build
when:
branch: [ master ]
- name: test
commands:
- go test
when:
branch: [ develop ]

View File

@@ -0,0 +1,83 @@
{
"platform": {},
"token": "3DA541559918A808C2402BBA5012F6C60B27661C",
"server": {
"name": "drone-temp-random",
"image": "docker-18-04",
"region": "nyc1",
"size": "s-1vcpu-1gb",
"user": "root"
},
"root": "/tmp/drone-random",
"files": [
{
"path": "/tmp/drone-random/home",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/home/drone",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/drone",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/drone/src",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/opt",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/home/drone/.netrc",
"mode": 384,
"data": "bWFjaGluZSBnaXRodWIuY29tIGxvZ2luIG9jdG9jYXQgcGFzc3dvcmQgY29ycmVjdC1ob3JzZS1iYXR0ZXJ5LXN0YXBsZQ=="
}
],
"steps": [
{
"args": [
"-e",
"/tmp/drone-random/opt/build"
],
"command": "/bin/sh",
"files": [
{
"path": "/tmp/drone-random/opt/build",
"mode": 448,
"data": "CnNldCAtZQoKZWNobyArICJnbyBidWlsZCIKZ28gYnVpbGQK"
}
],
"name": "build",
"secrets": [],
"working_dir": "/tmp/drone-random/drone/src"
},
{
"args": [
"-e",
"/tmp/drone-random/opt/test"
],
"command": "/bin/sh",
"depends_on": [
"build"
],
"files": [
{
"path": "/tmp/drone-random/opt/test",
"mode": 448,
"data": "CnNldCAtZQoKZWNobyArICJnbyB0ZXN0IgpnbyB0ZXN0Cg=="
}
],
"name": "test",
"secrets": [],
"working_dir": "/tmp/drone-random/drone/src"
}
]
}

View File

@@ -0,0 +1,23 @@
kind: pipeline
type: docker
name: default
clone:
disable: true
server:
image: docker-18-04
region: nyc1
size: s-1vcpu-1gb
token: 3DA541559918A808C2402BBA5012F6C60B27661C
steps:
- name: build
commands:
- go build
- name: test
commands:
- go test
depends_on: [ build ]

View File

@@ -0,0 +1,62 @@
{
"platform": {},
"token": "3DA541559918A808C2402BBA5012F6C60B27661C",
"server": {
"name": "drone-temp-random",
"image": "docker-18-04",
"region": "nyc1",
"size": "s-1vcpu-1gb",
"user": "root"
},
"root": "/tmp/drone-random",
"files": [
{
"path": "/tmp/drone-random/home",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/home/drone",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/drone",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/drone/src",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/opt",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/home/drone/.netrc",
"mode": 384,
"data": "bWFjaGluZSBnaXRodWIuY29tIGxvZ2luIG9jdG9jYXQgcGFzc3dvcmQgY29ycmVjdC1ob3JzZS1iYXR0ZXJ5LXN0YXBsZQ=="
}
],
"steps": [
{
"args": [
"-e",
"/tmp/drone-random/opt/build"
],
"command": "/bin/sh",
"files": [
{
"path": "/tmp/drone-random/opt/build",
"mode": 448,
"data": "CnNldCAtZQoKZWNobyArICJnbyBidWlsZCIKZ28gYnVpbGQKCmVjaG8gKyAiZ28gdGVzdCIKZ28gdGVzdAo="
}
],
"name": "build",
"working_dir": "/tmp/drone-random/drone/src"
}
]
}

View File

@@ -0,0 +1,19 @@
kind: pipeline
type: docker
name: default
clone:
disable: true
server:
image: docker-18-04
region: nyc1
size: s-1vcpu-1gb
token: 3DA541559918A808C2402BBA5012F6C60B27661C
steps:
- name: build
commands:
- go build
- go test

View File

@@ -0,0 +1,63 @@
{
"platform": {},
"token": "3DA541559918A808C2402BBA5012F6C60B27661C",
"server": {
"name": "drone-temp-random",
"image": "docker-18-04",
"region": "nyc1",
"size": "s-1vcpu-1gb",
"user": "root"
},
"root": "/tmp/drone-random",
"files": [
{
"path": "/tmp/drone-random/home",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/home/drone",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/drone",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/drone/src",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/opt",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/home/drone/.netrc",
"mode": 384,
"data": "bWFjaGluZSBnaXRodWIuY29tIGxvZ2luIG9jdG9jYXQgcGFzc3dvcmQgY29ycmVjdC1ob3JzZS1iYXR0ZXJ5LXN0YXBsZQ=="
}
],
"steps": [
{
"args": [
"-e",
"/tmp/drone-random/opt/build"
],
"command": "/bin/sh",
"files": [
{
"path": "/tmp/drone-random/opt/build",
"mode": 448,
"data": "CnNldCAtZQoKZWNobyArICJnbyBidWlsZCIKZ28gYnVpbGQK"
}
],
"name": "build",
"run_policy": 2,
"working_dir": "/tmp/drone-random/drone/src"
}
]
}

20
engine/compiler/testdata/run_always.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
kind: pipeline
type: docker
name: default
clone:
disable: true
server:
image: docker-18-04
region: nyc1
size: s-1vcpu-1gb
token: 3DA541559918A808C2402BBA5012F6C60B27661C
steps:
- name: build
commands:
- go build
when:
status: [ success, failure ]

View File

@@ -0,0 +1,63 @@
{
"platform": {},
"token": "3DA541559918A808C2402BBA5012F6C60B27661C",
"server": {
"name": "drone-temp-random",
"image": "docker-18-04",
"region": "nyc1",
"size": "s-1vcpu-1gb",
"user": "root"
},
"root": "/tmp/drone-random",
"files": [
{
"path": "/tmp/drone-random/home",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/home/drone",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/drone",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/drone/src",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/opt",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/home/drone/.netrc",
"mode": 384,
"data": "bWFjaGluZSBnaXRodWIuY29tIGxvZ2luIG9jdG9jYXQgcGFzc3dvcmQgY29ycmVjdC1ob3JzZS1iYXR0ZXJ5LXN0YXBsZQ=="
}
],
"steps": [
{
"args": [
"-e",
"/tmp/drone-random/opt/build"
],
"command": "/bin/sh",
"files": [
{
"path": "/tmp/drone-random/opt/build",
"mode": 448,
"data": "CnNldCAtZQoKZWNobyArICJnbyBidWlsZCIKZ28gYnVpbGQK"
}
],
"name": "build",
"run_policy": 1,
"working_dir": "/tmp/drone-random/drone/src"
}
]
}

View File

@@ -0,0 +1,20 @@
kind: pipeline
type: docker
name: default
clone:
disable: true
server:
image: docker-18-04
region: nyc1
size: s-1vcpu-1gb
token: 3DA541559918A808C2402BBA5012F6C60B27661C
steps:
- name: build
commands:
- go build
when:
status: [ failure ]

25
engine/compiler/testdata/secret.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
kind: pipeline
type: docker
name: default
clone:
disable: true
server:
image: docker-18-04
region: nyc1
size: s-1vcpu-1gb
token:
from_secret: token
steps:
- name: build
environment:
PASSWORD:
from_secret: my_password
USERNAME:
from_secret: my_username
commands:
- go build
- go test

104
engine/compiler/testdata/serial.json vendored Normal file
View File

@@ -0,0 +1,104 @@
{
"platform": {},
"token": "3DA541559918A808C2402BBA5012F6C60B27661C",
"server": {
"name": "drone-temp-random",
"image": "docker-18-04",
"region": "nyc1",
"size": "s-1vcpu-1gb",
"user": "root"
},
"root": "/tmp/drone-random",
"files": [
{
"path": "/tmp/drone-random/home",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/home/drone",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/drone",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/drone/src",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/opt",
"mode": 448,
"is_dir": true
},
{
"path": "/tmp/drone-random/home/drone/.netrc",
"mode": 384,
"data": "bWFjaGluZSBnaXRodWIuY29tIGxvZ2luIG9jdG9jYXQgcGFzc3dvcmQgY29ycmVjdC1ob3JzZS1iYXR0ZXJ5LXN0YXBsZQ=="
}
],
"steps": [
{
"args": [
"-e",
"/tmp/drone-random/opt/clone"
],
"command": "/bin/sh",
"files": [
{
"path": "/tmp/drone-random/opt/clone",
"mode": 448,
"data": "CnNldCAtZQoKZWNobyArICJnaXQgaW5pdCIKZ2l0IGluaXQKCmVjaG8gKyAiZ2l0IHJlbW90ZSBhZGQgb3JpZ2luICIKZ2l0IHJlbW90ZSBhZGQgb3JpZ2luIAoKZWNobyArICJnaXQgZmV0Y2ggIG9yaWdpbiArcmVmcy9oZWFkcy9tYXN0ZXI6IgpnaXQgZmV0Y2ggIG9yaWdpbiArcmVmcy9oZWFkcy9tYXN0ZXI6CgplY2hvICsgImdpdCBjaGVja291dCAgLWIgbWFzdGVyIgpnaXQgY2hlY2tvdXQgIC1iIG1hc3Rlcgo="
}
],
"secrets": [],
"name": "clone",
"run_policy": 2,
"working_dir": "/tmp/drone-random/drone/src"
},
{
"args": [
"-e",
"/tmp/drone-random/opt/build"
],
"command": "/bin/sh",
"depends_on": [
"clone"
],
"files": [
{
"path": "/tmp/drone-random/opt/build",
"mode": 448,
"data": "CnNldCAtZQoKZWNobyArICJnbyBidWlsZCIKZ28gYnVpbGQK"
}
],
"secrets": [],
"name": "build",
"working_dir": "/tmp/drone-random/drone/src"
},
{
"args": [
"-e",
"/tmp/drone-random/opt/test"
],
"command": "/bin/sh",
"depends_on": [
"build"
],
"files": [
{
"path": "/tmp/drone-random/opt/test",
"mode": 448,
"data": "CnNldCAtZQoKZWNobyArICJnbyB0ZXN0IgpnbyB0ZXN0Cg=="
}
],
"secrets": [],
"name": "test",
"working_dir": "/tmp/drone-random/drone/src"
}
]
}

19
engine/compiler/testdata/serial.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
kind: pipeline
type: docker
name: default
server:
image: docker-18-04
region: nyc1
size: s-1vcpu-1gb
token: 3DA541559918A808C2402BBA5012F6C60B27661C
steps:
- name: build
commands:
- go build
- name: test
commands:
- go test

132
engine/compiler/util.go Normal file
View File

@@ -0,0 +1,132 @@
// 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 compiler
import (
"strings"
"github.com/drone-runners/drone-runner-docker/engine"
"github.com/drone-runners/drone-runner-docker/engine/resource"
"github.com/drone/drone-go/drone"
"github.com/drone/runner-go/manifest"
)
// helper function returns true if the step is configured to
// always run regardless of status.
func isRunAlways(step *resource.Step) bool {
if len(step.When.Status.Include) == 0 &&
len(step.When.Status.Exclude) == 0 {
return false
}
return step.When.Status.Match(drone.StatusFailing) &&
step.When.Status.Match(drone.StatusPassing)
}
// helper function returns true if the step is configured to
// only run on failure.
func isRunOnFailure(step *resource.Step) bool {
if len(step.When.Status.Include) == 0 &&
len(step.When.Status.Exclude) == 0 {
return false
}
return step.When.Status.Match(drone.StatusFailing)
}
// helper function returns true if the pipeline specification
// manually defines an execution graph.
func isGraph(spec *engine.Spec) bool {
for _, step := range spec.Steps {
if len(step.DependsOn) > 0 {
return true
}
}
return false
}
// helper function creates the dependency graph for serial
// pipeline execution.
func configureSerial(spec *engine.Spec) {
var prev *engine.Step
for _, step := range spec.Steps {
if prev != nil {
step.DependsOn = []string{prev.Name}
}
prev = step
}
}
// helper function converts the environment variables to a map,
// returning only inline environment variables not derived from
// a secret.
func convertStaticEnv(src map[string]*manifest.Variable) map[string]string {
dst := map[string]string{}
for k, v := range src {
if strings.TrimSpace(v.Secret) == "" {
dst[k] = v.Value
}
}
return dst
}
// helper function converts the environment variables to a map,
// returning only inline environment variables not derived from
// a secret.
func convertSecretEnv(src map[string]*manifest.Variable) []*engine.Secret {
dst := []*engine.Secret{}
for k, v := range src {
if strings.TrimSpace(v.Secret) != "" {
dst = append(dst, &engine.Secret{
Name: v.Secret,
Mask: true,
Env: k,
})
}
}
return dst
}
// helper function modifies the pipeline dependency graph to
// account for the clone step.
func configureCloneDeps(spec *engine.Spec) {
for _, step := range spec.Steps {
if step.Name == "clone" {
continue
}
if len(step.DependsOn) == 0 {
step.DependsOn = []string{"clone"}
}
}
}
// helper function modifies the pipeline dependency graph to
// account for a disabled clone step.
func removeCloneDeps(spec *engine.Spec) {
for _, step := range spec.Steps {
if step.Name == "clone" {
return
}
}
for _, step := range spec.Steps {
if len(step.DependsOn) == 1 &&
step.DependsOn[0] == "clone" {
step.DependsOn = []string{}
}
}
}
// helper function modifies the pipeline dependency graph to
// account for the clone step.
func convertPullPolicy(s string) engine.PullPolicy {
switch strings.ToLower(s) {
case "always":
return engine.PullAlways
case "if-not-exists":
return engine.PullIfNotExists
case "never":
return engine.PullNever
default:
return engine.PullDefault
}
}

View File

@@ -0,0 +1,200 @@
// 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 compiler
import (
"testing"
"github.com/drone-runners/drone-runner-docker/engine"
"github.com/drone-runners/drone-runner-docker/engine/resource"
"github.com/drone/runner-go/manifest"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)
func Test_isRunAlways(t *testing.T) {
step := new(resource.Step)
if isRunAlways(step) == true {
t.Errorf("Want always run false if empty when clause")
}
step.When.Status.Include = []string{"success"}
if isRunAlways(step) == true {
t.Errorf("Want always run false if when success")
}
step.When.Status.Include = []string{"failure"}
if isRunAlways(step) == true {
t.Errorf("Want always run false if when faiure")
}
step.When.Status.Include = []string{"success", "failure"}
if isRunAlways(step) == false {
t.Errorf("Want always run true if when success, failure")
}
}
func Test_isRunOnFailure(t *testing.T) {
step := new(resource.Step)
if isRunOnFailure(step) == true {
t.Errorf("Want run on failure false if empty when clause")
}
step.When.Status.Include = []string{"success"}
if isRunOnFailure(step) == true {
t.Errorf("Want run on failure false if when success")
}
step.When.Status.Include = []string{"failure"}
if isRunOnFailure(step) == false {
t.Errorf("Want run on failure true if when faiure")
}
step.When.Status.Include = []string{"success", "failure"}
if isRunOnFailure(step) == false {
t.Errorf("Want run on failure true if when success, failure")
}
}
func Test_isGraph(t *testing.T) {
spec := new(engine.Spec)
spec.Steps = []*engine.Step{
{DependsOn: []string{}},
}
if isGraph(spec) == true {
t.Errorf("Expect is graph false if deps not exist")
}
spec.Steps[0].DependsOn = []string{"clone"}
if isGraph(spec) == false {
t.Errorf("Expect is graph true if deps exist")
}
}
func Test_configureSerial(t *testing.T) {
before := new(engine.Spec)
before.Steps = []*engine.Step{
{Name: "build"},
{Name: "test"},
{Name: "deploy"},
}
after := new(engine.Spec)
after.Steps = []*engine.Step{
{Name: "build"},
{Name: "test", DependsOn: []string{"build"}},
{Name: "deploy", DependsOn: []string{"test"}},
}
configureSerial(before)
opts := cmpopts.IgnoreUnexported(engine.Spec{})
if diff := cmp.Diff(before, after, opts); diff != "" {
t.Errorf("Unexpected serial configuration")
t.Log(diff)
}
}
func Test_convertStaticEnv(t *testing.T) {
vars := map[string]*manifest.Variable{
"username": &manifest.Variable{Value: "octocat"},
"password": &manifest.Variable{Secret: "password"},
}
envs := convertStaticEnv(vars)
want := map[string]string{"username": "octocat"}
if diff := cmp.Diff(envs, want); diff != "" {
t.Errorf("Unexpected environment variable set")
t.Log(diff)
}
}
func Test_convertSecretEnv(t *testing.T) {
vars := map[string]*manifest.Variable{
"USERNAME": &manifest.Variable{Value: "octocat"},
"PASSWORD": &manifest.Variable{Secret: "password"},
}
envs := convertSecretEnv(vars)
want := []*engine.Secret{
{
Name: "password",
Env: "PASSWORD",
Mask: true,
},
}
if diff := cmp.Diff(envs, want); diff != "" {
t.Errorf("Unexpected secret list")
t.Log(diff)
}
}
func Test_configureCloneDeps(t *testing.T) {
before := new(engine.Spec)
before.Steps = []*engine.Step{
{Name: "clone"},
{Name: "backend"},
{Name: "frontend"},
{Name: "deploy", DependsOn: []string{
"backend", "frontend",
}},
}
after := new(engine.Spec)
after.Steps = []*engine.Step{
{Name: "clone"},
{Name: "backend", DependsOn: []string{"clone"}},
{Name: "frontend", DependsOn: []string{"clone"}},
{Name: "deploy", DependsOn: []string{
"backend", "frontend",
}},
}
configureCloneDeps(before)
opts := cmpopts.IgnoreUnexported(engine.Spec{})
if diff := cmp.Diff(before, after, opts); diff != "" {
t.Errorf("Unexpected dependency adjustment")
t.Log(diff)
}
}
func Test_removeCloneDeps(t *testing.T) {
before := new(engine.Spec)
before.Steps = []*engine.Step{
{Name: "backend", DependsOn: []string{"clone"}},
{Name: "frontend", DependsOn: []string{"clone"}},
{Name: "deploy", DependsOn: []string{
"backend", "frontend",
}},
}
after := new(engine.Spec)
after.Steps = []*engine.Step{
{Name: "backend", DependsOn: []string{}},
{Name: "frontend", DependsOn: []string{}},
{Name: "deploy", DependsOn: []string{
"backend", "frontend",
}},
}
removeCloneDeps(before)
opts := cmpopts.IgnoreUnexported(engine.Spec{})
if diff := cmp.Diff(before, after, opts); diff != "" {
t.Errorf("Unexpected result after removing clone deps")
t.Log(diff)
}
}
func Test_removeCloneDeps_CloneEnabled(t *testing.T) {
before := new(engine.Spec)
before.Steps = []*engine.Step{
{Name: "clone"},
{Name: "test", DependsOn: []string{"clone"}},
}
after := new(engine.Spec)
after.Steps = []*engine.Step{
{Name: "clone"},
{Name: "test", DependsOn: []string{"clone"}},
}
removeCloneDeps(before)
opts := cmpopts.IgnoreUnexported(engine.Spec{})
if diff := cmp.Diff(before, after, opts); diff != "" {
t.Errorf("Expect clone dependencies not removed")
t.Log(diff)
}
}

View File

@@ -0,0 +1,74 @@
// 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 compiler
import (
stdpath "path"
"strings"
"github.com/drone-runners/drone-runner-docker/engine"
"github.com/drone-runners/drone-runner-docker/engine/resource"
)
const (
workspacePath = "/drone/src"
workspaceName = "workspace"
workspaceHostName = "host"
)
func createWorkspace(from *resource.Pipeline) (base, path, full string) {
base = from.Workspace.Base
path = from.Workspace.Path
if base == "" {
base = workspacePath
}
full = stdpath.Join(base, path)
if from.Platform.OS == "windows" {
base = toWindowsDrive(base)
path = toWindowsPath(path)
full = toWindowsDrive(full)
}
return base, path, full
}
func setupWorkdir(src *resource.Step, dst *engine.Step, path string) {
// if the working directory is already set
// do not alter.
if dst.WorkingDir != "" {
return
}
// if the user is running the container as a
// service (detached mode) with no commands, we
// should use the default working directory.
if dst.Detach && len(src.Commands) == 0 {
return
}
// else set the working directory.
dst.WorkingDir = path
}
// helper function appends the workspace base and
// path to the step's list of environment variables.
func setupWorkspaceEnv(step *engine.Step, base, path, full string) {
step.Envs["DRONE_WORKSPACE_BASE"] = base
step.Envs["DRONE_WORKSPACE_PATH"] = path
step.Envs["DRONE_WORKSPACE"] = full
step.Envs["CI_WORKSPACE_BASE"] = base
step.Envs["CI_WORKSPACE_PATH"] = path
step.Envs["CI_WORKSPACE"] = full
}
// helper function converts the path to a valid windows
// path, including the default C drive.
func toWindowsDrive(s string) string {
return "c:" + toWindowsPath(s)
}
// helper function converts the path to a valid windows
// path, replacing backslashes with forward slashes.
func toWindowsPath(s string) string {
return strings.Replace(s, "/", "\\", -1)
}

View File

@@ -0,0 +1,149 @@
// 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 compiler
import (
"testing"
"github.com/drone-runners/drone-runner-docker/engine"
"github.com/drone-runners/drone-runner-docker/engine/resource"
"github.com/drone/runner-go/manifest"
)
func TestSetupWorkspace(t *testing.T) {
tests := []struct {
path string
src *resource.Step
dst *engine.Step
want string
}{
{
path: "/drone/src",
src: &resource.Step{},
dst: &engine.Step{},
want: "/drone/src",
},
// do not override the user-defined working dir.
{
path: "/drone/src",
src: &resource.Step{},
dst: &engine.Step{WorkingDir: "/foo"},
want: "/foo",
},
// do not override the default working directory
// for service containers with no commands.
{
path: "/drone/src",
src: &resource.Step{},
dst: &engine.Step{Detach: true},
want: "",
},
// overrides the default working directory
// for service containers with commands.
{
path: "/drone/src",
src: &resource.Step{Commands: []string{"whoami"}},
dst: &engine.Step{Detach: true},
want: "/drone/src",
},
}
for _, test := range tests {
setupWorkdir(test.src, test.dst, test.path)
if got, want := test.dst.WorkingDir, test.want; got != want {
t.Errorf("Want working_dir %s, got %s", want, got)
}
}
}
func TestToWindows(t *testing.T) {
got := toWindowsDrive("/go/src/github.com/octocat/hello-world")
want := "c:\\go\\src\\github.com\\octocat\\hello-world"
if got != want {
t.Errorf("Want windows drive %q, got %q", want, got)
}
}
func TestCreateWorkspace(t *testing.T) {
tests := []struct {
from *resource.Pipeline
base string
path string
full string
}{
{
from: &resource.Pipeline{
Workspace: resource.Workspace{
Base: "",
Path: "",
},
},
base: "/drone/src",
path: "",
full: "/drone/src",
},
{
from: &resource.Pipeline{
Workspace: resource.Workspace{
Base: "",
Path: "",
},
Platform: manifest.Platform{
OS: "windows",
},
},
base: "c:\\drone\\src",
path: "",
full: "c:\\drone\\src",
},
{
from: &resource.Pipeline{
Workspace: resource.Workspace{
Base: "/drone",
Path: "src",
},
},
base: "/drone",
path: "src",
full: "/drone/src",
},
{
from: &resource.Pipeline{
Workspace: resource.Workspace{
Base: "/drone",
Path: "src",
},
Platform: manifest.Platform{
OS: "windows",
},
},
base: "c:\\drone",
path: "src",
full: "c:\\drone\\src",
},
{
from: &resource.Pipeline{
Workspace: resource.Workspace{
Base: "/foo",
Path: "bar",
},
},
base: "/foo",
path: "bar",
full: "/foo/bar",
},
}
for _, test := range tests {
base, path, full := createWorkspace(test.from)
if got, want := test.base, base; got != want {
t.Errorf("Want workspace base %s, got %s", want, got)
}
if got, want := test.path, path; got != want {
t.Errorf("Want workspace path %s, got %s", want, got)
}
if got, want := test.full, full; got != want {
t.Errorf("Want workspace %s, got %s", want, got)
}
}
}

118
engine/const.go Normal file
View File

@@ -0,0 +1,118 @@
// 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 engine
import (
"bytes"
"encoding/json"
)
// PullPolicy defines the container image pull policy.
type PullPolicy int
// PullPolicy enumeration.
const (
PullDefault PullPolicy = iota
PullAlways
PullIfNotExists
PullNever
)
func (p PullPolicy) String() string {
return pullPolicyID[p]
}
var pullPolicyID = map[PullPolicy]string{
PullDefault: "default",
PullAlways: "always",
PullIfNotExists: "if-not-exists",
PullNever: "never",
}
var pullPolicyName = map[string]PullPolicy{
"": PullDefault,
"default": PullDefault,
"always": PullAlways,
"if-not-exists": PullIfNotExists,
"never": PullNever,
}
// MarshalJSON marshals the string representation of the
// pull type to JSON.
func (p *PullPolicy) MarshalJSON() ([]byte, error) {
buffer := bytes.NewBufferString(`"`)
buffer.WriteString(pullPolicyID[*p])
buffer.WriteString(`"`)
return buffer.Bytes(), nil
}
// UnmarshalJSON unmarshals the json representation of the
// pull type from a string value.
func (p *PullPolicy) UnmarshalJSON(b []byte) error {
// unmarshal as string
var s string
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
// lookup value
*p = pullPolicyName[s]
return nil
}
// RunPolicy defines the policy for starting containers
// based on the point-in-time pass or fail state of
// the pipeline.
type RunPolicy int
// RunPolicy enumeration.
const (
RunOnSuccess RunPolicy = iota
RunOnFailure
RunAlways
RunNever
)
func (r RunPolicy) String() string {
return runPolicyID[r]
}
var runPolicyID = map[RunPolicy]string{
RunOnSuccess: "on-success",
RunOnFailure: "on-failure",
RunAlways: "always",
RunNever: "never",
}
var runPolicyName = map[string]RunPolicy{
"": RunOnSuccess,
"on-success": RunOnSuccess,
"on-failure": RunOnFailure,
"always": RunAlways,
"never": RunNever,
}
// MarshalJSON marshals the string representation of the
// run type to JSON.
func (r *RunPolicy) MarshalJSON() ([]byte, error) {
buffer := bytes.NewBufferString(`"`)
buffer.WriteString(runPolicyID[*r])
buffer.WriteString(`"`)
return buffer.Bytes(), nil
}
// UnmarshalJSON unmarshals the json representation of the
// run type from a string value.
func (r *RunPolicy) UnmarshalJSON(b []byte) error {
// unmarshal as string
var s string
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
// lookup value
*r = runPolicyName[s]
return nil
}

237
engine/const_test.go Normal file
View File

@@ -0,0 +1,237 @@
// 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 engine
import (
"bytes"
"encoding/json"
"testing"
)
//
// runtime policy unit tests.
//
func TestRunPolicy_Marshal(t *testing.T) {
tests := []struct {
policy RunPolicy
data string
}{
{
policy: RunAlways,
data: `"always"`,
},
{
policy: RunOnFailure,
data: `"on-failure"`,
},
{
policy: RunOnSuccess,
data: `"on-success"`,
},
{
policy: RunNever,
data: `"never"`,
},
}
for _, test := range tests {
data, err := json.Marshal(&test.policy)
if err != nil {
t.Error(err)
return
}
if bytes.Equal([]byte(test.data), data) == false {
t.Errorf("Failed to marshal policy %s", test.policy)
}
}
}
func TestRunPolicy_Unmarshal(t *testing.T) {
tests := []struct {
policy RunPolicy
data string
}{
{
policy: RunAlways,
data: `"always"`,
},
{
policy: RunOnFailure,
data: `"on-failure"`,
},
{
policy: RunOnSuccess,
data: `"on-success"`,
},
{
policy: RunNever,
data: `"never"`,
},
{
// no policy should default to on-success
policy: RunOnSuccess,
data: `""`,
},
}
for _, test := range tests {
var policy RunPolicy
err := json.Unmarshal([]byte(test.data), &policy)
if err != nil {
t.Error(err)
return
}
if got, want := policy, test.policy; got != want {
t.Errorf("Want policy %q, got %q", want, got)
}
}
}
func TestRunPolicy_UnmarshalTypeError(t *testing.T) {
var policy RunPolicy
err := json.Unmarshal([]byte("[]"), &policy)
if _, ok := err.(*json.UnmarshalTypeError); !ok {
t.Errorf("Expect unmarshal error return when JSON invalid")
}
}
func TestRunPolicy_String(t *testing.T) {
tests := []struct {
policy RunPolicy
value string
}{
{
policy: RunAlways,
value: "always",
},
{
policy: RunOnFailure,
value: "on-failure",
},
{
policy: RunOnSuccess,
value: "on-success",
},
}
for _, test := range tests {
if got, want := test.policy.String(), test.value; got != want {
t.Errorf("Want policy string %q, got %q", want, got)
}
}
}
//
// pull policy unit tests.
//
func TestPullPolicy_Marshal(t *testing.T) {
tests := []struct {
policy PullPolicy
data string
}{
{
policy: PullAlways,
data: `"always"`,
},
{
policy: PullDefault,
data: `"default"`,
},
{
policy: PullIfNotExists,
data: `"if-not-exists"`,
},
{
policy: PullNever,
data: `"never"`,
},
}
for _, test := range tests {
data, err := json.Marshal(&test.policy)
if err != nil {
t.Error(err)
return
}
if bytes.Equal([]byte(test.data), data) == false {
t.Errorf("Failed to marshal policy %s", test.policy)
}
}
}
func TestPullPolicy_Unmarshal(t *testing.T) {
tests := []struct {
policy PullPolicy
data string
}{
{
policy: PullAlways,
data: `"always"`,
},
{
policy: PullDefault,
data: `"default"`,
},
{
policy: PullIfNotExists,
data: `"if-not-exists"`,
},
{
policy: PullNever,
data: `"never"`,
},
{
// no policy should default to on-success
policy: PullDefault,
data: `""`,
},
}
for _, test := range tests {
var policy PullPolicy
err := json.Unmarshal([]byte(test.data), &policy)
if err != nil {
t.Error(err)
return
}
if got, want := policy, test.policy; got != want {
t.Errorf("Want policy %q, got %q", want, got)
}
}
}
func TestPullPolicy_UnmarshalTypeError(t *testing.T) {
var policy PullPolicy
err := json.Unmarshal([]byte("[]"), &policy)
if _, ok := err.(*json.UnmarshalTypeError); !ok {
t.Errorf("Expect unmarshal error return when JSON invalid")
}
}
func TestPullPolicy_String(t *testing.T) {
tests := []struct {
policy PullPolicy
value string
}{
{
policy: PullAlways,
value: "always",
},
{
policy: PullDefault,
value: "default",
},
{
policy: PullIfNotExists,
value: "if-not-exists",
},
{
policy: PullNever,
value: "never",
},
}
for _, test := range tests {
if got, want := test.policy.String(), test.value; got != want {
t.Errorf("Want policy string %q, got %q", want, got)
}
}
}

23
engine/engine.go Normal file
View File

@@ -0,0 +1,23 @@
// 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 engine
import (
"context"
"io"
)
// Engine is the interface that must be implemented by a
// pipeline execution engine.
type Engine interface {
// Setup the pipeline environment.
Setup(context.Context, *Spec) error
// Destroy the pipeline environment.
Destroy(context.Context, *Spec) error
// Run runs the pipeine step.
Run(context.Context, *Spec, *Step, io.Writer) (*State, error)
}

33
engine/engine_impl.go Normal file
View File

@@ -0,0 +1,33 @@
// 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 engine
import (
"context"
"io"
)
// New returns a new engine.
func New(publickeyFile, privatekeyFile string) (Engine, error) {
return &engine{}, nil
}
type engine struct {
}
// Setup the pipeline environment.
func (e *engine) Setup(ctx context.Context, spec *Spec) error {
return nil
}
// Destroy the pipeline environment.
func (e *engine) Destroy(ctx context.Context, spec *Spec) error {
return nil
}
// Run runs the pipeline step.
func (e *engine) Run(ctx context.Context, spec *Spec, step *Step, output io.Writer) (*State, error) {
return nil, nil
}

View File

@@ -0,0 +1,56 @@
// Code generated automatically. DO NOT EDIT.
// 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 replacer
import (
"fmt"
"io"
"strings"
"github.com/drone-runners/drone-runner-docker/engine"
)
const maskedf = "[secret:%s]"
// Replacer is an io.Writer that finds and masks sensitive data.
type Replacer struct {
w io.WriteCloser
r *strings.Replacer
}
// New returns a replacer that wraps writer w.
func New(w io.WriteCloser, secrets []*engine.Secret) io.WriteCloser {
var oldnew []string
for _, secret := range secrets {
if len(secret.Data) == 0 || secret.Mask == false {
continue
}
name := strings.ToLower(secret.Name)
masked := fmt.Sprintf(maskedf, name)
oldnew = append(oldnew, string(secret.Data))
oldnew = append(oldnew, masked)
}
if len(oldnew) == 0 {
return w
}
return &Replacer{
w: w,
r: strings.NewReplacer(oldnew...),
}
}
// Write writes p to the base writer. The method scans for any
// sensitive data in p and masks before writing.
func (r *Replacer) Write(p []byte) (n int, err error) {
_, err = r.w.Write([]byte(r.r.Replace(string(p))))
return len(p), err
}
// Close closes the base writer.
func (r *Replacer) Close() error {
return r.w.Close()
}

View File

@@ -0,0 +1,56 @@
// Code generated automatically. DO NOT EDIT.
// 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 replacer
import (
"bytes"
"io"
"testing"
"github.com/drone-runners/drone-runner-docker/engine"
)
func TestReplace(t *testing.T) {
secrets := []*engine.Secret{
{Name: "DOCKER_USERNAME", Data: []byte("octocat"), Mask: false},
{Name: "DOCKER_PASSWORD", Data: []byte("correct-horse-batter-staple"), Mask: true},
{Name: "DOCKER_EMAIL", Data: []byte(""), Mask: true},
}
buf := new(bytes.Buffer)
w := New(&nopCloser{buf}, secrets)
w.Write([]byte("username octocat password correct-horse-batter-staple"))
w.Close()
if got, want := buf.String(), "username octocat password [secret:docker_password]"; got != want {
t.Errorf("Want masked string %s, got %s", want, got)
}
}
// this test verifies that if there are no secrets to scan and
// mask, the io.WriteCloser is returned as-is.
func TestReplaceNone(t *testing.T) {
secrets := []*engine.Secret{
{Name: "DOCKER_USERNAME", Data: []byte("octocat"), Mask: false},
{Name: "DOCKER_PASSWORD", Data: []byte("correct-horse-batter-staple"), Mask: false},
}
buf := new(bytes.Buffer)
w := &nopCloser{buf}
r := New(w, secrets)
if w != r {
t.Errorf("Expect buffer returned with no replacer")
}
}
type nopCloser struct {
io.Writer
}
func (*nopCloser) Close() error {
return nil
}

View File

@@ -0,0 +1,5 @@
// 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 resource

24
engine/resource/lookup.go Normal file
View File

@@ -0,0 +1,24 @@
// 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 resource
import (
"errors"
"github.com/drone/runner-go/manifest"
)
// Lookup returns the named pipeline from the Manifest.
func Lookup(name string, manifest *manifest.Manifest) (*Pipeline, error) {
for _, resource := range manifest.Resources {
if resource.GetName() != name {
continue
}
if pipeline, ok := resource.(*Pipeline); ok {
return pipeline, nil
}
}
return nil, errors.New("resource not found")
}

View File

@@ -0,0 +1,45 @@
// 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 resource
import (
"testing"
"github.com/drone/runner-go/manifest"
)
func TestLookup(t *testing.T) {
want := &Pipeline{Name: "default"}
m := &manifest.Manifest{
Resources: []manifest.Resource{want},
}
got, err := Lookup("default", m)
if err != nil {
t.Error(err)
}
if got != want {
t.Errorf("Expect resource not found error")
}
}
func TestLookupNotFound(t *testing.T) {
m := &manifest.Manifest{
Resources: []manifest.Resource{
&manifest.Secret{
Kind: "secret",
Name: "password",
},
// matches name, but is not of kind pipeline
&manifest.Secret{
Kind: "secret",
Name: "default",
},
},
}
_, err := Lookup("default", m)
if err == nil {
t.Errorf("Expect resource not found error")
}
}

54
engine/resource/parser.go Normal file
View File

@@ -0,0 +1,54 @@
// 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 resource
import (
"errors"
"github.com/drone/runner-go/manifest"
"github.com/buildkite/yaml"
)
func init() {
manifest.Register(parse)
}
// parse parses the raw resource and returns an Exec pipeline.
func parse(r *manifest.RawResource) (manifest.Resource, bool, error) {
if !match(r) {
return nil, false, nil
}
out := new(Pipeline)
err := yaml.Unmarshal(r.Data, out)
if err != nil {
return out, true, err
}
err = lint(out)
return out, true, err
}
// match returns true if the resource matches the kind and type.
func match(r *manifest.RawResource) bool {
return r.Kind == Kind && r.Type == Type
}
func lint(pipeline *Pipeline) error {
// ensure pipeline steps are not unique.
names := map[string]struct{}{}
for _, step := range pipeline.Steps {
if step.Name == "" {
return errors.New("Linter: invalid or missing step name")
}
if len(step.Name) > 100 {
return errors.New("Linter: step name cannot exceed 100 characters")
}
if _, ok := names[step.Name]; ok {
return errors.New("Linter: duplicate step name")
}
names[step.Name] = struct{}{}
}
return nil
}

View File

@@ -0,0 +1,159 @@
// 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 resource
import (
"testing"
"github.com/drone/runner-go/manifest"
"github.com/google/go-cmp/cmp"
)
func TestParse(t *testing.T) {
got, err := manifest.ParseFile("testdata/manifest.yml")
if err != nil {
t.Error(err)
return
}
want := []manifest.Resource{
&manifest.Signature{
Kind: "signature",
Hmac: "a8842634682b78946a2",
},
&manifest.Secret{
Kind: "secret",
Type: "encrypted",
Name: "token",
Data: "f0e4c2f76c58916ec25",
},
&Pipeline{
Kind: "pipeline",
Type: "docker",
Name: "default",
Version: "1",
Workspace: Workspace{
Path: "/drone/src",
},
Platform: manifest.Platform{
OS: "linux",
Arch: "arm64",
},
Clone: manifest.Clone{
Depth: 50,
},
PullSecrets: []string{"dockerconfigjson"},
Trigger: manifest.Conditions{
Branch: manifest.Condition{
Include: []string{"master"},
},
},
Services: []*Step{
{
Name: "redis",
Image: "redis:latest",
Entrypoint: []string{"/bin/redis-server"},
Command: []string{"--debug"},
},
},
Steps: []*Step{
{
Name: "build",
Image: "golang",
Detach: false,
DependsOn: []string{"clone"},
Commands: []string{
"go build",
"go test",
},
Environment: map[string]*manifest.Variable{
"GOOS": &manifest.Variable{Value: "linux"},
"GOARCH": &manifest.Variable{Value: "arm64"},
},
Failure: "ignore",
When: manifest.Conditions{
Event: manifest.Condition{
Include: []string{"push"},
},
},
},
},
},
}
if diff := cmp.Diff(got.Resources, want); diff != "" {
t.Errorf("Unexpected manifest")
t.Log(diff)
}
}
func TestParseErr(t *testing.T) {
_, err := manifest.ParseFile("testdata/malformed.yml")
if err == nil {
t.Errorf("Expect error when malformed yaml")
}
}
func TestParseLintErr(t *testing.T) {
_, err := manifest.ParseFile("testdata/linterr.yml")
if err == nil {
t.Errorf("Expect linter returns error")
return
}
}
func TestParseNoMatch(t *testing.T) {
r := &manifest.RawResource{Kind: "pipeline", Type: "exec"}
_, match, _ := parse(r)
if match {
t.Errorf("Expect no match")
}
}
func TestMatch(t *testing.T) {
r := &manifest.RawResource{
Kind: "pipeline",
Type: "docker",
}
if match(r) == false {
t.Errorf("Expect match, got false")
}
r = &manifest.RawResource{
Kind: "approval",
Type: "docker",
}
if match(r) == true {
t.Errorf("Expect kind mismatch, got true")
}
r = &manifest.RawResource{
Kind: "pipeline",
Type: "dummy",
}
if match(r) == true {
t.Errorf("Expect type mismatch, got true")
}
}
func TestLint(t *testing.T) {
p := new(Pipeline)
p.Steps = []*Step{{Name: "build"}, {Name: "test"}}
if err := lint(p); err != nil {
t.Errorf("Expect no lint error, got %s", err)
}
p.Steps = []*Step{{Name: "build"}, {Name: "build"}}
if err := lint(p); err == nil {
t.Errorf("Expect error when duplicate name")
}
p.Steps = []*Step{{Name: "build"}, {Name: ""}}
if err := lint(p); err == nil {
t.Errorf("Expect error when empty name")
}
}

153
engine/resource/pipeline.go Normal file
View File

@@ -0,0 +1,153 @@
// 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 resource
import "github.com/drone/runner-go/manifest"
var (
_ manifest.Resource = (*Pipeline)(nil)
_ manifest.TriggeredResource = (*Pipeline)(nil)
_ manifest.DependantResource = (*Pipeline)(nil)
_ manifest.PlatformResource = (*Pipeline)(nil)
)
// TODO(bradrydzewski) add resource limits
// Defines the Resource Kind and Type.
const (
Kind = "pipeline"
Type = "docker"
)
// Pipeline is a pipeline resource that executes pipelines
// on the host machine without any virtualization.
type Pipeline struct {
Version string `json:"version,omitempty"`
Kind string `json:"kind,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Deps []string `json:"depends_on,omitempty"`
Clone manifest.Clone `json:"clone,omitempty"`
Concurrency manifest.Concurrency `json:"concurrency,omitempty"`
Node map[string]string `json:"node,omitempty"`
Platform manifest.Platform `json:"platform,omitempty"`
Trigger manifest.Conditions `json:"conditions,omitempty"`
Services []*Step `json:"services,omitempty"`
Steps []*Step `json:"steps,omitempty"`
Volumes []*Volume `json:"volumes,omitempty"`
PullSecrets []string `json:"image_pull_secrets,omitempty" yaml:"image_pull_secrets"`
Workspace Workspace `json:"workspace,omitempty"`
}
// GetVersion returns the resource version.
func (p *Pipeline) GetVersion() string { return p.Version }
// GetKind returns the resource kind.
func (p *Pipeline) GetKind() string { return p.Kind }
// GetType returns the resource type.
func (p *Pipeline) GetType() string { return p.Type }
// GetName returns the resource name.
func (p *Pipeline) GetName() string { return p.Name }
// GetDependsOn returns the resource dependencies.
func (p *Pipeline) GetDependsOn() []string { return p.Deps }
// GetTrigger returns the resource triggers.
func (p *Pipeline) GetTrigger() manifest.Conditions { return p.Trigger }
// GetNodes returns the resource node labels.
func (p *Pipeline) GetNodes() map[string]string { return p.Node }
// GetPlatform returns the resource platform.
func (p *Pipeline) GetPlatform() manifest.Platform { return p.Platform }
// GetConcurrency returns the resource concurrency limits.
func (p *Pipeline) GetConcurrency() manifest.Concurrency { return p.Concurrency }
// GetStep returns the named step. If no step exists with the
// given name, a nil value is returned.
func (p *Pipeline) GetStep(name string) *Step {
for _, step := range p.Steps {
if step.Name == name {
return step
}
}
return nil
}
type (
// Step defines a Pipeline step.
Step struct {
Command []string `json:"command,omitempty"`
Commands []string `json:"commands,omitempty"`
Detach bool `json:"detach,omitempty"`
DependsOn []string `json:"depends_on,omitempty" yaml:"depends_on"`
Devices []*VolumeDevice `json:"devices,omitempty"`
DNS []string `json:"dns,omitempty"`
DNSSearch []string `json:"dns_search,omitempty" yaml:"dns_search"`
Entrypoint []string `json:"entrypoint,omitempty"`
Environment map[string]*manifest.Variable `json:"environment,omitempty"`
ExtraHosts []string `json:"extra_hosts,omitempty" yaml:"extra_hosts"`
Failure string `json:"failure,omitempty"`
Image string `json:"image,omitempty"`
Network string `json:"network_mode,omitempty" yaml:"network_mode"`
Name string `json:"name,omitempty"`
Privileged bool `json:"privileged,omitempty"`
Pull string `json:"pull,omitempty"`
Settings map[string]*manifest.Parameter `json:"settings,omitempty"`
Shell string `json:"shell,omitempty"`
User string `json:"user,omitempty"`
Volumes []*VolumeMount `json:"volumes,omitempty"`
When manifest.Conditions `json:"when,omitempty"`
WorkingDir string `json:"working_dir,omitempty" yaml:"working_dir"`
// Resources *Resources `json:"resources,omitempty"`
}
// Volume that can be mounted by containers.
Volume struct {
Name string `json:"name,omitempty"`
EmptyDir *VolumeEmptyDir `json:"temp,omitempty" yaml:"temp"`
HostPath *VolumeHostPath `json:"host,omitempty" yaml:"host"`
}
// VolumeDevice describes a mapping of a raw block
// device within a container.
VolumeDevice struct {
Name string `json:"name,omitempty"`
DevicePath string `json:"path,omitempty" yaml:"path"`
}
// VolumeMount describes a mounting of a Volume
// within a container.
VolumeMount struct {
Name string `json:"name,omitempty"`
MountPath string `json:"path,omitempty" yaml:"path"`
}
// VolumeEmptyDir mounts a temporary directory from the
// host node's filesystem into the container. This can
// be used as a shared scratch space.
VolumeEmptyDir struct {
Medium string `json:"medium,omitempty"`
SizeLimit manifest.BytesSize `json:"size_limit,omitempty" yaml:"size_limit"`
}
// VolumeHostPath mounts a file or directory from the
// host node's filesystem into your container.
VolumeHostPath struct {
Path string `json:"path,omitempty"`
}
// Workspace represents the pipeline workspace configuration.
Workspace struct {
Base string `json:"base,omitempty"`
Path string `json:"path,omitempty"`
}
)

View File

@@ -0,0 +1,71 @@
// 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 resource
import (
"testing"
"github.com/drone/runner-go/manifest"
"github.com/google/go-cmp/cmp"
)
func TestGetStep(t *testing.T) {
step1 := &Step{Name: "build"}
step2 := &Step{Name: "test"}
pipeline := &Pipeline{
Steps: []*Step{step1, step2},
}
if pipeline.GetStep("build") != step1 {
t.Errorf("Expected named step")
}
if pipeline.GetStep("deploy") != nil {
t.Errorf("Expected nil step")
}
}
func TestGetters(t *testing.T) {
platform := manifest.Platform{
OS: "linux",
Arch: "amd64",
}
trigger := manifest.Conditions{
Branch: manifest.Condition{
Include: []string{"master"},
},
}
pipeline := &Pipeline{
Version: "1.0.0",
Kind: "pipeline",
Type: "docker",
Name: "default",
Deps: []string{"before"},
Platform: platform,
Trigger: trigger,
}
if got, want := pipeline.GetVersion(), pipeline.Version; got != want {
t.Errorf("Want Version %s, got %s", want, got)
}
if got, want := pipeline.GetKind(), pipeline.Kind; got != want {
t.Errorf("Want Kind %s, got %s", want, got)
}
if got, want := pipeline.GetType(), pipeline.Type; got != want {
t.Errorf("Want Type %s, got %s", want, got)
}
if got, want := pipeline.GetName(), pipeline.Name; got != want {
t.Errorf("Want Name %s, got %s", want, got)
}
if diff := cmp.Diff(pipeline.GetDependsOn(), pipeline.Deps); diff != "" {
t.Errorf("Unexpected Deps")
t.Log(diff)
}
if diff := cmp.Diff(pipeline.GetTrigger(), pipeline.Trigger); diff != "" {
t.Errorf("Unexpected Trigger")
t.Log(diff)
}
if got, want := pipeline.GetPlatform(), pipeline.Platform; got != want {
t.Errorf("Want Platform %s, got %s", want, got)
}
}

15
engine/resource/testdata/linterr.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
---
kind: pipeline
type: docker
server:
image: docker-18-04
region: nyc1
size: s-1vcpu-1gb
steps:
- commands:
- go build
- go test
...

View File

@@ -0,0 +1,8 @@
---
kind: pipeline
type: docker
steps:
foo: bar
...

54
engine/resource/testdata/manifest.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
---
kind: signature
hmac: a8842634682b78946a2
---
kind: secret
type: encrypted
name: token
data: f0e4c2f76c58916ec25
---
kind: pipeline
type: docker
name: default
version: 1
platform:
os: linux
arch: arm64
workspace:
path: /drone/src
clone:
depth: 50
steps:
- name: build
image: golang
detach: false
failure: ignore
commands:
- go build
- go test
environment:
GOOS: linux
GOARCH: arm64
depends_on: [ clone ]
when:
event: [ push ]
services:
- name: redis
image: redis:latest
entrypoint: [ "/bin/redis-server" ]
command: [ "--debug" ]
image_pull_secrets:
- dockerconfigjson
trigger:
branch: [ master ]
...

5
engine/resource/testdata/nomatch.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
---
kind: pipeline
type: docker
...

115
engine/spec.go Normal file
View File

@@ -0,0 +1,115 @@
// 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 engine
type (
// Spec provides the pipeline spec. This provides the
// required instructions for reproducible pipeline
// execution.
Spec struct {
Platform Platform `json:"platform,omitempty"`
Files []*File `json:"files,omitempty"`
Steps []*Step `json:"steps,omitempty"`
Volumes []*Volume `json:"volumes,omitempty"`
Network Network `json:"network"`
}
// Step defines a pipeline step.
Step struct {
ID string `json:"id,omitempty"`
Command []string `json:"args,omitempty"`
Detach bool `json:"detach,omitempty"`
DependsOn []string `json:"depends_on,omitempty"`
DNS []string `json:"dns,omitempty"`
DNSSearch []string `json:"dns_search,omitempty"`
Entrypoint []string `json:"entrypoint,omitempty"`
Envs map[string]string `json:"environment,omitempty"`
ExtraHosts []string `json:"extra_hosts,omitempty"`
Files []*File `json:"files,omitempty"`
IgnoreErr bool `json:"ignore_err,omitempty"`
IgnoreStdout bool `json:"ignore_stderr,omitempty"`
IgnoreStderr bool `json:"ignore_stdout,omitempty"`
Image string `json:"image,omitempty"`
Name string `json:"name,omitempty"`
Network string `json:"network,omitempty"`
Networks []string `json:"networks,omitempty"`
Privileged bool `json:"privileged,omitempty"`
Pull PullPolicy `json:"pull,omitempty"`
RunPolicy RunPolicy `json:"run_policy,omitempty"`
Secrets []*Secret `json:"secrets,omitempty"`
User string `json:"user,omitempty"`
Volumes []*VolumeMount `json:"volumes,omitempty"`
WorkingDir string `json:"working_dir,omitempty"`
}
// File defines a file that should be uploaded or
// mounted somewhere in the step container or virtual
// machine prior to command execution.
File struct {
Path string `json:"path,omitempty"`
Mode uint32 `json:"mode,omitempty"`
Data []byte `json:"data,omitempty"`
IsDir bool `json:"is_dir,omitempty"`
}
// Platform defines the target platform.
Platform struct {
OS string `json:"os,omitempty"`
Arch string `json:"arch,omitempty"`
Variant string `json:"variant,omitempty"`
Version string `json:"version,omitempty"`
}
// Secret represents a secret variable.
Secret struct {
Name string `json:"name,omitempty"`
Env string `json:"env,omitempty"`
Data []byte `json:"data,omitempty"`
Mask bool `json:"mask,omitempty"`
}
// State represents the process state.
State struct {
ExitCode int // Container exit code
Exited bool // Container exited
OOMKilled bool // Container is oom killed
}
// Volume that can be mounted by containers.
Volume struct {
EmptyDir *VolumeEmptyDir `json:"temp,omitempty"`
HostPath *VolumeHostPath `json:"host,omitempty"`
}
// VolumeMount describes a mounting of a Volume
// within a container.
VolumeMount struct {
Name string `json:"name,omitempty"`
Path string `json:"path,omitempty"`
}
// VolumeEmptyDir mounts a temporary directory from the
// host node's filesystem into the container. This can
// be used as a shared scratch space.
VolumeEmptyDir struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Medium string `json:"medium,omitempty"`
SizeLimit int64 `json:"size_limit,omitempty"`
}
// VolumeHostPath mounts a file or directory from the
// host node's filesystem into your container.
VolumeHostPath struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Path string `json:"path,omitempty"`
}
// Network that is created and attached to containers
Network struct {
ID string `json:"id,omitempty"`
}
)

5
engine/util.go Normal file
View File

@@ -0,0 +1,5 @@
// 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 engine

5
engine/util_test.go Normal file
View File

@@ -0,0 +1,5 @@
// 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 engine

33
go.mod Normal file
View File

@@ -0,0 +1,33 @@
module github.com/drone-runners/drone-runner-docker
go 1.12
require (
github.com/buildkite/yaml v2.1.0+incompatible
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9
github.com/digitalocean/godo v1.19.0
github.com/docker/distribution v2.7.1+incompatible
github.com/drone/drone-go v1.0.5-0.20190504210458-4d6116b897ba
github.com/drone/drone-runtime v1.0.7-0.20190729202838-87c84080f4a1
github.com/drone/drone-yaml v1.2.2
github.com/drone/envsubst v1.0.2
github.com/drone/runner-go v1.2.3-0.20191008184914-35b0486b7f23
github.com/drone/signal v1.0.0
github.com/ghodss/yaml v1.0.0
github.com/google/go-cmp v0.3.0
github.com/gosimple/slug v1.5.0
github.com/hashicorp/go-multierror v1.0.0
github.com/joho/godotenv v1.3.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/kr/fs v0.1.0 // indirect
github.com/mattn/go-isatty v0.0.8
github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4
github.com/pkg/errors v0.8.1 // indirect
github.com/pkg/sftp v1.10.1-0.20190613163056-79ae07e7783e
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect
github.com/sirupsen/logrus v1.4.2
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/sync v0.0.0-20190423024810-112230192c58
gopkg.in/alecthomas/kingpin.v2 v2.2.6
)

160
go.sum Normal file
View File

@@ -0,0 +1,160 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
docker.io/go-docker v1.0.0/go.mod h1:7tiAn5a0LFmjbPDbyTPOaTTOuG1ZRNXdPA6RvKY+fpY=
github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d h1:j6oB/WPCigdOkxtuPl1VSIiLpy7Mdsu6phQffbF19Ng=
github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d/go.mod h1:3cARGAK9CfW3HoxCy1a0G4TKrdiKke8ftOMEOHyySYs=
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e h1:rl2Aq4ZODqTDkeSqQBy+fzpZPamacO1Srp8zq7jf2Sc=
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/bmatcuk/doublestar v1.1.1 h1:YroD6BJCZBYx06yYFEWvUuKVWQn3vLLQAVmDmvTSaiQ=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/buildkite/yaml v2.1.0+incompatible h1:xirI+ql5GzfikVNDmt+yeiXpf/v1Gt03qXTtT5WXdr8=
github.com/buildkite/yaml v2.1.0+incompatible/go.mod h1:UoU8vbcwu1+vjZq01+KrpSeLBgQQIjL/H7Y6KwikUrI=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9 h1:74lLNRzvsdIlkTgfDSMuaPjBr4cf6k7pwQQANm/yLKU=
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/digitalocean/godo v1.19.0 h1:9ApuchfzGD/XI8Zm0RRnZnytdfYHPjPTRKTnmzQNV7o=
github.com/digitalocean/godo v1.19.0/go.mod h1:AAPQ+tiM4st79QHlEBTg8LM7JQNre4SAQCbn56wEyKY=
github.com/docker/distribution v0.0.0-20170726174610-edc3ab29cdff/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/drone/drone-go v1.0.4 h1:Yom1lix1Lmk3KmKIsBSQJF1bw0YR2lDGaFQrXxqHMko=
github.com/drone/drone-go v1.0.4/go.mod h1:GxyeGClYohaKNYJv/ZpsmVHtMJ7WhoT+uDaJNcDIrk4=
github.com/drone/drone-go v1.0.5-0.20190504210458-4d6116b897ba h1:GKiT4UPBligLXJAP1zRllHvTUygAAlgS3t9LM9aasp0=
github.com/drone/drone-go v1.0.5-0.20190504210458-4d6116b897ba/go.mod h1:GxyeGClYohaKNYJv/ZpsmVHtMJ7WhoT+uDaJNcDIrk4=
github.com/drone/drone-runtime v1.0.7-0.20190729202838-87c84080f4a1 h1:9xaZM1rM1/0FqFEijgnFcvWd0vRqOw+iO1YR7pBgPCw=
github.com/drone/drone-runtime v1.0.7-0.20190729202838-87c84080f4a1/go.mod h1:+osgwGADc/nyl40J0fdsf8Z09bgcBZXvXXnLOY48zYs=
github.com/drone/drone-yaml v1.2.2 h1:Srf8OlAHhR7SXX5Ax01dP5tpZENsrEKyg35E2nNkIew=
github.com/drone/drone-yaml v1.2.2/go.mod h1:QsqliFK8nG04AHFN9tTn9XJomRBQHD4wcejWW1uz/10=
github.com/drone/envsubst v1.0.2 h1:dpYLMAspQHW0a8dZpLRKe9jCNvIGZPhCPrycZzIHdqo=
github.com/drone/envsubst v1.0.2/go.mod h1:bkZbnc/2vh1M12Ecn7EYScpI4YGYU0etwLJICOWi8Z0=
github.com/drone/runner-go v1.1.0 h1:x2UEG8POjACkN28tqyHOEffZ4V4XUjea95lSbzx+xPY=
github.com/drone/runner-go v1.1.0/go.mod h1:+XIpVivm7KINzTD1oabOy+IwBKNBExl0Sqwhlh8E7/0=
github.com/drone/runner-go v1.1.1-0.20190715182255-1c863ab7b042 h1:KAuPeK2bf0UMA/PegB4LLrzqwuHIGB2fzFeUf8pygwM=
github.com/drone/runner-go v1.1.1-0.20190715182255-1c863ab7b042/go.mod h1:GvB5hX023g0n5UZUjMBmudk6givdDDuLcls7Nolm5v8=
github.com/drone/runner-go v1.1.1-0.20190715221101-a8307ea13bc0 h1:9NFdh0aIbP9ls4/GCWW+YwRb/PaQUQxrhfxfNOt4XEw=
github.com/drone/runner-go v1.1.1-0.20190715221101-a8307ea13bc0/go.mod h1:GvB5hX023g0n5UZUjMBmudk6givdDDuLcls7Nolm5v8=
github.com/drone/runner-go v1.1.1-0.20190716043234-8449fe8013cd h1:DKxgLLjdzPUNuJVpLUIndY2amxR1ciLxqIXaoy4/SYA=
github.com/drone/runner-go v1.1.1-0.20190716043234-8449fe8013cd/go.mod h1:GvB5hX023g0n5UZUjMBmudk6givdDDuLcls7Nolm5v8=
github.com/drone/runner-go v1.1.1-0.20190716194717-969b4e42cd4b h1:PWQTdNZujrBy1ESeAPa2afKSvWjD/YfCLQgsaAz4drA=
github.com/drone/runner-go v1.1.1-0.20190716194717-969b4e42cd4b/go.mod h1:GvB5hX023g0n5UZUjMBmudk6givdDDuLcls7Nolm5v8=
github.com/drone/runner-go v1.2.2 h1:fwYgjyJl6KdjQGEUFof9+HLtNpK3iHq7UuR+/aYNyDk=
github.com/drone/runner-go v1.2.3-0.20191008184914-35b0486b7f23 h1:sj9myNBjsR7C6HFdR0Lp79KtZw5MHf97FfRIsmoA1Ys=
github.com/drone/runner-go v1.2.3-0.20191008184914-35b0486b7f23/go.mod h1:GvB5hX023g0n5UZUjMBmudk6givdDDuLcls7Nolm5v8=
github.com/drone/signal v1.0.0 h1:NrnM2M/4yAuU/tXs6RP1a1ZfxnaHwYkd0kJurA1p6uI=
github.com/drone/signal v1.0.0/go.mod h1:S8t92eFT0g4WUgEc/LxG+LCuiskpMNsG0ajAMGnyZpc=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gogo/protobuf v0.0.0-20170307180453-100ba4e88506/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gosimple/slug v1.5.0 h1:AIIjgCjHcLpX8LzM2NpG4QGW9kUfqv0OLiFRfPv/H3E=
github.com/gosimple/slug v1.5.0/go.mod h1:ER78kgg1Mv0NQGlXiDe57DpCyfbNywXXZ9mIorhxAf0=
github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4 h1:dnMxwus89s86tI8rcGVp2HwZzlz7c5o92VOy7dSckBQ=
github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4/go.mod h1:cojhOHk1gbMeklOyDP2oKKLftefXoJreOQGOrXk+Z38=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/petar/GoLLRB v0.0.0-20130427215148-53be0d36a84c/go.mod h1:HUpKUBZnpzkdx0kD/+Yfuft+uD3zHGtXF/XJB14TUr4=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.0 h1:DGA1KlA9esU6WcicH+P8PxFZOl15O6GYtab1cIJdOlE=
github.com/pkg/sftp v1.10.0/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk=
github.com/pkg/sftp v1.10.1-0.20190613163056-79ae07e7783e h1:OFJvqBwYiN41kBIfsgm7DZhqT3bMljQ4UIr86BcPoVI=
github.com/pkg/sftp v1.10.1-0.20190613163056-79ae07e7783e/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/vinzenz/yaml v0.0.0-20170920082545-91409cdd725d/go.mod h1:mb5taDqMnJiZNRQ3+02W2IFG+oEz1+dTuCXkp4jpkfo=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
k8s.io/api v0.0.0-20181130031204-d04500c8c3dd/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
k8s.io/apimachinery v0.0.0-20181201231028-18a5ff3097b4/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/client-go v9.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

6
internal/internal.go Normal file
View File

@@ -0,0 +1,6 @@
// 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 internal contains runner internals.
package internal

51
internal/match/match.go Normal file
View File

@@ -0,0 +1,51 @@
// 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 match
import (
"path/filepath"
"github.com/drone/drone-go/drone"
)
// NOTE most runners do not require match capabilities. This is
// provided as a defense in depth mechanism given the sensitive
// nature of this runner executing code directly on the host.
// The matching function is a last line of defence to prevent
// unauthorized code from running on the host machine.
// Func returns a new match function that returns true if the
// repository and build do not match the allowd repository names
// and build events.
func Func(repos, events []string, trusted bool) func(*drone.Repo, *drone.Build) bool {
return func(repo *drone.Repo, build *drone.Build) bool {
// if trusted mode is enabled, only match repositories
// that are trusted.
if trusted && repo.Trusted == false {
return false
}
if match(repo.Slug, repos) == false {
return false
}
if match(build.Event, events) == false {
return false
}
return true
}
}
func match(s string, patterns []string) bool {
// if no matching patterns are defined the string
// is always considered a match.
if len(patterns) == 0 {
return true
}
for _, pattern := range patterns {
if match, _ := filepath.Match(pattern, s); match {
return true
}
}
return false
}

View File

@@ -0,0 +1,125 @@
// 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 match
import (
"testing"
"github.com/drone/drone-go/drone"
)
func TestFunc(t *testing.T) {
tests := []struct {
repo string
event string
trusted bool
match bool
matcher func(*drone.Repo, *drone.Build) bool
}{
//
// Expect match true
//
// repository, event and trusted flag matching
{
repo: "octocat/hello-world",
event: "push",
trusted: true,
match: true,
matcher: Func([]string{"spaceghost/*", "octocat/*"}, []string{"push"}, true),
},
// repoisitory matching
{
repo: "octocat/hello-world",
event: "pull_request",
trusted: false,
match: true,
matcher: Func([]string{"spaceghost/*", "octocat/*"}, []string{}, false),
},
// event matching
{
repo: "octocat/hello-world",
event: "pull_request",
trusted: false,
match: true,
matcher: Func([]string{}, []string{"pull_request"}, false),
},
// trusted flag matching
{
repo: "octocat/hello-world",
event: "pull_request",
trusted: true,
match: true,
matcher: Func([]string{}, []string{}, true),
},
//
// Expect match false
//
// repository matching
{
repo: "spaceghost/hello-world",
event: "pull_request",
trusted: false,
match: false,
matcher: Func([]string{"octocat/*"}, []string{}, false),
},
// event matching
{
repo: "octocat/hello-world",
event: "pull_request",
trusted: false,
match: false,
matcher: Func([]string{}, []string{"push"}, false),
},
// trusted flag matching
{
repo: "octocat/hello-world",
event: "pull_request",
trusted: false,
match: false,
matcher: Func([]string{}, []string{}, true),
},
// does not match repository
{
repo: "foo/hello-world",
event: "push",
trusted: true,
match: false,
matcher: Func([]string{"spaceghost/*", "octocat/*"}, []string{"push"}, true),
},
// does not match event
{
repo: "octocat/hello-world",
event: "pull_request",
trusted: true,
match: false,
matcher: Func([]string{"spaceghost/*", "octocat/*"}, []string{"push"}, true),
},
// does not match trusted flag
{
repo: "octocat/hello-world",
event: "push",
trusted: false,
match: false,
matcher: Func([]string{"spaceghost/*", "octocat/*"}, []string{"push"}, true),
},
}
for i, test := range tests {
repo := &drone.Repo{
Slug: test.repo,
Trusted: test.trusted,
}
build := &drone.Build{
Event: test.event,
}
match := test.matcher(repo, build)
if match != test.match {
t.Errorf("Expect match %v at index %d", test.match, i)
}
}
}

8
internal/mock/mock.go Normal file
View File

@@ -0,0 +1,8 @@
// 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 mock
//go:generate mockgen -package=mock -destination=mock_engine_gen.go github.com/drone-runners/drone-runner-docker/engine Engine
//go:generate mockgen -package=mock -destination=mock_execer_gen.go github.com/drone-runners/drone-runner-docker/runtime Execer

View File

@@ -0,0 +1,177 @@
// 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 platform contains code to provision and destroy server
// instances on the Digital Ocean cloud platform.
package platform
import (
"context"
"time"
"github.com/drone/runner-go/logger"
"github.com/digitalocean/godo"
"golang.org/x/oauth2"
)
type (
// RegisterArgs provides arguments to register the SSH
// public key with the account.
RegisterArgs struct {
Fingerprint string
Name string
Data string
Token string
}
// DestroyArgs provides arguments to destroy the server
// instance.
DestroyArgs struct {
ID int
IP string
Token string
}
// ProvisionArgs provides arguments to provision instances.
ProvisionArgs struct {
Key string
Image string
Name string
Region string
Size string
Token string
}
// Instance represents a provisioned server instance.
Instance struct {
ID int
IP string
}
)
// Provision provisions the server instance.
func Provision(ctx context.Context, args ProvisionArgs) (Instance, error) {
res := Instance{}
req := &godo.DropletCreateRequest{
Name: args.Name,
Region: args.Region,
Size: args.Size,
Tags: []string{"drone"},
IPv6: false,
SSHKeys: []godo.DropletCreateSSHKey{
{Fingerprint: args.Key},
},
Image: godo.DropletCreateImage{
Slug: args.Image,
},
}
logger := logger.FromContext(ctx).
WithField("region", req.Region).
WithField("image", req.Image.Slug).
WithField("size", req.Size).
WithField("name", req.Name)
logger.Debug("instance create")
client := newClient(ctx, args.Token)
droplet, _, err := client.Droplets.Create(ctx, req)
if err != nil {
logger.WithError(err).Error("cannot create instance")
return res, err
}
// record the droplet ID
res.ID = droplet.ID
logger.WithField("name", req.Name).
Info("instance created")
// poll the digitalocean endpoint for server updates
// and exit when a network address is allocated.
interval := time.Duration(0)
poller:
for {
select {
case <-ctx.Done():
logger.WithField("name", req.Name).
Debug("cannot ascertain network")
return res, ctx.Err()
case <-time.After(interval):
interval = time.Second * 30
logger.WithField("name", req.Name).
Debug("find instance network")
droplet, _, err = client.Droplets.Get(ctx, res.ID)
if err != nil {
logger.WithError(err).
Error("cannot find instance")
return res, err
}
for _, network := range droplet.Networks.V4 {
if network.Type == "public" {
res.IP = network.IPAddress
}
}
if res.IP != "" {
break poller
}
}
}
logger.WithField("name", req.Name).
WithField("ip", res.IP).
WithField("id", res.ID).
Debug("instance network ready")
return res, nil
}
// Destroy destroys the server instance.
func Destroy(ctx context.Context, args DestroyArgs) error {
client := newClient(ctx, args.Token)
_, err := client.Droplets.Delete(ctx, args.ID)
if err != nil {
logger.FromContext(ctx).
WithError(err).
WithField("id", args.ID).
WithField("ip", args.IP).
Error("cannot terminate server")
}
return err
}
// RegisterKey registers the ssh public key with the account if
// it is not already registered.
func RegisterKey(ctx context.Context, args RegisterArgs) error {
client := newClient(ctx, args.Token)
_, _, err := client.Keys.GetByFingerprint(ctx, args.Fingerprint)
if err == nil {
return nil
}
// if the ssh key does not exists we attempt to register
// with the digital ocean account.
_, _, err = client.Keys.Create(ctx, &godo.KeyCreateRequest{
Name: args.Name,
PublicKey: args.Data,
})
return err
}
// helper function returns a new docker client.
func newClient(ctx context.Context, token string) *godo.Client {
return godo.NewClient(
oauth2.NewClient(ctx, oauth2.StaticTokenSource(
&oauth2.Token{
AccessToken: token,
},
)),
)
}

View File

@@ -0,0 +1,5 @@
// 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 platform

View File

@@ -0,0 +1,93 @@
# Polyform Free Trial License 1.0.0
<https://polyformproject.org/licenses/free-trial/1.0.0>
## Acceptance
In order to get any license under these terms, you must agree
to them as both strict obligations and conditions to all
your licenses.
## Copyright License
The licensor grants you a copyright license for the software
to do everything you might do with the software that would
otherwise infringe the licensor's copyright in it for any
permitted purpose. However, you may only make changes or
new works based on the software according to [Changes and New
Works License](#changes-and-new-works-license), and you may
not distribute copies of the software.
## Changes and New Works License
The licensor grants you an additional copyright license to
make changes and new works based on the software for any
permitted purpose.
## Patent License
The licensor grants you a patent license for the software that
covers patent claims the licensor can license, or becomes able
to license, that you would infringe by using the software.
## Fair Use
You may have "fair use" rights for the software under the
law. These terms do not limit them.
## Free Trial
Use to evaluate whether the software suits a particular
application for less than 32 consecutive calendar days, on
behalf of you or your company, is use for a permitted purpose.
## No Other Rights
These terms do not allow you to sublicense or transfer any of
your licenses to anyone else, or prevent the licensor from
granting licenses to anyone else. These terms do not imply
any other licenses.
## Patent Defense
If you make any written claim that the software infringes or
contributes to infringement of any patent, your patent license
for the software granted under these terms ends immediately. If
your company makes such a claim, your patent license ends
immediately for work on behalf of your company.
## Violations
If you violate any of these terms, or do anything with the
software not covered by your licenses, all your licenses
end immediately.
## No Liability
***As far as the law allows, the software comes as is, without
any warranty or condition, and the licensor will not be liable
to you for any damages arising out of these terms or the use
or nature of the software, under any kind of legal claim.***
## Definitions
The **licensor** is the individual or entity offering these
terms, and the **software** is the software the licensor makes
available under these terms.
**You** refers to the individual or entity agreeing to these
terms.
**Your company** is any legal entity, sole proprietorship,
or other kind of organization that you work for, plus all
organizations that have control over, are under the control of,
or are under common control with that organization. **Control**
means ownership of substantially all the assets of an entity,
or the power to direct its management and policies by vote,
contract, or otherwise. Control can be direct or indirect.
**Your licenses** are all the licenses granted to you for the
software under these terms.
**Use** means anything you do with the software requiring one
of your licenses.

View File

@@ -0,0 +1,121 @@
# Polyform Small Business License 1.0.0
<https://polyformproject.org/licenses/small-business/1.0.0>
## Acceptance
In order to get any license under these terms, you must agree
to them as both strict obligations and conditions to all
your licenses.
## Copyright License
The licensor grants you a copyright license for the
software to do everything you might do with the software
that would otherwise infringe the licensor's copyright
in it for any permitted purpose. However, you may
only distribute the software according to [Distribution
License](#distribution-license) and make changes or new works
based on the software according to [Changes and New Works
License](#changes-and-new-works-license).
## Distribution License
The licensor grants you an additional copyright license
to distribute copies of the software. Your license
to distribute covers distributing the software with
changes and new works permitted by [Changes and New Works
License](#changes-and-new-works-license).
## Notices
You must ensure that anyone who gets a copy of any part of
the software from you also gets a copy of these terms or the
URL for them above, as well as copies of any plain-text lines
beginning with `Required Notice:` that the licensor provided
with the software. For example:
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
## Changes and New Works License
The licensor grants you an additional copyright license to
make changes and new works based on the software for any
permitted purpose.
## Patent License
The licensor grants you a patent license for the software that
covers patent claims the licensor can license, or becomes able
to license, that you would infringe by using the software.
## Fair Use
You may have "fair use" rights for the software under the
law. These terms do not limit them.
## Small Business
Use of the software for the benefit of your company is use for
a permitted purpose if your company has fewer than 100 total
individuals working as employees and independent contractors,
and less than 1,000,000 USD (2019) total revenue in the prior
tax year. Adjust this revenue threshold for inflation according
to the United States Bureau of Labor Statistics' consumer price
index for all urban consumers, U.S. city average, for all items,
not seasonally adjusted, with 19821984=100 reference base.
## No Other Rights
These terms do not allow you to sublicense or transfer any of
your licenses to anyone else, or prevent the licensor from
granting licenses to anyone else. These terms do not imply
any other licenses.
## Patent Defense
If you make any written claim that the software infringes or
contributes to infringement of any patent, your patent license
for the software granted under these terms ends immediately. If
your company makes such a claim, your patent license ends
immediately for work on behalf of your company.
## Violations
The first time you are notified in writing that you have
violated any of these terms, or done anything with the software
not covered by your licenses, your licenses can nonetheless
continue if you come into full compliance with these terms,
and take practical steps to correct past violations, within
32 days of receiving notice. Otherwise, all your licenses
end immediately.
## No Liability
***As far as the law allows, the software comes as is, without
any warranty or condition, and the licensor will not be liable
to you for any damages arising out of these terms or the use
or nature of the software, under any kind of legal claim.***
## Definitions
The **licensor** is the individual or entity offering these
terms, and the **software** is the software the licensor makes
available under these terms.
**You** refers to the individual or entity agreeing to these
terms.
**Your company** is any legal entity, sole proprietorship,
or other kind of organization that you work for, plus all
organizations that have control over, are under the control of,
or are under common control with that organization. **Control**
means ownership of substantially all the assets of an entity,
or the power to direct its management and policies by vote,
contract, or otherwise. Control can be direct or indirect.
**Your licenses** are all the licenses granted to you for the
software under these terms.
**Use** means anything you do with the software requiring one
of your licenses.

14
main.go Normal file
View File

@@ -0,0 +1,14 @@
// 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 main
import (
"github.com/drone-runners/drone-runner-docker/command"
_ "github.com/joho/godotenv/autoload"
)
func main() {
command.Command()
}

242
runtime/execer.go Normal file
View File

@@ -0,0 +1,242 @@
// Code generated automatically. DO NOT EDIT.
// 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 runtime
import (
"context"
"sync"
"github.com/drone-runners/drone-runner-docker/engine"
"github.com/drone-runners/drone-runner-docker/engine/replacer"
"github.com/drone/drone-go/drone"
"github.com/drone/runner-go/environ"
"github.com/drone/runner-go/logger"
"github.com/drone/runner-go/pipeline"
"github.com/hashicorp/go-multierror"
"github.com/natessilva/dag"
"golang.org/x/sync/semaphore"
)
// Execer is the execution context for executing the intermediate
// representation of a pipeline.
type Execer interface {
Exec(context.Context, *engine.Spec, *pipeline.State) error
}
type execer struct {
mu sync.Mutex
engine engine.Engine
reporter pipeline.Reporter
streamer pipeline.Streamer
sem *semaphore.Weighted
}
// NewExecer returns a new execer used
func NewExecer(
reporter pipeline.Reporter,
streamer pipeline.Streamer,
engine engine.Engine,
procs int64,
) Execer {
exec := &execer{
reporter: reporter,
streamer: streamer,
engine: engine,
}
if procs > 0 {
// optional semaphor that limits the number of steps
// that can execute concurrently.
exec.sem = semaphore.NewWeighted(procs)
}
return exec
}
// Exec executes the intermediate representation of the pipeline
// and returns an error if execution fails.
func (e *execer) Exec(ctx context.Context, spec *engine.Spec, state *pipeline.State) error {
defer e.engine.Destroy(noContext, spec)
if err := e.engine.Setup(noContext, spec); err != nil {
state.FailAll(err)
return e.reporter.ReportStage(noContext, state)
}
// create a directed graph, where each vertex in the graph
// is a pipeline step.
var d dag.Runner
for _, s := range spec.Steps {
step := s
d.AddVertex(step.Name, func() error {
return e.exec(ctx, state, spec, step)
})
}
// create the vertex edges from the values configured in the
// depends_on attribute.
for _, s := range spec.Steps {
for _, dep := range s.DependsOn {
d.AddEdge(dep, s.Name)
}
}
var result error
if err := d.Run(); err != nil {
multierror.Append(result, err)
}
// once pipeline execution completes, notify the state
// manageer that all steps are finished.
state.FinishAll()
if err := e.reporter.ReportStage(noContext, state); err != nil {
multierror.Append(result, err)
}
return result
}
func (e *execer) exec(ctx context.Context, state *pipeline.State, spec *engine.Spec, step *engine.Step) error {
var result error
select {
case <-ctx.Done():
state.Cancel()
return nil
default:
}
log := logger.FromContext(ctx)
log = log.WithField("step.name", step.Name)
ctx = logger.WithContext(ctx, log)
if e.sem != nil {
// the semaphore limits the number of steps that can run
// concurrently. acquire the semaphore and release when
// the pipeline completes.
if err := e.sem.Acquire(ctx, 1); err != nil {
return nil
}
defer func() {
// recover from a panic to ensure the semaphore is
// released to prevent deadlock. we do not expect a
// panic, however, we are being overly cautious.
if r := recover(); r != nil {
// TODO(bradrydzewsi) log the panic.
}
// release the semaphore
e.sem.Release(1)
}()
}
switch {
case state.Skipped():
return nil
case state.Cancelled():
return nil
case step.RunPolicy == engine.RunNever:
return nil
case step.RunPolicy == engine.RunAlways:
break
case step.RunPolicy == engine.RunOnFailure && state.Failed() == false:
state.Skip(step.Name)
return e.reporter.ReportStep(noContext, state, step.Name)
case step.RunPolicy == engine.RunOnSuccess && state.Failed():
state.Skip(step.Name)
return e.reporter.ReportStep(noContext, state, step.Name)
}
state.Start(step.Name)
err := e.reporter.ReportStep(noContext, state, step.Name)
if err != nil {
return err
}
copy := cloneStep(step)
// the pipeline environment variables need to be updated to
// reflect the current state of the build and stage.
state.Lock()
copy.Envs = environ.Combine(
copy.Envs,
environ.Build(state.Build),
environ.Stage(state.Stage),
environ.Step(findStep(state, step.Name)),
)
state.Unlock()
// writer used to stream build logs.
wc := e.streamer.Stream(noContext, state, step.Name)
wc = replacer.New(wc, step.Secrets)
// if the step is configured as a daemon, it is detached
// from the main process and executed separately.
// todo(bradrydzewski) this code is still experimental.
if step.Detach {
go func() {
e.engine.Run(ctx, spec, copy, wc)
wc.Close()
}()
return nil
}
exited, err := e.engine.Run(ctx, spec, copy, wc)
// close the stream. If the session is a remote session, the
// full log buffer is uploaded to the remote server.
if err := wc.Close(); err != nil {
multierror.Append(result, err)
}
if exited != nil {
state.Finish(step.Name, exited.ExitCode)
err := e.reporter.ReportStep(noContext, state, step.Name)
if err != nil {
multierror.Append(result, err)
}
// if the exit code is 78 the system will skip all
// subsequent pending steps in the pipeline.
if exited.ExitCode == 78 {
state.SkipAll()
}
return result
}
switch err {
case context.Canceled, context.DeadlineExceeded:
state.Cancel()
return nil
}
// if the step failed with an internal error (as oppsed to a
// runtime error) the step is failed.
state.Fail(step.Name, err)
err = e.reporter.ReportStep(noContext, state, step.Name)
if err != nil {
multierror.Append(result, err)
}
return result
}
// helper function to clone a step. The runner mutates a step to
// update the environment variables to reflect the current
// pipeline state.
func cloneStep(src *engine.Step) *engine.Step {
dst := new(engine.Step)
*dst = *src
dst.Envs = environ.Combine(src.Envs)
return dst
}
// helper function returns the named step from the state.
func findStep(state *pipeline.State, name string) *drone.Step {
for _, step := range state.Stage.Steps {
if step.Name == name {
return step
}
}
panic("step not found: " + name)
}

39
runtime/execer_test.go Normal file
View File

@@ -0,0 +1,39 @@
// Code generated automatically. DO NOT EDIT.
// 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 runtime
import (
"testing"
)
func TestExec(t *testing.T) {
t.Skip()
}
func TestExec_NonZeroExit(t *testing.T) {
t.Skip()
}
func TestExec_Exit78(t *testing.T) {
t.Skip()
}
func TestExec_Error(t *testing.T) {
t.Skip()
}
func TestExec_CtxError(t *testing.T) {
t.Skip()
}
func TestExec_ReportError(t *testing.T) {
t.Skip()
}
func TestExec_SkipCtxDone(t *testing.T) {
t.Skip()
}

72
runtime/poller.go Normal file
View File

@@ -0,0 +1,72 @@
// Code generated automatically. DO NOT EDIT.
// 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 runtime
import (
"context"
"sync"
"github.com/drone/runner-go/client"
"github.com/drone/runner-go/logger"
)
var noContext = context.Background()
// Poller polls the server for pending stages and dispatches
// for execution by the Runner.
type Poller struct {
Client client.Client
Filter *client.Filter
Runner *Runner
}
// Poll opens N connections to the server to poll for pending
// stages for execution. Pending stages are dispatched to a
// Runner for execution.
func (p *Poller) Poll(ctx context.Context, n int) {
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)
go func(i int) {
for {
select {
case <-ctx.Done():
wg.Done()
return
default:
p.poll(ctx, i+1)
}
}
}(i)
}
wg.Wait()
}
// poll requests a stage for execution from the server, and then
// dispatches for execution.
func (p *Poller) poll(ctx context.Context, thread int) error {
log := logger.FromContext(ctx).WithField("thread", thread)
log.WithField("thread", thread).Debug("request stage from remote server")
// request a new build stage for execution from the central
// build server.
stage, err := p.Client.Request(ctx, p.Filter)
if err != nil {
log.WithError(err).Error("cannot request stage")
return err
}
// exit if a nil or empty stage is returned from the system
// and allow the runner to retry.
if stage == nil || stage.ID == 0 {
return nil
}
return p.Runner.Run(
logger.WithContext(noContext, log), stage)
}

27
runtime/poller_test.go Normal file
View File

@@ -0,0 +1,27 @@
// Code generated automatically. DO NOT EDIT.
// 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 runtime
import (
"testing"
)
func TestPoll(t *testing.T) {
t.Skip()
}
func TestPoll_NilStage(t *testing.T) {
t.Skip()
}
func TestPoll_EmptyStage(t *testing.T) {
t.Skip()
}
func TestPoll_RequestError(t *testing.T) {
t.Skip()
}

236
runtime/runner.go Normal file
View File

@@ -0,0 +1,236 @@
// Code generated automatically. DO NOT EDIT.
// 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 runtime
import (
"context"
"errors"
"fmt"
"strings"
"time"
"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/resource"
"github.com/drone/drone-go/drone"
"github.com/drone/envsubst"
"github.com/drone/runner-go/client"
"github.com/drone/runner-go/environ"
"github.com/drone/runner-go/logger"
"github.com/drone/runner-go/manifest"
"github.com/drone/runner-go/pipeline"
"github.com/drone/runner-go/secret"
)
// Runnner runs the pipeline.
type Runner struct {
// Client is the remote client responsible for interacting
// with the central server.
Client client.Client
// Execer is responsible for executing intermediate
// representation of the pipeline and returns its results.
Execer Execer
// Reporter reports pipeline status back to the remote
// server.
Reporter pipeline.Reporter
// Environ provides custom, global environment variables
// that are added to every pipeline step.
Environ map[string]string
// Machine provides the runner with the name of the host
// machine executing the pipeline.
Machine string
// Match is an optional function that returns true if the
// repository or build match user-defined criteria. This is
// intended as a security measure to prevent a runner from
// processing an unwanted pipeline.
Match func(*drone.Repo, *drone.Build) bool
// Secret provides the compiler with secrets.
Secret secret.Provider
}
// Run runs the pipeline stage.
func (s *Runner) Run(ctx context.Context, stage *drone.Stage) error {
log := logger.FromContext(ctx).
WithField("stage.id", stage.ID).
WithField("stage.name", stage.Name).
WithField("stage.number", stage.Number)
log.Debug("stage received")
// delivery to a single agent is not guaranteed, which means
// we need confirm receipt. The first agent that confirms
// receipt of the stage can assume ownership.
stage.Machine = s.Machine
err := s.Client.Accept(ctx, stage)
if err != nil {
log.WithError(err).Error("cannot accept stage")
return err
}
log.Debug("stage accepted")
data, err := s.Client.Detail(ctx, stage)
if err != nil {
log.WithError(err).Error("cannot get stage details")
return err
}
log = log.WithField("repo.id", data.Repo.ID).
WithField("repo.namespace", data.Repo.Namespace).
WithField("repo.name", data.Repo.Name).
WithField("build.id", data.Build.ID).
WithField("build.number", data.Build.Number)
log.Debug("stage details fetched")
ctxdone, cancel := context.WithCancel(ctx)
defer cancel()
timeout := time.Duration(data.Repo.Timeout) * time.Minute
ctxtimeout, cancel := context.WithTimeout(ctxdone, timeout)
defer cancel()
ctxcancel, cancel := context.WithCancel(ctxtimeout)
defer cancel()
// next we opens a connection to the server to watch for
// cancellation requests. If a build is cancelled the running
// stage should also be cancelled.
go func() {
done, _ := s.Client.Watch(ctxdone, data.Build.ID)
if done {
cancel()
log.Debugln("received cancellation")
} else {
log.Debugln("done listening for cancellations")
}
}()
envs := environ.Combine(
s.Environ,
environ.System(data.System),
environ.Repo(data.Repo),
environ.Build(data.Build),
environ.Stage(stage),
environ.Link(data.Repo, data.Build, data.System),
data.Build.Params,
)
// string substitution function ensures that string
// replacement variables are escaped and quoted if they
// contain a newline character.
subf := func(k string) string {
v := envs[k]
if strings.Contains(v, "\n") {
v = fmt.Sprintf("%q", v)
}
return v
}
state := &pipeline.State{
Build: data.Build,
Stage: stage,
Repo: data.Repo,
System: data.System,
}
// evaluates whether or not the agent can process the
// pipeline. An agent may choose to reject a repository
// or build for security reasons.
if s.Match != nil && s.Match(data.Repo, data.Build) == false {
log.Error("cannot process stage, access denied")
state.FailAll(errors.New("insufficient permission to run the pipeline"))
return s.Reporter.ReportStage(noContext, state)
}
// evaluates string replacement expressions and returns an
// update configuration file string.
config, err := envsubst.Eval(string(data.Config.Data), subf)
if err != nil {
log.WithError(err).Error("cannot emulate bash substitution")
state.FailAll(err)
return s.Reporter.ReportStage(noContext, state)
}
// parse the yaml configuration file.
manifest, err := manifest.ParseString(config)
if err != nil {
log.WithError(err).Error("cannot parse configuration file")
state.FailAll(err)
return s.Reporter.ReportStage(noContext, state)
}
// find the named stage in the yaml configuration file.
resource, err := resource.Lookup(stage.Name, manifest)
if err != nil {
log.WithError(err).Error("cannot find pipeline resource")
state.FailAll(err)
return s.Reporter.ReportStage(noContext, state)
}
secrets := secret.Combine(
secret.Static(data.Secrets),
secret.Encrypted(),
s.Secret,
)
// compile the yaml configuration file to an intermediate
// representation, and then
comp := &compiler.Compiler{
Pipeline: resource,
Manifest: manifest,
Environ: s.Environ,
Build: data.Build,
Stage: stage,
Repo: data.Repo,
System: data.System,
Netrc: data.Netrc,
Secret: secrets,
}
spec := comp.Compile(ctx)
for _, src := range spec.Steps {
// steps that are skipped are ignored and are not stored
// in the drone database, nor displayed in the UI.
if src.RunPolicy == engine.RunNever {
continue
}
stage.Steps = append(stage.Steps, &drone.Step{
Name: src.Name,
Number: len(stage.Steps) + 1,
StageID: stage.ID,
Status: drone.StatusPending,
ErrIgnore: src.IgnoreErr,
})
}
stage.Started = time.Now().Unix()
stage.Status = drone.StatusRunning
if err := s.Client.Update(ctx, stage); err != nil {
log.WithError(err).Error("cannot update stage")
return err
}
log.Debug("updated stage to running")
ctxcancel = logger.WithContext(ctxcancel, log)
err = s.Execer.Exec(ctxcancel, spec, state)
if err != nil {
log.WithError(err).Debug("stage failed")
return err
}
log.Debug("updated stage to complete")
return nil
}

7
runtime/runner_test.go Normal file
View File

@@ -0,0 +1,7 @@
// Code generated automatically. DO NOT EDIT.
// 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 runtime

18
scripts/build.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/sh
# disable go modules
export GOPATH=""
# disable cgo
export CGO_ENABLED=0
set -e
set -x
# linux
GOOS=linux GOARCH=amd64 go build -o release/linux/amd64/drone-runner-docker
GOOS=linux GOARCH=arm64 go build -o release/linux/arm64/drone-runner-docker
GOOS=linux GOARCH=arm go build -o release/linux/arm/drone-runner-docker
# windows
GOOS=windows go build -o release/windows/amd64/drone-runner-docker.exe