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

feat: add hetzner cloud provider #167

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function.
* Amazon AWS [Config options](https://github.com/hashicorp/go-discover/blob/8b3ddf4/provider/aws/aws_discover.go#L19-L34)
* DigitalOcean [Config options](https://github.com/hashicorp/go-discover/blob/8b3ddf4/provider/digitalocean/digitalocean_discover.go#L22-L30)
* Google Cloud [Config options](https://github.com/hashicorp/go-discover/blob/8b3ddf4/provider/gce/gce_discover.go#L23-L43)
* Hetzner Cloud [Config options](https://github.com/hashicorp/go-discover/blob/master/provider/hcloud/hcloud_discover.go#L17-L24)
* Linode [Config options](https://github.com/hashicorp/go-discover/blob/master/provider/linode/linode_discover.go#L30-L41)
* mDNS [Config options](https://github.com/hashicorp/go-discover/blob/master/provider/mdns/mdns_provider.go#L19-L31)
* Microsoft Azure [Config options](https://github.com/hashicorp/go-discover/blob/8b3ddf4/provider/azure/azure_discover.go#L24-L62)
Expand Down Expand Up @@ -61,6 +62,9 @@ provider=digitalocean region=... tag_name=... api_token=...
# Google Cloud
provider=gce project_name=... zone_pattern=eu-west-* tag_value=consul credentials_file=...

# Hetzner Cloud
provider=hcloud location=... label_selector=... address_type=... api_token=...

# Linode
provider=linode tag_name=... region=us-east address_type=private_v4 api_token=...

Expand Down Expand Up @@ -166,7 +170,7 @@ sub-package.

## Testing

**Note: Due to the `go.sum` checksum errors referenced in [#68](https://github.com/hashicorp/go-discover/issues/68),
**Note: Due to the `go.sum` checksum errors referenced in [#68](https://github.com/hashicorp/go-discover/issues/68),
you will need Go 1.11.4+ to build/test go-discover.**

Configuration tests can be run with Go:
Expand Down
2 changes: 2 additions & 0 deletions discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/hashicorp/go-discover/provider/azure"
"github.com/hashicorp/go-discover/provider/digitalocean"
"github.com/hashicorp/go-discover/provider/gce"
"github.com/hashicorp/go-discover/provider/hcloud"
"github.com/hashicorp/go-discover/provider/linode"
"github.com/hashicorp/go-discover/provider/mdns"
"github.com/hashicorp/go-discover/provider/os"
Expand Down Expand Up @@ -50,6 +51,7 @@ var Providers = map[string]Provider{
"azure": &azure.Provider{},
"digitalocean": &digitalocean.Provider{},
"gce": &gce.Provider{},
"hcloud": &hcloud.Provider{},
"linode": &linode.Provider{},
"mdns": &mdns.Provider{},
"os": &os.Provider{},
Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ require (
github.com/hashicorp/go-multierror v1.0.0
github.com/hashicorp/mdns v1.0.1
github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443
github.com/hetznercloud/hcloud-go v1.37.0
github.com/imdario/mergo v0.3.6 // indirect
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da // indirect
github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62
github.com/linode/linodego v0.7.1
github.com/mitchellh/go-homedir v1.1.0
github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2
github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c
github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 // indirect
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d
github.com/stretchr/testify v1.7.0
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.480
Expand Down
195 changes: 191 additions & 4 deletions go.sum

Large diffs are not rendered by default.

129 changes: 129 additions & 0 deletions provider/hcloud/hcloud_discover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Package hcloud provides node discovery for Hetzner Cloud.
package hcloud

import (
"context"
"fmt"
"io/ioutil"
"log"
"os"

"github.com/hetznercloud/hcloud-go/hcloud"
)

type Provider struct{}

func (p *Provider) Help() string {
return `Hetzner Cloud:

provider: "hcloud"
location: The Hetzner Cloud datacenter location to filter by (eg. "fsn1")
label_selector: The label selector to filter by
address_type: "private_v4", "public_v4" or "public_v6", defaults to "private_v4". In the case of private networks, the first one will be used
api_token: The Hetzner Cloud API token to use, can also be provided by environment variable: HCLOUD_TOKEN
`
}

// serverIP returns the IP address of the specified type for the hcloud server.
func serverIP(s *hcloud.Server, addrType string, l *log.Logger) string {
switch addrType {
case "public_v4":
if !s.PublicNet.IPv4.Blocked {
l.Printf("[INFO] discover-hcloud: instance %s (%d) has public IP %s", s.Name, s.ID, s.PublicNet.IPv4.IP.String())
return s.PublicNet.IPv4.IP.String()
} else if len(s.PublicNet.FloatingIPs) != 0 {
l.Printf("[INFO] discover-hcloud: public IPv4 for instance %s (%d) is blocked, checking associated floating IPs", s.Name, s.ID)
for _, floatingIP := range s.PublicNet.FloatingIPs {
if floatingIP.Type == hcloud.FloatingIPTypeIPv4 && !floatingIP.Blocked {
l.Printf("[INFO] discover-hcloud: instance %s (%d) has floating IP %s", s.Name, s.ID, floatingIP.IP.String())
return floatingIP.IP.String()
}
}
}
case "public_v6":
if !s.PublicNet.IPv6.Blocked {
l.Printf("[INFO] discover-hcloud: instance %s (%d) has public IP %s", s.Name, s.ID, s.PublicNet.IPv6.IP.String())
return s.PublicNet.IPv6.IP.String()
} else if len(s.PublicNet.FloatingIPs) != 0 {
l.Printf("[INFO] discover-hcloud: public IPv6 for instance %s (%d) is blocked, checking associated floating IPs", s.Name, s.ID)
for _, floatingIP := range s.PublicNet.FloatingIPs {
if floatingIP.Type == hcloud.FloatingIPTypeIPv6 && !floatingIP.Blocked {
l.Printf("[INFO] discover-hcloud: instance %s (%d) has floating IP %s", s.Name, s.ID, floatingIP.IP.String())
return floatingIP.IP.String()
}
}
}
case "private_v4":
if len(s.PrivateNet) == 0 {
l.Printf("[INFO] discover-hcloud: instance %s (%d) has no private IP", s.Name, s.ID)
} else {
l.Printf("[INFO] discover-hcloud: instance %s (%d) has private IP %s", s.Name, s.ID, s.PrivateNet[0].IP.String())
return s.PrivateNet[0].IP.String()
}
default:
}

l.Printf("[DEBUG] discover-hcloud: instance %s (%d) has no valid associated IP address", s.Name, s.ID)
return ""
}

func (p *Provider) Addrs(args map[string]string, l *log.Logger) ([]string, error) {
if args["provider"] != "hcloud" {
return nil, fmt.Errorf("discover-hcloud: invalid provider %s", args["provider"])
}

if l == nil {
l = log.New(ioutil.Discard, "", 0)
}

labelSelector := args["label_selector"]
addrType := args["address_type"]
location := args["location"]
apiToken := args["api_token"]

if apiToken == "" {
l.Printf("[INFO] no API token specified, checking environment variable HCLOUD_TOKEN")
apiToken = os.Getenv("HCLOUD_TOKEN")
if apiToken == "" {
return nil, fmt.Errorf("discover-hcloud: no api_token specified")
}
}

if addrType == "" {
l.Printf("[INFO] discover-hcloud: address type not provided, using 'private_v4'")
addrType = "private_v4"
}

if addrType != "private_v4" && addrType != "public_v4" && addrType != "public_v6" {
l.Printf("[INFO] discover-hcloud: address_type %s is invalid, falling back to 'private_v4'. valid values are: private_v4, public_v4, public_v6", addrType)
addrType = "private_v4"
}

l.Printf("[DEBUG] discover-hcloud: using address_type=%s label_selector=%s location=%s", addrType, labelSelector, location)

client := hcloud.NewClient(hcloud.WithToken(apiToken))
Thunderbottom marked this conversation as resolved.
Show resolved Hide resolved

options := hcloud.ServerListOpts{
ListOpts: hcloud.ListOpts{
LabelSelector: labelSelector,
},
Status: []hcloud.ServerStatus{hcloud.ServerStatusRunning},
}

servers, err := client.Server.AllWithOpts(context.Background(), options)
if err != nil {
return nil, fmt.Errorf("discover-hcloud: %s", err)
}

var addrs []string
for _, s := range servers {
if location == "" || location == s.Datacenter.Location.Name {
if serverIP := serverIP(s, addrType, l); serverIP != "" {
addrs = append(addrs, serverIP)
}
}
}

log.Printf("[DEBUG] discover-hcloud: found IP addresses: %v", addrs)
return addrs, nil
}
47 changes: 47 additions & 0 deletions provider/hcloud/hcloud_discover_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package hcloud_test
Thunderbottom marked this conversation as resolved.
Show resolved Hide resolved

import (
"log"
"os"
"testing"

discover "github.com/hashicorp/go-discover"
"github.com/hashicorp/go-discover/provider/hcloud"
)

var _ discover.Provider = (*hcloud.Provider)(nil)
var addrTests = map[string]struct {
addrType string
location string
outLen int
}{
"public ipv4 all locations": {"public_v4", "", 2},
"public ipv6 all locations": {"public_v6", "", 2},
"private ipv4 all locations": {"private_v4", "", 2},
"private ipv4 fsn1 datacenter": {"private_v4", "fsn1", 1},
"public ipv6 nbg1 datacenter": {"public_v6", "nbg1", 1},
"public ipv4 hel1 datacenter": {"public_v4", "hel1", 0},
}

func TestAddrs(t *testing.T) {
l := log.New(os.Stderr, "", log.LstdFlags)
for name, at := range addrTests {
t.Run(name, func(t *testing.T) {
args := discover.Config{
"provider": "hcloud",
"label_selector": "go-discover-test-tag",
"address_type": at.addrType,
}
p := &hcloud.Provider{}
addrs, err := p.Addrs(args, l)

if err != nil {
t.Fatal(err)
}

if len(addrs) != at.outLen {
t.Fatalf("expected: %d, got: %d", at.outLen, len(addrs))
}
})
}
}
19 changes: 19 additions & 0 deletions test/tf/hcloud/input.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
variable "prefix" {
default = "go-discover"
}

variable "hcloud_image" {
default = "ubuntu-20.04"
}

variable "hcloud_location_nbg" {
default = "nbg1"
}

variable "hcloud_location_fsn" {
default = "fsn1"
}

variable "hcloud_size" {
default = "cx11"
}
46 changes: 46 additions & 0 deletions test/tf/hcloud/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
provider "hcloud" {
}

resource "hcloud_server" "test-01" {
count = 2
image = var.hcloud_image
name = "${var.prefix}-01-${count.index + 1}"
server_type = var.hcloud_size
location = var.hcloud_location_nbg
labels = {
"${var.prefix}-test-tag" : ""
}
}

resource "hcloud_server" "test-02" {
image = var.hcloud_image
name = "${var.prefix}-02"
server_type = var.hcloud_size
location = var.hcloud_location_fsn
}

resource "hcloud_network" "internal" {
name = "${var.prefix}-internal"
ip_range = "10.0.0.0/8"
labels = {
"${var.prefix}-test-tag" : ""
}
}

resource "hcloud_network_subnet" "subnet" {
network_id = hcloud_network.internal.id
type = "cloud"
network_zone = "eu-central"
ip_range = "10.0.1.0/24"
}

resource "hcloud_server_network" "test-01" {
count = 2
server_id = hcloud_server.test-01[count.index].id
subnet_id = hcloud_network_subnet.subnet.id
}

resource "hcloud_server_network" "test-02" {
server_id = hcloud_server.test-02.id
subnet_id = hcloud_network_subnet.subnet.id
}
10 changes: 10 additions & 0 deletions test/tf/hcloud/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "1.36.0"
}
}

required_version = ">=0.12"
}