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

6
internal/internal.go Normal file
View File

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

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

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

View File

@@ -0,0 +1,125 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.
package match
import (
"testing"
"github.com/drone/drone-go/drone"
)
func TestFunc(t *testing.T) {
tests := []struct {
repo string
event string
trusted bool
match bool
matcher func(*drone.Repo, *drone.Build) bool
}{
//
// Expect match true
//
// repository, event and trusted flag matching
{
repo: "octocat/hello-world",
event: "push",
trusted: true,
match: true,
matcher: Func([]string{"spaceghost/*", "octocat/*"}, []string{"push"}, true),
},
// repoisitory matching
{
repo: "octocat/hello-world",
event: "pull_request",
trusted: false,
match: true,
matcher: Func([]string{"spaceghost/*", "octocat/*"}, []string{}, false),
},
// event matching
{
repo: "octocat/hello-world",
event: "pull_request",
trusted: false,
match: true,
matcher: Func([]string{}, []string{"pull_request"}, false),
},
// trusted flag matching
{
repo: "octocat/hello-world",
event: "pull_request",
trusted: true,
match: true,
matcher: Func([]string{}, []string{}, true),
},
//
// Expect match false
//
// repository matching
{
repo: "spaceghost/hello-world",
event: "pull_request",
trusted: false,
match: false,
matcher: Func([]string{"octocat/*"}, []string{}, false),
},
// event matching
{
repo: "octocat/hello-world",
event: "pull_request",
trusted: false,
match: false,
matcher: Func([]string{}, []string{"push"}, false),
},
// trusted flag matching
{
repo: "octocat/hello-world",
event: "pull_request",
trusted: false,
match: false,
matcher: Func([]string{}, []string{}, true),
},
// does not match repository
{
repo: "foo/hello-world",
event: "push",
trusted: true,
match: false,
matcher: Func([]string{"spaceghost/*", "octocat/*"}, []string{"push"}, true),
},
// does not match event
{
repo: "octocat/hello-world",
event: "pull_request",
trusted: true,
match: false,
matcher: Func([]string{"spaceghost/*", "octocat/*"}, []string{"push"}, true),
},
// does not match trusted flag
{
repo: "octocat/hello-world",
event: "push",
trusted: false,
match: false,
matcher: Func([]string{"spaceghost/*", "octocat/*"}, []string{"push"}, true),
},
}
for i, test := range tests {
repo := &drone.Repo{
Slug: test.repo,
Trusted: test.trusted,
}
build := &drone.Build{
Event: test.event,
}
match := test.matcher(repo, build)
if match != test.match {
t.Errorf("Expect match %v at index %d", test.match, i)
}
}
}

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

@@ -0,0 +1,8 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.
package mock
//go:generate mockgen -package=mock -destination=mock_engine_gen.go github.com/drone-runners/drone-runner-docker/engine Engine
//go:generate mockgen -package=mock -destination=mock_execer_gen.go github.com/drone-runners/drone-runner-docker/runtime Execer

View File

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

View File

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