initial commit [ci skip]

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

View File

@@ -0,0 +1,5 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.
package resource

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

@@ -0,0 +1,24 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.
package resource
import (
"errors"
"github.com/drone/runner-go/manifest"
)
// Lookup returns the named pipeline from the Manifest.
func Lookup(name string, manifest *manifest.Manifest) (*Pipeline, error) {
for _, resource := range manifest.Resources {
if resource.GetName() != name {
continue
}
if pipeline, ok := resource.(*Pipeline); ok {
return pipeline, nil
}
}
return nil, errors.New("resource not found")
}

View File

@@ -0,0 +1,45 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.
package resource
import (
"testing"
"github.com/drone/runner-go/manifest"
)
func TestLookup(t *testing.T) {
want := &Pipeline{Name: "default"}
m := &manifest.Manifest{
Resources: []manifest.Resource{want},
}
got, err := Lookup("default", m)
if err != nil {
t.Error(err)
}
if got != want {
t.Errorf("Expect resource not found error")
}
}
func TestLookupNotFound(t *testing.T) {
m := &manifest.Manifest{
Resources: []manifest.Resource{
&manifest.Secret{
Kind: "secret",
Name: "password",
},
// matches name, but is not of kind pipeline
&manifest.Secret{
Kind: "secret",
Name: "default",
},
},
}
_, err := Lookup("default", m)
if err == nil {
t.Errorf("Expect resource not found error")
}
}

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

@@ -0,0 +1,54 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.
package resource
import (
"errors"
"github.com/drone/runner-go/manifest"
"github.com/buildkite/yaml"
)
func init() {
manifest.Register(parse)
}
// parse parses the raw resource and returns an Exec pipeline.
func parse(r *manifest.RawResource) (manifest.Resource, bool, error) {
if !match(r) {
return nil, false, nil
}
out := new(Pipeline)
err := yaml.Unmarshal(r.Data, out)
if err != nil {
return out, true, err
}
err = lint(out)
return out, true, err
}
// match returns true if the resource matches the kind and type.
func match(r *manifest.RawResource) bool {
return r.Kind == Kind && r.Type == Type
}
func lint(pipeline *Pipeline) error {
// ensure pipeline steps are not unique.
names := map[string]struct{}{}
for _, step := range pipeline.Steps {
if step.Name == "" {
return errors.New("Linter: invalid or missing step name")
}
if len(step.Name) > 100 {
return errors.New("Linter: step name cannot exceed 100 characters")
}
if _, ok := names[step.Name]; ok {
return errors.New("Linter: duplicate step name")
}
names[step.Name] = struct{}{}
}
return nil
}

View File

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

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

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

View File

@@ -0,0 +1,71 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.
package resource
import (
"testing"
"github.com/drone/runner-go/manifest"
"github.com/google/go-cmp/cmp"
)
func TestGetStep(t *testing.T) {
step1 := &Step{Name: "build"}
step2 := &Step{Name: "test"}
pipeline := &Pipeline{
Steps: []*Step{step1, step2},
}
if pipeline.GetStep("build") != step1 {
t.Errorf("Expected named step")
}
if pipeline.GetStep("deploy") != nil {
t.Errorf("Expected nil step")
}
}
func TestGetters(t *testing.T) {
platform := manifest.Platform{
OS: "linux",
Arch: "amd64",
}
trigger := manifest.Conditions{
Branch: manifest.Condition{
Include: []string{"master"},
},
}
pipeline := &Pipeline{
Version: "1.0.0",
Kind: "pipeline",
Type: "docker",
Name: "default",
Deps: []string{"before"},
Platform: platform,
Trigger: trigger,
}
if got, want := pipeline.GetVersion(), pipeline.Version; got != want {
t.Errorf("Want Version %s, got %s", want, got)
}
if got, want := pipeline.GetKind(), pipeline.Kind; got != want {
t.Errorf("Want Kind %s, got %s", want, got)
}
if got, want := pipeline.GetType(), pipeline.Type; got != want {
t.Errorf("Want Type %s, got %s", want, got)
}
if got, want := pipeline.GetName(), pipeline.Name; got != want {
t.Errorf("Want Name %s, got %s", want, got)
}
if diff := cmp.Diff(pipeline.GetDependsOn(), pipeline.Deps); diff != "" {
t.Errorf("Unexpected Deps")
t.Log(diff)
}
if diff := cmp.Diff(pipeline.GetTrigger(), pipeline.Trigger); diff != "" {
t.Errorf("Unexpected Trigger")
t.Log(diff)
}
if got, want := pipeline.GetPlatform(), pipeline.Platform; got != want {
t.Errorf("Want Platform %s, got %s", want, got)
}
}

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

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

View File

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

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

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

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

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