Files
linuxkit/pkg/metadata/main.go
Luke Hodkinson 3dc23b96ac 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 <furious.luke@gmail.com>
2017-06-24 07:43:07 +10:00

211 lines
5.1 KiB
Go

package main
import (
"encoding/json"
"io/ioutil"
"log"
"os"
"path"
"strconv"
"syscall"
)
const (
// ConfigPath is where the data is extracted to
ConfigPath = "/var/config"
// MountPoint is where the CDROM is mounted
MountPoint = "/cdrom"
// Hostname is the filename in configPath where the hostname is stored
Hostname = "hostname"
// SSH is the path where sshd configuration from the provider is stored
SSH = "ssh"
// TODO(rneugeba): Need to check this is the same everywhere
cdromDev = "/dev/sr0"
)
// Provider is a generic interface for metadata/userdata providers.
type Provider interface {
// String should return a unique name for the Provider
String() string
// Probe returns true if the provider was detected.
Probe() bool
// Extract user data. This may write some data, specific to a
// provider, to ConfigPath and should return the generic userdata.
Extract() ([]byte, error)
}
// netProviders is a list of Providers offering metadata/userdata over the network
var netProviders []Provider
// cdromProviders is a list of Providers offering metadata/userdata data via CDROM
var cdromProviders []Provider
func init() {
netProviders = []Provider{NewGCP(), NewVultr(), NewAWS()}
cdromProviders = []Provider{NewCDROM()}
}
func main() {
if err := os.MkdirAll(ConfigPath, 0755); err != nil {
log.Fatalf("Could not create %s: %s", ConfigPath, err)
}
var p Provider
var userdata []byte
var err error
found := false
for _, p = range netProviders {
if p.Probe() {
log.Printf("%s: Probe succeeded", p)
userdata, err = p.Extract()
found = true
break
}
}
if !found {
log.Printf("Trying CDROM")
if err := os.Mkdir(MountPoint, 0755); err != nil {
log.Printf("CDROM: Failed to create %s: %s", MountPoint, err)
goto ErrorOut
}
if err := mountCDROM(cdromDev, MountPoint); err != nil {
log.Printf("Failed to mount cdrom: %s", err)
goto ErrorOut
}
defer syscall.Unmount(MountPoint, 0)
// Don't worry about removing MountPoint. We are in a container
for _, p = range cdromProviders {
if p.Probe() {
log.Printf("%s: Probe succeeded", p)
userdata, err = p.Extract()
found = true
break
}
}
}
ErrorOut:
if !found {
log.Printf("No metadata/userdata found. Bye")
return
}
if err != nil {
log.Printf("Error during metadata probe: %s", err)
}
err = ioutil.WriteFile(path.Join(ConfigPath, "provider"), []byte(p.String()), 0644)
if err != nil {
log.Printf("Error writing metadata provider: %s", err)
}
if userdata != nil {
if err := processUserData(userdata); err != nil {
log.Printf("Could not extract user data: %s", err)
}
}
// Handle setting the hostname as a special case. We want to
// do this early and don't really want another container for it.
hostname, err := ioutil.ReadFile(path.Join(ConfigPath, Hostname))
if err == nil {
err := syscall.Sethostname(hostname)
if err != nil {
log.Printf("Failed to set hostname: %s", err)
} else {
log.Printf("Set hostname to: %s", string(hostname))
}
}
}
// If the userdata is a json file, create a directory/file hierarchy.
// Example:
// {
// "foobar" : {
// "foo" : {
// "perm": "0644",
// "content": "hello"
// }
// }
// Will create foobar/foo with mode 0644 and content "hello"
func processUserData(data []byte) error {
// Always write the raw data to a file
err := ioutil.WriteFile(path.Join(ConfigPath, "userdata"), data, 0644)
if err != nil {
log.Printf("Could not write userdata: %s", err)
return err
}
var fd interface{}
if err := json.Unmarshal(data, &fd); err != nil {
// Userdata is no JSON, presumably...
log.Printf("Could not unmarshall userdata: %s", err)
// This is not an error
return nil
}
cm, ok := fd.(map[string]interface{})
if !ok {
log.Printf("Could convert JSON to desired format: %s", fd)
return nil
}
for d, val := range cm {
dir := path.Join(ConfigPath, d)
if err := os.Mkdir(dir, 0755); err != nil {
log.Printf("Failed to create %s: %s", dir, err)
continue
}
files, ok := val.(map[string]interface{})
if !ok {
log.Printf("Could convert JSON for files: %s", val)
continue
}
for f, i := range files {
p := uint64(0644)
var c string
switch fi := i.(type) {
case map[string]interface{}:
if _, ok := fi["perm"]; !ok {
log.Printf("No permission provided %s:%s", f, fi)
continue
}
if _, ok := fi["content"]; !ok {
log.Printf("No content provided %s:%s", f, fi)
continue
}
c = fi["content"].(string)
if p, err = strconv.ParseUint(fi["perm"].(string), 8, 32); err != nil {
log.Printf("Failed to parse permission %s: %s", fi, err)
continue
}
case string:
c = fi
default:
log.Printf("Couldn't convert JSON for items: %s", i)
continue
}
if err := ioutil.WriteFile(path.Join(dir, f), []byte(c), os.FileMode(p)); err != nil {
log.Printf("Failed to write %s/%s: %s", dir, f, err)
continue
}
}
}
return nil
}
// mountCDROM mounts a CDROM/DVD device under mountPoint
func mountCDROM(device, mountPoint string) error {
// We may need to poll a little for device ready
return syscall.Mount(device, mountPoint, "iso9660", syscall.MS_RDONLY, "")
}