cord-776 create build / runtime containers for autmation uservices
Change-Id: I246973192adef56a250ffe93a5f65fff488840c1
diff --git a/automation/vendor/github.com/juju/utils/LICENSE b/automation/vendor/github.com/juju/utils/LICENSE
new file mode 100644
index 0000000..ade9307
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/LICENSE
@@ -0,0 +1,191 @@
+All files in this repository are licensed as follows. If you contribute
+to this repository, it is assumed that you license your contribution
+under the same license unless you state otherwise.
+
+All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file.
+
+This software is licensed under the LGPLv3, included below.
+
+As a special exception to the GNU Lesser General Public License version 3
+("LGPL3"), the copyright holders of this Library give you permission to
+convey to a third party a Combined Work that links statically or dynamically
+to this Library without providing any Minimal Corresponding Source or
+Minimal Application Code as set out in 4d or providing the installation
+information set out in section 4e, provided that you comply with the other
+provisions of LGPL3 and provided that you meet, for the Application the
+terms and conditions of the license(s) which apply to the Application.
+
+Except as stated in this special exception, the provisions of LGPL3 will
+continue to comply in full to this Library. If you modify this Library, you
+may apply this exception to your version of this Library, but you are not
+obliged to do so. If you do not wish to do so, delete this exception
+statement from your version. This exception does not (and cannot) modify any
+license terms which apply to the Application, with which you must still
+comply.
+
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/automation/vendor/github.com/juju/utils/LICENSE.golang b/automation/vendor/github.com/juju/utils/LICENSE.golang
new file mode 100644
index 0000000..953076d
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/LICENSE.golang
@@ -0,0 +1,32 @@
+This licence applies to the following files:
+
+* filepath/stdlib.go
+* filepath/stdlibmatch.go
+
+Copyright (c) 2010 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/automation/vendor/github.com/juju/utils/Makefile b/automation/vendor/github.com/juju/utils/Makefile
new file mode 100644
index 0000000..9c69f32
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/Makefile
@@ -0,0 +1,10 @@
+PROJECT := github.com/juju/utils
+
+check-licence:
+ @(fgrep -rl "Licensed under the LGPLv3" .;\
+ fgrep -rl "MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT" .;\
+ find . -name "*.go") | sed -e 's,\./,,' | sort | uniq -u | \
+ xargs -I {} echo FAIL: licence missed: {}
+
+check: check-licence
+ go test $(PROJECT)/...
\ No newline at end of file
diff --git a/automation/vendor/github.com/juju/utils/README.md b/automation/vendor/github.com/juju/utils/README.md
new file mode 100644
index 0000000..ef08948
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/README.md
@@ -0,0 +1,4 @@
+juju/utils
+============
+
+This package provides general utility packages and functions.
diff --git a/automation/vendor/github.com/juju/utils/attempt.go b/automation/vendor/github.com/juju/utils/attempt.go
new file mode 100644
index 0000000..3becab2
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/attempt.go
@@ -0,0 +1,80 @@
+// Copyright 2011, 2012, 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "time"
+)
+
+// The Attempt and AttemptStrategy types are copied from those in launchpad.net/goamz/aws.
+
+// AttemptStrategy represents a strategy for waiting for an action
+// to complete successfully.
+type AttemptStrategy struct {
+ Total time.Duration // total duration of attempt.
+ Delay time.Duration // interval between each try in the burst.
+ Min int // minimum number of retries; overrides Total
+}
+
+type Attempt struct {
+ strategy AttemptStrategy
+ last time.Time
+ end time.Time
+ force bool
+ count int
+}
+
+// Start begins a new sequence of attempts for the given strategy.
+func (s AttemptStrategy) Start() *Attempt {
+ now := time.Now()
+ return &Attempt{
+ strategy: s,
+ last: now,
+ end: now.Add(s.Total),
+ force: true,
+ }
+}
+
+// Next waits until it is time to perform the next attempt or returns
+// false if it is time to stop trying.
+// It always returns true the first time it is called - we are guaranteed to
+// make at least one attempt.
+func (a *Attempt) Next() bool {
+ now := time.Now()
+ sleep := a.nextSleep(now)
+ if !a.force && !now.Add(sleep).Before(a.end) && a.strategy.Min <= a.count {
+ return false
+ }
+ a.force = false
+ if sleep > 0 && a.count > 0 {
+ time.Sleep(sleep)
+ now = time.Now()
+ }
+ a.count++
+ a.last = now
+ return true
+}
+
+func (a *Attempt) nextSleep(now time.Time) time.Duration {
+ sleep := a.strategy.Delay - now.Sub(a.last)
+ if sleep < 0 {
+ return 0
+ }
+ return sleep
+}
+
+// HasNext returns whether another attempt will be made if the current
+// one fails. If it returns true, the following call to Next is
+// guaranteed to return true.
+func (a *Attempt) HasNext() bool {
+ if a.force || a.strategy.Min > a.count {
+ return true
+ }
+ now := time.Now()
+ if now.Add(a.nextSleep(now)).Before(a.end) {
+ a.force = true
+ return true
+ }
+ return false
+}
diff --git a/automation/vendor/github.com/juju/utils/clock/clock.go b/automation/vendor/github.com/juju/utils/clock/clock.go
new file mode 100644
index 0000000..59a511d
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/clock/clock.go
@@ -0,0 +1,53 @@
+// Copyright 2015 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package clock
+
+import "time"
+
+// Clock provides an interface for dealing with clocks.
+type Clock interface {
+ // Now returns the current clock time.
+ Now() time.Time
+
+ // After waits for the duration to elapse and then sends the
+ // current time on the returned channel.
+ After(time.Duration) <-chan time.Time
+
+ // AfterFunc waits for the duration to elapse and then calls f in its own goroutine.
+ // It returns a Timer that can be used to cancel the call using its Stop method.
+ AfterFunc(d time.Duration, f func()) Timer
+
+ // NewTimer creates a new Timer that will send the current time
+ // on its channel after at least duration d.
+ NewTimer(d time.Duration) Timer
+}
+
+// Alarm returns a channel that will have the time sent on it at some point
+// after the supplied time occurs.
+//
+// This is short for c.After(t.Sub(c.Now())).
+func Alarm(c Clock, t time.Time) <-chan time.Time {
+ return c.After(t.Sub(c.Now()))
+}
+
+// The Timer type represents a single event.
+// A Timer must be created with AfterFunc.
+// This interface follows time.Timer's methods but provides easier mocking.
+type Timer interface {
+ // When the Timer expires, the current time will be sent on the
+ // channel returned from Chan, unless the Timer was created by
+ // AfterFunc.
+ Chan() <-chan time.Time
+
+ // Reset changes the timer to expire after duration d.
+ // It returns true if the timer had been active, false if
+ // the timer had expired or been stopped.
+ Reset(time.Duration) bool
+
+ // Stop prevents the Timer from firing. It returns true if
+ // the call stops the timer, false if the timer has already expired or been stopped.
+ // Stop does not close the channel, to prevent a read
+ // from the channel succeeding incorrectly.
+ Stop() bool
+}
diff --git a/automation/vendor/github.com/juju/utils/clock/wall.go b/automation/vendor/github.com/juju/utils/clock/wall.go
new file mode 100644
index 0000000..9bfc351
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/clock/wall.go
@@ -0,0 +1,47 @@
+// Copyright 2015 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package clock
+
+import (
+ "time"
+)
+
+// WallClock exposes wall-clock time via the Clock interface.
+var WallClock wallClock
+
+// ensure that WallClock does actually implement the Clock interface.
+var _ Clock = WallClock
+
+// WallClock exposes wall-clock time as returned by time.Now.
+type wallClock struct{}
+
+// Now is part of the Clock interface.
+func (wallClock) Now() time.Time {
+ return time.Now()
+}
+
+// After implements Clock.After.
+func (wallClock) After(d time.Duration) <-chan time.Time {
+ return time.After(d)
+}
+
+// AfterFunc implements Clock.AfterFunc.
+func (wallClock) AfterFunc(d time.Duration, f func()) Timer {
+ return wallTimer{time.AfterFunc(d, f)}
+}
+
+// NewTimer implements Clock.NewTimer.
+func (wallClock) NewTimer(d time.Duration) Timer {
+ return wallTimer{time.NewTimer(d)}
+}
+
+// wallTimer implements the Timer interface.
+type wallTimer struct {
+ *time.Timer
+}
+
+// Chan implements Timer.Chan.
+func (t wallTimer) Chan() <-chan time.Time {
+ return t.C
+}
diff --git a/automation/vendor/github.com/juju/utils/command.go b/automation/vendor/github.com/juju/utils/command.go
new file mode 100644
index 0000000..4bd51cd
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/command.go
@@ -0,0 +1,19 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "os/exec"
+)
+
+// RunCommand executes the command and return the combined output.
+func RunCommand(command string, args ...string) (output string, err error) {
+ cmd := exec.Command(command, args...)
+ out, err := cmd.CombinedOutput()
+ output = string(out)
+ if err != nil {
+ return output, err
+ }
+ return output, nil
+}
diff --git a/automation/vendor/github.com/juju/utils/dependencies.tsv b/automation/vendor/github.com/juju/utils/dependencies.tsv
new file mode 100644
index 0000000..76d098e
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/dependencies.tsv
@@ -0,0 +1,24 @@
+github.com/juju/cmd git 7c57a7d5a20602e4563a83f2d530283ca0e6f481 2016-08-10T12:53:08Z
+github.com/juju/errors git 1b5e39b83d1835fa480e0c2ddefb040ee82d58b3 2015-09-16T12:56:42Z
+github.com/juju/gnuflag git 4e76c56581859c14d9d87e1ddbe29e1c0f10195f 2016-08-09T16:52:14Z
+github.com/juju/httpprof git 14bf14c307672fd2456bdbf35d19cf0ccd3cf565 2014-12-17T16:00:36Z
+github.com/juju/httprequest git 89d547093c45e293599088cc63e805c6f1205dc0 2016-03-02T10:09:58Z
+github.com/juju/loggo git 15901ae4de786d05edae84a27c93d3fbef66c91e 2016-08-04T22:15:26Z
+github.com/juju/retry git 62c62032529169c7ec02fa48f93349604c345e1f 2015-10-29T02:48:21Z
+github.com/juju/testing git 7177264a582e2c00d08277eaa91d88f8eb0fd869 2016-09-26T12:59:16Z
+github.com/juju/version git 4ae6172c00626779a5a462c3e3d22fc0e889431a 2016-06-03T19:49:58Z
+github.com/julienschmidt/httprouter git 77a895ad01ebc98a4dc95d8355bc825ce80a56f6 2015-10-13T22:55:20Z
+github.com/masterzen/azure-sdk-for-go git ee4f0065d00cd12b542f18f5bc45799e88163b12 2016-07-20T05:16:58Z
+github.com/masterzen/simplexml git 4572e39b1ab9fe03ee513ce6fc7e289e98482190 2016-06-08T18:30:07Z
+github.com/masterzen/winrm git 7a535cd943fccaeed196718896beec3fb51aff41 2016-08-04T09:38:27Z
+github.com/masterzen/xmlpath git 13f4951698adc0fa9c1dda3e275d489a24201161 2014-02-18T18:59:01Z
+github.com/nu7hatch/gouuid git 179d4d0c4d8d407a32af483c2354df1d2c91e6c3 2016-02-18t18:59:01Z
+golang.org/x/crypto git aedad9a179ec1ea11b7064c57cbc6dc30d7724ec 2015-08-30T18:06:42Z
+golang.org/x/net git ea47fc708ee3e20177f3ca3716217c4ab75942cb 2015-08-29T23:03:18Z
+golang.org/x/text git b01949dc0793a9af5e4cb3fce4d42999e76e8ca1 2016-05-25T23:07:23Z
+gopkg.in/check.v1 git 4f90aeace3a26ad7021961c297b22c42160c7b25 2016-01-05T16:49:36Z
+gopkg.in/errgo.v1 git 66cb46252b94c1f3d65646f54ee8043ab38d766c 2015-10-07T15:31:57Z
+gopkg.in/juju/names.v2 git e38bc90539f22af61a9c656d35068bd5f0a5b30a 2016-05-25T23:07:23Z
+gopkg.in/mgo.v2 git 4d04138ffef2791c479c0c8bbffc30b34081b8d9 2015-10-26T16:34:53Z
+gopkg.in/tomb.v1 git dd632973f1e7218eb1089048e0798ec9ae7dceb8 2014-10-24T13:56:13Z
+gopkg.in/yaml.v2 git a83829b6f1293c91addabc89d0571c246397bbf4 2016-03-01T20:40:22Z
diff --git a/automation/vendor/github.com/juju/utils/file.go b/automation/vendor/github.com/juju/utils/file.go
new file mode 100644
index 0000000..75fe5a6
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/file.go
@@ -0,0 +1,149 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path"
+ "path/filepath"
+ "regexp"
+)
+
+// UserHomeDir returns the home directory for the specified user, or the
+// home directory for the current user if the specified user is empty.
+func UserHomeDir(userName string) (hDir string, err error) {
+ if userName == "" {
+ // TODO (wallyworld) - fix tests on Windows
+ // Ordinarily, we'd always use user.Current() to get the current user
+ // and then get the HomeDir from that. But our tests rely on poking
+ // a value into $HOME in order to override the normal home dir for the
+ // current user. So we're forced to use Home() to make the tests pass.
+ // All of our tests currently construct paths with the default user in
+ // mind eg "~/foo".
+ return Home(), nil
+ }
+ hDir, err = homeDir(userName)
+ if err != nil {
+ return "", err
+ }
+ return hDir, nil
+}
+
+// Only match paths starting with ~ (~user/test, ~/test). This will prevent
+// accidental expansion on Windows when short form paths are present (C:\users\ADMINI~1\test)
+var userHomePathRegexp = regexp.MustCompile("(^~(?P<user>[^/]*))(?P<path>.*)")
+
+// NormalizePath expands a path containing ~ to its absolute form,
+// and removes any .. or . path elements.
+func NormalizePath(dir string) (string, error) {
+ if userHomePathRegexp.MatchString(dir) {
+ user := userHomePathRegexp.ReplaceAllString(dir, "$user")
+ userHomeDir, err := UserHomeDir(user)
+ if err != nil {
+ return "", err
+ }
+ dir = userHomePathRegexp.ReplaceAllString(dir, fmt.Sprintf("%s$path", userHomeDir))
+ }
+ return filepath.Clean(dir), nil
+}
+
+// EnsureBaseDir ensures that path is always prefixed by baseDir,
+// allowing for the fact that path might have a Window drive letter in
+// it.
+func EnsureBaseDir(baseDir, path string) string {
+ if baseDir == "" {
+ return path
+ }
+ volume := filepath.VolumeName(path)
+ return filepath.Join(baseDir, path[len(volume):])
+}
+
+// JoinServerPath joins any number of path elements into a single path, adding
+// a path separator (based on the current juju server OS) if necessary. The
+// result is Cleaned; in particular, all empty strings are ignored.
+func JoinServerPath(elem ...string) string {
+ return path.Join(elem...)
+}
+
+// UniqueDirectory returns "path/name" if that directory doesn't exist. If it
+// does, the method starts appending .1, .2, etc until a unique name is found.
+func UniqueDirectory(path, name string) (string, error) {
+ dir := filepath.Join(path, name)
+ _, err := os.Stat(dir)
+ if os.IsNotExist(err) {
+ return dir, nil
+ }
+ for i := 1; ; i++ {
+ dir := filepath.Join(path, fmt.Sprintf("%s.%d", name, i))
+ _, err := os.Stat(dir)
+ if os.IsNotExist(err) {
+ return dir, nil
+ } else if err != nil {
+ return "", err
+ }
+ }
+}
+
+// CopyFile writes the contents of the given source file to dest.
+func CopyFile(dest, source string) error {
+ df, err := os.Create(dest)
+ if err != nil {
+ return err
+ }
+ f, err := os.Open(source)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ _, err = io.Copy(df, f)
+ return err
+}
+
+// AtomicWriteFileAndChange atomically writes the filename with the
+// given contents and calls the given function after the contents were
+// written, but before the file is renamed.
+func AtomicWriteFileAndChange(filename string, contents []byte, change func(*os.File) error) (err error) {
+ dir, file := filepath.Split(filename)
+ f, err := ioutil.TempFile(dir, file)
+ if err != nil {
+ return fmt.Errorf("cannot create temp file: %v", err)
+ }
+ defer f.Close()
+ defer func() {
+ if err != nil {
+ // Don't leave the temp file lying around on error.
+ // Close the file before removing. Trying to remove an open file on
+ // Windows will fail.
+ f.Close()
+ os.Remove(f.Name())
+ }
+ }()
+ if _, err := f.Write(contents); err != nil {
+ return fmt.Errorf("cannot write %q contents: %v", filename, err)
+ }
+ if err := change(f); err != nil {
+ return err
+ }
+ f.Close()
+ if err := ReplaceFile(f.Name(), filename); err != nil {
+ return fmt.Errorf("cannot replace %q with %q: %v", f.Name(), filename, err)
+ }
+ return nil
+}
+
+// AtomicWriteFile atomically writes the filename with the given
+// contents and permissions, replacing any existing file at the same
+// path.
+func AtomicWriteFile(filename string, contents []byte, perms os.FileMode) (err error) {
+ return AtomicWriteFileAndChange(filename, contents, func(f *os.File) error {
+ // FileMod.Chmod() is not implemented on Windows, however, os.Chmod() is
+ if err := os.Chmod(f.Name(), perms); err != nil {
+ return fmt.Errorf("cannot set permissions: %v", err)
+ }
+ return nil
+ })
+}
diff --git a/automation/vendor/github.com/juju/utils/file_unix.go b/automation/vendor/github.com/juju/utils/file_unix.go
new file mode 100644
index 0000000..4380290
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/file_unix.go
@@ -0,0 +1,75 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+// +build !windows
+
+package utils
+
+import (
+ "fmt"
+ "os"
+ "os/user"
+ "strconv"
+ "strings"
+
+ "github.com/juju/errors"
+)
+
+func homeDir(userName string) (string, error) {
+ u, err := user.Lookup(userName)
+ if err != nil {
+ return "", errors.NewUserNotFound(err, "no such user")
+ }
+ return u.HomeDir, nil
+}
+
+// MoveFile atomically moves the source file to the destination, returning
+// whether the file was moved successfully. If the destination already exists,
+// it returns an error rather than overwrite it.
+//
+// On unix systems, an error may occur with a successful move, if the source
+// file location cannot be unlinked.
+func MoveFile(source, destination string) (bool, error) {
+ err := os.Link(source, destination)
+ if err != nil {
+ return false, err
+ }
+ err = os.Remove(source)
+ if err != nil {
+ return true, err
+ }
+ return true, nil
+}
+
+// ReplaceFile atomically replaces the destination file or directory
+// with the source. The errors that are returned are identical to
+// those returned by os.Rename.
+func ReplaceFile(source, destination string) error {
+ return os.Rename(source, destination)
+}
+
+// MakeFileURL returns a file URL if a directory is passed in else it does nothing
+func MakeFileURL(in string) string {
+ if strings.HasPrefix(in, "/") {
+ return "file://" + in
+ }
+ return in
+}
+
+// ChownPath sets the uid and gid of path to match that of the user
+// specified.
+func ChownPath(path, username string) error {
+ u, err := user.Lookup(username)
+ if err != nil {
+ return fmt.Errorf("cannot lookup %q user id: %v", username, err)
+ }
+ uid, err := strconv.Atoi(u.Uid)
+ if err != nil {
+ return fmt.Errorf("invalid user id %q: %v", u.Uid, err)
+ }
+ gid, err := strconv.Atoi(u.Gid)
+ if err != nil {
+ return fmt.Errorf("invalid group id %q: %v", u.Gid, err)
+ }
+ return os.Chown(path, uid, gid)
+}
diff --git a/automation/vendor/github.com/juju/utils/file_windows.go b/automation/vendor/github.com/juju/utils/file_windows.go
new file mode 100644
index 0000000..9bb3936
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/file_windows.go
@@ -0,0 +1,142 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+// +build windows
+
+package utils
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "syscall"
+ "unsafe"
+
+ "github.com/juju/errors"
+)
+
+const (
+ movefile_replace_existing = 0x1
+ movefile_write_through = 0x8
+)
+
+//sys moveFileEx(lpExistingFileName *uint16, lpNewFileName *uint16, dwFlags uint32) (err error) = MoveFileExW
+
+// MoveFile atomically moves the source file to the destination, returning
+// whether the file was moved successfully. If the destination already exists,
+// it returns an error rather than overwrite it.
+func MoveFile(source, destination string) (bool, error) {
+ src, err := syscall.UTF16PtrFromString(source)
+ if err != nil {
+ return false, &os.LinkError{"move", source, destination, err}
+ }
+ dest, err := syscall.UTF16PtrFromString(destination)
+ if err != nil {
+ return false, &os.LinkError{"move", source, destination, err}
+ }
+
+ // see http://msdn.microsoft.com/en-us/library/windows/desktop/aa365240(v=vs.85).aspx
+ if err := moveFileEx(src, dest, movefile_write_through); err != nil {
+ return false, &os.LinkError{"move", source, destination, err}
+ }
+ return true, nil
+
+}
+
+// ReplaceFile atomically replaces the destination file or directory with the source.
+// The errors that are returned are identical to those returned by os.Rename.
+func ReplaceFile(source, destination string) error {
+ src, err := syscall.UTF16PtrFromString(source)
+ if err != nil {
+ return &os.LinkError{"replace", source, destination, err}
+ }
+ dest, err := syscall.UTF16PtrFromString(destination)
+ if err != nil {
+ return &os.LinkError{"replace", source, destination, err}
+ }
+
+ // see http://msdn.microsoft.com/en-us/library/windows/desktop/aa365240(v=vs.85).aspx
+ if err := moveFileEx(src, dest, movefile_replace_existing|movefile_write_through); err != nil {
+ return &os.LinkError{"replace", source, destination, err}
+ }
+ return nil
+}
+
+// MakeFileURL returns a proper file URL for the given path/directory
+func MakeFileURL(in string) string {
+ in = filepath.ToSlash(in)
+ // for windows at least should be <letter>: to be considered valid
+ // so we cant do anything with less than that.
+ if len(in) < 2 {
+ return in
+ }
+ if string(in[1]) != ":" {
+ return in
+ }
+ // since go 1.6 http client will only take this format.
+ return "file://" + in
+}
+
+func getUserSID(username string) (string, error) {
+ sid, _, _, e := syscall.LookupSID("", username)
+ if e != nil {
+ return "", e
+ }
+ sidStr, err := sid.String()
+ return sidStr, err
+}
+
+func readRegString(h syscall.Handle, key string) (value string, err error) {
+ var typ uint32
+ var buf uint32
+
+ // Get size of registry key
+ err = syscall.RegQueryValueEx(h, syscall.StringToUTF16Ptr(key), nil, &typ, nil, &buf)
+ if err != nil {
+ return value, err
+ }
+
+ n := make([]uint16, buf/2+1)
+ err = syscall.RegQueryValueEx(h, syscall.StringToUTF16Ptr(key), nil, &typ, (*byte)(unsafe.Pointer(&n[0])), &buf)
+ if err != nil {
+ return value, err
+ }
+ return syscall.UTF16ToString(n[:]), err
+}
+
+func homeFromRegistry(sid string) (string, error) {
+ var h syscall.Handle
+ // This key will exist on all platforms we support the agent on (windows server 2008 and above)
+ keyPath := fmt.Sprintf("Software\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\%s", sid)
+ err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE,
+ syscall.StringToUTF16Ptr(keyPath),
+ 0, syscall.KEY_READ, &h)
+ if err != nil {
+ return "", err
+ }
+ defer syscall.RegCloseKey(h)
+ str, err := readRegString(h, "ProfileImagePath")
+ if err != nil {
+ return "", err
+ }
+ return str, nil
+}
+
+// homeDir returns a local user home dir on Windows
+// user.Lookup() does not populate Gid and HomeDir on Windows,
+// so we get it from the registry
+func homeDir(user string) (string, error) {
+ u, err := getUserSID(user)
+ if err != nil {
+ return "", errors.NewUserNotFound(err, "no such user")
+ }
+ return homeFromRegistry(u)
+}
+
+// ChownPath is not implemented for Windows.
+func ChownPath(path, username string) error {
+ // This only exists to allow building on Windows. User lookup and
+ // file ownership needs to be handled in a completely different
+ // way and hasn't yet been implemented.
+ return nil
+}
diff --git a/automation/vendor/github.com/juju/utils/gomaxprocs.go b/automation/vendor/github.com/juju/utils/gomaxprocs.go
new file mode 100644
index 0000000..5977a86
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/gomaxprocs.go
@@ -0,0 +1,26 @@
+// Copyright 2014 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "os"
+ "runtime"
+)
+
+var gomaxprocs = runtime.GOMAXPROCS
+var numCPU = runtime.NumCPU
+
+// UseMultipleCPUs sets GOMAXPROCS to the number of CPU cores unless it has
+// already been overridden by the GOMAXPROCS environment variable.
+func UseMultipleCPUs() {
+ if envGOMAXPROCS := os.Getenv("GOMAXPROCS"); envGOMAXPROCS != "" {
+ n := gomaxprocs(0)
+ logger.Debugf("GOMAXPROCS already set in environment to %q, %d internally",
+ envGOMAXPROCS, n)
+ return
+ }
+ n := numCPU()
+ logger.Debugf("setting GOMAXPROCS to %d", n)
+ gomaxprocs(n)
+}
diff --git a/automation/vendor/github.com/juju/utils/home_unix.go b/automation/vendor/github.com/juju/utils/home_unix.go
new file mode 100644
index 0000000..6b450be
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/home_unix.go
@@ -0,0 +1,19 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+// +build !windows
+
+package utils
+
+import (
+ "os"
+)
+
+// Home returns the os-specific home path as specified in the environment.
+func Home() string {
+ return os.Getenv("HOME")
+}
+
+// SetHome sets the os-specific home path in the environment.
+func SetHome(s string) error {
+ return os.Setenv("HOME", s)
+}
diff --git a/automation/vendor/github.com/juju/utils/home_windows.go b/automation/vendor/github.com/juju/utils/home_windows.go
new file mode 100644
index 0000000..e61225c
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/home_windows.go
@@ -0,0 +1,25 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "os"
+ "path/filepath"
+)
+
+// Home returns the os-specific home path as specified in the environment.
+func Home() string {
+ return filepath.Join(os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH"))
+}
+
+// SetHome sets the os-specific home path in the environment.
+func SetHome(s string) error {
+ v := filepath.VolumeName(s)
+ if v != "" {
+ if err := os.Setenv("HOMEDRIVE", v); err != nil {
+ return err
+ }
+ }
+ return os.Setenv("HOMEPATH", s[len(v):])
+}
diff --git a/automation/vendor/github.com/juju/utils/http-1_4.go b/automation/vendor/github.com/juju/utils/http-1_4.go
new file mode 100644
index 0000000..87bd5b1
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/http-1_4.go
@@ -0,0 +1,24 @@
+// Copyright 2016 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+//+build !go1.7
+
+package utils
+
+import (
+ "fmt"
+ "net"
+ "net/http"
+)
+
+// installHTTPDialShim patches the default HTTP transport so
+// that it fails when an attempt is made to dial a non-local
+// host.
+func installHTTPDialShim(t *http.Transport) {
+ t.Dial = func(network, addr string) (net.Conn, error) {
+ if !OutgoingAccessAllowed && !isLocalAddr(addr) {
+ return nil, fmt.Errorf("access to address %q not allowed", addr)
+ }
+ return net.Dial(network, addr)
+ }
+}
diff --git a/automation/vendor/github.com/juju/utils/http-1_7.go b/automation/vendor/github.com/juju/utils/http-1_7.go
new file mode 100644
index 0000000..d676dcb
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/http-1_7.go
@@ -0,0 +1,35 @@
+// Copyright 2016 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+//+build go1.7
+
+package utils
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "net/http"
+ "time"
+)
+
+var ctxtDialer = &net.Dialer{
+ Timeout: 30 * time.Second,
+ KeepAlive: 30 * time.Second,
+}
+
+// installHTTPDialShim patches the default HTTP transport so
+// that it fails when an attempt is made to dial a non-local
+// host.
+//
+// Note that this is Go version dependent because in Go 1.7 and above,
+// the DialContext field was introduced (and set in http.DefaultTransport)
+// which overrides the Dial field.
+func installHTTPDialShim(t *http.Transport) {
+ t.DialContext = func(ctxt context.Context, network, addr string) (net.Conn, error) {
+ if !OutgoingAccessAllowed && !isLocalAddr(addr) {
+ return nil, fmt.Errorf("access to address %q not allowed", addr)
+ }
+ return ctxtDialer.DialContext(ctxt, network, addr)
+ }
+}
diff --git a/automation/vendor/github.com/juju/utils/http.go b/automation/vendor/github.com/juju/utils/http.go
new file mode 100644
index 0000000..2f5ddf3
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/http.go
@@ -0,0 +1,117 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "crypto/tls"
+ "encoding/base64"
+ "fmt"
+ "net"
+ "net/http"
+ "strings"
+ "sync"
+)
+
+var insecureClient = (*http.Client)(nil)
+var insecureClientMutex = sync.Mutex{}
+
+func init() {
+ defaultTransport := http.DefaultTransport.(*http.Transport)
+ installHTTPDialShim(defaultTransport)
+ registerFileProtocol(defaultTransport)
+}
+
+// registerFileProtocol registers support for file:// URLs on the given transport.
+func registerFileProtocol(transport *http.Transport) {
+ transport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
+}
+
+// SSLHostnameVerification is used as a switch for when a given provider might
+// use self-signed credentials and we should not try to verify the hostname on
+// the TLS/SSL certificates
+type SSLHostnameVerification bool
+
+const (
+ // VerifySSLHostnames ensures we verify the hostname on the certificate
+ // matches the host we are connecting and is signed
+ VerifySSLHostnames = SSLHostnameVerification(true)
+ // NoVerifySSLHostnames informs us to skip verifying the hostname
+ // matches a valid certificate
+ NoVerifySSLHostnames = SSLHostnameVerification(false)
+)
+
+// GetHTTPClient returns either a standard http client or
+// non validating client depending on the value of verify.
+func GetHTTPClient(verify SSLHostnameVerification) *http.Client {
+ if verify == VerifySSLHostnames {
+ return GetValidatingHTTPClient()
+ }
+ return GetNonValidatingHTTPClient()
+}
+
+// GetValidatingHTTPClient returns a new http.Client that
+// verifies the server's certificate chain and hostname.
+func GetValidatingHTTPClient() *http.Client {
+ return &http.Client{}
+}
+
+// GetNonValidatingHTTPClient returns a new http.Client that
+// does not verify the server's certificate chain and hostname.
+func GetNonValidatingHTTPClient() *http.Client {
+ return &http.Client{
+ Transport: NewHttpTLSTransport(&tls.Config{
+ InsecureSkipVerify: true,
+ }),
+ }
+}
+
+// BasicAuthHeader creates a header that contains just the "Authorization"
+// entry. The implementation was originally taked from net/http but this is
+// needed externally from the http request object in order to use this with
+// our websockets. See 2 (end of page 4) http://www.ietf.org/rfc/rfc2617.txt
+// "To receive authorization, the client sends the userid and password,
+// separated by a single colon (":") character, within a base64 encoded string
+// in the credentials."
+func BasicAuthHeader(username, password string) http.Header {
+ auth := username + ":" + password
+ encoded := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
+ return http.Header{
+ "Authorization": {encoded},
+ }
+}
+
+// ParseBasicAuth attempts to find an Authorization header in the supplied
+// http.Header and if found parses it as a Basic header. See 2 (end of page 4)
+// http://www.ietf.org/rfc/rfc2617.txt "To receive authorization, the client
+// sends the userid and password, separated by a single colon (":") character,
+// within a base64 encoded string in the credentials."
+func ParseBasicAuthHeader(h http.Header) (userid, password string, err error) {
+ parts := strings.Fields(h.Get("Authorization"))
+ if len(parts) != 2 || parts[0] != "Basic" {
+ return "", "", fmt.Errorf("invalid or missing HTTP auth header")
+ }
+ // Challenge is a base64-encoded "tag:pass" string.
+ // See RFC 2617, Section 2.
+ challenge, err := base64.StdEncoding.DecodeString(parts[1])
+ if err != nil {
+ return "", "", fmt.Errorf("invalid HTTP auth encoding")
+ }
+ tokens := strings.SplitN(string(challenge), ":", 2)
+ if len(tokens) != 2 {
+ return "", "", fmt.Errorf("invalid HTTP auth contents")
+ }
+ return tokens[0], tokens[1], nil
+}
+
+// OutgoingAccessAllowed determines whether connections other than
+// localhost can be dialled.
+var OutgoingAccessAllowed = true
+
+func isLocalAddr(addr string) bool {
+ host, _, err := net.SplitHostPort(addr)
+ if err != nil {
+ return false
+ }
+ return host == "localhost" || net.ParseIP(host).IsLoopback()
+}
diff --git a/automation/vendor/github.com/juju/utils/isubuntu.go b/automation/vendor/github.com/juju/utils/isubuntu.go
new file mode 100644
index 0000000..d85ed9a
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/isubuntu.go
@@ -0,0 +1,17 @@
+// Copyright 2011, 2012, 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "strings"
+)
+
+// IsUbuntu executes lxb_release to see if the host OS is Ubuntu.
+func IsUbuntu() bool {
+ out, err := RunCommand("lsb_release", "-i", "-s")
+ if err != nil {
+ return false
+ }
+ return strings.TrimSpace(out) == "Ubuntu"
+}
diff --git a/automation/vendor/github.com/juju/utils/limiter.go b/automation/vendor/github.com/juju/utils/limiter.go
new file mode 100644
index 0000000..60bd066
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/limiter.go
@@ -0,0 +1,59 @@
+// Copyright 2011, 2012, 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "fmt"
+)
+
+type empty struct{}
+type limiter chan empty
+
+// Limiter represents a limited resource (eg a semaphore).
+type Limiter interface {
+ // Acquire another unit of the resource.
+ // Acquire returns false to indicate there is no more availability,
+ // until another entity calls Release.
+ Acquire() bool
+ // AcquireWait requests a unit of resource, but blocks until one is
+ // available.
+ AcquireWait()
+ // Release returns a unit of the resource. Calling Release when there
+ // are no units Acquired is an error.
+ Release() error
+}
+
+func NewLimiter(max int) Limiter {
+ return make(limiter, max)
+}
+
+// Acquire requests some resources that you can return later
+// It returns 'true' if there are resources available, but false if they are
+// not. Callers are responsible for calling Release if this returns true, but
+// should not release if this returns false.
+func (l limiter) Acquire() bool {
+ e := empty{}
+ select {
+ case l <- e:
+ return true
+ default:
+ return false
+ }
+}
+
+// AcquireWait waits for the resource to become available before returning.
+func (l limiter) AcquireWait() {
+ e := empty{}
+ l <- e
+}
+
+// Release returns the resource to the available pool.
+func (l limiter) Release() error {
+ select {
+ case <-l:
+ return nil
+ default:
+ return fmt.Errorf("Release without an associated Acquire")
+ }
+}
diff --git a/automation/vendor/github.com/juju/utils/multireader.go b/automation/vendor/github.com/juju/utils/multireader.go
new file mode 100644
index 0000000..b8431f9
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/multireader.go
@@ -0,0 +1,189 @@
+// Copyright 2016 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "io"
+ "sort"
+
+ "github.com/juju/errors"
+)
+
+// SizeReaderAt combines io.ReaderAt with a Size method.
+type SizeReaderAt interface {
+ // Size returns the size of the data readable
+ // from the reader.
+ Size() int64
+ io.ReaderAt
+}
+
+// NewMultiReaderAt is like io.MultiReader but produces a ReaderAt
+// (and Size), instead of just a reader.
+//
+// Note: this implementation was taken from a talk given
+// by Brad Fitzpatrick as OSCON 2013.
+//
+// http://talks.golang.org/2013/oscon-dl.slide#49
+// https://github.com/golang/talks/blob/master/2013/oscon-dl/server-compose.go
+func NewMultiReaderAt(parts ...SizeReaderAt) SizeReaderAt {
+ m := &multiReaderAt{
+ parts: make([]offsetAndSource, 0, len(parts)),
+ }
+ var off int64
+ for _, p := range parts {
+ m.parts = append(m.parts, offsetAndSource{off, p})
+ off += p.Size()
+ }
+ m.size = off
+ return m
+}
+
+type offsetAndSource struct {
+ off int64
+ SizeReaderAt
+}
+
+type multiReaderAt struct {
+ parts []offsetAndSource
+ size int64
+}
+
+func (m *multiReaderAt) Size() int64 {
+ return m.size
+}
+
+func (m *multiReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
+ wantN := len(p)
+
+ // Skip past the requested offset.
+ skipParts := sort.Search(len(m.parts), func(i int) bool {
+ // This function returns whether parts[i] will
+ // contribute any bytes to our output.
+ part := m.parts[i]
+ return part.off+part.Size() > off
+ })
+ parts := m.parts[skipParts:]
+
+ // How far to skip in the first part.
+ needSkip := off
+ if len(parts) > 0 {
+ needSkip -= parts[0].off
+ }
+
+ for len(parts) > 0 && len(p) > 0 {
+ readP := p
+ partSize := parts[0].Size()
+ if int64(len(readP)) > partSize-needSkip {
+ readP = readP[:partSize-needSkip]
+ }
+ pn, err0 := parts[0].ReadAt(readP, needSkip)
+ if err0 != nil {
+ return n, err0
+ }
+ n += pn
+ p = p[pn:]
+ if int64(pn)+needSkip == partSize {
+ parts = parts[1:]
+ }
+ needSkip = 0
+ }
+
+ if n != wantN {
+ err = io.ErrUnexpectedEOF
+ }
+ return
+}
+
+// NewMultiReaderSeeker returns an io.ReadSeeker that combines
+// all the given readers into a single one. It assumes that
+// all the seekers are initially positioned at the start.
+func NewMultiReaderSeeker(readers ...io.ReadSeeker) io.ReadSeeker {
+ sreaders := make([]SizeReaderAt, len(readers))
+ for i, r := range readers {
+ r1, err := newSizeReaderAt(r)
+ if err != nil {
+ panic(err)
+ }
+ sreaders[i] = r1
+ }
+ return &readSeeker{
+ r: NewMultiReaderAt(sreaders...),
+ }
+}
+
+// newSizeReaderAt adapts an io.ReadSeeker to a SizeReaderAt.
+// Note that it doesn't strictly adhere to the ReaderAt
+// contract because it's not safe to call ReadAt concurrently.
+// This doesn't matter because io.ReadSeeker doesn't
+// need to be thread-safe and this is only used in that
+// context.
+func newSizeReaderAt(r io.ReadSeeker) (SizeReaderAt, error) {
+ size, err := r.Seek(0, 2)
+ if err != nil {
+ return nil, err
+ }
+ return &sizeReaderAt{
+ r: r,
+ size: size,
+ off: size,
+ }, nil
+}
+
+// sizeReaderAt adapts an io.ReadSeeker to a SizeReaderAt.
+type sizeReaderAt struct {
+ r io.ReadSeeker
+ size int64
+ off int64
+}
+
+// ReadAt implemnts SizeReaderAt.ReadAt.
+func (r *sizeReaderAt) ReadAt(buf []byte, off int64) (n int, err error) {
+ if off != r.off {
+ _, err = r.r.Seek(off, 0)
+ if err != nil {
+ return 0, err
+ }
+ r.off = off
+ }
+ n, err = io.ReadFull(r.r, buf)
+ r.off += int64(n)
+ return n, err
+}
+
+// Size implemnts SizeReaderAt.Size.
+func (r *sizeReaderAt) Size() int64 {
+ return r.size
+}
+
+// readSeeker adapts a SizeReaderAt to an io.ReadSeeker.
+type readSeeker struct {
+ r SizeReaderAt
+ off int64
+}
+
+// Seek implements io.Seeker.Seek.
+func (r *readSeeker) Seek(off int64, whence int) (int64, error) {
+ switch whence {
+ case 0:
+ case 1:
+ off += r.off
+ case 2:
+ off = r.r.Size() + off
+ }
+ if off < 0 {
+ return 0, errors.New("negative position")
+ }
+ r.off = off
+ return off, nil
+}
+
+// Read implements io.Reader.Read.
+func (r *readSeeker) Read(buf []byte) (int, error) {
+ n, err := r.r.ReadAt(buf, r.off)
+ r.off += int64(n)
+ if err == io.ErrUnexpectedEOF {
+ err = io.EOF
+ }
+ return n, err
+}
diff --git a/automation/vendor/github.com/juju/utils/naturalsort.go b/automation/vendor/github.com/juju/utils/naturalsort.go
new file mode 100644
index 0000000..d337093
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/naturalsort.go
@@ -0,0 +1,95 @@
+// Copyright 2016 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "fmt"
+ "sort"
+ "strconv"
+ "unicode"
+)
+
+// SortStringsNaturally sorts strings according to their natural sort order.
+func SortStringsNaturally(s []string) []string {
+ sort.Sort(naturally(s))
+ return s
+}
+
+type naturally []string
+
+func (n naturally) Len() int {
+ return len(n)
+}
+
+func (n naturally) Swap(a, b int) {
+ n[a], n[b] = n[b], n[a]
+}
+
+// Less sorts by non-numeric prefix and numeric suffix
+// when one exists.
+func (n naturally) Less(a, b int) bool {
+ aVal := n[a]
+ bVal := n[b]
+
+ for {
+ // If bVal is empty, then aVal can't be less than it.
+ if bVal == "" {
+ return false
+ }
+ // If aVal is empty here, then is must be less than bVal.
+ if aVal == "" {
+ return true
+ }
+
+ aPrefix, aNumber, aRemainder := splitAtNumber(aVal)
+ bPrefix, bNumber, bRemainder := splitAtNumber(bVal)
+ if aPrefix != bPrefix {
+ return aPrefix < bPrefix
+ }
+ if aNumber != bNumber {
+ return aNumber < bNumber
+ }
+
+ // Everything is the same so far, try again with the remainer.
+ aVal = aRemainder
+ bVal = bRemainder
+ }
+}
+
+// splitAtNumber splits given string at the first digit, returning the
+// prefix before the number, the integer represented by the first
+// series of digits, and the remainder of the string after the first
+// series of digits. If no digits are present, the number is returned
+// as -1 and the remainder is empty.
+func splitAtNumber(str string) (string, int, string) {
+ i := indexOfDigit(str)
+ if i == -1 {
+ // no numbers
+ return str, -1, ""
+ }
+ j := i + indexOfNonDigit(str[i:])
+ n, err := strconv.Atoi(str[i:j])
+ if err != nil {
+ panic(fmt.Sprintf("parsing number %v: %v", str[i:j], err)) // should never happen
+ }
+ return str[:i], n, str[j:]
+}
+
+func indexOfDigit(str string) int {
+ for i, rune := range str {
+ if unicode.IsDigit(rune) {
+ return i
+ }
+ }
+ return -1
+}
+
+func indexOfNonDigit(str string) int {
+ for i, rune := range str {
+ if !unicode.IsDigit(rune) {
+ return i
+ }
+ }
+ return len(str)
+}
diff --git a/automation/vendor/github.com/juju/utils/network.go b/automation/vendor/github.com/juju/utils/network.go
new file mode 100644
index 0000000..505a6e9
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/network.go
@@ -0,0 +1,46 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/juju/loggo"
+)
+
+var logger = loggo.GetLogger("juju.utils")
+
+// GetIPv4Address iterates through the addresses expecting the format from
+// func (ifi *net.Interface) Addrs() ([]net.Addr, error)
+func GetIPv4Address(addresses []net.Addr) (string, error) {
+ for _, addr := range addresses {
+ ip, _, err := net.ParseCIDR(addr.String())
+ if err != nil {
+ return "", err
+ }
+ ipv4 := ip.To4()
+ if ipv4 == nil {
+ continue
+ }
+ return ipv4.String(), nil
+ }
+ return "", fmt.Errorf("no addresses match")
+}
+
+// GetAddressForInterface looks for the network interface
+// and returns the IPv4 address from the possible addresses.
+func GetAddressForInterface(interfaceName string) (string, error) {
+ iface, err := net.InterfaceByName(interfaceName)
+ if err != nil {
+ logger.Errorf("cannot find network interface %q: %v", interfaceName, err)
+ return "", err
+ }
+ addrs, err := iface.Addrs()
+ if err != nil {
+ logger.Errorf("cannot get addresses for network interface %q: %v", interfaceName, err)
+ return "", err
+ }
+ return GetIPv4Address(addrs)
+}
diff --git a/automation/vendor/github.com/juju/utils/os.go b/automation/vendor/github.com/juju/utils/os.go
new file mode 100644
index 0000000..146f793
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/os.go
@@ -0,0 +1,41 @@
+// Copyright 2015 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+// These are the names of the operating systems recognized by Go.
+const (
+ OSWindows = "windows"
+ OSDarwin = "darwin"
+ OSDragonfly = "dragonfly"
+ OSFreebsd = "freebsd"
+ OSLinux = "linux"
+ OSNacl = "nacl"
+ OSNetbsd = "netbsd"
+ OSOpenbsd = "openbsd"
+ OSSolaris = "solaris"
+)
+
+// OSUnix is the list of unix-like operating systems recognized by Go.
+// See http://golang.org/src/path/filepath/path_unix.go.
+var OSUnix = []string{
+ OSDarwin,
+ OSDragonfly,
+ OSFreebsd,
+ OSLinux,
+ OSNacl,
+ OSNetbsd,
+ OSOpenbsd,
+ OSSolaris,
+}
+
+// OSIsUnix determines whether or not the given OS name is one of the
+// unix-like operating systems recognized by Go.
+func OSIsUnix(os string) bool {
+ for _, goos := range OSUnix {
+ if os == goos {
+ return true
+ }
+ }
+ return false
+}
diff --git a/automation/vendor/github.com/juju/utils/password.go b/automation/vendor/github.com/juju/utils/password.go
new file mode 100644
index 0000000..914e8e4
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/password.go
@@ -0,0 +1,92 @@
+// Copyright 2012, 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "crypto/rand"
+ "crypto/sha512"
+ "encoding/base64"
+ "fmt"
+ "io"
+
+ "golang.org/x/crypto/pbkdf2"
+)
+
+// CompatSalt is because Juju 1.16 and older used a hard-coded salt to compute
+// the password hash for all users and agents
+var CompatSalt = string([]byte{0x75, 0x82, 0x81, 0xca})
+
+const randomPasswordBytes = 18
+
+// MinAgentPasswordLength describes how long agent passwords should be. We
+// require this length because we assume enough entropy in the Agent password
+// that it is safe to not do extra rounds of iterated hashing.
+var MinAgentPasswordLength = base64.StdEncoding.EncodedLen(randomPasswordBytes)
+
+// RandomBytes returns n random bytes.
+func RandomBytes(n int) ([]byte, error) {
+ buf := make([]byte, n)
+ _, err := io.ReadFull(rand.Reader, buf)
+ if err != nil {
+ return nil, fmt.Errorf("cannot read random bytes: %v", err)
+ }
+ return buf, nil
+}
+
+// RandomPassword generates a random base64-encoded password.
+func RandomPassword() (string, error) {
+ b, err := RandomBytes(randomPasswordBytes)
+ if err != nil {
+ return "", err
+ }
+ return base64.StdEncoding.EncodeToString(b), nil
+}
+
+// RandomSalt generates a random base64 data suitable for using as a password
+// salt The pbkdf2 guideline is to use 8 bytes of salt, so we do 12 raw bytes
+// into 16 base64 bytes. (The alternative is 6 raw into 8 base64).
+func RandomSalt() (string, error) {
+ b, err := RandomBytes(12)
+ if err != nil {
+ return "", err
+ }
+ return base64.StdEncoding.EncodeToString(b), nil
+}
+
+// FastInsecureHash specifies whether a fast, insecure version of the hash
+// algorithm will be used. Changing this will cause PasswordHash to
+// produce incompatible passwords. It should only be changed for
+// testing purposes - to make tests run faster.
+var FastInsecureHash = false
+
+// UserPasswordHash returns base64-encoded one-way hash password that is
+// computationally hard to crack by iterating through possible passwords.
+func UserPasswordHash(password string, salt string) string {
+ if salt == "" {
+ panic("salt is not allowed to be empty")
+ }
+ iter := 8192
+ if FastInsecureHash {
+ iter = 1
+ }
+ // Generate 18 byte passwords because we know that MongoDB
+ // uses the MD5 sum of the password anyway, so there's
+ // no point in using more bytes. (18 so we don't get base 64
+ // padding characters).
+ h := pbkdf2.Key([]byte(password), []byte(salt), iter, 18, sha512.New)
+ return base64.StdEncoding.EncodeToString(h)
+}
+
+// AgentPasswordHash returns base64-encoded one-way hash of password. This is
+// not suitable for User passwords because those will have limited entropy (see
+// UserPasswordHash). However, since we generate long random passwords for
+// agents, we can trust that there is sufficient entropy to prevent brute force
+// search. And using a faster hash allows us to restart the state machines and
+// have 1000s of agents log in in a reasonable amount of time.
+func AgentPasswordHash(password string) string {
+ sum := sha512.New()
+ sum.Write([]byte(password))
+ h := sum.Sum(nil)
+ return base64.StdEncoding.EncodeToString(h[:18])
+}
diff --git a/automation/vendor/github.com/juju/utils/randomstring.go b/automation/vendor/github.com/juju/utils/randomstring.go
new file mode 100644
index 0000000..662f514
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/randomstring.go
@@ -0,0 +1,42 @@
+// Copyright 2015 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "math/rand"
+ "sync"
+ "time"
+)
+
+// Can be used as a sane default argument for RandomString
+var (
+ LowerAlpha = []rune("abcdefghijklmnopqrstuvwxyz")
+ UpperAlpha = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
+ Digits = []rune("0123456789")
+)
+
+var (
+ randomStringMu sync.Mutex
+ randomStringRand *rand.Rand
+)
+
+func init() {
+ randomStringRand = rand.New(
+ rand.NewSource(time.Now().UnixNano()),
+ )
+}
+
+// RandomString will return a string of length n that will only
+// contain runes inside validRunes
+func RandomString(n int, validRunes []rune) string {
+ randomStringMu.Lock()
+ defer randomStringMu.Unlock()
+
+ runes := make([]rune, n)
+ for i := range runes {
+ runes[i] = validRunes[randomStringRand.Intn(len(validRunes))]
+ }
+
+ return string(runes)
+}
diff --git a/automation/vendor/github.com/juju/utils/relativeurl.go b/automation/vendor/github.com/juju/utils/relativeurl.go
new file mode 100644
index 0000000..b70f1d5
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/relativeurl.go
@@ -0,0 +1,62 @@
+// Copyright 2016 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "strings"
+
+ "github.com/juju/errors"
+)
+
+// RelativeURLPath returns a relative URL path that is lexically
+// equivalent to targpath when interpreted by url.URL.ResolveReference.
+// On success, the returned path will always be non-empty and relative
+// to basePath, even if basePath and targPath share no elements.
+//
+// It is assumed that both basePath and targPath are normalized
+// (have no . or .. elements).
+//
+// An error is returned if basePath or targPath are not absolute paths.
+func RelativeURLPath(basePath, targPath string) (string, error) {
+ if !strings.HasPrefix(basePath, "/") {
+ return "", errors.New("non-absolute base URL")
+ }
+ if !strings.HasPrefix(targPath, "/") {
+ return "", errors.New("non-absolute target URL")
+ }
+ baseParts := strings.Split(basePath, "/")
+ targParts := strings.Split(targPath, "/")
+
+ // For the purposes of dotdot, the last element of
+ // the paths are irrelevant. We save the last part
+ // of the target path for later.
+ lastElem := targParts[len(targParts)-1]
+ baseParts = baseParts[0 : len(baseParts)-1]
+ targParts = targParts[0 : len(targParts)-1]
+
+ // Find the common prefix between the two paths:
+ var i int
+ for ; i < len(baseParts); i++ {
+ if i >= len(targParts) || baseParts[i] != targParts[i] {
+ break
+ }
+ }
+ dotdotCount := len(baseParts) - i
+ targOnly := targParts[i:]
+ result := make([]string, 0, dotdotCount+len(targOnly)+1)
+ for i := 0; i < dotdotCount; i++ {
+ result = append(result, "..")
+ }
+ result = append(result, targOnly...)
+ result = append(result, lastElem)
+ final := strings.Join(result, "/")
+ if final == "" {
+ // If the final result is empty, the last element must
+ // have been empty, so the target was slash terminated
+ // and there were no previous elements, so "."
+ // is appropriate.
+ final = "."
+ }
+ return final, nil
+}
diff --git a/automation/vendor/github.com/juju/utils/set/ints.go b/automation/vendor/github.com/juju/utils/set/ints.go
new file mode 100644
index 0000000..02009ea
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/set/ints.go
@@ -0,0 +1,112 @@
+// Copyright 2014 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package set
+
+import (
+ "sort"
+)
+
+// Ints represents the classic "set" data structure, and contains ints.
+type Ints map[int]bool
+
+// NewInts creates and initializes an Ints and populates it with
+// initial values as specified in the parameters.
+func NewInts(initial ...int) Ints {
+ result := make(Ints)
+ for _, value := range initial {
+ result.Add(value)
+ }
+ return result
+}
+
+// Size returns the number of elements in the set.
+func (is Ints) Size() int {
+ return len(is)
+}
+
+// IsEmpty is true for empty or uninitialized sets.
+func (is Ints) IsEmpty() bool {
+ return len(is) == 0
+}
+
+// Add puts a value into the set.
+func (is Ints) Add(value int) {
+ if is == nil {
+ panic("uninitalised set")
+ }
+ is[value] = true
+}
+
+// Remove takes a value out of the set. If value wasn't in the set to start
+// with, this method silently succeeds.
+func (is Ints) Remove(value int) {
+ delete(is, value)
+}
+
+// Contains returns true if the value is in the set, and false otherwise.
+func (is Ints) Contains(value int) bool {
+ _, exists := is[value]
+ return exists
+}
+
+// Values returns an unordered slice containing all the values in the set.
+func (is Ints) Values() []int {
+ result := make([]int, len(is))
+ i := 0
+ for key := range is {
+ result[i] = key
+ i++
+ }
+ return result
+}
+
+// SortedValues returns an ordered slice containing all the values in the set.
+func (is Ints) SortedValues() []int {
+ values := is.Values()
+ sort.Ints(values)
+ return values
+}
+
+// Union returns a new Ints representing a union of the elments in the
+// method target and the parameter.
+func (is Ints) Union(other Ints) Ints {
+ result := make(Ints)
+ // Use the internal map rather than going through the friendlier functions
+ // to avoid extra allocation of slices.
+ for value := range is {
+ result[value] = true
+ }
+ for value := range other {
+ result[value] = true
+ }
+ return result
+}
+
+// Intersection returns a new Ints representing a intersection of the elments in the
+// method target and the parameter.
+func (is Ints) Intersection(other Ints) Ints {
+ result := make(Ints)
+ // Use the internal map rather than going through the friendlier functions
+ // to avoid extra allocation of slices.
+ for value := range is {
+ if other.Contains(value) {
+ result[value] = true
+ }
+ }
+ return result
+}
+
+// Difference returns a new Ints representing all the values in the
+// target that are not in the parameter.
+func (is Ints) Difference(other Ints) Ints {
+ result := make(Ints)
+ // Use the internal map rather than going through the friendlier functions
+ // to avoid extra allocation of slices.
+ for value := range is {
+ if !other.Contains(value) {
+ result[value] = true
+ }
+ }
+ return result
+}
diff --git a/automation/vendor/github.com/juju/utils/set/strings.go b/automation/vendor/github.com/juju/utils/set/strings.go
new file mode 100644
index 0000000..b89f932
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/set/strings.go
@@ -0,0 +1,112 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package set
+
+import (
+ "sort"
+)
+
+// Strings represents the classic "set" data structure, and contains strings.
+type Strings map[string]bool
+
+// NewStrings creates and initializes a Strings and populates it with
+// initial values as specified in the parameters.
+func NewStrings(initial ...string) Strings {
+ result := make(Strings)
+ for _, value := range initial {
+ result.Add(value)
+ }
+ return result
+}
+
+// Size returns the number of elements in the set.
+func (s Strings) Size() int {
+ return len(s)
+}
+
+// IsEmpty is true for empty or uninitialized sets.
+func (s Strings) IsEmpty() bool {
+ return len(s) == 0
+}
+
+// Add puts a value into the set.
+func (s Strings) Add(value string) {
+ if s == nil {
+ panic("uninitalised set")
+ }
+ s[value] = true
+}
+
+// Remove takes a value out of the set. If value wasn't in the set to start
+// with, this method silently succeeds.
+func (s Strings) Remove(value string) {
+ delete(s, value)
+}
+
+// Contains returns true if the value is in the set, and false otherwise.
+func (s Strings) Contains(value string) bool {
+ _, exists := s[value]
+ return exists
+}
+
+// Values returns an unordered slice containing all the values in the set.
+func (s Strings) Values() []string {
+ result := make([]string, len(s))
+ i := 0
+ for key := range s {
+ result[i] = key
+ i++
+ }
+ return result
+}
+
+// SortedValues returns an ordered slice containing all the values in the set.
+func (s Strings) SortedValues() []string {
+ values := s.Values()
+ sort.Strings(values)
+ return values
+}
+
+// Union returns a new Strings representing a union of the elments in the
+// method target and the parameter.
+func (s Strings) Union(other Strings) Strings {
+ result := make(Strings)
+ // Use the internal map rather than going through the friendlier functions
+ // to avoid extra allocation of slices.
+ for value := range s {
+ result[value] = true
+ }
+ for value := range other {
+ result[value] = true
+ }
+ return result
+}
+
+// Intersection returns a new Strings representing a intersection of the elments in the
+// method target and the parameter.
+func (s Strings) Intersection(other Strings) Strings {
+ result := make(Strings)
+ // Use the internal map rather than going through the friendlier functions
+ // to avoid extra allocation of slices.
+ for value := range s {
+ if other.Contains(value) {
+ result[value] = true
+ }
+ }
+ return result
+}
+
+// Difference returns a new Strings representing all the values in the
+// target that are not in the parameter.
+func (s Strings) Difference(other Strings) Strings {
+ result := make(Strings)
+ // Use the internal map rather than going through the friendlier functions
+ // to avoid extra allocation of slices.
+ for value := range s {
+ if !other.Contains(value) {
+ result[value] = true
+ }
+ }
+ return result
+}
diff --git a/automation/vendor/github.com/juju/utils/set/tags.go b/automation/vendor/github.com/juju/utils/set/tags.go
new file mode 100644
index 0000000..0fb6f87
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/set/tags.go
@@ -0,0 +1,150 @@
+// Copyright 2014 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package set
+
+import (
+ "sort"
+
+ "github.com/juju/errors"
+ "gopkg.in/juju/names.v2"
+)
+
+// Tags represents the Set data structure, it implements tagSet
+// and contains names.Tags.
+type Tags map[names.Tag]bool
+
+// NewTags creates and initializes a Tags and populates it with
+// inital values as specified in the parameters.
+func NewTags(initial ...names.Tag) Tags {
+ result := make(Tags)
+ for _, value := range initial {
+ result.Add(value)
+ }
+ return result
+}
+
+// NewTagsFromStrings creates and initializes a Tags and populates it
+// by using names.ParseTag on the initial values specified in the parameters.
+func NewTagsFromStrings(initial ...string) (Tags, error) {
+ result := make(Tags)
+ for _, value := range initial {
+ tag, err := names.ParseTag(value)
+ if err != nil {
+ return result, errors.Trace(err)
+ }
+ result.Add(tag)
+ }
+ return result, nil
+}
+
+// Size returns the number of elements in the set.
+func (t Tags) Size() int {
+ return len(t)
+}
+
+// IsEmpty is true for empty or uninitialized sets.
+func (t Tags) IsEmpty() bool {
+ return len(t) == 0
+}
+
+// Add puts a value into the set.
+func (t Tags) Add(value names.Tag) {
+ if t == nil {
+ panic("uninitalised set")
+ }
+ t[value] = true
+}
+
+// Remove takes a value out of the set. If value wasn't in the set to start
+// with, this method silently succeeds.
+func (t Tags) Remove(value names.Tag) {
+ delete(t, value)
+}
+
+// Contains returns true if the value is in the set, and false otherwise.
+func (t Tags) Contains(value names.Tag) bool {
+ _, exists := t[value]
+ return exists
+}
+
+// Values returns an unordered slice containing all the values in the set.
+func (t Tags) Values() []names.Tag {
+ result := make([]names.Tag, len(t))
+ i := 0
+ for key := range t {
+ result[i] = key
+ i++
+ }
+ return result
+}
+
+// stringValues returns a list of strings that represent a names.Tag
+// Used internally by the SortedValues method.
+func (t Tags) stringValues() []string {
+ result := make([]string, t.Size())
+ i := 0
+ for key := range t {
+ result[i] = key.String()
+ i++
+ }
+ return result
+}
+
+// SortedValues returns an ordered slice containing all the values in the set.
+func (t Tags) SortedValues() []names.Tag {
+ values := t.stringValues()
+ sort.Strings(values)
+
+ result := make([]names.Tag, len(values))
+ for i, value := range values {
+ // We already know only good strings can live in the Tags set
+ // so we can safely ignore the error here.
+ tag, _ := names.ParseTag(value)
+ result[i] = tag
+ }
+ return result
+}
+
+// Union returns a new Tags representing a union of the elments in the
+// method target and the parameter.
+func (t Tags) Union(other Tags) Tags {
+ result := make(Tags)
+ // Use the internal map rather than going through the friendlier functions
+ // to avoid extra allocation of slices.
+ for value := range t {
+ result[value] = true
+ }
+ for value := range other {
+ result[value] = true
+ }
+ return result
+}
+
+// Intersection returns a new Tags representing a intersection of the elments in the
+// method target and the parameter.
+func (t Tags) Intersection(other Tags) Tags {
+ result := make(Tags)
+ // Use the internal map rather than going through the friendlier functions
+ // to avoid extra allocation of slices.
+ for value := range t {
+ if other.Contains(value) {
+ result[value] = true
+ }
+ }
+ return result
+}
+
+// Difference returns a new Tags representing all the values in the
+// target that are not in the parameter.
+func (t Tags) Difference(other Tags) Tags {
+ result := make(Tags)
+ // Use the internal map rather than going through the friendlier functions
+ // to avoid extra allocation of slices.
+ for value := range t {
+ if !other.Contains(value) {
+ result[value] = true
+ }
+ }
+ return result
+}
diff --git a/automation/vendor/github.com/juju/utils/size.go b/automation/vendor/github.com/juju/utils/size.go
new file mode 100644
index 0000000..8f6f88d
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/size.go
@@ -0,0 +1,78 @@
+// Copyright 2014 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "math"
+ "strconv"
+ "strings"
+ "unicode"
+
+ "github.com/juju/errors"
+)
+
+// ParseSize parses the string as a size, in mebibytes.
+//
+// The string must be a is a non-negative number with
+// an optional multiplier suffix (M, G, T, P, E, Z, or Y).
+// If the suffix is not specified, "M" is implied.
+func ParseSize(str string) (MB uint64, err error) {
+ // Find the first non-digit/period:
+ i := strings.IndexFunc(str, func(r rune) bool {
+ return r != '.' && !unicode.IsDigit(r)
+ })
+ var multiplier float64 = 1
+ if i > 0 {
+ suffix := str[i:]
+ multiplier = 0
+ for j := 0; j < len(sizeSuffixes); j++ {
+ base := string(sizeSuffixes[j])
+ // M, MB, or MiB are all valid.
+ switch suffix {
+ case base, base + "B", base + "iB":
+ multiplier = float64(sizeSuffixMultiplier(j))
+ break
+ }
+ }
+ if multiplier == 0 {
+ return 0, errors.Errorf("invalid multiplier suffix %q, expected one of %s", suffix, []byte(sizeSuffixes))
+ }
+ str = str[:i]
+ }
+
+ val, err := strconv.ParseFloat(str, 64)
+ if err != nil || val < 0 {
+ return 0, errors.Errorf("expected a non-negative number, got %q", str)
+ }
+ val *= multiplier
+ return uint64(math.Ceil(val)), nil
+}
+
+var sizeSuffixes = "MGTPEZY"
+
+func sizeSuffixMultiplier(i int) int {
+ return 1 << uint(i*10)
+}
+
+// SizeTracker tracks the number of bytes passing through
+// its Write method (which is otherwise a no-op).
+//
+// Use SizeTracker with io.MultiWriter() to track number of bytes
+// written. Use with io.TeeReader() to track number of bytes read.
+type SizeTracker struct {
+ // size is the number of bytes written so far.
+ size int64
+}
+
+// Size returns the number of bytes written so far.
+func (st SizeTracker) Size() int64 {
+ return st.size
+}
+
+// Write implements io.Writer.
+func (st *SizeTracker) Write(data []byte) (n int, err error) {
+ n = len(data)
+ st.size += int64(n)
+ return n, nil
+}
diff --git a/automation/vendor/github.com/juju/utils/systemerrmessages_unix.go b/automation/vendor/github.com/juju/utils/systemerrmessages_unix.go
new file mode 100644
index 0000000..7a0edd4
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/systemerrmessages_unix.go
@@ -0,0 +1,16 @@
+// Copyright 2014 Canonical Ltd.
+// Copyright 2014 Cloudbase Solutions SRL
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+// +build !windows
+
+package utils
+
+// The following are strings/regex-es which match common Unix error messages
+// that may be returned in case of failed calls to the system.
+// Any extra leading/trailing regex-es are left to be added by the developer.
+const (
+ NoSuchUserErrRegexp = `user: unknown user [a-z0-9_-]*`
+ NoSuchFileErrRegexp = `no such file or directory`
+ MkdirFailErrRegexp = `.* not a directory`
+)
diff --git a/automation/vendor/github.com/juju/utils/systemerrmessages_windows.go b/automation/vendor/github.com/juju/utils/systemerrmessages_windows.go
new file mode 100644
index 0000000..b24d453
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/systemerrmessages_windows.go
@@ -0,0 +1,14 @@
+// Copyright 2014 Canonical Ltd.
+// Copyright 2014 Cloudbase Solutions SRL
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+// The following are strings/regex-es which match common Windows error messages
+// that may be returned in case of failed calls to the system.
+// Any extra leading/trailing regex-es are left to be added by the developer.
+const (
+ NoSuchUserErrRegexp = `No mapping between account names and security IDs was done\.`
+ NoSuchFileErrRegexp = `The system cannot find the (file|path) specified\.`
+ MkdirFailErrRegexp = `mkdir .*` + NoSuchFileErrRegexp
+)
diff --git a/automation/vendor/github.com/juju/utils/timeit.go b/automation/vendor/github.com/juju/utils/timeit.go
new file mode 100644
index 0000000..172b593
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/timeit.go
@@ -0,0 +1,57 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "fmt"
+ "os"
+ "time"
+)
+
+type timer struct {
+ action string
+ start time.Time
+ depth int
+ duration time.Duration
+ subActions []*timer
+}
+
+func (t *timer) String() string {
+ this := fmt.Sprintf("%.3fs %*s%s\n", t.duration.Seconds(), t.depth, "", t.action)
+ for _, sub := range t.subActions {
+ this += sub.String()
+ }
+ return this
+}
+
+var stack []*timer
+
+// Start a timer, used for tracking time spent.
+// Generally used with either defer, as in:
+// defer utils.Timeit("my func")()
+// Which will track how much time is spent in your function. Or
+// if you want to track the time spent in a function you are calling
+// then you would use:
+// toc := utils.Timeit("anotherFunc()")
+// anotherFunc()
+// toc()
+// This tracks nested calls by indenting the output, and will print out the
+// full stack of timing when we reach the top of the stack.
+func Timeit(action string) func() {
+ cur := &timer{action: action, start: time.Now(), depth: len(stack)}
+ if len(stack) != 0 {
+ tip := stack[len(stack)-1]
+ tip.subActions = append(tip.subActions, cur)
+ }
+ stack = append(stack, cur)
+ return func() {
+ cur.duration = time.Since(cur.start)
+ if len(stack) == 0 || cur == stack[0] {
+ fmt.Fprint(os.Stderr, cur)
+ stack = nil
+ } else {
+ stack = stack[0 : len(stack)-1]
+ }
+ }
+}
diff --git a/automation/vendor/github.com/juju/utils/timer.go b/automation/vendor/github.com/juju/utils/timer.go
new file mode 100644
index 0000000..6b32f09
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/timer.go
@@ -0,0 +1,124 @@
+// Copyright 2015 Canonical Ltd.
+// Copyright 2015 Cloudbase Solutions SRL
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "math/rand"
+ "time"
+
+ "github.com/juju/utils/clock"
+)
+
+// Countdown implements a timer that will call a provided function.
+// after a internally stored duration. The steps as well as min and max
+// durations are declared upon initialization and depend on
+// the particular implementation.
+//
+// TODO(katco): 2016-08-09: This type is deprecated: lp:1611427
+type Countdown interface {
+ // Reset stops the timer and resets its duration to the minimum one.
+ // Start must be called to start the timer again.
+ Reset()
+
+ // Start starts the internal timer.
+ // At the end of the timer, if Reset hasn't been called in the mean time
+ // Func will be called and the duration is increased for the next call.
+ Start()
+}
+
+// NewBackoffTimer creates and initializes a new BackoffTimer
+// A backoff timer starts at min and gets multiplied by factor
+// until it reaches max. Jitter determines whether a small
+// randomization is added to the duration.
+//
+// TODO(katco): 2016-08-09: This type is deprecated: lp:1611427
+func NewBackoffTimer(config BackoffTimerConfig) *BackoffTimer {
+ return &BackoffTimer{
+ config: config,
+ currentDuration: config.Min,
+ }
+}
+
+// BackoffTimer implements Countdown.
+// A backoff timer starts at min and gets multiplied by factor
+// until it reaches max. Jitter determines whether a small
+// randomization is added to the duration.
+//
+// TODO(katco): 2016-08-09: This type is deprecated: lp:1611427
+type BackoffTimer struct {
+ config BackoffTimerConfig
+
+ timer clock.Timer
+ currentDuration time.Duration
+}
+
+// BackoffTimerConfig is a helper struct for backoff timer
+// that encapsulates config information.
+//
+// TODO(katco): 2016-08-09: This type is deprecated: lp:1611427
+type BackoffTimerConfig struct {
+ // The minimum duration after which Func is called.
+ Min time.Duration
+
+ // The maximum duration after which Func is called.
+ Max time.Duration
+
+ // Determines whether a small randomization is applied to
+ // the duration.
+ Jitter bool
+
+ // The factor by which you want the duration to increase
+ // every time.
+ Factor int64
+
+ // Func is the function that will be called when the countdown reaches 0.
+ Func func()
+
+ // Clock provides the AfterFunc function used to call func.
+ // It is exposed here so it's easier to mock it in tests.
+ Clock clock.Clock
+}
+
+// Start implements the Timer interface.
+// Any existing timer execution is stopped before
+// a new one is created.
+func (t *BackoffTimer) Start() {
+ if t.timer != nil {
+ t.timer.Stop()
+ }
+ t.timer = t.config.Clock.AfterFunc(t.currentDuration, t.config.Func)
+
+ // Since it's a backoff timer we will increase
+ // the duration after each signal.
+ t.increaseDuration()
+}
+
+// Reset implements the Timer interface.
+func (t *BackoffTimer) Reset() {
+ if t.timer != nil {
+ t.timer.Stop()
+ }
+ if t.currentDuration > t.config.Min {
+ t.currentDuration = t.config.Min
+ }
+}
+
+// increaseDuration will increase the duration based on
+// the current value and the factor. If jitter is true
+// it will add a 0.3% jitter to the final value.
+func (t *BackoffTimer) increaseDuration() {
+ current := int64(t.currentDuration)
+ nextDuration := time.Duration(current * t.config.Factor)
+ if t.config.Jitter {
+ // Get a factor in [-1; 1].
+ randFactor := (rand.Float64() * 2) - 1
+ jitter := float64(nextDuration) * randFactor * 0.03
+ nextDuration = nextDuration + time.Duration(jitter)
+ }
+ if nextDuration > t.config.Max {
+ nextDuration = t.config.Max
+ }
+ t.currentDuration = nextDuration
+}
diff --git a/automation/vendor/github.com/juju/utils/tls.go b/automation/vendor/github.com/juju/utils/tls.go
new file mode 100644
index 0000000..b805af8
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/tls.go
@@ -0,0 +1,72 @@
+// Copyright 2016 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "crypto/tls"
+ "net/http"
+ "time"
+)
+
+// NewHttpTLSTransport returns a new http.Transport constructed with the TLS config
+// and the necessary parameters for Juju.
+func NewHttpTLSTransport(tlsConfig *tls.Config) *http.Transport {
+ // See https://code.google.com/p/go/issues/detail?id=4677
+ // We need to force the connection to close each time so that we don't
+ // hit the above Go bug.
+ transport := &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ TLSClientConfig: tlsConfig,
+ DisableKeepAlives: true,
+ TLSHandshakeTimeout: 10 * time.Second,
+ }
+ installHTTPDialShim(transport)
+ registerFileProtocol(transport)
+ return transport
+}
+
+// knownGoodCipherSuites contains the list of secure cipher suites to use
+// with tls.Config. This list matches those that Go 1.6 implements from
+// https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations.
+//
+// https://tools.ietf.org/html/rfc7525#section-4.2 excludes RSA exchange completely
+// so we could be more strict if all our clients will support
+// TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256/384. Unfortunately Go's crypto library
+// is limited and doesn't support DHE-RSA-AES256-GCM-SHA384 and
+// DHE-RSA-AES256-SHA256, which are part of the recommended set.
+//
+// Unfortunately we can't drop the RSA algorithms because our servers aren't
+// generating ECDHE keys.
+var knownGoodCipherSuites = []uint16{
+ // These are technically useless for Juju, since we use an RSA certificate,
+ // but they also don't hurt anything, and supporting an ECDSA certificate
+ // could be useful in the future.
+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+
+ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+
+ // Windows doesn't support GCM currently, so we need these for RSA support.
+ tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+
+ // We need this so that we have at least one suite in common
+ // with the default gnutls installed for precise and trusty.
+ tls.TLS_RSA_WITH_AES_256_CBC_SHA,
+}
+
+// SecureTLSConfig returns a tls.Config that conforms to Juju's security
+// standards, so as to avoid known security vulnerabilities in certain
+// configurations.
+//
+// Currently it excludes RC4 implementations from the available ciphersuites,
+// requires ciphersuites that provide forward secrecy, and sets the minimum TLS
+// version to 1.2.
+func SecureTLSConfig() *tls.Config {
+ return &tls.Config{
+ CipherSuites: knownGoodCipherSuites,
+ MinVersion: tls.VersionTLS12,
+ }
+}
diff --git a/automation/vendor/github.com/juju/utils/trivial.go b/automation/vendor/github.com/juju/utils/trivial.go
new file mode 100644
index 0000000..642e213
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/trivial.go
@@ -0,0 +1,147 @@
+// Copyright 2012, 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "bytes"
+ "compress/gzip"
+ "crypto/sha256"
+ "encoding/hex"
+ "io"
+ "io/ioutil"
+ "os"
+ "strings"
+ "unicode"
+)
+
+// TODO(ericsnow) Move the quoting helpers into the shell package?
+
+// ShQuote quotes s so that when read by bash, no metacharacters
+// within s will be interpreted as such.
+func ShQuote(s string) string {
+ // single-quote becomes single-quote, double-quote, single-quote, double-quote, single-quote
+ return `'` + strings.Replace(s, `'`, `'"'"'`, -1) + `'`
+}
+
+// WinPSQuote quotes s so that when read by powershell, no metacharacters
+// within s will be interpreted as such.
+func WinPSQuote(s string) string {
+ // See http://ss64.com/ps/syntax-esc.html#quotes.
+ // Double quotes inside single quotes are fine, double single quotes inside
+ // single quotes, not so much so. Having double quoted strings inside single
+ // quoted strings, ensure no expansion happens.
+ return `'` + strings.Replace(s, `'`, `"`, -1) + `'`
+}
+
+// WinCmdQuote quotes s so that when read by cmd.exe, no metacharacters
+// within s will be interpreted as such.
+func WinCmdQuote(s string) string {
+ // See http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx.
+ quoted := winCmdQuote(s)
+ return winCmdEscapeMeta(quoted)
+}
+
+func winCmdQuote(s string) string {
+ var escaped string
+ for _, c := range s {
+ switch c {
+ case '\\', '"':
+ escaped += `\`
+ }
+ escaped += string(c)
+ }
+ return `"` + escaped + `"`
+}
+
+func winCmdEscapeMeta(str string) string {
+ const meta = `()%!^"<>&|`
+ var newStr string
+ for _, c := range str {
+ if strings.Contains(meta, string(c)) {
+ newStr += "^"
+ }
+ newStr += string(c)
+ }
+ return newStr
+}
+
+// CommandString flattens a sequence of command arguments into a
+// string suitable for executing in a shell, escaping slashes,
+// variables and quotes as necessary; each argument is double-quoted
+// if and only if necessary.
+func CommandString(args ...string) string {
+ var buf bytes.Buffer
+ for i, arg := range args {
+ needsQuotes := false
+ var argBuf bytes.Buffer
+ for _, r := range arg {
+ if unicode.IsSpace(r) {
+ needsQuotes = true
+ } else if r == '"' || r == '$' || r == '\\' {
+ needsQuotes = true
+ argBuf.WriteByte('\\')
+ }
+ argBuf.WriteRune(r)
+ }
+ if i > 0 {
+ buf.WriteByte(' ')
+ }
+ if needsQuotes {
+ buf.WriteByte('"')
+ argBuf.WriteTo(&buf)
+ buf.WriteByte('"')
+ } else {
+ argBuf.WriteTo(&buf)
+ }
+ }
+ return buf.String()
+}
+
+// Gzip compresses the given data.
+func Gzip(data []byte) []byte {
+ var buf bytes.Buffer
+ w := gzip.NewWriter(&buf)
+ if _, err := w.Write(data); err != nil {
+ // Compression should never fail unless it fails
+ // to write to the underlying writer, which is a bytes.Buffer
+ // that never fails.
+ panic(err)
+ }
+ if err := w.Close(); err != nil {
+ panic(err)
+ }
+ return buf.Bytes()
+}
+
+// Gunzip uncompresses the given data.
+func Gunzip(data []byte) ([]byte, error) {
+ r, err := gzip.NewReader(bytes.NewReader(data))
+ if err != nil {
+ return nil, err
+ }
+ return ioutil.ReadAll(r)
+}
+
+// ReadSHA256 returns the SHA256 hash of the contents read from source
+// (hex encoded) and the size of the source in bytes.
+func ReadSHA256(source io.Reader) (string, int64, error) {
+ hash := sha256.New()
+ size, err := io.Copy(hash, source)
+ if err != nil {
+ return "", 0, err
+ }
+ digest := hex.EncodeToString(hash.Sum(nil))
+ return digest, size, nil
+}
+
+// ReadFileSHA256 is like ReadSHA256 but reads the contents of the
+// given file.
+func ReadFileSHA256(filename string) (string, int64, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return "", 0, err
+ }
+ defer f.Close()
+ return ReadSHA256(f)
+}
diff --git a/automation/vendor/github.com/juju/utils/username.go b/automation/vendor/github.com/juju/utils/username.go
new file mode 100644
index 0000000..5107e3b
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/username.go
@@ -0,0 +1,77 @@
+// Copyright 2015 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "os"
+ "os/user"
+
+ "github.com/juju/errors"
+)
+
+// ResolveSudo returns the original username if sudo was used. The
+// original username is extracted from the OS environment.
+func ResolveSudo(username string) string {
+ return resolveSudo(username, os.Getenv)
+}
+
+func resolveSudo(username string, getenvFunc func(string) string) string {
+ if username != "root" {
+ return username
+ }
+ // sudo was probably called, get the original user.
+ if username := getenvFunc("SUDO_USER"); username != "" {
+ return username
+ }
+ return username
+}
+
+// EnvUsername returns the username from the OS environment.
+func EnvUsername() (string, error) {
+ return os.Getenv("USER"), nil
+}
+
+// OSUsername returns the username of the current OS user (based on UID).
+func OSUsername() (string, error) {
+ u, err := user.Current()
+ if err != nil {
+ return "", errors.Trace(err)
+ }
+ return u.Username, nil
+}
+
+// ResolveUsername returns the username determined by the provided
+// functions. The functions are tried in the same order in which they
+// were passed in. An error returned from any of them is immediately
+// returned. If an empty string is returned then that signals that the
+// function did not find the username and the next function is tried.
+// Once a username is found, the provided resolveSudo func (if any) is
+// called with that username and the result is returned. If no username
+// is found then errors.NotFound is returned.
+func ResolveUsername(resolveSudo func(string) string, usernameFuncs ...func() (string, error)) (string, error) {
+ for _, usernameFunc := range usernameFuncs {
+ username, err := usernameFunc()
+ if err != nil {
+ return "", errors.Trace(err)
+ }
+ if username != "" {
+ if resolveSudo != nil {
+ if original := resolveSudo(username); original != "" {
+ username = original
+ }
+ }
+ return username, nil
+ }
+ }
+ return "", errors.NotFoundf("username")
+}
+
+// LocalUsername determines the current username on the local host.
+func LocalUsername() (string, error) {
+ username, err := ResolveUsername(ResolveSudo, EnvUsername, OSUsername)
+ if err != nil {
+ return "", errors.Annotatef(err, "cannot get current user from the environment: %v", os.Environ())
+ }
+ return username, nil
+}
diff --git a/automation/vendor/github.com/juju/utils/uuid.go b/automation/vendor/github.com/juju/utils/uuid.go
new file mode 100644
index 0000000..2404efc
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/uuid.go
@@ -0,0 +1,90 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "regexp"
+ "strings"
+)
+
+// UUID represent a universal identifier with 16 octets.
+type UUID [16]byte
+
+// regex for validating that the UUID matches RFC 4122.
+// This package generates version 4 UUIDs but
+// accepts any UUID version.
+// http://www.ietf.org/rfc/rfc4122.txt
+var (
+ block1 = "[0-9a-f]{8}"
+ block2 = "[0-9a-f]{4}"
+ block3 = "[0-9a-f]{4}"
+ block4 = "[0-9a-f]{4}"
+ block5 = "[0-9a-f]{12}"
+
+ UUIDSnippet = block1 + "-" + block2 + "-" + block3 + "-" + block4 + "-" + block5
+ validUUID = regexp.MustCompile("^" + UUIDSnippet + "$")
+)
+
+func UUIDFromString(s string) (UUID, error) {
+ if !IsValidUUIDString(s) {
+ return UUID{}, fmt.Errorf("invalid UUID: %q", s)
+ }
+ s = strings.Replace(s, "-", "", 4)
+ raw, err := hex.DecodeString(s)
+ if err != nil {
+ return UUID{}, err
+ }
+ var uuid UUID
+ copy(uuid[:], raw)
+ return uuid, nil
+}
+
+// IsValidUUIDString returns true, if the given string matches a valid UUID (version 4, variant 2).
+func IsValidUUIDString(s string) bool {
+ return validUUID.MatchString(s)
+}
+
+// MustNewUUID returns a new uuid, if an error occurs it panics.
+func MustNewUUID() UUID {
+ uuid, err := NewUUID()
+ if err != nil {
+ panic(err)
+ }
+ return uuid
+}
+
+// NewUUID generates a new version 4 UUID relying only on random numbers.
+func NewUUID() (UUID, error) {
+ uuid := UUID{}
+ if _, err := io.ReadFull(rand.Reader, []byte(uuid[0:16])); err != nil {
+ return UUID{}, err
+ }
+ // Set version (4) and variant (2) according to RfC 4122.
+ var version byte = 4 << 4
+ var variant byte = 8 << 4
+ uuid[6] = version | (uuid[6] & 15)
+ uuid[8] = variant | (uuid[8] & 15)
+ return uuid, nil
+}
+
+// Copy returns a copy of the UUID.
+func (uuid UUID) Copy() UUID {
+ uuidCopy := uuid
+ return uuidCopy
+}
+
+// Raw returns a copy of the UUID bytes.
+func (uuid UUID) Raw() [16]byte {
+ return [16]byte(uuid)
+}
+
+// String returns a hexadecimal string representation with
+// standardized separators.
+func (uuid UUID) String() string {
+ return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:16])
+}
diff --git a/automation/vendor/github.com/juju/utils/yaml.go b/automation/vendor/github.com/juju/utils/yaml.go
new file mode 100644
index 0000000..2d443a0
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/yaml.go
@@ -0,0 +1,107 @@
+// Copyright 2012, 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package utils
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "github.com/juju/errors"
+
+ "gopkg.in/yaml.v2"
+)
+
+// WriteYaml marshals obj as yaml to a temporary file in the same directory
+// as path, than atomically replaces path with the temporary file.
+func WriteYaml(path string, obj interface{}) error {
+ data, err := yaml.Marshal(obj)
+ if err != nil {
+ return errors.Trace(err)
+ }
+ dir := filepath.Dir(path)
+ f, err := ioutil.TempFile(dir, "juju")
+ if err != nil {
+ return errors.Trace(err)
+ }
+ tmp := f.Name()
+ if _, err := f.Write(data); err != nil {
+ f.Close() // don't leak file handle
+ os.Remove(tmp) // don't leak half written files on disk
+ return errors.Trace(err)
+ }
+ // Explicitly close the file before moving it. This is needed on Windows
+ // where the OS will not allow us to move a file that still has an open
+ // file handle. Must check the error on close because filesystems can delay
+ // reporting errors until the file is closed.
+ if err := f.Close(); err != nil {
+ os.Remove(tmp) // don't leak half written files on disk
+ return errors.Trace(err)
+ }
+
+ // ioutils.TempFile creates files 0600, but this function has a contract
+ // that files will be world readable, 0644 after replacement.
+ if err := os.Chmod(tmp, 0644); err != nil {
+ os.Remove(tmp) // remove file with incorrect permissions.
+ return errors.Trace(err)
+ }
+
+ return ReplaceFile(tmp, path)
+}
+
+// ReadYaml unmarshals the yaml contained in the file at path into obj. See
+// goyaml.Unmarshal. If path is not found, the error returned will be compatible
+// with os.IsNotExist.
+func ReadYaml(path string, obj interface{}) error {
+ data, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err // cannot wrap here because callers check for NotFound.
+ }
+ return yaml.Unmarshal(data, obj)
+}
+
+// ConformYAML ensures all keys of any nested maps are strings. This is
+// necessary because YAML unmarshals map[interface{}]interface{} in nested
+// maps, which cannot be serialized by json or bson. Also, handle
+// []interface{}. cf. gopkg.in/juju/charm.v4/actions.go cleanse
+func ConformYAML(input interface{}) (interface{}, error) {
+ switch typedInput := input.(type) {
+
+ case map[string]interface{}:
+ newMap := make(map[string]interface{})
+ for key, value := range typedInput {
+ newValue, err := ConformYAML(value)
+ if err != nil {
+ return nil, err
+ }
+ newMap[key] = newValue
+ }
+ return newMap, nil
+
+ case map[interface{}]interface{}:
+ newMap := make(map[string]interface{})
+ for key, value := range typedInput {
+ typedKey, ok := key.(string)
+ if !ok {
+ return nil, errors.New("map keyed with non-string value")
+ }
+ newMap[typedKey] = value
+ }
+ return ConformYAML(newMap)
+
+ case []interface{}:
+ newSlice := make([]interface{}, len(typedInput))
+ for i, sliceValue := range typedInput {
+ newSliceValue, err := ConformYAML(sliceValue)
+ if err != nil {
+ return nil, errors.New("map keyed with non-string value")
+ }
+ newSlice[i] = newSliceValue
+ }
+ return newSlice, nil
+
+ default:
+ return input, nil
+ }
+}
diff --git a/automation/vendor/github.com/juju/utils/zfile_windows.go b/automation/vendor/github.com/juju/utils/zfile_windows.go
new file mode 100644
index 0000000..b1a50f1
--- /dev/null
+++ b/automation/vendor/github.com/juju/utils/zfile_windows.go
@@ -0,0 +1,28 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+// mksyscall_windows.pl -l32 file_windows.go
+// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
+
+package utils
+
+import "unsafe"
+import "syscall"
+
+var (
+ modkernel32 = syscall.NewLazyDLL("kernel32.dll")
+
+ procMoveFileExW = modkernel32.NewProc("MoveFileExW")
+)
+
+func moveFileEx(lpExistingFileName *uint16, lpNewFileName *uint16, dwFlags uint32) (err error) {
+ r1, _, e1 := syscall.Syscall(procMoveFileExW.Addr(), 3, uintptr(unsafe.Pointer(lpExistingFileName)), uintptr(unsafe.Pointer(lpNewFileName)), uintptr(dwFlags))
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}