Merge pull request #273 from pulumi/logs

Support for AWS Cloudwatch Logs
This commit is contained in:
Luke Hoban 2017-06-28 17:49:41 -07:00 committed by GitHub
commit 2ec62d0b9a
16 changed files with 1068 additions and 15 deletions

View file

@ -2,6 +2,7 @@
import * as aws from "@lumi/aws";
import * as lumi from "@lumi/lumi";
let region = aws.config.requireRegion();
///////////////////
// Lambda Function
@ -28,7 +29,7 @@ let role = new aws.iam.Role("mylambdarole", {
let lambda = new aws.lambda.Function("mylambda", {
code: new lumi.asset.AssetArchive({
"index.js": new lumi.asset.String(
"exports.handler = (e, c, cb) => cb({statusCode: 200, body: 'Hello, world!'});",
"exports.handler = (e, c, cb) => cb(null, {statusCode: 200, body: 'Hello, world!'});",
),
}),
role: role,
@ -36,6 +37,37 @@ let lambda = new aws.lambda.Function("mylambda", {
runtime: aws.lambda.NodeJS6d10Runtime,
});
///////////////////
// Logging
///////////////////
let logGroup = new aws.cloudwatch.LogGroup("mylambda-logs", {
logGroupName: "/aws/lambda/" + lambda.functionName,
retentionInDays: 7,
});
let logcollector = new aws.lambda.Function("mylambda-logcollector", {
code: new lumi.asset.AssetArchive({
"index.js": new lumi.asset.String(
"exports.handler = (e, c, cb) => console.log(e);",
),
}),
role: role,
handler: "index.handler",
runtime: aws.lambda.NodeJS6d10Runtime,
});
let permission = new aws.lambda.Permission("logcollector-permission", {
action: "lambda:InvokeFunction",
principal: "logs." + region + ".amazonaws.com",
sourceARN: logGroup.id + ":*",
function: logcollector,
});
let logSubscription = new aws.cloudwatch.LogSubscriptionFilter("logsubscription", {
destinationArn: logcollector.id,
logGroupName: logGroup.logGroupName!,
filterPattern: "",
});
///////////////////
// DynamoDB Table
@ -51,11 +83,9 @@ let music = new aws.dynamodb.Table("music", {
writeCapacity: 1,
});
///////////////////
// APIGateway RestAPI
///////////////////
let region = aws.config.requireRegion();
let swaggerSpec = {
swagger: "2.0",

View file

@ -0,0 +1,17 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package cloudwatch
import (
"github.com/pulumi/lumi/pkg/resource/idl"
)
// LogGroup is a CloudWatch Logs log group. For more information, see
// http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html.
type LogGroup struct {
idl.NamedResource
// The name of the log group.
LogGroupName *string `lumi:"logGroupName,optional,replaces"`
// The number of days log events are kept in CloudWatch Logs. When a log event expires, CloudWatch Logs automatically deletes it.
RetentionInDays *float64 `lumi:"retentionInDays,optional"`
}

View file

@ -0,0 +1,37 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package cloudwatch
import (
aws "github.com/pulumi/lumi/lib/aws/idl"
"github.com/pulumi/lumi/pkg/resource/idl"
)
// LogSubscriptionFilter is a CloudWatch Logs subscription filter. For more information, see
// http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CreateSubscriptionFilter.html.
type LogSubscriptionFilter struct {
idl.NamedResource
// The name of the log group.
LogGroupName string `lumi:"logGroupName,replaces"`
// A filter pattern for subscribing to a filtered stream of log events.
FilterPattern string `lumi:"filterPattern"`
// The ARN of the destination to deliver matching log events to.
DestinationArn string `lumi:"destinationArn,replaces"`
// The ARN of an IAM role that grants CloudWatch Logs permissions to deliver ingested log events to the
// destination stream. You don't need to provide the ARN when you are working with a logical destination for
// cross-account delivery.
RoleARN *aws.ARN `lumi:"roleArn,optional"`
// The method used to distribute log data to the destination, when the destination is an Amazon Kinesis stream.
// By default, log data is grouped by log stream. For a more even distribution, you can group log data randomly.
Distribution *LogSubscriptionDistribution `lumi:"distribution,optional"`
// The time the log group subscription gilter was created.
CreationTime *float64 `lumi:"creationTime,out"`
}
type LogSubscriptionDistribution string
const (
RandomDistribution LogSubscriptionDistribution = "Random"
ByLogStreamDistribution LogSubscriptionDistribution = "ByLogStream"
)

View file

@ -1,4 +1,6 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
export * from "./alarm";
export * from "./logGroup";
export * from "./logSubscriptionFilter";

View file

@ -0,0 +1,32 @@
// *** 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";
export class LogGroup extends lumi.NamedResource implements LogGroupArgs {
public readonly logGroupName?: string;
public retentionInDays?: number;
public static get(id: lumi.ID): LogGroup {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): LogGroup[] {
return <any>undefined; // functionality provided by the runtime
}
constructor(name: string, args?: LogGroupArgs) {
super(name);
if (args !== undefined) {
this.logGroupName = args.logGroupName;
this.retentionInDays = args.retentionInDays;
}
}
}
export interface LogGroupArgs {
readonly logGroupName?: string;
retentionInDays?: number;
}

View file

@ -0,0 +1,58 @@
// *** 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 {ARN} from "../types";
export let ByLogStreamDistribution: LogSubscriptionDistribution = "ByLogStream";
export let RandomDistribution: LogSubscriptionDistribution = "Random";
export type LogSubscriptionDistribution =
"ByLogStream" |
"Random";
export class LogSubscriptionFilter extends lumi.NamedResource implements LogSubscriptionFilterArgs {
public readonly logGroupName: string;
public filterPattern: string;
public readonly destinationArn: string;
public roleArn?: ARN;
public distribution?: LogSubscriptionDistribution;
public creationTime: number;
public static get(id: lumi.ID): LogSubscriptionFilter {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): LogSubscriptionFilter[] {
return <any>undefined; // functionality provided by the runtime
}
constructor(name: string, args: LogSubscriptionFilterArgs) {
super(name);
if (args.logGroupName === undefined) {
throw new Error("Missing required argument 'logGroupName'");
}
this.logGroupName = args.logGroupName;
if (args.filterPattern === undefined) {
throw new Error("Missing required argument 'filterPattern'");
}
this.filterPattern = args.filterPattern;
if (args.destinationArn === undefined) {
throw new Error("Missing required argument 'destinationArn'");
}
this.destinationArn = args.destinationArn;
this.roleArn = args.roleArn;
this.distribution = args.distribution;
}
}
export interface LogSubscriptionFilterArgs {
readonly logGroupName: string;
filterPattern: string;
readonly destinationArn: string;
roleArn?: ARN;
distribution?: LogSubscriptionDistribution;
}

View file

@ -8,6 +8,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/apigateway"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/elasticbeanstalk"
@ -33,6 +34,7 @@ type Context struct {
// per-service connections (lazily allocated and reused);
apigateway *apigateway.APIGateway
cloudwatchlogs *cloudwatchlogs.CloudWatchLogs
dynamodb *dynamodb.DynamoDB
ec2 *ec2.EC2
elasticbeanstalk *elasticbeanstalk.ElasticBeanstalk
@ -106,6 +108,14 @@ func (ctx *Context) APIGateway() *apigateway.APIGateway {
return ctx.apigateway
}
func (ctx *Context) CloudwatchLogs() *cloudwatchlogs.CloudWatchLogs {
contract.Assert(ctx.sess != nil)
if ctx.cloudwatchlogs == nil {
ctx.cloudwatchlogs = cloudwatchlogs.New(ctx.sess)
}
return ctx.cloudwatchlogs
}
func (ctx *Context) DynamoDB() *dynamodb.DynamoDB {
contract.Assert(ctx.sess != nil)
if ctx.dynamodb == nil {

View file

@ -0,0 +1,175 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package cloudwatch
import (
"crypto/sha1"
"fmt"
"regexp"
"github.com/aws/aws-sdk-go/aws"
awscloudwatch "github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/pkg/errors"
"github.com/pulumi/lumi/pkg/resource"
"github.com/pulumi/lumi/pkg/util/convutil"
"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/cloudwatch"
)
const LogGroupToken = cloudwatch.LogGroupToken
// constants for the various logGroup limits.
const (
minLogGroupName = 1
maxLogGroupName = 512
)
var (
logGroupNameRegexp = regexp.MustCompile(`^[\.\-_/#A-Za-z0-9]+$`)
)
// NewLogGroupProvider creates a provider that handles Cloudwatch LogGroup operations.
func NewLogGroupProvider(ctx *awsctx.Context) lumirpc.ResourceProviderServer {
ops := &logGroupProvider{ctx}
return cloudwatch.NewLogGroupProvider(ops)
}
type logGroupProvider struct {
ctx *awsctx.Context
}
// Check validates that the given property bag is valid for a resource of the given type.
func (p *logGroupProvider) Check(ctx context.Context, obj *cloudwatch.LogGroup,
property string) error {
switch property {
case cloudwatch.LogGroup_LogGroupName:
if obj.LogGroupName != nil {
if len(*obj.LogGroupName) < minLogGroupName {
return fmt.Errorf("less than minimum length of %v", minLogGroupName)
} else if len(*obj.LogGroupName) > maxLogGroupName {
return fmt.Errorf("exceeded the maximum length of %v", maxLogGroupName)
} else if !logGroupNameRegexp.MatchString(*obj.LogGroupName) {
return fmt.Errorf("contains invalid characters (must match '%v')", logGroupNameRegexp)
}
}
case cloudwatch.LogGroup_RetentionInDays:
if obj.RetentionInDays != nil {
switch int(*obj.RetentionInDays) {
case 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653:
default:
return fmt.Errorf("not an allowed value for retentionInDays '%v'", int(*obj.RetentionInDays))
}
}
}
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 *logGroupProvider) Create(ctx context.Context,
obj *cloudwatch.LogGroup) (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.LogGroupName != nil {
name = *obj.LogGroupName
} else {
name = resource.NewUniqueHex(*obj.Name+"-", maxLogGroupName, sha1.Size)
}
_, err := p.ctx.CloudwatchLogs().CreateLogGroup(&awscloudwatch.CreateLogGroupInput{
LogGroupName: aws.String(name),
})
if err != nil {
return "", err
}
if obj.RetentionInDays != nil {
_, err := p.ctx.CloudwatchLogs().PutRetentionPolicy(&awscloudwatch.PutRetentionPolicyInput{
LogGroupName: aws.String(name),
RetentionInDays: convutil.Float64PToInt64P(obj.RetentionInDays),
})
if err != nil {
return "", err
}
}
return arn.NewResourceID("logs", p.ctx.Region(), p.ctx.AccountID(), "log-group", name), nil
}
// Get reads the instance state identified by ID, returning a populated resource object, or an error if not found.
func (p *logGroupProvider) Get(ctx context.Context,
id resource.ID) (*cloudwatch.LogGroup, error) {
logGroupName, err := arn.ParseResourceName(id)
if err != nil {
return nil, err
}
resp, err := p.ctx.CloudwatchLogs().DescribeLogGroups(&awscloudwatch.DescribeLogGroupsInput{
LogGroupNamePrefix: aws.String(logGroupName),
})
if err != nil {
return nil, err
} else if resp == nil {
return nil, errors.New("Cloudwatch query returned an empty response")
}
var logGroup *awscloudwatch.LogGroup
for _, group := range resp.LogGroups {
if *group.LogGroupName == logGroupName {
logGroup = group
}
}
if logGroup == nil {
return nil, nil
}
return &cloudwatch.LogGroup{
LogGroupName: aws.String(logGroupName),
RetentionInDays: convutil.Int64PToFloat64P(logGroup.RetentionInDays),
}, nil
}
// InspectChange checks what impacts a hypothetical update will have on the resource's properties.
func (p *logGroupProvider) InspectChange(ctx context.Context, id resource.ID,
old *cloudwatch.LogGroup, new *cloudwatch.LogGroup,
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 *logGroupProvider) Update(ctx context.Context, id resource.ID,
old *cloudwatch.LogGroup, new *cloudwatch.LogGroup, diff *resource.ObjectDiff) error {
logGroupName, err := arn.ParseResourceName(id)
if err != nil {
return err
}
if diff.Changed(cloudwatch.LogGroup_RetentionInDays) {
if new.RetentionInDays != nil {
_, err := p.ctx.CloudwatchLogs().PutRetentionPolicy(&awscloudwatch.PutRetentionPolicyInput{
LogGroupName: aws.String(logGroupName),
RetentionInDays: convutil.Float64PToInt64P(new.RetentionInDays),
})
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 *logGroupProvider) Delete(ctx context.Context, id resource.ID) error {
logGroupName, err := arn.ParseResourceName(id)
if err != nil {
return err
}
fmt.Printf("Deleting Cloudwatch LogGroup '%v'\n", id)
_, err = p.ctx.CloudwatchLogs().DeleteLogGroup(&awscloudwatch.DeleteLogGroupInput{
LogGroupName: aws.String(logGroupName),
})
return err
}

View file

@ -0,0 +1,169 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package cloudwatch
import (
"crypto/sha1"
"fmt"
"strings"
"github.com/aws/aws-sdk-go/aws"
awscloudwatch "github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/pkg/errors"
"github.com/pulumi/lumi/pkg/resource"
"github.com/pulumi/lumi/pkg/util/contract"
"github.com/pulumi/lumi/pkg/util/convutil"
"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"
awscommon "github.com/pulumi/lumi/lib/aws/rpc"
"github.com/pulumi/lumi/lib/aws/rpc/cloudwatch"
)
const LogSubscriptionFilterToken = cloudwatch.LogSubscriptionFilterToken
// constants for the various logSubscriptionFilter limits.
const (
maxLogSubscriptionFilterName = 512
)
// NewLogSubscriptionFilterProvider creates a provider that handles Cloudwatch LogSubscriptionFilter operations.
func NewLogSubscriptionFilterProvider(ctx *awsctx.Context) lumirpc.ResourceProviderServer {
ops := &logSubscriptionFilterProvider{ctx}
return cloudwatch.NewLogSubscriptionFilterProvider(ops)
}
type logSubscriptionFilterProvider struct {
ctx *awsctx.Context
}
func (p *logSubscriptionFilterProvider) newLogSubscriptionFilterID(logGroupName string,
filterName string) resource.ID {
return arn.NewResourceID("logs", p.ctx.Region(), p.ctx.AccountID(), "log-group",
logGroupName+":subscription-filter:"+filterName)
}
func (p *logSubscriptionFilterProvider) parseLogSubscriptionFilterID(id resource.ID) (string, string, error) {
parts, err := arn.ARN(id).Parse()
if err != nil {
return "", "", err
}
resParts := strings.Split(parts.Resource, ":")
contract.Assert(len(resParts) == 4)
logGroupName := resParts[1]
filterName := resParts[3]
return logGroupName, filterName, nil
}
// Check validates that the given property bag is valid for a resource of the given type.
func (p *logSubscriptionFilterProvider) Check(ctx context.Context, obj *cloudwatch.LogSubscriptionFilter,
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 *logSubscriptionFilterProvider) Create(ctx context.Context,
obj *cloudwatch.LogSubscriptionFilter) (resource.ID, error) {
name := resource.NewUniqueHex(*obj.Name+"-", maxLogSubscriptionFilterName, sha1.Size)
var roleArn *string
if obj.RoleARN != nil {
tmp := string(*obj.RoleARN)
roleArn = &tmp
}
var distribution *string
if obj.Distribution != nil {
tmp := string(*obj.Distribution)
distribution = &tmp
}
filter := &awscloudwatch.PutSubscriptionFilterInput{
FilterName: aws.String(name),
LogGroupName: aws.String(obj.LogGroupName),
DestinationArn: aws.String(obj.DestinationArn),
FilterPattern: aws.String(obj.FilterPattern),
RoleArn: roleArn,
Distribution: distribution,
}
_, err := p.ctx.CloudwatchLogs().PutSubscriptionFilter(filter)
if err != nil {
return "", err
}
return p.newLogSubscriptionFilterID(obj.LogGroupName, name), nil
}
// Get reads the instance state identified by ID, returning a populated resource object, or an error if not found.
func (p *logSubscriptionFilterProvider) Get(ctx context.Context,
id resource.ID) (*cloudwatch.LogSubscriptionFilter, error) {
logGroupName, filterName, err := p.parseLogSubscriptionFilterID(id)
if err != nil {
return nil, err
}
resp, err := p.ctx.CloudwatchLogs().DescribeSubscriptionFilters(&awscloudwatch.DescribeSubscriptionFiltersInput{
LogGroupName: aws.String(logGroupName),
})
if err != nil {
return nil, err
} else if resp == nil {
return nil, errors.New("Cloudwatch query returned an empty response")
} else if len(resp.SubscriptionFilters) == 0 {
return nil, nil
} else if len(resp.SubscriptionFilters) > 1 {
return nil, errors.New("Only one subscription filter expected per log group")
}
filter := resp.SubscriptionFilters[0]
contract.Assert(*filter.FilterName == filterName)
var distribution *cloudwatch.LogSubscriptionDistribution
if filter.Distribution != nil {
tmp := cloudwatch.LogSubscriptionDistribution(*filter.Distribution)
distribution = &tmp
}
var roleARN *awscommon.ARN
if filter.RoleArn != nil {
tmp := awscommon.ARN(*filter.RoleArn)
roleARN = &tmp
}
return &cloudwatch.LogSubscriptionFilter{
LogGroupName: logGroupName,
DestinationArn: aws.StringValue(filter.DestinationArn),
CreationTime: convutil.Int64PToFloat64P(filter.CreationTime),
Distribution: distribution,
RoleARN: roleARN,
}, nil
}
// InspectChange checks what impacts a hypothetical update will have on the resource's properties.
func (p *logSubscriptionFilterProvider) InspectChange(ctx context.Context, id resource.ID,
old *cloudwatch.LogSubscriptionFilter, new *cloudwatch.LogSubscriptionFilter,
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 *logSubscriptionFilterProvider) Update(ctx context.Context, id resource.ID,
old *cloudwatch.LogSubscriptionFilter, new *cloudwatch.LogSubscriptionFilter, diff *resource.ObjectDiff) error {
contract.Failf("Not yet implemented - log subscription filter update")
return nil
}
// Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist.
func (p *logSubscriptionFilterProvider) Delete(ctx context.Context, id resource.ID) error {
logGroupName, filterName, err := p.parseLogSubscriptionFilterID(id)
if err != nil {
return err
}
fmt.Printf("Deleting Cloudwatch LogSubscriptionFilter '%v'\n", id)
_, err = p.ctx.CloudwatchLogs().DeleteSubscriptionFilter(&awscloudwatch.DeleteSubscriptionFilterInput{
LogGroupName: aws.String(logGroupName),
FilterName: aws.String(filterName),
})
return err
}

View file

@ -13,9 +13,11 @@ import (
awsiam "github.com/aws/aws-sdk-go/service/iam"
awslambda "github.com/aws/aws-sdk-go/service/lambda"
"github.com/pulumi/lumi/lib/aws/provider/awsctx"
cloudwatchprovider "github.com/pulumi/lumi/lib/aws/provider/cloudwatch"
iamprovider "github.com/pulumi/lumi/lib/aws/provider/iam"
"github.com/pulumi/lumi/lib/aws/provider/testutil"
rpc "github.com/pulumi/lumi/lib/aws/rpc"
"github.com/pulumi/lumi/lib/aws/rpc/cloudwatch"
"github.com/pulumi/lumi/lib/aws/rpc/iam"
"github.com/pulumi/lumi/lib/aws/rpc/lambda"
"github.com/pulumi/lumi/pkg/resource"
@ -26,11 +28,11 @@ func Test(t *testing.T) {
t.Parallel()
prefix := resource.NewUniqueHex("lumitest", 20, 20)
ctx := testutil.CreateContext(t)
awsctx := testutil.CreateContext(t)
defer func() {
funcerr := cleanupFunctions(prefix, ctx)
funcerr := cleanupFunctions(prefix, awsctx)
assert.Nil(t, funcerr)
roleerr := cleanupRoles(prefix, ctx)
roleerr := cleanupRoles(prefix, awsctx)
assert.Nil(t, roleerr)
}()
@ -44,9 +46,14 @@ func Test(t *testing.T) {
}
resources := map[string]testutil.Resource{
"role": {Provider: iamprovider.NewRoleProvider(ctx), Token: iam.RoleToken},
"f": {Provider: NewFunctionProvider(ctx), Token: FunctionToken},
"permission": {Provider: NewPermissionProvider(ctx), Token: PermissionToken},
"role": {Provider: iamprovider.NewRoleProvider(awsctx), Token: iam.RoleToken},
"f": {Provider: NewFunctionProvider(awsctx), Token: FunctionToken},
"logcollector": {Provider: NewFunctionProvider(awsctx), Token: FunctionToken},
"permission": {Provider: NewPermissionProvider(awsctx), Token: PermissionToken},
"loggroup": {Provider: cloudwatchprovider.NewLogGroupProvider(awsctx),
Token: cloudwatchprovider.LogGroupToken},
"filter": {Provider: cloudwatchprovider.NewLogSubscriptionFilterProvider(awsctx),
Token: cloudwatchprovider.LogSubscriptionFilterToken},
}
steps := []testutil.Step{
{
@ -86,19 +93,53 @@ func Test(t *testing.T) {
}
},
},
testutil.ResourceGenerator{
Name: "logcollector",
Creator: func(ctx testutil.Context) interface{} {
return &lambda.Function{
Name: aws.String(prefix),
Code: code,
Handler: "index.handler",
Runtime: lambda.NodeJS6d10Runtime,
Role: ctx.GetResourceID("role"),
}
},
},
testutil.ResourceGenerator{
Name: "loggroup",
Creator: func(ctx testutil.Context) interface{} {
return &cloudwatch.LogGroup{
Name: aws.String(prefix),
LogGroupName: aws.String("/aws/lambda/" +
ctx.GetOutputProps("f").Fields["functionName"].GetStringValue()),
RetentionInDays: aws.Float64(float64(7)),
}
},
},
testutil.ResourceGenerator{
Name: "permission",
Creator: func(ctx testutil.Context) interface{} {
sourceARN = rpc.ARN(string(ctx.GetResourceID("loggroup")) + ":*")
return &lambda.Permission{
Name: aws.String(prefix),
Function: ctx.GetResourceID("f"),
Function: ctx.GetResourceID("logcollector"),
Action: "lambda:InvokeFunction",
Principal: "s3.amazonaws.com",
SourceAccount: aws.String("111111111111"),
Principal: "logs." + awsctx.Region() + ".amazonaws.com",
SourceAccount: aws.String(awsctx.AccountID()),
SourceARN: &sourceARN,
}
},
},
testutil.ResourceGenerator{
Name: "filter",
Creator: func(ctx testutil.Context) interface{} {
return &cloudwatch.LogSubscriptionFilter{
Name: aws.String(prefix),
DestinationArn: string(ctx.GetResourceID("logcollector")),
LogGroupName: ctx.GetOutputProps("loggroup").Fields["logGroupName"].GetStringValue(),
}
},
},
},
}

View file

@ -29,9 +29,9 @@ const (
)
var (
actionRegexp = regexp.MustCompile(`(lambda:[*]|lambda:[a-zA-Z]+|[*])`)
sourceAccountRegexp = regexp.MustCompile(`\d{12}`)
sourceARNRegexp = regexp.MustCompile(`arn:aws:([a-zA-Z0-9\-])+:([a-z]{2}-[a-z]+-\d{1})?:(\d{12})?:(.*)`)
actionRegexp = regexp.MustCompile(`^(lambda:[*]|lambda:[a-zA-Z]+|[*])$`)
sourceAccountRegexp = regexp.MustCompile(`^\d{12}$`)
sourceARNRegexp = regexp.MustCompile(`^arn:aws:([a-zA-Z0-9\-])+:([a-z]{2}-[a-z]+-\d{1})?:(\d{12})?:(.*)$`)
)
type policy struct {

View file

@ -13,6 +13,7 @@ import (
"github.com/pulumi/lumi/lib/aws/provider/apigateway"
"github.com/pulumi/lumi/lib/aws/provider/awsctx"
"github.com/pulumi/lumi/lib/aws/provider/cloudwatch"
"github.com/pulumi/lumi/lib/aws/provider/dynamodb"
"github.com/pulumi/lumi/lib/aws/provider/ec2"
"github.com/pulumi/lumi/lib/aws/provider/elasticbeanstalk"
@ -38,6 +39,8 @@ func NewProvider(host *provider.HostClient) (*Provider, error) {
apigateway.DeploymentToken: apigateway.NewDeploymentProvider(ctx),
apigateway.RestAPIToken: apigateway.NewRestAPIProvider(ctx),
apigateway.StageToken: apigateway.NewStageProvider(ctx),
cloudwatch.LogGroupToken: cloudwatch.NewLogGroupProvider(ctx),
cloudwatch.LogSubscriptionFilterToken: cloudwatch.NewLogSubscriptionFilterProvider(ctx),
dynamodb.TableToken: dynamodb.NewTableProvider(ctx),
ec2.InstanceToken: ec2.NewInstanceProvider(ctx),
ec2.SecurityGroupToken: ec2.NewSecurityGroupProvider(ctx),

View file

@ -17,6 +17,7 @@ import (
type Context interface {
GetResourceID(name string) resource.ID
GetOutputProps(name string) *structpb.Struct
}
type Resource struct {
@ -132,6 +133,13 @@ func (p *providerTest) GetResourceID(name string) resource.ID {
return resource.ID("")
}
func (p *providerTest) GetOutputProps(name string) *structpb.Struct {
if props, ok := p.outProps[name]; ok {
return props
}
return nil
}
var _ Context = &providerTest{}
func createResource(t *testing.T, res interface{}, provider lumirpc.ResourceProviderServer,

View file

@ -0,0 +1,212 @@
// *** 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 cloudwatch
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 LogGroup resource provider */
// LogGroupToken is the type token corresponding to the LogGroup package type.
const LogGroupToken = tokens.Type("aws:cloudwatch/logGroup:LogGroup")
// LogGroupProviderOps is a pluggable interface for LogGroup-related management functionality.
type LogGroupProviderOps interface {
Check(ctx context.Context, obj *LogGroup, property string) error
Create(ctx context.Context, obj *LogGroup) (resource.ID, error)
Get(ctx context.Context, id resource.ID) (*LogGroup, error)
InspectChange(ctx context.Context,
id resource.ID, old *LogGroup, new *LogGroup, diff *resource.ObjectDiff) ([]string, error)
Update(ctx context.Context,
id resource.ID, old *LogGroup, new *LogGroup, diff *resource.ObjectDiff) error
Delete(ctx context.Context, id resource.ID) error
}
// LogGroupProvider is a dynamic gRPC-based plugin for managing LogGroup resources.
type LogGroupProvider struct {
ops LogGroupProviderOps
}
// NewLogGroupProvider allocates a resource provider that delegates to a ops instance.
func NewLogGroupProvider(ops LogGroupProviderOps) lumirpc.ResourceProviderServer {
contract.Assert(ops != nil)
return &LogGroupProvider{ops: ops}
}
func (p *LogGroupProvider) Check(
ctx context.Context, req *lumirpc.CheckRequest) (*lumirpc.CheckResponse, error) {
contract.Assert(req.GetType() == string(LogGroupToken))
obj, _, err := p.Unmarshal(req.GetProperties())
if err != nil {
return plugin.NewCheckResponse(err), nil
}
var failures []error
if failure := p.ops.Check(ctx, obj, ""); failure != nil {
failures = append(failures, failure)
}
unks := req.GetUnknowns()
if !unks["name"] {
if failure := p.ops.Check(ctx, obj, "name"); failure != nil {
failures = append(failures,
resource.NewPropertyError("LogGroup", "name", failure))
}
}
if !unks["logGroupName"] {
if failure := p.ops.Check(ctx, obj, "logGroupName"); failure != nil {
failures = append(failures,
resource.NewPropertyError("LogGroup", "logGroupName", failure))
}
}
if !unks["retentionInDays"] {
if failure := p.ops.Check(ctx, obj, "retentionInDays"); failure != nil {
failures = append(failures,
resource.NewPropertyError("LogGroup", "retentionInDays", failure))
}
}
if len(failures) > 0 {
return plugin.NewCheckResponse(resource.NewErrors(failures)), nil
}
return plugin.NewCheckResponse(nil), nil
}
func (p *LogGroupProvider) Name(
ctx context.Context, req *lumirpc.NameRequest) (*lumirpc.NameResponse, error) {
contract.Assert(req.GetType() == string(LogGroupToken))
obj, _, err := p.Unmarshal(req.GetProperties())
if err != nil {
return nil, err
}
if obj.Name == nil || *obj.Name == "" {
if req.Unknowns[LogGroup_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 *LogGroupProvider) Create(
ctx context.Context, req *lumirpc.CreateRequest) (*lumirpc.CreateResponse, error) {
contract.Assert(req.GetType() == string(LogGroupToken))
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 *LogGroupProvider) Get(
ctx context.Context, req *lumirpc.GetRequest) (*lumirpc.GetResponse, error) {
contract.Assert(req.GetType() == string(LogGroupToken))
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 *LogGroupProvider) InspectChange(
ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) {
contract.Assert(req.GetType() == string(LogGroupToken))
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("logGroupName") {
replaces = append(replaces, "logGroupName")
}
}
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 *LogGroupProvider) Update(
ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) {
contract.Assert(req.GetType() == string(LogGroupToken))
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 *LogGroupProvider) Delete(
ctx context.Context, req *lumirpc.DeleteRequest) (*pbempty.Empty, error) {
contract.Assert(req.GetType() == string(LogGroupToken))
id := resource.ID(req.GetId())
if err := p.ops.Delete(ctx, id); err != nil {
return nil, err
}
return &pbempty.Empty{}, nil
}
func (p *LogGroupProvider) Unmarshal(
v *pbstruct.Struct) (*LogGroup, resource.PropertyMap, error) {
var obj LogGroup
props := plugin.UnmarshalProperties(nil, v, plugin.MarshalOptions{RawResources: true})
return &obj, props, mapper.MapIU(props.Mappable(), &obj)
}
/* Marshalable LogGroup structure(s) */
// LogGroup is a marshalable representation of its corresponding IDL type.
type LogGroup struct {
Name *string `lumi:"name,optional"`
LogGroupName *string `lumi:"logGroupName,optional"`
RetentionInDays *float64 `lumi:"retentionInDays,optional"`
}
// LogGroup's properties have constants to make dealing with diffs and property bags easier.
const (
LogGroup_Name = "name"
LogGroup_LogGroupName = "logGroupName"
LogGroup_RetentionInDays = "retentionInDays"
)

View file

@ -0,0 +1,256 @@
// *** 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 cloudwatch
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"
__aws "github.com/pulumi/lumi/lib/aws/rpc"
)
/* RPC stubs for LogSubscriptionFilter resource provider */
// LogSubscriptionFilterToken is the type token corresponding to the LogSubscriptionFilter package type.
const LogSubscriptionFilterToken = tokens.Type("aws:cloudwatch/logSubscriptionFilter:LogSubscriptionFilter")
// LogSubscriptionFilterProviderOps is a pluggable interface for LogSubscriptionFilter-related management functionality.
type LogSubscriptionFilterProviderOps interface {
Check(ctx context.Context, obj *LogSubscriptionFilter, property string) error
Create(ctx context.Context, obj *LogSubscriptionFilter) (resource.ID, error)
Get(ctx context.Context, id resource.ID) (*LogSubscriptionFilter, error)
InspectChange(ctx context.Context,
id resource.ID, old *LogSubscriptionFilter, new *LogSubscriptionFilter, diff *resource.ObjectDiff) ([]string, error)
Update(ctx context.Context,
id resource.ID, old *LogSubscriptionFilter, new *LogSubscriptionFilter, diff *resource.ObjectDiff) error
Delete(ctx context.Context, id resource.ID) error
}
// LogSubscriptionFilterProvider is a dynamic gRPC-based plugin for managing LogSubscriptionFilter resources.
type LogSubscriptionFilterProvider struct {
ops LogSubscriptionFilterProviderOps
}
// NewLogSubscriptionFilterProvider allocates a resource provider that delegates to a ops instance.
func NewLogSubscriptionFilterProvider(ops LogSubscriptionFilterProviderOps) lumirpc.ResourceProviderServer {
contract.Assert(ops != nil)
return &LogSubscriptionFilterProvider{ops: ops}
}
func (p *LogSubscriptionFilterProvider) Check(
ctx context.Context, req *lumirpc.CheckRequest) (*lumirpc.CheckResponse, error) {
contract.Assert(req.GetType() == string(LogSubscriptionFilterToken))
obj, _, err := p.Unmarshal(req.GetProperties())
if err != nil {
return plugin.NewCheckResponse(err), nil
}
var failures []error
if failure := p.ops.Check(ctx, obj, ""); failure != nil {
failures = append(failures, failure)
}
unks := req.GetUnknowns()
if !unks["name"] {
if failure := p.ops.Check(ctx, obj, "name"); failure != nil {
failures = append(failures,
resource.NewPropertyError("LogSubscriptionFilter", "name", failure))
}
}
if !unks["logGroupName"] {
if failure := p.ops.Check(ctx, obj, "logGroupName"); failure != nil {
failures = append(failures,
resource.NewPropertyError("LogSubscriptionFilter", "logGroupName", failure))
}
}
if !unks["filterPattern"] {
if failure := p.ops.Check(ctx, obj, "filterPattern"); failure != nil {
failures = append(failures,
resource.NewPropertyError("LogSubscriptionFilter", "filterPattern", failure))
}
}
if !unks["destinationArn"] {
if failure := p.ops.Check(ctx, obj, "destinationArn"); failure != nil {
failures = append(failures,
resource.NewPropertyError("LogSubscriptionFilter", "destinationArn", failure))
}
}
if !unks["roleArn"] {
if failure := p.ops.Check(ctx, obj, "roleArn"); failure != nil {
failures = append(failures,
resource.NewPropertyError("LogSubscriptionFilter", "roleArn", failure))
}
}
if !unks["distribution"] {
if failure := p.ops.Check(ctx, obj, "distribution"); failure != nil {
failures = append(failures,
resource.NewPropertyError("LogSubscriptionFilter", "distribution", failure))
}
}
if len(failures) > 0 {
return plugin.NewCheckResponse(resource.NewErrors(failures)), nil
}
return plugin.NewCheckResponse(nil), nil
}
func (p *LogSubscriptionFilterProvider) Name(
ctx context.Context, req *lumirpc.NameRequest) (*lumirpc.NameResponse, error) {
contract.Assert(req.GetType() == string(LogSubscriptionFilterToken))
obj, _, err := p.Unmarshal(req.GetProperties())
if err != nil {
return nil, err
}
if obj.Name == nil || *obj.Name == "" {
if req.Unknowns[LogSubscriptionFilter_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 *LogSubscriptionFilterProvider) Create(
ctx context.Context, req *lumirpc.CreateRequest) (*lumirpc.CreateResponse, error) {
contract.Assert(req.GetType() == string(LogSubscriptionFilterToken))
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 *LogSubscriptionFilterProvider) Get(
ctx context.Context, req *lumirpc.GetRequest) (*lumirpc.GetResponse, error) {
contract.Assert(req.GetType() == string(LogSubscriptionFilterToken))
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 *LogSubscriptionFilterProvider) InspectChange(
ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) {
contract.Assert(req.GetType() == string(LogSubscriptionFilterToken))
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("logGroupName") {
replaces = append(replaces, "logGroupName")
}
if diff.Changed("destinationArn") {
replaces = append(replaces, "destinationArn")
}
}
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 *LogSubscriptionFilterProvider) Update(
ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) {
contract.Assert(req.GetType() == string(LogSubscriptionFilterToken))
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 *LogSubscriptionFilterProvider) Delete(
ctx context.Context, req *lumirpc.DeleteRequest) (*pbempty.Empty, error) {
contract.Assert(req.GetType() == string(LogSubscriptionFilterToken))
id := resource.ID(req.GetId())
if err := p.ops.Delete(ctx, id); err != nil {
return nil, err
}
return &pbempty.Empty{}, nil
}
func (p *LogSubscriptionFilterProvider) Unmarshal(
v *pbstruct.Struct) (*LogSubscriptionFilter, resource.PropertyMap, error) {
var obj LogSubscriptionFilter
props := plugin.UnmarshalProperties(nil, v, plugin.MarshalOptions{RawResources: true})
return &obj, props, mapper.MapIU(props.Mappable(), &obj)
}
/* Marshalable LogSubscriptionFilter structure(s) */
// LogSubscriptionFilter is a marshalable representation of its corresponding IDL type.
type LogSubscriptionFilter struct {
Name *string `lumi:"name,optional"`
LogGroupName string `lumi:"logGroupName"`
FilterPattern string `lumi:"filterPattern"`
DestinationArn string `lumi:"destinationArn"`
RoleARN *__aws.ARN `lumi:"roleArn,optional"`
Distribution *LogSubscriptionDistribution `lumi:"distribution,optional"`
CreationTime *float64 `lumi:"creationTime,optional"`
}
// LogSubscriptionFilter's properties have constants to make dealing with diffs and property bags easier.
const (
LogSubscriptionFilter_Name = "name"
LogSubscriptionFilter_LogGroupName = "logGroupName"
LogSubscriptionFilter_FilterPattern = "filterPattern"
LogSubscriptionFilter_DestinationArn = "destinationArn"
LogSubscriptionFilter_RoleARN = "roleArn"
LogSubscriptionFilter_Distribution = "distribution"
LogSubscriptionFilter_CreationTime = "creationTime"
)
/* Typedefs */
type (
LogSubscriptionDistribution string
)
/* Constants */
const (
ByLogStreamDistribution LogSubscriptionDistribution = "ByLogStream"
RandomDistribution LogSubscriptionDistribution = "Random"
)

View file

@ -54,6 +54,9 @@ func (p *SubscriptionProvider) Check(
return plugin.NewCheckResponse(err), nil
}
var failures []error
if failure := p.ops.Check(ctx, obj, ""); failure != nil {
failures = append(failures, failure)
}
unks := req.GetUnknowns()
if !unks["name"] {
if failure := p.ops.Check(ctx, obj, "name"); failure != nil {