Description
openedon Jan 19, 2024
What happened?
The API Gateway's API Resources are updated as expected when updating the API Gateway, but the Stage is not. As a result, the deleted Stage still exists in AWS, while the output of the Pulumi run indicates that the Stage is deleted.
The deleted Stage endpoint is not reflected in AWS until another AWS Resource endpoint kicks off a new/updated deployment, which results in a "delayed delete."
Example
- Add Lambda urn and region
- Run
pulumi up
. (Verify paths exists in the AWS Console) - Remove an endpoint for Paths Array (The endpoints should be correct in Resources, but incorrect in Stages)
- Remove or add another endpoint from Paths Array
- Run
pulumi up
(The Stage endpoint from the previous run should now be deleted)
import * as aws from '@pulumi/aws';
import * as pulumi from '@pulumi/pulumi';
import * as crypto from "crypto";
import { InlineProgramArgs, LocalWorkspace } from '@pulumi/pulumi/automation';
// *************************************************************************************
// CONFIGURATION:
const API_NAME = 'api-demo-issue';
const REGION = ''; // region
const LAMBDA_ARN = ''; // <--- arn
const PATHS = ['users', 'cities', 'jobs']; // <--- add/remove paths here
// *************************************************************************************
const apiGateway = (apiName:string) => {
console.log('Creating API Gateway');
const awsProvider = createAwsProvider(REGION);
const api = createApi(`${apiName}-api`, apiName, awsProvider);
let dependencies: pulumi.Resource[] = [];
PATHS.forEach(path => {
const resource = createResource(`${apiName}-${path}-resource`, api, path, awsProvider, api.rootResourceId);
dependencies.push(resource);
const apiMethod = createMethod(`${apiName}-${path}-method`, api, resource, 'GET', awsProvider);
dependencies.push(apiMethod);
const integration = createIntegration(`${apiName}-${path}-integration`, api, resource, apiMethod, LAMBDA_ARN, awsProvider, REGION);
dependencies.push(integration);
});
const permission = createPermission(`${apiName}-permission`, api, LAMBDA_ARN, awsProvider);
dependencies.push(permission);
pulumi.all([...dependencies]).apply(dependencies => {
const deployment = createDepoyment(`${apiName}-deployment`, api, awsProvider, dependencies);
const stage = createStage(`${apiName}-stage`, api, deployment, awsProvider);
});
return api;
};
const createAwsProvider = (region: pulumi.Input<aws.Region> | undefined) => {
return new aws.Provider('custom-provider', {
region
});
};
const createApi = (name:string, apiName:string, provider: aws.Provider) => {
return new aws.apigateway.RestApi(name, {
name: apiName,
endpointConfiguration: {
types: 'REGIONAL'
}
}, {
provider
});
};
const createDepoyment = (name: string, api: aws.apigateway.RestApi, provider: aws.Provider, dependencies: pulumi.Resource[]) => {
const endpointsHash = crypto.createHash('sha1').update(JSON.stringify(PATHS)).digest('hex');
console.log({endpointsHash});
return new aws.apigateway.Deployment(name, {
restApi: api.id,
triggers: {
redeployment: endpointsHash
},
}, {
provider,
dependsOn: dependencies
});
};
const createStage = (name: string, api: aws.apigateway.RestApi, deployment: aws.apigateway.Deployment, provider: aws.Provider) => {
return new aws.apigateway.Stage(name, {
restApi: api.id,
deployment: deployment.id,
stageName: 'prod',
}, {
provider,
dependsOn: [deployment]
});
};
const createResource = (name: string, api: aws.apigateway.RestApi, pathPart: string, provider: aws.Provider, parentId: pulumi.Output<string>) => {
return new aws.apigateway.Resource(name, {
restApi: api.id,
parentId: parentId,
pathPart,
}, {
provider,
dependsOn: [api]
});
};
const createMethod = (name: string, api: aws.apigateway.RestApi, resource: aws.apigateway.Resource, method: string, provider: aws.Provider) => {
return new aws.apigateway.Method(name, {
restApi: api.id,
resourceId: resource.id,
httpMethod: method,
authorization: 'NONE',
}, {
provider,
dependsOn: [resource]
});
};
const createIntegration = (name: string, api: aws.apigateway.RestApi, resource: aws.apigateway.Resource, method: aws.apigateway.Method, lambdaArn: string, provider: aws.Provider, region: string) => {
const lambdaUri = `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations`;
return new aws.apigateway.Integration(name, {
restApi: api.id,
resourceId: resource.id,
httpMethod: method.httpMethod.apply(method => method),
integrationHttpMethod: 'POST',
type: 'AWS_PROXY',
uri: lambdaUri,
}, {
provider,
dependsOn: [method]
});
};
const createPermission = (name: string, api: aws.apigateway.RestApi, lambdaArn: string, provider: aws.Provider) => {
return new aws.lambda.Permission(name, {
action: 'lambda:InvokeFunction',
function: lambdaArn,
principal: 'apigateway.amazonaws.com',
sourceArn: api.executionArn.apply(arn => `${arn}/*/*/*`)
}, {
provider,
dependsOn: [api]
});
};
(async () => {
console.log('starting...');
const pulumiProgram = async () => {
apiGateway(API_NAME);
};
const args: InlineProgramArgs = {
projectName: API_NAME,
stackName: API_NAME,
program: pulumiProgram
};
const stack = await LocalWorkspace.createOrSelectStack(args);
console.log('Pulumi stack.up()');
await stack.up({ onOutput: console.log }).catch(err => console.log(err));
})();
Output of pulumi about
Customer's Version
Dependencies:
NAME VERSION
@aws-sdk/client-api-gateway 3.490.0
@pulumi/aws 6.18.0
@pulumi/pulumi 3.101.1
My Version
Dependencies:
NAME VERSION
@pulumi/aws 6.13.3
@pulumi/pulumi 3.100.0
Additional context
No response
Contributing
Vote on this issue by adding a 👍 reaction.
To contribute a fix for this issue, leave a comment (and link to your pull request, if you've opened one already).