Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add driver for Brightbox Cloud #118

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions cmd/drone-autoscaler/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/drone/autoscaler"
"github.com/drone/autoscaler/config"
"github.com/drone/autoscaler/drivers/amazon"
"github.com/drone/autoscaler/drivers/brightbox"
"github.com/drone/autoscaler/drivers/digitalocean"
"github.com/drone/autoscaler/drivers/google"
"github.com/drone/autoscaler/drivers/hetznercloud"
Expand Down Expand Up @@ -300,6 +301,17 @@ func setupProvider(c config.Config) (autoscaler.Provider, error) {
packet.WithHostname(c.Packet.Hostname),
packet.WithTags(c.Packet.Tags...),
), nil
case c.Brightbox.ClientID != "":
return brightbox.New(
brightbox.WithApiURL(c.Brightbox.ApiURL),
brightbox.WithClientID(c.Brightbox.ClientID),
brightbox.WithClientSecret(c.Brightbox.ClientSecret),
brightbox.WithImage(c.Brightbox.Image),
brightbox.WithServerType(c.Brightbox.ServerType),
brightbox.WithServerGroups(c.Brightbox.ServerGroups),
brightbox.WithUserData(c.Brightbox.UserData),
brightbox.WithUserDataFile(c.Brightbox.UserDataFile),
), nil
case os.Getenv("AWS_ACCESS_KEY_ID") != "" || os.Getenv("AWS_IAM") != "":
return amazon.New(
amazon.WithDeviceName(c.Amazon.DeviceName),
Expand Down
11 changes: 11 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,17 @@ type (
MarketType string `envconfig:"DRONE_AMAZON_MARKET_TYPE"`
}

Brightbox struct {
ApiURL string `envconfig:"DRONE_BRIGHTBOX_API_URL"`
ClientID string `envconfig:"DRONE_BRIGHTBOX_CLIENT_ID"`
ClientSecret string `envconfig:"DRONE_BRIGHTBOX_CLIENT_SECRET"`
Image string `envconfig:"DRONE_BRIGHTBOX_IMAGE"`
ServerType string `envconfig:"DRONE_BRIGHTBOX_SERVER_TYPE"`
ServerGroups []string `envconfig:"DRONE_BRIGHTBOX_SERVER_GROUPS"`
UserData string `envconfig:"DRONE_BRIGHTBOX_USERDATA"`
UserDataFile string `envconfig:"DRONE_BRIGHTBOX_USERDATA_FILE"`
}

DigitalOcean struct {
Token string
Image string
Expand Down
119 changes: 119 additions & 0 deletions drivers/brightbox/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package brightbox

import (
"bytes"
"context"
"encoding/base64"
"errors"
"time"

"github.com/drone/autoscaler"
"github.com/drone/autoscaler/logger"

"github.com/brightbox/gobrightbox"
)

func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceCreateOpts) (*autoscaler.Instance, error) {
p.init.Do(func() {
p.setup(ctx)
})

buf := new(bytes.Buffer)
err := p.userdata.Execute(buf, &opts)
if err != nil {
return nil, err
}

var userdata = new(string)
*userdata = base64.StdEncoding.EncodeToString(buf.Bytes())

in := &gobrightbox.ServerOptions{
Image: p.image,
Name: &opts.Name,
ServerType: p.serverType,
UserData: userdata,
ServerGroups: p.serverGroups,
}

logger := logger.FromContext(ctx).
WithField("name", opts.Name).
WithField("type", p.serverType).
WithField("image", p.image).
WithField("groups", p.serverGroups)

logger.Debugln("instance create")

server, err := p.client.CreateServer(in)
if err != nil {
logger.WithError(err).
Errorln("instance create failed")
return nil, err
}

// wait for the server to become active
interval := time.Second * 10
poller:
for {
select {
case <-ctx.Done():
logger.WithField("ID", server.ID).
Debugln("instance create deadline exceeded")

return nil, ctx.Err()
case <-time.After(interval):
server, err := p.client.Server(server.ID)
if err != nil {
logger.WithError(err).
Errorln("cannot get instance details")
continue
}

if server.Status == "active" {
break poller
} else if server.Status == "failed" {
err = errors.New("brightbox: new server entered 'failed' state")
logger.WithError(err).
Errorln("instance create failed")
return nil, err
}
}
}

logger.Infoln("instance create success")

cip := &gobrightbox.CloudIPOptions{
Name: &opts.Name,
}

logger.Debugln("map Cloud IP")

cloudip, err := p.client.CreateCloudIP(cip)
if err != nil {
logger.WithError(err).
Errorln("failed to provision CloudIP")
return nil, err
}

err = p.client.MapCloudIPtoServer(cloudip.ID, server.ID)
if err != nil {
logger.WithError(err).
Errorln("failed to map CloudIP")
return nil, err
}

logger.Infoln("map Cloud IP success")

return &autoscaler.Instance{
Provider: autoscaler.ProviderBrightbox,
Region: p.region,
ID: server.ID,
Name: server.Name,
Address: cloudip.PublicIPv4,
Image: in.Image,
Size: in.ServerType,
}, nil
}
5 changes: 5 additions & 0 deletions drivers/brightbox/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package brightbox
87 changes: 87 additions & 0 deletions drivers/brightbox/destroy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package brightbox

import (
"context"
"time"

"github.com/drone/autoscaler"
"github.com/drone/autoscaler/logger"
)

func (p *provider) Destroy(ctx context.Context, instance *autoscaler.Instance) error {
logger := logger.FromContext(ctx).
WithField("id", instance.ID).
WithField("name", instance.Name).
WithField("ip", instance.Address)

logger.Debugln("terminate instance")

server, err := p.client.Server(instance.ID)
if err != nil {
logger.WithError(err).
Errorln("cannot retrive instance details")
return err
}

if len(server.CloudIPs) > 0 {

logger.Debugln("unmap Cloud IP")

cipID := server.CloudIPs[0].ID

err := p.client.UnMapCloudIP(cipID)
if err != nil {
logger.WithError(err).
Errorln("failed to unmap Cloud IP")
return err
}

// wait for CIP to become unmapped
interval := time.Second * 2
poller:
for {
select {
case <-ctx.Done():
logger.WithField("ID", server.ID).
Debugln("unmap Cloud IP deadline exceeded")

return err
case <-time.After(interval):
cip, err := p.client.CloudIP(cipID)
if err != nil {
logger.WithError(err).
Errorln("cannot retrive Cloud IP details")
continue
}

if cip.Status == "unmapped" {
break poller
}
}
}

err = p.client.DestroyCloudIP(cipID)
if err != nil {
logger.WithError(err).
Errorln("failed to destroy Cloud IP")
return err
}
}

logger.Infoln("unmap Cloud IP success")

err = p.client.DestroyServer(instance.ID)
if err != nil {
logger.WithError(err).
Errorln("terminate instance failed")
return err
}

logger.Infoln("terminate instance success")

return nil
}
5 changes: 5 additions & 0 deletions drivers/brightbox/destroy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package brightbox
88 changes: 88 additions & 0 deletions drivers/brightbox/option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package brightbox

import (
"io/ioutil"

"github.com/drone/autoscaler/drivers/internal/userdata"
)

// Option configures a Brightbox provider option.
type Option func(*provider) error

// WithApiURL returns an option to set the API endpoint URL
func WithApiURL(apiURL string) Option {
return func(p *provider) error {
p.apiURL = apiURL
return nil
}
}

// WithClientID returns an option to set the API client ID
func WithClientID(clientID string) Option {
return func(p *provider) error {
p.clientID = clientID
return nil
}
}

// WithClientSecret returns an option to set the API client secret
func WithClientSecret(clientSecret string) Option {
return func(p *provider) error {
p.clientSecret = clientSecret
return nil
}
}

// WithImage returns an option to set the image.
func WithImage(image string) Option {
return func(p *provider) error {
p.image = image
return nil
}
}

// WithServerType returns an option to set the server type
func WithServerType(serverType string) Option {
return func(p *provider) error {
p.serverType = serverType
return nil
}
}

// WithServerGroups returns an option to set the server groups
func WithServerGroups(serverGroups []string) Option {
return func(p *provider) error {
p.serverGroups = serverGroups
return nil
}
}

// WithUserData returns an option to set the cloud-init
// template from text.
func WithUserData(text string) Option {
return func(p *provider) error {
if text != "" {
p.userdata = userdata.Parse(text)
}
return nil
}
}

// WithUserDataFile returns an option to set the cloud-init
// template from file.
func WithUserDataFile(filepath string) Option {
return func(p *provider) error {
if filepath != "" {
b, err := ioutil.ReadFile(filepath)
if err != nil {
return err
}
p.userdata = userdata.Parse(string(b))
}
return nil
}
}
37 changes: 37 additions & 0 deletions drivers/brightbox/option_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package brightbox

import "testing"

func TestOptions(t *testing.T) {
p := New(
WithApiURL("https://api.gb1.brightbox.com"),
WithClientID("cli-xxxxx"),
WithClientSecret("supersecret"),
WithImage("img-xxxxx"),
WithServerType("typ-xxxxx"),
WithServerGroups([]string{"grp-aaaaa"}),
).(*provider)

if got, want := p.apiURL, "https://api.gb1.brightbox.com"; got != want {
t.Errorf("Want API URL %q, got %q", want, got)
}
if got, want := p.clientID, "cli-xxxxx"; got != want {
t.Errorf("Want client ID %q, got %q", want, got)
}
if got, want := p.clientSecret, "supersecret"; got != want {
t.Errorf("Want client secret %q, got %q", want, got)
}
if got, want := p.image, "img-xxxxx"; got != want {
t.Errorf("Want image %q, got %q", want, got)
}
if got, want := p.serverType, "typ-xxxxx"; got != want {
t.Errorf("Want server type %q, got %q", want, got)
}
if got, want := p.serverGroups[0], "grp-aaaaa"; got != want {
t.Errorf("Want server groups %q, got %q", want, got)
}
}
Loading