| @@ -1,30 +0,0 @@ | ||||
| package cloudinit | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| func ParseUserData(contents []byte) (interface{}, error) { | ||||
| 	bytereader := bytes.NewReader(contents) | ||||
| 	bufreader := bufio.NewReader(bytereader) | ||||
| 	header, _ := bufreader.ReadString('\n') | ||||
|  | ||||
| 	if strings.HasPrefix(header, "#!") { | ||||
| 		log.Printf("Parsing user-data as script") | ||||
| 		return Script(contents), nil | ||||
|  | ||||
| 	} else if header == "#cloud-config\n" { | ||||
| 		log.Printf("Parsing user-data as cloud-config") | ||||
| 		cfg, err := NewCloudConfig(contents) | ||||
| 		if err != nil { | ||||
| 			log.Fatal(err.Error()) | ||||
| 		} | ||||
| 		return *cfg, nil | ||||
| 	} else { | ||||
| 		return nil, fmt.Errorf("Unrecognized user-data header: %s", header) | ||||
| 	} | ||||
| } | ||||
| @@ -1,66 +0,0 @@ | ||||
| package cloudinit | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path" | ||||
| ) | ||||
|  | ||||
| func PrepWorkspace(workspace string) error { | ||||
| 	// Ensure workspace exists and is a directory | ||||
| 	info, err := os.Stat(workspace) | ||||
| 	if err == nil { | ||||
| 		if !info.IsDir() { | ||||
| 			return fmt.Errorf("%s is not a directory", workspace) | ||||
| 		} | ||||
| 	} else { | ||||
| 		err = os.MkdirAll(workspace, 0755) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Ensure scripts dir in workspace exists and is a directory | ||||
| 	scripts := path.Join(workspace, "scripts") | ||||
| 	info, err = os.Stat(scripts) | ||||
| 	if err == nil { | ||||
| 		if !info.IsDir() { | ||||
| 			return fmt.Errorf("%s is not a directory", scripts) | ||||
| 		} | ||||
| 	} else { | ||||
| 		err = os.Mkdir(scripts, 0755) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func PersistScriptInWorkspace(script Script, workspace string) (string, error) { | ||||
| 	scriptsDir := path.Join(workspace, "scripts") | ||||
| 	f, err := ioutil.TempFile(scriptsDir, "") | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	defer f.Close() | ||||
|  | ||||
| 	f.Chmod(0744) | ||||
|  | ||||
| 	_, err = f.Write(script) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// Ensure script has been written to disk before returning, as the | ||||
| 	// next natural thing to do is execute it | ||||
| 	f.Sync() | ||||
|  | ||||
| 	return f.Name(), nil | ||||
| } | ||||
|  | ||||
| func PersistScriptUnitNameInWorkspace(name string, workspace string) error { | ||||
| 	unitPath := path.Join(workspace, "scripts", "unit-name") | ||||
| 	return ioutil.WriteFile(unitPath, []byte(name), 0644) | ||||
| } | ||||
| @@ -1,63 +0,0 @@ | ||||
| package cloudinit | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| type WriteFile struct { | ||||
| 	Encoding           string | ||||
| 	Content            string | ||||
| 	Owner              string | ||||
| 	Path               string | ||||
| 	RawFilePermissions string `yaml:"permissions"` | ||||
| } | ||||
|  | ||||
| func (wf *WriteFile) Permissions() (os.FileMode, error) { | ||||
| 	if wf.RawFilePermissions == "" { | ||||
| 		return os.FileMode(0644), nil | ||||
| 	} | ||||
|  | ||||
| 	// Parse string representation of file mode as octal | ||||
| 	perm, err := strconv.ParseInt(wf.RawFilePermissions, 8, 32) | ||||
| 	if err != nil { | ||||
| 		return 0, errors.New("Unable to parse file permissions as octal integer") | ||||
| 	} | ||||
| 	return os.FileMode(perm), nil | ||||
| } | ||||
|  | ||||
| func ProcessWriteFile(base string, wf *WriteFile) error { | ||||
| 	if wf.Encoding != "" { | ||||
| 		return fmt.Errorf("Unable to write file with encoding %s", wf.Encoding) | ||||
| 	} | ||||
|  | ||||
| 	fullPath := path.Join(base, wf.Path) | ||||
|  | ||||
| 	if err := os.MkdirAll(path.Dir(fullPath), os.FileMode(0755)); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	perm, err := wf.Permissions() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := ioutil.WriteFile(fullPath, []byte(wf.Content), perm); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if wf.Owner != "" { | ||||
| 		// We shell out since we don't have a way to look up unix groups natively | ||||
| 		cmd := exec.Command("chown", wf.Owner, fullPath) | ||||
| 		if err := cmd.Run(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
| @@ -1,21 +1,22 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"flag" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/coreos/coreos-cloudinit/cloudinit" | ||||
| 	"github.com/coreos/coreos-cloudinit/datasource" | ||||
| 	"github.com/coreos/coreos-cloudinit/initialize" | ||||
| 	"github.com/coreos/coreos-cloudinit/system" | ||||
| ) | ||||
|  | ||||
| const version = "0.1.2+git" | ||||
|  | ||||
| func main() { | ||||
| 	var userdata []byte | ||||
| 	var err error | ||||
|  | ||||
| 	var printVersion bool | ||||
| 	flag.BoolVar(&printVersion, "version", false, "Print the version and exit") | ||||
|  | ||||
| @@ -29,7 +30,7 @@ func main() { | ||||
| 	flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data") | ||||
|  | ||||
| 	var sshKeyName string | ||||
| 	flag.StringVar(&sshKeyName, "ssh-key-name", cloudinit.DefaultSSHKeyName, "Add SSH keys to the system with the given name") | ||||
| 	flag.StringVar(&sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name") | ||||
|  | ||||
| 	flag.Parse() | ||||
|  | ||||
| @@ -43,49 +44,48 @@ func main() { | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	var ds datasource.Datasource | ||||
| 	if file != "" { | ||||
| 		log.Printf("Reading user-data from file: %s", file) | ||||
| 		userdata, err = ioutil.ReadFile(file) | ||||
| 		if err != nil { | ||||
| 			log.Fatal(err.Error()) | ||||
| 		} | ||||
| 		ds = datasource.NewLocalFile(file) | ||||
| 	} else if url != "" { | ||||
| 		log.Printf("Reading user-data from metadata service") | ||||
| 		svc := cloudinit.NewMetadataService(url) | ||||
| 		userdata, err = svc.UserData() | ||||
| 		if err != nil { | ||||
| 			log.Fatal(err.Error()) | ||||
| 		} | ||||
| 		ds = datasource.NewMetadataService(url) | ||||
| 	} else { | ||||
| 		fmt.Println("Provide one of --from-file or --from-url") | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	log.Printf("Fetching user-data from datasource of type %q", ds.Type()) | ||||
| 	userdata, err := ds.Fetch() | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed fetching user-data from datasource: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if len(userdata) == 0 { | ||||
| 		log.Printf("No user data to handle, exiting.") | ||||
| 		os.Exit(0) | ||||
| 	} | ||||
|  | ||||
| 	parsed, err := cloudinit.ParseUserData(userdata) | ||||
| 	parsed, err := ParseUserData(userdata) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed parsing user-data: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	err = cloudinit.PrepWorkspace(workspace) | ||||
| 	env := initialize.NewEnvironment("/", workspace) | ||||
| 	err = initialize.PrepWorkspace(env.Workspace()) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed preparing workspace: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	switch t := parsed.(type) { | ||||
| 	case cloudinit.CloudConfig: | ||||
| 		err = cloudinit.ApplyCloudConfig(t, sshKeyName) | ||||
| 	case cloudinit.Script: | ||||
| 	case initialize.CloudConfig: | ||||
| 		err = initialize.Apply(t, env) | ||||
| 	case system.Script: | ||||
| 		var path string | ||||
| 		path, err = cloudinit.PersistScriptInWorkspace(t, workspace) | ||||
| 		path, err = initialize.PersistScriptInWorkspace(t, env.Workspace()) | ||||
| 		if err == nil { | ||||
| 			var name string | ||||
| 			name, err = cloudinit.ExecuteScript(path) | ||||
| 			cloudinit.PersistScriptUnitNameInWorkspace(name, workspace) | ||||
| 			name, err = system.ExecuteScript(path) | ||||
| 			initialize.PersistUnitNameInWorkspace(name, workspace) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -93,3 +93,24 @@ func main() { | ||||
| 		log.Fatalf("Failed resolving user-data: %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ParseUserData(contents []byte) (interface{}, error) { | ||||
| 	bytereader := bytes.NewReader(contents) | ||||
| 	bufreader := bufio.NewReader(bytereader) | ||||
| 	header, _ := bufreader.ReadString('\n') | ||||
|  | ||||
| 	if strings.HasPrefix(header, "#!") { | ||||
| 		log.Printf("Parsing user-data as script") | ||||
| 		return system.Script(contents), nil | ||||
|  | ||||
| 	} else if header == "#cloud-config\n" { | ||||
| 		log.Printf("Parsing user-data as cloud-config") | ||||
| 		cfg, err := initialize.NewCloudConfig(contents) | ||||
| 		if err != nil { | ||||
| 			log.Fatal(err.Error()) | ||||
| 		} | ||||
| 		return *cfg, nil | ||||
| 	} else { | ||||
| 		return nil, fmt.Errorf("Unrecognized user-data header: %s", header) | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										6
									
								
								datasource/datasource.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								datasource/datasource.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| package datasource | ||||
|  | ||||
| type Datasource interface { | ||||
| 	Fetch() ([]byte, error) | ||||
| 	Type()  string | ||||
| } | ||||
							
								
								
									
										21
									
								
								datasource/file.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								datasource/file.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| package datasource | ||||
|  | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| ) | ||||
|  | ||||
| type localFile struct { | ||||
| 	path string | ||||
| } | ||||
|  | ||||
| func NewLocalFile(path string) *localFile { | ||||
| 	return &localFile{path} | ||||
| } | ||||
|  | ||||
| func (self *localFile) Fetch() ([]byte, error) { | ||||
| 	return ioutil.ReadFile(self.path) | ||||
| } | ||||
|  | ||||
| func (self *localFile) Type() string { | ||||
| 	return "local-file" | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| package cloudinit | ||||
| package datasource | ||||
| 
 | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| @@ -14,7 +14,7 @@ func NewMetadataService(url string) *metadataService { | ||||
| 	return &metadataService{url, http.Client{}} | ||||
| } | ||||
| 
 | ||||
| func (ms *metadataService) UserData() ([]byte, error) { | ||||
| func (ms *metadataService) Fetch() ([]byte, error) { | ||||
| 	resp, err := ms.client.Get(ms.url) | ||||
| 	if err != nil { | ||||
| 		return []byte{}, err | ||||
| @@ -33,4 +33,6 @@ func (ms *metadataService) UserData() ([]byte, error) { | ||||
| 	return respBytes, nil | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| func (ms *metadataService) Type() string { | ||||
| 	return "metadata-service" | ||||
| } | ||||
| @@ -1,24 +1,25 @@ | ||||
| package cloudinit | ||||
| package initialize | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"path" | ||||
| 
 | ||||
| 	"github.com/coreos/coreos-cloudinit/third_party/launchpad.net/goyaml" | ||||
| ) | ||||
| 
 | ||||
| const DefaultSSHKeyName = "coreos-cloudinit" | ||||
| 	"github.com/coreos/coreos-cloudinit/system" | ||||
| ) | ||||
| 
 | ||||
| type CloudConfig struct { | ||||
| 	SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"` | ||||
| 	Coreos            struct { | ||||
| 		Etcd  EtcdEnvironment | ||||
| 		Fleet struct{ Autostart bool } | ||||
| 		Units []Unit | ||||
| 		Units []system.Unit | ||||
| 	} | ||||
| 	WriteFiles []WriteFile `yaml:"write_files"` | ||||
| 	WriteFiles []system.File `yaml:"write_files"` | ||||
| 	Hostname   string | ||||
| 	Users      []User | ||||
| 	Users      []system.User | ||||
| } | ||||
| 
 | ||||
| func NewCloudConfig(contents []byte) (*CloudConfig, error) { | ||||
| @@ -39,9 +40,9 @@ func (cc CloudConfig) String() string { | ||||
| 	return stringified | ||||
| } | ||||
| 
 | ||||
| func ApplyCloudConfig(cfg CloudConfig, sshKeyName string) error { | ||||
| func Apply(cfg CloudConfig, env *Environment) error { | ||||
| 	if cfg.Hostname != "" { | ||||
| 		if err := SetHostname(cfg.Hostname); err != nil { | ||||
| 		if err := system.SetHostname(cfg.Hostname); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		log.Printf("Set hostname to %s", cfg.Hostname) | ||||
| @@ -54,18 +55,18 @@ func ApplyCloudConfig(cfg CloudConfig, sshKeyName string) error { | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			if UserExists(&user) { | ||||
| 			if system.UserExists(&user) { | ||||
| 				log.Printf("User '%s' exists, ignoring creation-time fields", user.Name) | ||||
| 				if user.PasswordHash != "" { | ||||
| 					log.Printf("Setting '%s' user's password", user.Name) | ||||
| 					if err := SetUserPassword(user.Name, user.PasswordHash); err != nil { | ||||
| 					if err := system.SetUserPassword(user.Name, user.PasswordHash); err != nil { | ||||
| 						log.Printf("Failed setting '%s' user's password: %v", user.Name, err) | ||||
| 						return err | ||||
| 					} | ||||
| 				} | ||||
| 			} else { | ||||
| 				log.Printf("Creating user '%s'", user.Name) | ||||
| 				if err := CreateUser(&user); err != nil { | ||||
| 				if err := system.CreateUser(&user); err != nil { | ||||
| 					log.Printf("Failed creating user '%s': %v", user.Name, err) | ||||
| 					return err | ||||
| 				} | ||||
| @@ -73,7 +74,7 @@ func ApplyCloudConfig(cfg CloudConfig, sshKeyName string) error { | ||||
| 
 | ||||
| 			if len(user.SSHAuthorizedKeys) > 0 { | ||||
| 				log.Printf("Authorizing %d SSH keys for user '%s'", len(user.SSHAuthorizedKeys), user.Name) | ||||
| 				if err := AuthorizeSSHKeys(user.Name, sshKeyName, user.SSHAuthorizedKeys); err != nil { | ||||
| 				if err := system.AuthorizeSSHKeys(user.Name, env.SSHKeyName(), user.SSHAuthorizedKeys); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| @@ -81,7 +82,7 @@ func ApplyCloudConfig(cfg CloudConfig, sshKeyName string) error { | ||||
| 	} | ||||
| 
 | ||||
| 	if len(cfg.SSHAuthorizedKeys) > 0 { | ||||
| 		err := AuthorizeSSHKeys("core", sshKeyName, cfg.SSHAuthorizedKeys) | ||||
| 		err := system.AuthorizeSSHKeys("core", env.SSHKeyName(), cfg.SSHAuthorizedKeys) | ||||
| 		if err == nil { | ||||
| 			log.Printf("Authorized SSH keys for core user") | ||||
| 		} else { | ||||
| @@ -91,7 +92,8 @@ func ApplyCloudConfig(cfg CloudConfig, sshKeyName string) error { | ||||
| 
 | ||||
| 	if len(cfg.WriteFiles) > 0 { | ||||
| 		for _, file := range cfg.WriteFiles { | ||||
| 			if err := ProcessWriteFile("/", &file); err != nil { | ||||
| 			file.Path = path.Join(env.Root(), file.Path) | ||||
| 			if err := system.WriteFile(&file); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			log.Printf("Wrote file %s to filesystem", file.Path) | ||||
| @@ -99,7 +101,7 @@ func ApplyCloudConfig(cfg CloudConfig, sshKeyName string) error { | ||||
| 	} | ||||
| 
 | ||||
| 	if len(cfg.Coreos.Etcd) > 0 { | ||||
| 		if err := WriteEtcdEnvironment("/", cfg.Coreos.Etcd); err != nil { | ||||
| 		if err := WriteEtcdEnvironment(cfg.Coreos.Etcd, env.Root()); err != nil { | ||||
| 			log.Fatalf("Failed to write etcd config to filesystem: %v", err) | ||||
| 		} | ||||
| 
 | ||||
| @@ -109,7 +111,7 @@ func ApplyCloudConfig(cfg CloudConfig, sshKeyName string) error { | ||||
| 	if len(cfg.Coreos.Units) > 0 { | ||||
| 		for _, unit := range cfg.Coreos.Units { | ||||
| 			log.Printf("Placing unit %s on filesystem", unit.Name) | ||||
| 			dst, err := PlaceUnit("/", &unit) | ||||
| 			dst, err := system.PlaceUnit(&unit, env.Root()) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| @@ -117,7 +119,7 @@ func ApplyCloudConfig(cfg CloudConfig, sshKeyName string) error { | ||||
| 
 | ||||
| 			if unit.Group() != "network" { | ||||
| 				log.Printf("Enabling unit file %s", dst) | ||||
| 				if err := EnableUnitFile(dst, unit.Runtime); err != nil { | ||||
| 				if err := system.EnableUnitFile(dst, unit.Runtime); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				log.Printf("Enabled unit %s", unit.Name) | ||||
| @@ -125,12 +127,12 @@ func ApplyCloudConfig(cfg CloudConfig, sshKeyName string) error { | ||||
| 				log.Printf("Skipping enable for network-like unit %s", unit.Name) | ||||
| 			} | ||||
| 		} | ||||
| 		DaemonReload() | ||||
| 		StartUnits(cfg.Coreos.Units) | ||||
| 		system.DaemonReload() | ||||
| 		system.StartUnits(cfg.Coreos.Units) | ||||
| 	} | ||||
| 
 | ||||
| 	if cfg.Coreos.Fleet.Autostart { | ||||
| 		err := StartUnitByName("fleet.service") | ||||
| 		err := system.StartUnitByName("fleet.service") | ||||
| 		if err == nil { | ||||
| 			log.Printf("Started fleet service.") | ||||
| 		} else { | ||||
| @@ -1,4 +1,4 @@ | ||||
| package cloudinit | ||||
| package initialize | ||||
| 
 | ||||
| import ( | ||||
| 	"strings" | ||||
							
								
								
									
										33
									
								
								initialize/env.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								initialize/env.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| package initialize | ||||
|  | ||||
| import ( | ||||
| 	"path" | ||||
| ) | ||||
|  | ||||
| const DefaultSSHKeyName = "coreos-cloudinit" | ||||
|  | ||||
| type Environment struct { | ||||
| 	root      string | ||||
| 	workspace string | ||||
| 	sshKeyName string | ||||
| } | ||||
|  | ||||
| func NewEnvironment(root, workspace string) *Environment { | ||||
| 	return &Environment{root, workspace, DefaultSSHKeyName} | ||||
| } | ||||
|  | ||||
| func (self *Environment) Workspace() string { | ||||
| 	return path.Join(self.root, self.workspace) | ||||
| } | ||||
|  | ||||
| func (self *Environment) Root() string { | ||||
| 	return self.root | ||||
| } | ||||
|  | ||||
| func (self *Environment) SSHKeyName() string { | ||||
| 	return self.sshKeyName | ||||
| } | ||||
|  | ||||
| func (self *Environment) SetSSHKeyName(name string) { | ||||
| 	self.sshKeyName = name | ||||
| } | ||||
| @@ -1,11 +1,12 @@ | ||||
| package cloudinit | ||||
| package initialize | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/coreos/coreos-cloudinit/system" | ||||
| ) | ||||
| 
 | ||||
| type EtcdEnvironment map[string]string | ||||
| @@ -34,15 +35,12 @@ func (ec EtcdEnvironment) String() (out string) { | ||||
| } | ||||
| 
 | ||||
| // Write an EtcdEnvironment to the appropriate path on disk for etcd.service | ||||
| func WriteEtcdEnvironment(root string, env EtcdEnvironment) error { | ||||
| 	cfgDir := path.Join(root, "etc", "systemd", "system", "etcd.service.d") | ||||
| 	cfgFile := path.Join(cfgDir, "20-cloudinit.conf") | ||||
| 
 | ||||
| 	if _, err := os.Stat(cfgDir); err != nil { | ||||
| 		if err := os.MkdirAll(cfgDir, os.FileMode(0755)); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| func WriteEtcdEnvironment(env EtcdEnvironment, root string) error { | ||||
| 	file := system.File{ | ||||
| 		Path: path.Join(root, "etc", "systemd", "system", "etcd.service.d", "20-cloudinit.conf"), | ||||
| 		RawFilePermissions: "0644", | ||||
| 		Content: env.String(), | ||||
| 	} | ||||
| 
 | ||||
| 	return ioutil.WriteFile(cfgFile, []byte(env.String()), os.FileMode(0644)) | ||||
| 	return system.WriteFile(&file) | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| package cloudinit | ||||
| package initialize | ||||
| 
 | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| @@ -55,7 +55,7 @@ func TestEtcdEnvironmentWrittenToDisk(t *testing.T) { | ||||
| 	} | ||||
| 	defer syscall.Rmdir(dir) | ||||
| 
 | ||||
| 	if err := WriteEtcdEnvironment(dir, ec); err != nil { | ||||
| 	if err := WriteEtcdEnvironment(ec, dir); err != nil { | ||||
| 		t.Fatalf("Processing of EtcdEnvironment failed: %v", err) | ||||
| 	} | ||||
| 
 | ||||
							
								
								
									
										48
									
								
								initialize/workspace.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								initialize/workspace.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| package initialize | ||||
|  | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"path" | ||||
|  | ||||
| 	"github.com/coreos/coreos-cloudinit/system" | ||||
| ) | ||||
|  | ||||
| func PrepWorkspace(workspace string) error { | ||||
| 	if err := system.EnsureDirectoryExists(workspace); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	scripts := path.Join(workspace, "scripts") | ||||
| 	if err := system.EnsureDirectoryExists(scripts); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func PersistScriptInWorkspace(script system.Script, workspace string) (string, error) { | ||||
| 	scriptsPath := path.Join(workspace, "scripts") | ||||
| 	tmp, err := ioutil.TempFile(scriptsPath, "") | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	tmp.Close() | ||||
|  | ||||
| 	file := system.File{ | ||||
| 		Path: tmp.Name(), | ||||
| 		RawFilePermissions: "0744", | ||||
| 		Content: string(script), | ||||
| 	} | ||||
|  | ||||
| 	err = system.WriteFile(&file) | ||||
| 	return file.Path, err | ||||
| } | ||||
|  | ||||
| func PersistUnitNameInWorkspace(name string, workspace string) error { | ||||
| 	file := system.File{ | ||||
| 		Path: path.Join(workspace, "scripts", "unit-name"), | ||||
| 		RawFilePermissions: "0644", | ||||
| 		Content: name, | ||||
| 	} | ||||
| 	return system.WriteFile(&file) | ||||
| } | ||||
							
								
								
									
										77
									
								
								system/file.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								system/file.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| package system | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| type File struct { | ||||
| 	Encoding    string | ||||
| 	Content     string | ||||
| 	Owner       string | ||||
| 	Path        string | ||||
| 	RawFilePermissions string `yaml:"permissions"` | ||||
| } | ||||
|  | ||||
| func (f *File) Permissions() (os.FileMode, error) { | ||||
| 	if f.RawFilePermissions == "" { | ||||
| 		return os.FileMode(0644), nil | ||||
| 	} | ||||
|  | ||||
| 	// Parse string representation of file mode as octal | ||||
| 	perm, err := strconv.ParseInt(f.RawFilePermissions, 8, 32) | ||||
| 	if err != nil { | ||||
| 		return 0, errors.New("Unable to parse file permissions as octal integer") | ||||
| 	} | ||||
| 	return os.FileMode(perm), nil | ||||
| } | ||||
|  | ||||
|  | ||||
| func WriteFile(f *File) error { | ||||
| 	if f.Encoding != "" { | ||||
| 		return fmt.Errorf("Unable to write file with encoding %s", f.Encoding) | ||||
| 	} | ||||
|  | ||||
| 	if err := os.MkdirAll(path.Dir(f.Path), os.FileMode(0755)); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	perm, err := f.Permissions() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := ioutil.WriteFile(f.Path, []byte(f.Content), perm); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if f.Owner != "" { | ||||
| 		// We shell out since we don't have a way to look up unix groups natively | ||||
| 		cmd := exec.Command("chown", f.Owner, f.Path) | ||||
| 		if err := cmd.Run(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func EnsureDirectoryExists(dir string) error { | ||||
| 	info, err := os.Stat(dir) | ||||
| 	if err == nil { | ||||
| 		if !info.IsDir() { | ||||
| 			return fmt.Errorf("%s is not a directory", dir) | ||||
| 		} | ||||
| 	} else { | ||||
| 		err = os.MkdirAll(dir, 0755) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| package cloudinit | ||||
| package system | ||||
| 
 | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| @@ -9,21 +9,23 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| func TestWriteFileUnencodedContent(t *testing.T) { | ||||
| 	wf := WriteFile{ | ||||
| 		Path:               "/tmp/foo", | ||||
| 		Content:            "bar", | ||||
| 	} | ||||
| 	dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create tempdir: %v", err) | ||||
| 	} | ||||
| 	defer syscall.Rmdir(dir) | ||||
| 
 | ||||
| 	if err := ProcessWriteFile(dir, &wf); err != nil { | ||||
| 		t.Fatalf("Processing of WriteFile failed: %v", err) | ||||
| 	fullPath := path.Join(dir, "tmp", "foo") | ||||
| 
 | ||||
| 	wf := File{ | ||||
| 		Path:        fullPath, | ||||
| 		Content:     "bar", | ||||
| 		RawFilePermissions: "0644", | ||||
| 	} | ||||
| 
 | ||||
| 	fullPath := path.Join(dir, "tmp", "foo") | ||||
| 	if err := WriteFile(&wf); err != nil { | ||||
| 		t.Fatalf("Processing of WriteFile failed: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	fi, err := os.Stat(fullPath) | ||||
| 	if err != nil { | ||||
| @@ -45,38 +47,40 @@ func TestWriteFileUnencodedContent(t *testing.T) { | ||||
| } | ||||
| 
 | ||||
| func TestWriteFileInvalidPermission(t *testing.T) { | ||||
| 	wf := WriteFile{ | ||||
| 		Path:               "/tmp/foo", | ||||
| 		Content:            "bar", | ||||
| 		RawFilePermissions: "pants", | ||||
| 	} | ||||
| 	dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create tempdir: %v", err) | ||||
| 	} | ||||
| 	defer syscall.Rmdir(dir) | ||||
| 
 | ||||
| 	if err := ProcessWriteFile(dir, &wf); err == nil { | ||||
| 	wf := File{ | ||||
| 		Path:        path.Join(dir, "tmp", "foo"), | ||||
| 		Content:     "bar", | ||||
| 		RawFilePermissions: "pants", | ||||
| 	} | ||||
| 
 | ||||
| 	if err := WriteFile(&wf); err == nil { | ||||
| 		t.Fatalf("Expected error to be raised when writing file with invalid permission") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestWriteFilePermissions(t *testing.T) { | ||||
| 	wf := WriteFile{ | ||||
| 		Path:               "/tmp/foo", | ||||
| 		RawFilePermissions: "0755", | ||||
| 	} | ||||
| 	dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create tempdir: %v", err) | ||||
| 	} | ||||
| 	defer syscall.Rmdir(dir) | ||||
| 
 | ||||
| 	if err := ProcessWriteFile(dir, &wf); err != nil { | ||||
| 		t.Fatalf("Processing of WriteFile failed: %v", err) | ||||
| 	fullPath := path.Join(dir, "tmp", "foo") | ||||
| 
 | ||||
| 	wf := File{ | ||||
| 		Path:               fullPath, | ||||
| 		RawFilePermissions: "0755", | ||||
| 	} | ||||
| 
 | ||||
| 	fullPath := path.Join(dir, "tmp", "foo") | ||||
| 	if err := WriteFile(&wf); err != nil { | ||||
| 		t.Fatalf("Processing of WriteFile failed: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	fi, err := os.Stat(fullPath) | ||||
| 	if err != nil { | ||||
| @@ -89,19 +93,19 @@ func TestWriteFilePermissions(t *testing.T) { | ||||
| } | ||||
| 
 | ||||
| func TestWriteFileEncodedContent(t *testing.T) { | ||||
| 	wf := WriteFile{ | ||||
| 		Path:     "/tmp/foo", | ||||
| 		Content:  "", | ||||
| 		Encoding: "base64", | ||||
| 	} | ||||
| 
 | ||||
| 	dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create tempdir: %v", err) | ||||
| 	} | ||||
| 	defer syscall.Rmdir(dir) | ||||
| 
 | ||||
| 	if err := ProcessWriteFile(dir, &wf); err == nil { | ||||
| 	wf := File{ | ||||
| 		Path: path.Join(dir, "tmp", "foo"), | ||||
| 		Content: "", | ||||
| 		Encoding: "base64", | ||||
| 	} | ||||
| 
 | ||||
| 	if err := WriteFile(&wf); err == nil { | ||||
| 		t.Fatalf("Expected error to be raised when writing file with encoding") | ||||
| 	} | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| package cloudinit | ||||
| package system | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| @@ -1,8 +1,7 @@ | ||||
| package cloudinit | ||||
| package system | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| @@ -36,7 +35,7 @@ func (u *Unit) Group() (group string) { | ||||
| 
 | ||||
| type Script []byte | ||||
| 
 | ||||
| func PlaceUnit(root string, u *Unit) (string, error) { | ||||
| func PlaceUnit(u *Unit, root string) (string, error) { | ||||
| 	dir := "etc" | ||||
| 	if u.Runtime { | ||||
| 		dir = "run" | ||||
| @@ -50,7 +49,14 @@ func PlaceUnit(root string, u *Unit) (string, error) { | ||||
| 	} | ||||
| 
 | ||||
| 	dst = path.Join(dst, u.Name) | ||||
| 	err := ioutil.WriteFile(dst, []byte(u.Content), os.FileMode(0644)) | ||||
| 
 | ||||
| 	file := File{ | ||||
| 		Path: dst, | ||||
| 		Content: u.Content, | ||||
| 		RawFilePermissions: "0644", | ||||
| 	} | ||||
| 
 | ||||
| 	err := WriteFile(&file) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| @@ -1,4 +1,4 @@ | ||||
| package cloudinit | ||||
| package system | ||||
| 
 | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| @@ -26,7 +26,7 @@ Address=10.209.171.177/19 | ||||
| 	} | ||||
| 	defer syscall.Rmdir(dir) | ||||
| 
 | ||||
| 	if _, err := PlaceUnit(dir, &u); err != nil { | ||||
| 	if _, err := PlaceUnit(&u, dir); err != nil { | ||||
| 		t.Fatalf("PlaceUnit failed: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| @@ -72,7 +72,7 @@ Where=/media/state | ||||
| 	} | ||||
| 	defer syscall.Rmdir(dir) | ||||
| 
 | ||||
| 	if _, err := PlaceUnit(dir, &u); err != nil { | ||||
| 	if _, err := PlaceUnit(&u, dir); err != nil { | ||||
| 		t.Fatalf("PlaceUnit failed: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| @@ -1,4 +1,4 @@ | ||||
| package cloudinit | ||||
| package system | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
							
								
								
									
										6
									
								
								test
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								test
									
									
									
									
									
								
							| @@ -4,5 +4,7 @@ echo "Building bin/coreos-cloudinit" | ||||
| . build | ||||
|  | ||||
| echo "Running tests..." | ||||
| go test -i github.com/coreos/coreos-cloudinit/cloudinit | ||||
| go test -v github.com/coreos/coreos-cloudinit/cloudinit | ||||
| for pkg in "./initialize ./system"; do | ||||
| 	go test -i $pkg | ||||
| 	go test -v $pkg | ||||
| done | ||||
|   | ||||
		Reference in New Issue
	
	Block a user