Skip to content

AWS API Gateway Stage Delete Issues #3289

Closed

Description

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

  1. Add Lambda urn and region
  2. Run pulumi up . (Verify paths exists in the AWS Console)
  3. Remove an endpoint for Paths Array (The endpoints should be correct in Resources, but incorrect in Stages)
  4. Remove or add another endpoint from Paths Array
  5. 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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Labels

kind/bugSome behavior is incorrect or out of specresolution/by-designThis issue won't be fixed because the functionality is working as designed

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions