Skip to content

Commit

Permalink
Support local plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
Frassle committed Aug 29, 2024
1 parent f20ccea commit d7d176a
Show file tree
Hide file tree
Showing 11 changed files with 1,081 additions and 1 deletion.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/stretchr/testify v1.9.0
github.com/zclconf/go-cty v1.13.2
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.33.0
gopkg.in/yaml.v3 v3.0.1
)

Expand Down Expand Up @@ -186,7 +187,6 @@ require (
google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240311173647-c811ad7063a7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240311173647-c811ad7063a7 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
lukechampine.com/frand v1.4.2 // indirect
mvdan.cc/gofumpt v0.5.0 // indirect
Expand Down
12 changes: 12 additions & 0 deletions pkg/tests/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,15 @@ func TestEnvVarsKeepConflictingValues(t *testing.T) {
}
integration.ProgramTest(t, &testOptions)
}

// Test a local provider plugin.
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestLocalPlugin(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("testdata", "local"),
LocalProviders: []integration.LocalDependency{
{Package: "testprovider", Path: "testprovider"},
},
})
}
8 changes: 8 additions & 0 deletions pkg/tests/testdata/local/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: local
scription: An integration test showing the use of a a local plugin
runtime: yaml
resources:
res1:
type: testprovider:index:Random
res2:
type: testprovider:index:Echo
2 changes: 2 additions & 0 deletions pkg/tests/testprovider/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pulumi-resource-testprovider
schema-testprovider.json
1 change: 1 addition & 0 deletions pkg/tests/testprovider/PulumiPlugin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
runtime: go
101 changes: 101 additions & 0 deletions pkg/tests/testprovider/component.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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.
//go:build !all
// +build !all

package main

import (
"errors"
"fmt"
"reflect"

"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

type Random struct {
pulumi.CustomResourceState

Length pulumi.IntOutput `pulumi:"length"`
Result pulumi.StringOutput `pulumi:"result"`
}

func NewRandom(ctx *pulumi.Context,
name string, args *RandomArgs, opts ...pulumi.ResourceOption,
) (*Random, error) {
if args == nil || args.Length == nil {
return nil, errors.New("missing required argument 'Length'")
}
var resource Random
err := ctx.RegisterResource("testprovider:index:Random", name, args, &resource, opts...)
if err != nil {
return nil, err
}
return &resource, nil
}

type randomArgs struct {
Length int `pulumi:"length"`
Prefix string `pulumi:"prefix"`
}

type RandomArgs struct {
Length pulumi.IntInput
Prefix pulumi.StringInput
}

func (RandomArgs) ElementType() reflect.Type {
return reflect.TypeOf((*randomArgs)(nil)).Elem()
}

type Component struct {
pulumi.ResourceState

ChildID pulumi.IDOutput `pulumi:"childId"`
}

type ComponentArgs struct {
Length int `pulumi:"length"`
}

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("testprovider:index:Component", name, component, opts...)
if err != nil {
return nil, err
}

res, err := NewRandom(ctx, fmt.Sprintf("child-%s", name), &RandomArgs{
Length: pulumi.Int(args.Length),
}, pulumi.Parent(component))
if err != nil {
return nil, err
}

component.ChildID = res.ID()

if err := ctx.RegisterResourceOutputs(component, pulumi.Map{
"childId": component.ChildID,
}); err != nil {
return nil, err
}

return component, nil
}
222 changes: 222 additions & 0 deletions pkg/tests/testprovider/echo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// 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.
//go:build !all
// +build !all

package main

import (
"context"
"os"
"strconv"

pschema "github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
rpc "github.com/pulumi/pulumi/sdk/v3/proto/go"

"google.golang.org/protobuf/types/known/emptypb"
)

func init() {
providerSchema.Resources["testprovider:index:Echo"] = pschema.ResourceSpec{
ObjectTypeSpec: pschema.ObjectTypeSpec{
Description: "A test resource that echoes its input.",
Properties: map[string]pschema.PropertySpec{
"echo": {
TypeSpec: pschema.TypeSpec{
Ref: "pulumi.json#/Any",
},
Description: "Input to echo.",
},
},
Type: "object",
},
InputProperties: map[string]pschema.PropertySpec{
"echo": {
TypeSpec: pschema.TypeSpec{
Ref: "pulumi.json#/Any",
},
Description: "An echoed input.",
},
},
Methods: map[string]string{
"doEchoMethod": "testprovider:index:Echo/doEchoMethod",
},
}
providerSchema.Functions["testprovider:index:doEcho"] = pschema.FunctionSpec{
Description: "A test invoke that echoes its input.",
Inputs: &pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"echo": {
TypeSpec: pschema.TypeSpec{
Type: "string",
},
},
},
},
Outputs: &pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"echo": {
TypeSpec: pschema.TypeSpec{
Type: "string",
},
},
},
},
}
if os.Getenv("PULUMI_TEST_MULTI_ARGUMENT_INPUTS") != "" {
// Conditionally add this if an env flag is set, since it does not work with all langs
providerSchema.Functions["testprovider:index:doMultiEcho"] = pschema.FunctionSpec{
Description: "A test invoke that echoes its input, using multiple inputs.",
MultiArgumentInputs: []string{
"echoA",
"echoB",
},
Inputs: &pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"echoA": {
TypeSpec: pschema.TypeSpec{
Type: "string",
},
},
"echoB": {
TypeSpec: pschema.TypeSpec{
Type: "string",
},
},
},
},
Outputs: &pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"echoA": {
TypeSpec: pschema.TypeSpec{
Type: "string",
},
},
"echoB": {
TypeSpec: pschema.TypeSpec{
Type: "string",
},
},
},
},
}
}
providerSchema.Functions["testprovider:index:Echo/doEchoMethod"] = pschema.FunctionSpec{
Description: "A test call that echoes its input.",
Inputs: &pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"__self__": {
TypeSpec: pschema.TypeSpec{
Ref: "#/types/testprovider:index:Echo",
},
},
"echo": {
TypeSpec: pschema.TypeSpec{
Type: "string",
},
},
},
},
Outputs: &pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"echo": {
TypeSpec: pschema.TypeSpec{
Type: "string",
},
},
},
},
}
}

type echoProvider struct {
id int
}

func (p *echoProvider) Check(ctx context.Context, req *rpc.CheckRequest) (*rpc.CheckResponse, error) {
return &rpc.CheckResponse{Inputs: req.News, Failures: nil}, nil
}

func (p *echoProvider) Diff(ctx context.Context, req *rpc.DiffRequest) (*rpc.DiffResponse, error) {
olds, err := plugin.UnmarshalProperties(req.GetOlds(), plugin.MarshalOptions{KeepUnknowns: true, SkipNulls: true})
if err != nil {
return nil, err
}

news, err := plugin.UnmarshalProperties(req.GetNews(), plugin.MarshalOptions{KeepUnknowns: true, SkipNulls: true})
if err != nil {
return nil, err
}

d := olds.Diff(news)
changes := rpc.DiffResponse_DIFF_NONE
var replaces []string
if d != nil && d.Changed("echo") {
changes = rpc.DiffResponse_DIFF_SOME
replaces = append(replaces, "echo")
}

return &rpc.DiffResponse{
Changes: changes,
Replaces: replaces,
}, nil
}

func (p *echoProvider) Create(ctx context.Context, req *rpc.CreateRequest) (*rpc.CreateResponse, error) {
inputs, err := plugin.UnmarshalProperties(req.GetProperties(), plugin.MarshalOptions{
KeepUnknowns: true,
SkipNulls: true,
})
if err != nil {
return nil, err
}

outputProperties, err := plugin.MarshalProperties(
inputs,
plugin.MarshalOptions{KeepUnknowns: true, SkipNulls: true},
)
if err != nil {
return nil, err
}

p.id++
return &rpc.CreateResponse{
Id: strconv.Itoa(p.id),
Properties: outputProperties,
}, nil
}

func (p *echoProvider) Read(ctx context.Context, req *rpc.ReadRequest) (*rpc.ReadResponse, error) {
return &rpc.ReadResponse{
Id: req.Id,
Properties: req.Properties,
}, nil
}

func (p *echoProvider) Update(ctx context.Context, req *rpc.UpdateRequest) (*rpc.UpdateResponse, error) {
panic("Update not implemented")
}

func (p *echoProvider) Delete(ctx context.Context, req *rpc.DeleteRequest) (*emptypb.Empty, error) {
return &emptypb.Empty{}, nil
}

func (p *echoProvider) Invoke(ctx context.Context, req *rpc.InvokeRequest) (*rpc.InvokeResponse, error) {
return &rpc.InvokeResponse{Return: req.Args}, nil
}

func (p *echoProvider) Call(ctx context.Context, req *rpc.CallRequest) (*rpc.CallResponse, error) {
return &rpc.CallResponse{Return: req.Args}, nil
}
Loading

0 comments on commit d7d176a

Please sign in to comment.