Implement name property in AWS provider/library
This commit is contained in:
parent
c120f62964
commit
14e3f19437
|
@ -247,7 +247,7 @@ let awsInstanceType2Arch: { [name: string]: { Arch: string; } } = {
|
|||
}
|
||||
};
|
||||
|
||||
let securityGroup = new SecurityGroup({
|
||||
let securityGroup = new SecurityGroup("group", {
|
||||
groupDescription: "Enable SSH access",
|
||||
securityGroupIngress: [{
|
||||
ipProtocol: "tcp",
|
||||
|
@ -257,9 +257,10 @@ let securityGroup = new SecurityGroup({
|
|||
}]
|
||||
});
|
||||
|
||||
let instance = new Instance({
|
||||
let instance = new Instance("instance", {
|
||||
instanceType: instanceType,
|
||||
securityGroups: [securityGroup],
|
||||
keyName: keyName,
|
||||
imageId: awsRegionArch2AMI[region][awsInstanceType2Arch[instanceType].Arch]
|
||||
});
|
||||
|
||||
|
|
|
@ -8,12 +8,14 @@ export class Resource
|
|||
extends mu.Resource
|
||||
implements ResourceProperties {
|
||||
|
||||
public readonly name: string;
|
||||
public readonly resource: string;
|
||||
public readonly properties?: any;
|
||||
public readonly dependsOn?: mu.Stack[];
|
||||
|
||||
constructor(args: ResourceProperties) {
|
||||
super();
|
||||
this.name = args.name;
|
||||
this.resource = args.resource;
|
||||
this.properties = args.properties;
|
||||
this.dependsOn = args.dependsOn;
|
||||
|
@ -21,6 +23,8 @@ export class Resource
|
|||
}
|
||||
|
||||
export interface ResourceProperties {
|
||||
// The resource name.
|
||||
readonly name: string;
|
||||
// The CF resource name.
|
||||
readonly resource: string;
|
||||
// An optional list of properties to map.
|
||||
|
|
|
@ -16,9 +16,10 @@ export class Instance
|
|||
public securityGroups?: SecurityGroup[];
|
||||
public keyName?: string;
|
||||
|
||||
constructor(args: InstanceProperties) {
|
||||
constructor(name: string, args: InstanceProperties) {
|
||||
super({
|
||||
resource: "AWS::EC2::Instance",
|
||||
name: name,
|
||||
resource: "AWS::EC2::Instance",
|
||||
properties: args,
|
||||
});
|
||||
this.imageId = args.imageId;
|
||||
|
|
|
@ -7,8 +7,9 @@ import * as cloudformation from '../cloudformation';
|
|||
// @name: aws/ec2/internetGateway
|
||||
// @website: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-internet-gateway.html
|
||||
export class InternetGateway extends cloudformation.Resource {
|
||||
constructor(args: InternetGatewayArgs) {
|
||||
constructor(name: string, args: InternetGatewayArgs) {
|
||||
super({
|
||||
name: name,
|
||||
resource: "AWS::EC2::InternetGateway",
|
||||
properties: args,
|
||||
});
|
||||
|
|
|
@ -10,8 +10,9 @@ import * as cloudformation from '../cloudformation';
|
|||
// @name: aws/ec2/route
|
||||
// @website: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route.html
|
||||
export class Route extends cloudformation.Resource {
|
||||
constructor(args: RouteArgs) {
|
||||
constructor(name: string, args: RouteArgs) {
|
||||
super({
|
||||
name: name,
|
||||
resource: "AWS::EC2::Route",
|
||||
dependsOn: [
|
||||
args.vpcGatewayAttachment,
|
||||
|
|
|
@ -9,8 +9,9 @@ import * as cloudformation from '../cloudformation';
|
|||
// @name: aws/ec2/routeTable
|
||||
// @website: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route-table.html
|
||||
export class RouteTable extends cloudformation.Resource {
|
||||
constructor(args: RouteTableArgs) {
|
||||
constructor(name: string, args: RouteTableArgs) {
|
||||
super({
|
||||
name: name,
|
||||
resource: "AWS::EC2::RouteTable",
|
||||
properties: args,
|
||||
});
|
||||
|
|
|
@ -17,8 +17,9 @@ export class SecurityGroup
|
|||
public securityGroupEgress?: SecurityGroupRule[];
|
||||
public securityGroupIngress?: SecurityGroupRule[];
|
||||
|
||||
constructor(args: SecurityGroupProperties) {
|
||||
constructor(name: string, args: SecurityGroupProperties) {
|
||||
super({
|
||||
name: name,
|
||||
resource: "AWS::EC2::SecurityGroup",
|
||||
properties: args,
|
||||
});
|
||||
|
|
|
@ -8,8 +8,9 @@ import * as cloudformation from '../cloudformation';
|
|||
// @name: aws/ec2/securityGroupEgressRule
|
||||
// @website: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-security-group-egress.html
|
||||
export class SecurityGroupEgress extends cloudformation.Resource {
|
||||
constructor(args: SecurityGroupEgressArgs) {
|
||||
constructor(name: string, args: SecurityGroupEgressArgs) {
|
||||
super({
|
||||
name: name,
|
||||
resource: "AWS::EC2::SecurityGroupEgress",
|
||||
properties: args,
|
||||
});
|
||||
|
|
|
@ -8,8 +8,9 @@ import * as cloudformation from '../cloudformation';
|
|||
// @name: aws/ec2/securityGroupIngressRule
|
||||
// @website: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-security-group-ingress.html
|
||||
export class SecurityGroupIngress extends cloudformation.Resource {
|
||||
constructor(args: SecurityGroupIngressArgs) {
|
||||
constructor(name: string, args: SecurityGroupIngressArgs) {
|
||||
super({
|
||||
name: name,
|
||||
resource: "AWS::EC2::SecurityGroupIngress",
|
||||
properties: args,
|
||||
});
|
||||
|
|
|
@ -8,8 +8,9 @@ import * as cloudformation from '../cloudformation';
|
|||
// @name: aws/ec2/subnet
|
||||
// @website: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet.html
|
||||
export class Subnet extends cloudformation.Resource {
|
||||
constructor(args: SubnetArgs) {
|
||||
constructor(name: string, args: SubnetArgs) {
|
||||
super({
|
||||
name: name,
|
||||
resource: "AWS::EC2::Subnet",
|
||||
properties: args,
|
||||
});
|
||||
|
|
|
@ -7,8 +7,9 @@ import * as cloudformation from '../cloudformation';
|
|||
// @name: aws/ec2/vpc
|
||||
// @website: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html
|
||||
export class VPC extends cloudformation.Resource {
|
||||
constructor(args: VPCArgs) {
|
||||
constructor(name: string, args: VPCArgs) {
|
||||
super({
|
||||
name: name,
|
||||
resource: "AWS::EC2::VPC",
|
||||
properties: args,
|
||||
});
|
||||
|
|
|
@ -9,8 +9,9 @@ import * as cloudformation from '../cloudformation';
|
|||
// @name: aws/ec2/vpcGatewayAttachment
|
||||
// @website: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-gateway-attachment.html
|
||||
export class VPCGatewayAttachment extends cloudformation.Resource {
|
||||
constructor(args: VPCGatewayAttachmentArgs) {
|
||||
constructor(name: string, args: VPCGatewayAttachmentArgs) {
|
||||
super({
|
||||
name: name,
|
||||
resource: "AWS::EC2::VPCGatewayAttachment",
|
||||
properties: args,
|
||||
});
|
||||
|
|
|
@ -9,8 +9,9 @@ import * as cloudformation from '../cloudformation';
|
|||
// @name: aws/ec2/vpcPeeringConnection
|
||||
// @website: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcpeeringconnection.html
|
||||
export class VPCPeeringConnection extends cloudformation.Resource {
|
||||
constructor(args: VPCPeeringConnectionArgs) {
|
||||
constructor(name: string, args: VPCPeeringConnectionArgs) {
|
||||
super({
|
||||
name: name,
|
||||
resource: "AWS::EC2::VPCPeeringConnection",
|
||||
properties: args,
|
||||
});
|
||||
|
|
|
@ -30,6 +30,13 @@ type instanceProvider struct {
|
|||
ctx *awsctx.Context
|
||||
}
|
||||
|
||||
// Name names a given resource. Sometimes this will be assigned by a developer, and so the provider
|
||||
// simply fetches it from the property bag; other times, the provider will assign this based on its own algorithm.
|
||||
// In any case, resources with the same name must be safe to use interchangeably with one another.
|
||||
func (p *instanceProvider) Name(ctx context.Context, req *murpc.NameRequest) (*murpc.NameResponse, error) {
|
||||
return nil, nil // use the AWS provider default name
|
||||
}
|
||||
|
||||
// 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 *instanceProvider) Create(ctx context.Context, req *murpc.CreateRequest) (*murpc.CreateResponse, error) {
|
||||
|
|
|
@ -31,6 +31,13 @@ type securityGroupProvider struct {
|
|||
ctx *awsctx.Context
|
||||
}
|
||||
|
||||
// Name names a given resource. Sometimes this will be assigned by a developer, and so the provider
|
||||
// simply fetches it from the property bag; other times, the provider will assign this based on its own algorithm.
|
||||
// In any case, resources with the same name must be safe to use interchangeably with one another.
|
||||
func (p *securityGroupProvider) Name(ctx context.Context, req *murpc.NameRequest) (*murpc.NameResponse, error) {
|
||||
return nil, nil // use the AWS provider default name
|
||||
}
|
||||
|
||||
// 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 *securityGroupProvider) Create(ctx context.Context, req *murpc.CreateRequest) (*murpc.CreateResponse, error) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
pbempty "github.com/golang/protobuf/ptypes/empty"
|
||||
"github.com/marapongo/mu/pkg/resource"
|
||||
"github.com/marapongo/mu/pkg/tokens"
|
||||
"github.com/marapongo/mu/sdk/go/pkg/murpc"
|
||||
"golang.org/x/net/context"
|
||||
|
@ -35,6 +36,36 @@ func NewProvider() (*Provider, error) {
|
|||
|
||||
var _ murpc.ResourceProviderServer = (*Provider)(nil)
|
||||
|
||||
const nameProperty string = "name" // the property used for naming AWS resources.
|
||||
|
||||
// Name names a given resource. Sometimes this will be assigned by a developer, and so the provider
|
||||
// simply fetches it from the property bag; other times, the provider will assign this based on its own algorithm.
|
||||
// In any case, resources with the same name must be safe to use interchangeably with one another.
|
||||
func (p *Provider) Name(ctx context.Context, req *murpc.NameRequest) (*murpc.NameResponse, error) {
|
||||
// First, see if the provider overrides the naming.
|
||||
t := tokens.Type(req.GetType())
|
||||
if prov, has := p.impls[t]; has {
|
||||
if res, err := prov.Name(ctx, req); res != nil || err != nil {
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("Unrecognized resource type (Create): %v", t)
|
||||
}
|
||||
|
||||
// If the provider didn't override, we can go ahead and default to the name property.
|
||||
// TODO: eventually, we want to specialize some resources, like SecurityGroups, since they already have names.
|
||||
if nameprop, has := req.GetProperties().Fields[nameProperty]; has {
|
||||
name := resource.UnmarshalPropertyValue(nameprop)
|
||||
if name.IsString() {
|
||||
return &murpc.NameResponse{Name: name.StringValue()}, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf(
|
||||
"Resource '%v' had a name property '%v', but it wasn't a string", t, nameProperty)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Resource '%v' was missing a name property '%v'", t, nameProperty)
|
||||
}
|
||||
|
||||
// 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 *Provider) Create(ctx context.Context, req *murpc.CreateRequest) (*murpc.CreateResponse, error) {
|
||||
|
|
|
@ -143,8 +143,10 @@ func execPlugin(name string) (*os.Process, io.WriteCloser, io.ReadCloser, io.Rea
|
|||
func (p *Plugin) Name(t tokens.Type, props PropertyMap) (tokens.QName, error) {
|
||||
glog.V(7).Infof("Plugin[%v].Name(t=%v,#props=%v) executing", p.pkg, t, len(props))
|
||||
req := &murpc.NameRequest{
|
||||
Type: string(t),
|
||||
Properties: MarshalProperties(p.ctx, props),
|
||||
Type: string(t),
|
||||
Properties: MarshalProperties(p.ctx, props, MarshalOptions{
|
||||
SkipMonikers: true, // often used during moniker creation; IDs won't be ready.
|
||||
}),
|
||||
}
|
||||
|
||||
resp, err := p.client.Name(p.ctx.Request(), req)
|
||||
|
@ -163,7 +165,7 @@ func (p *Plugin) Create(t tokens.Type, props PropertyMap) (ID, error, ResourceSt
|
|||
glog.V(7).Infof("Plugin[%v].Create(t=%v,#props=%v) executing", p.pkg, t, len(props))
|
||||
req := &murpc.CreateRequest{
|
||||
Type: string(t),
|
||||
Properties: MarshalProperties(p.ctx, props),
|
||||
Properties: MarshalProperties(p.ctx, props, MarshalOptions{}),
|
||||
}
|
||||
|
||||
resp, err := p.client.Create(p.ctx.Request(), req)
|
||||
|
@ -211,8 +213,8 @@ func (p *Plugin) Update(id ID, t tokens.Type, olds PropertyMap, news PropertyMap
|
|||
req := &murpc.UpdateRequest{
|
||||
Id: string(id),
|
||||
Type: string(t),
|
||||
Olds: MarshalProperties(p.ctx, olds),
|
||||
News: MarshalProperties(p.ctx, news),
|
||||
Olds: MarshalProperties(p.ctx, olds, MarshalOptions{}),
|
||||
News: MarshalProperties(p.ctx, news, MarshalOptions{}),
|
||||
}
|
||||
|
||||
resp, err := p.client.Update(p.ctx.Request(), req)
|
||||
|
|
|
@ -12,61 +12,73 @@ import (
|
|||
"github.com/marapongo/mu/pkg/util/contract"
|
||||
)
|
||||
|
||||
// MarshalOptions controls the marshaling of RPC structures.
|
||||
type MarshalOptions struct {
|
||||
SkipMonikers bool // true to skip monikers (e.g., if they aren't ready yet).
|
||||
}
|
||||
|
||||
// MarshalProperties marshals a resource's property map as a "JSON-like" protobuf structure. Any monikers are replaced
|
||||
// with their resource IDs during marshaling; it is an error to marshal a moniker for a resource without an ID.
|
||||
func MarshalProperties(ctx *Context, props PropertyMap) *structpb.Struct {
|
||||
func MarshalProperties(ctx *Context, props PropertyMap, opts MarshalOptions) *structpb.Struct {
|
||||
result := &structpb.Struct{
|
||||
Fields: make(map[string]*structpb.Value),
|
||||
}
|
||||
for _, key := range StablePropertyKeys(props) {
|
||||
result.Fields[string(key)] = MarshalPropertyValue(ctx, props[key])
|
||||
if v, use := MarshalPropertyValue(ctx, props[key], opts); use {
|
||||
result.Fields[string(key)] = v
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// MarshalPropertyValue marshals a single resource property value into its "JSON-like" value representation.
|
||||
func MarshalPropertyValue(ctx *Context, v PropertyValue) *structpb.Value {
|
||||
func MarshalPropertyValue(ctx *Context, v PropertyValue, opts MarshalOptions) (*structpb.Value, bool) {
|
||||
if v.IsNull() {
|
||||
return &structpb.Value{
|
||||
Kind: &structpb.Value_NullValue{
|
||||
structpb.NullValue_NULL_VALUE,
|
||||
},
|
||||
}
|
||||
}, true
|
||||
} else if v.IsBool() {
|
||||
return &structpb.Value{
|
||||
Kind: &structpb.Value_BoolValue{
|
||||
v.BoolValue(),
|
||||
},
|
||||
}
|
||||
}, true
|
||||
} else if v.IsNumber() {
|
||||
return &structpb.Value{
|
||||
Kind: &structpb.Value_NumberValue{
|
||||
v.NumberValue(),
|
||||
},
|
||||
}
|
||||
}, true
|
||||
} else if v.IsString() {
|
||||
return &structpb.Value{
|
||||
Kind: &structpb.Value_StringValue{
|
||||
v.StringValue(),
|
||||
},
|
||||
}
|
||||
}, true
|
||||
} else if v.IsArray() {
|
||||
var elems []*structpb.Value
|
||||
for _, elem := range v.ArrayValue() {
|
||||
elems = append(elems, MarshalPropertyValue(ctx, elem))
|
||||
if elemv, use := MarshalPropertyValue(ctx, elem, opts); use {
|
||||
elems = append(elems, elemv)
|
||||
}
|
||||
}
|
||||
return &structpb.Value{
|
||||
Kind: &structpb.Value_ListValue{
|
||||
&structpb.ListValue{elems},
|
||||
},
|
||||
}
|
||||
}, true
|
||||
} else if v.IsObject() {
|
||||
return &structpb.Value{
|
||||
Kind: &structpb.Value_StructValue{
|
||||
MarshalProperties(ctx, v.ObjectValue()),
|
||||
MarshalProperties(ctx, v.ObjectValue(), opts),
|
||||
},
|
||||
}
|
||||
}, true
|
||||
} else if v.IsResource() {
|
||||
if opts.SkipMonikers {
|
||||
return nil, false
|
||||
}
|
||||
m := v.ResourceValue()
|
||||
res, has := ctx.MksRes[m]
|
||||
contract.Assertf(has, "Expected resource moniker '%v' to exist at marshal time", m)
|
||||
|
@ -77,10 +89,10 @@ func MarshalPropertyValue(ctx *Context, v PropertyValue) *structpb.Value {
|
|||
Kind: &structpb.Value_StringValue{
|
||||
string(id),
|
||||
},
|
||||
}
|
||||
}, true
|
||||
} else {
|
||||
contract.Failf("Unrecognized property value: %v (type=%v)", v.V, reflect.TypeOf(v.V))
|
||||
return nil
|
||||
return nil, true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue