Files
linuxkit/pkg/metadata/main.go
Ken Cochrane 60d099176c Added AWS provider to metadata package
Signed-off-by: Ken Cochrane <kencochrane@gmail.com>
2017-06-01 16:44:58 -04: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(), 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, "")
}