Move docker/import to convey/import.
--- a/convey.yml Sat Mar 30 00:40:25 2019 -0500
+++ b/convey.yml Sat Mar 30 16:28:57 2019 -0500
@@ -6,7 +6,7 @@
# tasks for the default plan
@@ -65,7 +65,7 @@
# tasks for the deploy plan
- convey-${CONVEY_VERSION}-freebsd-amd64
- convey-${CONVEY_VERSION}-darwin-amd64
@@ -87,7 +87,7 @@
# tasks for creating images
build-image-bitbucket-upload:
--- a/docker/docker.go Sat Mar 30 00:40:25 2019 -0500
+++ b/docker/docker.go Sat Mar 30 16:28:57 2019 -0500
@@ -27,7 +27,6 @@
// Tasks is a map of all docker tasks.
Tasks = map[string]tasks.Task{
--- a/path/path.go Sat Mar 30 00:40:25 2019 -0500
+++ b/path/path.go Sat Mar 30 16:28:57 2019 -0500
@@ -34,7 +34,7 @@
return parts[0], trimBase(base, parts[1])
- return parts[0], trimBase(base, DestFromSrc(parts[0]))
+ return parts[0], trimBase(base, parts[0]) func trimBase(base, path string) string {
@@ -48,16 +48,3 @@
return strings.TrimPrefix(path, base)
-// DestFromSrc returns "." if the given filename does not reference a
-// file or directory outside of the current working directory and the
-func DestFromSrc(file string) string {
- clean := strings.TrimRight(file, string(os.PathSeparator))
- if !strings.ContainsRune(clean, os.PathSeparator) {
--- a/path/path_test.go Sat Mar 30 00:40:25 2019 -0500
+++ b/path/path_test.go Sat Mar 30 16:28:57 2019 -0500
@@ -41,8 +41,8 @@
"foo:bar": {"foo", "bar"},
"foo:bar:baz": {"foo", "bar:baz"},
"foo/bar:baz": {"foo/bar", "baz"},
+ "file": {"file", "file"}, + "dir/": {"dir/", "dir/"}, "nested/file": {"nested/file", "nested/file"},
--- a/runtime/runtime.go Sat Mar 30 00:40:25 2019 -0500
+++ b/runtime/runtime.go Sat Mar 30 16:28:57 2019 -0500
@@ -17,6 +17,8 @@
+ "github.com/aphistic/gomol" "bitbucket.org/rw_grim/convey/environment"
"bitbucket.org/rw_grim/convey/state"
@@ -40,6 +42,10 @@
Containers: NewContainers(),
+ rt.cleanupList.Add(func(*gomol.LogAdapter) { --- a/state/state.go Sat Mar 30 00:40:25 2019 -0500
+++ b/state/state.go Sat Mar 30 16:28:57 2019 -0500
@@ -26,6 +26,7 @@
"bitbucket.org/rw_grim/convey/logging"
"bitbucket.org/rw_grim/convey/network"
+ "bitbucket.org/rw_grim/convey/workspace" // State holds all of the runtime data during a run.
@@ -35,7 +36,7 @@
+ Workspace *workspace.Workspace @@ -55,7 +56,7 @@
- ws, err := newWorkspace(pwd)
+ ws, err := workspace.New(pwd) --- a/state/workspace.go Sat Mar 30 00:40:25 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-func newWorkspace(root string) (*Workspace, error) {
- parent := filepath.Join(root, ".convey")
- err := os.MkdirAll(parent, 0700)
- path, err := ioutil.TempDir(parent, "")
- workspace := &Workspace{
-func (ws *Workspace) Destroy() {
- if err := os.RemoveAll(ws.path); err != nil {
- fmt.Printf("error removing worksace: %s\n", err)
- // try to remove the .convey directory. This will only succeed when
- // our state is the only one and that's intentional.
- dir := filepath.Dir(ws.path)
- if err := os.Remove(dir); err != nil {
- fmt.Printf("error removing .convey: %s\n", err)
-func (ws *Workspace) TaskDirectory(name string) (string, error) {
- dir := filepath.Join(ws.path, name)
- err := os.MkdirAll(dir, 0700)
-func (ws *Workspace) Path() string {
--- a/tasks/export.go Sat Mar 30 00:40:25 2019 -0500
+++ b/tasks/export.go Sat Mar 30 16:28:57 2019 -0500
@@ -17,12 +17,12 @@
"github.com/aphistic/gomol"
"bitbucket.org/rw_grim/convey/environment"
+ "bitbucket.org/rw_grim/convey/path" "bitbucket.org/rw_grim/convey/runtime"
@@ -45,21 +45,18 @@
-func (e *Export) copyFile(filename string) error {
- return errors.New("Not implemented")
func (e *Export) Execute(name string, logger *gomol.LogAdapter, env *environment.Environment, rt *runtime.Runtime) error {
- for _, patterns := range e.Files {
- matches, err := filepath.Glob(filepath.Join(rt.State.CfgPath, patterns))
+ for _, pattern := range e.Files { + src, dst := path.ParseFilePath("", pattern) + matches, err := filepath.Glob(filepath.Join(rt.State.Workspace.Path(), src)) for _, match := range matches {
- err = e.copyFile(match)
+ err = rt.State.Workspace.Export(match, dst) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tasks/import.go Sat Mar 30 16:28:57 2019 -0500
@@ -0,0 +1,71 @@
+// Copyright 2016-2019 Gary Kramlich <grim@reaperworld.com> +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + "github.com/aphistic/gomol" + "bitbucket.org/rw_grim/convey/environment" + "bitbucket.org/rw_grim/convey/path" + "bitbucket.org/rw_grim/convey/runtime" + Files []string `yaml:"files"` + Path string `yaml:"path"` +// New creates a new Import task. +func (i *Import) New() Task { +// Valid validates the export task. +func (i *Import) Valid() error { +func (i *Import) Execute(name string, logger *gomol.LogAdapter, env *environment.Environment, rt *runtime.Runtime) error { + for _, pattern := range i.Files { + src, dst := path.ParseFilePath(rt.State.CfgPath, pattern) + matches, err := filepath.Glob(filepath.Join(rt.State.CfgPath, src)) + return fmt.Errorf("no matches found for %s", src) + for _, match := range matches { + err = rt.State.Workspace.Import(match, dst) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tasks/import_test.go Sat Mar 30 16:28:57 2019 -0500
@@ -0,0 +1,84 @@
+// Copyright 2016-2019 Gary Kramlich <grim@reaperworld.com> +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + . "github.com/onsi/gomega" + "bitbucket.org/rw_grim/convey/environment" + "bitbucket.org/rw_grim/convey/logging" + "bitbucket.org/rw_grim/convey/path" + "bitbucket.org/rw_grim/convey/runtime" + "bitbucket.org/rw_grim/convey/state" +func testImport(t *testing.T, files []string) { + g.Expect(err).To(BeNil()) + env := environment.New() + rt := runtime.NewWithEnvironment(st, env) + // create our task and runn it. + importTask := &Import{Files: files} + err = importTask.Execute("", logging.NewAdapter("test"), env, rt) + g.Expect(err).To(BeNil()) + for _, file := range files { + _, dst := path.ParseFilePath(st.CfgPath, file) + _, err = os.Stat(filepath.Join(st.Workspace.Volume(), dst)) + Expect(err).ToNot(Equal(os.IsNotExist(err))) +// TestImportSingleSourceOnly tests that the following yaml functions +func TestImportSingleSourceOnly(t *testing.T) { + testImport(t, []string{"import_test.go"}) +// TestImportSingleSourceWithDestination tests that the following yaml +// single-source-with-destination: +// - import_test.go:imported.go +func TestImportSingleSourceWithDestination(t *testing.T) { + testImport(t, []string{"import_test.go:imported.go"}) --- a/tasks/tasks.go Sat Mar 30 00:40:25 2019 -0500
+++ b/tasks/tasks.go Sat Mar 30 16:28:57 2019 -0500
@@ -30,6 +30,7 @@
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/workspace/fileio.go Sat Mar 30 16:28:57 2019 -0500
@@ -0,0 +1,74 @@
+// Copyright 2016-2019 Gary Kramlich <grim@reaperworld.com> +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +func (ws *Workspace) TaskDirectory(name string) (string, error) { + dir := filepath.Join(ws.path, name) + err := os.MkdirAll(dir, 0700) +// CopyFile copies the contents from src to dst atomically. +// If dst does not exist, CopyFile creates it with permissions perm. +// If the copy fails, CopyFile aborts and dst is preserved. +func (ws *Workspace) copyFile(src, dst string, perm os.FileMode) error { + in, err := os.Open(src) + tmp, err := ioutil.TempFile(filepath.Dir(dst), "") + _, err = io.Copy(tmp, in) + if err = tmp.Close(); err != nil { + if err = os.Chmod(tmp.Name(), perm); err != nil { + return os.Rename(tmp.Name(), dst) +func (ws *Workspace) Import(src, dst string) error { + realDst := filepath.Join(ws.volumePath, dst) + return ws.copyFile(src, realDst, 0644) +func (ws *Workspace) Export(src, dst string) error { + realSrc := filepath.Join(ws.volumePath, src) + return ws.copyFile(realSrc, dst, 0644) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/workspace/workspace.go Sat Mar 30 16:28:57 2019 -0500
@@ -0,0 +1,76 @@
+// Copyright 2016-2019 Gary Kramlich <grim@reaperworld.com> +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +func New(root string) (*Workspace, error) { + parent := filepath.Join(root, ".convey") + err := os.MkdirAll(parent, 0700) + path, err := ioutil.TempDir(parent, "") + volumePath := filepath.Join(path, "volume") + err = os.Mkdir(volumePath, 0700) + workspace := &Workspace{ + volumePath: volumePath, +func (ws *Workspace) Destroy() { + if err := os.RemoveAll(ws.path); err != nil { + fmt.Printf("error removing worksace: %s\n", err) + // try to remove the .convey directory. This will only succeed when + // our state is the only one and that's intentional. + dir := filepath.Dir(ws.path) + if err := os.Remove(dir); err != nil { + fmt.Printf("error removing .convey: %s\n", err) +func (ws *Workspace) Path() string { +func (ws *Workspace) Volume() string {