Commit fbc15510 authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

command/push: partially implemented, tests

parent 84c83447
package command
import (
"path/filepath"
"testing"
"github.com/mitchellh/cli"
)
const fixturesDir = "./test-fixtures"
func fatalCommand(t *testing.T, m Meta) {
ui := m.Ui.(*cli.MockUi)
t.Fatalf(
"Bad exit code.\n\nStdout:\n\n%s\n\nStderr:\n\n%s",
ui.OutputWriter.String(),
ui.ErrorWriter.String())
}
func testFixture(n string) string {
return filepath.Join(fixturesDir, n)
}
func testMeta(t *testing.T) Meta {
return Meta{
Ui: new(cli.MockUi),
......
......@@ -3,18 +3,30 @@ package command
import (
"flag"
"fmt"
"io"
"path/filepath"
"strings"
"github.com/hashicorp/harmony-go/archive"
"github.com/mitchellh/packer/packer"
)
// archiveTemplateEntry is the name the template always takes within the slug.
const archiveTemplateEntry = ".packer-template.json"
type PushCommand struct {
Meta
// For tests:
uploadFn func(io.Reader, *uploadOpts) (<-chan struct{}, <-chan error, error)
}
func (c *PushCommand) Run(args []string) int {
var token string
f := flag.NewFlagSet("push", flag.ContinueOnError)
f.Usage = func() { c.Ui.Error(c.Help()) }
f.StringVar(&token, "token", "", "token")
if err := f.Parse(args); err != nil {
return 1
}
......@@ -32,8 +44,66 @@ func (c *PushCommand) Run(args []string) int {
return 1
}
// TODO: validate the template
println(tpl.Push.Name)
// Validate some things
if tpl.Push.Name == "" {
c.Ui.Error(fmt.Sprintf(
"The 'push' section must be specified in the template with\n" +
"at least the 'name' option set."))
return 1
}
// Build the archiving options
var opts archive.ArchiveOpts
opts.Include = tpl.Push.Include
opts.Exclude = tpl.Push.Exclude
opts.VCS = tpl.Push.VCS
opts.Extra = map[string]string{
archiveTemplateEntry: args[0],
}
// Determine the path we're archiving
path := tpl.Push.BaseDir
if path == "" {
path, err = filepath.Abs(args[0])
if err != nil {
c.Ui.Error(fmt.Sprintf("Error determining path to archive: %s", err))
return 1
}
path = filepath.Dir(path)
}
// Build the upload options
var uploadOpts uploadOpts
uploadOpts.Slug = tpl.Push.Name
uploadOpts.Token = token
// Start the archiving process
r, archiveErrCh, err := archive.Archive(path, &opts)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error archiving: %s", err))
return 1
}
// Start the upload process
doneCh, uploadErrCh, err := c.upload(r, &uploadOpts)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error starting upload: %s", err))
return 1
}
err = nil
select {
case err = <-archiveErrCh:
err = fmt.Errorf("Error archiving: %s", err)
case err = <-uploadErrCh:
err = fmt.Errorf("Error uploading: %s", err)
case <-doneCh:
}
if err != nil {
c.Ui.Error(err.Error())
return 1
}
return 0
}
......@@ -45,6 +115,11 @@ Usage: packer push [options] TEMPLATE
Push the template and the files it needs to a Packer build service.
This will not initiate any builds, it will only update the templates
used for builds.
Options:
-token=<token> Access token to use to upload. If blank, the
TODO environmental variable will be used.
`
return strings.TrimSpace(helpText)
......@@ -53,3 +128,18 @@ Usage: packer push [options] TEMPLATE
func (*PushCommand) Synopsis() string {
return "push template files to a Packer build service"
}
func (c *PushCommand) upload(
r io.Reader, opts *uploadOpts) (<-chan struct{}, <-chan error, error) {
if c.uploadFn != nil {
return c.uploadFn(r, opts)
}
return nil, nil, nil
}
type uploadOpts struct {
URL string
Slug string
Token string
}
package command
import (
"fmt"
"archive/tar"
"bytes"
"compress/gzip"
"io"
"path/filepath"
"reflect"
"sort"
"testing"
)
......@@ -19,3 +27,117 @@ func TestPush_multiArgs(t *testing.T) {
t.Fatalf("bad: %#v", code)
}
}
func TestPush(t *testing.T) {
var actualR io.Reader
var actualOpts *uploadOpts
uploadFn := func(r io.Reader, opts *uploadOpts) (<-chan struct{}, <-chan error, error) {
actualR = r
actualOpts = opts
doneCh := make(chan struct{})
close(doneCh)
return doneCh, nil, nil
}
c := &PushCommand{
Meta: testMeta(t),
uploadFn: uploadFn,
}
args := []string{filepath.Join(testFixture("push"), "template.json")}
if code := c.Run(args); code != 0 {
fatalCommand(t, c.Meta)
}
actual := testArchive(t, actualR)
expected := []string{
archiveTemplateEntry,
"template.json",
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestPush_noName(t *testing.T) {
uploadFn := func(r io.Reader, opts *uploadOpts) (<-chan struct{}, <-chan error, error) {
return nil, nil, nil
}
c := &PushCommand{
Meta: testMeta(t),
uploadFn: uploadFn,
}
args := []string{filepath.Join(testFixture("push-no-name"), "template.json")}
if code := c.Run(args); code != 1 {
fatalCommand(t, c.Meta)
}
}
func TestPush_uploadError(t *testing.T) {
uploadFn := func(r io.Reader, opts *uploadOpts) (<-chan struct{}, <-chan error, error) {
return nil, nil, fmt.Errorf("bad")
}
c := &PushCommand{
Meta: testMeta(t),
uploadFn: uploadFn,
}
args := []string{filepath.Join(testFixture("push"), "template.json")}
if code := c.Run(args); code != 1 {
fatalCommand(t, c.Meta)
}
}
func TestPush_uploadErrorCh(t *testing.T) {
uploadFn := func(r io.Reader, opts *uploadOpts) (<-chan struct{}, <-chan error, error) {
errCh := make(chan error, 1)
errCh <- fmt.Errorf("bad")
return nil, errCh, nil
}
c := &PushCommand{
Meta: testMeta(t),
uploadFn: uploadFn,
}
args := []string{filepath.Join(testFixture("push"), "template.json")}
if code := c.Run(args); code != 1 {
fatalCommand(t, c.Meta)
}
}
func testArchive(t *testing.T, r io.Reader) []string {
// Finish the archiving process in-memory
var buf bytes.Buffer
if _, err := io.Copy(&buf, r); err != nil {
t.Fatalf("err: %s", err)
}
gzipR, err := gzip.NewReader(&buf)
if err != nil {
t.Fatalf("err: %s", err)
}
tarR := tar.NewReader(gzipR)
// Read all the entries
result := make([]string, 0, 5)
for {
hdr, err := tarR.Next()
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("err: %s", err)
}
result = append(result, hdr.Name)
}
sort.Strings(result)
return result
}
{
"builders": [{"type": "dummy"}]
}
{
"builders": [{"type": "dummy"}],
"push": {
"name": "foo/bar"
}
}
......@@ -45,6 +45,7 @@ type Template struct {
// PushConfig is the configuration structure for the push settings.
type PushConfig struct {
Name string
BaseDir string
Include []string
Exclude []string
VCS bool
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment