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

Add github cleanup actions #1

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
932573d
Add github cleanup action
nmlinaric May 20, 2021
35694e8
Update descriptions
nmlinaric May 20, 2021
668c190
Update test action
nmlinaric May 20, 2021
b08361f
Fix action level
nmlinaric May 21, 2021
2ada068
Fix indentation
nmlinaric May 21, 2021
5f428d3
Fix indentation
nmlinaric May 21, 2021
681814c
Try to run action
nmlinaric May 21, 2021
60eec8d
Another try
nmlinaric May 21, 2021
b141791
Another try
nmlinaric May 21, 2021
91dc4d1
fix test ci
mpetrunic May 24, 2021
301ff59
fix image name
mpetrunic May 24, 2021
fa70d25
fix image not pulling
mpetrunic May 24, 2021
2516d45
Don't require excluded versions
nmlinaric May 25, 2021
06c8d38
Use double quotes
nmlinaric May 31, 2021
4a3c46e
Use node v12
nmlinaric May 31, 2021
48f7eeb
Install dependecies and add build step in test
nmlinaric May 31, 2021
5e3d463
Fix required params
nmlinaric May 31, 2021
13d1bbf
Pipe deleted packages to logger
nmlinaric May 31, 2021
9594045
Log deteled packages
nmlinaric May 31, 2021
e17febc
Add postgres image to test
nmlinaric May 31, 2021
6356625
Fix typos
nmlinaric Jun 2, 2021
d337831
Call main function
nmlinaric Jun 2, 2021
45c73db
Add gitignore
nmlinaric Jun 2, 2021
a3e58e9
Remove branch from test
nmlinaric Jun 14, 2021
d749c0b
Remove second image from test
nmlinaric Jun 14, 2021
f455847
Wrap main func with try-catch
nmlinaric Jun 14, 2021
63ada88
Pass ogranization input to test workflow
nmlinaric Jun 17, 2021
b4db41d
Use PAT for auth
nmlinaric Jun 18, 2021
4ecbddd
Fix organisation name
nmlinaric Jun 23, 2021
e18418b
Fix package filtering
nmlinaric Jun 23, 2021
fbf50ff
Fix filtering + passing data to action
nmlinaric Jun 24, 2021
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
40 changes: 40 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Test action

on:
push:
branches:
- 'master'
pull_request:
branches:
- 'master'
mpetrunic marked this conversation as resolved.
Show resolved Hide resolved

jobs:
test:
name: Test GitHub action
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: ghcr.io
- name: Install dependencies
run: yarn install
- name: Build
run: yarn run build
- name: tag hello world image
run: docker pull hello-world:latest && docker tag hello-world:latest ghcr.io/nodefactoryio/github-packages-cleanup-sample:${{github.sha}}
- name: push hello world image
run: docker push ghcr.io/nodefactoryio/github-packages-cleanup-sample:${{github.sha}}
- name: Run cleanup action
uses: ./
id: deleted-packages
with:
token: ${{ secrets.GITHUB_TOKEN }}
package_name: github-packages-cleanup-sample
num_versions_to_keep: 1
nmlinaric marked this conversation as resolved.
Show resolved Hide resolved
- name: Log deleted packages
run: echo "Deleted packages ${{ steps.deleted-packages.outputs.DELETED_PACKAGES }}"
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules/
dist/
.idea
.env
.nyc_output
lib
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# GitHub packages cleanup

GitHub action for deleting versions of a package from [GitHub Packages](https://github.com/features/packages).
GitHub action for ghcr images cleanup from [GitHub Packages](https://github.com/features/packages).

## Things that can be done
* Delete a single version
* Delete multiple versions
* Delete oldest version(s)
* Delete packages owned by an user or organisation
* Delete packages owned by an user or organization
* Delete version(s) of a package that is hosted in the same repo that is executing the workflow
* Delete version(s) of a package that is hosted in a different repo than the one executing the workflow
25 changes: 25 additions & 0 deletions action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: "github ghcr images cleanup"
author: "NodeFactory"
description: "github ghcr images cleanup from the github package registry"
inputs:
excluded_versions:
description: "allows to exclude certain versions, eg. x.y.z, stable, beta, latest,..."
required: false
num_versions_to_keep:
description: "number of most recent versions to keep (excluded versions aren't included into count"
required: true
package_name:
description: "name of github package"
required: true
token:
description: "github auth token"
required: true
username:
description: "github username"
required: false
organization:
description: "github organization"
required: false
runs:
using: "node12"
main: "dist/index.js"
28 changes: 28 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "github-packages-cleanup",
"version": "1.0.0",
"description": "TypeScript github action for ghcr images cleanup",
"main": "dist/index.js",
"author": {
"name": "NodeFactory",
"email": "[email protected]"
},
"license": "MIT",
"private": false,
"repository": {
"type": "git",
"url": "git+https://[email protected]:nodefactoryio/github-packages-cleanup.git"
},
"scripts": {
"build": "ncc build src/index.ts"
},
"dependencies": {
"@actions/core": "^1.2.7",
"@actions/github": "^4.0.0"
},
"devDependencies": {
"@types/node": "^15.0.2",
"@vercel/ncc": "^0.28.5",
"typescript": "^4.2.4"
}
}
112 changes: 112 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { getInput, setFailed, setOutput } from "@actions/core";
import { getOctokit } from "@actions/github";
import { OctokitResponse } from "@octokit/types"
import { FetchPackagesResponse } from "./types";

const excludedVersion = getInput('excluded_versions', { required: false });
const numberOfVersionsToKeep = Number(getInput('num_versions_to_keep', { required: true }));
const packageName = getInput('package_name', { required: true });
const token = getInput('token', { required: true });
const username = getInput('username', { required: false });
const organization = getInput('organization', { required: false });


const octokit = getOctokit(token);

async function main() {
try {
// delete packages with token auth
if (token && !username && !organization) {
const fetchedPackages = await getAuthUserPackageVersions();
const packagesToDelete = filterOutPackages(fetchedPackages);
packagesToDelete.forEach(async (element) => {
const output = await deleteAuthUserPackageVersions(element!.id);
setOutput('DELETED_PACKAGES', output);
});
// delete user packages
} else if (token && username && !organization) {
const fetchedPackages = await getUserPackageVersions();
const packagesToDelete = filterOutPackages(fetchedPackages);
packagesToDelete.forEach(async (element) => {
const output = await deleteUserPackageVersions(element!.id);
setOutput('DELETED_PACKAGES', output);
});
// delete organization packages
} else if (token && !username && organization) {
const fetchedPackages = await getOrganizationPackageVersions();
const packagesToDelete = filterOutPackages(fetchedPackages);
packagesToDelete.forEach(async (element) => {
const output = await deleteOrganizationPackageVersions(element!.id);
setOutput('DELETED_PACKAGES', output);
});
} else {
setFailed("Failed to fetch packages");
}
} catch (e) {
console.error(`Deleting package failed because of: ${e}`);
}
}

async function getAuthUserPackageVersions(): Promise<OctokitResponse<FetchPackagesResponse[], number>> {
return await octokit.request('GET /user/packages/{package_type}/{package_name}/versions', {
package_type: 'container',
package_name: packageName,
});
}

async function deleteAuthUserPackageVersions(packageId: number): Promise<OctokitResponse<FetchPackagesResponse[], number>> {
return await octokit.request('DELETE /user/packages/{package_type}/{package_name}/versions/{package_version_id}', {
package_type: 'container',
package_name: packageName,
package_version_id: packageId
});
}

async function getUserPackageVersions(): Promise<OctokitResponse<FetchPackagesResponse[], number>> {
return await octokit.request('GET /users/{username}/packages/{package_type}/{package_name}/versions', {
package_type: 'container',
package_name: packageName,
username: username
});
}

async function deleteUserPackageVersions(packageId: number): Promise<OctokitResponse<FetchPackagesResponse[], number>> {
return await octokit.request('DELETE /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}', {
package_type: 'container',
package_name: packageName,
username: username,
package_version_id: packageId
});
}

async function getOrganizationPackageVersions(): Promise<OctokitResponse<FetchPackagesResponse[], number>> {
return await octokit.request('GET /orgs/{org}/packages/{package_type}/{package_name}/versions', {
package_type: 'container',
package_name: packageName,
org: organization
});
}

async function deleteOrganizationPackageVersions(packageId: number): Promise<OctokitResponse<FetchPackagesResponse[], number>> {
return await octokit.request('DELETE /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}', {
package_type: 'container',
package_name: packageName,
org: organization,
package_version_id: packageId
});
}

function filterOutPackages(existingPackages: OctokitResponse<FetchPackagesResponse[], number>): (FetchPackagesResponse | undefined)[] {
return existingPackages.data.map((item) => {
// find package versions matching regex
if (!item.metadata?.container?.tags[0]?.match(excludedVersion)) {
return item;
};
})
// filter out undefined values
.filter(item => item)
// packages for deletion - omitting last n values
.slice(0, length - numberOfVersionsToKeep);
}

main();
20 changes: 20 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export type PackageType = "npm" | "docker" | "container" | "maven" | "rubygems" | "nuget";

export type FetchPackagesResponse = {
id: number;
name: string;
url: string;
package_html_url?: string;
html_url?: string;
license?: string;
description?: string;
created_at: string;
updated_at: string;
deleted_at?: string;
metadata?: {
package_type: PackageType;
container?: {
tags: Array<any>
}
}
}
29 changes: 29 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"sourceMap": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"strict": true,
"noImplicitAny": true,
"resolveJsonModule": true,
"typeRoots": [
"./node_modules/@types",
"./@types"
]
},
"ts-node": {
"transpileOnly": true,
"files": true
},
"include": [
"src/**/*.ts",
],
"exclude": [
"node_modules"
]
}
Loading