From b62d4c91b60dda013224ff324dead43a4f0bc21b Mon Sep 17 00:00:00 2001 From: Rob Herley Date: Wed, 17 Jan 2024 16:18:49 -0500 Subject: [PATCH] add public and internal methods to delete artifacts --- .../__tests__/delete-artifacts.test.ts | 170 +++++++++++++++++ .../src/generated/results/api/v1/artifact.ts | 148 ++++++++++++++- .../results/api/v1/artifact.twirp.ts | 177 ++++++++++++++++++ packages/artifact/src/internal/client.ts | 60 +++++- .../src/internal/delete/delete-artifact.ts | 80 ++++++++ .../src/internal/shared/interfaces.ts | 10 + 6 files changed, 642 insertions(+), 3 deletions(-) create mode 100644 packages/artifact/__tests__/delete-artifacts.test.ts create mode 100644 packages/artifact/src/internal/delete/delete-artifact.ts diff --git a/packages/artifact/__tests__/delete-artifacts.test.ts b/packages/artifact/__tests__/delete-artifacts.test.ts new file mode 100644 index 00000000..98727d62 --- /dev/null +++ b/packages/artifact/__tests__/delete-artifacts.test.ts @@ -0,0 +1,170 @@ +import * as github from '@actions/github' +import type {RestEndpointMethods} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/method-types' +import type {RequestInterface} from '@octokit/types' +import { + deleteArtifactInternal, + deleteArtifactPublic +} from '../src/internal/delete/delete-artifact' +import * as config from '../src/internal/shared/config' +import {ArtifactServiceClientJSON} from '../src/generated' +import * as util from '../src/internal/shared/util' +import {noopLogs} from './common' + +type MockedRequest = jest.MockedFunction> + +type MockedDeleteArtifact = jest.MockedFunction< + RestEndpointMethods['actions']['deleteArtifact'] +> + +jest.mock('@actions/github', () => ({ + getOctokit: jest.fn().mockReturnValue({ + request: jest.fn(), + rest: { + actions: { + deleteArtifact: jest.fn() + } + } + }) +})) + +const fixtures = { + repo: 'toolkit', + owner: 'actions', + token: 'ghp_1234567890', + runId: 123, + backendIds: { + workflowRunBackendId: 'c4d7c21f-ba3f-4ddc-a8c8-6f2f626f8422', + workflowJobRunBackendId: '760803a1-f890-4d25-9a6e-a3fc01a0c7cf' + }, + artifacts: [ + { + id: 1, + name: 'my-artifact', + size: 456, + createdAt: new Date('2023-12-01') + }, + { + id: 2, + name: 'my-artifact', + size: 456, + createdAt: new Date('2023-12-02') + } + ] +} + +describe('delete-artifact', () => { + beforeAll(() => { + noopLogs() + }) + + describe('public', () => { + it('should delete an artifact', async () => { + const mockRequest = github.getOctokit(fixtures.token) + .request as MockedRequest + mockRequest.mockResolvedValueOnce({ + status: 200, + headers: {}, + url: '', + data: { + artifacts: [ + { + name: fixtures.artifacts[0].name, + id: fixtures.artifacts[0].id, + size_in_bytes: fixtures.artifacts[0].size, + created_at: fixtures.artifacts[0].createdAt.toISOString() + } + ] + } + }) + + const mockDeleteArtifact = github.getOctokit(fixtures.token).rest.actions + .deleteArtifact as MockedDeleteArtifact + mockDeleteArtifact.mockResolvedValueOnce({ + status: 204, + headers: {}, + url: '', + data: null as never + }) + + const response = await deleteArtifactPublic( + fixtures.artifacts[0].name, + fixtures.runId, + fixtures.owner, + fixtures.repo, + fixtures.token + ) + + expect(response).toEqual({ + id: fixtures.artifacts[0].id + }) + }) + + it('should fail if non-200 response', async () => { + const mockRequest = github.getOctokit(fixtures.token) + .request as MockedRequest + mockRequest.mockResolvedValueOnce({ + status: 200, + headers: {}, + url: '', + data: { + artifacts: [ + { + name: fixtures.artifacts[0].name, + id: fixtures.artifacts[0].id, + size_in_bytes: fixtures.artifacts[0].size, + created_at: fixtures.artifacts[0].createdAt.toISOString() + } + ] + } + }) + + const mockDeleteArtifact = github.getOctokit(fixtures.token).rest.actions + .deleteArtifact as MockedDeleteArtifact + mockDeleteArtifact.mockRejectedValue(new Error('boom')) + + await expect( + deleteArtifactPublic( + fixtures.artifacts[0].name, + fixtures.runId, + fixtures.owner, + fixtures.repo, + fixtures.token + ) + ).rejects.toThrow('boom') + }) + }) + + describe('internal', () => { + beforeEach(() => { + jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token') + jest + .spyOn(util, 'getBackendIdsFromToken') + .mockReturnValue(fixtures.backendIds) + jest + .spyOn(config, 'getResultsServiceUrl') + .mockReturnValue('https://results.local') + }) + + it('should return a list of artifacts', async () => { + jest + .spyOn(ArtifactServiceClientJSON.prototype, 'DeleteArtifact') + .mockResolvedValue({ + ok: true, + artifactId: fixtures.artifacts[0].id.toString() + }) + const response = await deleteArtifactInternal(fixtures.artifacts[0].name) + expect(response).toEqual({ + id: fixtures.artifacts[0].id + }) + }) + + it('should fail if non-200 response', async () => { + jest + .spyOn(ArtifactServiceClientJSON.prototype, 'DeleteArtifact') + .mockRejectedValue(new Error('boom')) + await expect( + deleteArtifactInternal(fixtures.artifacts[0].id) + ).rejects.toThrow('boom') + }) + }) +}) diff --git a/packages/artifact/src/generated/results/api/v1/artifact.ts b/packages/artifact/src/generated/results/api/v1/artifact.ts index acce3037..7bb7f4be 100644 --- a/packages/artifact/src/generated/results/api/v1/artifact.ts +++ b/packages/artifact/src/generated/results/api/v1/artifact.ts @@ -196,6 +196,36 @@ export interface GetSignedArtifactURLResponse { */ signedUrl: string; } +/** + * @generated from protobuf message github.actions.results.api.v1.DeleteArtifactRequest + */ +export interface DeleteArtifactRequest { + /** + * @generated from protobuf field: string workflow_run_backend_id = 1; + */ + workflowRunBackendId: string; + /** + * @generated from protobuf field: string workflow_job_run_backend_id = 2; + */ + workflowJobRunBackendId: string; + /** + * @generated from protobuf field: string name = 3; + */ + name: string; +} +/** + * @generated from protobuf message github.actions.results.api.v1.DeleteArtifactResponse + */ +export interface DeleteArtifactResponse { + /** + * @generated from protobuf field: bool ok = 1; + */ + ok: boolean; + /** + * @generated from protobuf field: int64 artifact_id = 2; + */ + artifactId: string; +} // @generated message type with reflection information, may provide speed optimized methods class CreateArtifactRequest$Type extends MessageType { constructor() { @@ -759,6 +789,121 @@ class GetSignedArtifactURLResponse$Type extends MessageType { + constructor() { + super("github.actions.results.api.v1.DeleteArtifactRequest", [ + { no: 1, name: "workflow_run_backend_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "workflow_job_run_backend_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): DeleteArtifactRequest { + const message = { workflowRunBackendId: "", workflowJobRunBackendId: "", name: "" }; + globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this }); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: DeleteArtifactRequest): DeleteArtifactRequest { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string workflow_run_backend_id */ 1: + message.workflowRunBackendId = reader.string(); + break; + case /* string workflow_job_run_backend_id */ 2: + message.workflowJobRunBackendId = reader.string(); + break; + case /* string name */ 3: + message.name = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: DeleteArtifactRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string workflow_run_backend_id = 1; */ + if (message.workflowRunBackendId !== "") + writer.tag(1, WireType.LengthDelimited).string(message.workflowRunBackendId); + /* string workflow_job_run_backend_id = 2; */ + if (message.workflowJobRunBackendId !== "") + writer.tag(2, WireType.LengthDelimited).string(message.workflowJobRunBackendId); + /* string name = 3; */ + if (message.name !== "") + writer.tag(3, WireType.LengthDelimited).string(message.name); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message github.actions.results.api.v1.DeleteArtifactRequest + */ +export const DeleteArtifactRequest = new DeleteArtifactRequest$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class DeleteArtifactResponse$Type extends MessageType { + constructor() { + super("github.actions.results.api.v1.DeleteArtifactResponse", [ + { no: 1, name: "ok", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, + { no: 2, name: "artifact_id", kind: "scalar", T: 3 /*ScalarType.INT64*/ } + ]); + } + create(value?: PartialMessage): DeleteArtifactResponse { + const message = { ok: false, artifactId: "0" }; + globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this }); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: DeleteArtifactResponse): DeleteArtifactResponse { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* bool ok */ 1: + message.ok = reader.bool(); + break; + case /* int64 artifact_id */ 2: + message.artifactId = reader.int64().toString(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: DeleteArtifactResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* bool ok = 1; */ + if (message.ok !== false) + writer.tag(1, WireType.Varint).bool(message.ok); + /* int64 artifact_id = 2; */ + if (message.artifactId !== "0") + writer.tag(2, WireType.Varint).int64(message.artifactId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message github.actions.results.api.v1.DeleteArtifactResponse + */ +export const DeleteArtifactResponse = new DeleteArtifactResponse$Type(); /** * @generated ServiceType for protobuf service github.actions.results.api.v1.ArtifactService */ @@ -766,5 +911,6 @@ export const ArtifactService = new ServiceType("github.actions.results.api.v1.Ar { name: "CreateArtifact", options: {}, I: CreateArtifactRequest, O: CreateArtifactResponse }, { name: "FinalizeArtifact", options: {}, I: FinalizeArtifactRequest, O: FinalizeArtifactResponse }, { name: "ListArtifacts", options: {}, I: ListArtifactsRequest, O: ListArtifactsResponse }, - { name: "GetSignedArtifactURL", options: {}, I: GetSignedArtifactURLRequest, O: GetSignedArtifactURLResponse } + { name: "GetSignedArtifactURL", options: {}, I: GetSignedArtifactURLRequest, O: GetSignedArtifactURLResponse }, + { name: "DeleteArtifact", options: {}, I: DeleteArtifactRequest, O: DeleteArtifactResponse } ]); diff --git a/packages/artifact/src/generated/results/api/v1/artifact.twirp.ts b/packages/artifact/src/generated/results/api/v1/artifact.twirp.ts index 4871eb6b..bc092117 100644 --- a/packages/artifact/src/generated/results/api/v1/artifact.twirp.ts +++ b/packages/artifact/src/generated/results/api/v1/artifact.twirp.ts @@ -17,6 +17,8 @@ import { ListArtifactsResponse, GetSignedArtifactURLRequest, GetSignedArtifactURLResponse, + DeleteArtifactRequest, + DeleteArtifactResponse, } from "./artifact"; //==================================// @@ -43,6 +45,9 @@ export interface ArtifactServiceClient { GetSignedArtifactURL( request: GetSignedArtifactURLRequest ): Promise; + DeleteArtifact( + request: DeleteArtifactRequest + ): Promise; } export class ArtifactServiceClientJSON implements ArtifactServiceClient { @@ -53,6 +58,7 @@ export class ArtifactServiceClientJSON implements ArtifactServiceClient { this.FinalizeArtifact.bind(this); this.ListArtifacts.bind(this); this.GetSignedArtifactURL.bind(this); + this.DeleteArtifact.bind(this); } CreateArtifact( request: CreateArtifactRequest @@ -129,6 +135,26 @@ export class ArtifactServiceClientJSON implements ArtifactServiceClient { }) ); } + + DeleteArtifact( + request: DeleteArtifactRequest + ): Promise { + const data = DeleteArtifactRequest.toJson(request, { + useProtoFieldName: true, + emitDefaultValues: false, + }); + const promise = this.rpc.request( + "github.actions.results.api.v1.ArtifactService", + "DeleteArtifact", + "application/json", + data as object + ); + return promise.then((data) => + DeleteArtifactResponse.fromJson(data as any, { + ignoreUnknownFields: true, + }) + ); + } } export class ArtifactServiceClientProtobuf implements ArtifactServiceClient { @@ -139,6 +165,7 @@ export class ArtifactServiceClientProtobuf implements ArtifactServiceClient { this.FinalizeArtifact.bind(this); this.ListArtifacts.bind(this); this.GetSignedArtifactURL.bind(this); + this.DeleteArtifact.bind(this); } CreateArtifact( request: CreateArtifactRequest @@ -197,6 +224,21 @@ export class ArtifactServiceClientProtobuf implements ArtifactServiceClient { GetSignedArtifactURLResponse.fromBinary(data as Uint8Array) ); } + + DeleteArtifact( + request: DeleteArtifactRequest + ): Promise { + const data = DeleteArtifactRequest.toBinary(request); + const promise = this.rpc.request( + "github.actions.results.api.v1.ArtifactService", + "DeleteArtifact", + "application/protobuf", + data + ); + return promise.then((data) => + DeleteArtifactResponse.fromBinary(data as Uint8Array) + ); + } } //==================================// @@ -220,6 +262,10 @@ export interface ArtifactServiceTwirp { ctx: T, request: GetSignedArtifactURLRequest ): Promise; + DeleteArtifact( + ctx: T, + request: DeleteArtifactRequest + ): Promise; } export enum ArtifactServiceMethod { @@ -227,6 +273,7 @@ export enum ArtifactServiceMethod { FinalizeArtifact = "FinalizeArtifact", ListArtifacts = "ListArtifacts", GetSignedArtifactURL = "GetSignedArtifactURL", + DeleteArtifact = "DeleteArtifact", } export const ArtifactServiceMethodList = [ @@ -234,6 +281,7 @@ export const ArtifactServiceMethodList = [ ArtifactServiceMethod.FinalizeArtifact, ArtifactServiceMethod.ListArtifacts, ArtifactServiceMethod.GetSignedArtifactURL, + ArtifactServiceMethod.DeleteArtifact, ]; export function createArtifactServiceServer< @@ -333,6 +381,26 @@ function matchArtifactServiceRoute( interceptors ); }; + case "DeleteArtifact": + return async ( + ctx: T, + service: ArtifactServiceTwirp, + data: Buffer, + interceptors?: Interceptor< + T, + DeleteArtifactRequest, + DeleteArtifactResponse + >[] + ) => { + ctx = { ...ctx, methodName: "DeleteArtifact" }; + await events.onMatch(ctx); + return handleArtifactServiceDeleteArtifactRequest( + ctx, + service, + data, + interceptors + ); + }; default: events.onNotFound(); const msg = `no handler found`; @@ -463,6 +531,35 @@ function handleArtifactServiceGetSignedArtifactURLRequest< throw new TwirpError(TwirpErrorCode.BadRoute, msg); } } + +function handleArtifactServiceDeleteArtifactRequest< + T extends TwirpContext = TwirpContext +>( + ctx: T, + service: ArtifactServiceTwirp, + data: Buffer, + interceptors?: Interceptor[] +): Promise { + switch (ctx.contentType) { + case TwirpContentType.JSON: + return handleArtifactServiceDeleteArtifactJSON( + ctx, + service, + data, + interceptors + ); + case TwirpContentType.Protobuf: + return handleArtifactServiceDeleteArtifactProtobuf( + ctx, + service, + data, + interceptors + ); + default: + const msg = "unexpected Content-Type"; + throw new TwirpError(TwirpErrorCode.BadRoute, msg); + } +} async function handleArtifactServiceCreateArtifactJSON< T extends TwirpContext = TwirpContext >( @@ -646,6 +743,50 @@ async function handleArtifactServiceGetSignedArtifactURLJSON< }) as string ); } + +async function handleArtifactServiceDeleteArtifactJSON< + T extends TwirpContext = TwirpContext +>( + ctx: T, + service: ArtifactServiceTwirp, + data: Buffer, + interceptors?: Interceptor[] +) { + let request: DeleteArtifactRequest; + let response: DeleteArtifactResponse; + + try { + const body = JSON.parse(data.toString() || "{}"); + request = DeleteArtifactRequest.fromJson(body, { + ignoreUnknownFields: true, + }); + } catch (e) { + if (e instanceof Error) { + const msg = "the json request could not be decoded"; + throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true); + } + } + + if (interceptors && interceptors.length > 0) { + const interceptor = chainInterceptors(...interceptors) as Interceptor< + T, + DeleteArtifactRequest, + DeleteArtifactResponse + >; + response = await interceptor(ctx, request!, (ctx, inputReq) => { + return service.DeleteArtifact(ctx, inputReq); + }); + } else { + response = await service.DeleteArtifact(ctx, request!); + } + + return JSON.stringify( + DeleteArtifactResponse.toJson(response, { + useProtoFieldName: true, + emitDefaultValues: false, + }) as string + ); +} async function handleArtifactServiceCreateArtifactProtobuf< T extends TwirpContext = TwirpContext >( @@ -797,3 +938,39 @@ async function handleArtifactServiceGetSignedArtifactURLProtobuf< return Buffer.from(GetSignedArtifactURLResponse.toBinary(response)); } + +async function handleArtifactServiceDeleteArtifactProtobuf< + T extends TwirpContext = TwirpContext +>( + ctx: T, + service: ArtifactServiceTwirp, + data: Buffer, + interceptors?: Interceptor[] +) { + let request: DeleteArtifactRequest; + let response: DeleteArtifactResponse; + + try { + request = DeleteArtifactRequest.fromBinary(data); + } catch (e) { + if (e instanceof Error) { + const msg = "the protobuf request could not be decoded"; + throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true); + } + } + + if (interceptors && interceptors.length > 0) { + const interceptor = chainInterceptors(...interceptors) as Interceptor< + T, + DeleteArtifactRequest, + DeleteArtifactResponse + >; + response = await interceptor(ctx, request!, (ctx, inputReq) => { + return service.DeleteArtifact(ctx, inputReq); + }); + } else { + response = await service.DeleteArtifact(ctx, request!); + } + + return Buffer.from(DeleteArtifactResponse.toBinary(response)); +} diff --git a/packages/artifact/src/internal/client.ts b/packages/artifact/src/internal/client.ts index c4971d9f..fc55236a 100644 --- a/packages/artifact/src/internal/client.ts +++ b/packages/artifact/src/internal/client.ts @@ -8,13 +8,18 @@ import { ListArtifactsOptions, ListArtifactsResponse, DownloadArtifactResponse, - FindOptions + FindOptions, + DeleteArtifactResponse } from './shared/interfaces' import {uploadArtifact} from './upload/upload-artifact' import { downloadArtifactPublic, downloadArtifactInternal } from './download/download-artifact' +import { + deleteArtifactPublic, + deleteArtifactInternal +} from './delete/delete-artifact' import {getArtifactPublic, getArtifactInternal} from './find/get-artifact' import {listArtifactsPublic, listArtifactsInternal} from './find/list-artifacts' import {GHESNotSupportedError} from './shared/errors' @@ -77,7 +82,7 @@ export interface ArtifactClient { * * If `options.findBy` is specified, this will use the public Download Artifact API https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#download-an-artifact * - * @param artifactId The name of the artifact to download + * @param artifactId The id of the artifact to download * @param options Extra options that allow for the customization of the download behavior * @returns single DownloadArtifactResponse object */ @@ -85,6 +90,20 @@ export interface ArtifactClient { artifactId: number, options?: DownloadArtifactOptions & FindOptions ): Promise + + /** + * Delete an Artifact + * + * If `options.findBy` is specified, this will use the public Download Artifact API https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#delete-an-artifact + * + * @param artifactName The name of the artifact to delete + * @param options Extra options that allow for the customization of the delete behavior + * @returns single DeleteArtifactResponse object + */ + deleteArtifact( + artifactName: string, + options?: FindOptions + ): Promise } /** @@ -225,4 +244,41 @@ If the error persists, please check whether Actions and API requests are operati throw error } } + + async deleteArtifact( + artifactName: string, + options?: FindOptions + ): Promise { + try { + if (isGhes()) { + throw new GHESNotSupportedError() + } + + if (options?.findBy) { + const { + findBy: {repositoryOwner, repositoryName, workflowRunId, token} + } = options + + return deleteArtifactPublic( + artifactName, + workflowRunId, + repositoryOwner, + repositoryName, + token + ) + } + + return deleteArtifactInternal(artifactName) + } catch (error) { + warning( + `Delete Artifact failed with error: ${error}. + +Errors can be temporary, so please try again and optionally run the action with debug mode enabled for more information. + +If the error persists, please check whether Actions and API requests are operating normally at [https://githubstatus.com](https://www.githubstatus.com).` + ) + + throw error + } + } } diff --git a/packages/artifact/src/internal/delete/delete-artifact.ts b/packages/artifact/src/internal/delete/delete-artifact.ts new file mode 100644 index 00000000..4183049b --- /dev/null +++ b/packages/artifact/src/internal/delete/delete-artifact.ts @@ -0,0 +1,80 @@ +import {info} from '@actions/core' +import {getOctokit} from '@actions/github' +import {DeleteArtifactResponse} from '../shared/interfaces' +import {getUserAgentString} from '../shared/user-agent' +import {getRetryOptions} from '../find/retry-options' +import {defaults as defaultGitHubOptions} from '@actions/github/lib/utils' +import {requestLog} from '@octokit/plugin-request-log' +import {retry} from '@octokit/plugin-retry' +import {OctokitOptions} from '@octokit/core/dist-types/types' +import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client' +import {getBackendIdsFromToken} from '../shared/util' +import {DeleteArtifactRequest} from '../../generated' +import {getArtifactPublic} from '../find/get-artifact' +import {InvalidResponseError} from '../shared/errors' + +export async function deleteArtifactPublic( + artifactName: string, + workflowRunId: number, + repositoryOwner: string, + repositoryName: string, + token: string +): Promise { + const [retryOpts, requestOpts] = getRetryOptions(defaultGitHubOptions) + + const opts: OctokitOptions = { + log: undefined, + userAgent: getUserAgentString(), + previews: undefined, + retry: retryOpts, + request: requestOpts + } + + const github = getOctokit(token, opts, retry, requestLog) + + const getArtifactResp = await getArtifactPublic( + artifactName, + workflowRunId, + repositoryOwner, + repositoryName, + token + ) + + const deleteArtifactResp = await github.rest.actions.deleteArtifact({ + owner: repositoryOwner, + repo: repositoryName, + artifact_id: getArtifactResp.artifact.id + }) + + if (deleteArtifactResp.status !== 204) { + throw new InvalidResponseError( + `Invalid response from GitHub API: ${deleteArtifactResp.status} (${deleteArtifactResp?.headers?.['x-github-request-id']})` + ) + } + + return { + id: getArtifactResp.artifact.id + } +} + +export async function deleteArtifactInternal( + artifactName +): Promise { + const artifactClient = internalArtifactTwirpClient() + + const {workflowRunBackendId, workflowJobRunBackendId} = + getBackendIdsFromToken() + + const req: DeleteArtifactRequest = { + workflowRunBackendId, + workflowJobRunBackendId, + name: artifactName + } + + const res = await artifactClient.DeleteArtifact(req) + info(`Artifact '${artifactName}' (ID: ${res.artifactId}) deleted`) + + return { + id: Number(res.artifactId) + } +} diff --git a/packages/artifact/src/internal/shared/interfaces.ts b/packages/artifact/src/internal/shared/interfaces.ts index c661721e..eb55ae8b 100644 --- a/packages/artifact/src/internal/shared/interfaces.ts +++ b/packages/artifact/src/internal/shared/interfaces.ts @@ -147,3 +147,13 @@ export interface FindOptions { repositoryName: string } } + +/** + * Response from the server when deleting an artifact + */ +export interface DeleteArtifactResponse { + /** + * The id of the artifact that was deleted + */ + id: number +}