Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RE-1851 Remove deployments from the integration environment #10594

Merged
merged 1 commit into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading