Skip to content

Commit

Permalink
Remove deployments for the integration environment
Browse files Browse the repository at this point in the history
  • Loading branch information
HenryNguyen5 committed Sep 13, 2023
1 parent 411c119 commit b35e694
Show file tree
Hide file tree
Showing 8 changed files with 845 additions and 7 deletions.
59 changes: 59 additions & 0 deletions .github/actions/delete-deployments/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Delete Deployments
description: Delete deployments by env and ref
inputs:
environment:
required: true
description: The Github environment to filter deployments by
ref:
required: true
description: The ref to filter deployments by
dry-run:
required: false
description: Whether to actually delete deployments or not
github-token:
description: "The Github token to use for authentication"
required: true
default: ${{ github.token }}
num-of-pages:
required: false
description: The number of pages (of 100 per page) to fetch deployments from, set to 'all' to fetch all deployments
default: "all"
starting-page:
required: false
description: The page to start fetching deployments from, only valid if num-of-pages is set to a number
repository:
required: false
description: The owner and repository name to delete deployments from, defaults to the current repository, ex. 'smartcontractkit/chainlink'
default: ${{ github.repository }}

runs:
using: composite
steps:
- uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd #v2.2.4
with:
version: ^8.0.0

- uses: actions/setup-node@v3
with:
node-version: "18"
cache: "pnpm"
cache-dependency-path: "./.github/actions/delete-deployments/pnpm-lock.yaml"

- name: Install dependencies
shell: bash
run: pnpm i --prod
working-directory: "./.github/actions/delete-deployments"

- name: Run deployment deleter
shell: bash
run: pnpm start
env:
NUM_OF_PAGES: ${{ inputs.num-of-pages }}
STARTING_PAGE: ${{ inputs.starting-page }}
GITHUB_TOKEN: ${{ inputs.github-token }}
ENVIRONMENT: ${{ inputs.environment }}
REF: ${{ inputs.ref }}
DRY_RUN: ${{ inputs.dry-run }}
OWNER: ${{ inputs.owner }}
REPOSITORY: ${{ inputs.repository }}
working-directory: "./.github/actions/delete-deployments"
232 changes: 232 additions & 0 deletions .github/actions/delete-deployments/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import { Octokit } from "@octokit/action";
import { info, warning, isDebug } from "@actions/core";
import { throttling } from "@octokit/plugin-throttling";
import { retry } from "@octokit/plugin-retry";

async function main() {
const {
dryRun,
environment,
numOfPages,
owner,
ref,
repo,
debug,
startingPage,
} = getInputs();
const octokit = getOctokit(debug);

const deployments = await getDeployments({
octokit,
owner,
repo,
environment,
ref,
paginateOptions: {
numOfPages,
startingPage,
},
});
const deploymentIds = deployments.map((d) => d.id);
if (dryRun) {
info(`Dry run: would delete deployments (${deploymentIds.length})`);
return;
}

info(`Deleting deployments (${deploymentIds.length})`);
const deleteDeployments = deploymentIds.map(async (id) => {
const sharedArgs = {
owner,
repo,
deployment_id: id,
request: {
retries: 0,
},
};

const setStatus = await octokit.repos
.createDeploymentStatus({
...sharedArgs,
state: "inactive",
})
.then(() => true)
.catch((e) => {
warning(
`Marking deployment id ${id} to "inactive" failed: ${e.message}`
);
return false;
});
if (!setStatus) return false;

return octokit.repos
.deleteDeployment({
...sharedArgs,
})
.then(() => true)
.catch((e) => {
warning(`Deleting deployment id ${id} failed: ${e.message}`);
return false;
});
});

const processed = await Promise.all(deleteDeployments);
const succeeded = processed.filter((p) => !!p);
info(
`Successfully deleted ${succeeded.length}/${processed.length} deployments`
);
}
main();

function getInputs() {
const debug = !!(process.env.DEBUG || isDebug());

const dryRun = process.env.DRY_RUN === "true";

const environment = process.env.ENVIRONMENT;
if (!environment) throw new Error("ENVIRONMENT not set");

const ref = process.env.REF;

const repository = process.env.REPOSITORY;
if (!repository) throw new Error("REPOSITORY not set");
const [owner, repo] = repository.split("/");

const rawStartingPage = process.env.STARTING_PAGE;

let startingPage: number | undefined;
if (rawStartingPage) {
startingPage = parseInt(rawStartingPage);
if (isNaN(startingPage)) {
throw new Error(`STARTING_PAGE is not a number: ${rawStartingPage}`);
}
if (startingPage < 0) {
throw new Error(
`STARTING_PAGE must be a positive integer or zero: ${rawStartingPage}`
);
}
info(`Starting from page ${startingPage}`);
}

const rawNumOfPages = process.env.NUM_OF_PAGES;
let numOfPages: "all" | number = "all";
if (rawNumOfPages === "all") {
info("Fetching all pages of deployments");
} else {
const parsedPages = parseInt(rawNumOfPages || "");
if (isNaN(parsedPages)) {
throw new Error(`NUM_OF_PAGES is not a number: ${rawNumOfPages}`);
}
if (parsedPages < 1) {
throw new Error(`NUM_OF_PAGES must be greater than 0: ${rawNumOfPages}`);
}
numOfPages = parsedPages;
}

if (numOfPages === "all" && startingPage) {
throw new Error(`Cannot use STARTING_PAGE with NUM_OF_PAGES=all`);
}

const parsedInputs = {
environment,
ref,
owner,
repo,
numOfPages,
startingPage,
dryRun,
debug,
};
info(`Configuration: ${JSON.stringify(parsedInputs)}`);
return parsedInputs;
}

function getOctokit(debug: boolean) {
const OctokitAPI = Octokit.plugin(throttling, retry);
const octokit = new OctokitAPI({
log: debug ? console : undefined,
throttle: {
onRateLimit: (retryAfter, options, octokit, retryCount) => {
octokit.log.warn(
// Types are busted from octokit
//@ts-expect-error
`Request quota exhausted for request ${options.method} ${options.url}`
);

octokit.log.info(`Retrying after ${retryAfter} seconds!`);
return true;
},
onSecondaryRateLimit: (_retryAfter, options, octokit) => {
octokit.log.warn(
// Types are busted from octokit
//@ts-expect-error
`SecondaryRateLimit detected for request ${options.method} ${options.url}`
);
return true;
},
},
});

return octokit;
}

async function getDeployments({
octokit,
owner,
repo,
environment,
ref,
paginateOptions,
}: {
octokit: ReturnType<typeof getOctokit>;
owner: string;
repo: string;
environment: string;
ref?: string;
paginateOptions: {
numOfPages: number | "all";
startingPage?: number;
};
}) {
const listDeploymentsSharedArgs: Parameters<
typeof octokit.repos.listDeployments
>[0] = {
owner,
repo,
environment,
ref,
per_page: 100,
request: {
retries: 20,
},
};

if (paginateOptions.numOfPages === "all") {
info(`Fetching all deployments`);
const deployments = await octokit.paginate(octokit.repos.listDeployments, {
...listDeploymentsSharedArgs,
});

return deployments;
} else {
info(
`Fetching ${
paginateOptions.numOfPages * listDeploymentsSharedArgs.per_page!
} deployments`
);
const deployments: Awaited<
ReturnType<typeof octokit.repos.listDeployments>
>["data"] = [];

const offset = paginateOptions.startingPage || 0;
for (let i = offset; i < paginateOptions.numOfPages + offset; i++) {
const deploymentPage = await octokit.repos.listDeployments({
...listDeploymentsSharedArgs,
page: i,
});

deployments.push(...deploymentPage.data);
}

return deployments;
}
}
25 changes: 25 additions & 0 deletions .github/actions/delete-deployments/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "delete-deployments",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"start": "ts-node -T .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@actions/core": "^1.10.1",
"@octokit/action": "^6.0.5",
"@octokit/plugin-retry": "^6.0.0",
"@octokit/plugin-throttling": "^7.0.0",
"ts-node": "^10.9.1"
},
"devDependencies": {
"@octokit/types": "^11.1.0",
"@types/node": "^18",
"typescript": "^5.2.2"
}
}
Loading

0 comments on commit b35e694

Please sign in to comment.