initial commit [ci skip]
This commit is contained in:
53
engine/compiler/clone.go
Normal file
53
engine/compiler/clone.go
Normal 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),
|
||||
}
|
||||
}
|
||||
126
engine/compiler/clone_test.go
Normal file
126
engine/compiler/clone_test.go
Normal 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
238
engine/compiler/compiler.go
Normal 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
|
||||
}
|
||||
186
engine/compiler/compiler_test.go
Normal file
186
engine/compiler/compiler_test.go
Normal 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)
|
||||
}
|
||||
56
engine/compiler/encoder/encoder.go
Normal file
56
engine/compiler/encoder/encoder.go
Normal 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)
|
||||
}
|
||||
63
engine/compiler/encoder/encoder_test.go
Normal file
63
engine/compiler/encoder/encoder_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
71
engine/compiler/image/image.go
Normal file
71
engine/compiler/image/image.go
Normal 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
|
||||
}
|
||||
299
engine/compiler/image/image_test.go
Normal file
299
engine/compiler/image/image_test.go
Normal 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
85
engine/compiler/os.go
Normal 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
128
engine/compiler/os_test.go
Normal 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
35
engine/compiler/script.go
Normal 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)
|
||||
}
|
||||
5
engine/compiler/script_test.go
Normal file
5
engine/compiler/script_test.go
Normal 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
|
||||
59
engine/compiler/shell/powershell/powershell.go
Normal file
59
engine/compiler/shell/powershell/powershell.go
Normal 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 }
|
||||
`
|
||||
5
engine/compiler/shell/powershell/powershell_test.go
Normal file
5
engine/compiler/shell/powershell/powershell_test.go
Normal 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
|
||||
56
engine/compiler/shell/shell.go
Normal file
56
engine/compiler/shell/shell.go
Normal 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
|
||||
`
|
||||
5
engine/compiler/shell/shell_test.go
Normal file
5
engine/compiler/shell/shell_test.go
Normal 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
108
engine/compiler/step.go
Normal 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
104
engine/compiler/testdata/graph.json
vendored
Normal 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
20
engine/compiler/testdata/graph.yml
vendored
Normal 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
82
engine/compiler/testdata/match.json
vendored
Normal 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
26
engine/compiler/testdata/match.yml
vendored
Normal 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 ]
|
||||
83
engine/compiler/testdata/noclone_graph.json
vendored
Normal file
83
engine/compiler/testdata/noclone_graph.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
23
engine/compiler/testdata/noclone_graph.yml
vendored
Normal file
23
engine/compiler/testdata/noclone_graph.yml
vendored
Normal 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 ]
|
||||
62
engine/compiler/testdata/noclone_serial.json
vendored
Normal file
62
engine/compiler/testdata/noclone_serial.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
engine/compiler/testdata/noclone_serial.yml
vendored
Normal file
19
engine/compiler/testdata/noclone_serial.yml
vendored
Normal 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
|
||||
63
engine/compiler/testdata/run_always.json
vendored
Normal file
63
engine/compiler/testdata/run_always.json
vendored
Normal 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
20
engine/compiler/testdata/run_always.yml
vendored
Normal 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 ]
|
||||
63
engine/compiler/testdata/run_failure.json
vendored
Normal file
63
engine/compiler/testdata/run_failure.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
20
engine/compiler/testdata/run_failure.yml
vendored
Normal file
20
engine/compiler/testdata/run_failure.yml
vendored
Normal 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
25
engine/compiler/testdata/secret.yml
vendored
Normal 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
104
engine/compiler/testdata/serial.json
vendored
Normal 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
19
engine/compiler/testdata/serial.yml
vendored
Normal 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
132
engine/compiler/util.go
Normal 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
|
||||
}
|
||||
}
|
||||
200
engine/compiler/util_test.go
Normal file
200
engine/compiler/util_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
74
engine/compiler/workspace.go
Normal file
74
engine/compiler/workspace.go
Normal 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)
|
||||
}
|
||||
149
engine/compiler/workspace_test.go
Normal file
149
engine/compiler/workspace_test.go
Normal 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
118
engine/const.go
Normal 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
237
engine/const_test.go
Normal 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
23
engine/engine.go
Normal 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
33
engine/engine_impl.go
Normal 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
|
||||
}
|
||||
56
engine/replacer/replacer.go
Normal file
56
engine/replacer/replacer.go
Normal 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()
|
||||
}
|
||||
56
engine/replacer/replacer_test.go
Normal file
56
engine/replacer/replacer_test.go
Normal 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
|
||||
}
|
||||
5
engine/resource/linter.go
Normal file
5
engine/resource/linter.go
Normal 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
24
engine/resource/lookup.go
Normal 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")
|
||||
}
|
||||
45
engine/resource/lookup_test.go
Normal file
45
engine/resource/lookup_test.go
Normal 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
54
engine/resource/parser.go
Normal 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
|
||||
}
|
||||
159
engine/resource/parser_test.go
Normal file
159
engine/resource/parser_test.go
Normal 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
153
engine/resource/pipeline.go
Normal 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"`
|
||||
}
|
||||
)
|
||||
71
engine/resource/pipeline_test.go
Normal file
71
engine/resource/pipeline_test.go
Normal 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
15
engine/resource/testdata/linterr.yml
vendored
Normal 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
|
||||
|
||||
...
|
||||
8
engine/resource/testdata/malformed.yml
vendored
Normal file
8
engine/resource/testdata/malformed.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
|
||||
steps:
|
||||
foo: bar
|
||||
|
||||
...
|
||||
54
engine/resource/testdata/manifest.yml
vendored
Normal file
54
engine/resource/testdata/manifest.yml
vendored
Normal 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
5
engine/resource/testdata/nomatch.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
|
||||
...
|
||||
115
engine/spec.go
Normal file
115
engine/spec.go
Normal 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
5
engine/util.go
Normal 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
5
engine/util_test.go
Normal 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
|
||||
Reference in New Issue
Block a user