initial import

This commit is contained in:
Brian Waldon
2014-03-04 16:36:05 -08:00
commit 4e47d1e1ee
55 changed files with 10565 additions and 0 deletions

58
cloudinit/cloud_config.go Normal file
View File

@@ -0,0 +1,58 @@
package cloudinit
import (
"log"
"launchpad.net/goyaml"
)
type CloudConfig struct {
SSH_Authorized_Keys []string
Coreos struct{Etcd struct{ Discovery_URL string }; Fleet struct{ Autostart bool } }
}
func NewCloudConfig(contents []byte) (*CloudConfig, error) {
var cfg CloudConfig
err := goyaml.Unmarshal(contents, &cfg)
return &cfg, err
}
func (cc CloudConfig) String() string {
bytes, err := goyaml.Marshal(cc)
if err == nil {
return string(bytes)
} else {
return ""
}
}
func ResolveCloudConfig(cfg CloudConfig) error {
if len(cfg.SSH_Authorized_Keys) > 0 {
err := AuthorizeSSHKeys(cfg.SSH_Authorized_Keys)
if err == nil {
log.Printf("Authorized SSH keys for core user")
} else {
return err
}
}
if cfg.Coreos.Etcd.Discovery_URL != "" {
err := PersistEtcdDiscoveryURL(cfg.Coreos.Etcd.Discovery_URL)
if err == nil {
log.Printf("Consumed etcd discovery url")
} else {
log.Fatalf("Failed to persist etcd discovery url to filesystem: %v", err)
}
}
if cfg.Coreos.Fleet.Autostart {
err := StartUnit("fleet.service")
if err == nil {
log.Printf("Started fleet service.")
} else {
return err
}
}
return nil
}

View File

@@ -0,0 +1,78 @@
package cloudinit
import (
"testing"
)
// Assert that the parsing of a cloud config file "generally works"
func TestCloudConfigEmpty(t *testing.T) {
cfg, err := NewCloudConfig([]byte{})
if err != nil {
t.Fatalf("Encountered unexpected error :%v", err)
}
keys := cfg.SSH_Authorized_Keys
if len(keys) != 0 {
t.Error("Parsed incorrect number of SSH keys")
}
if cfg.Coreos.Etcd.Discovery_URL != "" {
t.Error("Parsed incorrect value of discovery url")
}
if cfg.Coreos.Fleet.Autostart {
t.Error("Expected AutostartFleet not to be defined")
}
}
// Assert that the parsing of a cloud config file "generally works"
func TestCloudConfig(t *testing.T) {
contents := []byte(`
coreos:
etcd:
discovery_url: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
fleet:
autostart: Yes
ssh_authorized_keys:
- foobar
- foobaz
`)
cfg, err := NewCloudConfig(contents)
if err != nil {
t.Fatalf("Encountered unexpected error :%v", err)
}
keys := cfg.SSH_Authorized_Keys
if len(keys) != 2 {
t.Error("Parsed incorrect number of SSH keys")
} else if keys[0] != "foobar" {
t.Error("Expected first SSH key to be 'foobar'")
} else if keys[1] != "foobaz" {
t.Error("Expected first SSH key to be 'foobaz'")
}
if cfg.Coreos.Etcd.Discovery_URL != "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877" {
t.Error("Failed to parse etcd discovery url")
}
if !cfg.Coreos.Fleet.Autostart {
t.Error("Expected AutostartFleet to be true")
}
}
// Assert that our interface conversion doesn't panic
func TestCloudConfigKeysNotList(t *testing.T) {
contents := []byte(`
ssh_authorized_keys:
- foo: bar
`)
cfg, err := NewCloudConfig(contents)
if err != nil {
t.Fatalf("Encountered unexpected error :%v", err)
}
keys := cfg.SSH_Authorized_Keys
if len(keys) != 0 {
t.Error("Parsed incorrect number of SSH keys")
}
}

25
cloudinit/etcd.go Normal file
View File

@@ -0,0 +1,25 @@
package cloudinit
import (
"io/ioutil"
"log"
"os"
"path"
)
const (
etcdDiscoveryPath = "/var/run/etcd/bootstrap.disco"
)
func PersistEtcdDiscoveryURL(url string) error {
dir := path.Dir(etcdDiscoveryPath)
if _, err := os.Stat(dir); err != nil {
log.Printf("Creating directory /var/run/etcd")
err := os.MkdirAll(dir, os.FileMode(0644))
if err != nil {
return err
}
}
return ioutil.WriteFile(etcdDiscoveryPath, []byte(url), os.FileMode(0644))
}

View File

@@ -0,0 +1,31 @@
package cloudinit
import (
"io/ioutil"
"net/http"
)
type metadataService struct {
client http.Client
}
func NewMetadataService() *metadataService {
return &metadataService{http.Client{}}
}
func (ms *metadataService) UserData() ([]byte, error) {
resp, err := ms.client.Get("http://169.254.169.254/2012-01-12/user-data")
if err != nil {
return []byte{}, err
}
defer resp.Body.Close()
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return respBytes, nil
}

59
cloudinit/ssh_key.go Normal file
View File

@@ -0,0 +1,59 @@
package cloudinit
import (
"fmt"
"io"
"io/ioutil"
"os/exec"
"strings"
)
// Add the provide SSH public key to the core user's list of
// authorized keys
func AuthorizeSSHKeys(keys []string) error {
for i, key := range keys {
keys[i] = strings.TrimSpace(key)
}
// join all keys with newlines, ensuring the resulting string
// also ends with a newline
joined := fmt.Sprintf("%s\n", strings.Join(keys, "\n"))
cmd := exec.Command("update-ssh-keys", "-u", "core", "-a", "coreos-init")
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return err
}
err = cmd.Start()
if err != nil {
stdin.Close()
return err
}
_, err = io.WriteString(stdin, joined)
if err != nil {
return err
}
stdin.Close()
stdoutBytes, _ := ioutil.ReadAll(stdout)
stderrBytes, _ := ioutil.ReadAll(stderr)
err = cmd.Wait()
if err != nil {
return fmt.Errorf("Call to update-ssh-keys failed with %v: %s %s", err, string(stdoutBytes), string(stderrBytes))
}
return nil
}

41
cloudinit/systemd.go Normal file
View File

@@ -0,0 +1,41 @@
package cloudinit
import (
"fmt"
"log"
"path"
"github.com/coreos/go-systemd/dbus"
)
type Script []byte
func StartUnit(name string) error {
conn, err := dbus.New()
if err != nil {
return err
}
_, err = conn.StartUnit(name, "replace")
return err
}
func ExecuteScript(scriptPath string) error {
props := []dbus.Property{
dbus.PropDescription("Unit generated and executed by coreos-init on behalf of user"),
dbus.PropExecStart([]string{"/bin/bash", scriptPath}, false),
}
base := path.Base(scriptPath)
name := fmt.Sprintf("coreos-init-%s.service", base)
log.Printf("Creating transient systemd unit '%s'", name)
conn, err := dbus.New()
if err != nil {
return err
}
_, err = conn.StartTransientUnit(name, "replace", props...)
return err
}

30
cloudinit/user_data.go Normal file
View File

@@ -0,0 +1,30 @@
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)
}
}

61
cloudinit/workspace.go Normal file
View File

@@ -0,0 +1,61 @@
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, 0644)
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, 0644)
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
}