diff --git a/README.md b/README.md index cf3df518e5fa..6caeff6bae86 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,13 @@ For example, create three web servers: ```typescript let aws = require("@pulumi/aws"); let sg = new aws.ec2.SecurityGroup("web-sg", { - ingress: [{ protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"]}], + ingress: [{ protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] }], }); for (let i = 0; i < 3; i++) { new aws.ec2.Instance(`web-${i}`, { ami: "ami-7172b611", instanceType: "t2.micro", - securityGroups: [ sg.name ], + vpcSecurityGroupIds: [sg.id], userData: `#!/bin/bash echo "Hello, World!" > index.html nohup python -m SimpleHTTPServer 80 &`, diff --git a/pkg/codegen/docs/gen.go b/pkg/codegen/docs/gen.go index 013d48090375..72be15914ec8 100644 --- a/pkg/codegen/docs/gen.go +++ b/pkg/codegen/docs/gen.go @@ -43,7 +43,6 @@ import ( "github.com/pulumi/pulumi/pkg/v3/codegen/nodejs" "github.com/pulumi/pulumi/pkg/v3/codegen/python" "github.com/pulumi/pulumi/pkg/v3/codegen/schema" - "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" ) //go:embed templates/*.tmpl @@ -1690,14 +1689,6 @@ func (mod *modContext) getTypes(member interface{}, types nestedTypeUsageInfo) { } } -type fs map[string][]byte - -func (fs fs) add(path string, contents []byte) { - _, has := fs[path] - contract.Assertf(!has, "duplicate file: %s", path) - fs[path] = contents -} - // getModuleFileName returns the file name to use for a module. func (mod *modContext) getModuleFileName() string { dctx := mod.docGenContext @@ -1713,13 +1704,13 @@ func (mod *modContext) getModuleFileName() string { return mod.mod } -func (mod *modContext) gen(fs fs) error { +func (mod *modContext) gen(fs codegen.Fs) error { dctx := mod.docGenContext modName := mod.getModuleFileName() addFile := func(name, contents string) { p := path.Join(modName, name, "_index.md") - fs.add(p, []byte(contents)) + fs.Add(p, []byte(contents)) } // Resources @@ -1758,7 +1749,7 @@ func (mod *modContext) gen(fs fs) error { return err } - fs.add(path.Join(modName, "_index.md"), buffer.Bytes()) + fs.Add(path.Join(modName, "_index.md"), buffer.Bytes()) return nil } @@ -2090,7 +2081,7 @@ func (dctx *docGenContext) generatePackage(tool string, pkg *schema.Package) (ma defer glog.Flush() glog.V(3).Infoln("generating package docs now...") - files := fs{} + files := codegen.Fs{} modules := []string{} modMap := dctx.modules() for k := range modMap { diff --git a/pkg/codegen/dotnet/gen.go b/pkg/codegen/dotnet/gen.go index e81f9e80bc47..c290ada3fc53 100644 --- a/pkg/codegen/dotnet/gen.go +++ b/pkg/codegen/dotnet/gen.go @@ -1961,14 +1961,6 @@ func (mod *modContext) genConfig(variables []*schema.Property) (string, error) { return w.String(), nil } -type fs map[string][]byte - -func (fs fs) add(path string, contents []byte) { - _, has := fs[path] - contract.Assertf(!has, "duplicate file: %s", path) - fs[path] = contents -} - func (mod *modContext) genUtilities() (string, error) { // Strip any 'v' off of the version. w := &bytes.Buffer{} @@ -1986,7 +1978,7 @@ func (mod *modContext) genUtilities() (string, error) { return w.String(), nil } -func (mod *modContext) gen(fs fs) error { +func (mod *modContext) gen(fs codegen.Fs) error { nsComponents := strings.Split(mod.namespaceName, ".") if len(nsComponents) > 0 { // Trim off "Pulumi.Pkg" @@ -2012,7 +2004,7 @@ func (mod *modContext) gen(fs fs) error { addFile := func(name, contents string) { p := path.Join(dir, name) files = append(files, p) - fs.add(p, []byte(contents)) + fs.Add(p, []byte(contents)) } // Ensure that the target module directory contains a README.md file. @@ -2020,7 +2012,7 @@ func (mod *modContext) gen(fs fs) error { if readme != "" && readme[len(readme)-1] != '\n' { readme += "\n" } - fs.add(filepath.Join(dir, "README.md"), []byte(readme)) + fs.Add(filepath.Join(dir, "README.md"), []byte(readme)) // Utilities, config switch mod.mod { @@ -2029,7 +2021,7 @@ func (mod *modContext) gen(fs fs) error { if err != nil { return err } - fs.add("Utilities.cs", []byte(utilities)) + fs.Add("Utilities.cs", []byte(utilities)) case "config": if len(mod.pkg.Config) > 0 { config, err := mod.genConfig(mod.pkg.Config) @@ -2158,7 +2150,7 @@ func genPackageMetadata(pkg *schema.Package, assemblyName string, packageReferences map[string]string, projectReferences []string, - files fs) error { + files codegen.Fs) error { projectFile, err := genProjectFile(pkg, assemblyName, packageReferences, projectReferences) if err != nil { @@ -2177,7 +2169,7 @@ func genPackageMetadata(pkg *schema.Package, lang, ok := pkg.Language["csharp"].(CSharpPackageInfo) if pkg.Version != nil && ok && lang.RespectSchemaVersion { - files.add("version.txt", []byte(pkg.Version.String())) + files.Add("version.txt", []byte(pkg.Version.String())) pulumiPlugin.Version = pkg.Version.String() } @@ -2186,9 +2178,9 @@ func genPackageMetadata(pkg *schema.Package, return err } - files.add(assemblyName+".csproj", projectFile) - files.add("logo.png", logo) - files.add("pulumi-plugin.json", plugin) + files.Add(assemblyName+".csproj", projectFile) + files.Add("logo.png", logo) + files.Add("pulumi-plugin.json", plugin) return nil } @@ -2472,9 +2464,9 @@ func GeneratePackage(tool string, pkg *schema.Package, extraFiles map[string][]b assemblyName := info.GetRootNamespace() + "." + namespaceName(info.Namespaces, pkg.Name) // Generate each module. - files := fs{} + files := codegen.Fs{} for p, f := range extraFiles { - files.add(p, f) + files.Add(p, f) } for _, mod := range modules { diff --git a/pkg/codegen/go/gen.go b/pkg/codegen/go/gen.go index da01ae9a0f82..029f2ef7e03f 100644 --- a/pkg/codegen/go/gen.go +++ b/pkg/codegen/go/gen.go @@ -3560,7 +3560,7 @@ func GeneratePackage(tool string, pkg *schema.Package) (map[string][]byte, error name := packageName(pkg) pathPrefix := packageRoot(pkg) - files := map[string][]byte{} + files := codegen.Fs{} // Generate pulumi-plugin.json pulumiPlugin := &plugin.PulumiPluginJSON{ @@ -3575,13 +3575,10 @@ func GeneratePackage(tool string, pkg *schema.Package) (map[string][]byte, error if err != nil { return nil, fmt.Errorf("Failed to format pulumi-plugin.json: %w", err) } - files[path.Join(pathPrefix, "pulumi-plugin.json")] = pulumiPluginJSON + files.Add(path.Join(pathPrefix, "pulumi-plugin.json"), pulumiPluginJSON) setFile := func(relPath, contents string) { relPath = path.Join(pathPrefix, relPath) - if _, ok := files[relPath]; ok { - panic(fmt.Errorf("duplicate file: %s", relPath)) - } // Run Go formatter on the code before saving to disk formattedSource, err := format.Source([]byte(contents)) @@ -3590,7 +3587,7 @@ func GeneratePackage(tool string, pkg *schema.Package) (map[string][]byte, error panic(fmt.Errorf("invalid Go source code:\n\n%s\n: %w", relPath, err)) } - files[relPath] = formattedSource + files.Add(relPath, formattedSource) } for _, mod := range pkgMods { diff --git a/pkg/codegen/nodejs/gen.go b/pkg/codegen/nodejs/gen.go index a6667b17a649..20bdcabfa2e9 100644 --- a/pkg/codegen/nodejs/gen.go +++ b/pkg/codegen/nodejs/gen.go @@ -2024,14 +2024,6 @@ func (mod *modContext) genEnum(w io.Writer, enum *schema.EnumType) error { return nil } -type fs map[string][]byte - -func (fs fs) add(path string, contents []byte) { - _, has := fs[path] - contract.Assertf(!has, "duplicate file: %s", path) - fs[path] = contents -} - func (mod *modContext) isReservedSourceFileName(name string) bool { switch name { case "index.ts": @@ -2047,7 +2039,7 @@ func (mod *modContext) isReservedSourceFileName(name string) bool { } } -func (mod *modContext) gen(fs fs) error { +func (mod *modContext) gen(fs codegen.Fs) error { var files []fileInfo for _, path := range mod.extraSourceFiles { files = append(files, fileInfo{ @@ -2064,7 +2056,7 @@ func (mod *modContext) gen(fs fs) error { fileType: fileType, pathToNodeModule: p, }) - fs.add(p, []byte(contents)) + fs.Add(p, []byte(contents)) } addResourceFile := func(resourceFileInfo resourceFileInfo, name, contents string) { @@ -2074,7 +2066,7 @@ func (mod *modContext) gen(fs fs) error { resourceFileInfo: resourceFileInfo, pathToNodeModule: p, }) - fs.add(p, []byte(contents)) + fs.Add(p, []byte(contents)) } addFunctionFile := func(info functionFileInfo, name, contents string) { @@ -2084,7 +2076,7 @@ func (mod *modContext) gen(fs fs) error { functionFileInfo: info, pathToNodeModule: p, }) - fs.add(p, []byte(contents)) + fs.Add(p, []byte(contents)) } // Utilities, config, readme @@ -2093,7 +2085,7 @@ func (mod *modContext) gen(fs fs) error { buffer := &bytes.Buffer{} mod.genHeader(buffer, nil, nil, nil) mod.genUtilitiesFile(buffer) - fs.add(path.Join(modDir, "utilities.ts"), buffer.Bytes()) + fs.Add(path.Join(modDir, "utilities.ts"), buffer.Bytes()) // Ensure that the top-level (provider) module directory contains a README.md file. readme := mod.pkg.Language["nodejs"].(NodePackageInfo).Readme @@ -2112,7 +2104,7 @@ func (mod *modContext) gen(fs fs) error { if readme != "" && readme[len(readme)-1] != '\n' { readme += "\n" } - fs.add(path.Join(modDir, "README.md"), []byte(readme)) + fs.Add(path.Join(modDir, "README.md"), []byte(readme)) case "config": if len(mod.pkg.Config) > 0 { buffer := &bytes.Buffer{} @@ -2186,7 +2178,7 @@ func (mod *modContext) gen(fs fs) error { fileName = path.Join(modDir, "index.ts") } fileName = path.Join("types", "enums", fileName) - fs.add(fileName, buffer.Bytes()) + fs.Add(fileName, buffer.Bytes()) } // Nested types @@ -2197,12 +2189,12 @@ func (mod *modContext) gen(fs fs) error { return err } for _, file := range files { - fs.add(file.name(), file.contents()) + fs.Add(file.name(), file.contents()) } } // Index - fs.add(path.Join(modDir, "index.ts"), []byte(mod.genIndex(files))) + fs.Add(path.Join(modDir, "index.ts"), []byte(mod.genIndex(files))) return nil } @@ -2449,14 +2441,14 @@ func (mod *modContext) genEnums(buffer *bytes.Buffer, enums []*schema.EnumType) } // genPackageMetadata generates all the non-code metadata required by a Pulumi package. -func genPackageMetadata(pkg *schema.Package, info NodePackageInfo, files fs) error { +func genPackageMetadata(pkg *schema.Package, info NodePackageInfo, fs codegen.Fs) error { // The generator already emitted Pulumi.yaml, so that leaves three more files to write out: // 1) package.json: minimal NPM package metadata // 2) tsconfig.json: instructions for TypeScript compilation // 3) install-pulumi-plugin.js: plugin install script - files.add("package.json", []byte(genNPMPackageMetadata(pkg, info))) - files.add("tsconfig.json", []byte(genTypeScriptProjectFile(info, files))) - files.add("scripts/install-pulumi-plugin.js", []byte(genInstallScript(pkg.PluginDownloadURL))) + fs.Add("package.json", []byte(genNPMPackageMetadata(pkg, info))) + fs.Add("tsconfig.json", []byte(genTypeScriptProjectFile(info, fs))) + fs.Add("scripts/install-pulumi-plugin.js", []byte(genInstallScript(pkg.PluginDownloadURL))) return nil } @@ -2577,7 +2569,7 @@ func genNPMPackageMetadata(pkg *schema.Package, info NodePackageInfo) string { return string(npmjson) + "\n" } -func genTypeScriptProjectFile(info NodePackageInfo, files fs) string { +func genTypeScriptProjectFile(info NodePackageInfo, files codegen.Fs) string { w := &bytes.Buffer{} fmt.Fprintf(w, `{ @@ -2845,9 +2837,9 @@ func GeneratePackage(tool string, pkg *schema.Package, extraFiles map[string][]b } pkg.Language["nodejs"] = info - files := fs{} + files := codegen.Fs{} for p, f := range extraFiles { - files.add(p, f) + files.Add(p, f) } for _, mod := range modules { if err := mod.gen(files); err != nil { diff --git a/pkg/codegen/python/gen.go b/pkg/codegen/python/gen.go index c2af1f7aca9e..3dcbe76973ee 100644 --- a/pkg/codegen/python/gen.go +++ b/pkg/codegen/python/gen.go @@ -397,14 +397,6 @@ func relPathToRelImport(relPath string) string { return relImport } -type fs map[string][]byte - -func (fs fs) add(path string, contents []byte) { - _, has := fs[path] - contract.Assertf(!has, "duplicate file: %s", path) - fs[path] = contents -} - func (mod *modContext) genUtilitiesFile() []byte { buffer := &bytes.Buffer{} genStandardHeader(buffer, mod.tool) @@ -421,7 +413,7 @@ def get_plugin_download_url(): return buffer.Bytes() } -func (mod *modContext) gen(fs fs) error { +func (mod *modContext) gen(fs codegen.Fs) error { dir := path.Join(mod.pyPkgName, mod.mod) var exports []string @@ -440,14 +432,14 @@ func (mod *modContext) gen(fs fs) error { if !strings.HasSuffix(name, ".pyi") { exports = append(exports, name[:len(name)-len(".py")]) } - fs.add(p, []byte(contents)) + fs.Add(p, []byte(contents)) } // Utilities, config, readme switch mod.mod { case "": - fs.add(filepath.Join(dir, "_utilities.py"), mod.genUtilitiesFile()) - fs.add(filepath.Join(dir, "py.typed"), []byte{}) + fs.Add(filepath.Join(dir, "_utilities.py"), mod.genUtilitiesFile()) + fs.Add(filepath.Join(dir, "py.typed"), []byte{}) // Ensure that the top-level (provider) module directory contains a README.md file. @@ -473,7 +465,7 @@ func (mod *modContext) gen(fs fs) error { readme += "\n" } } - fs.add(filepath.Join(dir, "README.md"), []byte(readme)) + fs.Add(filepath.Join(dir, "README.md"), []byte(readme)) case "config": if len(mod.pkg.Config) > 0 { @@ -546,7 +538,7 @@ func (mod *modContext) gen(fs fs) error { // Index if !mod.isEmpty() { - fs.add(path.Join(dir, "__init__.py"), []byte(mod.genInit(exports))) + fs.Add(path.Join(dir, "__init__.py"), []byte(mod.genInit(exports))) } return nil @@ -927,7 +919,7 @@ func allTypesAreOverlays(types []*schema.ObjectType) bool { return true } -func (mod *modContext) genTypes(dir string, fs fs) error { +func (mod *modContext) genTypes(dir string, fs codegen.Fs) error { genTypes := func(file string, input bool) error { w := &bytes.Buffer{} @@ -1003,7 +995,7 @@ func (mod *modContext) genTypes(dir string, fs fs) error { } } if hasTypes { - fs.add(path.Join(dir, file), w.Bytes()) + fs.Add(path.Join(dir, file), w.Bytes()) } return nil } @@ -2866,9 +2858,9 @@ func GeneratePackage(tool string, pkg *schema.Package, extraFiles map[string][]b pkgName = pyPack(pkg.Name) } - files := fs{} + files := codegen.Fs{} for p, f := range extraFiles { - files.add(filepath.Join(pkgName, p), f) + files.Add(filepath.Join(pkgName, p), f) } for _, mod := range modules { @@ -2882,14 +2874,14 @@ func GeneratePackage(tool string, pkg *schema.Package, extraFiles map[string][]b if err != nil { return nil, err } - files.add(filepath.Join(pkgName, "pulumi-plugin.json"), plugin) + files.Add(filepath.Join(pkgName, "pulumi-plugin.json"), plugin) // Finally emit the package metadata (setup.py). setup, err := genPackageMetadata(tool, pkg, pkgName, info.Requires, info.PythonRequires) if err != nil { return nil, err } - files.add("setup.py", []byte(setup)) + files.Add("setup.py", []byte(setup)) return files, nil } diff --git a/pkg/codegen/schema/schema.go b/pkg/codegen/schema/schema.go index 22c806916e26..b112ae82eb90 100644 --- a/pkg/codegen/schema/schema.go +++ b/pkg/codegen/schema/schema.go @@ -1146,7 +1146,7 @@ func (pkg *Package) marshalFunction(f *Function) (FunctionSpec, error) { if f.Outputs != nil { outs, err := pkg.marshalObject(f.Outputs, true) if err != nil { - return FunctionSpec{}, fmt.Errorf("marshaloutg outputs: %w", err) + return FunctionSpec{}, fmt.Errorf("marshaling outputs: %w", err) } outputs = &outs.ObjectTypeSpec } diff --git a/pkg/codegen/utilities.go b/pkg/codegen/utilities.go index ee4edf6c5060..90af78e7ebbc 100644 --- a/pkg/codegen/utilities.go +++ b/pkg/codegen/utilities.go @@ -172,3 +172,15 @@ func ExpandShortEnumName(name string) string { } return name } + +// A simple in memory file system. +type Fs map[string][]byte + +// Add a new file to the Fs. +// +// Panic if the file is a duplicate. +func (fs Fs) Add(path string, contents []byte) { + _, has := fs[path] + contract.Assertf(!has, "duplicate file: %s", path) + fs[path] = contents +} diff --git a/sdk/nodejs/Makefile b/sdk/nodejs/Makefile index f83c94119b81..915ff23d16e4 100644 --- a/sdk/nodejs/Makefile +++ b/sdk/nodejs/Makefile @@ -69,7 +69,7 @@ unit_tests:: $(TEST_ALL_DEPS) yarn run nyc -s mocha 'bin/tests_with_mocks/**/*.spec.js' test_auto:: $(TEST_ALL_DEPS) - yarn run nyc -s mocha --timeout 120000 'bin/tests/automation/**/*.spec.js' + yarn run nyc -s mocha --timeout 220000 'bin/tests/automation/**/*.spec.js' TSC_SUPPORTED_VERSIONS = ~3.7.3 ^3 ^4 diff --git a/sdk/nodejs/automation/remoteWorkspace.ts b/sdk/nodejs/automation/remoteWorkspace.ts index fa7c3e024805..00305134a814 100644 --- a/sdk/nodejs/automation/remoteWorkspace.ts +++ b/sdk/nodejs/automation/remoteWorkspace.ts @@ -149,17 +149,17 @@ export interface RemoteWorkspaceOptions { async function createLocalWorkspace(args: RemoteGitProgramArgs, opts?: RemoteWorkspaceOptions): Promise { if (!isFullyQualifiedStackName(args.stackName)) { - throw new Error(`"${args.stackName}" stack name must be fully qualified`); + throw new Error(`stack name "${args.stackName}" must be fully qualified.`); } if (!args.url) { throw new Error("url is required."); } - if (args.commitHash && args.branch) { - throw new Error("commitHash and branch cannot both be specified."); + if (args.branch && args.commitHash) { + throw new Error("branch and commitHash cannot both be specified."); } - if (!args.commitHash && !args.branch) { - throw new Error("at least commitHash or branch are required."); + if (!args.branch && !args.commitHash) { + throw new Error("either branch or commitHash is required."); } if (args.auth) { if (args.auth.sshPrivateKey && args.auth.sshPrivateKeyPath) { diff --git a/sdk/nodejs/tests/automation/localWorkspace.spec.ts b/sdk/nodejs/tests/automation/localWorkspace.spec.ts index 10dfcb0a83c9..d835c47b42cb 100644 --- a/sdk/nodejs/tests/automation/localWorkspace.spec.ts +++ b/sdk/nodejs/tests/automation/localWorkspace.spec.ts @@ -28,6 +28,7 @@ import { } from "../../automation"; import { Config, output } from "../../index"; import { asyncTest } from "../util"; +import { getTestOrg, getTestSuffix } from "./util"; const versionRegex = /(\d+\.)(\d+\.)(\d+)(-.*)?/; const userAgent = "pulumi/pulumi/test"; @@ -887,11 +888,6 @@ describe(`checkVersionIsValid`, () => { }); }); - -const getTestSuffix = () => { - return Math.floor(100000 + Math.random() * 900000); -}; - const normalizeConfigKey = (key: string, projectName: string) => { const parts = key.split(":"); if (parts.length < 2) { @@ -899,11 +895,3 @@ const normalizeConfigKey = (key: string, projectName: string) => { } return ""; }; - -const getTestOrg = () => { - let testOrg = "pulumi-test"; - if (process.env.PULUMI_TEST_ORG) { - testOrg = process.env.PULUMI_TEST_ORG; - } - return testOrg; -}; diff --git a/sdk/nodejs/tests/automation/remoteWorkspace.spec.ts b/sdk/nodejs/tests/automation/remoteWorkspace.spec.ts index 8479cb64622d..2bf7d93a1845 100644 --- a/sdk/nodejs/tests/automation/remoteWorkspace.spec.ts +++ b/sdk/nodejs/tests/automation/remoteWorkspace.spec.ts @@ -14,7 +14,206 @@ import assert from "assert"; -import { isFullyQualifiedStackName } from "../../automation"; +import { + fullyQualifiedStackName, + isFullyQualifiedStackName, + LocalWorkspace, + RemoteGitAuthArgs, + RemoteGitProgramArgs, + RemoteStack, + RemoteWorkspace, + RemoteWorkspaceOptions, +} from "../../automation"; +import { getTestOrg, getTestSuffix } from "./util"; + +const testRepo = "https://github.com/pulumi/test-repo.git"; + +describe("RemoteWorkspace", () => { + describe("selectStack", () => { + describe("throws appropriate errors", () => testErrors(RemoteWorkspace.selectStack)); + }); + + describe("createStack", () => { + describe("throws appropriate errors", () => testErrors(RemoteWorkspace.createStack)); + + it(`runs through the stack lifecycle`, async function () { + // This test requires the service with access to Pulumi Deployments. + // Set PULUMI_ACCESS_TOKEN to an access token with access to Pulumi Deployments + // and set PULUMI_TEST_DEPLOYMENTS_API to any value to enable the test. + if (!process.env.PULUMI_ACCESS_TOKEN) { + this.skip(); + return; + } + if (!process.env.PULUMI_TEST_DEPLOYMENTS_API) { + this.skip(); + return; + } + + await testLifecycle(RemoteWorkspace.createStack); + }); + }); + + describe("createOrSelectStack", () => { + describe("throws appropriate errors", () => testErrors(RemoteWorkspace.createOrSelectStack)); + + it(`runs through the stack lifecycle`, async function () { + // This test requires the service with access to Pulumi Deployments. + // Set PULUMI_ACCESS_TOKEN to an access token with access to Pulumi Deployments + // and set PULUMI_TEST_DEPLOYMENTS_API to any value to enable the test. + if (!process.env.PULUMI_ACCESS_TOKEN) { + this.skip(); + return; + } + if (!process.env.PULUMI_TEST_DEPLOYMENTS_API) { + this.skip(); + return; + } + + await testLifecycle(RemoteWorkspace.createOrSelectStack); + }); + }); +}); + +function testErrors(fn: (args: RemoteGitProgramArgs, opts?: RemoteWorkspaceOptions) => Promise) { + const stack = "owner/project/stack"; + const tests: { + name: string; + stackName: string; + url: string; + branch?: string; + commitHash?: string; + auth?: RemoteGitAuthArgs; + error: string; + }[] = [ + { + name: "stack empty", + stackName: "", + url: "", + error: `stack name "" must be fully qualified.`, + }, + { + name: "stack just name", + stackName: "name", + url: "", + error: `stack name "name" must be fully qualified.`, + }, + { + name: "stack just name & owner", + stackName: "owner/name", + url: "", + error: `stack name "owner/name" must be fully qualified.`, + }, + { + name: "stack just sep", + stackName: "/", + url: "", + error: `stack name "/" must be fully qualified.`, + }, + { + name: "stack just two seps", + stackName: "//", + url: "", + error: `stack name "//" must be fully qualified.`, + }, + { + name: "stack just three seps", + stackName: "///", + url: "", + error: `stack name "///" must be fully qualified.`, + }, + { + name: "stack invalid", + stackName: "owner/project/stack/wat", + url: "", + error: `stack name "owner/project/stack/wat" must be fully qualified.`, + }, + { + name: "no url", + stackName: stack, + url: "", + error: `url is required.`, + }, + { + name: "no branch or commit", + stackName: stack, + url: testRepo, + error: `either branch or commitHash is required.`, + }, + { + name: "both branch and commit", + stackName: stack, + url: testRepo, + branch: "branch", + commitHash: "commit", + error: `branch and commitHash cannot both be specified.`, + }, + { + name: "both ssh private key and path", + stackName: stack, + url: testRepo, + branch: "branch", + auth: { + sshPrivateKey: "key", + sshPrivateKeyPath: "path", + }, + error: `sshPrivateKey and sshPrivateKeyPath cannot both be specified.`, + }, + ]; + + tests.forEach(test => { + it(`${test.name}`, async () => { + const { stackName, url, branch, commitHash, auth } = test; + await assert.rejects(async () => { + await fn({ stackName, url, branch, commitHash, auth }); + }, { + message: test.error, + }); + }); + }); +} + +async function testLifecycle(fn: (args: RemoteGitProgramArgs, opts?: RemoteWorkspaceOptions) => Promise) { + const stackName = fullyQualifiedStackName(getTestOrg(), "go_remote_proj", `int_test${getTestSuffix()}`); + const stack = await fn({ + stackName, + url: testRepo, + branch: "refs/heads/master", + projectPath: "goproj", + },{ + preRunCommands: [ + `pulumi config set bar abc --stack ${stackName}`, + `pulumi config set --secret buzz secret --stack ${stackName}`, + ], + }); + + // pulumi up + const upRes = await stack.up(); + assert.strictEqual(Object.keys(upRes.outputs).length, 3); + assert.strictEqual(upRes.outputs["exp_static"].value, "foo"); + assert.strictEqual(upRes.outputs["exp_static"].secret, false); + assert.strictEqual(upRes.outputs["exp_cfg"].value, "abc"); + assert.strictEqual(upRes.outputs["exp_cfg"].secret, false); + assert.strictEqual(upRes.outputs["exp_secret"].value, "secret"); + assert.strictEqual(upRes.outputs["exp_secret"].secret, true); + assert.strictEqual(upRes.summary.kind, "update"); + assert.strictEqual(upRes.summary.result, "succeeded"); + + // pulumi preview + const preRes = await stack.preview(); + assert.strictEqual(preRes.changeSummary.same, 1); + + // pulumi refresh + const refRes = await stack.refresh(); + assert.strictEqual(refRes.summary.kind, "refresh"); + assert.strictEqual(refRes.summary.result, "succeeded"); + + // pulumi destroy + const destroyRes = await stack.destroy(); + assert.strictEqual(destroyRes.summary.kind, "destroy"); + assert.strictEqual(destroyRes.summary.result, "succeeded"); + + await (await LocalWorkspace.create({})).removeStack(stackName); +} describe("isFullyQualifiedStackName", () => { const tests = [ diff --git a/sdk/nodejs/tests/automation/util.ts b/sdk/nodejs/tests/automation/util.ts new file mode 100644 index 000000000000..e1dcd2427ada --- /dev/null +++ b/sdk/nodejs/tests/automation/util.ts @@ -0,0 +1,27 @@ +// Copyright 2016-2022, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** @internal */ +export function getTestSuffix() { + return Math.floor(100000 + Math.random() * 900000); +} + +/** @internal */ +export function getTestOrg() { + let testOrg = "pulumi-test"; + if (process.env.PULUMI_TEST_ORG) { + testOrg = process.env.PULUMI_TEST_ORG; + } + return testOrg; +} diff --git a/sdk/nodejs/tsconfig.json b/sdk/nodejs/tsconfig.json index 8804972f62b0..70aa28fdb76d 100644 --- a/sdk/nodejs/tsconfig.json +++ b/sdk/nodejs/tsconfig.json @@ -108,6 +108,7 @@ "tests/automation/cmd.spec.ts", "tests/automation/localWorkspace.spec.ts", "tests/automation/remoteWorkspace.spec.ts", + "tests/automation/util.ts", "tests_with_mocks/mocks.spec.ts" ]