add delete-only-untagged-versions param (#94)

Add a new boolean parameter to action `delete-only-untagged-versions`. When true, only versions that do not have any tags will be deleted.
This commit is contained in:
Anupam
2023-03-03 16:42:34 +05:30
committed by GitHub
parent b9ed39f728
commit 029d95066b
10 changed files with 207 additions and 12 deletions

View File

@@ -58,6 +58,11 @@ This action deletes versions of a package from [GitHub Packages](https://github.
# Cannot be used with `num-old-versions-to-delete` and `ignore-versions`.
delete-only-pre-release-versions:
# If true it will delete only the untagged versions in case of container package.
# Does not work for other package types and will be ignored.
# Defaults to false.
delete-only-untagged-versions:
# The token used to authenticate with GitHub Packages.
# Defaults to github.token.
# Required if the repo running the workflow does not have access to delete the package.
@@ -88,6 +93,7 @@ This action deletes versions of a package from [GitHub Packages](https://github.
- [Valid Input Combinations](#valid-input-combinations)
- [Scenarios](#scenarios)
- [Delete all pre-release versions except y latest pre-release package versions](#delete-all-pre-release-versions-except-y-latest-pre-release-package-versions)
- [Delete all untagged container versions except y latest untagged versions](#delete-all-untagged-container-versions-except-y-latest-untagged-versions)
- [Delete all except y latest versions while ignoring particular package versions](#delete-all-except-y-latest-versions-while-ignoring-particular-package-versions)
- [Delete oldest x number of versions while ignoring particular package versions](#delete-oldest-x-number-of-versions-while-ignoring-particular-package-versions)
- [Delete all except y latest versions of a package](#delete-all-except-y-latest-versions-of-a-package)
@@ -95,8 +101,9 @@ This action deletes versions of a package from [GitHub Packages](https://github.
- [Delete oldest version of a package](#delete-oldest-version-of-a-package)
- [Delete a specific version of a package](#delete-a-specific-version-of-a-package)
- [Delete multiple specific versions of a package](#delete-multiple-specific-versions-of-a-package)
- [GitHub Enterprise Server](#github-enterprise-server)
- [License](#license)
### Delete all pre-release versions except y latest pre-release package versions
@@ -133,6 +140,25 @@ This action deletes versions of a package from [GitHub Packages](https://github.
<br>
### Delete all untagged container versions except y latest untagged versions
To delete all untagged versions of a container package except y latest untagged versions, the __package-name__, __package-type__, __min-versions-to-keep__ and __delete-only-untagged-versions__ inputs are required. __package-type__ must be container for this scenario.
__Example__
Delete all untagged versions except latest 10
```yaml
- uses: actions/delete-package-versions@v4
with:
package-name: 'test-package'
package-type: 'container'
min-versions-to-keep: 10
delete-only-untagged-versions: 'true'
```
<br>
### Delete all except y latest versions while ignoring particular package versions
To delete all except y latest versions while ignoring particular package versions, the __package-name__, __min-versions-to-keep__ and __ignore-versions__ inputs are required.
@@ -375,6 +401,10 @@ This action deletes versions of a package from [GitHub Packages](https://github.
token: ${{ secrets.PAT }}
```
# GitHub Enterprise Server
This action works with GitHub Enterprise Server. It uses the `GITHUB_API_URL` environment variable to determine the GitHub Enterprise Server URL. The environment variable is automatically set in workflows. GitHub Connect should be configured on the server to allow using the action from dotcom. See - [Enabling automatic access to GitHub.com actions using GitHub Connect](https://docs.github.com/en/enterprise-server/admin/github-actions/managing-access-to-actions-from-githubcom/enabling-automatic-access-to-githubcom-actions-using-github-connect)
# License
The scripts and documentation in this project are released under the [MIT License](https://github.com/actions/delete-package-versions/blob/main/LICENSE)
@@ -382,4 +412,3 @@ The scripts and documentation in this project are released under the [MIT Licens
[api]: https://docs.github.com/en/rest/packages
[token]: https://help.github.com/en/packages/publishing-and-managing-packages/about-github-packages#about-tokens
[secret]: https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets

View File

@@ -255,6 +255,53 @@ describe('index tests -- call rest', () => {
})
})
it('finalIds test - delete only untagged versions with minVersionsToKeep', done => {
const numVersions = 50
const numTaggedVersions = 20
const numUntaggedVersions = numVersions - numTaggedVersions
const taggedVersions = getMockedVersionsResponse(
numTaggedVersions,
0,
'container',
true
)
const untaggedVersions = getMockedVersionsResponse(
numUntaggedVersions,
numTaggedVersions,
'container',
false
)
const versions = taggedVersions.concat(untaggedVersions)
let apiCalled = 0
server.use(
rest.get(
'https://api.github.com/users/test-owner/packages/container/test-package/versions',
(req, res, ctx) => {
apiCalled++
return res(ctx.status(200), ctx.json(versions))
}
)
)
finalIds(
getInput({
minVersionsToKeep: 10,
deleteUntaggedVersions: 'true',
packageType: 'container'
})
).subscribe(ids => {
expect(apiCalled).toBe(1)
expect(ids.length).toBe(numUntaggedVersions - 10)
for (let i = 0; i < numUntaggedVersions - 10; i++) {
expect(ids[i]).toBe(untaggedVersions[i].id.toString())
}
done()
})
})
it('finalIds test - no versions deleted if API error even once', done => {
const numVersions = RATE_LIMIT * 2
let apiCalled = 0

View File

@@ -77,6 +77,54 @@ describe('get versions tests -- mock rest', () => {
})
})
it('getOldestVersions -- success - container tagged versions', done => {
const numVersions = 6
const numTaggedVersions = 3
const numUntaggedVersions = numVersions - numTaggedVersions
const respTagged = getMockedVersionsResponse(
numTaggedVersions,
0,
'container',
true
)
const respUntagged = getMockedVersionsResponse(
numUntaggedVersions,
numTaggedVersions,
'container',
false
)
const resp = respTagged.concat(respUntagged)
server.use(
rest.get(
'https://api.github.com/users/test-owner/packages/container/test-package/versions',
(req, res, ctx) => {
return res(ctx.status(200), ctx.json(resp))
}
)
)
getOldestVersions({numVersions, packageType: 'container'}).subscribe(
result => {
expect(result.versions.length).toBe(numVersions)
for (let i = 0; i < numVersions; i++) {
expect(result.versions[i].id).toBe(resp[i].id)
expect(result.versions[i].version).toBe(resp[i].name)
expect(result.versions[i].created_at).toBe(resp[i].created_at)
if (i < numTaggedVersions) {
expect(result.versions[i].tagged).toBe(true)
} else {
expect(result.versions[i].tagged).toBe(false)
}
}
expect(result.paginate).toBe(true)
expect(result.totalCount).toBe(numVersions)
done()
}
)
})
it('getOldestVersions -- paginate is false when fetched versions is less than page size', done => {
const numVersions = 5

View File

@@ -2,23 +2,44 @@ import {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods/dis
type GetVersionsResponseData =
RestEndpointMethodTypes['packages']['getAllPackageVersionsForPackageOwnedByUser']['response']['data']
type PackageType =
RestEndpointMethodTypes['packages']['getAllPackageVersionsForPackageOwnedByUser']['parameters']['package_type']
export function getMockedVersionsResponse(
numVersions: number,
offset = 0
offset = 0,
packageType = 'npm',
tagged = false
): GetVersionsResponseData {
const versions: GetVersionsResponseData = []
for (let i = 1 + offset; i <= numVersions + offset; ++i) {
const created_at = new Date()
created_at.setUTCFullYear(2000 + Number(i), 1, 1)
versions.push({
let version = {
id: i,
name: `${i}.0.0`,
url: '',
created_at: created_at.toUTCString(),
package_html_url: '',
updated_at: ''
})
updated_at: '',
metadata: {
package_type: packageType as PackageType
}
} as GetVersionsResponseData[0]
if (packageType === 'container' && tagged) {
version = {
...version,
metadata: {
package_type: packageType as PackageType,
container: {
tags: [`latest${i}`] as string[]
}
}
}
}
versions.push(version)
}
return versions
}

View File

@@ -55,6 +55,13 @@ inputs:
required: false
default: "false"
delete-only-untagged-versions:
description: >
Deletes only untagged versions in case of a container package. Does not work for other package types.
By default this is set to false
required: false
default: "false"
token:
description: >
Token with the necessary scopes to delete package versions.

22
dist/index.js vendored
View File

@@ -38,6 +38,9 @@ function finalIds(input) {
Then compute number of versions to delete (toDelete) based on the inputs.
*/
value = value.filter(info => !input.ignoreVersions.test(info.version));
if (input.deleteUntaggedVersions === 'true') {
value = value.filter(info => !info.tagged);
}
let toDelete = 0;
if (input.minVersionsToKeep < 0) {
toDelete = Math.min(value.length, Math.min(input.numOldVersionsToDelete, exports.RATE_LIMIT));
@@ -88,7 +91,8 @@ const defaultParams = {
minVersionsToKeep: 0,
ignoreVersions: new RegExp(''),
deletePreReleaseVersions: '',
token: ''
token: '',
deleteUntaggedVersions: ''
};
class Input {
constructor(params) {
@@ -103,6 +107,7 @@ class Input {
this.deletePreReleaseVersions = validatedParams.deletePreReleaseVersions;
this.token = validatedParams.token;
this.numDeleted = 0;
this.deleteUntaggedVersions = validatedParams.deleteUntaggedVersions;
}
hasOldestVersionQueryInfo() {
return !!(this.owner &&
@@ -123,6 +128,9 @@ class Input {
this.minVersionsToKeep > 0 ? this.minVersionsToKeep : 0;
this.ignoreVersions = new RegExp('^(0|[1-9]\\d*)((\\.(0|[1-9]\\d*))*)$');
}
if (this.packageType.toLowerCase() !== 'container') {
this.deleteUntaggedVersions = 'false';
}
if (this.minVersionsToKeep >= 0) {
this.numOldVersionsToDelete = 0;
}
@@ -214,10 +222,17 @@ function getOldestVersions(owner, packageName, packageType, numVersions, page, t
}), (0, operators_1.map)(response => {
const resp = {
versions: response.data.map((version) => {
let tagged = false;
if (package_type === 'container' &&
version.metadata &&
version.metadata.container) {
tagged = version.metadata.container.tags.length > 0;
}
return {
id: version.id,
version: version.name,
created_at: version.created_at
created_at: version.created_at,
tagged
};
}),
page,
@@ -43916,7 +43931,8 @@ function getActionInput() {
minVersionsToKeep: Number((0, core_1.getInput)('min-versions-to-keep')),
ignoreVersions: RegExp((0, core_1.getInput)('ignore-versions')),
deletePreReleaseVersions: (0, core_1.getInput)('delete-only-pre-release-versions').toLowerCase(),
token: (0, core_1.getInput)('token')
token: (0, core_1.getInput)('token'),
deleteUntaggedVersions: (0, core_1.getInput)('delete-only-untagged-versions').toLowerCase()
});
}
function run() {

View File

@@ -72,6 +72,11 @@ export function finalIds(input: Input): Observable<string[]> {
Then compute number of versions to delete (toDelete) based on the inputs.
*/
value = value.filter(info => !input.ignoreVersions.test(info.version))
if (input.deleteUntaggedVersions === 'true') {
value = value.filter(info => !info.tagged)
}
let toDelete = 0
if (input.minVersionsToKeep < 0) {
toDelete = Math.min(

View File

@@ -8,6 +8,7 @@ export interface InputParams {
ignoreVersions?: RegExp
token?: string
deletePreReleaseVersions?: string
deleteUntaggedVersions?: string
}
const defaultParams = {
@@ -19,7 +20,8 @@ const defaultParams = {
minVersionsToKeep: 0,
ignoreVersions: new RegExp(''),
deletePreReleaseVersions: '',
token: ''
token: '',
deleteUntaggedVersions: ''
}
export class Input {
@@ -33,6 +35,7 @@ export class Input {
deletePreReleaseVersions: string
token: string
numDeleted: number
deleteUntaggedVersions: string
constructor(params?: InputParams) {
const validatedParams: Required<InputParams> = {...defaultParams, ...params}
@@ -47,6 +50,7 @@ export class Input {
this.deletePreReleaseVersions = validatedParams.deletePreReleaseVersions
this.token = validatedParams.token
this.numDeleted = 0
this.deleteUntaggedVersions = validatedParams.deleteUntaggedVersions
}
hasOldestVersionQueryInfo(): boolean {
@@ -76,6 +80,10 @@ export class Input {
this.ignoreVersions = new RegExp('^(0|[1-9]\\d*)((\\.(0|[1-9]\\d*))*)$')
}
if (this.packageType.toLowerCase() !== 'container') {
this.deleteUntaggedVersions = 'false'
}
if (this.minVersionsToKeep >= 0) {
this.numOldVersionsToDelete = 0
}

View File

@@ -19,7 +19,10 @@ function getActionInput(): Input {
deletePreReleaseVersions: getInput(
'delete-only-pre-release-versions'
).toLowerCase(),
token: getInput('token')
token: getInput('token'),
deleteUntaggedVersions: getInput(
'delete-only-untagged-versions'
).toLowerCase()
})
}

View File

@@ -8,6 +8,7 @@ export interface RestVersionInfo {
id: number
version: string
created_at: string
tagged: boolean
}
export interface RestQueryInfo {
@@ -56,10 +57,20 @@ export function getOldestVersions(
map(response => {
const resp = {
versions: response.data.map((version: GetVersionsResponse[0]) => {
let tagged = false
if (
package_type === 'container' &&
version.metadata &&
version.metadata.container
) {
tagged = version.metadata.container.tags.length > 0
}
return {
id: version.id,
version: version.name,
created_at: version.created_at
created_at: version.created_at,
tagged
}
}),
page,