From 3dc23b96ac498fe31d053042f79187866b82b975 Mon Sep 17 00:00:00 2001 From: Luke Hodkinson Date: Wed, 21 Jun 2017 20:14:58 +1000 Subject: [PATCH] Add a metadata provider for Vultr Vultr uses a very similar approach to AWS, including using the same IP address for serving metadata. In fact, it seems as though if AWS appears first in the list of providers, that provider mistakenly believes to be running on AWS (hence the insertion of `NewVultr` in between GCP and AWS. I don't believe AWS servers will accidentally try to use the Vultr provider, as it seems that the `/v1/` endpoint doesn't exist on AWS. Signed-off-by: Luke Hodkinson --- pkg/metadata/main.go | 2 +- pkg/metadata/provider_vultr.go | 123 +++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 pkg/metadata/provider_vultr.go diff --git a/pkg/metadata/main.go b/pkg/metadata/main.go index 61b7ee603..d30588c42 100644 --- a/pkg/metadata/main.go +++ b/pkg/metadata/main.go @@ -47,7 +47,7 @@ var netProviders []Provider var cdromProviders []Provider func init() { - netProviders = []Provider{NewGCP(), NewAWS()} + netProviders = []Provider{NewGCP(), NewVultr(), NewAWS()} cdromProviders = []Provider{NewCDROM()} } diff --git a/pkg/metadata/provider_vultr.go b/pkg/metadata/provider_vultr.go new file mode 100644 index 000000000..4a761a63d --- /dev/null +++ b/pkg/metadata/provider_vultr.go @@ -0,0 +1,123 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "path" + "time" +) + +const ( + vultrMetaDataURL = "http://169.254.169.254/v1/" +) + +// ProviderVultr is the type implementing the Provider interface for Vultr +type ProviderVultr struct { +} + +// NewVultr returns a new ProviderVultr +func NewVultr() *ProviderVultr { + return &ProviderVultr{} +} + +func (p *ProviderVultr) String() string { + return "Vultr" +} + +// Probe checks if we are running on Vultr +func (p *ProviderVultr) Probe() bool { + // Getting the index should always work... + _, err := vultrGet(vultrMetaDataURL) + return (err == nil) +} + +// Extract gets both the Vultr specific and generic userdata +func (p *ProviderVultr) Extract() ([]byte, error) { + // Get host name. This must not fail + hostname, err := vultrGet(vultrMetaDataURL + "hostname") + if err != nil { + return nil, err + } + err = ioutil.WriteFile(path.Join(ConfigPath, Hostname), hostname, 0644) + if err != nil { + return nil, fmt.Errorf("Vultr: Failed to write hostname: %s", err) + } + + // public ipv4 + vultrMetaGet("interfaces/0/ipv4/address", "public_ipv4", 0644) + + // private ipv4 + vultrMetaGet("interfaces/1/ipv4/address", "private_ipv4", 0644) + + // private netmask + vultrMetaGet("interfaces/1/ipv4/netmask", "private_netmask", 0644) + + // region code + vultrMetaGet("region/regioncode", "region_code", 0644) + + // instance-id + vultrMetaGet("instanceid", "instance_id", 0644) + + return nil, nil +} + +// lookup a value (lookupName) in Vultr metaservice and store in given fileName +func vultrMetaGet(lookupName string, fileName string, fileMode os.FileMode) { + if lookupValue, err := vultrGet(vultrMetaDataURL + lookupName); err == nil { + // we got a value from the metadata server, now save to filesystem + err = ioutil.WriteFile(path.Join(ConfigPath, fileName), lookupValue, fileMode) + if err != nil { + // we couldn't save the file for some reason + log.Printf("Vultr: Failed to write %s:%s %s", fileName, lookupValue, err) + } + } else { + // we did not get a value back from the metadata server + log.Printf("Vultr: Failed to get %s: %s", lookupName, err) + } +} + +// vultrGet requests and extracts the requested URL +func vultrGet(url string) ([]byte, error) { + var client = &http.Client{ + Timeout: time.Second * 2, + } + + req, err := http.NewRequest("", url, nil) + if err != nil { + return nil, fmt.Errorf("Vultr: http.NewRequest failed: %s", err) + } + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("Vultr: Could not contact metadata service: %s", err) + } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("Vultr: Status not ok: %d", resp.StatusCode) + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("Vultr: Failed to read http response: %s", err) + } + return body, nil +} + +// SSH keys: +func (p *ProviderVultr) handleSSH() error { + sshKeys, err := vultrGet(vultrMetaDataURL + "public-keys") + if err != nil { + return fmt.Errorf("Failed to get sshKeys: %s", err) + } + + if err := os.Mkdir(path.Join(ConfigPath, SSH), 0755); err != nil { + return fmt.Errorf("Failed to create %s: %s", SSH, err) + } + + err = ioutil.WriteFile(path.Join(ConfigPath, SSH, "authorized_keys"), sshKeys, 0600) + if err != nil { + return fmt.Errorf("Failed to write ssh keys: %s", err) + } + return nil +}