mirror of https://github.com/pulumi/pulumi.git
371 lines
12 KiB
Go
371 lines
12 KiB
Go
// Copyright 2016-2023, 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.
|
|
//go:build !all
|
|
// +build !all
|
|
|
|
// A provider with resources for use in tests.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
pschema "github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
|
"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"
|
|
rpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
|
|
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
|
)
|
|
|
|
const (
|
|
providerName = "testprovider"
|
|
version = "0.0.1"
|
|
)
|
|
|
|
var providerSchema = pschema.PackageSpec{
|
|
Name: "testprovider",
|
|
Description: "A test provider.",
|
|
DisplayName: "testprovider",
|
|
|
|
Config: pschema.ConfigSpec{},
|
|
|
|
Provider: pschema.ResourceSpec{
|
|
ObjectTypeSpec: pschema.ObjectTypeSpec{
|
|
Description: "The provider type for the testprovider package.",
|
|
Type: "object",
|
|
},
|
|
InputProperties: map[string]pschema.PropertySpec{},
|
|
},
|
|
|
|
Types: map[string]pschema.ComplexTypeSpec{},
|
|
Resources: map[string]pschema.ResourceSpec{},
|
|
Functions: map[string]pschema.FunctionSpec{},
|
|
Language: map[string]pschema.RawMessage{},
|
|
}
|
|
|
|
// Minimal set of methods to implement a basic provider.
|
|
type testProvider interface {
|
|
Check(ctx context.Context, req *rpc.CheckRequest) (*rpc.CheckResponse, error)
|
|
Diff(ctx context.Context, req *rpc.DiffRequest) (*rpc.DiffResponse, error)
|
|
Create(ctx context.Context, req *rpc.CreateRequest) (*rpc.CreateResponse, error)
|
|
Read(ctx context.Context, req *rpc.ReadRequest) (*rpc.ReadResponse, error)
|
|
Update(ctx context.Context, req *rpc.UpdateRequest) (*rpc.UpdateResponse, error)
|
|
Delete(ctx context.Context, req *rpc.DeleteRequest) (*emptypb.Empty, error)
|
|
Invoke(ctx context.Context, req *rpc.InvokeRequest) (*rpc.InvokeResponse, error)
|
|
Call(ctx context.Context, req *rpc.CallRequest) (*rpc.CallResponse, error)
|
|
}
|
|
|
|
var testProviders = func() map[string]testProvider {
|
|
ep := &echoProvider{}
|
|
|
|
testProviders := map[string]testProvider{
|
|
"testprovider:index:Random": &randomProvider{},
|
|
"testprovider:index:Echo": ep,
|
|
"testprovider:index:Echo/doEchoMethod": ep,
|
|
"testprovider:index:doEcho": ep,
|
|
"testprovider:index:doMultiEcho": ep,
|
|
"testprovider:index:FailsOnDelete": &failsOnDeleteProvider{},
|
|
"testprovider:index:FailsOnCreate": &failsOnCreateProvider{},
|
|
}
|
|
|
|
return testProviders
|
|
}()
|
|
|
|
func providerForURN(urn string) (testProvider, string, bool) {
|
|
ty := string(resource.URN(urn).Type())
|
|
provider, ok := testProviders[ty]
|
|
return provider, ty, ok
|
|
}
|
|
|
|
//nolint:unused,deadcode
|
|
func main() {
|
|
if err := provider.Main(providerName, func(host *provider.HostClient) (rpc.ResourceProviderServer, error) {
|
|
return makeProvider(host, providerName, version)
|
|
}); err != nil {
|
|
cmdutil.ExitError(err.Error())
|
|
}
|
|
}
|
|
|
|
type testproviderProvider struct {
|
|
rpc.UnimplementedResourceProviderServer
|
|
|
|
parameter string
|
|
|
|
host *provider.HostClient
|
|
name string
|
|
version string
|
|
}
|
|
|
|
func makeProvider(host *provider.HostClient, name, version string) (rpc.ResourceProviderServer, error) {
|
|
// Return the new provider
|
|
return &testproviderProvider{
|
|
host: host,
|
|
name: name,
|
|
version: version,
|
|
}, nil
|
|
}
|
|
|
|
// CheckConfig validates the configuration for this provider.
|
|
func (p *testproviderProvider) CheckConfig(ctx context.Context, req *rpc.CheckRequest) (*rpc.CheckResponse, error) {
|
|
return &rpc.CheckResponse{Inputs: req.GetNews()}, nil
|
|
}
|
|
|
|
// DiffConfig diffs the configuration for this provider.
|
|
func (p *testproviderProvider) DiffConfig(ctx context.Context, req *rpc.DiffRequest) (*rpc.DiffResponse, error) {
|
|
return &rpc.DiffResponse{}, nil
|
|
}
|
|
|
|
// Configure configures the resource provider with "globals" that control its behavior.
|
|
func (p *testproviderProvider) Configure(_ context.Context, req *rpc.ConfigureRequest) (*rpc.ConfigureResponse, error) {
|
|
return &rpc.ConfigureResponse{
|
|
AcceptSecrets: true,
|
|
}, nil
|
|
}
|
|
|
|
func (p *testproviderProvider) Parameterize(_ context.Context, req *rpc.ParameterizeRequest) (*rpc.ParameterizeResponse, error) {
|
|
switch params := req.GetParameters().(type) {
|
|
case *rpc.ParameterizeRequest_Args:
|
|
args := params.Args.Args
|
|
if len(args) != 1 {
|
|
return nil, fmt.Errorf("expected exactly one argument")
|
|
}
|
|
p.parameter = args[0]
|
|
case *rpc.ParameterizeRequest_Value:
|
|
val := string(params.Value.Value)
|
|
if val == "" {
|
|
return nil, fmt.Errorf("expected a non-empty string value")
|
|
}
|
|
p.parameter = val
|
|
default:
|
|
return nil, fmt.Errorf("unexpected parameter type")
|
|
}
|
|
|
|
for k, prov := range testProviders {
|
|
testProviders[strings.Replace(k, "testprovider", p.parameter, 1)] = prov
|
|
}
|
|
|
|
return &rpc.ParameterizeResponse{
|
|
Name: p.parameter,
|
|
Version: version,
|
|
}, nil
|
|
}
|
|
|
|
// Invoke dynamically executes a built-in function in the provider.
|
|
func (p *testproviderProvider) Invoke(_ context.Context, req *rpc.InvokeRequest) (*rpc.InvokeResponse, error) {
|
|
if p, ok := testProviders[req.GetTok()]; ok {
|
|
return p.Invoke(context.Background(), req)
|
|
}
|
|
|
|
tok := req.GetTok()
|
|
if tok == "testprovider:index:returnArgs" {
|
|
return &rpc.InvokeResponse{
|
|
Return: req.Args,
|
|
}, nil
|
|
}
|
|
return nil, fmt.Errorf("Unknown Invoke token '%s'", tok)
|
|
}
|
|
|
|
// StreamInvoke dynamically executes a built-in function in the provider. The result is streamed
|
|
// back as a series of messages.
|
|
func (p *testproviderProvider) StreamInvoke(req *rpc.InvokeRequest,
|
|
server rpc.ResourceProvider_StreamInvokeServer,
|
|
) error {
|
|
tok := req.GetTok()
|
|
return fmt.Errorf("Unknown StreamInvoke token '%s'", tok)
|
|
}
|
|
|
|
func (p *testproviderProvider) Call(_ context.Context, req *rpc.CallRequest) (*rpc.CallResponse, error) {
|
|
tok := req.GetTok()
|
|
|
|
if p, ok := testProviders[tok]; ok {
|
|
return p.Call(context.Background(), req)
|
|
}
|
|
|
|
return nil, fmt.Errorf("Unknown Call token '%s'", tok)
|
|
}
|
|
|
|
func (p *testproviderProvider) Check(ctx context.Context, req *rpc.CheckRequest) (*rpc.CheckResponse, error) {
|
|
provider, ty, ok := providerForURN(req.GetUrn())
|
|
if !ok {
|
|
return nil, fmt.Errorf("Unknown resource type '%s'", ty)
|
|
}
|
|
return provider.Check(ctx, req)
|
|
}
|
|
|
|
// Diff checks what impacts a hypothetical update will have on the resource's properties.
|
|
func (p *testproviderProvider) Diff(ctx context.Context, req *rpc.DiffRequest) (*rpc.DiffResponse, error) {
|
|
provider, ty, ok := providerForURN(req.GetUrn())
|
|
if !ok {
|
|
return nil, fmt.Errorf("Unknown resource type '%s'", ty)
|
|
}
|
|
return provider.Diff(ctx, req)
|
|
}
|
|
|
|
// Create allocates a new instance of the provided resource and returns its unique ID afterwards.
|
|
func (p *testproviderProvider) Create(ctx context.Context, req *rpc.CreateRequest) (*rpc.CreateResponse, error) {
|
|
provider, ty, ok := providerForURN(req.GetUrn())
|
|
if !ok {
|
|
return nil, fmt.Errorf("Unknown resource type '%s'", ty)
|
|
}
|
|
return provider.Create(ctx, req)
|
|
}
|
|
|
|
// Read the current live state associated with a resource.
|
|
func (p *testproviderProvider) Read(ctx context.Context, req *rpc.ReadRequest) (*rpc.ReadResponse, error) {
|
|
provider, ty, ok := providerForURN(req.GetUrn())
|
|
if !ok {
|
|
return nil, fmt.Errorf("Unknown resource type '%s'", ty)
|
|
}
|
|
return provider.Read(ctx, req)
|
|
}
|
|
|
|
// Update updates an existing resource with new values.
|
|
func (p *testproviderProvider) Update(ctx context.Context, req *rpc.UpdateRequest) (*rpc.UpdateResponse, error) {
|
|
provider, ty, ok := providerForURN(req.GetUrn())
|
|
if !ok {
|
|
return nil, fmt.Errorf("Unknown resource type '%s'", ty)
|
|
}
|
|
return provider.Update(ctx, req)
|
|
}
|
|
|
|
// Delete tears down an existing resource with the given ID. If it fails, the resource is assumed
|
|
// to still exist.
|
|
func (p *testproviderProvider) Delete(ctx context.Context, req *rpc.DeleteRequest) (*emptypb.Empty, error) {
|
|
provider, ty, ok := providerForURN(req.GetUrn())
|
|
if !ok {
|
|
return nil, fmt.Errorf("Unknown resource type '%s'", ty)
|
|
}
|
|
return provider.Delete(ctx, req)
|
|
}
|
|
|
|
// Construct creates a new component resource.
|
|
func (p *testproviderProvider) Construct(ctx context.Context, req *rpc.ConstructRequest) (*rpc.ConstructResponse, error) {
|
|
if req.Type != "testprovider:index:Component" {
|
|
return nil, fmt.Errorf("unknown resource type %s", req.Type)
|
|
}
|
|
|
|
return pulumiprovider.Construct(
|
|
ctx, req, p.host.EngineConn(),
|
|
func(ctx *pulumi.Context, typ, name string, inputs pulumiprovider.ConstructInputs,
|
|
options pulumi.ResourceOption,
|
|
) (*pulumiprovider.ConstructResult, error) {
|
|
args := &ComponentArgs{}
|
|
if err := inputs.CopyTo(args); err != nil {
|
|
return nil, fmt.Errorf("setting args: %w", err)
|
|
}
|
|
|
|
component, err := NewComponent(ctx, name, args, options)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return pulumiprovider.NewConstructResult(component)
|
|
})
|
|
}
|
|
|
|
// GetPluginInfo returns generic information about this plugin, like its version.
|
|
func (p *testproviderProvider) GetPluginInfo(context.Context, *emptypb.Empty) (*rpc.PluginInfo, error) {
|
|
return &rpc.PluginInfo{
|
|
Version: p.version,
|
|
}, nil
|
|
}
|
|
|
|
func (p *testproviderProvider) Attach(ctx context.Context, req *rpc.PluginAttach) (*emptypb.Empty, error) {
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetSchema returns the JSON-serialized schema for the provider.
|
|
func (p *testproviderProvider) GetSchema(ctx context.Context,
|
|
req *rpc.GetSchemaRequest,
|
|
) (*rpc.GetSchemaResponse, error) {
|
|
makeJSONString := func(v any) ([]byte, error) {
|
|
var out bytes.Buffer
|
|
encoder := json.NewEncoder(&out)
|
|
encoder.SetEscapeHTML(false)
|
|
encoder.SetIndent("", " ")
|
|
if err := encoder.Encode(v); err != nil {
|
|
return nil, err
|
|
}
|
|
return out.Bytes(), nil
|
|
}
|
|
|
|
sch := providerSchema
|
|
// If we have a parameter, we'll return a copy of the provider's resources and
|
|
// functions -- this is just enough to test that the engine is calling
|
|
// Parameterize and GetSchema correctly.
|
|
if req.SubpackageName != "" {
|
|
if req.SubpackageName == p.parameter {
|
|
sch = pschema.PackageSpec{
|
|
Name: p.parameter,
|
|
Version: "1.0.0",
|
|
Parameterization: &pschema.ParameterizationSpec{
|
|
BaseProvider: pschema.BaseProviderSpec{
|
|
Name: "testprovider",
|
|
Version: version,
|
|
},
|
|
Parameter: []byte(p.parameter),
|
|
},
|
|
Resources: map[string]pschema.ResourceSpec{},
|
|
Functions: map[string]pschema.FunctionSpec{},
|
|
}
|
|
|
|
for k, r := range providerSchema.Resources {
|
|
sch.Resources[strings.Replace(k, "testprovider", p.parameter, 1)] = r
|
|
for k, m := range r.Methods {
|
|
r.Methods[k] = strings.Replace(m, "testprovider", p.parameter, 1)
|
|
}
|
|
}
|
|
for k, f := range providerSchema.Functions {
|
|
sch.Functions[strings.Replace(k, "testprovider", p.parameter, 1)] = f
|
|
for k, prop := range f.Inputs.Properties {
|
|
if prop.TypeSpec.Ref != "" {
|
|
prop.TypeSpec.Ref = strings.Replace(prop.TypeSpec.Ref, "testprovider", p.parameter, 1)
|
|
f.Inputs.Properties[k] = prop
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
return nil, fmt.Errorf("expected subpackage %s", req.SubpackageName)
|
|
}
|
|
}
|
|
|
|
schemaJSON, err := makeJSONString(sch)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &rpc.GetSchemaResponse{
|
|
Schema: string(schemaJSON),
|
|
}, nil
|
|
}
|
|
|
|
// 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 *testproviderProvider) Cancel(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (p *testproviderProvider) GetMapping(context.Context, *rpc.GetMappingRequest) (*rpc.GetMappingResponse, error) {
|
|
return &rpc.GetMappingResponse{}, nil
|
|
}
|