initial commit [ci skip]
This commit is contained in:
6
internal/internal.go
Normal file
6
internal/internal.go
Normal 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
51
internal/match/match.go
Normal 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
|
||||
}
|
||||
125
internal/match/match_test.go
Normal file
125
internal/match/match_test.go
Normal 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
8
internal/mock/mock.go
Normal 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
|
||||
177
internal/platform/platform.go
Normal file
177
internal/platform/platform.go
Normal 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,
|
||||
},
|
||||
)),
|
||||
)
|
||||
}
|
||||
5
internal/platform/platform_test.go
Normal file
5
internal/platform/platform_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 platform
|
||||
Reference in New Issue
Block a user