Support for AWS SNS resources (#272)

Support for AWS SNS Topic and Subscription resources.
This commit is contained in:
Luke Hoban 2017-06-27 16:50:54 -07:00 committed by GitHub
parent 15a75c9ee4
commit 033c262918
14 changed files with 685 additions and 106 deletions

View file

@ -0,0 +1,33 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package sns
import (
"github.com/pulumi/lumi/pkg/resource/idl"
)
// An Amazon Simple Notification Service (Amazon SNS) topic subscription. For more information, see
// http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sns-subscription.html.
type Subscription struct {
idl.NamedResource
// A name for the topic. If you don't specify a name, a unique physical ID will be generated.
Topic *Topic `lumi:"topic,replaces"`
// The subscription's protocol.
Protocol Protocol `lumi:"protocol,replaces"`
// The subscription's endpoint (format depends on the protocol).
Endpoint string `lumi:"endpoint,replaces"`
}
// The protocols supported by the Amazon Simple Notification Service (Amazon SNS).
type Protocol string
const (
HTTSubscription Protocol = "http" // delivery of JSON-encoded message via HTTP POST.
HTTPSSubscription Protocol = "https" // delivery of JSON-encoded message via HTTPS POST.
EmailSubscription Protocol = "email" // delivery of message via SMTP.
EmailJSONSubscription Protocol = "email-json" // delivery of JSON-encoded message via SMTP.
SMSSubscription Protocol = "sms" // delivery of message via SMS.
SQSSubscription Protocol = "sqs" // delivery of JSON-encoded message to an Amazon SQS queue.
ApplicationSubscription Protocol = "application" // delivery of JSON-encoded message to a mobile app or device.
LambdaSubscription Protocol = "lambda" // delivery of JSON-encoded message to an AWS Lambda function.
)

View file

@ -14,27 +14,4 @@ type Topic struct {
TopicName *string `lumi:"topicName,replaces,optional"`
// A developer-defined string that can be used to identify this SNS topic.
DisplayName *string `lumi:"displayName,optional"`
// The SNS subscriptions (endpoints) for this topic.
Subscription *[]TopicSubscription `lumi:"subscription,optional"`
}
type TopicSubscription struct {
// The subscription's protocol.
Protocol TopicProtocol `lumi:"protocol"`
// The subscription's endpoint (format depends on the protocol).
Endpoint string `lumi:"endpoint"`
}
// The protocols supported by the Amazon Simple Notification Service (Amazon SNS).
type TopicProtocol string
const (
HTTPTopic TopicProtocol = "http" // delivery of JSON-encoded message via HTTP POST.
HTTPSTopic TopicProtocol = "https" // delivery of JSON-encoded message via HTTPS POST.
EmailTopic TopicProtocol = "email" // delivery of message via SMTP.
EmailJSONTopic TopicProtocol = "email-json" // delivery of JSON-encoded message via SMTP.
SMSTopic TopicProtocol = "sms" // delivery of message via SMS.
SQSTopic TopicProtocol = "sqs" // delivery of JSON-encoded message to an Amazon SQS queue.
ApplicationTopic TopicProtocol = "application" // delivery of JSON-encoded message to a mobile app or device.
LambdaTopic TopicProtocol = "lambda" // delivery of JSON-encoded message to an AWS Lambda function.
)

View file

@ -4,8 +4,6 @@
/* tslint:disable:ordered-imports variable-name */
import * as lumi from "@lumi/lumi";
import {TopicSubscription} from "../sns/topic";
export let AverageStatistic: AlarmStatistic = "Average";
export let BitsMetric: AlarmMetric = "Bits";
export let BitsPerSecondMetric: AlarmMetric = "Bits/Second";
@ -46,7 +44,6 @@ export let ThresholdLessThanOrEqualTo: AlarmComparisonOperator = "LessThanOrEqua
export class ActionTarget extends lumi.NamedResource implements ActionTargetArgs {
public readonly topicName?: string;
public displayName?: string;
public subscription?: TopicSubscription[];
public static get(id: lumi.ID): ActionTarget {
return <any>undefined; // functionality provided by the runtime
@ -61,7 +58,6 @@ export class ActionTarget extends lumi.NamedResource implements ActionTargetArgs
if (args !== undefined) {
this.topicName = args.topicName;
this.displayName = args.displayName;
this.subscription = args.subscription;
}
}
}
@ -69,7 +65,6 @@ export class ActionTarget extends lumi.NamedResource implements ActionTargetArgs
export interface ActionTargetArgs {
readonly topicName?: string;
displayName?: string;
subscription?: TopicSubscription[];
}
export class Alarm extends lumi.NamedResource implements AlarmArgs {

View file

@ -1,4 +1,5 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
export * from "./subscription";
export * from "./topic";

View file

@ -0,0 +1,63 @@
// *** WARNING: this file was generated by the Lumi IDL Compiler (LUMIDL). ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
/* tslint:disable:ordered-imports variable-name */
import * as lumi from "@lumi/lumi";
import {Topic} from "./topic";
export let ApplicationSubscription: Protocol = "application";
export let EmailJSONSubscription: Protocol = "email-json";
export let EmailSubscription: Protocol = "email";
export let HTTPSSubscription: Protocol = "https";
export let HTTSubscription: Protocol = "http";
export let LambdaSubscription: Protocol = "lambda";
export let SMSSubscription: Protocol = "sms";
export let SQSSubscription: Protocol = "sqs";
export type Protocol =
"application" |
"email-json" |
"email" |
"https" |
"http" |
"lambda" |
"sms" |
"sqs";
export class Subscription extends lumi.NamedResource implements SubscriptionArgs {
public readonly topic: Topic;
public readonly protocol: Protocol;
public readonly endpoint: string;
public static get(id: lumi.ID): Subscription {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Subscription[] {
return <any>undefined; // functionality provided by the runtime
}
constructor(name: string, args: SubscriptionArgs) {
super(name);
if (args.topic === undefined) {
throw new Error("Missing required argument 'topic'");
}
this.topic = args.topic;
if (args.protocol === undefined) {
throw new Error("Missing required argument 'protocol'");
}
this.protocol = args.protocol;
if (args.endpoint === undefined) {
throw new Error("Missing required argument 'endpoint'");
}
this.endpoint = args.endpoint;
}
}
export interface SubscriptionArgs {
readonly topic: Topic;
readonly protocol: Protocol;
readonly endpoint: string;
}

View file

@ -4,19 +4,9 @@
/* tslint:disable:ordered-imports variable-name */
import * as lumi from "@lumi/lumi";
export let ApplicationTopic: TopicProtocol = "application";
export let EmailJSONTopic: TopicProtocol = "email-json";
export let EmailTopic: TopicProtocol = "email";
export let HTTPSTopic: TopicProtocol = "https";
export let HTTPTopic: TopicProtocol = "http";
export let LambdaTopic: TopicProtocol = "lambda";
export let SMSTopic: TopicProtocol = "sms";
export let SQSTopic: TopicProtocol = "sqs";
export class Topic extends lumi.NamedResource implements TopicArgs {
public readonly topicName?: string;
public displayName?: string;
public subscription?: TopicSubscription[];
public static get(id: lumi.ID): Topic {
return <any>undefined; // functionality provided by the runtime
@ -31,7 +21,6 @@ export class Topic extends lumi.NamedResource implements TopicArgs {
if (args !== undefined) {
this.topicName = args.topicName;
this.displayName = args.displayName;
this.subscription = args.subscription;
}
}
}
@ -39,21 +28,5 @@ export class Topic extends lumi.NamedResource implements TopicArgs {
export interface TopicArgs {
readonly topicName?: string;
displayName?: string;
subscription?: TopicSubscription[];
}
export type TopicProtocol =
"application" |
"email-json" |
"email" |
"https" |
"http" |
"lambda" |
"sms" |
"sqs";
export interface TopicSubscription {
protocol: TopicProtocol;
endpoint: string;
}

View file

@ -14,6 +14,7 @@ import (
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/lambda"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/sns"
"github.com/golang/glog"
"github.com/pkg/errors"
"github.com/pulumi/lumi/pkg/resource/provider"
@ -38,6 +39,7 @@ type Context struct {
iam *iam.IAM
lambda *lambda.Lambda
s3 *s3.S3
sns *sns.SNS
}
const regionConfig = "aws:config:region"
@ -152,6 +154,14 @@ func (ctx *Context) S3() *s3.S3 {
return ctx.s3
}
func (ctx *Context) SNS() *sns.SNS {
contract.Assert(ctx.sess != nil)
if ctx.sns == nil {
ctx.sns = sns.New(ctx.sess)
}
return ctx.sns
}
// Request manufactures a standard Golang context object for a request within this overall AWS context.
func (ctx *Context) Request() context.Context {
// IDEA: unify this with the gRPC context; this will be easier once gRPC moves to the standard Golang context.

View file

@ -19,6 +19,7 @@ import (
"github.com/pulumi/lumi/lib/aws/provider/iam"
"github.com/pulumi/lumi/lib/aws/provider/lambda"
"github.com/pulumi/lumi/lib/aws/provider/s3"
"github.com/pulumi/lumi/lib/aws/provider/sns"
)
// Provider implements the AWS resource provider's operations for all known AWS types.
@ -49,6 +50,8 @@ func NewProvider(host *provider.HostClient) (*Provider, error) {
iam.RoleToken: iam.NewRoleProvider(ctx),
s3.BucketToken: s3.NewBucketProvider(ctx),
s3.ObjectToken: s3.NewObjectProvider(ctx),
sns.TopicToken: sns.NewTopicProvider(ctx),
sns.SubscriptionToken: sns.NewSubscriptionProvider(ctx),
},
}, nil
}

View file

@ -0,0 +1,70 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package sns
import (
"fmt"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
awssns "github.com/aws/aws-sdk-go/service/sns"
"github.com/pulumi/lumi/lib/aws/provider/awsctx"
"github.com/pulumi/lumi/lib/aws/provider/testutil"
"github.com/pulumi/lumi/lib/aws/rpc/sns"
"github.com/pulumi/lumi/pkg/resource"
"github.com/stretchr/testify/assert"
)
func Test(t *testing.T) {
t.Parallel()
prefix := resource.NewUniqueHex("lumitest", 20, 20)
ctx := testutil.CreateContext(t)
defer func() {
err := cleanupTopics(prefix, ctx)
assert.Nil(t, err)
}()
resources := map[string]testutil.Resource{
"topic": {Provider: NewTopicProvider(ctx), Token: TopicToken},
}
steps := []testutil.Step{
{
testutil.ResourceGenerator{
Name: "topic",
Creator: func(ctx testutil.Context) interface{} {
return &sns.Topic{
Name: aws.String(prefix),
DisplayName: aws.String(prefix),
}
},
},
},
}
props := testutil.ProviderTest(t, resources, steps)
assert.NotNil(t, props)
}
func cleanupTopics(prefix string, ctx *awsctx.Context) error {
fmt.Printf("Cleaning up topic with name:%v\n", prefix)
list, err := ctx.SNS().ListTopics(&awssns.ListTopicsInput{})
if err != nil {
return err
}
cleaned := 0
for _, topic := range list.Topics {
if strings.Contains(aws.StringValue(topic.TopicArn), prefix) {
if _, delerr := ctx.SNS().DeleteTopic(&awssns.DeleteTopicInput{
TopicArn: topic.TopicArn,
}); delerr != nil {
fmt.Printf("Unable to cleanup topic %v: %v\n", topic.TopicArn, delerr)
return delerr
}
cleaned++
}
}
fmt.Printf("Cleaned up %v topics\n", cleaned)
return nil
}

View file

@ -0,0 +1,111 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package sns
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
awssns "github.com/aws/aws-sdk-go/service/sns"
"github.com/pulumi/lumi/pkg/resource"
"github.com/pulumi/lumi/pkg/util/contract"
"github.com/pulumi/lumi/sdk/go/pkg/lumirpc"
"golang.org/x/net/context"
"github.com/pulumi/lumi/lib/aws/provider/arn"
"github.com/pulumi/lumi/lib/aws/provider/awsctx"
"github.com/pulumi/lumi/lib/aws/rpc/sns"
)
const SubscriptionToken = sns.SubscriptionToken
// NewSubscriptionProvider creates a provider that handles SNS subscription operations.
func NewSubscriptionProvider(ctx *awsctx.Context) lumirpc.ResourceProviderServer {
ops := &subscriptionProvider{ctx}
return sns.NewSubscriptionProvider(ops)
}
type subscriptionProvider struct {
ctx *awsctx.Context
}
// Check validates that the given property bag is valid for a resource of the given type.
func (p *subscriptionProvider) Check(ctx context.Context, obj *sns.Subscription, property string) error {
return nil
}
// 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 "transacational").
func (p *subscriptionProvider) Create(ctx context.Context, obj *sns.Subscription) (resource.ID, error) {
topicName, err := arn.ParseResourceName(obj.Topic)
if err != nil {
return "", err
}
fmt.Printf("Creating SNS Subscription on topic '%v'\n", topicName)
create := &awssns.SubscribeInput{
TopicArn: aws.String(string(obj.Topic)),
Endpoint: aws.String(obj.Endpoint),
Protocol: aws.String(string(obj.Protocol)),
}
resp, err := p.ctx.SNS().Subscribe(create)
if err != nil {
return "", err
}
contract.Assert(resp != nil)
contract.Assert(resp.SubscriptionArn != nil)
return resource.ID(*resp.SubscriptionArn), nil
}
// Get reads the instance state identified by ID, returning a populated resource object, or an error if not found.
func (p *subscriptionProvider) Get(ctx context.Context, id resource.ID) (*sns.Subscription, error) {
resp, err := p.ctx.SNS().GetSubscriptionAttributes(&awssns.GetSubscriptionAttributesInput{
SubscriptionArn: aws.String(string(id)),
})
if err != nil {
return nil, err
}
listResp, err := p.ctx.SNS().ListSubscriptionsByTopic(&awssns.ListSubscriptionsByTopicInput{
TopicArn: resp.Attributes["TopicArn"],
})
if err != nil {
return nil, err
}
var subscription *awssns.Subscription
for _, s := range listResp.Subscriptions {
if *s.SubscriptionArn == string(id) {
subscription = s
}
}
return &sns.Subscription{
Topic: resource.ID(aws.StringValue(resp.Attributes["TopicArn"])),
Endpoint: aws.StringValue(subscription.Endpoint),
Protocol: sns.Protocol(aws.StringValue(subscription.Protocol)),
}, nil
}
// InspectChange checks what impacts a hypothetical update will have on the resource's properties.
func (p *subscriptionProvider) InspectChange(ctx context.Context, id resource.ID,
old *sns.Subscription, new *sns.Subscription, diff *resource.ObjectDiff) ([]string, error) {
return nil, nil
}
// Update updates an existing resource with new values. Only those values in the provided property bag are updated
// to new values. The resource ID is returned and may be different if the resource had to be recreated.
func (p *subscriptionProvider) Update(ctx context.Context, id resource.ID,
old *sns.Subscription, new *sns.Subscription, diff *resource.ObjectDiff) error {
contract.Failf("No updatable properties on SNS Subscription")
return nil
}
// Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist.
func (p *subscriptionProvider) Delete(ctx context.Context, id resource.ID) error {
name, err := arn.ParseResourceName(id)
if err != nil {
return err
}
fmt.Printf("Deleting SNS Subscription '%v'\n", name)
_, err = p.ctx.SNS().Unsubscribe(&awssns.UnsubscribeInput{
SubscriptionArn: id.StringPtr(),
})
return err
}

View file

@ -0,0 +1,152 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package sns
import (
"crypto/sha1"
"fmt"
"regexp"
"github.com/aws/aws-sdk-go/aws"
awssns "github.com/aws/aws-sdk-go/service/sns"
"github.com/pulumi/lumi/pkg/resource"
"github.com/pulumi/lumi/sdk/go/pkg/lumirpc"
"golang.org/x/net/context"
"github.com/pulumi/lumi/lib/aws/provider/arn"
"github.com/pulumi/lumi/lib/aws/provider/awsctx"
"github.com/pulumi/lumi/lib/aws/rpc/sns"
"github.com/pulumi/lumi/pkg/util/contract"
)
const TopicToken = sns.TopicToken
// constants for the various topic limits.
const (
minTopicName = 1
maxTopicName = 256
displayNameAttributeName = "DisplayName"
)
var (
topicNameRegexp = regexp.MustCompile(`^[a-zA-Z0-9_\-]*$`)
topicNameDisallowedRegexp = regexp.MustCompile(`[^a-zA-Z0-9_\-]`)
)
// NewTopicProvider creates a provider that handles SNS topic operations.
func NewTopicProvider(ctx *awsctx.Context) lumirpc.ResourceProviderServer {
ops := &topicProvider{ctx}
return sns.NewTopicProvider(ops)
}
type topicProvider struct {
ctx *awsctx.Context
}
// Check validates that the given property bag is valid for a resource of the given type.
func (p *topicProvider) Check(ctx context.Context, obj *sns.Topic, property string) error {
switch property {
case sns.Topic_TopicName:
if name := obj.TopicName; name != nil {
if matched := topicNameRegexp.MatchString(*name); !matched {
fmt.Printf("Failed to match regexp\n")
return fmt.Errorf("did not match regexp %v", topicNameRegexp)
} else if len(*name) < minTopicName {
return fmt.Errorf("less than minimum length of %v", minTopicName)
} else if len(*name) > maxTopicName {
return fmt.Errorf("exceeded maximum length of %v", maxTopicName)
}
}
}
return nil
}
// 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 "transacational").
func (p *topicProvider) Create(ctx context.Context, obj *sns.Topic) (resource.ID, error) {
// If an explicit name is given, use it. Otherwise, auto-generate a name in part based on the resource name.
var name string
if obj.TopicName != nil {
name = *obj.TopicName
} else {
// SNS topic names have strict naming requirements. To use the Name property as a prefix, we
// need to convert it to a safe form first.
safeName := topicNameDisallowedRegexp.ReplaceAllString(*obj.Name, "-")
name = resource.NewUniqueHex(safeName+"-", maxTopicName, sha1.Size)
}
fmt.Printf("Creating SNS Topic '%v' with name '%v'\n", *obj.Name, name)
create := &awssns.CreateTopicInput{
Name: aws.String(name),
}
resp, err := p.ctx.SNS().CreateTopic(create)
if err != nil {
return "", err
}
contract.Assert(resp != nil)
contract.Assert(resp.TopicArn != nil)
if obj.DisplayName != nil {
_, err := p.ctx.SNS().SetTopicAttributes(&awssns.SetTopicAttributesInput{
TopicArn: resp.TopicArn,
AttributeName: aws.String(displayNameAttributeName),
AttributeValue: obj.DisplayName,
})
if err != nil {
return "", err
}
}
return resource.ID(*resp.TopicArn), nil
}
// Get reads the instance state identified by ID, returning a populated resource object, or an error if not found.
func (p *topicProvider) Get(ctx context.Context, id resource.ID) (*sns.Topic, error) {
name, err := arn.ParseResourceName(id)
if err != nil {
return nil, err
}
resp, err := p.ctx.SNS().GetTopicAttributes(&awssns.GetTopicAttributesInput{
TopicArn: aws.String(string(id)),
})
if err != nil {
return nil, err
}
return &sns.Topic{
TopicName: &name,
DisplayName: resp.Attributes[displayNameAttributeName],
}, nil
}
// InspectChange checks what impacts a hypothetical update will have on the resource's properties.
func (p *topicProvider) InspectChange(ctx context.Context, id resource.ID,
old *sns.Topic, new *sns.Topic, diff *resource.ObjectDiff) ([]string, error) {
return nil, nil
}
// Update updates an existing resource with new values. Only those values in the provided property bag are updated
// to new values. The resource ID is returned and may be different if the resource had to be recreated.
func (p *topicProvider) Update(ctx context.Context, id resource.ID,
old *sns.Topic, new *sns.Topic, diff *resource.ObjectDiff) error {
if diff.Changed(sns.Topic_DisplayName) {
_, err := p.ctx.SNS().SetTopicAttributes(&awssns.SetTopicAttributesInput{
TopicArn: aws.String(string(id)),
AttributeName: aws.String(displayNameAttributeName),
AttributeValue: new.DisplayName,
})
if err != nil {
return err
}
}
return nil
}
// Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist.
func (p *topicProvider) Delete(ctx context.Context, id resource.ID) error {
name, err := arn.ParseResourceName(id)
if err != nil {
return err
}
fmt.Printf("Deleting SNS Topic '%v'\n", name)
_, err = p.ctx.SNS().DeleteTopic(&awssns.DeleteTopicInput{
TopicArn: id.StringPtr(),
})
return err
}

View file

@ -16,8 +16,6 @@ import (
"github.com/pulumi/lumi/pkg/util/contract"
"github.com/pulumi/lumi/pkg/util/mapper"
"github.com/pulumi/lumi/sdk/go/pkg/lumirpc"
__sns "github.com/pulumi/lumi/lib/aws/rpc/sns"
)
/* RPC stubs for ActionTarget resource provider */
@ -78,12 +76,6 @@ func (p *ActionTargetProvider) Check(
resource.NewPropertyError("ActionTarget", "displayName", failure))
}
}
if !unks["subscription"] {
if failure := p.ops.Check(ctx, obj, "subscription"); failure != nil {
failures = append(failures,
resource.NewPropertyError("ActionTarget", "subscription", failure))
}
}
if len(failures) > 0 {
return plugin.NewCheckResponse(resource.NewErrors(failures)), nil
}
@ -208,7 +200,6 @@ type ActionTarget struct {
Name *string `lumi:"name,optional"`
TopicName *string `lumi:"topicName,optional"`
DisplayName *string `lumi:"displayName,optional"`
Subscription *[]__sns.TopicSubscription `lumi:"subscription,optional"`
}
// ActionTarget's properties have constants to make dealing with diffs and property bags easier.
@ -216,7 +207,6 @@ const (
ActionTarget_Name = "name"
ActionTarget_TopicName = "topicName"
ActionTarget_DisplayName = "displayName"
ActionTarget_Subscription = "subscription"
)
/* RPC stubs for Alarm resource provider */

View file

@ -0,0 +1,242 @@
// *** WARNING: this file was generated by the Lumi IDL Compiler (LUMIDL). ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
package sns
import (
"errors"
pbempty "github.com/golang/protobuf/ptypes/empty"
pbstruct "github.com/golang/protobuf/ptypes/struct"
"golang.org/x/net/context"
"github.com/pulumi/lumi/pkg/resource"
"github.com/pulumi/lumi/pkg/resource/plugin"
"github.com/pulumi/lumi/pkg/tokens"
"github.com/pulumi/lumi/pkg/util/contract"
"github.com/pulumi/lumi/pkg/util/mapper"
"github.com/pulumi/lumi/sdk/go/pkg/lumirpc"
)
/* RPC stubs for Subscription resource provider */
// SubscriptionToken is the type token corresponding to the Subscription package type.
const SubscriptionToken = tokens.Type("aws:sns/subscription:Subscription")
// SubscriptionProviderOps is a pluggable interface for Subscription-related management functionality.
type SubscriptionProviderOps interface {
Check(ctx context.Context, obj *Subscription, property string) error
Create(ctx context.Context, obj *Subscription) (resource.ID, error)
Get(ctx context.Context, id resource.ID) (*Subscription, error)
InspectChange(ctx context.Context,
id resource.ID, old *Subscription, new *Subscription, diff *resource.ObjectDiff) ([]string, error)
Update(ctx context.Context,
id resource.ID, old *Subscription, new *Subscription, diff *resource.ObjectDiff) error
Delete(ctx context.Context, id resource.ID) error
}
// SubscriptionProvider is a dynamic gRPC-based plugin for managing Subscription resources.
type SubscriptionProvider struct {
ops SubscriptionProviderOps
}
// NewSubscriptionProvider allocates a resource provider that delegates to a ops instance.
func NewSubscriptionProvider(ops SubscriptionProviderOps) lumirpc.ResourceProviderServer {
contract.Assert(ops != nil)
return &SubscriptionProvider{ops: ops}
}
func (p *SubscriptionProvider) Check(
ctx context.Context, req *lumirpc.CheckRequest) (*lumirpc.CheckResponse, error) {
contract.Assert(req.GetType() == string(SubscriptionToken))
obj, _, err := p.Unmarshal(req.GetProperties())
if err != nil {
return plugin.NewCheckResponse(err), nil
}
var failures []error
unks := req.GetUnknowns()
if !unks["name"] {
if failure := p.ops.Check(ctx, obj, "name"); failure != nil {
failures = append(failures,
resource.NewPropertyError("Subscription", "name", failure))
}
}
if !unks["topic"] {
if failure := p.ops.Check(ctx, obj, "topic"); failure != nil {
failures = append(failures,
resource.NewPropertyError("Subscription", "topic", failure))
}
}
if !unks["protocol"] {
if failure := p.ops.Check(ctx, obj, "protocol"); failure != nil {
failures = append(failures,
resource.NewPropertyError("Subscription", "protocol", failure))
}
}
if !unks["endpoint"] {
if failure := p.ops.Check(ctx, obj, "endpoint"); failure != nil {
failures = append(failures,
resource.NewPropertyError("Subscription", "endpoint", failure))
}
}
if len(failures) > 0 {
return plugin.NewCheckResponse(resource.NewErrors(failures)), nil
}
return plugin.NewCheckResponse(nil), nil
}
func (p *SubscriptionProvider) Name(
ctx context.Context, req *lumirpc.NameRequest) (*lumirpc.NameResponse, error) {
contract.Assert(req.GetType() == string(SubscriptionToken))
obj, _, err := p.Unmarshal(req.GetProperties())
if err != nil {
return nil, err
}
if obj.Name == nil || *obj.Name == "" {
if req.Unknowns[Subscription_Name] {
return nil, errors.New("Name property cannot be computed from unknown outputs")
}
return nil, errors.New("Name property cannot be empty")
}
return &lumirpc.NameResponse{Name: *obj.Name}, nil
}
func (p *SubscriptionProvider) Create(
ctx context.Context, req *lumirpc.CreateRequest) (*lumirpc.CreateResponse, error) {
contract.Assert(req.GetType() == string(SubscriptionToken))
obj, _, err := p.Unmarshal(req.GetProperties())
if err != nil {
return nil, err
}
id, err := p.ops.Create(ctx, obj)
if err != nil {
return nil, err
}
return &lumirpc.CreateResponse{Id: string(id)}, nil
}
func (p *SubscriptionProvider) Get(
ctx context.Context, req *lumirpc.GetRequest) (*lumirpc.GetResponse, error) {
contract.Assert(req.GetType() == string(SubscriptionToken))
id := resource.ID(req.GetId())
obj, err := p.ops.Get(ctx, id)
if err != nil {
return nil, err
}
return &lumirpc.GetResponse{
Properties: plugin.MarshalProperties(
nil, resource.NewPropertyMap(obj), plugin.MarshalOptions{}),
}, nil
}
func (p *SubscriptionProvider) InspectChange(
ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) {
contract.Assert(req.GetType() == string(SubscriptionToken))
id := resource.ID(req.GetId())
old, oldprops, err := p.Unmarshal(req.GetOlds())
if err != nil {
return nil, err
}
new, newprops, err := p.Unmarshal(req.GetNews())
if err != nil {
return nil, err
}
var replaces []string
diff := oldprops.Diff(newprops)
if diff != nil {
if diff.Changed("name") {
replaces = append(replaces, "name")
}
if diff.Changed("topic") {
replaces = append(replaces, "topic")
}
if diff.Changed("protocol") {
replaces = append(replaces, "protocol")
}
if diff.Changed("endpoint") {
replaces = append(replaces, "endpoint")
}
}
more, err := p.ops.InspectChange(ctx, id, old, new, diff)
if err != nil {
return nil, err
}
return &lumirpc.InspectChangeResponse{
Replaces: append(replaces, more...),
}, err
}
func (p *SubscriptionProvider) Update(
ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) {
contract.Assert(req.GetType() == string(SubscriptionToken))
id := resource.ID(req.GetId())
old, oldprops, err := p.Unmarshal(req.GetOlds())
if err != nil {
return nil, err
}
new, newprops, err := p.Unmarshal(req.GetNews())
if err != nil {
return nil, err
}
diff := oldprops.Diff(newprops)
if err := p.ops.Update(ctx, id, old, new, diff); err != nil {
return nil, err
}
return &pbempty.Empty{}, nil
}
func (p *SubscriptionProvider) Delete(
ctx context.Context, req *lumirpc.DeleteRequest) (*pbempty.Empty, error) {
contract.Assert(req.GetType() == string(SubscriptionToken))
id := resource.ID(req.GetId())
if err := p.ops.Delete(ctx, id); err != nil {
return nil, err
}
return &pbempty.Empty{}, nil
}
func (p *SubscriptionProvider) Unmarshal(
v *pbstruct.Struct) (*Subscription, resource.PropertyMap, error) {
var obj Subscription
props := plugin.UnmarshalProperties(nil, v, plugin.MarshalOptions{RawResources: true})
return &obj, props, mapper.MapIU(props.Mappable(), &obj)
}
/* Marshalable Subscription structure(s) */
// Subscription is a marshalable representation of its corresponding IDL type.
type Subscription struct {
Name *string `lumi:"name,optional"`
Topic resource.ID `lumi:"topic"`
Protocol Protocol `lumi:"protocol"`
Endpoint string `lumi:"endpoint"`
}
// Subscription's properties have constants to make dealing with diffs and property bags easier.
const (
Subscription_Name = "name"
Subscription_Topic = "topic"
Subscription_Protocol = "protocol"
Subscription_Endpoint = "endpoint"
)
/* Typedefs */
type (
Protocol string
)
/* Constants */
const (
ApplicationSubscription Protocol = "application"
EmailJSONSubscription Protocol = "email-json"
EmailSubscription Protocol = "email"
HTTPSSubscription Protocol = "https"
HTTSubscription Protocol = "http"
LambdaSubscription Protocol = "lambda"
SMSSubscription Protocol = "sms"
SQSSubscription Protocol = "sqs"
)

View file

@ -76,12 +76,6 @@ func (p *TopicProvider) Check(
resource.NewPropertyError("Topic", "displayName", failure))
}
}
if !unks["subscription"] {
if failure := p.ops.Check(ctx, obj, "subscription"); failure != nil {
failures = append(failures,
resource.NewPropertyError("Topic", "subscription", failure))
}
}
if len(failures) > 0 {
return plugin.NewCheckResponse(resource.NewErrors(failures)), nil
}
@ -206,7 +200,6 @@ type Topic struct {
Name *string `lumi:"name,optional"`
TopicName *string `lumi:"topicName,optional"`
DisplayName *string `lumi:"displayName,optional"`
Subscription *[]TopicSubscription `lumi:"subscription,optional"`
}
// Topic's properties have constants to make dealing with diffs and property bags easier.
@ -214,40 +207,6 @@ const (
Topic_Name = "name"
Topic_TopicName = "topicName"
Topic_DisplayName = "displayName"
Topic_Subscription = "subscription"
)
/* Marshalable TopicSubscription structure(s) */
// TopicSubscription is a marshalable representation of its corresponding IDL type.
type TopicSubscription struct {
Protocol TopicProtocol `lumi:"protocol"`
Endpoint string `lumi:"endpoint"`
}
// TopicSubscription's properties have constants to make dealing with diffs and property bags easier.
const (
TopicSubscription_Protocol = "protocol"
TopicSubscription_Endpoint = "endpoint"
)
/* Typedefs */
type (
TopicProtocol string
)
/* Constants */
const (
ApplicationTopic TopicProtocol = "application"
EmailJSONTopic TopicProtocol = "email-json"
EmailTopic TopicProtocol = "email"
HTTPSTopic TopicProtocol = "https"
HTTPTopic TopicProtocol = "http"
LambdaTopic TopicProtocol = "lambda"
SMSTopic TopicProtocol = "sms"
SQSTopic TopicProtocol = "sqs"
)