Note: This is a minor issue that I didn't get to for M11 that isn't required for M11 and would be fine merging for post-M11. When you specify a template name explicitly (e.g. `pulumi new typescript`), we'll try to download the template tarball without first downloading the JSON list of available templates. The JSON includes a description used when replacing the `${DESCRIPTION}` string in template files. Since we didn't download the JSON, we won't have a description, so we fallback to a default value (`"A Pulumi project."`). This also happens when specifying `--offline` to use an existing template under `~/.pulumi/templates`; we won't have a description for the template, so we fallback to a default description. The fallback value happens to be the same as the description for each of our current templates, so noone will currently notice an issue. For M11, I included initial support for a template manifest file where the description (and any future metadata) could be stored, but didn't go as far as actually reading the file. This change makes it so the CLI actually reads the description from the manifest file (if it exists), otherwise falling back to the default value as is done currently. Some minor related cleanup is included in this change.
343 lines
11 KiB
Go
343 lines
11 KiB
Go
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
|
|
|
package tests
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/user"
|
|
"path"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
ptesting "github.com/pulumi/pulumi/pkg/testing"
|
|
"github.com/pulumi/pulumi/pkg/workspace"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestPulumiNew(t *testing.T) {
|
|
t.Run("SanityTest", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
// Create a temporary local template.
|
|
template := createTemporaryLocalTemplate(t, "")
|
|
defer deleteTemporaryLocalTemplate(t, template)
|
|
|
|
// Create a subdirectory and CD into it.
|
|
subdir := path.Join(e.RootPath, "foo")
|
|
err := os.MkdirAll(subdir, os.ModePerm)
|
|
assert.NoError(t, err, "error creating subdirectory")
|
|
e.CWD = subdir
|
|
|
|
// Run pulumi new.
|
|
e.RunCommand("pulumi", "new", template, "--offline")
|
|
|
|
assertSuccess(t, subdir, "foo", "A Pulumi project.")
|
|
})
|
|
|
|
t.Run("SanityTestWithManifest", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
const description = "My project description."
|
|
|
|
// Create a temporary local template.
|
|
template := createTemporaryLocalTemplate(t, description)
|
|
defer deleteTemporaryLocalTemplate(t, template)
|
|
|
|
// Create a subdirectory and CD into it.
|
|
subdir := path.Join(e.RootPath, "foo")
|
|
err := os.MkdirAll(subdir, os.ModePerm)
|
|
assert.NoError(t, err, "error creating subdirectory")
|
|
e.CWD = subdir
|
|
|
|
// Run pulumi new.
|
|
e.RunCommand("pulumi", "new", template, "--offline")
|
|
|
|
assertSuccess(t, subdir, "foo", description)
|
|
})
|
|
|
|
t.Run("NoTemplateSpecified", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
// Confirm this will result in an error since it isn't an
|
|
// interactive terminal session.
|
|
stdout, stderr := e.RunCommandExpectError("pulumi", "new")
|
|
assert.Equal(t, "", stdout)
|
|
assert.NotEmpty(t, stderr)
|
|
})
|
|
|
|
t.Run("InvalidTemplateName", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
// An invalid template name.
|
|
template := "/this/is\\not/a/valid/templatename"
|
|
|
|
// Confirm this fails.
|
|
stdout, stderr := e.RunCommandExpectError("pulumi", "new", template)
|
|
assert.Equal(t, "", stdout)
|
|
assert.NotEmpty(t, stderr)
|
|
})
|
|
|
|
t.Run("LocalTemplateNotFound", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
// A template that will never exist remotely.
|
|
template := "this-is-not-the-template-youre-looking-for"
|
|
|
|
// Confirm this fails.
|
|
stdout, stderr := e.RunCommandExpectError("pulumi", "new", template, "--offline")
|
|
assert.Equal(t, "", stdout)
|
|
assert.NotEmpty(t, stderr)
|
|
|
|
// Ensure the unknown template dir doesn't remain in the home directory.
|
|
_, err := os.Stat(getTemplateDir(t, template))
|
|
assert.Error(t, err, "dir shouldn't exist")
|
|
})
|
|
|
|
t.Run("RemoteTemplateNotFound", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
// A template that will never exist remotely.
|
|
template := "this-is-not-the-template-youre-looking-for"
|
|
|
|
// Confirm this fails.
|
|
stdout, stderr := e.RunCommandExpectError("pulumi", "new", template)
|
|
assert.Equal(t, "", stdout)
|
|
assert.NotEmpty(t, stderr)
|
|
|
|
// Ensure the unknown template dir doesn't remain in the home directory.
|
|
_, err := os.Stat(getTemplateDir(t, template))
|
|
assert.Error(t, err, "dir shouldn't exist")
|
|
})
|
|
|
|
t.Run("NameDescriptionPassedExplicitly", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
// Create a temporary local template.
|
|
template := createTemporaryLocalTemplate(t, "")
|
|
defer deleteTemporaryLocalTemplate(t, template)
|
|
|
|
// Run pulumi new.
|
|
e.RunCommand("pulumi", "new", template, "--name", "bar", "--description", "A project.", "--offline")
|
|
|
|
assertSuccess(t, e.CWD, "bar", "A project.")
|
|
})
|
|
|
|
t.Run("WorkingDirNameIsntAValidProjectName", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
// Create a temporary local template.
|
|
template := createTemporaryLocalTemplate(t, "")
|
|
defer deleteTemporaryLocalTemplate(t, template)
|
|
|
|
// Create a subdirectory that contains an invalid char
|
|
// for project names and CD into it.
|
|
subdir := path.Join(e.RootPath, "@foo")
|
|
err := os.MkdirAll(subdir, os.ModePerm)
|
|
assert.NoError(t, err, "error creating subdirectory")
|
|
e.CWD = subdir
|
|
|
|
// Run pulumi new.
|
|
e.RunCommand("pulumi", "new", template, "--offline")
|
|
|
|
// Assert the default name used is "foo" not "@foo".
|
|
assertSuccess(t, subdir, "foo", "A Pulumi project.")
|
|
})
|
|
|
|
t.Run("ExistingFileNotOverwritten", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
// Create a temporary local template.
|
|
template := createTemporaryLocalTemplate(t, "")
|
|
defer deleteTemporaryLocalTemplate(t, template)
|
|
|
|
// Create a subdirectory and CD into it.
|
|
subdir := path.Join(e.RootPath, "foo")
|
|
err := os.MkdirAll(subdir, os.ModePerm)
|
|
assert.NoError(t, err, "error creating subdirectory")
|
|
e.CWD = subdir
|
|
|
|
// Create an existing Pulumi.yaml.
|
|
err = ioutil.WriteFile(filepath.Join(subdir, "Pulumi.yaml"), []byte("name: blah"), 0600)
|
|
assert.NoError(t, err, "creating Pulumi.yaml")
|
|
|
|
// Confirm failure due to existing file.
|
|
stdout, stderr := e.RunCommandExpectError("pulumi", "new", template, "--offline")
|
|
assert.Equal(t, "", stdout)
|
|
assert.Contains(t, stderr, "Pulumi.yaml")
|
|
|
|
// Confirm the contents of the file wasn't changed.
|
|
content := readFile(t, filepath.Join(subdir, "Pulumi.yaml"))
|
|
assert.Equal(t, "name: blah", content)
|
|
|
|
// Confirm no other files were copied over.
|
|
infos, err := ioutil.ReadDir(subdir)
|
|
assert.NoError(t, err, "reading the dir")
|
|
assert.Equal(t, 1, len(infos))
|
|
assert.Equal(t, "Pulumi.yaml", infos[0].Name())
|
|
assert.False(t, infos[0].IsDir())
|
|
})
|
|
|
|
t.Run("MultipleExistingFilesNotOverwritten", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
// Create a temporary local template.
|
|
template := createTemporaryLocalTemplate(t, "")
|
|
defer deleteTemporaryLocalTemplate(t, template)
|
|
|
|
// Create a subdirectory and CD into it.
|
|
subdir := path.Join(e.RootPath, "foo")
|
|
err := os.MkdirAll(subdir, os.ModePerm)
|
|
assert.NoError(t, err, "error creating subdirectory")
|
|
e.CWD = subdir
|
|
|
|
// Create an existing Pulumi.yaml.
|
|
err = ioutil.WriteFile(filepath.Join(subdir, "Pulumi.yaml"), []byte("name: blah"), 0600)
|
|
assert.NoError(t, err, "creating Pulumi.yaml")
|
|
|
|
// Create an existing test2.txt.
|
|
err = ioutil.WriteFile(filepath.Join(subdir, "test2.txt"), []byte("foo"), 0600)
|
|
assert.NoError(t, err, "creating test2.txt")
|
|
|
|
// Confirm failure due to existing files.
|
|
stdout, stderr := e.RunCommandExpectError("pulumi", "new", template, "--offline")
|
|
assert.Equal(t, "", stdout)
|
|
assert.Contains(t, stderr, "Pulumi.yaml")
|
|
assert.Contains(t, stderr, "test2.txt")
|
|
|
|
// Confirm the contents of Pulumi.yaml wasn't changed.
|
|
content := readFile(t, filepath.Join(subdir, "Pulumi.yaml"))
|
|
assert.Equal(t, "name: blah", content)
|
|
|
|
// Confirm the contents of test2.txt wasn't changed.
|
|
content = readFile(t, filepath.Join(subdir, "test2.txt"))
|
|
assert.Equal(t, "foo", content)
|
|
|
|
// Confirm no other files were copied over.
|
|
infos, err := ioutil.ReadDir(subdir)
|
|
assert.NoError(t, err, "reading the dir")
|
|
assert.Equal(t, 2, len(infos))
|
|
for _, info := range infos {
|
|
assert.True(t, info.Name() == "Pulumi.yaml" || info.Name() == "test2.txt")
|
|
assert.False(t, info.IsDir())
|
|
}
|
|
})
|
|
|
|
t.Run("MultipleExistingFilesForce", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
// Create a temporary local template.
|
|
template := createTemporaryLocalTemplate(t, "")
|
|
defer deleteTemporaryLocalTemplate(t, template)
|
|
|
|
// Create a subdirectory and CD into it.
|
|
subdir := path.Join(e.RootPath, "foo")
|
|
err := os.MkdirAll(subdir, os.ModePerm)
|
|
assert.NoError(t, err, "error creating subdirectory")
|
|
e.CWD = subdir
|
|
|
|
// Create an existing Pulumi.yaml.
|
|
err = ioutil.WriteFile(filepath.Join(subdir, "Pulumi.yaml"), []byte("name: blah"), 0600)
|
|
assert.NoError(t, err, "creating Pulumi.yaml")
|
|
|
|
// Create an existing test2.txt.
|
|
err = ioutil.WriteFile(filepath.Join(subdir, "test2.txt"), []byte("foo"), 0600)
|
|
assert.NoError(t, err, "creating test2.txt")
|
|
|
|
// Run pulumi new with --force.
|
|
e.RunCommand("pulumi", "new", template, "--force", "--offline")
|
|
|
|
assertSuccess(t, subdir, "foo", "A Pulumi project.")
|
|
})
|
|
}
|
|
|
|
func assertSuccess(t *testing.T, dir string, expectedProjectName string, expectedProjectDescription string) {
|
|
// Confirm the template file was copied/transformed.
|
|
content := readFile(t, filepath.Join(dir, "Pulumi.yaml"))
|
|
assert.Contains(t, content, fmt.Sprintf("name: %s", expectedProjectName))
|
|
assert.Contains(t, content, fmt.Sprintf("description: %s", expectedProjectDescription))
|
|
|
|
// Confirm the test1.txt file was copied.
|
|
content = readFile(t, filepath.Join(dir, "test1.txt"))
|
|
assert.Equal(t, "test1", content)
|
|
|
|
// Confirm the test2.txt file was copied.
|
|
content = readFile(t, filepath.Join(dir, "test2.txt"))
|
|
assert.Equal(t, "test2", content)
|
|
|
|
// Confirm the sub/blah.json file was copied.
|
|
content = readFile(t, filepath.Join(dir, "sub", "blah.json"))
|
|
assert.Equal(t, "{}", content)
|
|
|
|
// Confirm the .pulumi.template.yaml file was skipped.
|
|
_, err := os.Stat(filepath.Join(dir, ".pulumi.template.yaml"))
|
|
assert.Error(t, err)
|
|
|
|
infos, err := ioutil.ReadDir(dir)
|
|
assert.NoError(t, err, "reading dir")
|
|
assert.Equal(t, 4, len(infos))
|
|
}
|
|
|
|
func readFile(t *testing.T, filename string) string {
|
|
b, err := ioutil.ReadFile(filename)
|
|
assert.NoError(t, err, "reading file")
|
|
return string(b)
|
|
}
|
|
|
|
func createTemporaryLocalTemplate(t *testing.T, description string) string {
|
|
name := fmt.Sprintf("%v", time.Now().UnixNano())
|
|
dir := getTemplateDir(t, name)
|
|
err := os.MkdirAll(dir, 0700)
|
|
assert.NoError(t, err, "creating temporary template dir")
|
|
|
|
text := "name: ${PROJECT}\n" +
|
|
"description: ${DESCRIPTION}\n" +
|
|
"runtime: nodejs\n"
|
|
err = ioutil.WriteFile(filepath.Join(dir, "Pulumi.yaml"), []byte(text), 0600)
|
|
assert.NoError(t, err, "creating Pulumi.yaml")
|
|
|
|
err = ioutil.WriteFile(filepath.Join(dir, "test1.txt"), []byte("test1"), 0600)
|
|
assert.NoError(t, err, "creating test1.txt")
|
|
|
|
err = ioutil.WriteFile(filepath.Join(dir, "test2.txt"), []byte("test2"), 0600)
|
|
assert.NoError(t, err, "creating test2.txt")
|
|
|
|
err = os.MkdirAll(filepath.Join(dir, "sub"), os.ModePerm)
|
|
assert.NoError(t, err, "creating sub")
|
|
|
|
err = ioutil.WriteFile(filepath.Join(dir, "sub", "blah.json"), []byte("{}"), 0600)
|
|
assert.NoError(t, err, "creating sub/blah.json")
|
|
|
|
// If description is not empty, write it out in a manifest file.
|
|
if description != "" {
|
|
descriptionBytes := []byte(fmt.Sprintf("description: %s", description))
|
|
err = ioutil.WriteFile(filepath.Join(dir, ".pulumi.template.yaml"), descriptionBytes, 0600)
|
|
assert.NoError(t, err, "creating .pulumi.template.yaml")
|
|
}
|
|
|
|
return name
|
|
}
|
|
|
|
func deleteTemporaryLocalTemplate(t *testing.T, name string) {
|
|
err := os.RemoveAll(getTemplateDir(t, name))
|
|
assert.NoError(t, err, "deleting temporary template dir")
|
|
}
|
|
|
|
func getTemplateDir(t *testing.T, name string) string {
|
|
user, err := user.Current()
|
|
assert.NoError(t, err, "getting home directory")
|
|
return filepath.Join(user.HomeDir, workspace.BookkeepingDir, workspace.TemplateDir, name)
|
|
}
|