Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Anupam authored and GitHub committed Mar 3, 2023
1 parent b9ed39f commit 029d950
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 12 deletions.
33 changes: 31 additions & 2 deletions README.md
Expand Up @@ -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.
Expand Down Expand Up @@ -88,15 +93,17 @@ 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)
- [Delete oldest x number of versions of a package](#delete-oldest-x-number-of-versions-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 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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -375,11 +401,14 @@ 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)

[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

47 changes: 47 additions & 0 deletions __tests__/delete.test.ts
Expand Up @@ -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
Expand Down
48 changes: 48 additions & 0 deletions __tests__/version/get-version.test.ts
Expand Up @@ -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

Expand Down
29 changes: 25 additions & 4 deletions __tests__/version/rest.mock.ts
Expand Up @@ -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
}
7 changes: 7 additions & 0 deletions action.yml
Expand Up @@ -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.
Expand Down
22 changes: 19 additions & 3 deletions dist/index.js
Expand Up @@ -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));
Expand Down Expand Up @@ -88,7 +91,8 @@ const defaultParams = {
minVersionsToKeep: 0,
ignoreVersions: new RegExp(''),
deletePreReleaseVersions: '',
token: ''
token: '',
deleteUntaggedVersions: ''
};
class Input {
constructor(params) {
Expand All @@ -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 &&
Expand All @@ -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;
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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() {
Expand Down
5 changes: 5 additions & 0 deletions src/delete.ts
Expand Up @@ -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(
Expand Down
10 changes: 9 additions & 1 deletion src/input.ts
Expand Up @@ -8,6 +8,7 @@ export interface InputParams {
ignoreVersions?: RegExp
token?: string
deletePreReleaseVersions?: string
deleteUntaggedVersions?: string
}

const defaultParams = {
Expand All @@ -19,7 +20,8 @@ const defaultParams = {
minVersionsToKeep: 0,
ignoreVersions: new RegExp(''),
deletePreReleaseVersions: '',
token: ''
token: '',
deleteUntaggedVersions: ''
}

export class Input {
Expand All @@ -33,6 +35,7 @@ export class Input {
deletePreReleaseVersions: string
token: string
numDeleted: number
deleteUntaggedVersions: string

constructor(params?: InputParams) {
const validatedParams: Required<InputParams> = {...defaultParams, ...params}
Expand All @@ -47,6 +50,7 @@ export class Input {
this.deletePreReleaseVersions = validatedParams.deletePreReleaseVersions
this.token = validatedParams.token
this.numDeleted = 0
this.deleteUntaggedVersions = validatedParams.deleteUntaggedVersions
}

hasOldestVersionQueryInfo(): boolean {
Expand Down Expand Up @@ -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
}
Expand Down
5 changes: 4 additions & 1 deletion src/main.ts
Expand Up @@ -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()
})
}

Expand Down

0 comments on commit 029d950

Please sign in to comment.