initial commit [ci skip]
This commit is contained in:
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
|
||||
|
||||
...
|
||||
Reference in New Issue
Block a user