mirror of
				https://github.com/linuxkit/linuxkit.git
				synced 2025-11-04 08:25:51 +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"`
 | 
						|
}
 |