Skip to content

Commit

Permalink
Add Civo as a provisioner
Browse files Browse the repository at this point in the history
Closes: #6

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
  • Loading branch information
alexellis committed Nov 7, 2019
1 parent 43669a0 commit 80e3686
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 2 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Completed:
* [x] DigitalOcean support
* [x] Scaleway support
* [x] `inletsctl delete` command
* [x] Add Civo.com
* [x] Add poll interval `--poll 5s` for use with Civo that applies rate-limiting

Pending:

Expand Down
23 changes: 21 additions & 2 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (

func init() {
inletsCmd.AddCommand(createCmd)
createCmd.Flags().StringP("provider", "p", "digitalocean", "The cloud provider")
createCmd.Flags().StringP("provider", "p", "digitalocean", "The cloud provider - digitalocean, scaleway, or civo")
createCmd.Flags().StringP("region", "r", "lon1", "The region for your cloud provider")

createCmd.Flags().StringP("inlets-token", "t", "", "The inlets auth token for your exit node")
Expand All @@ -32,6 +32,8 @@ func init() {
createCmd.Flags().String("organisation-id", "", "Organisation ID (Scaleway)")

createCmd.Flags().StringP("remote-tcp", "c", "", `Remote host for inlets-pro to use for forwarding TCP connections`)

createCmd.Flags().DurationP("poll", "n", time.Second*2, "poll every N seconds")
}

// clientCmd represents the client sub command.
Expand Down Expand Up @@ -68,6 +70,12 @@ func runCreate(cmd *cobra.Command, _ []string) error {
}
}

var poll time.Duration
pollOverride, pollOverrideErr := cmd.Flags().GetDuration("poll")
if pollOverrideErr == nil {
poll = pollOverride
}

accessToken, err := getFileOrString(cmd.Flags(), "access-token-file", "access-token", true)
if err != nil {
return err
Expand Down Expand Up @@ -131,7 +139,7 @@ func runCreate(cmd *cobra.Command, _ []string) error {

max := 500
for i := 0; i < max; i++ {
time.Sleep(1 * time.Second)
time.Sleep(poll)

hostStatus, err := provisioner.Status(hostRes.ID)
if err != nil {
Expand Down Expand Up @@ -193,6 +201,8 @@ Command:
func getProvisioner(provider, accessToken, secretKey, organisationID, region string) (provision.Provisioner, error) {
if provider == "digitalocean" {
return provision.NewDigitalOceanProvisioner(accessToken)
} else if provider == "civo" {
return pkg.NewCivoProvisioner(accessToken)
} else if provider == "scaleway" {
return provision.NewScalewayProvisioner(accessToken, secretKey, organisationID, region)
}
Expand Down Expand Up @@ -223,6 +233,15 @@ func createHost(provider, name, region, userData string) (*provision.BasicHost,
UserData: userData,
Additional: map[string]string{},
}, nil
} else if provider == "civo" {
return &provision.BasicHost{
Name: name,
OS: "811a8dfb-8202-49ad-b1ef-1e6320b20497",
Plan: "g2.small",
Region: region,
UserData: userData,
Additional: map[string]string{},
}, nil
}

return nil, fmt.Errorf("no provisioner for provider: %s", provider)
Expand Down
151 changes: 151 additions & 0 deletions pkg/civo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package pkg

import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"

"strings"
"time"

"github.com/inlets/inlets-operator/pkg/provision"
)

// CivoProvisioner creates instances on civo.com
type CivoProvisioner struct {
APIKey string
}

// NewCivoProvisioner with an accessKey
func NewCivoProvisioner(accessKey string) (*CivoProvisioner, error) {

return &CivoProvisioner{
APIKey: accessKey,
}, nil
}

func (p *CivoProvisioner) Status(id string) (*provision.ProvisionedHost, error) {
host := &provision.ProvisionedHost{}

apiURL := fmt.Sprint("https://api.civo.com/v2/instances/", id)

req, err := http.NewRequest(http.MethodGet, apiURL, nil)
if err != nil {
return host, err
}
addAuth(req, p.APIKey)

req.Header.Add("Accept", "application/json")
instance := CreatedInstance{}

res, err := http.DefaultClient.Do(req)
if err != nil {
return host, err
}

var body []byte
if res.Body != nil {
defer res.Body.Close()
body, _ = ioutil.ReadAll(res.Body)
}

if res.StatusCode != http.StatusOK {
return host, fmt.Errorf("unexpected HTTP code: %d\n%q", res.StatusCode, string(body))
}

unmarshalErr := json.Unmarshal(body, &instance)
if unmarshalErr != nil {
return host, unmarshalErr
}

return &provision.ProvisionedHost{
ID: instance.ID,
IP: instance.PublicIP,
Status: strings.ToLower(instance.Status),
}, nil
}

func (p *CivoProvisioner) Delete(id string) error {
return nil
}

func (p *CivoProvisioner) Provision(host provision.BasicHost) (*provision.ProvisionedHost, error) {

log.Printf("Provisioning host with Civo\n")

if host.Region == "" {
host.Region = "lon1"
}

res, err := provisionCivoInstance(host, p.APIKey)

if err != nil {
return nil, err
}

return &provision.ProvisionedHost{
ID: res.ID,
}, nil
}

func provisionCivoInstance(host provision.BasicHost, key string) (CreatedInstance, error) {
instance := CreatedInstance{}

apiURL := "https://api.civo.com/v2/instances"

values := url.Values{}
values.Add("hostname", host.Name)
values.Add("size", host.Plan)
values.Add("public_ip", "true")
values.Add("template_id", host.OS)
values.Add("initial_user", "civo")
values.Add("script", host.UserData)
values.Add("script", "inlets")

req, err := http.NewRequest(http.MethodPost, apiURL, strings.NewReader(values.Encode()))
if err != nil {
return instance, err
}
addAuth(req, key)

req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

res, err := http.DefaultClient.Do(req)
if err != nil {
return instance, err
}

var body []byte
if res.Body != nil {
defer res.Body.Close()
body, _ = ioutil.ReadAll(res.Body)
}

if res.StatusCode != http.StatusOK {
return instance, fmt.Errorf("unexpected HTTP code: %d\n%q", res.StatusCode, string(body))
}

unmarshalErr := json.Unmarshal(body, &instance)
if unmarshalErr != nil {
return instance, unmarshalErr
}

fmt.Printf("Instance ID: %s\n", instance.ID)
return instance, nil
}

type CreatedInstance struct {
ID string `json:"id"`
CreatedAt time.Time `json:"created_at"`
PublicIP string `json:"public_ip"`
Status string `json:"status"`
}

func addAuth(r *http.Request, APIKey string) {
r.Header.Add("Authorization", fmt.Sprintf("bearer %s", APIKey))
r.Header.Add("User-Agent", "inlets")
}

0 comments on commit 80e3686

Please sign in to comment.