Support defining remote components in Go (#6403)
This commit is contained in:
parent
507082c003
commit
780a0c8a3d
|
@ -56,4 +56,7 @@
|
|||
[#6771](https://github.com/pulumi/pulumi/pull/6771)
|
||||
[#6781](https://github.com/pulumi/pulumi/pull/6781)
|
||||
|
||||
- [sdk/go] Support defining remote components in Go.
|
||||
[#6403](https://github.com/pulumi/pulumi/pull/6403)
|
||||
|
||||
### Bug Fixes
|
||||
|
|
1
Makefile
1
Makefile
|
@ -54,6 +54,7 @@ test_fast:: build
|
|||
|
||||
test_build:: $(SUB_PROJECTS:%=%_install)
|
||||
cd tests/integration/construct_component/testcomponent && yarn install && yarn link @pulumi/pulumi && yarn run tsc
|
||||
cd tests/integration/construct_component/testcomponent-go && go build -o pulumi-resource-testcomponent
|
||||
cd tests/integration/construct_component_slow/testcomponent && yarn install && yarn link @pulumi/pulumi && yarn run tsc
|
||||
cd tests/integration/construct_component_plain/testcomponent && yarn install && yarn link @pulumi/pulumi && yarn run tsc
|
||||
|
||||
|
|
|
@ -292,6 +292,7 @@
|
|||
|
||||
<Target Name="TestBuild">
|
||||
<Exec Command="yarn run tsc" WorkingDirectory="$(TestsDirectory)\integration\construct_component\testcomponent" />
|
||||
<Exec Command="go build -o pulumi-resource-testcomponent.exe" WorkingDirectory="$(TestsDirectory)\integration\construct_component\testcomponent-go" />
|
||||
<Exec Command="yarn run tsc" WorkingDirectory="$(TestsDirectory)\integration\construct_component_slow\testcomponent" />
|
||||
<Exec Command="yarn run tsc" WorkingDirectory="$(TestsDirectory)\integration\construct_component_plain\testcomponent" />
|
||||
|
||||
|
|
162
pkg/resource/provider/component_provider.go
Normal file
162
pkg/resource/provider/component_provider.go
Normal file
|
@ -0,0 +1,162 @@
|
|||
// Copyright 2016-2021, 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 provider
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/provider"
|
||||
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
|
||||
|
||||
pbempty "github.com/golang/protobuf/ptypes/empty"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type componentProvider struct {
|
||||
host *HostClient
|
||||
name string
|
||||
version string
|
||||
schema []byte
|
||||
construct provider.ConstructFunc
|
||||
}
|
||||
|
||||
// ComponentMain is an entrypoint for a resource provider plugin that implements `Construct` for component resources.
|
||||
// Using it isn't required but can cut down significantly on the amount of boilerplate necessary to fire up a new
|
||||
// resource provider for components.
|
||||
func ComponentMain(name, version string, schema []byte, construct provider.ConstructFunc) error {
|
||||
return Main(name, func(host *HostClient) (pulumirpc.ResourceProviderServer, error) {
|
||||
return &componentProvider{
|
||||
host: host,
|
||||
name: name,
|
||||
version: version,
|
||||
schema: schema,
|
||||
construct: construct,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetPluginInfo returns generic information about this plugin, like its version.
|
||||
func (p *componentProvider) GetPluginInfo(context.Context, *pbempty.Empty) (*pulumirpc.PluginInfo, error) {
|
||||
return &pulumirpc.PluginInfo{
|
||||
Version: p.version,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetSchema returns the JSON-encoded schema for this provider's package.
|
||||
func (p *componentProvider) GetSchema(ctx context.Context,
|
||||
req *pulumirpc.GetSchemaRequest) (*pulumirpc.GetSchemaResponse, error) {
|
||||
if v := req.GetVersion(); v != 0 {
|
||||
return nil, errors.Errorf("unsupported schema version %d", v)
|
||||
}
|
||||
schema := string(p.schema)
|
||||
if schema == "" {
|
||||
schema = "{}"
|
||||
}
|
||||
return &pulumirpc.GetSchemaResponse{Schema: schema}, nil
|
||||
}
|
||||
|
||||
// Configure configures the resource provider with "globals" that control its behavior.
|
||||
func (p *componentProvider) Configure(ctx context.Context,
|
||||
req *pulumirpc.ConfigureRequest) (*pulumirpc.ConfigureResponse, error) {
|
||||
return &pulumirpc.ConfigureResponse{
|
||||
AcceptSecrets: true,
|
||||
SupportsPreview: true,
|
||||
AcceptResources: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Construct creates a new instance of the provided component resource and returns its state.
|
||||
func (p *componentProvider) Construct(ctx context.Context,
|
||||
req *pulumirpc.ConstructRequest) (*pulumirpc.ConstructResponse, error) {
|
||||
return provider.Construct(ctx, req, p.host.conn, p.construct)
|
||||
}
|
||||
|
||||
// CheckConfig validates the configuration for this provider.
|
||||
func (p *componentProvider) CheckConfig(ctx context.Context,
|
||||
req *pulumirpc.CheckRequest) (*pulumirpc.CheckResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "CheckConfig is not yet implemented")
|
||||
}
|
||||
|
||||
// DiffConfig diffs the configuration for this provider.
|
||||
func (p *componentProvider) DiffConfig(ctx context.Context,
|
||||
req *pulumirpc.DiffRequest) (*pulumirpc.DiffResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "DiffConfig is not yet implemented")
|
||||
}
|
||||
|
||||
// StreamInvoke dynamically executes a built-in function in the provider. The result is streamed
|
||||
// back as a series of messages.
|
||||
func (p *componentProvider) StreamInvoke(req *pulumirpc.InvokeRequest,
|
||||
server pulumirpc.ResourceProvider_StreamInvokeServer) error {
|
||||
return status.Error(codes.Unimplemented, "StreamInvoke is not yet implemented")
|
||||
}
|
||||
|
||||
// Check validates that the given property bag is valid for a resource of the given type and returns
|
||||
// the inputs that should be passed to successive calls to Diff, Create, or Update for this
|
||||
// resource. As a rule, the provider inputs returned by a call to Check should preserve the original
|
||||
// representation of the properties as present in the program inputs. Though this rule is not
|
||||
// required for correctness, violations thereof can negatively impact the end-user experience, as
|
||||
// the provider inputs are using for detecting and rendering diffs.
|
||||
func (p *componentProvider) Check(ctx context.Context, req *pulumirpc.CheckRequest) (*pulumirpc.CheckResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "Check is not yet implemented")
|
||||
}
|
||||
|
||||
// Diff checks what impacts a hypothetical update will have on the resource's properties.
|
||||
func (p *componentProvider) Diff(ctx context.Context, req *pulumirpc.DiffRequest) (*pulumirpc.DiffResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "Diff is not yet implemented")
|
||||
}
|
||||
|
||||
// Create allocates a new instance of the provided resource and returns its unique ID afterwards.
|
||||
// (The input ID must be blank.) If this call fails, the resource must not have been created (i.e.,
|
||||
// it is "transactional").
|
||||
func (p *componentProvider) Create(ctx context.Context,
|
||||
req *pulumirpc.CreateRequest) (*pulumirpc.CreateResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "Create is not yet implemented")
|
||||
}
|
||||
|
||||
// Read the current live state associated with a resource. Enough state must be include in the
|
||||
// inputs to uniquely identify the resource; this is typically just the resource ID, but may also
|
||||
// include some properties.
|
||||
func (p *componentProvider) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*pulumirpc.ReadResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "Read is not yet implemented")
|
||||
}
|
||||
|
||||
// Update updates an existing resource with new values.
|
||||
func (p *componentProvider) Update(ctx context.Context,
|
||||
req *pulumirpc.UpdateRequest) (*pulumirpc.UpdateResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "Update is not yet implemented")
|
||||
}
|
||||
|
||||
// Delete tears down an existing resource with the given ID. If it fails, the resource is assumed
|
||||
// to still exist.
|
||||
func (p *componentProvider) Delete(ctx context.Context, req *pulumirpc.DeleteRequest) (*pbempty.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "Delete is not yet implemented")
|
||||
}
|
||||
|
||||
// Invoke dynamically executes a built-in function in the provider.
|
||||
func (p *componentProvider) Invoke(ctx context.Context,
|
||||
req *pulumirpc.InvokeRequest) (*pulumirpc.InvokeResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "Invoke is not yet implemented")
|
||||
}
|
||||
|
||||
// Cancel signals the provider to gracefully shut down and abort any ongoing resource operations.
|
||||
// Operations aborted in this way will return an error (e.g., `Update` and `Create` will either a
|
||||
// creation error or an initialization error). Since Cancel is advisory and non-blocking, it is up
|
||||
// to the host to decide how long to wait after Cancel is called before (e.g.)
|
||||
// hard-closing any gRPC connection.
|
||||
func (p *componentProvider) Cancel(context.Context, *pbempty.Empty) (*pbempty.Empty, error) {
|
||||
return &pbempty.Empty{}, nil
|
||||
}
|
|
@ -54,6 +54,11 @@ func (host *HostClient) Close() error {
|
|||
return host.conn.Close()
|
||||
}
|
||||
|
||||
// EngineConn provides the engine gRPC client connection.
|
||||
func (host *HostClient) EngineConn() *grpc.ClientConn {
|
||||
return host.conn
|
||||
}
|
||||
|
||||
func (host *HostClient) log(
|
||||
context context.Context, sev diag.Severity, urn resource.URN, msg string, ephemeral bool,
|
||||
) error {
|
||||
|
|
|
@ -79,7 +79,10 @@ func NewContext(ctx context.Context, info RunInfo) (*Context, error) {
|
|||
|
||||
var engineConn *grpc.ClientConn
|
||||
var engine pulumirpc.EngineClient
|
||||
if addr := info.EngineAddr; addr != "" {
|
||||
if info.engineConn != nil {
|
||||
engineConn = info.engineConn
|
||||
engine = pulumirpc.NewEngineClient(engineConn)
|
||||
} else if addr := info.EngineAddr; addr != "" {
|
||||
conn, err := grpc.Dial(
|
||||
info.EngineAddr,
|
||||
grpc.WithInsecure(),
|
||||
|
|
271
sdk/go/pulumi/provider.go
Normal file
271
sdk/go/pulumi/provider.go
Normal file
|
@ -0,0 +1,271 @@
|
|||
// Copyright 2016-2021, 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 (
|
||||
"context"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
|
||||
|
||||
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type constructFunc func(ctx *Context, typ, name string, inputs map[string]interface{},
|
||||
options ResourceOption) (URNInput, Input, error)
|
||||
|
||||
// construct adapts the gRPC ConstructRequest/ConstructResponse to/from the Pulumi Go SDK programming model.
|
||||
func construct(ctx context.Context, req *pulumirpc.ConstructRequest, engineConn *grpc.ClientConn,
|
||||
constructF constructFunc) (*pulumirpc.ConstructResponse, error) {
|
||||
|
||||
// Configure the RunInfo.
|
||||
runInfo := RunInfo{
|
||||
Project: req.GetProject(),
|
||||
Stack: req.GetStack(),
|
||||
Config: req.GetConfig(),
|
||||
Parallel: int(req.GetParallel()),
|
||||
DryRun: req.GetDryRun(),
|
||||
MonitorAddr: req.GetMonitorEndpoint(),
|
||||
engineConn: engineConn,
|
||||
}
|
||||
pulumiCtx, err := NewContext(ctx, runInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "constructing run context")
|
||||
}
|
||||
|
||||
// Deserialize the inputs and apply appropriate dependencies.
|
||||
inputDependencies := req.GetInputDependencies()
|
||||
deserializedInputs, err := plugin.UnmarshalProperties(
|
||||
req.GetInputs(),
|
||||
plugin.MarshalOptions{KeepSecrets: true, KeepResources: true, KeepUnknowns: req.GetDryRun()},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unmarshaling inputs")
|
||||
}
|
||||
inputs := make(map[string]interface{}, len(deserializedInputs))
|
||||
for key, input := range deserializedInputs {
|
||||
k := string(key)
|
||||
var deps []Resource
|
||||
if inputDeps, ok := inputDependencies[k]; ok {
|
||||
deps = make([]Resource, len(inputDeps.GetUrns()))
|
||||
for i, depURN := range inputDeps.GetUrns() {
|
||||
deps[i] = newDependencyResource(URN(depURN))
|
||||
}
|
||||
}
|
||||
|
||||
val, secret, err := unmarshalPropertyValue(pulumiCtx, input)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unmarshaling input %s", k)
|
||||
}
|
||||
|
||||
inputs[k] = &constructInput{
|
||||
value: val,
|
||||
secret: secret,
|
||||
deps: deps,
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild the resource options.
|
||||
aliases := make([]Alias, len(req.GetAliases()))
|
||||
for i, urn := range req.GetAliases() {
|
||||
aliases[i] = Alias{URN: URN(urn)}
|
||||
}
|
||||
dependencies := make([]Resource, len(req.GetDependencies()))
|
||||
for i, urn := range req.GetDependencies() {
|
||||
dependencies[i] = newDependencyResource(URN(urn))
|
||||
}
|
||||
providers := make(map[string]ProviderResource, len(req.GetProviders()))
|
||||
for pkg, ref := range req.GetProviders() {
|
||||
// Parse the URN and ID out of the provider reference.
|
||||
lastSep := strings.LastIndex(ref, "::")
|
||||
if lastSep == -1 {
|
||||
return nil, errors.Errorf("expected '::' in provider reference %s", ref)
|
||||
}
|
||||
urn := ref[0:lastSep]
|
||||
id := ref[lastSep+2:]
|
||||
providers[pkg] = newDependencyProviderResource(URN(urn), ID(id))
|
||||
}
|
||||
var parent Resource
|
||||
if req.GetParent() != "" {
|
||||
parent = newDependencyResource(URN(req.GetParent()))
|
||||
}
|
||||
opts := resourceOption(func(ro *resourceOptions) {
|
||||
ro.Aliases = aliases
|
||||
ro.DependsOn = dependencies
|
||||
ro.Protect = req.GetProtect()
|
||||
ro.Providers = providers
|
||||
ro.Parent = parent
|
||||
})
|
||||
|
||||
urn, state, err := constructF(pulumiCtx, req.GetType(), req.GetName(), inputs, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ensure all outstanding RPCs have completed before proceeding. Also, prevent any new RPCs from happening.
|
||||
pulumiCtx.waitForRPCs()
|
||||
if pulumiCtx.rpcError != nil {
|
||||
return nil, errors.Wrap(pulumiCtx.rpcError, "waiting for RPCs")
|
||||
}
|
||||
|
||||
rpcURN, _, _, err := urn.ToURNOutput().awaitURN(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Serialize all state properties, first by awaiting them, and then marshaling them to the requisite gRPC values.
|
||||
resolvedProps, propertyDeps, _, err := marshalInputs(state)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "marshaling properties")
|
||||
}
|
||||
|
||||
// Marshal all properties for the RPC call.
|
||||
keepUnknowns := req.GetDryRun()
|
||||
rpcProps, err := plugin.MarshalProperties(
|
||||
resolvedProps,
|
||||
plugin.MarshalOptions{KeepSecrets: true, KeepUnknowns: keepUnknowns, KeepResources: pulumiCtx.keepResources})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "marshaling properties")
|
||||
}
|
||||
|
||||
// Convert the property dependencies map for RPC and remove duplicates.
|
||||
rpcPropertyDeps := make(map[string]*pulumirpc.ConstructResponse_PropertyDependencies)
|
||||
for k, deps := range propertyDeps {
|
||||
sort.Slice(deps, func(i, j int) bool { return deps[i] < deps[j] })
|
||||
|
||||
urns := make([]string, 0, len(deps))
|
||||
for i, d := range deps {
|
||||
if i > 0 && urns[i-1] == string(d) {
|
||||
continue
|
||||
}
|
||||
urns = append(urns, string(d))
|
||||
}
|
||||
|
||||
rpcPropertyDeps[k] = &pulumirpc.ConstructResponse_PropertyDependencies{
|
||||
Urns: urns,
|
||||
}
|
||||
}
|
||||
|
||||
return &pulumirpc.ConstructResponse{
|
||||
Urn: string(rpcURN),
|
||||
State: rpcProps,
|
||||
StateDependencies: rpcPropertyDeps,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type constructInput struct {
|
||||
value interface{}
|
||||
secret bool
|
||||
deps []Resource
|
||||
}
|
||||
|
||||
// constructInputsMap returns the inputs as a Map.
|
||||
func constructInputsMap(inputs map[string]interface{}) Map {
|
||||
result := make(Map, len(inputs))
|
||||
for k, v := range inputs {
|
||||
val := v.(*constructInput)
|
||||
output := newOutput(anyOutputType, val.deps...)
|
||||
output.getState().resolve(val.value, true /*known*/, val.secret, nil)
|
||||
result[k] = output
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// constructInputsSetArgs sets the inputs on the given args struct.
|
||||
func constructInputsSetArgs(inputs map[string]interface{}, args interface{}) error {
|
||||
if args == nil {
|
||||
return errors.New("args must not be nil")
|
||||
}
|
||||
argsV := reflect.ValueOf(args)
|
||||
typ := argsV.Type()
|
||||
if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct {
|
||||
return errors.New("args must be a pointer to a struct")
|
||||
}
|
||||
argsV, typ = argsV.Elem(), typ.Elem()
|
||||
|
||||
for k, v := range inputs {
|
||||
val := v.(*constructInput)
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
fieldV := argsV.Field(i)
|
||||
if !fieldV.CanSet() {
|
||||
continue
|
||||
}
|
||||
field := typ.Field(i)
|
||||
tag, has := field.Tag.Lookup("pulumi")
|
||||
if !has || tag != k {
|
||||
continue
|
||||
}
|
||||
|
||||
if !field.Type.Implements(reflect.TypeOf((*Input)(nil)).Elem()) {
|
||||
continue
|
||||
}
|
||||
|
||||
outputType := anyOutputType
|
||||
|
||||
toOutputMethodName := "To" + strings.TrimSuffix(field.Type.Name(), "Input") + "Output"
|
||||
toOutputMethod, found := field.Type.MethodByName(toOutputMethodName)
|
||||
if found {
|
||||
mt := toOutputMethod.Type
|
||||
if mt.NumIn() != 0 || mt.NumOut() != 1 {
|
||||
continue
|
||||
}
|
||||
outputType = mt.Out(0)
|
||||
if !outputType.Implements(reflect.TypeOf((*Output)(nil)).Elem()) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
output := newOutput(outputType, val.deps...)
|
||||
output.getState().resolve(val.value, true /*known*/, val.secret, nil)
|
||||
fieldV.Set(reflect.ValueOf(output))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newConstructResult converts a resource into its associated URN and state.
|
||||
func newConstructResult(resource ComponentResource) (URNInput, Input, error) {
|
||||
if resource == nil {
|
||||
return nil, nil, errors.New("resource must not be nil")
|
||||
}
|
||||
|
||||
resourceV := reflect.ValueOf(resource)
|
||||
typ := resourceV.Type()
|
||||
if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct {
|
||||
return nil, nil, errors.New("resource must be a pointer to a struct")
|
||||
}
|
||||
resourceV, typ = resourceV.Elem(), typ.Elem()
|
||||
|
||||
state := make(Map)
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
fieldV := resourceV.Field(i)
|
||||
field := typ.Field(i)
|
||||
tag, has := field.Tag.Lookup("pulumi")
|
||||
if !has {
|
||||
continue
|
||||
}
|
||||
val := fieldV.Interface()
|
||||
if v, ok := val.(Input); ok {
|
||||
state[tag] = v
|
||||
}
|
||||
}
|
||||
|
||||
return resource.URN(), state, nil
|
||||
}
|
17
sdk/go/pulumi/provider/empty.s
Normal file
17
sdk/go/pulumi/provider/empty.s
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2016-2021, 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.
|
||||
|
||||
// This empty .s file enables the use of go:linkname to make certain unexported functions from
|
||||
// the pulumi package available in the provider package so the public API can be exposed from the
|
||||
// provider package.
|
92
sdk/go/pulumi/provider/provider.go
Normal file
92
sdk/go/pulumi/provider/provider.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
// Copyright 2016-2021, 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 provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// This file relies on implementations in ../provider_linked.go that are made available in this package via
|
||||
// go:linkname.
|
||||
|
||||
type ConstructFunc func(ctx *pulumi.Context, typ, name string, inputs ConstructInputs,
|
||||
options pulumi.ResourceOption) (*ConstructResult, error)
|
||||
|
||||
// Construct adapts the gRPC ConstructRequest/ConstructResponse to/from the Pulumi Go SDK programming model.
|
||||
func Construct(ctx context.Context, req *pulumirpc.ConstructRequest, engineConn *grpc.ClientConn,
|
||||
construct ConstructFunc) (*pulumirpc.ConstructResponse, error) {
|
||||
return linkedConstruct(ctx, req, engineConn, func(pulumiCtx *pulumi.Context, typ, name string,
|
||||
inputs map[string]interface{}, options pulumi.ResourceOption) (pulumi.URNInput, pulumi.Input, error) {
|
||||
result, err := construct(pulumiCtx, typ, name, ConstructInputs{inputs: inputs}, options)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return result.URN, result.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
// ConstructInputs represents the inputs associated with a call to Construct.
|
||||
type ConstructInputs struct {
|
||||
inputs map[string]interface{}
|
||||
}
|
||||
|
||||
// Map returns the inputs as a Map.
|
||||
func (inputs ConstructInputs) Map() pulumi.Map {
|
||||
return linkedConstructInputsMap(inputs.inputs)
|
||||
}
|
||||
|
||||
// SetArgs sets the inputs on the given args struct.
|
||||
func (inputs ConstructInputs) SetArgs(args interface{}) error {
|
||||
return linkedConstructInputsSetArgs(inputs.inputs, args)
|
||||
}
|
||||
|
||||
// ConstructResult is the result of a call to Construct.
|
||||
type ConstructResult struct {
|
||||
URN pulumi.URNInput
|
||||
State pulumi.Input
|
||||
}
|
||||
|
||||
// NewConstructResult creates a ConstructResult from the resource.
|
||||
func NewConstructResult(resource pulumi.ComponentResource) (*ConstructResult, error) {
|
||||
urn, state, err := linkedNewConstructResult(resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ConstructResult{
|
||||
URN: urn,
|
||||
State: state,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type constructFunc func(ctx *pulumi.Context, typ, name string, inputs map[string]interface{},
|
||||
options pulumi.ResourceOption) (pulumi.URNInput, pulumi.Input, error)
|
||||
|
||||
// linkedConstruct is made available here from ../provider_linked.go via go:linkname.
|
||||
func linkedConstruct(ctx context.Context, req *pulumirpc.ConstructRequest, engineConn *grpc.ClientConn,
|
||||
constructF constructFunc) (*pulumirpc.ConstructResponse, error)
|
||||
|
||||
// linkedConstructInputsMap is made available here from ../provider_linked.go via go:linkname.
|
||||
func linkedConstructInputsMap(inputs map[string]interface{}) pulumi.Map
|
||||
|
||||
// linkedConstructInputsSetArgs is made available here from ../provider_linked.go via go:linkname.
|
||||
func linkedConstructInputsSetArgs(inputs map[string]interface{}, args interface{}) error
|
||||
|
||||
// linkedNewConstructResult is made available here from ../provider_linked.go via go:linkname.
|
||||
func linkedNewConstructResult(resource pulumi.ComponentResource) (pulumi.URNInput, pulumi.Input, error)
|
50
sdk/go/pulumi/provider_linked.go
Normal file
50
sdk/go/pulumi/provider_linked.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2016-2021, 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.
|
||||
|
||||
//nolint:deadcode,lll
|
||||
package pulumi
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "unsafe" // unsafe is needed to use go:linkname
|
||||
|
||||
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// We want the public provider-related APIs to be exported from the provider package, but need to make use of unexported
|
||||
// functionality in this package for their implementations. To achieve this, go:linkname is used to make the following
|
||||
// functions available in the provider package.
|
||||
|
||||
//go:linkname linkedConstruct github.com/pulumi/pulumi/sdk/v3/go/pulumi/provider.linkedConstruct
|
||||
func linkedConstruct(ctx context.Context, req *pulumirpc.ConstructRequest, engineConn *grpc.ClientConn,
|
||||
constructF constructFunc) (*pulumirpc.ConstructResponse, error) {
|
||||
return construct(ctx, req, engineConn, constructF)
|
||||
}
|
||||
|
||||
//go:linkname linkedConstructInputsMap github.com/pulumi/pulumi/sdk/v3/go/pulumi/provider.linkedConstructInputsMap
|
||||
func linkedConstructInputsMap(inputs map[string]interface{}) Map {
|
||||
return constructInputsMap(inputs)
|
||||
}
|
||||
|
||||
//go:linkname linkedConstructInputsSetArgs github.com/pulumi/pulumi/sdk/v3/go/pulumi/provider.linkedConstructInputsSetArgs
|
||||
func linkedConstructInputsSetArgs(inputs map[string]interface{}, args interface{}) error {
|
||||
return constructInputsSetArgs(inputs, args)
|
||||
}
|
||||
|
||||
//go:linkname linkedNewConstructResult github.com/pulumi/pulumi/sdk/v3/go/pulumi/provider.linkedNewConstructResult
|
||||
func linkedNewConstructResult(resource ComponentResource) (URNInput, Input, error) {
|
||||
return newConstructResult(resource)
|
||||
}
|
|
@ -25,6 +25,8 @@ import (
|
|||
multierror "github.com/hashicorp/go-multierror"
|
||||
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var ErrPlugins = errors.New("pulumi: plugins requested")
|
||||
|
@ -132,6 +134,7 @@ type RunInfo struct {
|
|||
EngineAddr string
|
||||
Mocks MockResourceMonitor
|
||||
getPlugins bool
|
||||
engineConn *grpc.ClientConn // Pre-existing engine connection. If set this is used over EngineAddr.
|
||||
}
|
||||
|
||||
// getEnvInfo reads various program information from the process environment.
|
||||
|
|
|
@ -10,6 +10,7 @@ replace (
|
|||
|
||||
require (
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/golang/protobuf v1.4.3 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pulumi/pulumi-random/sdk/v3 v3.1.2-0.20210323111705-623d65f88c52
|
||||
|
|
2
tests/integration/construct_component/testcomponent-go/.gitignore
vendored
Normal file
2
tests/integration/construct_component/testcomponent-go/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
pulumi-resource-testcomponent
|
||||
pulumi-resource-testcomponent.exe
|
230
tests/integration/construct_component/testcomponent-go/main.go
Normal file
230
tests/integration/construct_component/testcomponent-go/main.go
Normal file
|
@ -0,0 +1,230 @@
|
|||
// Copyright 2016-2021, Pulumi Corporation. All rights reserved.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/v3/resource/provider"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
pulumiprovider "github.com/pulumi/pulumi/sdk/v3/go/pulumi/provider"
|
||||
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
|
||||
|
||||
pbempty "github.com/golang/protobuf/ptypes/empty"
|
||||
)
|
||||
|
||||
type Resource struct {
|
||||
pulumi.CustomResourceState
|
||||
}
|
||||
|
||||
type resourceArgs struct {
|
||||
Echo interface{} `pulumi:"echo"`
|
||||
}
|
||||
|
||||
type ResourceArgs struct {
|
||||
Echo pulumi.Input
|
||||
}
|
||||
|
||||
func (ResourceArgs) ElementType() reflect.Type {
|
||||
return reflect.TypeOf((*resourceArgs)(nil)).Elem()
|
||||
}
|
||||
|
||||
func NewResource(ctx *pulumi.Context, name string, echo pulumi.Input,
|
||||
opts ...pulumi.ResourceOption) (*Resource, error) {
|
||||
args := &ResourceArgs{Echo: echo}
|
||||
var resource Resource
|
||||
err := ctx.RegisterResource("testcomponent:index:Resource", name, args, &resource, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resource, nil
|
||||
}
|
||||
|
||||
type Component struct {
|
||||
pulumi.ResourceState
|
||||
|
||||
Echo pulumi.Input `pulumi:"echo"`
|
||||
ChildID pulumi.IDOutput `pulumi:"childId"`
|
||||
}
|
||||
|
||||
type ComponentArgs struct {
|
||||
Echo pulumi.Input `pulumi:"echo"`
|
||||
}
|
||||
|
||||
func NewComponent(ctx *pulumi.Context, name string, args *ComponentArgs,
|
||||
opts ...pulumi.ResourceOption) (*Component, error) {
|
||||
if args == nil {
|
||||
return nil, errors.New("args is required")
|
||||
}
|
||||
|
||||
component := &Component{}
|
||||
err := ctx.RegisterComponentResource("testcomponent:index:Component", name, component, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := NewResource(ctx, fmt.Sprintf("child-%s", name), args.Echo, pulumi.Parent(component))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
component.Echo = args.Echo //pulumi.ToOutput(args.Echo)
|
||||
component.ChildID = res.ID()
|
||||
|
||||
if err := ctx.RegisterResourceOutputs(component, pulumi.Map{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return component, nil
|
||||
}
|
||||
|
||||
const providerName = "testcomponent"
|
||||
const version = "0.0.1"
|
||||
|
||||
var currentID int
|
||||
|
||||
func main() {
|
||||
err := provider.Main(providerName, func(host *provider.HostClient) (pulumirpc.ResourceProviderServer, error) {
|
||||
return makeProvider(host, providerName, version)
|
||||
})
|
||||
if err != nil {
|
||||
cmdutil.ExitError(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
type testcomponentProvider struct {
|
||||
host *provider.HostClient
|
||||
name string
|
||||
version string
|
||||
}
|
||||
|
||||
func makeProvider(host *provider.HostClient, name, version string) (pulumirpc.ResourceProviderServer, error) {
|
||||
return &testcomponentProvider{
|
||||
host: host,
|
||||
name: name,
|
||||
version: version,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *testcomponentProvider) Create(ctx context.Context,
|
||||
req *pulumirpc.CreateRequest) (*pulumirpc.CreateResponse, error) {
|
||||
urn := resource.URN(req.GetUrn())
|
||||
typ := urn.Type()
|
||||
if typ != "testcomponent:index:Resource" {
|
||||
return nil, errors.Errorf("Unknown resource type '%s'", typ)
|
||||
}
|
||||
|
||||
id := currentID
|
||||
currentID++
|
||||
|
||||
return &pulumirpc.CreateResponse{
|
||||
Id: fmt.Sprintf("%v", id),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *testcomponentProvider) Construct(ctx context.Context,
|
||||
req *pulumirpc.ConstructRequest) (*pulumirpc.ConstructResponse, error) {
|
||||
return pulumiprovider.Construct(ctx, req, p.host.EngineConn(), func(ctx *pulumi.Context, typ, name string,
|
||||
inputs pulumiprovider.ConstructInputs, options pulumi.ResourceOption) (*pulumiprovider.ConstructResult, error) {
|
||||
|
||||
if typ != "testcomponent:index:Component" {
|
||||
return nil, errors.Errorf("unknown resource type %s", typ)
|
||||
}
|
||||
|
||||
args := &ComponentArgs{}
|
||||
if err := inputs.SetArgs(args); err != nil {
|
||||
return nil, errors.Wrap(err, "setting args")
|
||||
}
|
||||
|
||||
component, err := NewComponent(ctx, name, args, options)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating component")
|
||||
}
|
||||
|
||||
//return pulumiprovider.NewConstructResult(component)
|
||||
return &pulumiprovider.ConstructResult{
|
||||
URN: component.URN(),
|
||||
State: pulumi.Map{
|
||||
"echo": component.Echo,
|
||||
"childId": component.ChildID,
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (p *testcomponentProvider) CheckConfig(ctx context.Context,
|
||||
req *pulumirpc.CheckRequest) (*pulumirpc.CheckResponse, error) {
|
||||
return &pulumirpc.CheckResponse{Inputs: req.GetNews()}, nil
|
||||
}
|
||||
|
||||
func (p *testcomponentProvider) DiffConfig(ctx context.Context,
|
||||
req *pulumirpc.DiffRequest) (*pulumirpc.DiffResponse, error) {
|
||||
return &pulumirpc.DiffResponse{}, nil
|
||||
}
|
||||
|
||||
func (p *testcomponentProvider) Configure(ctx context.Context,
|
||||
req *pulumirpc.ConfigureRequest) (*pulumirpc.ConfigureResponse, error) {
|
||||
return &pulumirpc.ConfigureResponse{
|
||||
AcceptSecrets: true,
|
||||
SupportsPreview: true,
|
||||
AcceptResources: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *testcomponentProvider) Invoke(ctx context.Context,
|
||||
req *pulumirpc.InvokeRequest) (*pulumirpc.InvokeResponse, error) {
|
||||
return nil, errors.Errorf("Unknown Invoke token '%s'", req.GetTok())
|
||||
}
|
||||
|
||||
func (p *testcomponentProvider) StreamInvoke(req *pulumirpc.InvokeRequest,
|
||||
server pulumirpc.ResourceProvider_StreamInvokeServer) error {
|
||||
return errors.Errorf("Unknown StreamInvoke token '%s'", req.GetTok())
|
||||
}
|
||||
|
||||
func (p *testcomponentProvider) Check(ctx context.Context,
|
||||
req *pulumirpc.CheckRequest) (*pulumirpc.CheckResponse, error) {
|
||||
return &pulumirpc.CheckResponse{Inputs: req.News, Failures: nil}, nil
|
||||
}
|
||||
|
||||
func (p *testcomponentProvider) Diff(ctx context.Context, req *pulumirpc.DiffRequest) (*pulumirpc.DiffResponse, error) {
|
||||
return &pulumirpc.DiffResponse{}, nil
|
||||
}
|
||||
|
||||
func (p *testcomponentProvider) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*pulumirpc.ReadResponse, error) {
|
||||
return &pulumirpc.ReadResponse{
|
||||
Id: req.GetId(),
|
||||
Properties: req.GetProperties(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *testcomponentProvider) Update(ctx context.Context,
|
||||
req *pulumirpc.UpdateRequest) (*pulumirpc.UpdateResponse, error) {
|
||||
return &pulumirpc.UpdateResponse{
|
||||
Properties: req.GetNews(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *testcomponentProvider) Delete(ctx context.Context, req *pulumirpc.DeleteRequest) (*pbempty.Empty, error) {
|
||||
return &pbempty.Empty{}, nil
|
||||
}
|
||||
|
||||
func (p *testcomponentProvider) GetPluginInfo(context.Context, *pbempty.Empty) (*pulumirpc.PluginInfo, error) {
|
||||
return &pulumirpc.PluginInfo{
|
||||
Version: p.version,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *testcomponentProvider) GetSchema(ctx context.Context,
|
||||
req *pulumirpc.GetSchemaRequest) (*pulumirpc.GetSchemaResponse, error) {
|
||||
return &pulumirpc.GetSchemaResponse{}, nil
|
||||
}
|
||||
|
||||
func (p *testcomponentProvider) Cancel(context.Context, *pbempty.Empty) (*pbempty.Empty, error) {
|
||||
return &pbempty.Empty{}, nil
|
||||
}
|
|
@ -196,25 +196,52 @@ func TestLargeResourceDotNet(t *testing.T) {
|
|||
|
||||
// Test remote component construction in .NET.
|
||||
func TestConstructDotnet(t *testing.T) {
|
||||
// TODO[pulumi/pulumi#5455]: Dynamic providers fail to load when used from multi-lang components.
|
||||
// Until we've addressed this, set PULUMI_TEST_YARN_LINK_PULUMI, which tells the integration test
|
||||
// module to run `yarn install && yarn link @pulumi/pulumi` in the .NET program's directory, allowing
|
||||
// the Node.js dynamic provider plugin to load.
|
||||
// When the underlying issue has been fixed, the use of this environment variable inside the integration
|
||||
// test module should be removed.
|
||||
const testYarnLinkPulumiEnv = "PULUMI_TEST_YARN_LINK_PULUMI=true"
|
||||
tests := []struct {
|
||||
componentDir string
|
||||
expectedResourceCount int
|
||||
env []string
|
||||
}{
|
||||
{
|
||||
componentDir: "testcomponent",
|
||||
expectedResourceCount: 9,
|
||||
// TODO[pulumi/pulumi#5455]: Dynamic providers fail to load when used from multi-lang components.
|
||||
// Until we've addressed this, set PULUMI_TEST_YARN_LINK_PULUMI, which tells the integration test
|
||||
// module to run `yarn install && yarn link @pulumi/pulumi` in the Go program's directory, allowing
|
||||
// the Node.js dynamic provider plugin to load.
|
||||
// When the underlying issue has been fixed, the use of this environment variable inside the integration
|
||||
// test module should be removed.
|
||||
env: []string{"PULUMI_TEST_YARN_LINK_PULUMI=true"},
|
||||
},
|
||||
{
|
||||
componentDir: "testcomponent-python",
|
||||
expectedResourceCount: 9,
|
||||
env: []string{pulumiRuntimeVirtualEnv(t, filepath.Join("..", ".."))},
|
||||
},
|
||||
{
|
||||
componentDir: "testcomponent-go",
|
||||
expectedResourceCount: 8, // One less because no dynamic provider.
|
||||
},
|
||||
}
|
||||
|
||||
runtimeVenv := pulumiRuntimeVirtualEnv(t, filepath.Join("..", ".."))
|
||||
for _, test := range tests {
|
||||
t.Run(test.componentDir, func(t *testing.T) {
|
||||
pathEnv := componentPathEnv(t, "construct_component", test.componentDir)
|
||||
integration.ProgramTest(t,
|
||||
optsForConstructDotnet(t, test.expectedResourceCount, append(test.env, pathEnv)...))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
opts := &integration.ProgramTestOptions{
|
||||
Env: []string{testYarnLinkPulumiEnv, runtimeVenv},
|
||||
func optsForConstructDotnet(t *testing.T, expectedResourceCount int, env ...string) *integration.ProgramTestOptions {
|
||||
return &integration.ProgramTestOptions{
|
||||
Env: env,
|
||||
Dir: filepath.Join("construct_component", "dotnet"),
|
||||
Dependencies: []string{"Pulumi"},
|
||||
Quick: true,
|
||||
NoParallel: true, // avoid contention for Dir
|
||||
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
||||
assert.NotNil(t, stackInfo.Deployment)
|
||||
if assert.Equal(t, 9, len(stackInfo.Deployment.Resources)) {
|
||||
if assert.Equal(t, expectedResourceCount, len(stackInfo.Deployment.Resources)) {
|
||||
stackRes := stackInfo.Deployment.Resources[0]
|
||||
assert.NotNil(t, stackRes)
|
||||
assert.Equal(t, resource.RootStackType, stackRes.Type)
|
||||
|
@ -239,11 +266,6 @@ func TestConstructDotnet(t *testing.T) {
|
|||
}
|
||||
},
|
||||
}
|
||||
|
||||
runProgramSubTests(t, opts, map[string]string{
|
||||
"WithNodeProvider": componentPathEnv(t, "construct_component", "testcomponent"),
|
||||
"WithPythonProvider": componentPathEnv(t, "construct_component", "testcomponent-python"),
|
||||
})
|
||||
}
|
||||
|
||||
// Test remote component construction with a child resource that takes a long time to be created, ensuring it's created.
|
||||
|
|
|
@ -121,19 +121,44 @@ func TestLargeResourceGo(t *testing.T) {
|
|||
|
||||
// Test remote component construction in Go.
|
||||
func TestConstructGo(t *testing.T) {
|
||||
tests := []struct {
|
||||
componentDir string
|
||||
expectedResourceCount int
|
||||
env []string
|
||||
}{
|
||||
{
|
||||
componentDir: "testcomponent",
|
||||
expectedResourceCount: 9,
|
||||
// TODO[pulumi/pulumi#5455]: Dynamic providers fail to load when used from multi-lang components.
|
||||
// Until we've addressed this, set PULUMI_TEST_YARN_LINK_PULUMI, which tells the integration test
|
||||
// module to run `yarn install && yarn link @pulumi/pulumi` in the Go program's directory, allowing
|
||||
// the Node.js dynamic provider plugin to load.
|
||||
// When the underlying issue has been fixed, the use of this environment variable inside the integration
|
||||
// test module should be removed.
|
||||
env: []string{"PULUMI_TEST_YARN_LINK_PULUMI=true"},
|
||||
},
|
||||
{
|
||||
componentDir: "testcomponent-python",
|
||||
expectedResourceCount: 9,
|
||||
env: []string{pulumiRuntimeVirtualEnv(t, filepath.Join("..", ".."))},
|
||||
},
|
||||
{
|
||||
componentDir: "testcomponent-go",
|
||||
expectedResourceCount: 8, // One less because no dynamic provider.
|
||||
},
|
||||
}
|
||||
|
||||
// TODO[pulumi/pulumi#5455]: Dynamic providers fail to load when used from multi-lang components.
|
||||
// Until we've addressed this, set PULUMI_TEST_YARN_LINK_PULUMI, which tells the integration test
|
||||
// module to run `yarn install && yarn link @pulumi/pulumi` in the Go program's directory, allowing
|
||||
// the Node.js dynamic provider plugin to load.
|
||||
// When the underlying issue has been fixed, the use of this environment variable inside the integration
|
||||
// test module should be removed.
|
||||
const testYarnLinkPulumiEnv = "PULUMI_TEST_YARN_LINK_PULUMI=true"
|
||||
for _, test := range tests {
|
||||
t.Run(test.componentDir, func(t *testing.T) {
|
||||
pathEnv := componentPathEnv(t, "construct_component", test.componentDir)
|
||||
integration.ProgramTest(t, optsForConstructGo(t, test.expectedResourceCount, append(test.env, pathEnv)...))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
runtimeVenv := pulumiRuntimeVirtualEnv(t, filepath.Join("..", ".."))
|
||||
|
||||
opts := &integration.ProgramTestOptions{
|
||||
Env: []string{testYarnLinkPulumiEnv, runtimeVenv},
|
||||
func optsForConstructGo(t *testing.T, expectedResourceCount int, env ...string) *integration.ProgramTestOptions {
|
||||
return &integration.ProgramTestOptions{
|
||||
Env: env,
|
||||
Dir: filepath.Join("construct_component", "go"),
|
||||
Dependencies: []string{
|
||||
"github.com/pulumi/pulumi/sdk/v3",
|
||||
|
@ -142,7 +167,7 @@ func TestConstructGo(t *testing.T) {
|
|||
NoParallel: true, // avoid contention for Dir
|
||||
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
||||
assert.NotNil(t, stackInfo.Deployment)
|
||||
if assert.Equal(t, 9, len(stackInfo.Deployment.Resources)) {
|
||||
if assert.Equal(t, expectedResourceCount, len(stackInfo.Deployment.Resources)) {
|
||||
stackRes := stackInfo.Deployment.Resources[0]
|
||||
assert.NotNil(t, stackRes)
|
||||
assert.Equal(t, resource.RootStackType, stackRes.Type)
|
||||
|
@ -170,11 +195,6 @@ func TestConstructGo(t *testing.T) {
|
|||
}
|
||||
},
|
||||
}
|
||||
|
||||
runProgramSubTests(t, opts, map[string]string{
|
||||
"WithNodeProvider": componentPathEnv(t, "construct_component", "testcomponent"),
|
||||
"WithPythonProvider": componentPathEnv(t, "construct_component", "testcomponent-python"),
|
||||
})
|
||||
}
|
||||
|
||||
// Test remote component construction with a child resource that takes a long time to be created, ensuring it's created.
|
||||
|
|
|
@ -677,15 +677,45 @@ func TestConstructNode(t *testing.T) {
|
|||
t.Skip("Temporarily skipping test on Windows")
|
||||
}
|
||||
|
||||
opts := &integration.ProgramTestOptions{
|
||||
Env: []string{pulumiRuntimeVirtualEnv(t, filepath.Join("..", ".."))},
|
||||
tests := []struct {
|
||||
componentDir string
|
||||
expectedResourceCount int
|
||||
env []string
|
||||
}{
|
||||
{
|
||||
componentDir: "testcomponent",
|
||||
expectedResourceCount: 9,
|
||||
},
|
||||
{
|
||||
componentDir: "testcomponent-python",
|
||||
expectedResourceCount: 9,
|
||||
env: []string{pulumiRuntimeVirtualEnv(t, filepath.Join("..", ".."))},
|
||||
},
|
||||
{
|
||||
componentDir: "testcomponent-go",
|
||||
expectedResourceCount: 8, // One less because no dynamic provider.
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.componentDir, func(t *testing.T) {
|
||||
pathEnv := componentPathEnv(t, "construct_component", test.componentDir)
|
||||
integration.ProgramTest(t,
|
||||
optsForConstructNode(t, test.expectedResourceCount, append(test.env, pathEnv)...))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func optsForConstructNode(t *testing.T, expectedResourceCount int, env ...string) *integration.ProgramTestOptions {
|
||||
return &integration.ProgramTestOptions{
|
||||
Env: env,
|
||||
Dir: filepath.Join("construct_component", "nodejs"),
|
||||
Dependencies: []string{"@pulumi/pulumi"},
|
||||
Quick: true,
|
||||
NoParallel: true,
|
||||
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
||||
assert.NotNil(t, stackInfo.Deployment)
|
||||
if assert.Equal(t, 9, len(stackInfo.Deployment.Resources)) {
|
||||
if assert.Equal(t, expectedResourceCount, len(stackInfo.Deployment.Resources)) {
|
||||
stackRes := stackInfo.Deployment.Resources[0]
|
||||
assert.NotNil(t, stackRes)
|
||||
assert.Equal(t, resource.RootStackType, stackRes.Type)
|
||||
|
@ -710,11 +740,6 @@ func TestConstructNode(t *testing.T) {
|
|||
}
|
||||
},
|
||||
}
|
||||
|
||||
runProgramSubTests(t, opts, map[string]string{
|
||||
"WithNodeProvider": componentPathEnv(t, "construct_component", "testcomponent"),
|
||||
"WithPythonProvider": componentPathEnv(t, "construct_component", "testcomponent-python"),
|
||||
})
|
||||
}
|
||||
|
||||
// Test remote component construction with a child resource that takes a long time to be created, ensuring it's created.
|
||||
|
|
|
@ -378,19 +378,45 @@ func TestPythonResourceArgs(t *testing.T) {
|
|||
|
||||
// Test remote component construction in Python.
|
||||
func TestConstructPython(t *testing.T) {
|
||||
tests := []struct {
|
||||
componentDir string
|
||||
expectedResourceCount int
|
||||
env []string
|
||||
}{
|
||||
{
|
||||
componentDir: "testcomponent",
|
||||
expectedResourceCount: 9,
|
||||
// TODO[pulumi/pulumi#5455]: Dynamic providers fail to load when used from multi-lang components.
|
||||
// Until we've addressed this, set PULUMI_TEST_YARN_LINK_PULUMI, which tells the integration test
|
||||
// module to run `yarn install && yarn link @pulumi/pulumi` in the Go program's directory, allowing
|
||||
// the Node.js dynamic provider plugin to load.
|
||||
// When the underlying issue has been fixed, the use of this environment variable inside the integration
|
||||
// test module should be removed.
|
||||
env: []string{"PULUMI_TEST_YARN_LINK_PULUMI=true"},
|
||||
},
|
||||
{
|
||||
componentDir: "testcomponent-python",
|
||||
expectedResourceCount: 9,
|
||||
env: []string{pulumiRuntimeVirtualEnv(t, filepath.Join("..", ".."))},
|
||||
},
|
||||
{
|
||||
componentDir: "testcomponent-go",
|
||||
expectedResourceCount: 8, // One less because no dynamic provider.
|
||||
},
|
||||
}
|
||||
|
||||
// TODO[pulumi/pulumi#5455]: Dynamic providers fail to load when used from multi-lang components.
|
||||
// Until we've addressed this, set PULUMI_TEST_YARN_LINK_PULUMI, which tells the integration test
|
||||
// module to run `yarn install && yarn link @pulumi/pulumi` in the Python program's directory, allowing
|
||||
// the Node.js dynamic provider plugin to load.
|
||||
// When the underlying issue has been fixed, the use of this environment variable inside the integration
|
||||
// test module should be removed.
|
||||
const testYarnLinkPulumiEnv = "PULUMI_TEST_YARN_LINK_PULUMI=true"
|
||||
for _, test := range tests {
|
||||
t.Run(test.componentDir, func(t *testing.T) {
|
||||
pathEnv := componentPathEnv(t, "construct_component", test.componentDir)
|
||||
integration.ProgramTest(t,
|
||||
optsForConstructPython(t, test.expectedResourceCount, append(test.env, pathEnv)...))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
runtimeVenv := pulumiRuntimeVirtualEnv(t, filepath.Join("..", ".."))
|
||||
|
||||
opts := &integration.ProgramTestOptions{
|
||||
Env: []string{testYarnLinkPulumiEnv, runtimeVenv},
|
||||
func optsForConstructPython(t *testing.T, expectedResourceCount int, env ...string) *integration.ProgramTestOptions {
|
||||
return &integration.ProgramTestOptions{
|
||||
Env: env,
|
||||
Dir: filepath.Join("construct_component", "python"),
|
||||
Dependencies: []string{
|
||||
filepath.Join("..", "..", "sdk", "python", "env", "src"),
|
||||
|
@ -399,7 +425,7 @@ func TestConstructPython(t *testing.T) {
|
|||
NoParallel: true, // avoid contention for Dir
|
||||
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
||||
assert.NotNil(t, stackInfo.Deployment)
|
||||
if assert.Equal(t, 9, len(stackInfo.Deployment.Resources)) {
|
||||
if assert.Equal(t, expectedResourceCount, len(stackInfo.Deployment.Resources)) {
|
||||
stackRes := stackInfo.Deployment.Resources[0]
|
||||
assert.NotNil(t, stackRes)
|
||||
assert.Equal(t, resource.RootStackType, stackRes.Type)
|
||||
|
@ -427,11 +453,6 @@ func TestConstructPython(t *testing.T) {
|
|||
}
|
||||
},
|
||||
}
|
||||
|
||||
runProgramSubTests(t, opts, map[string]string{
|
||||
"WithNodeProvider": componentPathEnv(t, "construct_component", "testcomponent"),
|
||||
"WithPythonProvider": componentPathEnv(t, "construct_component", "testcomponent-python"),
|
||||
})
|
||||
}
|
||||
|
||||
// Test remote component construction with a child resource that takes a long time to be created, ensuring it's created.
|
||||
|
|
|
@ -566,11 +566,6 @@ func TestConfigPaths(t *testing.T) {
|
|||
e.RunCommand("pulumi", "stack", "rm", "--yes")
|
||||
}
|
||||
|
||||
//nolint:golint,deadcode
|
||||
func testComponentPathEnv(t *testing.T) string {
|
||||
return componentPathEnv(t, "construct_component", "testcomponent")
|
||||
}
|
||||
|
||||
//nolint:golint,deadcode
|
||||
func testComponentSlowPathEnv(t *testing.T) string {
|
||||
return componentPathEnv(t, "construct_component_slow", "testcomponent")
|
||||
|
|
Loading…
Reference in a new issue