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
+30 -1
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`. # Cannot be used with `num-old-versions-to-delete` and `ignore-versions`.
delete-only-pre-release-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. # The token used to authenticate with GitHub Packages.
# Defaults to github.token. # Defaults to github.token.
# Required if the repo running the workflow does not have access to delete the package. # 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) - [Valid Input Combinations](#valid-input-combinations)
- [Scenarios](#scenarios) - [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 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 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 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) - [Delete all except y latest versions of a package](#delete-all-except-y-latest-versions-of-a-package)
@@ -95,6 +101,7 @@ 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 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 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) - [Delete multiple specific versions of a package](#delete-multiple-specific-versions-of-a-package)
- [GitHub Enterprise Server](#github-enterprise-server)
- [License](#license) - [License](#license)
@@ -133,6 +140,25 @@ This action deletes versions of a package from [GitHub Packages](https://github.
<br> <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 ### 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. 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 }} 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 # License
The scripts and documentation in this project are released under the [MIT License](https://github.com/actions/delete-package-versions/blob/main/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 [api]: https://docs.github.com/en/rest/packages
[token]: https://help.github.com/en/packages/publishing-and-managing-packages/about-github-packages#about-tokens [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 [secret]: https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets
+47
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 => { it('finalIds test - no versions deleted if API error even once', done => {
const numVersions = RATE_LIMIT * 2 const numVersions = RATE_LIMIT * 2
let apiCalled = 0 let apiCalled = 0
+48
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 => { it('getOldestVersions -- paginate is false when fetched versions is less than page size', done => {
const numVersions = 5 const numVersions = 5
+25 -4
View File
@@ -2,23 +2,44 @@ import {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods/dis
type GetVersionsResponseData = type GetVersionsResponseData =
RestEndpointMethodTypes['packages']['getAllPackageVersionsForPackageOwnedByUser']['response']['data'] RestEndpointMethodTypes['packages']['getAllPackageVersionsForPackageOwnedByUser']['response']['data']
type PackageType =
RestEndpointMethodTypes['packages']['getAllPackageVersionsForPackageOwnedByUser']['parameters']['package_type']
export function getMockedVersionsResponse( export function getMockedVersionsResponse(
numVersions: number, numVersions: number,
offset = 0 offset = 0,
packageType = 'npm',
tagged = false
): GetVersionsResponseData { ): GetVersionsResponseData {
const versions: GetVersionsResponseData = [] const versions: GetVersionsResponseData = []
for (let i = 1 + offset; i <= numVersions + offset; ++i) { for (let i = 1 + offset; i <= numVersions + offset; ++i) {
const created_at = new Date() const created_at = new Date()
created_at.setUTCFullYear(2000 + Number(i), 1, 1) created_at.setUTCFullYear(2000 + Number(i), 1, 1)
versions.push({ let version = {
id: i, id: i,
name: `${i}.0.0`, name: `${i}.0.0`,
url: '', url: '',
created_at: created_at.toUTCString(), created_at: created_at.toUTCString(),
package_html_url: '', 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 return versions
} }
+7
View File
@@ -55,6 +55,13 @@ inputs:
required: false required: false
default: "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: token:
description: > description: >
Token with the necessary scopes to delete package versions. Token with the necessary scopes to delete package versions.
+19 -3
View File
@@ -38,6 +38,9 @@ function finalIds(input) {
Then compute number of versions to delete (toDelete) based on the inputs. Then compute number of versions to delete (toDelete) based on the inputs.
*/ */
value = value.filter(info => !input.ignoreVersions.test(info.version)); value = value.filter(info => !input.ignoreVersions.test(info.version));
if (input.deleteUntaggedVersions === 'true') {
value = value.filter(info => !info.tagged);
}
let toDelete = 0; let toDelete = 0;
if (input.minVersionsToKeep < 0) { if (input.minVersionsToKeep < 0) {
toDelete = Math.min(value.length, Math.min(input.numOldVersionsToDelete, exports.RATE_LIMIT)); toDelete = Math.min(value.length, Math.min(input.numOldVersionsToDelete, exports.RATE_LIMIT));
@@ -88,7 +91,8 @@ const defaultParams = {
minVersionsToKeep: 0, minVersionsToKeep: 0,
ignoreVersions: new RegExp(''), ignoreVersions: new RegExp(''),
deletePreReleaseVersions: '', deletePreReleaseVersions: '',
token: '' token: '',
deleteUntaggedVersions: ''
}; };
class Input { class Input {
constructor(params) { constructor(params) {
@@ -103,6 +107,7 @@ class Input {
this.deletePreReleaseVersions = validatedParams.deletePreReleaseVersions; this.deletePreReleaseVersions = validatedParams.deletePreReleaseVersions;
this.token = validatedParams.token; this.token = validatedParams.token;
this.numDeleted = 0; this.numDeleted = 0;
this.deleteUntaggedVersions = validatedParams.deleteUntaggedVersions;
} }
hasOldestVersionQueryInfo() { hasOldestVersionQueryInfo() {
return !!(this.owner && return !!(this.owner &&
@@ -123,6 +128,9 @@ class Input {
this.minVersionsToKeep > 0 ? this.minVersionsToKeep : 0; this.minVersionsToKeep > 0 ? this.minVersionsToKeep : 0;
this.ignoreVersions = new RegExp('^(0|[1-9]\\d*)((\\.(0|[1-9]\\d*))*)$'); this.ignoreVersions = new RegExp('^(0|[1-9]\\d*)((\\.(0|[1-9]\\d*))*)$');
} }
if (this.packageType.toLowerCase() !== 'container') {
this.deleteUntaggedVersions = 'false';
}
if (this.minVersionsToKeep >= 0) { if (this.minVersionsToKeep >= 0) {
this.numOldVersionsToDelete = 0; this.numOldVersionsToDelete = 0;
} }
@@ -214,10 +222,17 @@ function getOldestVersions(owner, packageName, packageType, numVersions, page, t
}), (0, operators_1.map)(response => { }), (0, operators_1.map)(response => {
const resp = { const resp = {
versions: response.data.map((version) => { 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 { return {
id: version.id, id: version.id,
version: version.name, version: version.name,
created_at: version.created_at created_at: version.created_at,
tagged
}; };
}), }),
page, page,
@@ -43916,7 +43931,8 @@ function getActionInput() {
minVersionsToKeep: Number((0, core_1.getInput)('min-versions-to-keep')), minVersionsToKeep: Number((0, core_1.getInput)('min-versions-to-keep')),
ignoreVersions: RegExp((0, core_1.getInput)('ignore-versions')), ignoreVersions: RegExp((0, core_1.getInput)('ignore-versions')),
deletePreReleaseVersions: (0, core_1.getInput)('delete-only-pre-release-versions').toLowerCase(), 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() { function run() {
+5
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. Then compute number of versions to delete (toDelete) based on the inputs.
*/ */
value = value.filter(info => !input.ignoreVersions.test(info.version)) value = value.filter(info => !input.ignoreVersions.test(info.version))
if (input.deleteUntaggedVersions === 'true') {
value = value.filter(info => !info.tagged)
}
let toDelete = 0 let toDelete = 0
if (input.minVersionsToKeep < 0) { if (input.minVersionsToKeep < 0) {
toDelete = Math.min( toDelete = Math.min(
+9 -1
View File
@@ -8,6 +8,7 @@ export interface InputParams {
ignoreVersions?: RegExp ignoreVersions?: RegExp
token?: string token?: string
deletePreReleaseVersions?: string deletePreReleaseVersions?: string
deleteUntaggedVersions?: string
} }
const defaultParams = { const defaultParams = {
@@ -19,7 +20,8 @@ const defaultParams = {
minVersionsToKeep: 0, minVersionsToKeep: 0,
ignoreVersions: new RegExp(''), ignoreVersions: new RegExp(''),
deletePreReleaseVersions: '', deletePreReleaseVersions: '',
token: '' token: '',
deleteUntaggedVersions: ''
} }
export class Input { export class Input {
@@ -33,6 +35,7 @@ export class Input {
deletePreReleaseVersions: string deletePreReleaseVersions: string
token: string token: string
numDeleted: number numDeleted: number
deleteUntaggedVersions: string
constructor(params?: InputParams) { constructor(params?: InputParams) {
const validatedParams: Required<InputParams> = {...defaultParams, ...params} const validatedParams: Required<InputParams> = {...defaultParams, ...params}
@@ -47,6 +50,7 @@ export class Input {
this.deletePreReleaseVersions = validatedParams.deletePreReleaseVersions this.deletePreReleaseVersions = validatedParams.deletePreReleaseVersions
this.token = validatedParams.token this.token = validatedParams.token
this.numDeleted = 0 this.numDeleted = 0
this.deleteUntaggedVersions = validatedParams.deleteUntaggedVersions
} }
hasOldestVersionQueryInfo(): boolean { hasOldestVersionQueryInfo(): boolean {
@@ -76,6 +80,10 @@ export class Input {
this.ignoreVersions = new RegExp('^(0|[1-9]\\d*)((\\.(0|[1-9]\\d*))*)$') this.ignoreVersions = new RegExp('^(0|[1-9]\\d*)((\\.(0|[1-9]\\d*))*)$')
} }
if (this.packageType.toLowerCase() !== 'container') {
this.deleteUntaggedVersions = 'false'
}
if (this.minVersionsToKeep >= 0) { if (this.minVersionsToKeep >= 0) {
this.numOldVersionsToDelete = 0 this.numOldVersionsToDelete = 0
} }
+4 -1
View File
@@ -19,7 +19,10 @@ function getActionInput(): Input {
deletePreReleaseVersions: getInput( deletePreReleaseVersions: getInput(
'delete-only-pre-release-versions' 'delete-only-pre-release-versions'
).toLowerCase(), ).toLowerCase(),
token: getInput('token') token: getInput('token'),
deleteUntaggedVersions: getInput(
'delete-only-untagged-versions'
).toLowerCase()
}) })
} }
+12 -1
View File
@@ -8,6 +8,7 @@ export interface RestVersionInfo {
id: number id: number
version: string version: string
created_at: string created_at: string
tagged: boolean
} }
export interface RestQueryInfo { export interface RestQueryInfo {
@@ -56,10 +57,20 @@ export function getOldestVersions(
map(response => { map(response => {
const resp = { const resp = {
versions: response.data.map((version: GetVersionsResponse[0]) => { 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 { return {
id: version.id, id: version.id,
version: version.name, version: version.name,
created_at: version.created_at created_at: version.created_at,
tagged
} }
}), }),
page, page,