Skip to content

Commit

Permalink
Add windows userdata support
Browse files Browse the repository at this point in the history
* refactor cloudinit packages to include support for windows userdata
* fix NewMachineConfig to store corect paths when on windows
  • Loading branch information
gabriel-samfira committed Aug 10, 2014
1 parent 2c66731 commit 258c01e
Show file tree
Hide file tree
Showing 45 changed files with 1,952 additions and 360 deletions.
24 changes: 12 additions & 12 deletions agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import (
"github.com/juju/names"
"github.com/juju/utils"

"github.com/juju/juju/cloudinit"
"github.com/juju/juju/environmentserver/authentication"
"github.com/juju/juju/juju/paths"
"github.com/juju/juju/mongo"
"github.com/juju/juju/network"
"github.com/juju/juju/state/api"
Expand All @@ -32,16 +34,10 @@ var logger = loggo.GetLogger("juju.agent")

// logDir returns a filesystem path to the location where juju
// may create a folder containing its logs
//
// TODO(gsamfira) 2014-07-31 https://github.com/juju/juju/pull/189
// Use the target series to decide the path.
var logDir = "/var/log"
var logDir = paths.MustSucceed(paths.LogDir(version.Current.Series))

// dataDir returns the default data directory for this running system
//
// TODO(gsamfira) 2014-07-31 https://github.com/juju/juju/pull/189
// Use the target series to decide the path.
var dataDir = "/var/lib/juju"
var dataDir = paths.MustSucceed(paths.DataDir(version.Current.Series))

// DefaultLogDir defines the default log directory for juju agents.
// It's defined as a variable so it could be overridden in tests.
Expand Down Expand Up @@ -112,7 +108,7 @@ type Config interface {
// WriteCommands returns shell commands to write the agent configuration.
// It returns an error if the configuration does not have all the right
// elements.
WriteCommands() ([]string, error)
WriteCommands(series string) ([]string, error)

// StateServingInfo returns the details needed to run
// a state server and reports whether those details
Expand Down Expand Up @@ -627,13 +623,17 @@ func (c *configInternal) fileContents() ([]byte, error) {
return buf.Bytes(), nil
}

func (c *configInternal) WriteCommands() ([]string, error) {
func (c *configInternal) WriteCommands(series string) ([]string, error) {
renderer, err := cloudinit.NewRenderer(series)
if err != nil {
return nil, err
}
data, err := c.fileContents()
if err != nil {
return nil, err
}
commands := []string{"mkdir -p " + utils.ShQuote(c.Dir())}
commands = append(commands, writeFileCommands(c.File(agentConfigFilename), data, 0600)...)
commands := renderer.Mkdir(c.Dir())
commands = append(commands, renderer.WriteFile(c.File(agentConfigFilename), string(data), 0600)...)
return commands, nil
}

Expand Down
15 changes: 14 additions & 1 deletion agent/format_whitebox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package agent

import (
"fmt"
"os"
"path/filepath"

Expand Down Expand Up @@ -47,14 +48,26 @@ func newTestConfig(c *gc.C) *configInternal {

func (*formatSuite) TestWriteCommands(c *gc.C) {
config := newTestConfig(c)
commands, err := config.WriteCommands()
commands, err := config.WriteCommands("quantal")
c.Assert(err, gc.IsNil)
c.Assert(commands, gc.HasLen, 3)
c.Assert(commands[0], gc.Matches, `mkdir -p '\S+/agents/machine-1'`)
c.Assert(commands[1], gc.Matches, `install -m 600 /dev/null '\S+/agents/machine-1/agent.conf'`)
c.Assert(commands[2], gc.Matches, `printf '%s\\n' '(.|\n)*' > '\S+/agents/machine-1/agent.conf'`)
}

func (*formatSuite) TestWindowsWriteCommands(c *gc.C) {
config := newTestConfig(c)
commands, err := config.WriteCommands("win8")
c.Assert(err, gc.IsNil)
c.Assert(commands, gc.HasLen, 2)
fmt.Println(config)
c.Assert(commands[0], gc.Matches, `mkdir \S+\\agents\\machine-1`)
c.Assert(commands[1], gc.Matches, `Set-Content '\S+/agents/machine-1/agent.conf' @"
(.|\n)*
"@`)
}

func (*formatSuite) TestWriteAgentConfig(c *gc.C) {
config := newTestConfig(c)
err := config.Write()
Expand Down
11 changes: 0 additions & 11 deletions cloudinit/cloudinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ package cloudinit
import (
"bytes"
"text/template"

"gopkg.in/yaml.v1"
)

// Config represents a set of cloud-init configuration options.
Expand All @@ -23,15 +21,6 @@ func New() *Config {
return &Config{make(map[string]interface{})}
}

// Render returns the cloud-init configuration as a YAML file.
func (cfg *Config) Render() ([]byte, error) {
data, err := yaml.Marshal(cfg.attrs)
if err != nil {
return nil, err
}
return append([]byte("#cloud-config\n"), data...), nil
}

func (cfg *Config) set(opt string, yes bool, value interface{}) {
if yes {
cfg.attrs[opt] = value
Expand Down
116 changes: 114 additions & 2 deletions cloudinit/cloudinit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package cloudinit_test

import (
"fmt"
"path"
"testing"

gc "launchpad.net/gocheck"
Expand Down Expand Up @@ -295,7 +296,9 @@ func (S) TestOutput(c *gc.C) {
for _, t := range ctests {
cfg := cloudinit.New()
t.setOption(cfg)
data, err := cfg.Render()
renderer, err := cloudinit.NewRenderer("quantal")
c.Assert(err, gc.IsNil)
data, err := renderer.Render(cfg)
c.Assert(err, gc.IsNil)
c.Assert(data, gc.NotNil)
c.Assert(string(data), gc.Equals, header+t.expect, gc.Commentf("test %q output differs", t.name))
Expand Down Expand Up @@ -364,10 +367,119 @@ func ExampleConfig() {
cfg := cloudinit.New()
cfg.AddPackage("juju")
cfg.AddPackage("ubuntu")
data, err := cfg.Render()
renderer, err := cloudinit.NewRenderer("quantal")
if err != nil {
fmt.Printf("render error: %v", err)
return
}
data, err := renderer.Render(cfg)
if err != nil {
fmt.Printf("render error: %v", err)
return
}
fmt.Printf("%s", data)
}

func (S) TestUbuntuMkdir(c *gc.C) {
compareOutput := "mkdir -p 'fake_dir'"
render, err := cloudinit.NewRenderer("precise")
c.Assert(err, gc.IsNil)
output := render.Mkdir("fake_dir")
c.Assert(err, gc.IsNil)
c.Assert(output, gc.NotNil)
c.Assert(output[0], gc.Equals, compareOutput, gc.Commentf("test %q output differs", "windows mkdir"))
}

func (S) TestUbuntuWriteFile(c *gc.C) {
filePath := path.Join("fake_dir", "test_file")
compareOutput := "install -m 17141 /dev/null 'fake_dir/test_file'"

render, err := cloudinit.NewRenderer("precise")
c.Assert(err, gc.IsNil)
output := render.WriteFile(filePath, "fake output", 7777)
c.Assert(err, gc.IsNil)
c.Assert(output, gc.NotNil)
c.Assert(output[0], gc.Equals, compareOutput, gc.Commentf("test %q output differs", "windows writefile"))
}

func (S) TestUbuntuFromSlash(c *gc.C) {
filePath := path.Join("tmp/file\\path//value\\")
compareOutput := "tmp/file\\path/value\\"

render, err := cloudinit.NewRenderer("precise")
c.Assert(err, gc.IsNil)
output := render.FromSlash(filePath)
c.Assert(err, gc.IsNil)
c.Assert(output, gc.NotNil)
c.Assert(output, gc.Equals, compareOutput, gc.Commentf("test %q output differs", "windows fromslash"))
}

func (S) TestUbuntuPathJoin(c *gc.C) {
dirPath := path.Join("fake", "dir")
compareOutput := "fake/dir/fakeFile"

render, err := cloudinit.NewRenderer("precise")
c.Assert(err, gc.IsNil)
output := render.PathJoin(dirPath, "fakeFile")
c.Assert(err, gc.IsNil)
c.Assert(output, gc.NotNil)
c.Assert(output, gc.Equals, compareOutput, gc.Commentf("test %q output differs", "windows writefile"))
}

func (S) TestWindowsRender(c *gc.C) {
compareOutput := "#ps1_sysnative\r\n\r\npowershell"
cfg := cloudinit.New()
cfg.AddRunCmd("powershell")
render, err := cloudinit.NewRenderer("win8")
c.Assert(err, gc.IsNil)
data, err := render.Render(cfg)
c.Assert(err, gc.IsNil)
c.Assert(data, gc.NotNil)
c.Assert(string(data), gc.Equals, compareOutput, gc.Commentf("test %q output differs", "windows renderer"))
}

func (S) TestWindowsMkdir(c *gc.C) {
render, err := cloudinit.NewRenderer("win8")
compareOutput := "mkdir fake_dir"
c.Assert(err, gc.IsNil)
output := render.Mkdir("fake_dir")
c.Assert(err, gc.IsNil)
c.Assert(output, gc.NotNil)
c.Assert(output[0], gc.Equals, compareOutput, gc.Commentf("test %q output differs", "windows mkdir"))
}

func (S) TestWindowsWriteFile(c *gc.C) {
filePath := path.Join("fake_dir", "test_file")
compareOutput := "Set-Content '" + filePath + "' @\"\nfake output\n\"@"

render, err := cloudinit.NewRenderer("win8")
c.Assert(err, gc.IsNil)
output := render.WriteFile(filePath, "fake output", 7777)
c.Assert(err, gc.IsNil)
c.Assert(output, gc.NotNil)
c.Assert(output[0], gc.Equals, compareOutput, gc.Commentf("test %q output differs", "windows writefile"))
}

func (S) TestWindowsFromSlash(c *gc.C) {
filePath := path.Join("fake/file\\path//value\\/\\")
compareOutput := "fake\\file\\path\\value\\\\\\"

render, err := cloudinit.NewRenderer("win8")
c.Assert(err, gc.IsNil)
output := render.FromSlash(filePath)
c.Assert(err, gc.IsNil)
c.Assert(output, gc.NotNil)
c.Assert(output, gc.Equals, compareOutput, gc.Commentf("test %q output differs", "windows fromslash"))
}

func (S) TestWindowsPathJoin(c *gc.C) {
dirPath := path.Join("fake", "dir")
compareOutput := "fake\\dir\\fakeFile"

render, err := cloudinit.NewRenderer("win8")
c.Assert(err, gc.IsNil)
output := render.PathJoin(render.FromSlash(dirPath), "fakeFile")
c.Assert(err, gc.IsNil)
c.Assert(output, gc.NotNil)
c.Assert(output, gc.Equals, compareOutput, gc.Commentf("test %q output differs", "windows writefile"))
}
46 changes: 46 additions & 0 deletions cloudinit/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cloudinit

import (
"github.com/juju/errors"

"github.com/juju/juju/version"
)

type Renderer interface {

// Mkdir returns an OS specific script for creating a directory
Mkdir(path string) []string

// WriteFile returns a command to write data
WriteFile(filename string, contents string, permission int) []string

// Render renders the userdata script for a particular OS type
Render(conf *Config) ([]byte, error)

// FromSlash returns the result of replacing each slash ('/') character
// in path with a separator character. Multiple slashes are replaced by
// multiple separators.

FromSlash(path string) string
// PathJoin will join a path using OS specific path separator.
// This works for selected OS instead of current OS

PathJoin(path ...string) string
}

// NewRenderer returns a Renderer interface for selected series
func NewRenderer(series string) (Renderer, error) {
operatingSystem, err := version.GetOSFromSeries(series)
if err != nil {
return nil, err
}

switch operatingSystem {
case version.Windows:
return &WindowsRenderer{}, nil
case version.Ubuntu:
return &UbuntuRenderer{}, nil
default:
return nil, errors.Errorf("No renderer could be found for %s", series)
}
}
81 changes: 81 additions & 0 deletions cloudinit/renderers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package cloudinit

import (
"fmt"
"path"
"strings"

"github.com/juju/utils"
"gopkg.in/yaml.v1"
)

// UbuntuRenderer represents an Ubuntu specific script render
// type that is responsible for this particular OS. It implements
// the Renderer interface
type UbuntuRenderer struct{}

func (w *UbuntuRenderer) Mkdir(path string) []string {
return []string{fmt.Sprintf(`mkdir -p %s`, utils.ShQuote(path))}
}

func (w *UbuntuRenderer) WriteFile(filename string, contents string, permission int) []string {
quotedFilename := utils.ShQuote(filename)
quotedContents := utils.ShQuote(contents)
return []string{
fmt.Sprintf("install -m %o /dev/null %s", permission, quotedFilename),
fmt.Sprintf(`printf '%%s\n' %s > %s`, quotedContents, quotedFilename),
}
}

func (w *UbuntuRenderer) FromSlash(filepath string) string {
return filepath
}

func (w *UbuntuRenderer) PathJoin(filepath ...string) string {
return path.Join(filepath...)
}

func (w *UbuntuRenderer) Render(conf *Config) ([]byte, error) {
data, err := yaml.Marshal(conf.attrs)
if err != nil {
return nil, err
}
return append([]byte("#cloud-config\n"), data...), nil
}

// WindowsRenderer represents a Windows specific script render
// type that is responsible for this particular OS. It implements
// the Renderer interface
type WindowsRenderer struct{}

func (w *WindowsRenderer) Mkdir(path string) []string {
return []string{fmt.Sprintf(`mkdir %s`, w.FromSlash(path))}
}

func (w *WindowsRenderer) WriteFile(filename string, contents string, permission int) []string {
return []string{
fmt.Sprintf("Set-Content '%s' @\"\n%s\n\"@", filename, contents),
}
}

func (w *WindowsRenderer) PathJoin(filepath ...string) string {
return strings.Join(filepath, `\`)
}

func (w *WindowsRenderer) FromSlash(path string) string {
return strings.Replace(path, "/", `\`, -1)
}

func (w *WindowsRenderer) Render(conf *Config) ([]byte, error) {
winCmds := conf.attrs["runcmd"]
var script []byte
newline := "\r\n"
header := "#ps1_sysnative\r\n"
script = append(script, header...)
for _, value := range winCmds.([]*command) {
script = append(script, newline...)
script = append(script, value.literal...)

}
return script, nil
}
Loading

0 comments on commit 258c01e

Please sign in to comment.