From 695bf98f84a759546b1c2d09e2e26423eede9aaa Mon Sep 17 00:00:00 2001 From: Rob Herley Date: Thu, 30 Nov 2023 03:47:04 +0000 Subject: [PATCH] rewrite artifacts client to have public and internal implementations --- .../src/generated/results/api/v1/artifact.ts | 401 +++++++++++- .../results/api/v1/artifact.twirp.ts | 610 ++++++++++++++---- packages/artifact/src/internal/client.ts | 131 ++-- .../internal/download/download-artifact.ts | 85 ++- .../src/internal/find/get-artifact.ts | 64 +- .../src/internal/find/list-artifacts.ts | 36 +- .../internal/shared/artifact-twirp-client.ts | 18 +- .../src/internal/shared/interfaces.ts | 18 +- packages/artifact/src/internal/shared/util.ts | 24 +- .../src/internal/upload/upload-artifact.ts | 16 +- 10 files changed, 1149 insertions(+), 254 deletions(-) diff --git a/packages/artifact/src/generated/results/api/v1/artifact.ts b/packages/artifact/src/generated/results/api/v1/artifact.ts index 106c4cda..7acbd3aa 100644 --- a/packages/artifact/src/generated/results/api/v1/artifact.ts +++ b/packages/artifact/src/generated/results/api/v1/artifact.ts @@ -90,6 +90,105 @@ export interface FinalizeArtifactResponse { */ artifactId: string; } +/** + * @generated from protobuf message github.actions.results.api.v1.ListArtifactsRequest + */ +export interface ListArtifactsRequest { + /** + * The backend plan ID + * + * @generated from protobuf field: string workflow_run_backend_id = 1; + */ + workflowRunBackendId: string; + /** + * The backend job ID + * + * @generated from protobuf field: string workflow_job_run_backend_id = 2; + */ + workflowJobRunBackendId: string; + /** + * (optional) Name of the artifact to filter on + * + * @generated from protobuf field: string name_filter = 3; + */ + nameFilter: string; + /** + * (optional) Monolith Database ID of the artifact to filter on + * + * @generated from protobuf field: int64 id_filter = 4; + */ + idFilter: string; +} +/** + * @generated from protobuf message github.actions.results.api.v1.ListArtifactsResponse + */ +export interface ListArtifactsResponse { + /** + * @generated from protobuf field: repeated github.actions.results.api.v1.ListArtifactsResponse.MonolithArtifact artifacts = 1; + */ + artifacts: ListArtifactsResponse_MonolithArtifact[]; +} +/** + * @generated from protobuf message github.actions.results.api.v1.ListArtifactsResponse.MonolithArtifact + */ +export interface ListArtifactsResponse_MonolithArtifact { + /** + * The backend plan ID + * + * @generated from protobuf field: string workflow_run_backend_id = 1; + */ + workflowRunBackendId: string; + /** + * The backend job ID + * + * @generated from protobuf field: string workflow_job_run_backend_id = 2; + */ + workflowJobRunBackendId: string; + /** + * Monolith database ID of the artifact + * + * @generated from protobuf field: int64 database_id = 3; + */ + databaseId: string; + /** + * Name of the artifact + * + * @generated from protobuf field: string name = 4; + */ + name: string; + /** + * Size of the artifact in bytes + * + * @generated from protobuf field: int64 size = 5; + */ + size: string; +} +/** + * @generated from protobuf message github.actions.results.api.v1.GetSignedArtifactURLRequest + */ +export interface GetSignedArtifactURLRequest { + /** + * @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.GetSignedArtifactURLResponse + */ +export interface GetSignedArtifactURLResponse { + /** + * @generated from protobuf field: string signed_url = 1; + */ + signedUrl: string; +} // @generated message type with reflection information, may provide speed optimized methods class CreateArtifactRequest$Type extends MessageType { constructor() { @@ -348,10 +447,310 @@ class FinalizeArtifactResponse$Type extends MessageType { + constructor() { + super("github.actions.results.api.v1.ListArtifactsRequest", [ + { 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_filter", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 4, name: "id_filter", kind: "scalar", T: 3 /*ScalarType.INT64*/ } + ]); + } + create(value?: PartialMessage): ListArtifactsRequest { + const message = { workflowRunBackendId: "", workflowJobRunBackendId: "", nameFilter: "", idFilter: "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?: ListArtifactsRequest): ListArtifactsRequest { + 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_filter */ 3: + message.nameFilter = reader.string(); + break; + case /* int64 id_filter */ 4: + message.idFilter = 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: ListArtifactsRequest, 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_filter = 3; */ + if (message.nameFilter !== "") + writer.tag(3, WireType.LengthDelimited).string(message.nameFilter); + /* int64 id_filter = 4; */ + if (message.idFilter !== "0") + writer.tag(4, WireType.Varint).int64(message.idFilter); + 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.ListArtifactsRequest + */ +export const ListArtifactsRequest = new ListArtifactsRequest$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class ListArtifactsResponse$Type extends MessageType { + constructor() { + super("github.actions.results.api.v1.ListArtifactsResponse", [ + { no: 1, name: "artifacts", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => ListArtifactsResponse_MonolithArtifact } + ]); + } + create(value?: PartialMessage): ListArtifactsResponse { + const message = { artifacts: [] }; + 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?: ListArtifactsResponse): ListArtifactsResponse { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* repeated github.actions.results.api.v1.ListArtifactsResponse.MonolithArtifact artifacts */ 1: + message.artifacts.push(ListArtifactsResponse_MonolithArtifact.internalBinaryRead(reader, reader.uint32(), options)); + 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: ListArtifactsResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* repeated github.actions.results.api.v1.ListArtifactsResponse.MonolithArtifact artifacts = 1; */ + for (let i = 0; i < message.artifacts.length; i++) + ListArtifactsResponse_MonolithArtifact.internalBinaryWrite(message.artifacts[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + 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.ListArtifactsResponse + */ +export const ListArtifactsResponse = new ListArtifactsResponse$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class ListArtifactsResponse_MonolithArtifact$Type extends MessageType { + constructor() { + super("github.actions.results.api.v1.ListArtifactsResponse.MonolithArtifact", [ + { 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: "database_id", kind: "scalar", T: 3 /*ScalarType.INT64*/ }, + { no: 4, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 5, name: "size", kind: "scalar", T: 3 /*ScalarType.INT64*/ } + ]); + } + create(value?: PartialMessage): ListArtifactsResponse_MonolithArtifact { + const message = { workflowRunBackendId: "", workflowJobRunBackendId: "", databaseId: "0", name: "", size: "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?: ListArtifactsResponse_MonolithArtifact): ListArtifactsResponse_MonolithArtifact { + 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 /* int64 database_id */ 3: + message.databaseId = reader.int64().toString(); + break; + case /* string name */ 4: + message.name = reader.string(); + break; + case /* int64 size */ 5: + message.size = 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: ListArtifactsResponse_MonolithArtifact, 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); + /* int64 database_id = 3; */ + if (message.databaseId !== "0") + writer.tag(3, WireType.Varint).int64(message.databaseId); + /* string name = 4; */ + if (message.name !== "") + writer.tag(4, WireType.LengthDelimited).string(message.name); + /* int64 size = 5; */ + if (message.size !== "0") + writer.tag(5, WireType.Varint).int64(message.size); + 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.ListArtifactsResponse.MonolithArtifact + */ +export const ListArtifactsResponse_MonolithArtifact = new ListArtifactsResponse_MonolithArtifact$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class GetSignedArtifactURLRequest$Type extends MessageType { + constructor() { + super("github.actions.results.api.v1.GetSignedArtifactURLRequest", [ + { 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): GetSignedArtifactURLRequest { + 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?: GetSignedArtifactURLRequest): GetSignedArtifactURLRequest { + 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: GetSignedArtifactURLRequest, 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.GetSignedArtifactURLRequest + */ +export const GetSignedArtifactURLRequest = new GetSignedArtifactURLRequest$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class GetSignedArtifactURLResponse$Type extends MessageType { + constructor() { + super("github.actions.results.api.v1.GetSignedArtifactURLResponse", [ + { no: 1, name: "signed_url", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): GetSignedArtifactURLResponse { + const message = { signedUrl: "" }; + 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?: GetSignedArtifactURLResponse): GetSignedArtifactURLResponse { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string signed_url */ 1: + message.signedUrl = 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: GetSignedArtifactURLResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string signed_url = 1; */ + if (message.signedUrl !== "") + writer.tag(1, WireType.LengthDelimited).string(message.signedUrl); + 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.GetSignedArtifactURLResponse + */ +export const GetSignedArtifactURLResponse = new GetSignedArtifactURLResponse$Type(); /** * @generated ServiceType for protobuf service github.actions.results.api.v1.ArtifactService */ export const ArtifactService = new ServiceType("github.actions.results.api.v1.ArtifactService", [ { name: "CreateArtifact", options: {}, I: CreateArtifactRequest, O: CreateArtifactResponse }, - { name: "FinalizeArtifact", options: {}, I: FinalizeArtifactRequest, O: FinalizeArtifactResponse } + { name: "FinalizeArtifact", options: {}, I: FinalizeArtifactRequest, O: FinalizeArtifactResponse }, + { name: "ListArtifacts", options: {}, I: ListArtifactsRequest, O: ListArtifactsResponse }, + { name: "GetSignedArtifactURL", options: {}, I: GetSignedArtifactURLRequest, O: GetSignedArtifactURLResponse } ]); 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 3d5f124e..4871eb6b 100644 --- a/packages/artifact/src/generated/results/api/v1/artifact.twirp.ts +++ b/packages/artifact/src/generated/results/api/v1/artifact.twirp.ts @@ -6,14 +6,18 @@ import { TwirpErrorCode, Interceptor, TwirpContentType, - chainInterceptors -} from 'twirp-ts' + chainInterceptors, +} from "twirp-ts"; import { CreateArtifactRequest, CreateArtifactResponse, FinalizeArtifactRequest, - FinalizeArtifactResponse -} from './artifact' + FinalizeArtifactResponse, + ListArtifactsRequest, + ListArtifactsResponse, + GetSignedArtifactURLRequest, + GetSignedArtifactURLResponse, +} from "./artifact"; //==================================// // Client Code // @@ -23,43 +27,51 @@ interface Rpc { request( service: string, method: string, - contentType: 'application/json' | 'application/protobuf', + contentType: "application/json" | "application/protobuf", data: object | Uint8Array - ): Promise + ): Promise; } export interface ArtifactServiceClient { CreateArtifact( request: CreateArtifactRequest - ): Promise + ): Promise; FinalizeArtifact( request: FinalizeArtifactRequest - ): Promise + ): Promise; + ListArtifacts(request: ListArtifactsRequest): Promise; + GetSignedArtifactURL( + request: GetSignedArtifactURLRequest + ): Promise; } export class ArtifactServiceClientJSON implements ArtifactServiceClient { - private readonly rpc: Rpc + private readonly rpc: Rpc; constructor(rpc: Rpc) { - this.rpc = rpc - this.CreateArtifact.bind(this) - this.FinalizeArtifact.bind(this) + this.rpc = rpc; + this.CreateArtifact.bind(this); + this.FinalizeArtifact.bind(this); + this.ListArtifacts.bind(this); + this.GetSignedArtifactURL.bind(this); } CreateArtifact( request: CreateArtifactRequest ): Promise { const data = CreateArtifactRequest.toJson(request, { useProtoFieldName: true, - emitDefaultValues: false - }) + emitDefaultValues: false, + }); const promise = this.rpc.request( - 'github.actions.results.api.v1.ArtifactService', - 'CreateArtifact', - 'application/json', + "github.actions.results.api.v1.ArtifactService", + "CreateArtifact", + "application/json", data as object - ) - return promise.then(data => - CreateArtifactResponse.fromJson(data as any, {ignoreUnknownFields: true}) - ) + ); + return promise.then((data) => + CreateArtifactResponse.fromJson(data as any, { + ignoreUnknownFields: true, + }) + ); } FinalizeArtifact( @@ -67,57 +79,123 @@ export class ArtifactServiceClientJSON implements ArtifactServiceClient { ): Promise { const data = FinalizeArtifactRequest.toJson(request, { useProtoFieldName: true, - emitDefaultValues: false - }) + emitDefaultValues: false, + }); const promise = this.rpc.request( - 'github.actions.results.api.v1.ArtifactService', - 'FinalizeArtifact', - 'application/json', + "github.actions.results.api.v1.ArtifactService", + "FinalizeArtifact", + "application/json", data as object - ) - return promise.then(data => + ); + return promise.then((data) => FinalizeArtifactResponse.fromJson(data as any, { - ignoreUnknownFields: true + ignoreUnknownFields: true, }) - ) + ); + } + + ListArtifacts(request: ListArtifactsRequest): Promise { + const data = ListArtifactsRequest.toJson(request, { + useProtoFieldName: true, + emitDefaultValues: false, + }); + const promise = this.rpc.request( + "github.actions.results.api.v1.ArtifactService", + "ListArtifacts", + "application/json", + data as object + ); + return promise.then((data) => + ListArtifactsResponse.fromJson(data as any, { ignoreUnknownFields: true }) + ); + } + + GetSignedArtifactURL( + request: GetSignedArtifactURLRequest + ): Promise { + const data = GetSignedArtifactURLRequest.toJson(request, { + useProtoFieldName: true, + emitDefaultValues: false, + }); + const promise = this.rpc.request( + "github.actions.results.api.v1.ArtifactService", + "GetSignedArtifactURL", + "application/json", + data as object + ); + return promise.then((data) => + GetSignedArtifactURLResponse.fromJson(data as any, { + ignoreUnknownFields: true, + }) + ); } } export class ArtifactServiceClientProtobuf implements ArtifactServiceClient { - private readonly rpc: Rpc + private readonly rpc: Rpc; constructor(rpc: Rpc) { - this.rpc = rpc - this.CreateArtifact.bind(this) - this.FinalizeArtifact.bind(this) + this.rpc = rpc; + this.CreateArtifact.bind(this); + this.FinalizeArtifact.bind(this); + this.ListArtifacts.bind(this); + this.GetSignedArtifactURL.bind(this); } CreateArtifact( request: CreateArtifactRequest ): Promise { - const data = CreateArtifactRequest.toBinary(request) + const data = CreateArtifactRequest.toBinary(request); const promise = this.rpc.request( - 'github.actions.results.api.v1.ArtifactService', - 'CreateArtifact', - 'application/protobuf', + "github.actions.results.api.v1.ArtifactService", + "CreateArtifact", + "application/protobuf", data - ) - return promise.then(data => + ); + return promise.then((data) => CreateArtifactResponse.fromBinary(data as Uint8Array) - ) + ); } FinalizeArtifact( request: FinalizeArtifactRequest ): Promise { - const data = FinalizeArtifactRequest.toBinary(request) + const data = FinalizeArtifactRequest.toBinary(request); const promise = this.rpc.request( - 'github.actions.results.api.v1.ArtifactService', - 'FinalizeArtifact', - 'application/protobuf', + "github.actions.results.api.v1.ArtifactService", + "FinalizeArtifact", + "application/protobuf", data - ) - return promise.then(data => + ); + return promise.then((data) => FinalizeArtifactResponse.fromBinary(data as Uint8Array) - ) + ); + } + + ListArtifacts(request: ListArtifactsRequest): Promise { + const data = ListArtifactsRequest.toBinary(request); + const promise = this.rpc.request( + "github.actions.results.api.v1.ArtifactService", + "ListArtifacts", + "application/protobuf", + data + ); + return promise.then((data) => + ListArtifactsResponse.fromBinary(data as Uint8Array) + ); + } + + GetSignedArtifactURL( + request: GetSignedArtifactURLRequest + ): Promise { + const data = GetSignedArtifactURLRequest.toBinary(request); + const promise = this.rpc.request( + "github.actions.results.api.v1.ArtifactService", + "GetSignedArtifactURL", + "application/protobuf", + data + ); + return promise.then((data) => + GetSignedArtifactURLResponse.fromBinary(data as Uint8Array) + ); } } @@ -129,33 +207,45 @@ export interface ArtifactServiceTwirp { CreateArtifact( ctx: T, request: CreateArtifactRequest - ): Promise + ): Promise; FinalizeArtifact( ctx: T, request: FinalizeArtifactRequest - ): Promise + ): Promise; + ListArtifacts( + ctx: T, + request: ListArtifactsRequest + ): Promise; + GetSignedArtifactURL( + ctx: T, + request: GetSignedArtifactURLRequest + ): Promise; } export enum ArtifactServiceMethod { - CreateArtifact = 'CreateArtifact', - FinalizeArtifact = 'FinalizeArtifact' + CreateArtifact = "CreateArtifact", + FinalizeArtifact = "FinalizeArtifact", + ListArtifacts = "ListArtifacts", + GetSignedArtifactURL = "GetSignedArtifactURL", } export const ArtifactServiceMethodList = [ ArtifactServiceMethod.CreateArtifact, - ArtifactServiceMethod.FinalizeArtifact -] + ArtifactServiceMethod.FinalizeArtifact, + ArtifactServiceMethod.ListArtifacts, + ArtifactServiceMethod.GetSignedArtifactURL, +]; export function createArtifactServiceServer< T extends TwirpContext = TwirpContext >(service: ArtifactServiceTwirp) { return new TwirpServer({ service, - packageName: 'github.actions.results.api.v1', - serviceName: 'ArtifactService', + packageName: "github.actions.results.api.v1", + serviceName: "ArtifactService", methodList: ArtifactServiceMethodList, - matchRoute: matchArtifactServiceRoute - }) + matchRoute: matchArtifactServiceRoute, + }); } function matchArtifactServiceRoute( @@ -163,7 +253,7 @@ function matchArtifactServiceRoute( events: RouterEvents ) { switch (method) { - case 'CreateArtifact': + case "CreateArtifact": return async ( ctx: T, service: ArtifactServiceTwirp, @@ -174,16 +264,16 @@ function matchArtifactServiceRoute( CreateArtifactResponse >[] ) => { - ctx = {...ctx, methodName: 'CreateArtifact'} - await events.onMatch(ctx) + ctx = { ...ctx, methodName: "CreateArtifact" }; + await events.onMatch(ctx); return handleArtifactServiceCreateArtifactRequest( ctx, service, data, interceptors - ) - } - case 'FinalizeArtifact': + ); + }; + case "FinalizeArtifact": return async ( ctx: T, service: ArtifactServiceTwirp, @@ -194,19 +284,59 @@ function matchArtifactServiceRoute( FinalizeArtifactResponse >[] ) => { - ctx = {...ctx, methodName: 'FinalizeArtifact'} - await events.onMatch(ctx) + ctx = { ...ctx, methodName: "FinalizeArtifact" }; + await events.onMatch(ctx); return handleArtifactServiceFinalizeArtifactRequest( ctx, service, data, interceptors - ) - } + ); + }; + case "ListArtifacts": + return async ( + ctx: T, + service: ArtifactServiceTwirp, + data: Buffer, + interceptors?: Interceptor< + T, + ListArtifactsRequest, + ListArtifactsResponse + >[] + ) => { + ctx = { ...ctx, methodName: "ListArtifacts" }; + await events.onMatch(ctx); + return handleArtifactServiceListArtifactsRequest( + ctx, + service, + data, + interceptors + ); + }; + case "GetSignedArtifactURL": + return async ( + ctx: T, + service: ArtifactServiceTwirp, + data: Buffer, + interceptors?: Interceptor< + T, + GetSignedArtifactURLRequest, + GetSignedArtifactURLResponse + >[] + ) => { + ctx = { ...ctx, methodName: "GetSignedArtifactURL" }; + await events.onMatch(ctx); + return handleArtifactServiceGetSignedArtifactURLRequest( + ctx, + service, + data, + interceptors + ); + }; default: - events.onNotFound() - const msg = `no handler found` - throw new TwirpError(TwirpErrorCode.BadRoute, msg) + events.onNotFound(); + const msg = `no handler found`; + throw new TwirpError(TwirpErrorCode.BadRoute, msg); } } @@ -225,17 +355,17 @@ function handleArtifactServiceCreateArtifactRequest< service, data, interceptors - ) + ); case TwirpContentType.Protobuf: return handleArtifactServiceCreateArtifactProtobuf( ctx, service, data, interceptors - ) + ); default: - const msg = 'unexpected Content-Type' - throw new TwirpError(TwirpErrorCode.BadRoute, msg) + const msg = "unexpected Content-Type"; + throw new TwirpError(TwirpErrorCode.BadRoute, msg); } } @@ -258,17 +388,79 @@ function handleArtifactServiceFinalizeArtifactRequest< service, data, interceptors - ) + ); case TwirpContentType.Protobuf: return handleArtifactServiceFinalizeArtifactProtobuf( ctx, service, data, interceptors - ) + ); default: - const msg = 'unexpected Content-Type' - throw new TwirpError(TwirpErrorCode.BadRoute, msg) + const msg = "unexpected Content-Type"; + throw new TwirpError(TwirpErrorCode.BadRoute, msg); + } +} + +function handleArtifactServiceListArtifactsRequest< + T extends TwirpContext = TwirpContext +>( + ctx: T, + service: ArtifactServiceTwirp, + data: Buffer, + interceptors?: Interceptor[] +): Promise { + switch (ctx.contentType) { + case TwirpContentType.JSON: + return handleArtifactServiceListArtifactsJSON( + ctx, + service, + data, + interceptors + ); + case TwirpContentType.Protobuf: + return handleArtifactServiceListArtifactsProtobuf( + ctx, + service, + data, + interceptors + ); + default: + const msg = "unexpected Content-Type"; + throw new TwirpError(TwirpErrorCode.BadRoute, msg); + } +} + +function handleArtifactServiceGetSignedArtifactURLRequest< + T extends TwirpContext = TwirpContext +>( + ctx: T, + service: ArtifactServiceTwirp, + data: Buffer, + interceptors?: Interceptor< + T, + GetSignedArtifactURLRequest, + GetSignedArtifactURLResponse + >[] +): Promise { + switch (ctx.contentType) { + case TwirpContentType.JSON: + return handleArtifactServiceGetSignedArtifactURLJSON( + ctx, + service, + data, + interceptors + ); + case TwirpContentType.Protobuf: + return handleArtifactServiceGetSignedArtifactURLProtobuf( + ctx, + service, + data, + interceptors + ); + default: + const msg = "unexpected Content-Type"; + throw new TwirpError(TwirpErrorCode.BadRoute, msg); } } async function handleArtifactServiceCreateArtifactJSON< @@ -279,16 +471,18 @@ async function handleArtifactServiceCreateArtifactJSON< data: Buffer, interceptors?: Interceptor[] ) { - let request: CreateArtifactRequest - let response: CreateArtifactResponse + let request: CreateArtifactRequest; + let response: CreateArtifactResponse; try { - const body = JSON.parse(data.toString() || '{}') - request = CreateArtifactRequest.fromJson(body, {ignoreUnknownFields: true}) + const body = JSON.parse(data.toString() || "{}"); + request = CreateArtifactRequest.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) + const msg = "the json request could not be decoded"; + throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true); } } @@ -297,20 +491,20 @@ async function handleArtifactServiceCreateArtifactJSON< T, CreateArtifactRequest, CreateArtifactResponse - > + >; response = await interceptor(ctx, request!, (ctx, inputReq) => { - return service.CreateArtifact(ctx, inputReq) - }) + return service.CreateArtifact(ctx, inputReq); + }); } else { - response = await service.CreateArtifact(ctx, request!) + response = await service.CreateArtifact(ctx, request!); } return JSON.stringify( CreateArtifactResponse.toJson(response, { useProtoFieldName: true, - emitDefaultValues: false + emitDefaultValues: false, }) as string - ) + ); } async function handleArtifactServiceFinalizeArtifactJSON< @@ -325,18 +519,18 @@ async function handleArtifactServiceFinalizeArtifactJSON< FinalizeArtifactResponse >[] ) { - let request: FinalizeArtifactRequest - let response: FinalizeArtifactResponse + let request: FinalizeArtifactRequest; + let response: FinalizeArtifactResponse; try { - const body = JSON.parse(data.toString() || '{}') + const body = JSON.parse(data.toString() || "{}"); request = FinalizeArtifactRequest.fromJson(body, { - ignoreUnknownFields: true - }) + 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) + const msg = "the json request could not be decoded"; + throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true); } } @@ -345,20 +539,112 @@ async function handleArtifactServiceFinalizeArtifactJSON< T, FinalizeArtifactRequest, FinalizeArtifactResponse - > + >; response = await interceptor(ctx, request!, (ctx, inputReq) => { - return service.FinalizeArtifact(ctx, inputReq) - }) + return service.FinalizeArtifact(ctx, inputReq); + }); } else { - response = await service.FinalizeArtifact(ctx, request!) + response = await service.FinalizeArtifact(ctx, request!); } return JSON.stringify( FinalizeArtifactResponse.toJson(response, { useProtoFieldName: true, - emitDefaultValues: false + emitDefaultValues: false, }) as string - ) + ); +} + +async function handleArtifactServiceListArtifactsJSON< + T extends TwirpContext = TwirpContext +>( + ctx: T, + service: ArtifactServiceTwirp, + data: Buffer, + interceptors?: Interceptor[] +) { + let request: ListArtifactsRequest; + let response: ListArtifactsResponse; + + try { + const body = JSON.parse(data.toString() || "{}"); + request = ListArtifactsRequest.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, + ListArtifactsRequest, + ListArtifactsResponse + >; + response = await interceptor(ctx, request!, (ctx, inputReq) => { + return service.ListArtifacts(ctx, inputReq); + }); + } else { + response = await service.ListArtifacts(ctx, request!); + } + + return JSON.stringify( + ListArtifactsResponse.toJson(response, { + useProtoFieldName: true, + emitDefaultValues: false, + }) as string + ); +} + +async function handleArtifactServiceGetSignedArtifactURLJSON< + T extends TwirpContext = TwirpContext +>( + ctx: T, + service: ArtifactServiceTwirp, + data: Buffer, + interceptors?: Interceptor< + T, + GetSignedArtifactURLRequest, + GetSignedArtifactURLResponse + >[] +) { + let request: GetSignedArtifactURLRequest; + let response: GetSignedArtifactURLResponse; + + try { + const body = JSON.parse(data.toString() || "{}"); + request = GetSignedArtifactURLRequest.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, + GetSignedArtifactURLRequest, + GetSignedArtifactURLResponse + >; + response = await interceptor(ctx, request!, (ctx, inputReq) => { + return service.GetSignedArtifactURL(ctx, inputReq); + }); + } else { + response = await service.GetSignedArtifactURL(ctx, request!); + } + + return JSON.stringify( + GetSignedArtifactURLResponse.toJson(response, { + useProtoFieldName: true, + emitDefaultValues: false, + }) as string + ); } async function handleArtifactServiceCreateArtifactProtobuf< T extends TwirpContext = TwirpContext @@ -368,15 +654,15 @@ async function handleArtifactServiceCreateArtifactProtobuf< data: Buffer, interceptors?: Interceptor[] ) { - let request: CreateArtifactRequest - let response: CreateArtifactResponse + let request: CreateArtifactRequest; + let response: CreateArtifactResponse; try { - request = CreateArtifactRequest.fromBinary(data) + request = CreateArtifactRequest.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) + const msg = "the protobuf request could not be decoded"; + throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true); } } @@ -385,15 +671,15 @@ async function handleArtifactServiceCreateArtifactProtobuf< T, CreateArtifactRequest, CreateArtifactResponse - > + >; response = await interceptor(ctx, request!, (ctx, inputReq) => { - return service.CreateArtifact(ctx, inputReq) - }) + return service.CreateArtifact(ctx, inputReq); + }); } else { - response = await service.CreateArtifact(ctx, request!) + response = await service.CreateArtifact(ctx, request!); } - return Buffer.from(CreateArtifactResponse.toBinary(response)) + return Buffer.from(CreateArtifactResponse.toBinary(response)); } async function handleArtifactServiceFinalizeArtifactProtobuf< @@ -408,15 +694,15 @@ async function handleArtifactServiceFinalizeArtifactProtobuf< FinalizeArtifactResponse >[] ) { - let request: FinalizeArtifactRequest - let response: FinalizeArtifactResponse + let request: FinalizeArtifactRequest; + let response: FinalizeArtifactResponse; try { - request = FinalizeArtifactRequest.fromBinary(data) + request = FinalizeArtifactRequest.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) + const msg = "the protobuf request could not be decoded"; + throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true); } } @@ -425,13 +711,89 @@ async function handleArtifactServiceFinalizeArtifactProtobuf< T, FinalizeArtifactRequest, FinalizeArtifactResponse - > + >; response = await interceptor(ctx, request!, (ctx, inputReq) => { - return service.FinalizeArtifact(ctx, inputReq) - }) + return service.FinalizeArtifact(ctx, inputReq); + }); } else { - response = await service.FinalizeArtifact(ctx, request!) + response = await service.FinalizeArtifact(ctx, request!); } - return Buffer.from(FinalizeArtifactResponse.toBinary(response)) + return Buffer.from(FinalizeArtifactResponse.toBinary(response)); +} + +async function handleArtifactServiceListArtifactsProtobuf< + T extends TwirpContext = TwirpContext +>( + ctx: T, + service: ArtifactServiceTwirp, + data: Buffer, + interceptors?: Interceptor[] +) { + let request: ListArtifactsRequest; + let response: ListArtifactsResponse; + + try { + request = ListArtifactsRequest.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, + ListArtifactsRequest, + ListArtifactsResponse + >; + response = await interceptor(ctx, request!, (ctx, inputReq) => { + return service.ListArtifacts(ctx, inputReq); + }); + } else { + response = await service.ListArtifacts(ctx, request!); + } + + return Buffer.from(ListArtifactsResponse.toBinary(response)); +} + +async function handleArtifactServiceGetSignedArtifactURLProtobuf< + T extends TwirpContext = TwirpContext +>( + ctx: T, + service: ArtifactServiceTwirp, + data: Buffer, + interceptors?: Interceptor< + T, + GetSignedArtifactURLRequest, + GetSignedArtifactURLResponse + >[] +) { + let request: GetSignedArtifactURLRequest; + let response: GetSignedArtifactURLResponse; + + try { + request = GetSignedArtifactURLRequest.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, + GetSignedArtifactURLRequest, + GetSignedArtifactURLResponse + >; + response = await interceptor(ctx, request!, (ctx, inputReq) => { + return service.GetSignedArtifactURL(ctx, inputReq); + }); + } else { + response = await service.GetSignedArtifactURL(ctx, request!); + } + + return Buffer.from(GetSignedArtifactURLResponse.toBinary(response)); } diff --git a/packages/artifact/src/internal/client.ts b/packages/artifact/src/internal/client.ts index 6ffc632c..c32611b6 100644 --- a/packages/artifact/src/internal/client.ts +++ b/packages/artifact/src/internal/client.ts @@ -6,12 +6,16 @@ import { DownloadArtifactOptions, GetArtifactResponse, ListArtifactsResponse, - DownloadArtifactResponse + DownloadArtifactResponse, + LookupOptions } from './shared/interfaces' import {uploadArtifact} from './upload/upload-artifact' -import {downloadArtifact} from './download/download-artifact' -import {getArtifact} from './find/get-artifact' -import {listArtifacts} from './find/list-artifacts' +import { + downloadArtifactPublic, + downloadArtifactInternal +} from './download/download-artifact' +import {getArtifactPublic, getArtifactInternal} from './find/get-artifact' +import {listArtifactsPublic, listArtifactsInternal} from './find/list-artifacts' export interface ArtifactClient { /** @@ -31,62 +35,46 @@ export interface ArtifactClient { ): Promise /** - * Lists all artifacts that are part of a workflow run. + * Lists all artifacts that are part of the current workflow run. + * This function will return at most 1000 artifacts per workflow run. * - * This calls the public List-Artifacts API https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts - * Due to paginated responses from the public API. This function will return at most 1000 artifacts per workflow run (100 per page * maximum 10 calls) + * If options.token is specified, this will call the public List-Artifacts API which can list from other runs. + * https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts * - * @param workflowRunId The workflow run id that the artifact belongs to - * @param repositoryOwner The owner of the repository that the artifact belongs to - * @param repositoryName The name of the repository that the artifact belongs to - * @param token A token with the appropriate permission to the repository to list artifacts + * @param options Extra options that allow for the customization of the list behavior * @returns ListArtifactResponse object */ - listArtifacts( - workflowRunId: number, - repositoryOwner: string, - repositoryName: string, - token: string - ): Promise + listArtifacts(options?: LookupOptions): Promise /** - * Finds an artifact by name given a repository and workflow run id. + * Finds an artifact by name. * - * This calls the public List-Artifacts API with a name filter https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts + * If options.token is specified, this will use the public List Artifacts API with a name filter which can get artifacts from other runs. + * https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts * @actions/artifact > 2.0.0 does not allow for creating multiple artifacts with the same name in the same workflow run. - * It is possible to have multiple artifacts with the same name in the same workflow run by using old versions of upload-artifact (v1,v2 and v3) or @actions/artifact < v2.0.0 + * It is possible to have multiple artifacts with the same name in the same workflow run by using old versions of upload-artifact (v1,v2 and v3), @actions/artifact < v2.0.0 or it is a rerun. * If there are multiple artifacts with the same name in the same workflow run this function will return the first artifact that matches the name. * * @param artifactName The name of the artifact to find - * @param workflowRunId The workflow run id that the artifact belongs to - * @param repositoryOwner The owner of the repository that the artifact belongs to - * @param repositoryName The name of the repository that the artifact belongs to - * @param token A token with the appropriate permission to the repository to find the artifact + * @param options Extra options that allow for the customization of the get behavior */ getArtifact( artifactName: string, - workflowRunId: number, - repositoryOwner: string, - repositoryName: string, - token: string + options?: LookupOptions ): Promise /** - * Downloads an artifact and unzips the content + * Downloads an artifact and unzips the content. + * + * If options.token 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 repositoryOwner The owner of the repository that the artifact belongs to - * @param repositoryName The name of the repository that the artifact belongs to - * @param token A token with the appropriate permission to the repository to download the artifact * @param options Extra options that allow for the customization of the download behavior * @returns single DownloadArtifactResponse object */ downloadArtifact( artifactId: number, - repositoryOwner: string, - repositoryName: string, - token: string, - options?: DownloadArtifactOptions + options?: DownloadArtifactOptions & LookupOptions ): Promise } @@ -137,10 +125,7 @@ If the error persists, please check whether Actions is operating normally at [ht */ async downloadArtifact( artifactId: number, - repositoryOwner: string, - repositoryName: string, - token: string, - options?: DownloadArtifactOptions + options?: DownloadArtifactOptions & LookupOptions ): Promise { if (isGhes()) { warning( @@ -152,13 +137,19 @@ If the error persists, please check whether Actions is operating normally at [ht } try { - return downloadArtifact( - artifactId, - repositoryOwner, - repositoryName, - token, - options - ) + if (options?.token) { + const {repositoryOwner, repositoryName, token, ...downloadOptions} = + options + return downloadArtifactPublic( + artifactId, + repositoryOwner, + repositoryName, + token, + downloadOptions + ) + } + + return downloadArtifactInternal(artifactId) } catch (error) { warning( `Artifact download failed with error: ${error}. @@ -177,12 +168,7 @@ If the error persists, please check whether Actions and API requests are operati /** * List Artifacts */ - async listArtifacts( - workflowRunId: number, - repositoryOwner: string, - repositoryName: string, - token: string - ): Promise { + async listArtifacts(options?: LookupOptions): Promise { if (isGhes()) { warning( `@actions/artifact v2.0.0+ and download-artifact@v4+ are not currently supported on GHES.` @@ -193,12 +179,16 @@ If the error persists, please check whether Actions and API requests are operati } try { - return listArtifacts( - workflowRunId, - repositoryOwner, - repositoryName, - token - ) + if (options?.token) { + return listArtifactsPublic( + options.workflowRunId, + options.repositoryOwner, + options.repositoryName, + options.token + ) + } + + return listArtifactsInternal() } catch (error: unknown) { warning( `Listing Artifacts failed with error: ${error}. @@ -219,10 +209,7 @@ If the error persists, please check whether Actions and API requests are operati */ async getArtifact( artifactName: string, - workflowRunId: number, - repositoryOwner: string, - repositoryName: string, - token: string + options?: LookupOptions ): Promise { if (isGhes()) { warning( @@ -234,13 +221,17 @@ If the error persists, please check whether Actions and API requests are operati } try { - return getArtifact( - artifactName, - workflowRunId, - repositoryOwner, - repositoryName, - token - ) + if (options?.token) { + return getArtifactPublic( + artifactName, + options.workflowRunId, + options.repositoryOwner, + options.repositoryName, + options.token + ) + } + + return getArtifactInternal(artifactName) } catch (error: unknown) { warning( `Fetching Artifact failed with error: ${error}. diff --git a/packages/artifact/src/internal/download/download-artifact.ts b/packages/artifact/src/internal/download/download-artifact.ts index 6301bfb5..ad629d5c 100644 --- a/packages/artifact/src/internal/download/download-artifact.ts +++ b/packages/artifact/src/internal/download/download-artifact.ts @@ -9,6 +9,9 @@ import { } from '../shared/interfaces' import {getUserAgentString} from '../shared/user-agent' import {getGitHubWorkspaceDir} from '../shared/config' +import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client' +import {GetSignedArtifactURLRequest, ListArtifactsRequest} from 'src/generated' +import {getBackendIdsFromToken} from '../shared/util' const scrubQueryParameters = (url: string): string => { const parsed = new URL(url) @@ -42,23 +45,14 @@ async function streamExtract(url: string, directory: string): Promise { return response.message.pipe(unzipper.Extract({path: directory})).promise() } -export async function downloadArtifact( +export async function downloadArtifactPublic( artifactId: number, repositoryOwner: string, repositoryName: string, token: string, options?: DownloadArtifactOptions ): Promise { - const downloadPath = options?.path || getGitHubWorkspaceDir() - - if (!(await exists(downloadPath))) { - core.debug( - `Artifact destination folder does not exist, creating: ${downloadPath}` - ) - await fs.mkdir(downloadPath, {recursive: true}) - } else { - core.debug(`Artifact destination folder already exists: ${downloadPath}`) - } + const downloadPath = await resolveOrCreateDirectory(options?.path) const api = github.getOctokit(token) @@ -99,3 +93,72 @@ export async function downloadArtifact( return {success: true, downloadPath} } + +export async function downloadArtifactInternal( + artifactId: number, + options?: DownloadArtifactOptions +): Promise { + const downloadPath = await resolveOrCreateDirectory(options?.path) + + const artifactClient = internalArtifactTwirpClient() + + const {workflowRunBackendId, workflowJobRunBackendId} = + getBackendIdsFromToken() + + const listReq: ListArtifactsRequest = { + workflowRunBackendId, + workflowJobRunBackendId, + nameFilter: '', + idFilter: '0' // TODO(robherley): zero values are awkward, use pb wrappers + } + + const {artifacts} = await artifactClient.ListArtifacts(listReq) + + if (artifacts.length === 0) { + core.warning( + `No artifacts found for ID: ${artifactId}\nAre you trying to download from a different run? Try specifying a github-token with \`actions:read\` scope.` + ) + return {success: false} + } + + if (artifacts.length > 1) { + core.warning('Multiple artifacts found, defaulting to first.') + } + + const signedReq: GetSignedArtifactURLRequest = { + workflowRunBackendId: artifacts[0].workflowRunBackendId, + workflowJobRunBackendId: artifacts[0].workflowJobRunBackendId, + name: artifacts[0].name + } + + const {signedUrl} = await artifactClient.GetSignedArtifactURL(signedReq) + + core.info( + `Redirecting to blob download url: ${scrubQueryParameters(signedUrl)}` + ) + + try { + core.info(`Starting download of artifact to: ${downloadPath}`) + await streamExtract(signedUrl, downloadPath) + core.info(`Artifact download completed successfully.`) + } catch (error) { + throw new Error(`Unable to download and extract artifact: ${error.message}`) + } + + return {success: true, downloadPath} +} + +async function resolveOrCreateDirectory( + downloadPath = getGitHubWorkspaceDir() +): Promise { + if (!(await exists(downloadPath))) { + core.debug( + `Artifact destination folder does not exist, creating: ${downloadPath}` + ) + await fs.mkdir(downloadPath, {recursive: true}) + } else { + core.debug(`Artifact destination folder already exists: ${downloadPath}`) + } + + return downloadPath +} diff --git a/packages/artifact/src/internal/find/get-artifact.ts b/packages/artifact/src/internal/find/get-artifact.ts index 24c38a99..5112f573 100644 --- a/packages/artifact/src/internal/find/get-artifact.ts +++ b/packages/artifact/src/internal/find/get-artifact.ts @@ -1,14 +1,17 @@ -import {GetArtifactResponse} from '../shared/interfaces' import {getOctokit} from '@actions/github' -import {getUserAgentString} from '../shared/user-agent' -import {defaults as defaultGitHubOptions} from '@actions/github/lib/utils' -import {getRetryOptions} from './retry-options' -import {requestLog} from '@octokit/plugin-request-log' import {retry} from '@octokit/plugin-retry' import * as core from '@actions/core' import {OctokitOptions} from '@octokit/core/dist-types/types' +import {defaults as defaultGitHubOptions} from '@actions/github/lib/utils' +import {getRetryOptions} from './retry-options' +import {requestLog} from '@octokit/plugin-request-log' +import {GetArtifactResponse} from '../shared/interfaces' +import {getBackendIdsFromToken} from '../shared/util' +import {getUserAgentString} from '../shared/user-agent' +import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client' +import {ListArtifactsRequest} from '../../generated' -export async function getArtifact( +export async function getArtifactPublic( artifactName: string, workflowRunId: number, repositoryOwner: string, @@ -62,8 +65,55 @@ export async function getArtifact( artifact: { name: getArtifactResp.data.artifacts[0].name, id: getArtifactResp.data.artifacts[0].id, - url: getArtifactResp.data.artifacts[0].url, size: getArtifactResp.data.artifacts[0].size_in_bytes } } } + +export async function getArtifactInternal( + artifactName: string +): Promise { + const artifactClient = internalArtifactTwirpClient() + + const {workflowRunBackendId, workflowJobRunBackendId} = + getBackendIdsFromToken() + + const req: ListArtifactsRequest = { + workflowRunBackendId, + workflowJobRunBackendId, + nameFilter: artifactName, + idFilter: '0' // TODO(robherley): int64 zero value, change this to be optional + } + + const res = await artifactClient.ListArtifacts(req) + + if (res.artifacts.length === 0) { + core.warning('no artifacts found') + return { + success: false + } + } + + if (res.artifacts.length > 1) { + core.warning( + 'more than one artifact found for a single name, returning first' + ) + } + + // In the case of reruns, we may have artifacts with the same name scoped under the same workflow run. + // Let's prefer the artifact closest scoped to this run. + // If it doesn't exist (e.g. partial rerun) we'll use the first match. + const artifact = + res.artifacts.find( + artifact => artifact.workflowRunBackendId === workflowRunBackendId + ) || res.artifacts[0] + + return { + success: true, + artifact: { + name: artifact.name, + id: Number(artifact.databaseId), + size: Number(artifact.size) + } + } +} diff --git a/packages/artifact/src/internal/find/list-artifacts.ts b/packages/artifact/src/internal/find/list-artifacts.ts index ea15fb36..7a23aea1 100644 --- a/packages/artifact/src/internal/find/list-artifacts.ts +++ b/packages/artifact/src/internal/find/list-artifacts.ts @@ -7,13 +7,16 @@ 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 {ListArtifactsRequest} from 'src/generated' // Limiting to 1000 for perf reasons const maximumArtifactCount = 1000 const paginationCount = 100 const maxNumberOfPages = maximumArtifactCount / paginationCount -export async function listArtifacts( +export async function listArtifactsPublic( workflowRunId: number, repositoryOwner: string, repositoryName: string, @@ -62,7 +65,6 @@ export async function listArtifacts( artifacts.push({ name: artifact.name, id: artifact.id, - url: artifact.url, size: artifact.size_in_bytes }) } @@ -89,13 +91,39 @@ export async function listArtifacts( artifacts.push({ name: artifact.name, id: artifact.id, - url: artifact.url, size: artifact.size_in_bytes }) } } - info(`Finished fetching artifact list`) + info(`Found ${artifacts.length} artifact(s)`) + + return { + artifacts + } +} + +export async function listArtifactsInternal(): Promise { + const artifactClient = internalArtifactTwirpClient() + + const {workflowRunBackendId, workflowJobRunBackendId} = + getBackendIdsFromToken() + + const req: ListArtifactsRequest = { + workflowRunBackendId, + workflowJobRunBackendId, + nameFilter: '', + idFilter: '0' // TODO(robherley): zero values are awkward, use pb wrappers + } + + const res = await artifactClient.ListArtifacts(req) + const artifacts = res.artifacts.map(artifact => ({ + name: artifact.name, + id: Number(artifact.databaseId), + size: Number(artifact.size) + })) + + info(`Found ${artifacts.length} artifact(s)`) return { artifacts diff --git a/packages/artifact/src/internal/shared/artifact-twirp-client.ts b/packages/artifact/src/internal/shared/artifact-twirp-client.ts index 09355808..2498ba3f 100644 --- a/packages/artifact/src/internal/shared/artifact-twirp-client.ts +++ b/packages/artifact/src/internal/shared/artifact-twirp-client.ts @@ -3,6 +3,7 @@ import {BearerCredentialHandler} from '@actions/http-client/lib/auth' import {info, debug} from '@actions/core' import {ArtifactServiceClientJSON} from '../../generated' import {getResultsServiceUrl, getRuntimeToken} from './config' +import {getUserAgentString} from './user-agent' // The twirp http client must implement this interface interface Rpc { @@ -157,17 +158,16 @@ class ArtifactHttpClient implements Rpc { } } -export function createArtifactTwirpClient( - type: 'upload' | 'download', - maxAttempts?: number, - baseRetryIntervalMilliseconds?: number, +export function internalArtifactTwirpClient(options?: { + maxAttempts?: number + baseRetryIntervalMilliseconds?: number retryMultiplier?: number -): ArtifactServiceClientJSON { +}): ArtifactServiceClientJSON { const client = new ArtifactHttpClient( - `@actions/artifact-${type}`, - maxAttempts, - baseRetryIntervalMilliseconds, - retryMultiplier + getUserAgentString(), + options?.maxAttempts, + options?.baseRetryIntervalMilliseconds, + options?.retryMultiplier ) return new ArtifactServiceClientJSON(client) } diff --git a/packages/artifact/src/internal/shared/interfaces.ts b/packages/artifact/src/internal/shared/interfaces.ts index b160d1be..a312c5db 100644 --- a/packages/artifact/src/internal/shared/interfaces.ts +++ b/packages/artifact/src/internal/shared/interfaces.ts @@ -120,13 +120,21 @@ export interface Artifact { */ id: number - /** - * The URL of the artifact - */ - url: string - /** * The size of the artifact in bytes */ size: number } + +// LookupOptions are for fetching Artifact(s) out of the scope of the current run. +// Must specify a PAT with actions:read scope for cross run/repo lookup otherwise these will be ignored. +export interface LookupOptions { + // Token with actions:read permissions + token: string + // WorkflowRun of the artifact(s) to lookup + workflowRunId: number + // Repository owner + repositoryOwner: string + // Repository name + repositoryName: string +} diff --git a/packages/artifact/src/internal/shared/util.ts b/packages/artifact/src/internal/shared/util.ts index bc98abde..07392b36 100644 --- a/packages/artifact/src/internal/shared/util.ts +++ b/packages/artifact/src/internal/shared/util.ts @@ -1,3 +1,4 @@ +import * as core from '@actions/core' import {getRuntimeToken} from './config' import jwt_decode from 'jwt-decode' @@ -11,7 +12,7 @@ interface ActionsToken { } const InvalidJwtError = new Error( - 'Failed to get backend IDs: The provided JWT token is invalid' + 'Failed to get backend IDs: The provided JWT token is invalid and/or missing claims' ) // uses the JWT token claims to get the @@ -41,24 +42,29 @@ export function getBackendIdsFromToken(): BackendIds { for (const scopes of scpParts) { const scopeParts = scopes.split(':') + if (scopeParts?.[0] !== 'Actions.Results') { + // not the Actions.Results scope + continue + } + /* * example scopeParts: * ["Actions.Results", "ce7f54c7-61c7-4aae-887f-30da475f5f1a", "ca395085-040a-526b-2ce8-bdc85f692774"] */ if (scopeParts.length !== 3) { - // not the Actions.Results scope - continue + // missing expected number of claims + throw InvalidJwtError } - if (scopeParts[0] !== 'Actions.Results') { - // not the Actions.Results scope - continue - } - - return { + const ids = { workflowRunBackendId: scopeParts[1], workflowJobRunBackendId: scopeParts[2] } + + core.debug(`Workflow Run Backend ID: ${ids.workflowRunBackendId}`) + core.debug(`Workflow Job Run Backend ID: ${ids.workflowJobRunBackendId}`) + + return ids } throw InvalidJwtError diff --git a/packages/artifact/src/internal/upload/upload-artifact.ts b/packages/artifact/src/internal/upload/upload-artifact.ts index bcc91ec8..011dc4c8 100644 --- a/packages/artifact/src/internal/upload/upload-artifact.ts +++ b/packages/artifact/src/internal/upload/upload-artifact.ts @@ -2,7 +2,7 @@ import * as core from '@actions/core' import {UploadOptions, UploadResponse} from '../shared/interfaces' import {getExpiration} from './retention' import {validateArtifactName} from './path-and-artifact-name-validation' -import {createArtifactTwirpClient} from '../shared/artifact-twirp-client' +import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client' import { UploadZipSpecification, getUploadZipSpecification, @@ -44,21 +44,9 @@ export async function uploadArtifact( // get the IDs needed for the artifact creation const backendIds = getBackendIdsFromToken() - if (!backendIds.workflowRunBackendId || !backendIds.workflowJobRunBackendId) { - core.warning( - `Failed to get the necessary backend ids which are required to create the artifact` - ) - return { - success: false - } - } - core.debug(`Workflow Run Backend ID: ${backendIds.workflowRunBackendId}`) - core.debug( - `Workflow Job Run Backend ID: ${backendIds.workflowJobRunBackendId}` - ) // create the artifact client - const artifactClient = createArtifactTwirpClient('upload') + const artifactClient = internalArtifactTwirpClient() // create the artifact const createArtifactReq: CreateArtifactRequest = {