Merge pull request #1456 from pulumi/golang
Support Pulumi programs written in Go
This commit is contained in:
commit
e772aac3c1
27 changed files with 3167 additions and 3 deletions
8
Gopkg.lock
generated
8
Gopkg.lock
generated
|
@ -306,6 +306,12 @@
|
||||||
packages = ["open"]
|
packages = ["open"]
|
||||||
revision = "75fb7ed4208cf72d323d7d02fd1a5964a7a9073c"
|
revision = "75fb7ed4208cf72d323d7d02fd1a5964a7a9073c"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/spf13/cast"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "8965335b8c7107321228e3e3702cab9832751bac"
|
||||||
|
version = "v1.2.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/spf13/cobra"
|
name = "github.com/spf13/cobra"
|
||||||
|
@ -554,6 +560,6 @@
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "a3c19b57bc10408ea6f35080bdca4a22257e7ab588c1fe3dfc88cf2b00aeea52"
|
inputs-digest = "f974a423ae8de19a1ba9f68115b5ab111040baece859d32f89e885094d3ebd86"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -1,5 +1,5 @@
|
||||||
PROJECT_NAME := Pulumi SDK
|
PROJECT_NAME := Pulumi SDK
|
||||||
SUB_PROJECTS := sdk/nodejs sdk/python
|
SUB_PROJECTS := sdk/nodejs sdk/python sdk/go
|
||||||
include build/common.mk
|
include build/common.mk
|
||||||
|
|
||||||
PROJECT := github.com/pulumi/pulumi
|
PROJECT := github.com/pulumi/pulumi
|
||||||
|
|
|
@ -18,11 +18,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/pulumi/pulumi/pkg/resource"
|
"github.com/pulumi/pulumi/pkg/resource"
|
||||||
"github.com/pulumi/pulumi/pkg/resource/deploy"
|
"github.com/pulumi/pulumi/pkg/resource/deploy"
|
||||||
"github.com/pulumi/pulumi/pkg/tokens"
|
"github.com/pulumi/pulumi/pkg/tokens"
|
||||||
"github.com/pulumi/pulumi/pkg/version"
|
"github.com/pulumi/pulumi/pkg/version"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type MockRegisterResourceEvent struct {
|
type MockRegisterResourceEvent struct {
|
||||||
|
|
|
@ -284,6 +284,8 @@ func NewPropertyValueRepl(v interface{},
|
||||||
obj[pk] = pv
|
obj[pk] = pv
|
||||||
}
|
}
|
||||||
return NewObjectProperty(obj)
|
return NewObjectProperty(obj)
|
||||||
|
case reflect.String:
|
||||||
|
return NewStringProperty(rv.String())
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
obj := NewPropertyMapRepl(v, replk, replv)
|
obj := NewPropertyMapRepl(v, replk, replv)
|
||||||
return NewObjectProperty(obj)
|
return NewObjectProperty(obj)
|
||||||
|
|
|
@ -181,6 +181,8 @@ type ProgramTestOptions struct {
|
||||||
Bin string
|
Bin string
|
||||||
// YarnBin is a location of a `yarn` executable to be run. Taken from the $PATH if missing.
|
// YarnBin is a location of a `yarn` executable to be run. Taken from the $PATH if missing.
|
||||||
YarnBin string
|
YarnBin string
|
||||||
|
// GoBin is a location of a `go` executable to be run. Taken from the $PATH if missing.
|
||||||
|
GoBin string
|
||||||
|
|
||||||
// Additional environment variaibles to pass for each command we run.
|
// Additional environment variaibles to pass for each command we run.
|
||||||
Env []string
|
Env []string
|
||||||
|
@ -366,6 +368,7 @@ type programTester struct {
|
||||||
opts *ProgramTestOptions // options that control this test run.
|
opts *ProgramTestOptions // options that control this test run.
|
||||||
bin string // the `pulumi` binary we are using.
|
bin string // the `pulumi` binary we are using.
|
||||||
yarnBin string // the `yarn` binary we are using.
|
yarnBin string // the `yarn` binary we are using.
|
||||||
|
goBin string // the `go` binary we are using.
|
||||||
}
|
}
|
||||||
|
|
||||||
func newProgramTester(t *testing.T, opts *ProgramTestOptions) *programTester {
|
func newProgramTester(t *testing.T, opts *ProgramTestOptions) *programTester {
|
||||||
|
@ -380,6 +383,10 @@ func (pt *programTester) getYarnBin() (string, error) {
|
||||||
return getCmdBin(&pt.yarnBin, "yarn", pt.opts.YarnBin)
|
return getCmdBin(&pt.yarnBin, "yarn", pt.opts.YarnBin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pt *programTester) getGoBin() (string, error) {
|
||||||
|
return getCmdBin(&pt.goBin, "go", pt.opts.GoBin)
|
||||||
|
}
|
||||||
|
|
||||||
func (pt *programTester) pulumiCmd(args []string) ([]string, error) {
|
func (pt *programTester) pulumiCmd(args []string) ([]string, error) {
|
||||||
bin, err := pt.getBin()
|
bin, err := pt.getBin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -910,6 +917,8 @@ func (pt *programTester) prepareProject(projectDir string) error {
|
||||||
return pt.prepareNodeJSProject(projinfo)
|
return pt.prepareNodeJSProject(projinfo)
|
||||||
case "python":
|
case "python":
|
||||||
return pt.preparePythonProject(projinfo)
|
return pt.preparePythonProject(projinfo)
|
||||||
|
case "go":
|
||||||
|
return pt.prepareGoProject(projinfo)
|
||||||
default:
|
default:
|
||||||
return errors.Errorf("unrecognized project runtime: %s", proj.Runtime)
|
return errors.Errorf("unrecognized project runtime: %s", proj.Runtime)
|
||||||
}
|
}
|
||||||
|
@ -952,3 +961,26 @@ func (pt *programTester) prepareNodeJSProject(projinfo *engine.Projinfo) error {
|
||||||
func (pt *programTester) preparePythonProject(projinfo *engine.Projinfo) error {
|
func (pt *programTester) preparePythonProject(projinfo *engine.Projinfo) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// prepareGoProject runs setup necessary to get a Go project ready for `pulumi` commands.
|
||||||
|
func (pt *programTester) prepareGoProject(projinfo *engine.Projinfo) error {
|
||||||
|
// Go programs are compiled, so we will compile the project first.
|
||||||
|
goBin, err := pt.getGoBin()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "locating `go` binary")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure GOPATH is known.
|
||||||
|
gopath := os.Getenv("GOPATH")
|
||||||
|
if gopath == "" {
|
||||||
|
return errors.New("$GOPATH must be set to test a Go project")
|
||||||
|
}
|
||||||
|
|
||||||
|
// To compile, simply run `go build -o $GOPATH/bin/<projname> .` from the project's working directory.
|
||||||
|
cwd, _, err := projinfo.GetPwdMain()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outBin := filepath.Join(gopath, "bin", string(projinfo.Proj.Name))
|
||||||
|
return pt.runCommand("go-build", []string{goBin, "build", "-o", outBin, "."}, cwd)
|
||||||
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ copy_package() {
|
||||||
run_go_build "${ROOT}"
|
run_go_build "${ROOT}"
|
||||||
run_go_build "${ROOT}/sdk/nodejs/cmd/pulumi-language-nodejs"
|
run_go_build "${ROOT}/sdk/nodejs/cmd/pulumi-language-nodejs"
|
||||||
run_go_build "${ROOT}/sdk/python/cmd/pulumi-language-python"
|
run_go_build "${ROOT}/sdk/python/cmd/pulumi-language-python"
|
||||||
|
run_go_build "${ROOT}/sdk/go/pulumi-language-go"
|
||||||
|
|
||||||
# Copy over the language and dynamic resource providers.
|
# Copy over the language and dynamic resource providers.
|
||||||
cp ${ROOT}/sdk/nodejs/dist/pulumi-resource-pulumi-nodejs ${PUBDIR}/bin/
|
cp ${ROOT}/sdk/nodejs/dist/pulumi-resource-pulumi-nodejs ${PUBDIR}/bin/
|
||||||
|
|
24
sdk/go/Makefile
Normal file
24
sdk/go/Makefile
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
PROJECT_NAME := Pulumi Go SDK
|
||||||
|
LANGHOST_PKG := github.com/pulumi/pulumi/sdk/go/pulumi-language-go
|
||||||
|
VERSION := $(shell ../../scripts/get-version)
|
||||||
|
PROJECT_PKGS := $(shell go list ./pulumi/... ./pulumi-language-go/... | grep -v /vendor/)
|
||||||
|
|
||||||
|
GOMETALINTERBIN := gometalinter
|
||||||
|
GOMETALINTER := ${GOMETALINTERBIN} --config=../../Gometalinter.json
|
||||||
|
|
||||||
|
TESTPARALLELISM := 10
|
||||||
|
|
||||||
|
include ../../build/common.mk
|
||||||
|
|
||||||
|
build::
|
||||||
|
go install -ldflags "-X github.com/pulumi/pulumi/pkg/version.Version=${VERSION}" ${LANGHOST_PKG}
|
||||||
|
|
||||||
|
install::
|
||||||
|
GOBIN=$(PULUMI_BIN) go install -ldflags "-X github.com/pulumi/pulumi/pkg/version.Version=${VERSION}" ${LANGHOST_PKG}
|
||||||
|
|
||||||
|
lint::
|
||||||
|
$(GOMETALINTER) ./pulumi/... | sort
|
||||||
|
$(GOMETALINTER) ./pulumi-language-go/... | sort
|
||||||
|
|
||||||
|
test_fast::
|
||||||
|
go test -cover -parallel ${TESTPARALLELISM} ${PROJECT_PKGS}
|
16
sdk/go/README.md
Normal file
16
sdk/go/README.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Pulumi Golang SDK
|
||||||
|
|
||||||
|
This directory contains support for writing Pulumi programs in the Go language. There are two aspects to this:
|
||||||
|
|
||||||
|
* `pulumi/` contains the client language bindings Pulumi program's code directly against;
|
||||||
|
* `pulumi-language-go/` contains the language host plugin that the Pulumi engine uses to orchestrate updates.
|
||||||
|
|
||||||
|
To author a Pulumi program in Go, simply say so in your `Pulumi.yaml`
|
||||||
|
|
||||||
|
name: <my-project>
|
||||||
|
runtime: go
|
||||||
|
|
||||||
|
and ensure you have `pulumi-language-go` on your path (it is distributed in the Pulumi download automatically).
|
||||||
|
|
||||||
|
By default, the language plugin will use your project's name, `<my-project>`, as the executable that it loads. This too
|
||||||
|
must be on your path for the language provider to load it when you run `pulumi preview` or `pulumi update`.
|
180
sdk/go/pulumi-language-go/main.go
Normal file
180
sdk/go/pulumi-language-go/main.go
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
// Copyright 2016-2018, 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.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
pbempty "github.com/golang/protobuf/ptypes/empty"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
"github.com/pulumi/pulumi/pkg/util/cmdutil"
|
||||||
|
"github.com/pulumi/pulumi/pkg/util/logging"
|
||||||
|
"github.com/pulumi/pulumi/pkg/util/rpcutil"
|
||||||
|
"github.com/pulumi/pulumi/pkg/version"
|
||||||
|
"github.com/pulumi/pulumi/sdk/go/pulumi"
|
||||||
|
pulumirpc "github.com/pulumi/pulumi/sdk/proto/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Launches the language host, which in turn fires up an RPC server implementing the LanguageRuntimeServer endpoint.
|
||||||
|
func main() {
|
||||||
|
var tracing string
|
||||||
|
flag.StringVar(&tracing, "tracing", "", "Emit tracing to a Zipkin-compatible tracing endpoint")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
args := flag.Args()
|
||||||
|
logging.InitLogging(false, 0, false)
|
||||||
|
cmdutil.InitTracing("pulumi-language-go", "pulumi-language-go", tracing)
|
||||||
|
|
||||||
|
// Pluck out the engine so we can do logging, etc.
|
||||||
|
if len(args) == 0 {
|
||||||
|
cmdutil.Exit(errors.New("missing required engine RPC address argument"))
|
||||||
|
}
|
||||||
|
engineAddress := args[0]
|
||||||
|
|
||||||
|
// Fire up a gRPC server, letting the kernel choose a free port.
|
||||||
|
port, done, err := rpcutil.Serve(0, nil, []func(*grpc.Server) error{
|
||||||
|
func(srv *grpc.Server) error {
|
||||||
|
host := newLanguageHost(engineAddress, tracing)
|
||||||
|
pulumirpc.RegisterLanguageRuntimeServer(srv, host)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
cmdutil.Exit(errors.Wrapf(err, "could not start language host RPC server"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, print out the port so that the spawner knows how to reach us.
|
||||||
|
fmt.Printf("%d\n", port)
|
||||||
|
|
||||||
|
// And finally wait for the server to stop serving.
|
||||||
|
if err := <-done; err != nil {
|
||||||
|
cmdutil.Exit(errors.Wrapf(err, "language host RPC stopped serving"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// goLanguageHost implements the LanguageRuntimeServer interface for use as an API endpoint.
|
||||||
|
type goLanguageHost struct {
|
||||||
|
engineAddress string
|
||||||
|
tracing string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLanguageHost(engineAddress, tracing string) pulumirpc.LanguageRuntimeServer {
|
||||||
|
return &goLanguageHost{
|
||||||
|
engineAddress: engineAddress,
|
||||||
|
tracing: tracing,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRequiredPlugins computes the complete set of anticipated plugins required by a program.
|
||||||
|
func (host *goLanguageHost) GetRequiredPlugins(ctx context.Context,
|
||||||
|
req *pulumirpc.GetRequiredPluginsRequest) (*pulumirpc.GetRequiredPluginsResponse, error) {
|
||||||
|
return &pulumirpc.GetRequiredPluginsResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPC endpoint for LanguageRuntimeServer::Run
|
||||||
|
func (host *goLanguageHost) Run(ctx context.Context, req *pulumirpc.RunRequest) (*pulumirpc.RunResponse, error) {
|
||||||
|
// Create the environment we'll use to run the process. This is how we pass the RunInfo to the actual
|
||||||
|
// Go program runtime, to avoid needing any sort of program interface other than just a main entrypoint.
|
||||||
|
env, err := host.constructEnv(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to prepare environment")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The program to execute is simply the name of the project. This ensures good Go toolability, whereby
|
||||||
|
// you can simply run `go install .` to build a Pulumi program prior to running it, among other benefits.
|
||||||
|
program := req.GetProject()
|
||||||
|
logging.V(5).Infoln("language host launching process: %s", program)
|
||||||
|
|
||||||
|
// Now simply spawn a process to execute the requested program, wiring up stdout/stderr directly.
|
||||||
|
var errResult string
|
||||||
|
cmd := exec.Command(program) // nolint: gas, intentionally running dynamic program name.
|
||||||
|
cmd.Env = env
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
|
// If the program ran, but exited with a non-zero error code. This will happen often, since user
|
||||||
|
// errors will trigger this. So, the error message should look as nice as possible.
|
||||||
|
if status, stok := exiterr.Sys().(syscall.WaitStatus); stok {
|
||||||
|
err = errors.Errorf("program exited with non-zero exit code: %d", status.ExitStatus())
|
||||||
|
} else {
|
||||||
|
err = errors.Wrapf(exiterr, "program exited unexpectedly")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, we didn't even get to run the program. This ought to never happen unless there's
|
||||||
|
// a bug or system condition that prevented us from running the language exec. Issue a scarier error.
|
||||||
|
err = errors.Wrapf(err, "problem executing program (could not run language executor)")
|
||||||
|
}
|
||||||
|
|
||||||
|
errResult = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pulumirpc.RunResponse{Error: errResult}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructEnv constructs an environment for a Go progam by enumerating all of the optional and non-optional
|
||||||
|
// arguments present in a RunRequest.
|
||||||
|
func (host *goLanguageHost) constructEnv(req *pulumirpc.RunRequest) ([]string, error) {
|
||||||
|
config, err := host.constructConfig(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var env []string
|
||||||
|
maybeAppendEnv := func(k, v string) {
|
||||||
|
if v != "" {
|
||||||
|
env = append(env, fmt.Sprintf("%s=%s", k, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeAppendEnv(pulumi.EnvProject, req.GetProject())
|
||||||
|
maybeAppendEnv(pulumi.EnvStack, req.GetStack())
|
||||||
|
maybeAppendEnv(pulumi.EnvConfig, config)
|
||||||
|
maybeAppendEnv(pulumi.EnvDryRun, fmt.Sprintf("%v", req.GetDryRun()))
|
||||||
|
maybeAppendEnv(pulumi.EnvParallel, fmt.Sprint(req.GetParallel()))
|
||||||
|
maybeAppendEnv(pulumi.EnvMonitor, req.GetMonitorAddress())
|
||||||
|
maybeAppendEnv(pulumi.EnvEngine, host.engineAddress)
|
||||||
|
|
||||||
|
return env, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructConfig json-serializes the configuration data given as part of a RunRequest.
|
||||||
|
func (host *goLanguageHost) constructConfig(req *pulumirpc.RunRequest) (string, error) {
|
||||||
|
configMap := req.GetConfig()
|
||||||
|
if configMap == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
configJSON, err := json.Marshal(configMap)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(configJSON), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (host *goLanguageHost) GetPluginInfo(ctx context.Context, req *pbempty.Empty) (*pulumirpc.PluginInfo, error) {
|
||||||
|
return &pulumirpc.PluginInfo{
|
||||||
|
Version: version.Version,
|
||||||
|
}, nil
|
||||||
|
}
|
110
sdk/go/pulumi/asset/asset.go
Normal file
110
sdk/go/pulumi/asset/asset.go
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
// Copyright 2016-2018, 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.
|
||||||
|
|
||||||
|
package asset
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/pulumi/pulumi/pkg/util/contract"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Asset represents a file that is managed in conjunction with Pulumi resources. An Asset may be backed by a number
|
||||||
|
// of sources, including local filesystem paths, in-memory blobs of text, or remote files referenced by a URL.
|
||||||
|
type Asset interface {
|
||||||
|
// Path returns the filesystem path, for file-based assets.
|
||||||
|
Path() string
|
||||||
|
// Text returns an in-memory blob of text, for string-based assets.
|
||||||
|
Text() string
|
||||||
|
// URI returns a URI, for remote network-based assets.
|
||||||
|
URI() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type asset struct {
|
||||||
|
path string
|
||||||
|
text string
|
||||||
|
uri string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileAsset creates an asset backed by a file and specified by that file's path.
|
||||||
|
func NewFileAsset(path string) Asset {
|
||||||
|
return &asset{path: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStringAsset creates an asset backed by a piece of in-memory text.
|
||||||
|
func NewStringAsset(text string) Asset {
|
||||||
|
return &asset{text: text}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRemoteAsset creates an asset backed by a remote file and specified by that file's URL.
|
||||||
|
func NewRemoteAsset(uri string) Asset {
|
||||||
|
return &asset{uri: uri}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path returns the asset's file path, if this is a file asset, or an empty string otherwise.
|
||||||
|
func (a *asset) Path() string { return a.path }
|
||||||
|
|
||||||
|
// Text returns the asset's textual string, if this is a string asset, or an empty string otherwise.
|
||||||
|
func (a *asset) Text() string { return a.text }
|
||||||
|
|
||||||
|
// URI returns the asset's URL, if this is a remote asset, or an empty string otherwise.
|
||||||
|
func (a *asset) URI() string { return a.uri }
|
||||||
|
|
||||||
|
// Archive represents a collection of Assets.
|
||||||
|
type Archive interface {
|
||||||
|
// Assets returns a map of named assets or archives, for collections.
|
||||||
|
Assets() map[string]interface{}
|
||||||
|
// Path returns the filesystem path, for file-based archives.
|
||||||
|
Path() string
|
||||||
|
// URI returns a URI, for remote network-based archives.
|
||||||
|
URI() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type archive struct {
|
||||||
|
assets map[string]interface{}
|
||||||
|
path string
|
||||||
|
uri string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAssetArchive creates a new archive from an in-memory collection of named assets or other archives.
|
||||||
|
func NewAssetArchive(assets map[string]interface{}) Archive {
|
||||||
|
for k, a := range assets {
|
||||||
|
if _, ok := a.(Asset); !ok {
|
||||||
|
if _, ok2 := a.(Archive); !ok2 {
|
||||||
|
contract.Failf(
|
||||||
|
"expected asset map to contain Assets and/or Archives; %s is %v", k, reflect.TypeOf(a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &archive{assets: assets}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileArchive creates an archive backed by a file and specified by that file's path.
|
||||||
|
func NewFileArchive(path string) Archive {
|
||||||
|
return &archive{path: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRemoteArchive creates an archive backed by a remote file and specified by that file's URL.
|
||||||
|
func NewRemoteArchive(uri string) Archive {
|
||||||
|
return &archive{uri: uri}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assets returns the archive's asset map, if this is a collection of archives/assets, or nil otherwise.
|
||||||
|
func (a *archive) Assets() map[string]interface{} { return a.assets }
|
||||||
|
|
||||||
|
// Path returns the archive's file path, if this is a file archive, or an empty string otherwise.
|
||||||
|
func (a *archive) Path() string { return a.path }
|
||||||
|
|
||||||
|
// URI returns the archive's URL, if this is a remote archive, or an empty string otherwise.
|
||||||
|
func (a *archive) URI() string { return a.uri }
|
247
sdk/go/pulumi/config/config.go
Normal file
247
sdk/go/pulumi/config/config.go
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
// Copyright 2016-2018, 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.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pulumi/pulumi/sdk/go/pulumi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is a struct that permits access to config as a "bag" with a package name. This avoids needing to access
|
||||||
|
// config with the fully qualified name all of the time (e.g., a bag whose namespace is "p" automatically translates
|
||||||
|
// attempted reads of keys "k" into "p:k"). This is optional but can save on some boilerplate when accessing config.
|
||||||
|
type Config struct {
|
||||||
|
ctx *pulumi.Context
|
||||||
|
namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new config bag with the given context and namespace.
|
||||||
|
func New(ctx *pulumi.Context, namespace string) *Config {
|
||||||
|
return &Config{ctx: ctx, namespace: namespace}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fullKey turns a simple configuration key into a fully resolved one, by prepending the bag's name.
|
||||||
|
func (c *Config) fullKey(key string) string {
|
||||||
|
return c.namespace + ":" + key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get loads an optional configuration value by its key, or returns "" if it doesn't exist.
|
||||||
|
func (c *Config) Get(key string) string {
|
||||||
|
return Get(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBool loads an optional bool configuration value by its key, or returns false if it doesn't exist.
|
||||||
|
func (c *Config) GetBool(key string) bool {
|
||||||
|
return GetBool(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFloat32 loads an optional float32 configuration value by its key, or returns 0.0 if it doesn't exist.
|
||||||
|
func (c *Config) GetFloat32(key string) float32 {
|
||||||
|
return GetFloat32(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFloat64 loads an optional float64 configuration value by its key, or returns 0.0 if it doesn't exist.
|
||||||
|
func (c *Config) GetFloat64(key string) float64 {
|
||||||
|
return GetFloat64(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt loads an optional int configuration value by its key, or returns 0 if it doesn't exist.
|
||||||
|
func (c *Config) GetInt(key string) int {
|
||||||
|
return GetInt(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt8 loads an optional int8 configuration value by its key, or returns 0 if it doesn't exist.
|
||||||
|
func (c *Config) GetInt8(key string) int8 {
|
||||||
|
return GetInt8(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt16 loads an optional int16 configuration value by its key, or returns 0 if it doesn't exist.
|
||||||
|
func (c *Config) GetInt16(key string) int16 {
|
||||||
|
return GetInt16(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt32 loads an optional int32 configuration value by its key, or returns 0 if it doesn't exist.
|
||||||
|
func (c *Config) GetInt32(key string) int32 {
|
||||||
|
return GetInt32(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt64 loads an optional int64 configuration value by its key, or returns 0 if it doesn't exist.
|
||||||
|
func (c *Config) GetInt64(key string) int64 {
|
||||||
|
return GetInt64(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint loads an optional uint configuration value by its key, or returns 0 if it doesn't exist.
|
||||||
|
func (c *Config) GetUint(key string) uint {
|
||||||
|
return GetUint(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint8 loads an optional uint8 configuration value by its key, or returns 0 if it doesn't exist.
|
||||||
|
func (c *Config) GetUint8(key string) uint8 {
|
||||||
|
return GetUint8(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint16 loads an optional uint16 configuration value by its key, or returns 0 if it doesn't exist.
|
||||||
|
func (c *Config) GetUint16(key string) uint16 {
|
||||||
|
return GetUint16(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint32 loads an optional uint32 configuration value by its key, or returns 0 if it doesn't exist.
|
||||||
|
func (c *Config) GetUint32(key string) uint32 {
|
||||||
|
return GetUint32(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint64 loads an optional uint64 configuration value by its key, or returns 0 if it doesn't exist.
|
||||||
|
func (c *Config) GetUint64(key string) uint64 {
|
||||||
|
return GetUint64(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Require loads a configuration value by its key, or panics if it doesn't exist.
|
||||||
|
func (c *Config) Require(key string) string {
|
||||||
|
return Require(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireBool loads a bool configuration value by its key, or panics if it doesn't exist.
|
||||||
|
func (c *Config) RequireBool(key string) bool {
|
||||||
|
return RequireBool(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireFloat32 loads a float32 configuration value by its key, or panics if it doesn't exist.
|
||||||
|
func (c *Config) RequireFloat32(key string) float32 {
|
||||||
|
return RequireFloat32(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireFloat64 loads a float64 configuration value by its key, or panics if it doesn't exist.
|
||||||
|
func (c *Config) RequireFloat64(key string) float64 {
|
||||||
|
return RequireFloat64(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireInt loads a int configuration value by its key, or panics if it doesn't exist.
|
||||||
|
func (c *Config) RequireInt(key string) int {
|
||||||
|
return RequireInt(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireInt8 loads a int8 configuration value by its key, or panics if it doesn't exist.
|
||||||
|
func (c *Config) RequireInt8(key string) int8 {
|
||||||
|
return RequireInt8(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireInt16 loads a int16 configuration value by its key, or panics if it doesn't exist.
|
||||||
|
func (c *Config) RequireInt16(key string) int16 {
|
||||||
|
return RequireInt16(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireInt32 loads a int32 configuration value by its key, or panics if it doesn't exist.
|
||||||
|
func (c *Config) RequireInt32(key string) int32 {
|
||||||
|
return RequireInt32(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireInt64 loads a int64 configuration value by its key, or panics if it doesn't exist.
|
||||||
|
func (c *Config) RequireInt64(key string) int64 {
|
||||||
|
return RequireInt64(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireUint loads a uint configuration value by its key, or panics if it doesn't exist.
|
||||||
|
func (c *Config) RequireUint(key string) uint {
|
||||||
|
return RequireUint(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireUint8 loads a uint8 configuration value by its key, or panics if it doesn't exist.
|
||||||
|
func (c *Config) RequireUint8(key string) uint8 {
|
||||||
|
return RequireUint8(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireUint16 loads a uint16 configuration value by its key, or panics if it doesn't exist.
|
||||||
|
func (c *Config) RequireUint16(key string) uint16 {
|
||||||
|
return RequireUint16(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireUint32 loads a uint32 configuration value by its key, or panics if it doesn't exist.
|
||||||
|
func (c *Config) RequireUint32(key string) uint32 {
|
||||||
|
return RequireUint32(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireUint64 loads a uint64 configuration value by its key, or panics if it doesn't exist.
|
||||||
|
func (c *Config) RequireUint64(key string) uint64 {
|
||||||
|
return RequireUint64(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try loads a configuration value by its key, returning a non-nil error if it doesn't exist.
|
||||||
|
func (c *Config) Try(key string) (string, error) {
|
||||||
|
return Try(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryBool loads an optional bool configuration value by its key, or returns an error if it doesn't exist.
|
||||||
|
func (c *Config) TryBool(key string) (bool, error) {
|
||||||
|
return TryBool(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryFloat32 loads an optional float32 configuration value by its key, or returns an error if it doesn't exist.
|
||||||
|
func (c *Config) TryFloat32(key string) (float32, error) {
|
||||||
|
return TryFloat32(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryFloat64 loads an optional float64 configuration value by its key, or returns an error if it doesn't exist.
|
||||||
|
func (c *Config) TryFloat64(key string) (float64, error) {
|
||||||
|
return TryFloat64(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryInt loads an optional int configuration value by its key, or returns an error if it doesn't exist.
|
||||||
|
func (c *Config) TryInt(key string) (int, error) {
|
||||||
|
return TryInt(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryInt8 loads an optional int8 configuration value by its key, or returns an error if it doesn't exist.
|
||||||
|
func (c *Config) TryInt8(key string) (int8, error) {
|
||||||
|
return TryInt8(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryInt16 loads an optional int16 configuration value by its key, or returns an error if it doesn't exist.
|
||||||
|
func (c *Config) TryInt16(key string) (int16, error) {
|
||||||
|
return TryInt16(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryInt32 loads an optional int32 configuration value by its key, or returns an error if it doesn't exist.
|
||||||
|
func (c *Config) TryInt32(key string) (int32, error) {
|
||||||
|
return TryInt32(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryInt64 loads an optional int64 configuration value by its key, or returns an error if it doesn't exist.
|
||||||
|
func (c *Config) TryInt64(key string) (int64, error) {
|
||||||
|
return TryInt64(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryUint loads an optional uint configuration value by its key, or returns an error if it doesn't exist.
|
||||||
|
func (c *Config) TryUint(key string) (uint, error) {
|
||||||
|
return TryUint(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryUint8 loads an optional uint8 configuration value by its key, or returns an error if it doesn't exist.
|
||||||
|
func (c *Config) TryUint8(key string) (uint8, error) {
|
||||||
|
return TryUint8(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryUint16 loads an optional uint16 configuration value by its key, or returns an error if it doesn't exist.
|
||||||
|
func (c *Config) TryUint16(key string) (uint16, error) {
|
||||||
|
return TryUint16(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryUint32 loads an optional uint32 configuration value by its key, or returns an error if it doesn't exist.
|
||||||
|
func (c *Config) TryUint32(key string) (uint32, error) {
|
||||||
|
return TryUint32(c.ctx, c.fullKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryUint64 loads an optional uint64 configuration value by its key, or returns an error if it doesn't exist.
|
||||||
|
func (c *Config) TryUint64(key string) (uint64, error) {
|
||||||
|
return TryUint64(c.ctx, c.fullKey(key))
|
||||||
|
}
|
79
sdk/go/pulumi/config/config_test.go
Normal file
79
sdk/go/pulumi/config/config_test.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
// Copyright 2016-2018, 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.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/pulumi/pulumi/sdk/go/pulumi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestConfig tests the basic config wrapper.
|
||||||
|
func TestConfig(t *testing.T) {
|
||||||
|
ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{
|
||||||
|
Config: map[string]string{
|
||||||
|
"testpkg:sss": "a string value",
|
||||||
|
"testpkg:bbb": "true",
|
||||||
|
"testpkg:intint": "42",
|
||||||
|
"testpkg:fpfpfp": "99.963",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
cfg := New(ctx, "testpkg")
|
||||||
|
|
||||||
|
// Test basic keys.
|
||||||
|
assert.Equal(t, "testpkg:sss", cfg.fullKey("sss"))
|
||||||
|
|
||||||
|
// Test Get, which returns a default value for missing entries rather than failing.
|
||||||
|
assert.Equal(t, "a string value", cfg.Get("sss"))
|
||||||
|
assert.Equal(t, true, cfg.GetBool("bbb"))
|
||||||
|
assert.Equal(t, 42, cfg.GetInt("intint"))
|
||||||
|
assert.Equal(t, 99.963, cfg.GetFloat64("fpfpfp"))
|
||||||
|
assert.Equal(t, "", cfg.Get("missing"))
|
||||||
|
|
||||||
|
// Test Require, which panics for missing entries.
|
||||||
|
assert.Equal(t, "a string value", cfg.Require("sss"))
|
||||||
|
assert.Equal(t, true, cfg.RequireBool("bbb"))
|
||||||
|
assert.Equal(t, 42, cfg.RequireInt("intint"))
|
||||||
|
assert.Equal(t, 99.963, cfg.RequireFloat64("fpfpfp"))
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r == nil {
|
||||||
|
t.Errorf("expected missing key for Require to panic")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_ = cfg.Require("missing")
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Test Try, which returns an error for missing entries.
|
||||||
|
k1, err := cfg.Try("sss")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "a string value", k1)
|
||||||
|
k2, err := cfg.TryBool("bbb")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, true, k2)
|
||||||
|
k3, err := cfg.TryInt("intint")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 42, k3)
|
||||||
|
k4, err := cfg.TryFloat64("fpfpfp")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 99.963, k4)
|
||||||
|
_, err = cfg.Try("missing")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
131
sdk/go/pulumi/config/get.go
Normal file
131
sdk/go/pulumi/config/get.go
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
// Copyright 2016-2018, 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.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
|
||||||
|
"github.com/pulumi/pulumi/sdk/go/pulumi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get loads an optional configuration value by its key, or returns "" if it doesn't exist.
|
||||||
|
func Get(ctx *pulumi.Context, key string) string {
|
||||||
|
v, _ := ctx.GetConfig(key)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBool loads an optional configuration value by its key, as a bool, or returns false if it doesn't exist.
|
||||||
|
func GetBool(ctx *pulumi.Context, key string) bool {
|
||||||
|
if v, ok := ctx.GetConfig(key); ok {
|
||||||
|
return cast.ToBool(v)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFloat32 loads an optional configuration value by its key, as a float32, or returns 0.0 if it doesn't exist.
|
||||||
|
func GetFloat32(ctx *pulumi.Context, key string) float32 {
|
||||||
|
if v, ok := ctx.GetConfig(key); ok {
|
||||||
|
return cast.ToFloat32(v)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFloat64 loads an optional configuration value by its key, as a float64, or returns 0.0 if it doesn't exist.
|
||||||
|
func GetFloat64(ctx *pulumi.Context, key string) float64 {
|
||||||
|
if v, ok := ctx.GetConfig(key); ok {
|
||||||
|
return cast.ToFloat64(v)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt loads an optional configuration value by its key, as a int, or returns 0 if it doesn't exist.
|
||||||
|
func GetInt(ctx *pulumi.Context, key string) int {
|
||||||
|
if v, ok := ctx.GetConfig(key); ok {
|
||||||
|
return cast.ToInt(v)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt8 loads an optional configuration value by its key, as a int8, or returns 0 if it doesn't exist.
|
||||||
|
func GetInt8(ctx *pulumi.Context, key string) int8 {
|
||||||
|
if v, ok := ctx.GetConfig(key); ok {
|
||||||
|
return cast.ToInt8(v)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt16 loads an optional configuration value by its key, as a int16, or returns 0 if it doesn't exist.
|
||||||
|
func GetInt16(ctx *pulumi.Context, key string) int16 {
|
||||||
|
if v, ok := ctx.GetConfig(key); ok {
|
||||||
|
return cast.ToInt16(v)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt32 loads an optional configuration value by its key, as a int32, or returns 0 if it doesn't exist.
|
||||||
|
func GetInt32(ctx *pulumi.Context, key string) int32 {
|
||||||
|
if v, ok := ctx.GetConfig(key); ok {
|
||||||
|
return cast.ToInt32(v)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt64 loads an optional configuration value by its key, as a int64, or returns 0 if it doesn't exist.
|
||||||
|
func GetInt64(ctx *pulumi.Context, key string) int64 {
|
||||||
|
if v, ok := ctx.GetConfig(key); ok {
|
||||||
|
return cast.ToInt64(v)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint loads an optional configuration value by its key, as a uint, or returns 0 if it doesn't exist.
|
||||||
|
func GetUint(ctx *pulumi.Context, key string) uint {
|
||||||
|
if v, ok := ctx.GetConfig(key); ok {
|
||||||
|
return cast.ToUint(v)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint8 loads an optional configuration value by its key, as a uint8, or returns 0 if it doesn't exist.
|
||||||
|
func GetUint8(ctx *pulumi.Context, key string) uint8 {
|
||||||
|
if v, ok := ctx.GetConfig(key); ok {
|
||||||
|
return cast.ToUint8(v)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint16 loads an optional configuration value by its key, as a uint16, or returns 0 if it doesn't exist.
|
||||||
|
func GetUint16(ctx *pulumi.Context, key string) uint16 {
|
||||||
|
if v, ok := ctx.GetConfig(key); ok {
|
||||||
|
return cast.ToUint16(v)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint32 loads an optional configuration value by its key, as a uint32, or returns 0 if it doesn't exist.
|
||||||
|
func GetUint32(ctx *pulumi.Context, key string) uint32 {
|
||||||
|
if v, ok := ctx.GetConfig(key); ok {
|
||||||
|
return cast.ToUint32(v)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint64 loads an optional configuration value by its key, as a uint64, or returns 0 if it doesn't exist.
|
||||||
|
func GetUint64(ctx *pulumi.Context, key string) uint64 {
|
||||||
|
if v, ok := ctx.GetConfig(key); ok {
|
||||||
|
return cast.ToUint64(v)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
109
sdk/go/pulumi/config/require.go
Normal file
109
sdk/go/pulumi/config/require.go
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
// Copyright 2016-2018, 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.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
|
||||||
|
"github.com/pulumi/pulumi/pkg/util/contract"
|
||||||
|
"github.com/pulumi/pulumi/sdk/go/pulumi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Require loads a configuration value by its key, or panics if it doesn't exist.
|
||||||
|
func Require(ctx *pulumi.Context, key string) string {
|
||||||
|
v, ok := ctx.GetConfig(key)
|
||||||
|
if !ok {
|
||||||
|
contract.Failf("missing required configuration variable '%s'; run `pulumi config` to set", key)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireBool loads an optional configuration value by its key, as a bool, or panics if it doesn't exist.
|
||||||
|
func RequireBool(ctx *pulumi.Context, key string) bool {
|
||||||
|
v := Require(ctx, key)
|
||||||
|
return cast.ToBool(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireFloat32 loads an optional configuration value by its key, as a float32, or panics if it doesn't exist.
|
||||||
|
func RequireFloat32(ctx *pulumi.Context, key string) float32 {
|
||||||
|
v := Require(ctx, key)
|
||||||
|
return cast.ToFloat32(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireFloat64 loads an optional configuration value by its key, as a float64, or panics if it doesn't exist.
|
||||||
|
func RequireFloat64(ctx *pulumi.Context, key string) float64 {
|
||||||
|
v := Require(ctx, key)
|
||||||
|
return cast.ToFloat64(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireInt loads an optional configuration value by its key, as a int, or panics if it doesn't exist.
|
||||||
|
func RequireInt(ctx *pulumi.Context, key string) int {
|
||||||
|
v := Require(ctx, key)
|
||||||
|
return cast.ToInt(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireInt8 loads an optional configuration value by its key, as a int8, or panics if it doesn't exist.
|
||||||
|
func RequireInt8(ctx *pulumi.Context, key string) int8 {
|
||||||
|
v := Require(ctx, key)
|
||||||
|
return cast.ToInt8(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireInt16 loads an optional configuration value by its key, as a int16, or panics if it doesn't exist.
|
||||||
|
func RequireInt16(ctx *pulumi.Context, key string) int16 {
|
||||||
|
v := Require(ctx, key)
|
||||||
|
return cast.ToInt16(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireInt32 loads an optional configuration value by its key, as a int32, or panics if it doesn't exist.
|
||||||
|
func RequireInt32(ctx *pulumi.Context, key string) int32 {
|
||||||
|
v := Require(ctx, key)
|
||||||
|
return cast.ToInt32(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireInt64 loads an optional configuration value by its key, as a int64, or panics if it doesn't exist.
|
||||||
|
func RequireInt64(ctx *pulumi.Context, key string) int64 {
|
||||||
|
v := Require(ctx, key)
|
||||||
|
return cast.ToInt64(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireUint loads an optional configuration value by its key, as a uint, or panics if it doesn't exist.
|
||||||
|
func RequireUint(ctx *pulumi.Context, key string) uint {
|
||||||
|
v := Require(ctx, key)
|
||||||
|
return cast.ToUint(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireUint8 loads an optional configuration value by its key, as a uint8, or panics if it doesn't exist.
|
||||||
|
func RequireUint8(ctx *pulumi.Context, key string) uint8 {
|
||||||
|
v := Require(ctx, key)
|
||||||
|
return cast.ToUint8(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireUint16 loads an optional configuration value by its key, as a uint16, or panics if it doesn't exist.
|
||||||
|
func RequireUint16(ctx *pulumi.Context, key string) uint16 {
|
||||||
|
v := Require(ctx, key)
|
||||||
|
return cast.ToUint16(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireUint32 loads an optional configuration value by its key, as a uint32, or panics if it doesn't exist.
|
||||||
|
func RequireUint32(ctx *pulumi.Context, key string) uint32 {
|
||||||
|
v := Require(ctx, key)
|
||||||
|
return cast.ToUint32(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireUint64 loads an optional configuration value by its key, as a uint64, or panics if it doesn't exist.
|
||||||
|
func RequireUint64(ctx *pulumi.Context, key string) uint64 {
|
||||||
|
v := Require(ctx, key)
|
||||||
|
return cast.ToUint64(v)
|
||||||
|
}
|
149
sdk/go/pulumi/config/try.go
Normal file
149
sdk/go/pulumi/config/try.go
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
// Copyright 2016-2018, 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.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
|
||||||
|
"github.com/pulumi/pulumi/sdk/go/pulumi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Try loads a configuration value by its key, returning a non-nil error if it doesn't exist.
|
||||||
|
func Try(ctx *pulumi.Context, key string) (string, error) {
|
||||||
|
v, ok := ctx.GetConfig(key)
|
||||||
|
if !ok {
|
||||||
|
return "",
|
||||||
|
errors.Errorf("missing required configuration variable '%s'; run `pulumi config` to set", key)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryBool loads an optional configuration value by its key, as a bool, or returns an error if it doesn't exist.
|
||||||
|
func TryBool(ctx *pulumi.Context, key string) (bool, error) {
|
||||||
|
v, err := Try(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return cast.ToBool(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryFloat32 loads an optional configuration value by its key, as a float32, or returns an error if it doesn't exist.
|
||||||
|
func TryFloat32(ctx *pulumi.Context, key string) (float32, error) {
|
||||||
|
v, err := Try(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cast.ToFloat32(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryFloat64 loads an optional configuration value by its key, as a float64, or returns an error if it doesn't exist.
|
||||||
|
func TryFloat64(ctx *pulumi.Context, key string) (float64, error) {
|
||||||
|
v, err := Try(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cast.ToFloat64(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryInt loads an optional configuration value by its key, as a int, or returns an error if it doesn't exist.
|
||||||
|
func TryInt(ctx *pulumi.Context, key string) (int, error) {
|
||||||
|
v, err := Try(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cast.ToInt(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryInt8 loads an optional configuration value by its key, as a int8, or returns an error if it doesn't exist.
|
||||||
|
func TryInt8(ctx *pulumi.Context, key string) (int8, error) {
|
||||||
|
v, err := Try(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cast.ToInt8(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryInt16 loads an optional configuration value by its key, as a int16, or returns an error if it doesn't exist.
|
||||||
|
func TryInt16(ctx *pulumi.Context, key string) (int16, error) {
|
||||||
|
v, err := Try(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cast.ToInt16(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryInt32 loads an optional configuration value by its key, as a int32, or returns an error if it doesn't exist.
|
||||||
|
func TryInt32(ctx *pulumi.Context, key string) (int32, error) {
|
||||||
|
v, err := Try(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cast.ToInt32(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryInt64 loads an optional configuration value by its key, as a int64, or returns an error if it doesn't exist.
|
||||||
|
func TryInt64(ctx *pulumi.Context, key string) (int64, error) {
|
||||||
|
v, err := Try(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cast.ToInt64(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryUint loads an optional configuration value by its key, as a uint, or returns an error if it doesn't exist.
|
||||||
|
func TryUint(ctx *pulumi.Context, key string) (uint, error) {
|
||||||
|
v, err := Try(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cast.ToUint(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryUint8 loads an optional configuration value by its key, as a uint8, or returns an error if it doesn't exist.
|
||||||
|
func TryUint8(ctx *pulumi.Context, key string) (uint8, error) {
|
||||||
|
v, err := Try(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cast.ToUint8(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryUint16 loads an optional configuration value by its key, as a uint16, or returns an error if it doesn't exist.
|
||||||
|
func TryUint16(ctx *pulumi.Context, key string) (uint16, error) {
|
||||||
|
v, err := Try(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cast.ToUint16(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryUint32 loads an optional configuration value by its key, as a uint32, or returns an error if it doesn't exist.
|
||||||
|
func TryUint32(ctx *pulumi.Context, key string) (uint32, error) {
|
||||||
|
v, err := Try(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cast.ToUint32(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryUint64 loads an optional configuration value by its key, as a uint64, or returns an error if it doesn't exist.
|
||||||
|
func TryUint64(ctx *pulumi.Context, key string) (uint64, error) {
|
||||||
|
v, err := Try(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cast.ToUint64(v), nil
|
||||||
|
}
|
514
sdk/go/pulumi/context.go
Normal file
514
sdk/go/pulumi/context.go
Normal file
|
@ -0,0 +1,514 @@
|
||||||
|
// Copyright 2016-2018, 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.
|
||||||
|
|
||||||
|
package pulumi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
structpb "github.com/golang/protobuf/ptypes/struct"
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
pulumirpc "github.com/pulumi/pulumi/sdk/proto/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Context handles registration of resources and exposes metadata about the current deployment context.
|
||||||
|
type Context struct {
|
||||||
|
ctx context.Context
|
||||||
|
info RunInfo
|
||||||
|
stackR URN
|
||||||
|
exports map[string]interface{}
|
||||||
|
monitor pulumirpc.ResourceMonitorClient
|
||||||
|
monitorConn *grpc.ClientConn
|
||||||
|
engine pulumirpc.EngineClient
|
||||||
|
engineConn *grpc.ClientConn
|
||||||
|
rpcs int // the number of outstanding RPC requests.
|
||||||
|
rpcsDone *sync.Cond // an event signaling completion of RPCs.
|
||||||
|
rpcsLock *sync.Mutex // a lock protecting the RPC count and event.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext creates a fresh run context out of the given metadata.
|
||||||
|
func NewContext(ctx context.Context, info RunInfo) (*Context, error) {
|
||||||
|
// Connect to the gRPC endpoints if we have addresses for them.
|
||||||
|
var monitorConn *grpc.ClientConn
|
||||||
|
var monitor pulumirpc.ResourceMonitorClient
|
||||||
|
if addr := info.MonitorAddr; addr != "" {
|
||||||
|
conn, err := grpc.Dial(info.MonitorAddr, grpc.WithInsecure())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "connecting to resource monitor over RPC")
|
||||||
|
}
|
||||||
|
monitorConn = conn
|
||||||
|
monitor = pulumirpc.NewResourceMonitorClient(monitorConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
var engineConn *grpc.ClientConn
|
||||||
|
var engine pulumirpc.EngineClient
|
||||||
|
if addr := info.EngineAddr; addr != "" {
|
||||||
|
conn, err := grpc.Dial(info.EngineAddr, grpc.WithInsecure())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "connecting to engine over RPC")
|
||||||
|
}
|
||||||
|
engineConn = conn
|
||||||
|
engine = pulumirpc.NewEngineClient(engineConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex := &sync.Mutex{}
|
||||||
|
return &Context{
|
||||||
|
ctx: ctx,
|
||||||
|
info: info,
|
||||||
|
exports: make(map[string]interface{}),
|
||||||
|
monitorConn: monitorConn,
|
||||||
|
monitor: monitor,
|
||||||
|
engineConn: engineConn,
|
||||||
|
engine: engine,
|
||||||
|
rpcs: 0,
|
||||||
|
rpcsLock: mutex,
|
||||||
|
rpcsDone: sync.NewCond(mutex),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements io.Closer and relinquishes any outstanding resources held by the context.
|
||||||
|
func (ctx *Context) Close() error {
|
||||||
|
if ctx.engineConn != nil {
|
||||||
|
if err := ctx.engineConn.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ctx.monitorConn != nil {
|
||||||
|
if err := ctx.monitorConn.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project returns the current project name.
|
||||||
|
func (ctx *Context) Project() string { return ctx.info.Project }
|
||||||
|
|
||||||
|
// Stack returns the current stack name being deployed into.
|
||||||
|
func (ctx *Context) Stack() string { return ctx.info.Stack }
|
||||||
|
|
||||||
|
// Parallel returns the degree of parallelism currently being used by the engine (1 being entirely serial).
|
||||||
|
func (ctx *Context) Parallel() int { return ctx.info.Parallel }
|
||||||
|
|
||||||
|
// DryRun is true when evaluating a program for purposes of planning, instead of performing a true deployment.
|
||||||
|
func (ctx *Context) DryRun() bool { return ctx.info.DryRun }
|
||||||
|
|
||||||
|
// GetConfig returns the config value, as a string, and a bool indicating whether it exists or not.
|
||||||
|
func (ctx *Context) GetConfig(key string) (string, bool) {
|
||||||
|
v, ok := ctx.info.Config[key]
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke will invoke a provider's function, identified by its token tok. This function call is synchronous.
|
||||||
|
func (ctx *Context) Invoke(tok string, args map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
if tok == "" {
|
||||||
|
return nil, errors.New("invoke token must not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize arguments, first by awaiting them, and then marshaling them to the requisite gRPC values.
|
||||||
|
// TODO[pulumi/pulumi#1483]: feels like we should be propagating dependencies to the outputs, instead of ignoring.
|
||||||
|
_, rpcArgs, _, err := marshalInputs(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "marshaling arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that we're about to make an outstanding RPC request, so that we can rendezvous during shutdown.
|
||||||
|
if err = ctx.beginRPC(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer ctx.endRPC()
|
||||||
|
|
||||||
|
// Now, invoke the RPC to the provider synchronously.
|
||||||
|
glog.V(9).Infof("Invoke(%s, #args=%d): RPC call being made synchronously", tok, len(args))
|
||||||
|
resp, err := ctx.monitor.Invoke(ctx.ctx, &pulumirpc.InvokeRequest{
|
||||||
|
Tok: tok,
|
||||||
|
Args: rpcArgs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
glog.V(9).Infof("Invoke(%s, ...): error: %v", tok, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there were any failures from the provider, return them.
|
||||||
|
if len(resp.Failures) > 0 {
|
||||||
|
glog.V(9).Infof("Invoke(%s, ...): success: w/ %d failures", tok, len(resp.Failures))
|
||||||
|
var ferr error
|
||||||
|
for _, failure := range resp.Failures {
|
||||||
|
ferr = multierror.Append(ferr,
|
||||||
|
errors.Errorf("%s invoke failed: %s (%s)", tok, failure.Reason, failure.Property))
|
||||||
|
}
|
||||||
|
return nil, ferr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwsie, simply unmarshal the output properties and return the result.
|
||||||
|
outs, err := unmarshalOutputs(resp.Return)
|
||||||
|
glog.V(9).Infof("Invoke(%s, ...): success: w/ %d outs (err=%v)", tok, len(outs), err)
|
||||||
|
return outs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadResource reads an existing custom resource's state from the resource monitor. Note that resources read in this
|
||||||
|
// way will not be part of the resulting stack's state, as they are presumed to belong to another.
|
||||||
|
func (ctx *Context) ReadResource(
|
||||||
|
t, name string, id ID, props map[string]interface{}, opts ...ResourceOpt) (*ResourceState, error) {
|
||||||
|
if t == "" {
|
||||||
|
return nil, errors.New("resource type argument cannot be empty")
|
||||||
|
} else if name == "" {
|
||||||
|
return nil, errors.New("resource name argument (for URN creation) cannot be empty")
|
||||||
|
} else if id == "" {
|
||||||
|
return nil, errors.New("resource ID is required for lookup and cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the inputs for an impending operation.
|
||||||
|
op, err := ctx.newResourceOperation(true, props, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that we're about to make an outstanding RPC request, so that we can rendezvous during shutdown.
|
||||||
|
if err = ctx.beginRPC(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kick off the resource read operation. This will happen asynchronously and resolve the above properties.
|
||||||
|
go func() {
|
||||||
|
glog.V(9).Infof("ReadResource(%s, %s): Goroutine spawned, RPC call being made", t, name)
|
||||||
|
resp, err := ctx.monitor.ReadResource(ctx.ctx, &pulumirpc.ReadResourceRequest{
|
||||||
|
Type: t,
|
||||||
|
Name: name,
|
||||||
|
Parent: op.parent,
|
||||||
|
Properties: op.rpcProps,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
glog.V(9).Infof("RegisterResource(%s, %s): error: %v", t, name, err)
|
||||||
|
} else {
|
||||||
|
glog.V(9).Infof("RegisterResource(%s, %s): success: %s %s ...", t, name, resp.Urn, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// No matter the outcome, make sure all promises are resolved.
|
||||||
|
op.complete(err, resp.Urn, string(id), resp.Properties)
|
||||||
|
|
||||||
|
// Signal the completion of this RPC and notify any potential awaiters.
|
||||||
|
ctx.endRPC()
|
||||||
|
}()
|
||||||
|
|
||||||
|
outs := make(map[string]*Output)
|
||||||
|
for k, s := range op.outState {
|
||||||
|
outs[k] = s.out
|
||||||
|
}
|
||||||
|
return &ResourceState{
|
||||||
|
URN: (*URNOutput)(op.outURN.out),
|
||||||
|
ID: (*IDOutput)(op.outID.out),
|
||||||
|
State: outs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterResource creates and registers a new resource object. t is the fully qualified type token and name is
|
||||||
|
// the "name" part to use in creating a stable and globally unique URN for the object. state contains the goal state
|
||||||
|
// for the resource object and opts contains optional settings that govern the way the resource is created.
|
||||||
|
func (ctx *Context) RegisterResource(
|
||||||
|
t, name string, custom bool, props map[string]interface{}, opts ...ResourceOpt) (*ResourceState, error) {
|
||||||
|
if t == "" {
|
||||||
|
return nil, errors.New("resource type argument cannot be empty")
|
||||||
|
} else if name == "" {
|
||||||
|
return nil, errors.New("resource name argument (for URN creation) cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the inputs for an impending operation.
|
||||||
|
op, err := ctx.newResourceOperation(custom, props, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that we're about to make an outstanding RPC request, so that we can rendezvous during shutdown.
|
||||||
|
if err = ctx.beginRPC(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kick off the resource registration. If we are actually performing a deployment, the resulting properties
|
||||||
|
// will be resolved asynchronously as the RPC operation completes. If we're just planning, values won't resolve.
|
||||||
|
go func() {
|
||||||
|
glog.V(9).Infof("RegisterResource(%s, %s): Goroutine spawned, RPC call being made", t, name)
|
||||||
|
resp, err := ctx.monitor.RegisterResource(ctx.ctx, &pulumirpc.RegisterResourceRequest{
|
||||||
|
Type: t,
|
||||||
|
Name: name,
|
||||||
|
Parent: op.parent,
|
||||||
|
Object: op.rpcProps,
|
||||||
|
Custom: custom,
|
||||||
|
Protect: op.protect,
|
||||||
|
Dependencies: op.deps,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
glog.V(9).Infof("RegisterResource(%s, %s): error: %v", t, name, err)
|
||||||
|
} else {
|
||||||
|
glog.V(9).Infof("RegisterResource(%s, %s): success: %s %s ...", t, name, resp.Urn, resp.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// No matter the outcome, make sure all promises are resolved.
|
||||||
|
op.complete(err, resp.Urn, resp.Id, resp.Object)
|
||||||
|
|
||||||
|
// Signal the completion of this RPC and notify any potential awaiters.
|
||||||
|
ctx.endRPC()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var id *IDOutput
|
||||||
|
if op.outID != nil {
|
||||||
|
id = (*IDOutput)(op.outID.out)
|
||||||
|
}
|
||||||
|
outs := make(map[string]*Output)
|
||||||
|
for k, s := range op.outState {
|
||||||
|
outs[k] = s.out
|
||||||
|
}
|
||||||
|
return &ResourceState{
|
||||||
|
URN: (*URNOutput)(op.outURN.out),
|
||||||
|
ID: id,
|
||||||
|
State: outs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resourceOperation reflects all of the inputs necessary to perform core resource RPC operations.
|
||||||
|
type resourceOperation struct {
|
||||||
|
ctx *Context
|
||||||
|
parent string
|
||||||
|
deps []string
|
||||||
|
protect bool
|
||||||
|
props map[string]interface{}
|
||||||
|
rpcProps *structpb.Struct
|
||||||
|
outURN *resourceOutput
|
||||||
|
outID *resourceOutput
|
||||||
|
outState map[string]*resourceOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
// newResourceOperation prepares the inputs for a resource operation, shared between read and register.
|
||||||
|
func (ctx *Context) newResourceOperation(custom bool, props map[string]interface{},
|
||||||
|
opts ...ResourceOpt) (*resourceOperation, error) {
|
||||||
|
// Get the parent and dependency URNs from the options, in addition to the protection bit. If there wasn't an
|
||||||
|
// explicit parent, and a root stack resource exists, we will automatically parent to that.
|
||||||
|
parent, optDeps, protect := ctx.getOpts(opts...)
|
||||||
|
|
||||||
|
// Serialize all properties, first by awaiting them, and then marshaling them to the requisite gRPC values.
|
||||||
|
keys, rpcProps, rpcDeps, err := marshalInputs(props)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "marshaling properties")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge all dependencies with what we got earlier from property marshaling, and remove duplicates.
|
||||||
|
var deps []string
|
||||||
|
depMap := make(map[URN]bool)
|
||||||
|
for _, dep := range append(optDeps, rpcDeps...) {
|
||||||
|
if _, has := depMap[dep]; !has {
|
||||||
|
deps = append(deps, string(dep))
|
||||||
|
depMap[dep] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(deps)
|
||||||
|
|
||||||
|
// Create a set of resolvers that we'll use to finalize state, for URNs, IDs, and output properties.
|
||||||
|
outURN, resolveURN, rejectURN := NewOutput(nil)
|
||||||
|
urn := &resourceOutput{out: outURN, resolve: resolveURN, reject: rejectURN}
|
||||||
|
|
||||||
|
var id *resourceOutput
|
||||||
|
if custom {
|
||||||
|
outID, resolveID, rejectID := NewOutput(nil)
|
||||||
|
id = &resourceOutput{out: outID, resolve: resolveID, reject: rejectID}
|
||||||
|
}
|
||||||
|
|
||||||
|
state := make(map[string]*resourceOutput)
|
||||||
|
for _, key := range keys {
|
||||||
|
outState, resolveState, rejectState := NewOutput(nil)
|
||||||
|
state[key] = &resourceOutput{
|
||||||
|
out: outState,
|
||||||
|
resolve: resolveState,
|
||||||
|
reject: rejectState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resourceOperation{
|
||||||
|
ctx: ctx,
|
||||||
|
parent: string(parent),
|
||||||
|
deps: deps,
|
||||||
|
protect: protect,
|
||||||
|
props: props,
|
||||||
|
rpcProps: rpcProps,
|
||||||
|
outURN: urn,
|
||||||
|
outID: id,
|
||||||
|
outState: state,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// complete finishes a resource operation given the set of RPC results.
|
||||||
|
func (op *resourceOperation) complete(err error, urn string, id string, result *structpb.Struct) {
|
||||||
|
var outprops map[string]interface{}
|
||||||
|
if err == nil {
|
||||||
|
outprops, err = unmarshalOutputs(result)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// If there was an error, we must reject everything: URN, ID, and state properties.
|
||||||
|
op.outURN.reject(err)
|
||||||
|
if op.outID != nil {
|
||||||
|
op.outID.reject(err)
|
||||||
|
}
|
||||||
|
for _, s := range op.outState {
|
||||||
|
s.reject(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Resolve the URN and ID.
|
||||||
|
op.outURN.resolve(URN(urn), true)
|
||||||
|
if op.outID != nil {
|
||||||
|
if id == "" && op.ctx.DryRun() {
|
||||||
|
op.outID.resolve("", false)
|
||||||
|
} else {
|
||||||
|
op.outID.resolve(ID(id), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// During previews, it's possible that nils will be returned due to unknown values. This function
|
||||||
|
// determines the known-ed-ness of a given value below.
|
||||||
|
isKnown := func(v interface{}) bool {
|
||||||
|
return !op.ctx.DryRun() || v != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now resolve all output properties.
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for k, v := range outprops {
|
||||||
|
if s, has := op.outState[k]; has {
|
||||||
|
s.resolve(v, isKnown(v))
|
||||||
|
seen[k] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't get back any inputs as outputs, resolve them to the inputs.
|
||||||
|
for k, s := range op.outState {
|
||||||
|
if !seen[k] {
|
||||||
|
v := op.props[k]
|
||||||
|
s.resolve(v, isKnown(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type resourceOutput struct {
|
||||||
|
out *Output
|
||||||
|
resolve func(interface{}, bool)
|
||||||
|
reject func(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOpts returns a set of resource options from an array of them. This includes the parent URN, any
|
||||||
|
// dependency URNs, and a boolean indicating whether the resource is to be protected.
|
||||||
|
func (ctx *Context) getOpts(opts ...ResourceOpt) (URN, []URN, bool) {
|
||||||
|
return ctx.getOptsParentURN(opts...),
|
||||||
|
ctx.getOptsDepURNs(opts...),
|
||||||
|
ctx.getOptsProtect(opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOptsParentURN returns a URN to use for a resource, given its options, defaulting to the current stack resource.
|
||||||
|
func (ctx *Context) getOptsParentURN(opts ...ResourceOpt) URN {
|
||||||
|
for _, opt := range opts {
|
||||||
|
if opt.Parent != nil {
|
||||||
|
return opt.Parent.URN()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx.stackR
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOptsDepURNs returns the set of dependency URNs in a resource's options.
|
||||||
|
func (ctx *Context) getOptsDepURNs(opts ...ResourceOpt) []URN {
|
||||||
|
var urns []URN
|
||||||
|
for _, opt := range opts {
|
||||||
|
for _, dep := range opt.DependsOn {
|
||||||
|
urns = append(urns, dep.URN())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return urns
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOptsProtect returns true if a resource's options indicate that it is to be protected.
|
||||||
|
func (ctx *Context) getOptsProtect(opts ...ResourceOpt) bool {
|
||||||
|
for _, opt := range opts {
|
||||||
|
if opt.Protect {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// noMoreRPCs is a sentinel value used to stop subsequent RPCs from occurring.
|
||||||
|
const noMoreRPCs = -1
|
||||||
|
|
||||||
|
// beginRPC attempts to start a new RPC request, returning a non-nil error if no more RPCs are permitted
|
||||||
|
// (usually because the program is shutting down).
|
||||||
|
func (ctx *Context) beginRPC() error {
|
||||||
|
ctx.rpcsLock.Lock()
|
||||||
|
defer ctx.rpcsLock.Unlock()
|
||||||
|
|
||||||
|
// If we're done with RPCs, return an error.
|
||||||
|
if ctx.rpcs == noMoreRPCs {
|
||||||
|
return errors.New("attempted illegal RPC after program completion")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.rpcs++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// endRPC signals the completion of an RPC and notifies any potential awaiters when outstanding RPCs hit zero.
|
||||||
|
func (ctx *Context) endRPC() {
|
||||||
|
ctx.rpcsLock.Lock()
|
||||||
|
defer ctx.rpcsLock.Unlock()
|
||||||
|
|
||||||
|
ctx.rpcs--
|
||||||
|
if ctx.rpcs == 0 {
|
||||||
|
ctx.rpcsDone.Broadcast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForRPCs awaits the completion of any outstanding RPCs and then leaves behind a sentinel to prevent
|
||||||
|
// any subsequent ones from starting. This is often used during the shutdown of a program to ensure no RPCs
|
||||||
|
// go missing due to the program exiting prior to their completion.
|
||||||
|
func (ctx *Context) waitForRPCs() {
|
||||||
|
ctx.rpcsLock.Lock()
|
||||||
|
defer ctx.rpcsLock.Unlock()
|
||||||
|
|
||||||
|
// Wait until the RPC count hits zero.
|
||||||
|
for ctx.rpcs > 0 {
|
||||||
|
ctx.rpcsDone.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the RPCs flag so that no more RPCs are permitted.
|
||||||
|
ctx.rpcs = noMoreRPCs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceState contains the results of a resource registration operation.
|
||||||
|
type ResourceState struct {
|
||||||
|
// URN will resolve to the resource's URN after registration has completed.
|
||||||
|
URN *URNOutput
|
||||||
|
// ID will resolve to the resource's ID after registration, provided this is for a custom resource.
|
||||||
|
ID *IDOutput
|
||||||
|
// State contains the full set of expected output properties and will resolve after completion.
|
||||||
|
State Outputs
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterResourceOutputs completes the resource registration, attaching an optional set of computed outputs.
|
||||||
|
func (ctx *Context) RegisterResourceOutputs(urn URN, outs map[string]interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export registers a key and value pair with the current context's stack.
|
||||||
|
func (ctx *Context) Export(name string, value interface{}) {
|
||||||
|
ctx.exports[name] = value
|
||||||
|
}
|
636
sdk/go/pulumi/properties.go
Normal file
636
sdk/go/pulumi/properties.go
Normal file
|
@ -0,0 +1,636 @@
|
||||||
|
// Copyright 2016-2018, 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.
|
||||||
|
|
||||||
|
package pulumi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
|
||||||
|
"github.com/pulumi/pulumi/sdk/go/pulumi/asset"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Output helps encode the relationship between resources in a Pulumi application. Specifically an output property
|
||||||
|
// holds onto a value and the resource it came from. An output value can then be provided when constructing new
|
||||||
|
// resources, allowing that new resource to know both the value as well as the resource the value came from. This
|
||||||
|
// allows for a precise "dependency graph" to be created, which properly tracks the relationship between resources.
|
||||||
|
type Output struct {
|
||||||
|
s *outputState // protect against value aliasing.
|
||||||
|
}
|
||||||
|
|
||||||
|
// outputState is a heap-allocated block of state for each output property, in case of aliasing.
|
||||||
|
type outputState struct {
|
||||||
|
sync chan *valueOrError // the channel for outputs whose values are not yet known.
|
||||||
|
voe *valueOrError // the value or error, after the channel has been rendezvoused with.
|
||||||
|
deps []Resource // the dependencies associated with this output property.
|
||||||
|
}
|
||||||
|
|
||||||
|
// valueOrError is a discriminated union between a value (possibly nil) or an error.
|
||||||
|
type valueOrError struct {
|
||||||
|
value interface{} // a value, if the output resolved to a value.
|
||||||
|
err error // an error, if the producer yielded an error instead of a value.
|
||||||
|
known bool // true if this value is known, versus just being a placeholder during previews.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOutput returns an output value that can be used to rendezvous with the production of a value or error. The
|
||||||
|
// function returns the output itself, plus two functions: one for resolving a value, and another for rejecting with an
|
||||||
|
// error; exactly one function must be called. This acts like a promise.
|
||||||
|
func NewOutput(deps []Resource) (*Output, func(interface{}, bool), func(error)) {
|
||||||
|
out := &Output{
|
||||||
|
s: &outputState{
|
||||||
|
sync: make(chan *valueOrError, 1),
|
||||||
|
deps: deps,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return out, out.resolve, out.reject
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve will resolve the output. It is not exported, because we want to control the capabilities tightly, such
|
||||||
|
// that anybody who happens to have an Output is not allowed to resolve it; only those who created it can.
|
||||||
|
func (out *Output) resolve(v interface{}, known bool) {
|
||||||
|
// If v is another output, chain this rather than resolving to an output directly.
|
||||||
|
if other, isOut := v.(*Output); known && isOut {
|
||||||
|
go func() {
|
||||||
|
real, otherKnown, err := other.Value()
|
||||||
|
if err != nil {
|
||||||
|
out.reject(err)
|
||||||
|
} else {
|
||||||
|
out.resolve(real, otherKnown)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
out.s.sync <- &valueOrError{value: v, known: known}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reject will reject the output. It is not exported, because we want to control the capabilities tightly, such
|
||||||
|
// that anybody who happens to have an Output is not allowed to reject it; only those who created it can.
|
||||||
|
func (out *Output) reject(err error) {
|
||||||
|
out.s.sync <- &valueOrError{err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply transforms the data of the output property using the applier func. The result remains an output property,
|
||||||
|
// and accumulates all implicated dependencies, so that resources can be properly tracked using a DAG. This function
|
||||||
|
// does not block awaiting the value; instead, it spawns a Goroutine that will await its availability.
|
||||||
|
func (out *Output) Apply(applier func(v interface{}) (interface{}, error)) *Output {
|
||||||
|
result, resolve, reject := NewOutput(out.Deps())
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil {
|
||||||
|
reject(err)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
if known {
|
||||||
|
// If we have a known value, run the applier to transform it.
|
||||||
|
u, err := applier(v)
|
||||||
|
if err != nil {
|
||||||
|
reject(err)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
// Now that we've transformed the value, it's possible we have another output. If so, pluck it
|
||||||
|
// out and go around to await it until we hit a real value. Note that we are not capturing the
|
||||||
|
// resources of this inner output, intentionally, as the output returned should be related to
|
||||||
|
// this output already.
|
||||||
|
if newout, ok := v.(*Output); ok {
|
||||||
|
out = newout
|
||||||
|
} else {
|
||||||
|
resolve(u, true)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the value isn't known, skip the apply function.
|
||||||
|
resolve(nil, false)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deps returns the dependencies for this output property.
|
||||||
|
func (out *Output) Deps() []Resource { return out.s.deps }
|
||||||
|
|
||||||
|
// Value retrieves the underlying value for this output property.
|
||||||
|
func (out *Output) Value() (interface{}, bool, error) {
|
||||||
|
// If neither error nor value are available, first await the channel. Only one Goroutine will make it through this
|
||||||
|
// and is responsible for closing the channel, to signal to other awaiters that it's safe to read the values.
|
||||||
|
if out.s.voe == nil {
|
||||||
|
if voe := <-out.s.sync; voe != nil {
|
||||||
|
out.s.voe = voe // first time through, publish the value.
|
||||||
|
close(out.s.sync) // and close the channel to signal to others that the memozied value is available.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.s.voe.value, out.s.voe.known, out.s.voe.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Archive retrives the underlying value for this output property as an archive.
|
||||||
|
func (out *Output) Archive() (asset.Archive, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return nil, known, err
|
||||||
|
}
|
||||||
|
return v.(asset.Archive), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array retrives the underlying value for this output property as an array.
|
||||||
|
func (out *Output) Array() ([]interface{}, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return nil, known, err
|
||||||
|
}
|
||||||
|
return cast.ToSlice(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asset retrives the underlying value for this output property as an asset.
|
||||||
|
func (out *Output) Asset() (asset.Asset, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return nil, known, err
|
||||||
|
}
|
||||||
|
return v.(asset.Asset), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool retrives the underlying value for this output property as a bool.
|
||||||
|
func (out *Output) Bool() (bool, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return false, known, err
|
||||||
|
}
|
||||||
|
return cast.ToBool(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map retrives the underlying value for this output property as a map.
|
||||||
|
func (out *Output) Map() (map[string]interface{}, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return nil, known, err
|
||||||
|
}
|
||||||
|
return cast.ToStringMap(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32 retrives the underlying value for this output property as a float32.
|
||||||
|
func (out *Output) Float32() (float32, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return 0, known, err
|
||||||
|
}
|
||||||
|
return cast.ToFloat32(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 retrives the underlying value for this output property as a float64.
|
||||||
|
func (out *Output) Float64() (float64, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return 0, known, err
|
||||||
|
}
|
||||||
|
return cast.ToFloat64(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID retrives the underlying value for this output property as an ID.
|
||||||
|
func (out *Output) ID() (ID, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return "", known, err
|
||||||
|
}
|
||||||
|
return ID(toString(v)), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int retrives the underlying value for this output property as a int.
|
||||||
|
func (out *Output) Int() (int, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return 0, known, err
|
||||||
|
}
|
||||||
|
return cast.ToInt(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int8 retrives the underlying value for this output property as a int8.
|
||||||
|
func (out *Output) Int8() (int8, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return 0, known, err
|
||||||
|
}
|
||||||
|
return cast.ToInt8(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int16 retrives the underlying value for this output property as a int16.
|
||||||
|
func (out *Output) Int16() (int16, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return 0, known, err
|
||||||
|
}
|
||||||
|
return cast.ToInt16(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32 retrives the underlying value for this output property as a int32.
|
||||||
|
func (out *Output) Int32() (int32, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return 0, known, err
|
||||||
|
}
|
||||||
|
return cast.ToInt32(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 retrives the underlying value for this output property as a int64.
|
||||||
|
func (out *Output) Int64() (int64, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return 0, known, err
|
||||||
|
}
|
||||||
|
return cast.ToInt64(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String retrives the underlying value for this output property as a string.
|
||||||
|
func (out *Output) String() (string, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return "", known, err
|
||||||
|
}
|
||||||
|
return toString(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint retrives the underlying value for this output property as a uint.
|
||||||
|
func (out *Output) Uint() (uint, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return 0, known, err
|
||||||
|
}
|
||||||
|
return cast.ToUint(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8 retrives the underlying value for this output property as a uint8.
|
||||||
|
func (out *Output) Uint8() (uint8, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return 0, known, err
|
||||||
|
}
|
||||||
|
return cast.ToUint8(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16 retrives the underlying value for this output property as a uint16.
|
||||||
|
func (out *Output) Uint16() (uint16, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return 0, known, err
|
||||||
|
}
|
||||||
|
return cast.ToUint16(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32 retrives the underlying value for this output property as a uint32.
|
||||||
|
func (out *Output) Uint32() (uint32, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return 0, known, err
|
||||||
|
}
|
||||||
|
return cast.ToUint32(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 retrives the underlying value for this output property as a uint64.
|
||||||
|
func (out *Output) Uint64() (uint64, bool, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return 0, known, err
|
||||||
|
}
|
||||||
|
return cast.ToUint64(v), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// URN retrives the underlying value for this output property as a URN.
|
||||||
|
func (out *Output) URN() (URN, error) {
|
||||||
|
v, known, err := out.Value()
|
||||||
|
if err != nil || !known {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return URN(toString(v)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outputs is a map of property name to value, one for each resource output property.
|
||||||
|
type Outputs map[string]*Output
|
||||||
|
|
||||||
|
// ArchiveOutput is an Output that is typed to return archive values.
|
||||||
|
type ArchiveOutput Output
|
||||||
|
|
||||||
|
// Value returns the underlying archive value.
|
||||||
|
func (out *ArchiveOutput) Value() (asset.Archive, bool, error) {
|
||||||
|
return (*Output)(out).Archive()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the archive value when it is available.
|
||||||
|
func (out *ArchiveOutput) Apply(applier func(asset.Archive) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(v.(asset.Archive))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayOutput is an Output that is typed to return arrays of values.
|
||||||
|
type ArrayOutput Output
|
||||||
|
|
||||||
|
// Value returns the underlying array value.
|
||||||
|
func (out *ArrayOutput) Value() ([]interface{}, bool, error) {
|
||||||
|
return (*Output)(out).Array()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the array value when it is available.
|
||||||
|
func (out *ArrayOutput) Apply(applier func([]interface{}) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(cast.ToSlice(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetOutput is an Output that is typed to return asset values.
|
||||||
|
type AssetOutput Output
|
||||||
|
|
||||||
|
// Value returns the underlying asset value.
|
||||||
|
func (out *AssetOutput) Value() (asset.Asset, bool, error) {
|
||||||
|
return (*Output)(out).Asset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the asset value when it is available.
|
||||||
|
func (out *AssetOutput) Apply(applier func(asset.Asset) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(v.(asset.Asset))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolOutput is an Output that is typed to return bool values.
|
||||||
|
type BoolOutput Output
|
||||||
|
|
||||||
|
// Value returns the underlying bool value.
|
||||||
|
func (out *BoolOutput) Value() (bool, bool, error) {
|
||||||
|
return (*Output)(out).Bool()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the bool value when it is available.
|
||||||
|
func (out *BoolOutput) Apply(applier func(bool) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(v.(bool))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32Output is an Output that is typed to return float32 values.
|
||||||
|
type Float32Output Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *Float32Output) Value() (float32, bool, error) {
|
||||||
|
return (*Output)(out).Float32()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the float32 value when it is available.
|
||||||
|
func (out *Float32Output) Apply(applier func(float32) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(cast.ToFloat32(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Output is an Output that is typed to return float64 values.
|
||||||
|
type Float64Output Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *Float64Output) Value() (float64, bool, error) {
|
||||||
|
return (*Output)(out).Float64()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the float64 value when it is available.
|
||||||
|
func (out *Float64Output) Apply(applier func(float64) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(cast.ToFloat64(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDOutput is an Output that is typed to return ID values.
|
||||||
|
type IDOutput Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *IDOutput) Value() (ID, bool, error) {
|
||||||
|
return (*Output)(out).ID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the ID value when it is available.
|
||||||
|
func (out *IDOutput) Apply(applier func(ID) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(ID(toString(v)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntOutput is an Output that is typed to return int values.
|
||||||
|
type IntOutput Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *IntOutput) Value() (int, bool, error) {
|
||||||
|
return (*Output)(out).Int()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the int value when it is available.
|
||||||
|
func (out *IntOutput) Apply(applier func(int) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(cast.ToInt(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int8Output is an Output that is typed to return int8 values.
|
||||||
|
type Int8Output Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *Int8Output) Value() (int8, bool, error) {
|
||||||
|
return (*Output)(out).Int8()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the int8 value when it is available.
|
||||||
|
func (out *Int8Output) Apply(applier func(int8) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(cast.ToInt8(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int16Output is an Output that is typed to return int16 values.
|
||||||
|
type Int16Output Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *Int16Output) Value() (int16, bool, error) {
|
||||||
|
return (*Output)(out).Int16()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the int16 value when it is available.
|
||||||
|
func (out *Int16Output) Apply(applier func(int16) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(cast.ToInt16(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32Output is an Output that is typed to return int32 values.
|
||||||
|
type Int32Output Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *Int32Output) Value() (int32, bool, error) {
|
||||||
|
return (*Output)(out).Int32()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the int32 value when it is available.
|
||||||
|
func (out *Int32Output) Apply(applier func(int32) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(cast.ToInt32(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Output is an Output that is typed to return int64 values.
|
||||||
|
type Int64Output Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *Int64Output) Value() (int64, bool, error) { return (*Output)(out).Int64() }
|
||||||
|
|
||||||
|
// Apply applies a transformation to the int64 value when it is available.
|
||||||
|
func (out *Int64Output) Apply(applier func(int64) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(cast.ToInt64(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapOutput is an Output that is typed to return map values.
|
||||||
|
type MapOutput Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *MapOutput) Value() (map[string]interface{}, bool, error) {
|
||||||
|
return (*Output)(out).Map()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the number value when it is available.
|
||||||
|
func (out *MapOutput) Apply(applier func(map[string]interface{}) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(cast.ToStringMap(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringOutput is an Output that is typed to return number values.
|
||||||
|
type StringOutput Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *StringOutput) Value() (string, bool, error) {
|
||||||
|
return (*Output)(out).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the number value when it is available.
|
||||||
|
func (out *StringOutput) Apply(applier func(string) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(toString(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UintOutput is an Output that is typed to return uint values.
|
||||||
|
type UintOutput Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *UintOutput) Value() (uint, bool, error) {
|
||||||
|
return (*Output)(out).Uint()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the uint value when it is available.
|
||||||
|
func (out *UintOutput) Apply(applier func(uint) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(cast.ToUint(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8Output is an Output that is typed to return uint8 values.
|
||||||
|
type Uint8Output Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *Uint8Output) Value() (uint8, bool, error) {
|
||||||
|
return (*Output)(out).Uint8()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the uint8 value when it is available.
|
||||||
|
func (out *Uint8Output) Apply(applier func(uint8) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(cast.ToUint8(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16Output is an Output that is typed to return uint16 values.
|
||||||
|
type Uint16Output Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *Uint16Output) Value() (uint16, bool, error) {
|
||||||
|
return (*Output)(out).Uint16()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the uint16 value when it is available.
|
||||||
|
func (out *Uint16Output) Apply(applier func(uint16) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(cast.ToUint16(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32Output is an Output that is typed to return uint32 values.
|
||||||
|
type Uint32Output Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *Uint32Output) Value() (uint32, bool, error) {
|
||||||
|
return (*Output)(out).Uint32()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the uint32 value when it is available.
|
||||||
|
func (out *Uint32Output) Apply(applier func(uint32) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(cast.ToUint32(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64Output is an Output that is typed to return uint64 values.
|
||||||
|
type Uint64Output Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *Uint64Output) Value() (uint64, bool, error) {
|
||||||
|
return (*Output)(out).Uint64()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the uint64 value when it is available.
|
||||||
|
func (out *Uint64Output) Apply(applier func(uint64) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(cast.ToUint64(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// URNOutput is an Output that is typed to return URN values.
|
||||||
|
type URNOutput Output
|
||||||
|
|
||||||
|
// Value returns the underlying number value.
|
||||||
|
func (out *URNOutput) Value() (URN, error) {
|
||||||
|
return (*Output)(out).URN()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a transformation to the URN value when it is available.
|
||||||
|
func (out *URNOutput) Apply(applier func(URN) (interface{}, error)) *Output {
|
||||||
|
return (*Output)(out).Apply(func(v interface{}) (interface{}, error) {
|
||||||
|
return applier(URN(toString(v)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// toString attempts to convert v to a string.
|
||||||
|
func toString(v interface{}) string {
|
||||||
|
if s := cast.ToString(v); s != "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if this can convert through reflection (e.g., for type aliases).
|
||||||
|
st := reflect.TypeOf("")
|
||||||
|
sv := reflect.ValueOf(v)
|
||||||
|
if sv.Type().ConvertibleTo(st) {
|
||||||
|
return sv.Convert(st).Interface().(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
277
sdk/go/pulumi/properties_test.go
Normal file
277
sdk/go/pulumi/properties_test.go
Normal file
|
@ -0,0 +1,277 @@
|
||||||
|
// Copyright 2016-2018, 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.
|
||||||
|
|
||||||
|
package pulumi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBasicOutputs(t *testing.T) {
|
||||||
|
// Just test basic resolve and reject functionality.
|
||||||
|
{
|
||||||
|
out, resolve, _ := NewOutput(nil)
|
||||||
|
go func() {
|
||||||
|
resolve(42, true)
|
||||||
|
}()
|
||||||
|
v, known, err := out.Value()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, known)
|
||||||
|
assert.NotNil(t, v)
|
||||||
|
assert.Equal(t, 42, v.(int))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
out, _, reject := NewOutput(nil)
|
||||||
|
go func() {
|
||||||
|
reject(errors.New("boom"))
|
||||||
|
}()
|
||||||
|
v, _, err := out.Value()
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Nil(t, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayOutputs(t *testing.T) {
|
||||||
|
out, resolve, _ := NewOutput(nil)
|
||||||
|
go func() {
|
||||||
|
resolve([]interface{}{nil, 0, "x"}, true)
|
||||||
|
}()
|
||||||
|
{
|
||||||
|
v, known, err := out.Array()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, known)
|
||||||
|
assert.NotNil(t, v)
|
||||||
|
if assert.Equal(t, 3, len(v)) {
|
||||||
|
assert.Equal(t, nil, v[0])
|
||||||
|
assert.Equal(t, 0, v[1])
|
||||||
|
assert.Equal(t, "x", v[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
arr := (*ArrayOutput)(out)
|
||||||
|
v, _, err := arr.Value()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, v)
|
||||||
|
if assert.Equal(t, 3, len(v)) {
|
||||||
|
assert.Equal(t, nil, v[0])
|
||||||
|
assert.Equal(t, 0, v[1])
|
||||||
|
assert.Equal(t, "x", v[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolOutputs(t *testing.T) {
|
||||||
|
out, resolve, _ := NewOutput(nil)
|
||||||
|
go func() {
|
||||||
|
resolve(true, true)
|
||||||
|
}()
|
||||||
|
{
|
||||||
|
v, known, err := out.Bool()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, known)
|
||||||
|
assert.True(t, v)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
b := (*BoolOutput)(out)
|
||||||
|
v, known, err := b.Value()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, known)
|
||||||
|
assert.True(t, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapOutputs(t *testing.T) {
|
||||||
|
out, resolve, _ := NewOutput(nil)
|
||||||
|
go func() {
|
||||||
|
resolve(map[string]interface{}{
|
||||||
|
"x": 1,
|
||||||
|
"y": false,
|
||||||
|
"z": "abc",
|
||||||
|
}, true)
|
||||||
|
}()
|
||||||
|
{
|
||||||
|
v, known, err := out.Map()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, known)
|
||||||
|
assert.NotNil(t, v)
|
||||||
|
assert.Equal(t, 1, v["x"])
|
||||||
|
assert.Equal(t, false, v["y"])
|
||||||
|
assert.Equal(t, "abc", v["z"])
|
||||||
|
}
|
||||||
|
{
|
||||||
|
b := (*MapOutput)(out)
|
||||||
|
v, known, err := b.Value()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, known)
|
||||||
|
assert.NotNil(t, v)
|
||||||
|
assert.Equal(t, 1, v["x"])
|
||||||
|
assert.Equal(t, false, v["y"])
|
||||||
|
assert.Equal(t, "abc", v["z"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNumberOutputs(t *testing.T) {
|
||||||
|
out, resolve, _ := NewOutput(nil)
|
||||||
|
go func() {
|
||||||
|
resolve(42.345, true)
|
||||||
|
}()
|
||||||
|
{
|
||||||
|
v, known, err := out.Float64()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, known)
|
||||||
|
assert.Equal(t, 42.345, v)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
b := (*Float64Output)(out)
|
||||||
|
v, known, err := b.Value()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, known)
|
||||||
|
assert.Equal(t, 42.345, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringOutputs(t *testing.T) {
|
||||||
|
out, resolve, _ := NewOutput(nil)
|
||||||
|
go func() {
|
||||||
|
resolve("a stringy output", true)
|
||||||
|
}()
|
||||||
|
{
|
||||||
|
v, known, err := out.String()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, known)
|
||||||
|
assert.Equal(t, "a stringy output", v)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
b := (*StringOutput)(out)
|
||||||
|
v, known, err := b.Value()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, known)
|
||||||
|
assert.Equal(t, "a stringy output", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveOutputToOutput(t *testing.T) {
|
||||||
|
// Test that resolving an output to an output yields the value, not the output.
|
||||||
|
{
|
||||||
|
out, resolve, _ := NewOutput(nil)
|
||||||
|
go func() {
|
||||||
|
other, resolveOther, _ := NewOutput(nil)
|
||||||
|
resolve(other, true)
|
||||||
|
go func() { resolveOther(99, true) }()
|
||||||
|
}()
|
||||||
|
v, known, err := out.Value()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, known)
|
||||||
|
assert.Equal(t, v, 99)
|
||||||
|
}
|
||||||
|
// Similarly, test that resolving an output to a rejected output yields an error.
|
||||||
|
{
|
||||||
|
out, resolve, _ := NewOutput(nil)
|
||||||
|
go func() {
|
||||||
|
other, _, rejectOther := NewOutput(nil)
|
||||||
|
resolve(other, true)
|
||||||
|
go func() { rejectOther(errors.New("boom")) }()
|
||||||
|
}()
|
||||||
|
v, _, err := out.Value()
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Nil(t, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputApply(t *testing.T) {
|
||||||
|
// Test that resolved outputs lead to applies being run.
|
||||||
|
{
|
||||||
|
out, resolve, _ := NewOutput(nil)
|
||||||
|
go func() { resolve(42, true) }()
|
||||||
|
var ranApp bool
|
||||||
|
b := (*IntOutput)(out)
|
||||||
|
app := b.Apply(func(v int) (interface{}, error) {
|
||||||
|
ranApp = true
|
||||||
|
return v + 1, nil
|
||||||
|
})
|
||||||
|
v, known, err := app.Value()
|
||||||
|
assert.True(t, ranApp)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, known)
|
||||||
|
assert.Equal(t, v, 43)
|
||||||
|
}
|
||||||
|
// Test that resolved, but known outputs, skip the running of applies.
|
||||||
|
{
|
||||||
|
out, resolve, _ := NewOutput(nil)
|
||||||
|
go func() { resolve(42, false) }()
|
||||||
|
var ranApp bool
|
||||||
|
b := (*IntOutput)(out)
|
||||||
|
app := b.Apply(func(v int) (interface{}, error) {
|
||||||
|
ranApp = true
|
||||||
|
return v + 1, nil
|
||||||
|
})
|
||||||
|
_, known, err := app.Value()
|
||||||
|
assert.False(t, ranApp)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.False(t, known)
|
||||||
|
}
|
||||||
|
// Test that rejected outputs do not run the apply, and instead flow the error.
|
||||||
|
{
|
||||||
|
out, _, reject := NewOutput(nil)
|
||||||
|
go func() { reject(errors.New("boom")) }()
|
||||||
|
var ranApp bool
|
||||||
|
b := (*IntOutput)(out)
|
||||||
|
app := b.Apply(func(v int) (interface{}, error) {
|
||||||
|
ranApp = true
|
||||||
|
return v + 1, nil
|
||||||
|
})
|
||||||
|
v, _, err := app.Value()
|
||||||
|
assert.False(t, ranApp)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Nil(t, v)
|
||||||
|
}
|
||||||
|
// Test that an an apply that returns an output returns the resolution of that output, not the output itself.
|
||||||
|
{
|
||||||
|
out, resolve, _ := NewOutput(nil)
|
||||||
|
go func() { resolve(42, true) }()
|
||||||
|
var ranApp bool
|
||||||
|
b := (*IntOutput)(out)
|
||||||
|
app := b.Apply(func(v int) (interface{}, error) {
|
||||||
|
other, resolveOther, _ := NewOutput(nil)
|
||||||
|
go func() { resolveOther(v+1, true) }()
|
||||||
|
ranApp = true
|
||||||
|
return other, nil
|
||||||
|
})
|
||||||
|
v, known, err := app.Value()
|
||||||
|
assert.True(t, ranApp)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, known)
|
||||||
|
assert.Equal(t, v, 43)
|
||||||
|
}
|
||||||
|
// Test that an an apply that reject an output returns the rejection of that output, not the output itself.
|
||||||
|
{
|
||||||
|
out, resolve, _ := NewOutput(nil)
|
||||||
|
go func() { resolve(42, true) }()
|
||||||
|
var ranApp bool
|
||||||
|
b := (*IntOutput)(out)
|
||||||
|
app := b.Apply(func(v int) (interface{}, error) {
|
||||||
|
other, _, rejectOther := NewOutput(nil)
|
||||||
|
go func() { rejectOther(errors.New("boom")) }()
|
||||||
|
ranApp = true
|
||||||
|
return other, nil
|
||||||
|
})
|
||||||
|
v, _, err := app.Value()
|
||||||
|
assert.True(t, ranApp)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Nil(t, v)
|
||||||
|
}
|
||||||
|
}
|
54
sdk/go/pulumi/resource.go
Normal file
54
sdk/go/pulumi/resource.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// Copyright 2016-2018, 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.
|
||||||
|
|
||||||
|
package pulumi
|
||||||
|
|
||||||
|
type (
|
||||||
|
// ID is a unique identifier assigned by a resource provider to a resource.
|
||||||
|
ID string
|
||||||
|
// URN is an automatically generated logical URN, used to stably identify resources.
|
||||||
|
URN string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Resource represents a cloud resource managed by Pulumi.
|
||||||
|
type Resource interface {
|
||||||
|
// URN is this resource's stable logical URN used to distinctly address it before, during, and after deployments.
|
||||||
|
URN() URN
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomResource is a cloud resource whose create, read, update, and delete (CRUD) operations are managed by performing
|
||||||
|
// external operations on some physical entity. The engine understands how to diff and perform partial updates of them,
|
||||||
|
// and these CRUD operations are implemented in a dynamically loaded plugin for the defining package.
|
||||||
|
type CustomResource interface {
|
||||||
|
Resource
|
||||||
|
// ID is the provider-assigned unique identifier for this managed resource. It is set during deployments,
|
||||||
|
// but might be missing ("") during planning phases.
|
||||||
|
ID() ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComponentResource is a resource that aggregates one or more other child resources into a higher level abstraction.
|
||||||
|
// The component resource itself is a resource, but does not require custom CRUD operations for provisioning.
|
||||||
|
type ComponentResource interface {
|
||||||
|
Resource
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceOpt contains optional settings that control a resource's behavior.
|
||||||
|
type ResourceOpt struct {
|
||||||
|
// Parent is an optional parent resource to which this resource belongs.
|
||||||
|
Parent Resource
|
||||||
|
// DependsOn is an optional array of explicit dependencies on other resources.
|
||||||
|
DependsOn []Resource
|
||||||
|
// Protect, when set to true, ensures that this resource cannot be deleted (without first setting it to false).
|
||||||
|
Protect bool
|
||||||
|
}
|
289
sdk/go/pulumi/rpc.go
Normal file
289
sdk/go/pulumi/rpc.go
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
// Copyright 2016-2018, 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.
|
||||||
|
|
||||||
|
package pulumi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
structpb "github.com/golang/protobuf/ptypes/struct"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
|
||||||
|
"github.com/pulumi/pulumi/pkg/resource"
|
||||||
|
"github.com/pulumi/pulumi/pkg/resource/plugin"
|
||||||
|
"github.com/pulumi/pulumi/sdk/go/pulumi/asset"
|
||||||
|
)
|
||||||
|
|
||||||
|
// marshalInputs turns resource property inputs into a gRPC struct suitable for marshaling.
|
||||||
|
func marshalInputs(props map[string]interface{}) ([]string, *structpb.Struct, []URN, error) {
|
||||||
|
var keys []string
|
||||||
|
for key := range props {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
var depURNs []URN
|
||||||
|
pmap := make(map[string]interface{})
|
||||||
|
for _, key := range keys {
|
||||||
|
// Get the underlying value, possibly waiting for an output to arrive.
|
||||||
|
v, deps, err := marshalInput(props[key])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, errors.Wrapf(err, "awaiting input property %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pmap[key] = v
|
||||||
|
|
||||||
|
// Record all dependencies accumulated from reading this property.
|
||||||
|
for _, dep := range deps {
|
||||||
|
depURNs = append(depURNs, dep.URN())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal all properties for the RPC call.
|
||||||
|
m, err := plugin.MarshalProperties(
|
||||||
|
resource.NewPropertyMapFromMap(pmap),
|
||||||
|
plugin.MarshalOptions{KeepUnknowns: true},
|
||||||
|
)
|
||||||
|
return keys, m, depURNs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// nolint: gas, linter thinks these are creds, but they aren't.
|
||||||
|
rpcTokenSpecialSigKey = "4dabf18193072939515e22adb298388d"
|
||||||
|
rpcTokenSpecialAssetSig = "c44067f5952c0a294b673a41bacd8c17"
|
||||||
|
rpcTokenSpecialArchiveSig = "0def7320c3a5731c473e5ecbe6d01bc7"
|
||||||
|
rpcTokenUnknownValue = "04da6b54-80e4-46f7-96ec-b56ff0331ba9"
|
||||||
|
)
|
||||||
|
|
||||||
|
// marshalInput marshals an input value, returning its raw serializable value along with any dependencies.
|
||||||
|
func marshalInput(v interface{}) (interface{}, []Resource, error) {
|
||||||
|
// If nil, just return that.
|
||||||
|
if v == nil {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, look for some well known types.
|
||||||
|
switch t := v.(type) {
|
||||||
|
case bool, int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, float64, string:
|
||||||
|
return t, nil, nil
|
||||||
|
case asset.Asset:
|
||||||
|
return map[string]interface{}{
|
||||||
|
rpcTokenSpecialSigKey: rpcTokenSpecialAssetSig,
|
||||||
|
"path": t.Path(),
|
||||||
|
"text": t.Text(),
|
||||||
|
"uri": t.URI(),
|
||||||
|
}, nil, nil
|
||||||
|
case asset.Archive:
|
||||||
|
var assets map[string]interface{}
|
||||||
|
if as := t.Assets(); as != nil {
|
||||||
|
assets = make(map[string]interface{})
|
||||||
|
for k, a := range as {
|
||||||
|
aa, _, err := marshalInput(a)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
assets[k] = aa
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
rpcTokenSpecialSigKey: rpcTokenSpecialAssetSig,
|
||||||
|
"assets": assets,
|
||||||
|
"path": t.Path(),
|
||||||
|
"uri": t.URI(),
|
||||||
|
}, nil, nil
|
||||||
|
case Output:
|
||||||
|
return marshalInputOutput(&t)
|
||||||
|
case *Output:
|
||||||
|
return marshalInputOutput(t)
|
||||||
|
case CustomResource:
|
||||||
|
// Resources aren't serializable; instead, serialize a reference to ID, tracking as a dependency.a
|
||||||
|
e, d, err := marshalInput(t.ID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return e, append([]Resource{t}, d...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, handle the usual primitives (numbers, strings, arrays, maps, ...)
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
switch rk := rv.Type().Kind(); rk {
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
// If an array or a slice, create a new array by recursing into elements.
|
||||||
|
var arr []interface{}
|
||||||
|
var deps []Resource
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
elem := rv.Index(i)
|
||||||
|
e, d, err := marshalInput(elem.Interface())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
arr = append(arr, e)
|
||||||
|
deps = append(deps, d...)
|
||||||
|
}
|
||||||
|
return arr, deps, nil
|
||||||
|
case reflect.Map:
|
||||||
|
// For maps, only support string-based keys, and recurse into the values.
|
||||||
|
obj := make(map[string]interface{})
|
||||||
|
var deps []Resource
|
||||||
|
for _, key := range rv.MapKeys() {
|
||||||
|
k, ok := key.Interface().(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil,
|
||||||
|
errors.Errorf("expected map keys to be strings; got %v", reflect.TypeOf(key.Interface()))
|
||||||
|
}
|
||||||
|
value := rv.MapIndex(key)
|
||||||
|
mv, d, err := marshalInput(value.Interface())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
obj[k] = mv
|
||||||
|
deps = append(deps, d...)
|
||||||
|
}
|
||||||
|
return obj, deps, nil
|
||||||
|
case reflect.Ptr:
|
||||||
|
// See if this is an alias for *Output. If so, convert to an *Output, and recurse.
|
||||||
|
ot := reflect.TypeOf(&Output{})
|
||||||
|
if rv.Type().ConvertibleTo(ot) {
|
||||||
|
oo := rv.Convert(ot)
|
||||||
|
return marshalInput(oo.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
// For all other pointers, recurse into the underlying value.
|
||||||
|
if rv.IsNil() {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
return marshalInput(rv.Elem().Interface())
|
||||||
|
case reflect.String:
|
||||||
|
return marshalInput(rv.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil, errors.Errorf("unrecognized input property type: %v (%v)", v, reflect.TypeOf(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalInputOutput(out *Output) (interface{}, []Resource, error) {
|
||||||
|
// Await the value and return its raw value.
|
||||||
|
ov, known, err := out.Value()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the value is known, marshal it.
|
||||||
|
if known {
|
||||||
|
e, d, merr := marshalInput(ov)
|
||||||
|
if merr != nil {
|
||||||
|
return nil, nil, merr
|
||||||
|
}
|
||||||
|
return e, append(out.Deps(), d...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, simply return the unknown value sentinel.
|
||||||
|
return rpcTokenUnknownValue, out.Deps(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshalOutputs unmarshals all the outputs into a simple map.
|
||||||
|
func unmarshalOutputs(outs *structpb.Struct) (map[string]interface{}, error) {
|
||||||
|
outprops, err := plugin.UnmarshalProperties(outs, plugin.MarshalOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[string]interface{})
|
||||||
|
for k, v := range outprops.Mappable() {
|
||||||
|
result[k], err = unmarshalOutput(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshalOutput unmarshals a single output variable into its runtime representation. For the most part, this just
|
||||||
|
// returns the raw value. In a small number of cases, we need to change a type.
|
||||||
|
func unmarshalOutput(v interface{}) (interface{}, error) {
|
||||||
|
// Check for nils and unknowns.
|
||||||
|
if v == nil || v == rpcTokenUnknownValue {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the case of assets and archives, turn these into real asset and archive structures.
|
||||||
|
if m, ok := v.(map[string]interface{}); ok {
|
||||||
|
if m[rpcTokenSpecialSigKey] == rpcTokenSpecialAssetSig {
|
||||||
|
if path := m["path"]; path != nil {
|
||||||
|
return asset.NewFileAsset(cast.ToString(path)), nil
|
||||||
|
} else if text := m["text"]; text != nil {
|
||||||
|
return asset.NewStringAsset(cast.ToString(text)), nil
|
||||||
|
} else if uri := m["uri"]; uri != nil {
|
||||||
|
return asset.NewRemoteAsset(cast.ToString(uri)), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("expected asset to be one of File, String, or Remote; got none")
|
||||||
|
} else if m[rpcTokenSpecialSigKey] == rpcTokenSpecialArchiveSig {
|
||||||
|
if assets := m["assets"]; assets != nil {
|
||||||
|
as := make(map[string]interface{})
|
||||||
|
for k, v := range assets.(map[string]interface{}) {
|
||||||
|
a, err := unmarshalOutput(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
as[k] = a
|
||||||
|
}
|
||||||
|
return asset.NewAssetArchive(as), nil
|
||||||
|
} else if path := m["path"]; path != nil {
|
||||||
|
return asset.NewFileArchive(cast.ToString(path)), nil
|
||||||
|
} else if uri := m["uri"]; uri != nil {
|
||||||
|
return asset.NewRemoteArchive(cast.ToString(uri)), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("expected asset to be one of File, String, or Remote; got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For arrays and maps, just make sure to transform them deeply.
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
switch rk := rv.Type().Kind(); rk {
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
// If an array or a slice, create a new array by recursing into elements.
|
||||||
|
var arr []interface{}
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
elem := rv.Index(i)
|
||||||
|
e, err := unmarshalOutput(elem.Interface())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
arr = append(arr, e)
|
||||||
|
}
|
||||||
|
return arr, nil
|
||||||
|
case reflect.Map:
|
||||||
|
// For maps, only support string-based keys, and recurse into the values.
|
||||||
|
obj := make(map[string]interface{})
|
||||||
|
for _, key := range rv.MapKeys() {
|
||||||
|
k, ok := key.Interface().(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("expected map keys to be strings; got %v", reflect.TypeOf(key.Interface()))
|
||||||
|
}
|
||||||
|
value := rv.MapIndex(key)
|
||||||
|
mv, err := unmarshalOutput(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
obj[k] = mv
|
||||||
|
}
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
88
sdk/go/pulumi/rpc_test.go
Normal file
88
sdk/go/pulumi/rpc_test.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright 2016-2018, 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.
|
||||||
|
|
||||||
|
package pulumi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/pulumi/pulumi/sdk/go/pulumi/asset"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMarshalRoundtrip ensures that marshaling a complex structure to and from its on-the-wire gRPC format succeeds.
|
||||||
|
func TestMarshalRoundtrip(t *testing.T) {
|
||||||
|
// Create interesting inputs.
|
||||||
|
out, resolve, _ := NewOutput(nil)
|
||||||
|
resolve("outputty", true)
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"s": "a string",
|
||||||
|
"a": true,
|
||||||
|
"b": 42,
|
||||||
|
"cStringAsset": asset.NewStringAsset("put a lime in the coconut"),
|
||||||
|
"cFileAsset": asset.NewFileAsset("foo.txt"),
|
||||||
|
"cRemoteAsset": asset.NewRemoteAsset("https://pulumi.com/fake/asset.txt"),
|
||||||
|
"dAssetArchive": asset.NewAssetArchive(map[string]interface{}{
|
||||||
|
"subAsset": asset.NewFileAsset("bar.txt"),
|
||||||
|
"subArchive": asset.NewFileArchive("bar.zip"),
|
||||||
|
}),
|
||||||
|
"dFileArchive": asset.NewFileArchive("foo.zip"),
|
||||||
|
"dRemoteArchive": asset.NewRemoteArchive("https://pulumi.com/fake/archive.zip"),
|
||||||
|
"e": out,
|
||||||
|
"fArray": []interface{}{0, 1.3, "x", false},
|
||||||
|
"fMap": map[string]interface{}{
|
||||||
|
"x": "y",
|
||||||
|
"y": 999.9,
|
||||||
|
"z": false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal those inputs.
|
||||||
|
_, m, deps, err := marshalInputs(input)
|
||||||
|
if !assert.Nil(t, err) {
|
||||||
|
assert.Equal(t, 0, len(deps))
|
||||||
|
|
||||||
|
// Now just unmarshal and ensure the resulting map matches.
|
||||||
|
res, err := unmarshalOutputs(m)
|
||||||
|
if !assert.Nil(t, err) {
|
||||||
|
if !assert.NotNil(t, res) {
|
||||||
|
assert.Equal(t, "a string", res["s"])
|
||||||
|
assert.Equal(t, true, res["a"])
|
||||||
|
assert.Equal(t, 42, res["b"])
|
||||||
|
assert.Equal(t, "put a lime in the coconut", res["cStringAsset"].(asset.Asset).Text())
|
||||||
|
assert.Equal(t, "foo.txt", res["cFileAsset"].(asset.Asset).Path())
|
||||||
|
assert.Equal(t, "https://pulumi.com/fake/asset.txt", res["cRemoteAsset"].(asset.Asset).URI())
|
||||||
|
ar := res["dAssetArchive"].(asset.Archive).Assets()
|
||||||
|
assert.Equal(t, 2, len(ar))
|
||||||
|
assert.Equal(t, "bar.txt", ar["subAsset"].(asset.Asset).Path())
|
||||||
|
assert.Equal(t, "bar.zip", ar["subrchive"].(asset.Archive).Path())
|
||||||
|
assert.Equal(t, "foo.zip", res["dFileArchive"].(asset.Archive).Path())
|
||||||
|
assert.Equal(t, "https://pulumi.com/fake/archive.zip", res["dRemoteArchive"].(asset.Archive).URI())
|
||||||
|
assert.Equal(t, "outputty", res["e"])
|
||||||
|
aa := res["fArray"].([]interface{})
|
||||||
|
assert.Equal(t, 4, len(aa))
|
||||||
|
assert.Equal(t, 0, aa[0])
|
||||||
|
assert.Equal(t, 1.3, aa[1])
|
||||||
|
assert.Equal(t, "x", aa[2])
|
||||||
|
assert.Equal(t, false, aa[3])
|
||||||
|
am := res["fMap"].(map[string]interface{})
|
||||||
|
assert.Equal(t, 3, len(am))
|
||||||
|
assert.Equal(t, "y", am["x"])
|
||||||
|
assert.Equal(t, 999.9, am["y"])
|
||||||
|
assert.Equal(t, false, am["z"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
148
sdk/go/pulumi/run.go
Normal file
148
sdk/go/pulumi/run.go
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
// Copyright 2016-2018, 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.
|
||||||
|
|
||||||
|
package pulumi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/pulumi/pulumi/pkg/util/contract"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run executes the body of a Pulumi program, granting it access to a deployment context that it may use
|
||||||
|
// to register resources and orchestrate deployment activities. This connects back to the Pulumi engine using gRPC.
|
||||||
|
// If the program fails, the process will be terminated and the function will not return.
|
||||||
|
func Run(body RunFunc) {
|
||||||
|
if err := RunErr(body); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: program failed: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunErr executes the body of a Pulumi program, granting it access to a deployment context that it may use
|
||||||
|
// to register resources and orchestrate deployment activities. This connects back to the Pulumi engine using gRPC.
|
||||||
|
func RunErr(body RunFunc) error {
|
||||||
|
// Parse the info out of environment variables. This is a lame contract with the caller, but helps to keep
|
||||||
|
// boilerplate to a minimum in the average Pulumi Go program.
|
||||||
|
// TODO(joe): this is a fine default, but consider `...RunOpt`s to control how we get the various addresses, etc.
|
||||||
|
info := getEnvInfo()
|
||||||
|
|
||||||
|
// Validate some properties.
|
||||||
|
if info.Project == "" {
|
||||||
|
return errors.Errorf("missing project name")
|
||||||
|
} else if info.Stack == "" {
|
||||||
|
return errors.New("missing stack name")
|
||||||
|
} else if info.MonitorAddr == "" {
|
||||||
|
return errors.New("missing resource monitor RPC address")
|
||||||
|
} else if info.EngineAddr == "" {
|
||||||
|
return errors.New("missing engine RPC address")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a fresh context.
|
||||||
|
ctx, err := NewContext(context.TODO(), info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer contract.IgnoreClose(ctx)
|
||||||
|
|
||||||
|
// Create a root stack resource that we'll parent everything to.
|
||||||
|
reg, err := ctx.RegisterResource(
|
||||||
|
"pulumi:pulumi:Stack", fmt.Sprintf("%s-%s", info.Project, info.Stack), false, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx.stackR, err = reg.URN.Value()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
contract.Assertf(ctx.stackR != "", "expected root stack resource to have a non-empty URN")
|
||||||
|
|
||||||
|
// Execute the body.
|
||||||
|
var result error
|
||||||
|
if err = body(ctx); err != nil {
|
||||||
|
result = multierror.Append(result, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure all outstanding RPCs have completed before proceeding. Also, prevent any new RPCs from happening.
|
||||||
|
ctx.waitForRPCs()
|
||||||
|
|
||||||
|
// Register all the outputs to the stack object.
|
||||||
|
if err = ctx.RegisterResourceOutputs(ctx.stackR, ctx.exports); err != nil {
|
||||||
|
result = multierror.Append(result, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Propagate the error from the body, if any.
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunFunc executes the body of a Pulumi program. It may register resources using the deployment context
|
||||||
|
// supplied as an arguent and any non-nil return value is interpreted as a program error by the Pulumi runtime.
|
||||||
|
type RunFunc func(ctx *Context) error
|
||||||
|
|
||||||
|
// RunInfo contains all the metadata about a run request.
|
||||||
|
type RunInfo struct {
|
||||||
|
Project string
|
||||||
|
Stack string
|
||||||
|
Config map[string]string
|
||||||
|
Parallel int
|
||||||
|
DryRun bool
|
||||||
|
MonitorAddr string
|
||||||
|
EngineAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEnvInfo reads various program information from the process environment.
|
||||||
|
func getEnvInfo() RunInfo {
|
||||||
|
// Most of the variables are just strings, and we can read them directly. A few of them require more parsing.
|
||||||
|
parallel, _ := strconv.Atoi(os.Getenv(EnvParallel))
|
||||||
|
dryRun, _ := strconv.ParseBool(os.Getenv(EnvDryRun))
|
||||||
|
|
||||||
|
var config map[string]string
|
||||||
|
if cfg := os.Getenv(EnvConfig); cfg != "" {
|
||||||
|
_ = json.Unmarshal([]byte(cfg), &config)
|
||||||
|
}
|
||||||
|
|
||||||
|
return RunInfo{
|
||||||
|
Project: os.Getenv(EnvProject),
|
||||||
|
Stack: os.Getenv(EnvStack),
|
||||||
|
Config: config,
|
||||||
|
Parallel: parallel,
|
||||||
|
DryRun: dryRun,
|
||||||
|
MonitorAddr: os.Getenv(EnvMonitor),
|
||||||
|
EngineAddr: os.Getenv(EnvEngine),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// EnvProject is the envvar used to read the current Pulumi project name.
|
||||||
|
EnvProject = "PULUMI_PROJECT"
|
||||||
|
// EnvStack is the envvar used to read the current Pulumi stack name.
|
||||||
|
EnvStack = "PULUMI_STACK"
|
||||||
|
// EnvConfig is the envvar used to read the current Pulumi configuration variables.
|
||||||
|
EnvConfig = "PULUMI_CONFIG"
|
||||||
|
// EnvParallel is the envvar used to read the current Pulumi degree of parallelism.
|
||||||
|
EnvParallel = "PULUMI_PARALLEL"
|
||||||
|
// EnvDryRun is the envvar used to read the current Pulumi dry-run setting.
|
||||||
|
EnvDryRun = "PULUMI_DRY_RUN"
|
||||||
|
// EnvMonitor is the envvar used to read the current Pulumi monitor RPC address.
|
||||||
|
EnvMonitor = "PULUMI_MONITOR"
|
||||||
|
// EnvEngine is the envvar used to read the current Pulumi engine RPC address.
|
||||||
|
EnvEngine = "PULUMI_ENGINE"
|
||||||
|
)
|
3
tests/integration/config_basic/go/Pulumi.yaml
Normal file
3
tests/integration/config_basic/go/Pulumi.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
name: config_basic_go
|
||||||
|
runtime: go
|
||||||
|
description: A simple Go program that uses configuration.
|
30
tests/integration/config_basic/go/main.go
Normal file
30
tests/integration/config_basic/go/main.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/pulumi/pulumi/sdk/go/pulumi"
|
||||||
|
"github.com/pulumi/pulumi/sdk/go/pulumi/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
pulumi.Run(func(ctx *pulumi.Context) error {
|
||||||
|
// Just test that basic config works.
|
||||||
|
cfg := config.New(ctx, "config_basic_go")
|
||||||
|
|
||||||
|
// This value is plaintext and doesn't require encryption.
|
||||||
|
value := cfg.Require("aConfigValue")
|
||||||
|
if value != "this value is a value" {
|
||||||
|
return fmt.Errorf("aConfigValue not the expected value; got %s", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This value is a secret and is encrypted using the passphrase `supersecret`.
|
||||||
|
secret := cfg.Require("bEncryptedSecret")
|
||||||
|
if secret != "this super secret is encrypted" {
|
||||||
|
return fmt.Errorf("bEncryptedSecret not the expected value; got %s", secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
3
tests/integration/empty/go/Pulumi.yaml
Normal file
3
tests/integration/empty/go/Pulumi.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
name: emptygo
|
||||||
|
description: An empty Go Pulumi program.
|
||||||
|
runtime: go
|
13
tests/integration/empty/go/main.go
Normal file
13
tests/integration/empty/go/main.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pulumi/pulumi/sdk/go/pulumi"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
pulumi.Run(func(ctx *pulumi.Context) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
|
@ -35,6 +35,14 @@ func TestEmptyPython(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestEmptyGo simply tests that we can run an empty Go project.
|
||||||
|
func TestEmptyGo(t *testing.T) {
|
||||||
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
||||||
|
Dir: filepath.Join("empty", "go"),
|
||||||
|
Quick: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// TestProjectMain tests out the ability to override the main entrypoint.
|
// TestProjectMain tests out the ability to override the main entrypoint.
|
||||||
func TestProjectMain(t *testing.T) {
|
func TestProjectMain(t *testing.T) {
|
||||||
var test integration.ProgramTestOptions
|
var test integration.ProgramTestOptions
|
||||||
|
@ -356,3 +364,17 @@ func TestConfigBasicPython(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests basic configuration from the perspective of a Pulumi Go program.
|
||||||
|
func TestConfigBasicGo(t *testing.T) {
|
||||||
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
||||||
|
Dir: filepath.Join("config_basic", "go"),
|
||||||
|
Quick: true,
|
||||||
|
Config: map[string]string{
|
||||||
|
"aConfigValue": "this value is a value",
|
||||||
|
},
|
||||||
|
Secrets: map[string]string{
|
||||||
|
"bEncryptedSecret": "this super secret is encrypted",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue