Merge pull request #3 from swisstxt/feature/metadata

Switch metadata fetching to API
diff --git a/cloudstack.go b/cloudstack.go
index 736d4b9..e331185 100644
--- a/cloudstack.go
+++ b/cloudstack.go
@@ -22,9 +22,7 @@
 	"fmt"
 	"io"
 	"os"
-	"path/filepath"
 
-	"github.com/kardianos/osext"
 	"github.com/xanzy/go-cloudstack/cloudstack"
 	"gopkg.in/gcfg.v1"
 	"k8s.io/apimachinery/pkg/types"
@@ -50,7 +48,6 @@
 // CSCloud is an implementation of Interface for CloudStack.
 type CSCloud struct {
 	client    *cloudstack.CloudStackClient
-	metadata  *metadata
 	projectID string // If non-"", all resources will be created within this project
 	zone      string
 }
@@ -87,34 +84,12 @@
 		zone:      cfg.Global.Zone,
 	}
 
-	exe, err := osext.Executable()
-	if err != nil {
-		return nil, fmt.Errorf("cloud not find the service executable: %v", err)
-	}
-
-	// When running the kubelet service it's fine to not specify a config file (or only a
-	// partial config file) as all needed info can be retrieved anonymously using metadata.
-	if filepath.Base(exe) == "kubelet" || filepath.Base(exe) == "kubelet.exe" {
-		// In CloudStack your metadata is always served by the DHCP server.
-		dhcpServer, err := findDHCPServer()
-		if err == nil {
-			klog.V(4).Infof("Found metadata server: %v", dhcpServer)
-			cs.metadata = &metadata{dhcpServer: dhcpServer, zone: cs.zone}
-		} else {
-			klog.Errorf("Error searching metadata server: %v", err)
-		}
-	}
-
 	if cfg.Global.APIURL != "" && cfg.Global.APIKey != "" && cfg.Global.SecretKey != "" {
 		cs.client = cloudstack.NewAsyncClient(cfg.Global.APIURL, cfg.Global.APIKey, cfg.Global.SecretKey, !cfg.Global.SSLNoVerify)
 	}
 
 	if cs.client == nil {
-		if cs.metadata != nil {
-			klog.V(2).Infof("No API URL, key and secret are provided, so only using metadata!")
-		} else {
-			return nil, errors.New("no cloud provider config given")
-		}
+		return nil, errors.New("no cloud provider config given")
 	}
 
 	return cs, nil
@@ -135,10 +110,6 @@
 
 // Instances returns an implementation of Instances for CloudStack.
 func (cs *CSCloud) Instances() (cloudprovider.Instances, bool) {
-	if cs.metadata != nil {
-		return cs.metadata, true
-	}
-
 	if cs.client == nil {
 		return nil, false
 	}
@@ -148,10 +119,6 @@
 
 // Zones returns an implementation of Zones for CloudStack.
 func (cs *CSCloud) Zones() (cloudprovider.Zones, bool) {
-	if cs.metadata != nil {
-		return cs.metadata, true
-	}
-
 	if cs.client == nil {
 		return nil, false
 	}
@@ -165,6 +132,7 @@
 		return nil, false
 	}
 
+	klog.Warning("This cloud provider doesn't support clusters")
 	return nil, false
 }
 
@@ -174,6 +142,7 @@
 		return nil, false
 	}
 
+	klog.Warning("This cloud provider doesn't support routes")
 	return nil, false
 }
 
diff --git a/go.mod b/go.mod
index 69568a5..0813639 100644
--- a/go.mod
+++ b/go.mod
@@ -15,8 +15,8 @@
 	github.com/coreos/go-semver v0.2.0 // indirect
 	github.com/coreos/go-systemd v0.0.0-20181031085051-9002847aa142 // indirect
 	github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
-	github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c
-	github.com/d2g/dhcp4client v1.0.0
+	github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c // indirect
+	github.com/d2g/dhcp4client v1.0.0 // indirect
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
 	github.com/dnaeon/go-vcr v1.0.1 // indirect
 	github.com/docker/distribution v0.0.0-20181024170156-93e082742a00 // indirect
@@ -43,7 +43,7 @@
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/jonboulle/clockwork v0.1.0 // indirect
 	github.com/json-iterator/go v1.1.5 // indirect
-	github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1
+	github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect
 	github.com/marstr/guid v1.1.0 // indirect
 	github.com/mitchellh/mapstructure v1.1.2 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
diff --git a/metadata.go b/metadata.go
deleted file mode 100644
index 5854a4c..0000000
--- a/metadata.go
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
-Copyright 2016 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package cloudstack
-
-import (
-	"context"
-	"errors"
-	"fmt"
-	"io/ioutil"
-	"net"
-	"net/http"
-	"regexp"
-
-	"github.com/d2g/dhcp4"
-	"k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/types"
-	cloudprovider "k8s.io/cloud-provider"
-	"k8s.io/klog"
-)
-
-type metadata struct {
-	dhcpServer string
-	zone       string
-}
-
-type metadataType string
-
-const (
-	metadataTypeHostname     metadataType = "local-hostname"
-	metadataTypeExternalIP   metadataType = "public-ipv4"
-	metadataTypeInternalIP   metadataType = "local-ipv4"
-	metadataTypeInstanceID   metadataType = "instance-id"
-	metadataTypeInstanceType metadataType = "service-offering"
-	metadataTypeZone         metadataType = "availability-zone"
-)
-
-var labelInvalidCharsRegex *regexp.Regexp = regexp.MustCompile(`([^A-Za-z0-9][^-A-Za-z0-9_.]*)?[^A-Za-z0-9]`)
-
-// NodeAddresses returns the addresses of the specified instance.
-func (m *metadata) NodeAddresses(ctx context.Context, name types.NodeName) ([]v1.NodeAddress, error) {
-	externalIP, err := m.get(metadataTypeExternalIP)
-	if err != nil {
-		return nil, fmt.Errorf("could not get external IP: %v", err)
-	}
-
-	internalIP, err := m.get(metadataTypeInternalIP)
-	if err != nil {
-		return nil, fmt.Errorf("could not get internal IP: %v", err)
-	}
-
-	addresses := []v1.NodeAddress{
-		{Type: v1.NodeExternalIP, Address: externalIP},
-		{Type: v1.NodeInternalIP, Address: internalIP},
-	}
-
-	hostname, err := m.get(metadataTypeHostname)
-	if err != nil {
-		return nil, fmt.Errorf("could not get hostname: %v", err)
-	}
-	if hostname != "" {
-		addresses = append(addresses, v1.NodeAddress{Type: v1.NodeHostName, Address: hostname})
-	}
-
-	return addresses, nil
-}
-
-// NodeAddressesByProviderID returns the addresses of the specified instance.
-func (m *metadata) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]v1.NodeAddress, error) {
-	return nil, errors.New("NodeAddressesByProviderID not implemented")
-}
-
-// InstanceID returns the cloud provider ID of the specified instance.
-func (m *metadata) InstanceID(ctx context.Context, name types.NodeName) (string, error) {
-	instanceID, err := m.get(metadataTypeInstanceID)
-	if err != nil {
-		return "", fmt.Errorf("could not get instance ID: %v", err)
-	}
-
-	zone, err := m.get(metadataTypeZone)
-	if err != nil {
-		return "", fmt.Errorf("could not get zone: %v", err)
-	}
-
-	return "/" + zone + "/" + instanceID, nil
-}
-
-// InstanceType returns the type of the specified instance.
-func (m *metadata) InstanceType(ctx context.Context, name types.NodeName) (string, error) {
-	instanceTypeCS, err := m.get(metadataTypeInstanceType)
-
-	// Replace everything that doesn't match the metadata.labels regex
-	instanceType := labelInvalidCharsRegex.ReplaceAllString(instanceTypeCS, ``)
-
-	if err != nil {
-		return "", fmt.Errorf("could not get instance type: %v", err)
-	}
-
-	return instanceType, nil
-}
-
-// InstanceTypeByProviderID returns the type of the specified instance.
-func (m *metadata) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) {
-	return "", errors.New("InstanceTypeByProviderID not implemented")
-}
-
-// AddSSHKeyToAllInstances is currently not implemented.
-func (m *metadata) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error {
-	return cloudprovider.NotImplemented
-}
-
-// CurrentNodeName returns the name of the node we are currently running on.
-func (m *metadata) CurrentNodeName(ctx context.Context, hostname string) (types.NodeName, error) {
-	return types.NodeName(hostname), nil
-}
-
-// InstanceExistsByProviderID returns if the instance still exists.
-func (m *metadata) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) {
-	return false, errors.New("InstanceExistsByProviderID not implemented")
-}
-
-// InstanceShutdownByProviderID returns if the instance is shutdown.
-func (m *metadata) InstanceShutdownByProviderID(ctx context.Context, providerID string) (bool, error) {
-	return false, cloudprovider.NotImplemented
-}
-
-// GetZone returns the Zone containing the region that the program is running in.
-func (m *metadata) GetZone(ctx context.Context) (cloudprovider.Zone, error) {
-	zone := cloudprovider.Zone{}
-
-	if m.zone == "" {
-		zoneName, err := m.get(metadataTypeZone)
-		if err != nil {
-			return zone, fmt.Errorf("could not get zone: %v", err)
-		}
-
-		m.zone = zoneName
-	}
-
-	klog.V(2).Infof("Current zone is %v", zone)
-	zone.FailureDomain = m.zone
-	zone.Region = m.zone
-
-	return zone, nil
-}
-
-// GetZoneByProviderID returns the Zone, found by using the provider ID.
-func (m *metadata) GetZoneByProviderID(ctx context.Context, providerID string) (cloudprovider.Zone, error) {
-	return cloudprovider.Zone{}, errors.New("GetZoneByProviderID not implemented")
-}
-
-// GetZoneByNodeName returns the Zone, found by using the node name.
-func (m *metadata) GetZoneByNodeName(ctx context.Context, nodeName types.NodeName) (cloudprovider.Zone, error) {
-	return cloudprovider.Zone{}, errors.New("GetZoneByNodeName not implemented")
-}
-
-func (m *metadata) get(mdType metadataType) (string, error) {
-	url := fmt.Sprintf("http://%s/latest/meta-data/%s", m.dhcpServer, mdType)
-
-	resp, err := http.Get(url)
-	if err != nil {
-		return "", fmt.Errorf("error reading metadata: %v", err)
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		return "", fmt.Errorf("unexpected HTTP status: %d", resp.StatusCode)
-	}
-
-	data, err := ioutil.ReadAll(resp.Body)
-	if err != nil {
-		return "", fmt.Errorf("error reading response body: %d", resp.StatusCode)
-	}
-
-	return string(data), nil
-}
-
-func findDHCPServer() (string, error) {
-	nics, err := net.Interfaces()
-	if err != nil {
-		return "", fmt.Errorf("could not get interfaces: %v", err)
-	}
-
-	for _, nic := range nics {
-		if nic.Flags&net.FlagUp == 1 && nic.Flags&net.FlagLoopback == 0 && nic.Flags&net.FlagPointToPoint == 0 {
-			addrs, err := nic.Addrs()
-			if err != nil {
-				return "", fmt.Errorf("error reading IP addresses from interface %v: %v", nic.Name, err)
-			}
-
-			if addrs != nil {
-				client, err := newDHCPClient(&nic)
-				if err != nil {
-					return "", fmt.Errorf("error creating new DHCP client: %v", err)
-				}
-
-				discoverPacket, err := client.SendDiscoverPacket()
-				if err != nil {
-					return "", fmt.Errorf("error sending DHCP discover package: %v", err)
-				}
-
-				offerPacket, err := client.GetOffer(&discoverPacket)
-				if err != nil {
-					return "", fmt.Errorf("error receiving DHCP offer package: %v", err)
-				}
-
-				offerPacketOptions := offerPacket.ParseOptions()
-
-				if ipaddr, ok := offerPacketOptions[dhcp4.OptionServerIdentifier]; ok {
-					return net.IP(ipaddr).String(), nil
-				}
-			}
-		}
-	}
-
-	return "", errors.New("no server found")
-}
diff --git a/metadata_linux.go b/metadata_linux.go
deleted file mode 100644
index 02603bf..0000000
--- a/metadata_linux.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// +build linux
-
-/*
-Copyright 2016 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package cloudstack
-
-import (
-	"net"
-	"time"
-
-	"github.com/d2g/dhcp4client"
-)
-
-func newDHCPClient(nic *net.Interface) (*dhcp4client.Client, error) {
-	pktsock, err := dhcp4client.NewPacketSock(nic.Index)
-	if err != nil {
-		return nil, err
-	}
-
-	return dhcp4client.New(
-		dhcp4client.HardwareAddr(nic.HardwareAddr),
-		dhcp4client.Timeout(2*time.Second),
-		dhcp4client.Broadcast(false),
-		dhcp4client.Connection(pktsock),
-	)
-}
diff --git a/metadata_other.go b/metadata_other.go
deleted file mode 100644
index 74a7fe2..0000000
--- a/metadata_other.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// +build !linux
-
-/*
-Copyright 2016 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package cloudstack
-
-import (
-	"net"
-	"time"
-
-	"github.com/d2g/dhcp4client"
-)
-
-func newDHCPClient(nic *net.Interface) (*dhcp4client.Client, error) {
-	inetsock, err := dhcp4client.NewInetSock()
-	if err != nil {
-		return nil, err
-	}
-
-	return dhcp4client.New(
-		dhcp4client.HardwareAddr(nic.HardwareAddr),
-		dhcp4client.Timeout(2*time.Second),
-		dhcp4client.Broadcast(false),
-		dhcp4client.Connection(inetsock),
-	)
-}