mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-19 09:16:29 +00:00
282 lines
7.2 KiB
Go
282 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"flag"
|
|
"os"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
// ConfigPath is where the data is extracted to
|
|
ConfigPath = "/run/config"
|
|
|
|
// 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"
|
|
|
|
// Standard AWS-compatible Metadata URLs
|
|
userDataURL = "http://169.254.169.254/latest/user-data"
|
|
metaDataURL = "http://169.254.169.254/latest/meta-data/"
|
|
)
|
|
|
|
var (
|
|
defaultLogFormatter = &log.TextFormatter{}
|
|
)
|
|
|
|
// infoFormatter overrides the default format for Info() log events to
|
|
// provide an easier to read output
|
|
type infoFormatter struct {
|
|
}
|
|
|
|
func (f *infoFormatter) Format(entry *log.Entry) ([]byte, error) {
|
|
if entry.Level == log.InfoLevel {
|
|
return append([]byte(entry.Message), '\n'), nil
|
|
}
|
|
return defaultLogFormatter.Format(entry)
|
|
}
|
|
|
|
// 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
|
|
|
|
// fileProviders is a list of Providers offering metadata/userdata in a file on the filesystem
|
|
var fileProviders []Provider
|
|
|
|
func main() {
|
|
log.SetFormatter(new(infoFormatter))
|
|
log.SetLevel(log.InfoLevel)
|
|
flagVerbose := flag.Bool("v", false, "Verbose execution")
|
|
|
|
flag.Parse()
|
|
if *flagVerbose {
|
|
// Switch back to the standard formatter
|
|
log.SetFormatter(defaultLogFormatter)
|
|
log.SetLevel(log.DebugLevel)
|
|
}
|
|
|
|
providers := []string{"aws", "gcp", "hetzner", "openstack", "scaleway", "vultr", "digitalocean", "equinixmetal", "metaldata", "vmware", "cdrom"}
|
|
args := flag.Args()
|
|
if len(args) > 0 {
|
|
providers = args
|
|
}
|
|
for _, p := range providers {
|
|
switch {
|
|
case p == "aws":
|
|
netProviders = append(netProviders, NewAWS())
|
|
case p == "gcp":
|
|
netProviders = append(netProviders, NewGCP())
|
|
case p == "hetzner":
|
|
netProviders = append(netProviders, NewHetzner())
|
|
case p == "openstack":
|
|
netProviders = append(netProviders, NewOpenstack())
|
|
case p == "equinixmetal":
|
|
netProviders = append(netProviders, NewEquinixMetal())
|
|
case p == "scaleway":
|
|
netProviders = append(netProviders, NewScaleway())
|
|
case p == "vultr":
|
|
netProviders = append(netProviders, NewVultr())
|
|
case p == "digitalocean":
|
|
netProviders = append(netProviders, NewDigitalOcean())
|
|
case p == "metaldata":
|
|
netProviders = append(netProviders, NewMetalData())
|
|
case p == "vmware":
|
|
vmw := NewVMware()
|
|
if vmw != nil {
|
|
cdromProviders = append(cdromProviders, vmw)
|
|
}
|
|
case p == "cdrom":
|
|
cdromProviders = append(cdromProviders, ListCDROMs()...)
|
|
case strings.HasPrefix(p, "file="):
|
|
fileProviders = append(fileProviders, fileProvider(p[5:]))
|
|
default:
|
|
log.Fatalf("Unrecognised metadata provider: %s", p)
|
|
}
|
|
}
|
|
|
|
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 {
|
|
for _, p = range cdromProviders {
|
|
log.Printf("Trying %s", p.String())
|
|
if p.Probe() {
|
|
log.Printf("%s: Probe succeeded", p)
|
|
userdata, err = p.Extract()
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
for _, p = range fileProviders {
|
|
log.Printf("Trying file %s", p.String())
|
|
if p.Probe() {
|
|
log.Printf("%s: Probe succeeded", p)
|
|
userdata, err = p.Extract()
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
log.Printf("No metadata/userdata found. Bye")
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
log.Printf("Error during metadata probe: %s", err)
|
|
}
|
|
|
|
err = os.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(ConfigPath, 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 := os.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(basePath string, data []byte) error {
|
|
// Always write the raw data to a file
|
|
err := os.WriteFile(path.Join(basePath, "userdata"), data, 0644)
|
|
if err != nil {
|
|
log.Printf("Could not write userdata: %s", err)
|
|
return err
|
|
}
|
|
|
|
var root ConfigFile
|
|
if err := json.Unmarshal(data, &root); err != nil {
|
|
// Userdata is no JSON, presumably...
|
|
log.Printf("Could not unmarshall userdata: %s", err)
|
|
// This is not an error
|
|
return nil
|
|
}
|
|
|
|
for dir, entry := range root {
|
|
writeConfigFiles(path.Join(basePath, dir), entry)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeConfigFiles(target string, current Entry) {
|
|
if isFile(current) {
|
|
filemode, err := parseFileMode(current.Perm, 0644)
|
|
if err != nil {
|
|
log.Printf("Failed to parse permission %+v: %s", current, err)
|
|
return
|
|
}
|
|
if err := os.WriteFile(target, []byte(*current.Content), filemode); err != nil {
|
|
log.Printf("Failed to write %s: %s", target, err)
|
|
return
|
|
}
|
|
} else if isDirectory(current) {
|
|
filemode, err := parseFileMode(current.Perm, 0755)
|
|
if err != nil {
|
|
log.Printf("Failed to parse permission %+v: %s", current, err)
|
|
return
|
|
}
|
|
if err := os.MkdirAll(target, filemode); err != nil {
|
|
log.Printf("Failed to create %s: %s", target, err)
|
|
return
|
|
}
|
|
for dir, entry := range current.Entries {
|
|
writeConfigFiles(path.Join(target, dir), entry)
|
|
}
|
|
} else {
|
|
log.Printf("%s is invalid", target)
|
|
}
|
|
}
|
|
|
|
func isFile(json Entry) bool {
|
|
return json.Content != nil && json.Entries == nil
|
|
}
|
|
|
|
func isDirectory(json Entry) bool {
|
|
return json.Content == nil && json.Entries != nil
|
|
}
|
|
|
|
func parseFileMode(input string, defaultMode os.FileMode) (os.FileMode, error) {
|
|
if input != "" {
|
|
perm, err := strconv.ParseUint(input, 8, 32)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return os.FileMode(perm), nil
|
|
}
|
|
return defaultMode, nil
|
|
}
|
|
|
|
// ConfigFile represents the configuration file
|
|
type ConfigFile map[string]Entry
|
|
|
|
// Entry represents either a directory or a file
|
|
type Entry struct {
|
|
Perm string `json:"perm,omitempty"`
|
|
Content *string `json:"content,omitempty"`
|
|
Entries map[string]Entry `json:"entries,omitempty"`
|
|
}
|