Support Tags on aws.ec2.Instance
Also adds test coverage for aws.ec2.Instance resources.
This commit is contained in:
parent
72919f7526
commit
5d2dffdcc9
|
@ -31,6 +31,8 @@ type Instance struct {
|
|||
SecurityGroups *[]*SecurityGroup `lumi:"securityGroups,optional,replaces"`
|
||||
// Provides the name of the Amazon EC2 key pair.
|
||||
KeyName *string `lumi:"keyName,optional"`
|
||||
// Provides a list of tags to attach to the instance.
|
||||
Tags *[]Tag `lumi:"tags,optional"`
|
||||
|
||||
// Output properties:
|
||||
|
||||
|
@ -46,6 +48,12 @@ type Instance struct {
|
|||
PublicIP *string `lumi:"publicIP,out,optional"`
|
||||
}
|
||||
|
||||
// A Tag applied to an EC2 instance.
|
||||
type Tag struct {
|
||||
Key string `lumi:"key"`
|
||||
Value string `lumi:"value"`
|
||||
}
|
||||
|
||||
// InstanceType is an enum type with all the names of instance types available in EC2.
|
||||
type InstanceType string
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ export class Instance extends lumi.Resource implements InstanceArgs {
|
|||
public instanceType?: InstanceType;
|
||||
public readonly securityGroups?: SecurityGroup[];
|
||||
public keyName?: string;
|
||||
public tags?: Tag[];
|
||||
@lumi.out public availabilityZone: string;
|
||||
@lumi.out public privateDNSName?: string;
|
||||
@lumi.out public publicDNSName?: string;
|
||||
|
@ -88,6 +89,7 @@ export class Instance extends lumi.Resource implements InstanceArgs {
|
|||
this.instanceType = args.instanceType;
|
||||
this.securityGroups = args.securityGroups;
|
||||
this.keyName = args.keyName;
|
||||
this.tags = args.tags;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,6 +98,7 @@ export interface InstanceArgs {
|
|||
instanceType?: InstanceType;
|
||||
readonly securityGroups?: SecurityGroup[];
|
||||
keyName?: string;
|
||||
tags?: Tag[];
|
||||
}
|
||||
|
||||
export type InstanceType =
|
||||
|
@ -157,4 +160,9 @@ export type InstanceType =
|
|||
"x1.16xlarge" |
|
||||
"x1.32xlarge";
|
||||
|
||||
export interface Tag {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -4,45 +4,96 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
awsdynamodb "github.com/aws/aws-sdk-go/service/dynamodb"
|
||||
structpb "github.com/golang/protobuf/ptypes/struct"
|
||||
"github.com/pulumi/lumi/lib/aws/provider/awsctx"
|
||||
"github.com/pulumi/lumi/lib/aws/provider/testutil"
|
||||
"github.com/pulumi/lumi/lib/aws/rpc/dynamodb"
|
||||
"github.com/pulumi/lumi/pkg/resource"
|
||||
"github.com/pulumi/lumi/sdk/go/pkg/lumirpc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const TABLENAMEPREFIX = "lumitest"
|
||||
const RESOURCEPREFIX = "lumitest"
|
||||
|
||||
func Test(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, err := awsctx.New()
|
||||
assert.Nil(t, err, "expected no error getting AWS context")
|
||||
|
||||
cleanup(ctx)
|
||||
|
||||
testutil.ProviderTest(t, NewTableProvider(ctx), TableToken, []interface{}{
|
||||
&dynamodb.Table{
|
||||
Name: aws.String(RESOURCEPREFIX),
|
||||
Attributes: []dynamodb.Attribute{
|
||||
{Name: "Album", Type: "S"},
|
||||
{Name: "Artist", Type: "S"},
|
||||
{Name: "Sales", Type: "N"},
|
||||
},
|
||||
HashKey: "Album",
|
||||
RangeKey: aws.String("Artist"),
|
||||
ReadCapacity: 2,
|
||||
WriteCapacity: 2,
|
||||
GlobalSecondaryIndexes: &[]dynamodb.GlobalSecondaryIndex{
|
||||
{
|
||||
IndexName: "myGSI",
|
||||
HashKey: "Sales",
|
||||
RangeKey: aws.String("Artist"),
|
||||
ReadCapacity: 1,
|
||||
WriteCapacity: 1,
|
||||
NonKeyAttributes: []string{"Album"},
|
||||
ProjectionType: "INCLUDE",
|
||||
},
|
||||
},
|
||||
},
|
||||
&dynamodb.Table{
|
||||
Name: aws.String(RESOURCEPREFIX),
|
||||
Attributes: []dynamodb.Attribute{
|
||||
{Name: "Album", Type: "S"},
|
||||
{Name: "Artist", Type: "S"},
|
||||
{Name: "NumberOfSongs", Type: "N"},
|
||||
{Name: "Sales", Type: "N"},
|
||||
},
|
||||
HashKey: "Album",
|
||||
RangeKey: aws.String("Artist"),
|
||||
ReadCapacity: 1,
|
||||
WriteCapacity: 1,
|
||||
GlobalSecondaryIndexes: &[]dynamodb.GlobalSecondaryIndex{
|
||||
{
|
||||
IndexName: "myGSI",
|
||||
HashKey: "Sales",
|
||||
RangeKey: aws.String("Artist"),
|
||||
ReadCapacity: 1,
|
||||
WriteCapacity: 1,
|
||||
NonKeyAttributes: []string{"Album"},
|
||||
ProjectionType: "INCLUDE",
|
||||
},
|
||||
{
|
||||
IndexName: "myGSI2",
|
||||
HashKey: "NumberOfSongs",
|
||||
RangeKey: aws.String("Sales"),
|
||||
NonKeyAttributes: []string{"Album", "Artist"},
|
||||
ProjectionType: "INCLUDE",
|
||||
ReadCapacity: 1,
|
||||
WriteCapacity: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
func marshal(table dynamodb.Table) (*structpb.Struct, error) {
|
||||
byts, err := json.Marshal(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var obj map[string]interface{}
|
||||
err = json.Unmarshal(byts, &obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
props := resource.NewPropertyMapFromMap(obj)
|
||||
return resource.MarshalProperties(nil, props, resource.MarshalOptions{}), nil
|
||||
}
|
||||
|
||||
func cleanup(ctx *awsctx.Context) {
|
||||
fmt.Printf("Cleaning up tables with prefix: %v\n", TABLENAMEPREFIX)
|
||||
fmt.Printf("Cleaning up tables with prefix: %v\n", RESOURCEPREFIX)
|
||||
list, err := ctx.DynamoDB().ListTables(&awsdynamodb.ListTablesInput{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cleaned := 0
|
||||
for _, table := range list.TableNames {
|
||||
if strings.HasPrefix(aws.StringValue(table), TABLENAMEPREFIX) {
|
||||
if strings.HasPrefix(aws.StringValue(table), RESOURCEPREFIX) {
|
||||
ctx.DynamoDB().DeleteTable(&awsdynamodb.DeleteTableInput{
|
||||
TableName: table,
|
||||
})
|
||||
|
@ -51,135 +102,3 @@ func cleanup(ctx *awsctx.Context) {
|
|||
}
|
||||
fmt.Printf("Cleaned up %v tables\n", cleaned)
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a TableProvider
|
||||
ctx, err := awsctx.New()
|
||||
assert.Nil(t, err, "expected no error getting AWS context")
|
||||
tableProvider := NewTableProvider(ctx)
|
||||
|
||||
defer cleanup(ctx)
|
||||
|
||||
// Table to create
|
||||
tablename := TABLENAMEPREFIX
|
||||
table := dynamodb.Table{
|
||||
Name: &tablename,
|
||||
Attributes: []dynamodb.Attribute{
|
||||
{Name: "Album", Type: "S"},
|
||||
{Name: "Artist", Type: "S"},
|
||||
{Name: "Sales", Type: "N"},
|
||||
},
|
||||
HashKey: "Album",
|
||||
RangeKey: aws.String("Artist"),
|
||||
ReadCapacity: 2,
|
||||
WriteCapacity: 2,
|
||||
GlobalSecondaryIndexes: &[]dynamodb.GlobalSecondaryIndex{
|
||||
{
|
||||
IndexName: "myGSI",
|
||||
HashKey: "Sales",
|
||||
RangeKey: aws.String("Artist"),
|
||||
ReadCapacity: 1,
|
||||
WriteCapacity: 1,
|
||||
NonKeyAttributes: []string{"Album"},
|
||||
ProjectionType: "INCLUDE",
|
||||
},
|
||||
},
|
||||
}
|
||||
props, err := marshal(table)
|
||||
if !assert.NoError(t, err, "expected no error marshaling object to protobuf") {
|
||||
return
|
||||
}
|
||||
checkResp, err := tableProvider.Check(nil, &lumirpc.CheckRequest{
|
||||
Type: string(TableToken),
|
||||
Properties: props,
|
||||
})
|
||||
if !assert.NoError(t, err, "expected no error checking table") {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, 0, len(checkResp.Failures), "expected no check failures")
|
||||
|
||||
// Invoke Create request
|
||||
resp, err := tableProvider.Create(nil, &lumirpc.CreateRequest{
|
||||
Type: string(TableToken),
|
||||
Properties: props,
|
||||
})
|
||||
if !assert.NoError(t, err, "expected no error creating resource") {
|
||||
return
|
||||
}
|
||||
if !assert.NotNil(t, resp, "expected a non-nil response") {
|
||||
return
|
||||
}
|
||||
|
||||
id := resp.Id
|
||||
assert.Contains(t, id, "lumitest", "expected resource ID to contain `lumitest`")
|
||||
|
||||
// Table for update
|
||||
tablename2 := "lumitest"
|
||||
table2 := dynamodb.Table{
|
||||
Name: &tablename2,
|
||||
Attributes: []dynamodb.Attribute{
|
||||
{Name: "Album", Type: "S"},
|
||||
{Name: "Artist", Type: "S"},
|
||||
{Name: "NumberOfSongs", Type: "N"},
|
||||
{Name: "Sales", Type: "N"},
|
||||
},
|
||||
HashKey: "Album",
|
||||
RangeKey: aws.String("Artist"),
|
||||
ReadCapacity: 1,
|
||||
WriteCapacity: 1,
|
||||
GlobalSecondaryIndexes: &[]dynamodb.GlobalSecondaryIndex{
|
||||
{
|
||||
IndexName: "myGSI",
|
||||
HashKey: "Sales",
|
||||
RangeKey: aws.String("Artist"),
|
||||
ReadCapacity: 1,
|
||||
WriteCapacity: 1,
|
||||
NonKeyAttributes: []string{"Album"},
|
||||
ProjectionType: "INCLUDE",
|
||||
},
|
||||
{
|
||||
IndexName: "myGSI2",
|
||||
HashKey: "NumberOfSongs",
|
||||
RangeKey: aws.String("Sales"),
|
||||
NonKeyAttributes: []string{"Album", "Artist"},
|
||||
ProjectionType: "INCLUDE",
|
||||
ReadCapacity: 1,
|
||||
WriteCapacity: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
props2, err := marshal(table2)
|
||||
if !assert.NoError(t, err, "expected no error marshaling object to protobuf") {
|
||||
return
|
||||
}
|
||||
checkResp, err = tableProvider.Check(nil, &lumirpc.CheckRequest{
|
||||
Type: string(TableToken),
|
||||
Properties: props2,
|
||||
})
|
||||
if !assert.NoError(t, err, "expected no error checking table") {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, 0, len(checkResp.Failures), "expected no check failures")
|
||||
|
||||
// Invoke Update request
|
||||
_, err = tableProvider.Update(nil, &lumirpc.UpdateRequest{
|
||||
Type: string(TableToken),
|
||||
Id: id,
|
||||
Olds: props,
|
||||
News: props2,
|
||||
})
|
||||
if !assert.NoError(t, err, "expected no error creating resource") {
|
||||
return
|
||||
}
|
||||
|
||||
// Invoke the Delete request
|
||||
_, err = tableProvider.Delete(nil, &lumirpc.DeleteRequest{
|
||||
Type: string(TableToken),
|
||||
Id: id,
|
||||
})
|
||||
if !assert.NoError(t, err, "expected no error deleting resource") {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,13 +89,28 @@ func (p *instanceProvider) Create(ctx context.Context, obj *ec2.Instance) (resou
|
|||
its := string(*obj.InstanceType)
|
||||
instanceType = &its
|
||||
}
|
||||
var tagSpecifications []*awsec2.TagSpecification
|
||||
if obj.Tags != nil {
|
||||
var tags []*awsec2.Tag
|
||||
for _, tag := range *obj.Tags {
|
||||
tags = append(tags, &awsec2.Tag{
|
||||
Key: aws.String(tag.Key),
|
||||
Value: aws.String(tag.Value),
|
||||
})
|
||||
}
|
||||
tagSpecifications = []*awsec2.TagSpecification{{
|
||||
ResourceType: aws.String("instance"),
|
||||
Tags: tags,
|
||||
}}
|
||||
}
|
||||
create := &awsec2.RunInstancesInput{
|
||||
ImageId: aws.String(obj.ImageID),
|
||||
InstanceType: instanceType,
|
||||
SecurityGroupIds: secgrpIDs,
|
||||
KeyName: obj.KeyName,
|
||||
MinCount: aws.Int64(int64(1)),
|
||||
MaxCount: aws.Int64(int64(1)),
|
||||
ImageId: aws.String(obj.ImageID),
|
||||
InstanceType: instanceType,
|
||||
SecurityGroupIds: secgrpIDs,
|
||||
KeyName: obj.KeyName,
|
||||
MinCount: aws.Int64(int64(1)),
|
||||
MaxCount: aws.Int64(int64(1)),
|
||||
TagSpecifications: tagSpecifications,
|
||||
}
|
||||
|
||||
// Now go ahead and perform the action.
|
||||
|
@ -159,6 +174,14 @@ func (p *instanceProvider) Get(ctx context.Context, id resource.ID) (*ec2.Instan
|
|||
secgrpIDs = &ids
|
||||
}
|
||||
|
||||
var tags []ec2.Tag
|
||||
for _, tag := range inst.Tags {
|
||||
tags = append(tags, ec2.Tag{
|
||||
Key: aws.StringValue(tag.Key),
|
||||
Value: aws.StringValue(tag.Value),
|
||||
})
|
||||
}
|
||||
|
||||
instanceType := ec2.InstanceType(aws.StringValue(inst.InstanceType))
|
||||
return &ec2.Instance{
|
||||
ImageID: aws.StringValue(inst.ImageId),
|
||||
|
@ -170,6 +193,7 @@ func (p *instanceProvider) Get(ctx context.Context, id resource.ID) (*ec2.Instan
|
|||
PublicDNSName: inst.PublicDnsName,
|
||||
PrivateIP: inst.PrivateIpAddress,
|
||||
PublicIP: inst.PublicIpAddress,
|
||||
Tags: &tags,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -184,7 +208,50 @@ func (p *instanceProvider) InspectChange(ctx context.Context, id resource.ID,
|
|||
// to new values. The resource ID is returned and may be different if the resource had to be recreated.
|
||||
func (p *instanceProvider) Update(ctx context.Context, id resource.ID,
|
||||
old *ec2.Instance, new *ec2.Instance, diff *resource.ObjectDiff) error {
|
||||
return errors.New("No known updatable instance properties")
|
||||
iid, err := arn.ParseResourceName(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if diff.Changed(ec2.Instance_Tags) {
|
||||
newTagSet := newTagHashSet(new.Tags)
|
||||
oldTagSet := newTagHashSet(old.Tags)
|
||||
d := oldTagSet.Diff(newTagSet)
|
||||
var addOrUpdateTags []*awsec2.Tag
|
||||
for _, o := range d.AddOrUpdates() {
|
||||
option := o.(tagHash).item
|
||||
addOrUpdateTags = append(addOrUpdateTags, &awsec2.Tag{
|
||||
Key: aws.String(option.Key),
|
||||
Value: aws.String(option.Value),
|
||||
})
|
||||
}
|
||||
if len(addOrUpdateTags) > 0 {
|
||||
_, err := p.ctx.EC2().CreateTags(&awsec2.CreateTagsInput{
|
||||
Resources: []*string{aws.String(iid)},
|
||||
Tags: addOrUpdateTags,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var deleteTags []*awsec2.Tag
|
||||
for _, o := range d.Deletes() {
|
||||
option := o.(tagHash).item
|
||||
deleteTags = append(deleteTags, &awsec2.Tag{
|
||||
Key: aws.String(option.Key),
|
||||
Value: nil,
|
||||
})
|
||||
}
|
||||
if len(deleteTags) > 0 {
|
||||
_, err = p.ctx.EC2().DeleteTags(&awsec2.DeleteTagsInput{
|
||||
Resources: []*string{aws.String(iid)},
|
||||
Tags: deleteTags,
|
||||
})
|
||||
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.
|
||||
|
@ -203,3 +270,26 @@ func (p *instanceProvider) Delete(ctx context.Context, id resource.ID) error {
|
|||
return p.ctx.EC2().WaitUntilInstanceTerminated(
|
||||
&awsec2.DescribeInstancesInput{InstanceIds: []*string{aws.String(iid)}})
|
||||
}
|
||||
|
||||
type tagHash struct {
|
||||
item ec2.Tag
|
||||
}
|
||||
|
||||
var _ awsctx.Hashable = tagHash{}
|
||||
|
||||
func (option tagHash) HashKey() awsctx.Hash {
|
||||
return awsctx.Hash(option.item.Key)
|
||||
}
|
||||
func (option tagHash) HashValue() awsctx.Hash {
|
||||
return awsctx.Hash(option.item.Key + ":" + option.item.Value)
|
||||
}
|
||||
func newTagHashSet(options *[]ec2.Tag) *awsctx.HashSet {
|
||||
set := awsctx.NewHashSet()
|
||||
if options == nil {
|
||||
return set
|
||||
}
|
||||
for _, option := range *options {
|
||||
set.Add(tagHash{option})
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
|
102
lib/aws/provider/ec2/instance_test.go
Normal file
102
lib/aws/provider/ec2/instance_test.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package ec2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
awsec2 "github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/pulumi/lumi/lib/aws/provider/awsctx"
|
||||
"github.com/pulumi/lumi/lib/aws/provider/testutil"
|
||||
"github.com/pulumi/lumi/lib/aws/rpc/ec2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const RESOURCEPREFIX = "lumitest"
|
||||
|
||||
var amis = map[string]string{
|
||||
"us-east-1": "ami-6869aa05",
|
||||
"us-west-2": "ami-7172b611",
|
||||
"us-west-1": "ami-31490d51",
|
||||
"eu-west-1": "ami-f9dd458a",
|
||||
"eu-west-2": "ami-886369ec",
|
||||
"eu-central-1": "ami-ea26ce85",
|
||||
"ap-northeast-1": "ami-374db956",
|
||||
"ap-northeast-2": "ami-2b408b45",
|
||||
"ap-southeast-1": "ami-a59b49c6",
|
||||
"ap-southeast-2": "ami-dc361ebf",
|
||||
"ap-south-1": "ami-ffbdd790",
|
||||
"us-east-2": "ami-f6035893",
|
||||
"ca-central-1": "ami-730ebd17",
|
||||
"sa-east-1": "ami-6dd04501",
|
||||
"cn-north-1": "ami-8e6aa0e3",
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, err := awsctx.New()
|
||||
assert.Nil(t, err, "expected no error getting AWS context")
|
||||
|
||||
cleanup(ctx)
|
||||
|
||||
instanceType := ec2.InstanceType("t2.nano")
|
||||
|
||||
testutil.ProviderTest(t, NewInstanceProvider(ctx), InstanceToken, []interface{}{
|
||||
&ec2.Instance{
|
||||
Name: aws.String(RESOURCEPREFIX),
|
||||
InstanceType: &instanceType,
|
||||
ImageID: amis[ctx.Region()],
|
||||
Tags: &[]ec2.Tag{{
|
||||
Key: RESOURCEPREFIX,
|
||||
Value: RESOURCEPREFIX,
|
||||
}},
|
||||
},
|
||||
&ec2.Instance{
|
||||
Name: aws.String(RESOURCEPREFIX),
|
||||
InstanceType: &instanceType,
|
||||
ImageID: amis[ctx.Region()],
|
||||
Tags: &[]ec2.Tag{{
|
||||
Key: RESOURCEPREFIX,
|
||||
Value: RESOURCEPREFIX,
|
||||
}, {
|
||||
Key: "Hello",
|
||||
Value: "World",
|
||||
}},
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func cleanup(ctx *awsctx.Context) {
|
||||
fmt.Printf("Cleaning up instances with tag:%v=%v\n", RESOURCEPREFIX, RESOURCEPREFIX)
|
||||
list, err := ctx.EC2().DescribeInstances(&awsec2.DescribeInstancesInput{
|
||||
Filters: []*awsec2.Filter{{
|
||||
Name: aws.String("tag:" + RESOURCEPREFIX),
|
||||
Values: []*string{aws.String(RESOURCEPREFIX)},
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cleaned := 0
|
||||
instanceIds := []*string{}
|
||||
for _, reservation := range list.Reservations {
|
||||
for _, instance := range reservation.Instances {
|
||||
if aws.StringValue(instance.State.Name) != awsec2.InstanceStateNameTerminated {
|
||||
instanceIds = append(instanceIds, instance.InstanceId)
|
||||
cleaned++
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(instanceIds) > 0 {
|
||||
_, err = ctx.EC2().TerminateInstances(&awsec2.TerminateInstancesInput{
|
||||
InstanceIds: instanceIds,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Failed cleaning up %v tables: %v\n", cleaned, err)
|
||||
} else {
|
||||
fmt.Printf("Cleaned up %v tables\n", cleaned)
|
||||
}
|
||||
}
|
||||
}
|
110
lib/aws/provider/testutil/providerTest.go
Normal file
110
lib/aws/provider/testutil/providerTest.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
package testutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
structpb "github.com/golang/protobuf/ptypes/struct"
|
||||
"github.com/pulumi/lumi/pkg/resource"
|
||||
"github.com/pulumi/lumi/pkg/tokens"
|
||||
"github.com/pulumi/lumi/sdk/go/pkg/lumirpc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// ProviderTest takes a resource provider and array of resource steps and performs a Create, as many Udpates
|
||||
// as neeed, and finally a Delete operation to walk the resource through the requested lifecycle. It also
|
||||
// performs Check operations on each provided resource.
|
||||
func ProviderTest(t *testing.T, provider lumirpc.ResourceProviderServer, token tokens.Type, steps []interface{}) {
|
||||
|
||||
p := &providerTest{
|
||||
t: t,
|
||||
provider: provider,
|
||||
token: token,
|
||||
res: nil,
|
||||
lastProps: nil,
|
||||
}
|
||||
|
||||
id := ""
|
||||
for _, res := range steps {
|
||||
p.res = res
|
||||
if id == "" {
|
||||
id = p.createResource()
|
||||
if id == "" {
|
||||
t.Fatal("expected to succesfully create resource")
|
||||
}
|
||||
} else {
|
||||
if !p.updateResource(id) {
|
||||
t.Fatal("expected to succesfully update resource")
|
||||
}
|
||||
}
|
||||
}
|
||||
if !p.deleteResource(id) {
|
||||
t.Fatal("expected to succesfully delete resource")
|
||||
}
|
||||
}
|
||||
|
||||
type providerTest struct {
|
||||
t *testing.T
|
||||
provider lumirpc.ResourceProviderServer
|
||||
token tokens.Type
|
||||
res interface{}
|
||||
lastProps *structpb.Struct
|
||||
}
|
||||
|
||||
func (p *providerTest) createResource() string {
|
||||
props := resource.MarshalProperties(nil, resource.NewPropertyMap(p.res), resource.MarshalOptions{})
|
||||
checkResp, err := p.provider.Check(nil, &lumirpc.CheckRequest{
|
||||
Type: string(p.token),
|
||||
Properties: props,
|
||||
})
|
||||
if !assert.NoError(p.t, err, "expected no error checking table") {
|
||||
return ""
|
||||
}
|
||||
assert.Equal(p.t, 0, len(checkResp.Failures), "expected no check failures")
|
||||
resp, err := p.provider.Create(nil, &lumirpc.CreateRequest{
|
||||
Type: string(p.token),
|
||||
Properties: props,
|
||||
})
|
||||
if !assert.NoError(p.t, err, "expected no error creating resource") {
|
||||
return ""
|
||||
}
|
||||
if !assert.NotNil(p.t, resp, "expected a non-nil response") {
|
||||
return ""
|
||||
}
|
||||
id := resp.Id
|
||||
p.lastProps = props
|
||||
return id
|
||||
}
|
||||
|
||||
func (p *providerTest) updateResource(id string) bool {
|
||||
newProps := resource.MarshalProperties(nil, resource.NewPropertyMap(p.res), resource.MarshalOptions{})
|
||||
checkResp, err := p.provider.Check(nil, &lumirpc.CheckRequest{
|
||||
Type: string(p.token),
|
||||
Properties: newProps,
|
||||
})
|
||||
if !assert.NoError(p.t, err, "expected no error checking resource") {
|
||||
return false
|
||||
}
|
||||
assert.Equal(p.t, 0, len(checkResp.Failures), "expected no check failures")
|
||||
_, err = p.provider.Update(nil, &lumirpc.UpdateRequest{
|
||||
Type: string(p.token),
|
||||
Id: id,
|
||||
Olds: p.lastProps,
|
||||
News: newProps,
|
||||
})
|
||||
if !assert.NoError(p.t, err, "expected no error creating resource") {
|
||||
return false
|
||||
}
|
||||
p.lastProps = newProps
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *providerTest) deleteResource(id string) bool {
|
||||
_, err := p.provider.Delete(nil, &lumirpc.DeleteRequest{
|
||||
Type: string(p.token),
|
||||
Id: id,
|
||||
})
|
||||
if !assert.NoError(p.t, err, "expected no error deleting resource") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -182,6 +182,7 @@ type Instance struct {
|
|||
InstanceType *InstanceType `json:"instanceType,omitempty"`
|
||||
SecurityGroups *[]resource.ID `json:"securityGroups,omitempty"`
|
||||
KeyName *string `json:"keyName,omitempty"`
|
||||
Tags *[]Tag `json:"tags,omitempty"`
|
||||
AvailabilityZone string `json:"availabilityZone,omitempty"`
|
||||
PrivateDNSName *string `json:"privateDNSName,omitempty"`
|
||||
PublicDNSName *string `json:"publicDNSName,omitempty"`
|
||||
|
@ -196,6 +197,7 @@ const (
|
|||
Instance_InstanceType = "instanceType"
|
||||
Instance_SecurityGroups = "securityGroups"
|
||||
Instance_KeyName = "keyName"
|
||||
Instance_Tags = "tags"
|
||||
Instance_AvailabilityZone = "availabilityZone"
|
||||
Instance_PrivateDNSName = "privateDNSName"
|
||||
Instance_PublicDNSName = "publicDNSName"
|
||||
|
@ -203,6 +205,20 @@ const (
|
|||
Instance_PublicIP = "publicIP"
|
||||
)
|
||||
|
||||
/* Marshalable Tag structure(s) */
|
||||
|
||||
// Tag is a marshalable representation of its corresponding IDL type.
|
||||
type Tag struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// Tag's properties have constants to make dealing with diffs and property bags easier.
|
||||
const (
|
||||
Tag_Key = "key"
|
||||
Tag_Value = "value"
|
||||
)
|
||||
|
||||
/* Typedefs */
|
||||
|
||||
type (
|
||||
|
|
Loading…
Reference in a new issue