From 26a2f95c48863c7818cdf6e199796711b477fcbb Mon Sep 17 00:00:00 2001 From: Luke Hoban Date: Sat, 3 Jun 2017 16:36:06 -0700 Subject: [PATCH] Support output properties on aws.apigateway APIs --- lib/aws/idl/apigateway/deployment.go | 5 ++ lib/aws/idl/apigateway/restAPI.go | 16 ++++++ lib/aws/idl/apigateway/stage.go | 5 ++ lib/aws/pack/apigateway/deployment.ts | 2 + lib/aws/pack/apigateway/restAPI.ts | 3 ++ lib/aws/pack/apigateway/stage.ts | 2 + lib/aws/provider/apigateway/deployment.go | 54 +++++++++++++++---- .../apigateway/patchoperations_test.go | 16 +++--- lib/aws/provider/apigateway/restapi.go | 27 +++++++++- lib/aws/provider/apigateway/stage.go | 35 ++++++++++-- lib/aws/rpc/apigateway/deployment.go | 4 ++ lib/aws/rpc/apigateway/restAPI.go | 6 +++ lib/aws/rpc/apigateway/stage.go | 4 ++ pkg/eval/intrinsics_impl.go | 1 - 14 files changed, 155 insertions(+), 25 deletions(-) diff --git a/lib/aws/idl/apigateway/deployment.go b/lib/aws/idl/apigateway/deployment.go index 1a527c264..812cf3faa 100644 --- a/lib/aws/idl/apigateway/deployment.go +++ b/lib/aws/idl/apigateway/deployment.go @@ -32,6 +32,11 @@ type Deployment struct { // stageName is a name for the stage that API Gateway creates with this deployment. Use only alphanumeric // characters. StageName *string `lumi:"stageName,optional"` + + // The identifier for the deployment resource. + ID string `lumi:"id,out"` + // The date and time that the deployment resource was created. + CreatedDate string `lumi:"createdDate,out"` } type StageDescription struct { diff --git a/lib/aws/idl/apigateway/restAPI.go b/lib/aws/idl/apigateway/restAPI.go index 99849e5ea..8af39d95d 100644 --- a/lib/aws/idl/apigateway/restAPI.go +++ b/lib/aws/idl/apigateway/restAPI.go @@ -41,6 +41,22 @@ type RestAPI struct { APIName *string `lumi:"apiName,optional"` // Custom header parameters for the request. Parameters *[]string `lumi:"parameters,optional"` + + // The API's identifier. This identifier is unique across all of your APIs in Amazon API Gateway. + ID string `lumi:"id,out"` + // The timestamp when the API was created. + CreatedDate string `lumi:"createdDate,out"` + // A version identifier for the API. + Version string `lumi:"version,out"` + + // TODO[pulumi/lumi#198] Exposing array-valued output properties + // currently triggers failures serializing resource state, so + // supressing these properties. + // The warning messages reported when failonwarnings is turned on during API import. + //Warnings []string `lumi:"warnings,out"` + // The list of binary media types supported by the RestApi. By default, the RestApi supports only UTF-8-encoded + // text payloads. + //BinaryMediaTypes []string `lumi:"binaryMediaTypes,out"` } // S3Location is a property of the RestAPI resource that specifies the Amazon Simple Storage Service (Amazon S3) diff --git a/lib/aws/idl/apigateway/stage.go b/lib/aws/idl/apigateway/stage.go index e60faf983..6a3b354e7 100644 --- a/lib/aws/idl/apigateway/stage.go +++ b/lib/aws/idl/apigateway/stage.go @@ -43,4 +43,9 @@ type Stage struct { // variable value is the value. Variable names are limited to alphanumeric characters. Values must match the // following regular expression: `[A-Za-z0-9-._~:/?#&=,]+`. Variables *map[string]string `lumi:"variables,optional"` + + // The timestamp when the stage was created. + CreatedDate string `lumi:"createdDate,out"` + // The timestamp when the stage last updated. + LastUpdatedDate string `lumi:"lastUpdatedDate,out"` } diff --git a/lib/aws/pack/apigateway/deployment.ts b/lib/aws/pack/apigateway/deployment.ts index 0f15411b8..fc66f6d5f 100644 --- a/lib/aws/pack/apigateway/deployment.ts +++ b/lib/aws/pack/apigateway/deployment.ts @@ -13,6 +13,8 @@ export class Deployment extends lumi.Resource implements DeploymentArgs { public description?: string; public stageDescription?: StageDescription; public stageName?: string; + @lumi.out public id: string; + @lumi.out public createdDate: string; constructor(name: string, args: DeploymentArgs) { super(); diff --git a/lib/aws/pack/apigateway/restAPI.ts b/lib/aws/pack/apigateway/restAPI.ts index 077d79d51..bd1407245 100644 --- a/lib/aws/pack/apigateway/restAPI.ts +++ b/lib/aws/pack/apigateway/restAPI.ts @@ -14,6 +14,9 @@ export class RestAPI extends lumi.Resource implements RestAPIArgs { public failOnWarnings?: boolean; public apiName?: string; public parameters?: string[]; + @lumi.out public id: string; + @lumi.out public createdDate: string; + @lumi.out public version: string; constructor(name: string, args?: RestAPIArgs) { super(); diff --git a/lib/aws/pack/apigateway/stage.ts b/lib/aws/pack/apigateway/stage.ts index e997244d4..45df942c7 100644 --- a/lib/aws/pack/apigateway/stage.ts +++ b/lib/aws/pack/apigateway/stage.ts @@ -19,6 +19,8 @@ export class Stage extends lumi.Resource implements StageArgs { public description?: string; public methodSettings?: MethodSetting[]; public variables?: {[key: string]: string}; + @lumi.out public createdDate: string; + @lumi.out public lastUpdatedDate: string; constructor(name: string, args: StageArgs) { super(); diff --git a/lib/aws/provider/apigateway/deployment.go b/lib/aws/provider/apigateway/deployment.go index 34f29816d..b73883e67 100644 --- a/lib/aws/provider/apigateway/deployment.go +++ b/lib/aws/provider/apigateway/deployment.go @@ -4,7 +4,6 @@ package apigateway import ( "crypto/sha1" - "errors" "fmt" "strings" @@ -47,11 +46,12 @@ func (p *deploymentProvider) Check(ctx context.Context, obj *apigateway.Deployme // 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 *deploymentProvider) Create(ctx context.Context, obj *apigateway.Deployment) (resource.ID, error) { + // If an explicit name is given, use it. Otherwise, auto-generate a name in part based on the resource name. var stageName string if obj.StageName != nil { stageName = *obj.StageName } else { - stageName = resource.NewUniqueHex(obj.Name+"_", maxDeploymentName, sha1.Size) + stageName = resource.NewUniqueHex(*obj.Name+"_", maxDeploymentName, sha1.Size) } fmt.Printf("Creating APIGateway Deployment '%v'\n", obj.Name) create := &awsapigateway.CreateDeploymentInput{ @@ -63,13 +63,34 @@ func (p *deploymentProvider) Create(ctx context.Context, obj *apigateway.Deploym if err != nil { return "", err } - id := resource.ID(string(obj.RestAPI) + ":" + *deployment.Id + ":" + stageName) + id := resource.ID(string(obj.RestAPI) + ":" + *deployment.Id) return id, nil } // Get reads the instance state identified by ID, returning a populated resource object, or an error if not found. func (p *deploymentProvider) Get(ctx context.Context, id resource.ID) (*apigateway.Deployment, error) { - return nil, errors.New("Not yet implemented - Get") + parts := strings.Split(id.String(), ":") + contract.Assertf(len(parts) == 2, "expected deployment ID to be of the form :") + restAPIID := parts[0] + deploymentID := parts[1] + + resp, err := p.ctx.APIGateway().GetDeployment(&awsapigateway.GetDeploymentInput{ + RestApiId: aws.String(restAPIID), + DeploymentId: aws.String(deploymentID), + }) + if err != nil { + return nil, err + } + if resp == nil || resp.Id == nil { + return nil, nil + } + + return &apigateway.Deployment{ + RestAPI: resource.ID(restAPIID), + ID: aws.StringValue(resp.Id), + Description: resp.Description, + CreatedDate: resp.CreatedDate.String(), + }, nil } // InspectChange checks what impacts a hypothetical update will have on the resource's properties. @@ -88,7 +109,7 @@ func (p *deploymentProvider) Update(ctx context.Context, id resource.ID, } if len(ops) > 0 { parts := strings.Split(id.String(), ":") - contract.Assertf(len(parts) == 3, "expected stage ID to be of the form :") + contract.Assertf(len(parts) == 2, "expected deployment ID to be of the form :") deploymentID := parts[1] update := &awsapigateway.UpdateDeploymentInput{ RestApiId: aws.String(string(new.RestAPI)), @@ -107,14 +128,27 @@ func (p *deploymentProvider) Update(ctx context.Context, id resource.ID, func (p *deploymentProvider) Delete(ctx context.Context, id resource.ID) error { fmt.Printf("Deleting APIGateway Deployment '%v'\n", id) parts := strings.Split(id.String(), ":") - contract.Assertf(len(parts) == 3, "expected stage ID to be of the form :") + contract.Assertf(len(parts) == 2, "expected deployment ID to be of the form :") restAPIID := parts[0] deploymentID := parts[1] - stageName := parts[2] - _, err := p.ctx.APIGateway().DeleteStage(&awsapigateway.DeleteStageInput{ - RestApiId: aws.String(restAPIID), - StageName: aws.String(stageName), + resp, err := p.ctx.APIGateway().GetStages(&awsapigateway.GetStagesInput{ + RestApiId: aws.String(restAPIID), + DeploymentId: aws.String(deploymentID), }) + if err != nil || resp == nil { + return err + } + if len(resp.Item) == 1 { + // Assume that the single stage associated with this deployment + // is the stage that was automatically created along with the deployment. + _, err := p.ctx.APIGateway().DeleteStage(&awsapigateway.DeleteStageInput{ + RestApiId: aws.String(restAPIID), + StageName: resp.Item[0].StageName, + }) + if err != nil { + return err + } + } _, err = p.ctx.APIGateway().DeleteDeployment(&awsapigateway.DeleteDeploymentInput{ RestApiId: aws.String(restAPIID), DeploymentId: aws.String(deploymentID), diff --git a/lib/aws/provider/apigateway/patchoperations_test.go b/lib/aws/provider/apigateway/patchoperations_test.go index bab7945d2..985c887a4 100644 --- a/lib/aws/provider/apigateway/patchoperations_test.go +++ b/lib/aws/provider/apigateway/patchoperations_test.go @@ -11,7 +11,7 @@ import ( type TestStruct struct { Number float64 `json:"number"` - OptionalString *string `json:"string,omitempty"` + OptionalString *string `json:"optionalString,omitempty"` OptionalNumber *float64 `json:"optionalNumber,omitempty"` OptionalBool *bool `json:"optionalBool,omitempty"` OptionalArray *[]TestStruct `json:"optionalArray,omitempty"` @@ -45,31 +45,31 @@ func Test(t *testing.T) { expectedPatchOps := []*apigateway.PatchOperation{ &apigateway.PatchOperation{ Op: aws.String("add"), - Path: aws.String("/OptionalArray/1"), - Value: aws.String("{\"Number\": 1}"), + Path: aws.String("/optionalArray/1"), + Value: aws.String("{\"number\": 1}"), }, &apigateway.PatchOperation{ Op: aws.String("replace"), - Path: aws.String("/OptionalArray/0/Number"), + Path: aws.String("/optionalArray/0/number"), Value: aws.String("3"), }, &apigateway.PatchOperation{ Op: aws.String("add"), - Path: aws.String("/OptionalArray/0/OptionalBool"), + Path: aws.String("/optionalArray/0/optionalBool"), Value: aws.String("true"), }, &apigateway.PatchOperation{ Op: aws.String("remove"), - Path: aws.String("/OptionalBool"), + Path: aws.String("/optionalBool"), }, &apigateway.PatchOperation{ Op: aws.String("add"), - Path: aws.String("/OptionalNumber"), + Path: aws.String("/optionalNumber"), Value: aws.String("3"), }, &apigateway.PatchOperation{ Op: aws.String("replace"), - Path: aws.String("/OptionalString"), + Path: aws.String("/optionalString"), Value: aws.String("goodbye"), }, } diff --git a/lib/aws/provider/apigateway/restapi.go b/lib/aws/provider/apigateway/restapi.go index 4b273ef77..5865ef42c 100644 --- a/lib/aws/provider/apigateway/restapi.go +++ b/lib/aws/provider/apigateway/restapi.go @@ -46,11 +46,12 @@ func (p *restAPIProvider) Check(ctx context.Context, obj *apigateway.RestAPI) ([ // 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 *restAPIProvider) Create(ctx context.Context, obj *apigateway.RestAPI) (resource.ID, error) { + // If an explicit name is given, use it. Otherwise, auto-generate a name in part based on the resource name. var apiName string if obj.APIName != nil { apiName = *obj.APIName } else { - apiName = resource.NewUniqueHex(obj.Name+"-", maxRestAPIName, sha1.Size) + apiName = resource.NewUniqueHex(*obj.Name+"-", maxRestAPIName, sha1.Size) } // First create the API Gateway @@ -87,7 +88,29 @@ func (p *restAPIProvider) Create(ctx context.Context, obj *apigateway.RestAPI) ( // Get reads the instance state identified by ID, returning a populated resource object, or an error if not found. func (p *restAPIProvider) Get(ctx context.Context, id resource.ID) (*apigateway.RestAPI, error) { - return nil, errors.New("Not yet implemented - Get") + resp, err := p.ctx.APIGateway().GetRestApi(&awsapigateway.GetRestApiInput{ + RestApiId: aws.String(string(id)), + }) + if err != nil { + return nil, err + } + if resp == nil || resp.Id == nil { + return nil, nil + } + + return &apigateway.RestAPI{ + ID: aws.StringValue(resp.Id), + APIName: resp.Name, + Description: resp.Description, + CreatedDate: resp.CreatedDate.String(), + Version: aws.StringValue(resp.Version), + + // TODO[pulumi/lumi#198] Exposing array-valued output properties + // currently triggers failures serializing resource state, so + // supressing these properties. + // Warnings: aws.StringValueSlice(resp.Warnings), + // BinaryMediaTypes: aws.StringValueSlice(resp.BinaryMediaTypes), + }, nil } // InspectChange checks what impacts a hypothetical update will have on the resource's properties. diff --git a/lib/aws/provider/apigateway/stage.go b/lib/aws/provider/apigateway/stage.go index 2d3bd3db3..96887810f 100644 --- a/lib/aws/provider/apigateway/stage.go +++ b/lib/aws/provider/apigateway/stage.go @@ -3,7 +3,6 @@ package apigateway import ( - "errors" "fmt" "github.com/aws/aws-sdk-go/aws" @@ -52,7 +51,7 @@ func (p *stageProvider) Create(ctx context.Context, obj *apigateway.Stage) (reso } fmt.Printf("Creating APIGateway Stage '%v' with stage name '%v'\n", obj.Name, obj.StageName) parts := strings.Split(string(obj.Deployment), ":") - contract.Assertf(len(parts) == 3, "expected deployment ID to be of the form ::") + contract.Assertf(len(parts) == 2, "expected deployment ID to be of the form :") deploymentID := parts[1] create := &awsapigateway.CreateStageInput{ StageName: aws.String(obj.StageName), @@ -75,7 +74,35 @@ func (p *stageProvider) Create(ctx context.Context, obj *apigateway.Stage) (reso // Get reads the instance state identified by ID, returning a populated resource object, or an error if not found. func (p *stageProvider) Get(ctx context.Context, id resource.ID) (*apigateway.Stage, error) { - return nil, errors.New("Not yet implemented - Get") + parts := strings.Split(id.String(), ":") + contract.Assertf(len(parts) == 2, "expected stage ID to be of the form :") + restAPIID := parts[0] + stageName := parts[1] + + resp, err := p.ctx.APIGateway().GetStage(&awsapigateway.GetStageInput{ + RestApiId: aws.String(restAPIID), + StageName: aws.String(stageName), + }) + if err != nil { + return nil, err + } + if resp == nil || resp.DeploymentId == nil { + return nil, nil + } + deploymentID := resource.ID(restAPIID + ":" + aws.StringValue(resp.DeploymentId)) + variables := aws.StringValueMap(resp.Variables) + + return &apigateway.Stage{ + RestAPI: resource.ID(restAPIID), + Deployment: deploymentID, + CacheClusterEnabled: resp.CacheClusterEnabled, + CacheClusterSize: resp.CacheClusterSize, + StageName: aws.StringValue(resp.StageName), + Variables: &variables, + Description: resp.Description, + CreatedDate: resp.CreatedDate.String(), + LastUpdatedDate: resp.LastUpdatedDate.String(), + }, nil } // InspectChange checks what impacts a hypothetical update will have on the resource's properties. @@ -95,7 +122,7 @@ func (p *stageProvider) Update(ctx context.Context, id resource.ID, if diff.Updated(apigateway.Stage_Deployment) { parts := strings.Split(string(new.Deployment), ":") - contract.Assertf(len(parts) == 3, "expected deployment ID to be of the form ::") + contract.Assertf(len(parts) == 2, "expected deployment ID to be of the form :") deploymentID := parts[1] ops = append(ops, &awsapigateway.PatchOperation{ Op: aws.String("replace"), diff --git a/lib/aws/rpc/apigateway/deployment.go b/lib/aws/rpc/apigateway/deployment.go index 038e116f2..e6806dedb 100644 --- a/lib/aws/rpc/apigateway/deployment.go +++ b/lib/aws/rpc/apigateway/deployment.go @@ -179,6 +179,8 @@ type Deployment struct { Description *string `json:"description,omitempty"` StageDescription *StageDescription `json:"stageDescription,omitempty"` StageName *string `json:"stageName,omitempty"` + ID string `json:"id,omitempty"` + CreatedDate string `json:"createdDate,omitempty"` } // Deployment's properties have constants to make dealing with diffs and property bags easier. @@ -188,6 +190,8 @@ const ( Deployment_Description = "description" Deployment_StageDescription = "stageDescription" Deployment_StageName = "stageName" + Deployment_ID = "id" + Deployment_CreatedDate = "createdDate" ) /* Marshalable StageDescription structure(s) */ diff --git a/lib/aws/rpc/apigateway/restAPI.go b/lib/aws/rpc/apigateway/restAPI.go index 197037402..4e8e4d8c2 100644 --- a/lib/aws/rpc/apigateway/restAPI.go +++ b/lib/aws/rpc/apigateway/restAPI.go @@ -182,6 +182,9 @@ type RestAPI struct { FailOnWarnings *bool `json:"failOnWarnings,omitempty"` APIName *string `json:"apiName,omitempty"` Parameters *[]string `json:"parameters,omitempty"` + ID string `json:"id,omitempty"` + CreatedDate string `json:"createdDate,omitempty"` + Version string `json:"version,omitempty"` } // RestAPI's properties have constants to make dealing with diffs and property bags easier. @@ -194,6 +197,9 @@ const ( RestAPI_FailOnWarnings = "failOnWarnings" RestAPI_APIName = "apiName" RestAPI_Parameters = "parameters" + RestAPI_ID = "id" + RestAPI_CreatedDate = "createdDate" + RestAPI_Version = "version" ) /* Marshalable S3Location structure(s) */ diff --git a/lib/aws/rpc/apigateway/stage.go b/lib/aws/rpc/apigateway/stage.go index bfb503b3e..259ddb40a 100644 --- a/lib/aws/rpc/apigateway/stage.go +++ b/lib/aws/rpc/apigateway/stage.go @@ -190,6 +190,8 @@ type Stage struct { Description *string `json:"description,omitempty"` MethodSettings *[]MethodSetting `json:"methodSettings,omitempty"` Variables *map[string]string `json:"variables,omitempty"` + CreatedDate string `json:"createdDate,omitempty"` + LastUpdatedDate string `json:"lastUpdatedDate,omitempty"` } // Stage's properties have constants to make dealing with diffs and property bags easier. @@ -204,6 +206,8 @@ const ( Stage_Description = "description" Stage_MethodSettings = "methodSettings" Stage_Variables = "variables" + Stage_CreatedDate = "createdDate" + Stage_LastUpdatedDate = "lastUpdatedDate" ) diff --git a/pkg/eval/intrinsics_impl.go b/pkg/eval/intrinsics_impl.go index cfc90279e..483ceb532 100644 --- a/pkg/eval/intrinsics_impl.go +++ b/pkg/eval/intrinsics_impl.go @@ -21,7 +21,6 @@ import ( "fmt" "sort" "strconv" - "strings" "github.com/pulumi/lumi/pkg/compiler/ast"