command/push: partially implemented, tests
This commit is contained in:
parent
84c8344794
commit
fbc1551048
|
@ -1,11 +1,26 @@
|
|||
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
|
||||
}
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
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
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"builders": [{"type": "dummy"}]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"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
|
||||
|
|
Loading…
Reference in New Issue