UNPKG

@hapic/harbor

Version:

A harbor http api client.

650 lines (649 loc) 19.7 kB
import { CONNECTION_STRING_PARSE_ERROR_INSTANCE, Client, ConnectionStringParseError, createClient as createClient$1, createClientRegistry, isClient as isClient$1, isClientError, isClientErrorDueNetworkIssue, isClientErrorWithStatusCode, isConnectionStringParseError, isHapicError, isHttpResponseError, isNetworkError, markInstanceof, resolveConnectionOptions, splitConnectionString } from "hapic"; import { isObject, merge } from "smob"; //#region src/constants.ts let HeaderName = /* @__PURE__ */ function(HeaderName) { HeaderName["LOCATION"] = "location"; HeaderName["IS_RESOURCE_NAME"] = "X-Is-Resource-Name"; HeaderName["TOTAL_COUNT"] = "X-Total-Count"; return HeaderName; }({}); //#endregion //#region src/utils/connection-string.ts const FORMAT_HINT = "Harbor connection string must be in the following format: user:password@host"; function parseConnectionString(connectionString) { const { credentials, host } = splitConnectionString(connectionString, { message: FORMAT_HINT }); const authParts = credentials.split(":"); if (authParts.length !== 2) throw new ConnectionStringParseError(FORMAT_HINT); return { host, user: authParts[0], password: authParts[1] }; } //#endregion //#region src/utils/query-string.ts function buildQueryString(input, withQuestionMark = true) { if (typeof input === "undefined") return ""; const searchParams = new URLSearchParams(); const keys = Object.keys(input); for (const key of keys) { const value = input[key]; if (isObject(value)) { const childKeys = Object.keys(value); if (childKeys.length > 0) { const childSearchParams = new URLSearchParams(); for (const childKey of childKeys) childSearchParams.append(childKey, `${encodeURIComponent(value[childKey])}`); searchParams.append(key, childSearchParams.toString()); } } else if (Array.isArray(value)) searchParams.append(key, `${value.join(",")}`); else searchParams.append(key, `${value}`); } const queryString = searchParams.toString(); if (queryString.length > 0 && withQuestionMark) return `?${queryString}`; return ""; } //#endregion //#region src/utils/resource-id.ts function extractResourceIDOfResponse(response) { if (response && response.headers && response.headers.has("location")) { const value = response.headers.get("location"); if (value) { const id = Number.parseInt(value.substring(value.lastIndexOf("/") + 1), 10); if (!Number.isNaN(id)) return id; } } } //#endregion //#region src/utils/resource-meta.ts function extractResourceMetaOfResponse(response) { const meta = {}; if (response.headers && response.headers.has("X-Total-Count")) { const total = Number.parseInt(response.headers.get("X-Total-Count") || "0", 10); if (!Number.isNaN(total)) meta.total = total; } return meta; } //#endregion //#region src/domains/base.ts var BaseAPI = class { client; constructor(context) { context = context || {}; this.setClient(context.client); } setClient(input) { if (isClient$1(input)) this.client = input; else this.client = createClient$1(input); } }; //#endregion //#region src/domains/project/module.ts var ProjectAPI = class extends BaseAPI { constructor(context) { super(context); } async create(data) { return { id: extractResourceIDOfResponse(await this.client.post("projects", data)) }; } async delete(id, isProjectName = false) { const headers = {}; if (isProjectName) headers["X-Is-Resource-Name"] = true; await this.client.delete(`projects/${id}`, { headers }); } async update(id, data, isProjectName = false) { const headers = {}; if (isProjectName) headers["X-Is-Resource-Name"] = true; await this.client.put(`projects/${id}`, data, { headers }); } async getMany(options) { options = options || {}; const response = await this.client.get(`projects${buildQueryString(options.query)}`); return { data: response.data, meta: extractResourceMetaOfResponse(response) }; } async getAll(options) { options = options || {}; options.query = options.query || {}; if (!options.query.page_size) options.query.page_size = 50; if (!options.query.page) options.query.page = 1; const response = await this.getMany(options); if (response.data.length === options.query.page_size) { options.query.page++; const next = await this.getAll(options); response.data.push(...next.data); } return response; } async getOne(id, isProjectName = false) { const headers = {}; if (isProjectName) headers["X-Is-Resource-Name"] = true; const { data } = await this.client.get(`projects/${id}`, { headers }); return data; } }; //#endregion //#region src/domains/project-repository/utils.ts function parseLongProjectRepositoryName(name) { const index = name.indexOf("/"); if (index === -1) throw new Error("The project repository name could not parsed."); const projectName = name.substring(0, index); let repositoryName = name.substring(projectName.length + 1); let artifactDigest; if (repositoryName.includes("@")) { const index = repositoryName.indexOf("@"); artifactDigest = repositoryName.substring(index + 1); repositoryName = repositoryName.substring(0, index); } let artifactTag; if (repositoryName.includes(":")) { const index = repositoryName.indexOf(":"); artifactTag = repositoryName.substring(index + 1); repositoryName = repositoryName.substring(0, index); } return { projectName, repositoryName, ...artifactDigest ? { artifactDigest } : {}, ...artifactTag ? { artifactTag } : {} }; } function buildProjectRepositoryLongName(representation) { const str = `${representation.projectName}/${representation.repositoryName}`; if (representation.artifactTag) return `${str}:${representation.artifactTag}`; if (representation.artifactDigest) return `${str}@${representation.artifactDigest}`; return str; } //#endregion //#region src/domains/project-repository/module.ts var ProjectRepositoryAPI = class extends BaseAPI { constructor(context) { super(context); } async findOne(input) { let context; if (typeof input === "string") context = parseLongProjectRepositoryName(input); else context = input; const { data } = await this.getMany({ projectName: context.projectName, query: { q: { name: `~${context.repositoryName}` }, page_size: 1 } }); return data.shift(); } async getOne(input) { let context; if (typeof input === "string") context = parseLongProjectRepositoryName(input); else context = input; const { data } = await this.client.get(`projects/${context.projectName}/repositories/${context.repositoryName}`); const parsed = parseLongProjectRepositoryName(data.name); return { ...data, name_short: parsed.repositoryName, project_name: parsed.projectName }; } async getMany(context) { const result = await this.client.get(`projects/${context.projectName}/repositories${buildQueryString(context.query)}`); return { data: result.data.map((item) => { const parsed = parseLongProjectRepositoryName(item.name); return { ...item, name_short: parsed.repositoryName, project_name: parsed.projectName }; }), meta: extractResourceMetaOfResponse(result) }; } async getAll(context) { context.query = context.query || {}; if (!context.query.page_size) context.query.page_size = 50; if (!context.query.page) context.query.page = 1; const response = await this.getMany(context); if (response.data.length === context.query.page_size) { context.query.page++; const next = await this.getAll(context); response.data.push(...next.data); } return response; } async update(context) { await this.client.put(`projects/${context.projectName}/repositories/${context.repositoryName}`, context.data); } async delete(input) { let context; if (typeof input === "string") context = parseLongProjectRepositoryName(input); else context = input; await this.client.delete(`projects/${context.projectName}/repositories/${context.repositoryName}`); } }; //#endregion //#region src/domains/project-repository-artifact/module.ts var ProjectRepositoryArtifactAPI = class extends BaseAPI { constructor(context) { super(context); } async getMany(context) { const { data } = await this.client.get(`projects/${context.projectName}/repositories/${context.repositoryName}/artifacts${buildQueryString(context.query)}`); return data; } async copy(destination, source) { let from; if (typeof source === "string") from = source; else { if (!source.artifactTag && !source.artifactDigest) source.artifactTag = "latest"; from = buildProjectRepositoryLongName(source); } await this.client.post(`projects/${destination.projectName}/repositories/${destination.repositoryName}/artifacts?from=${from}`); } async delete(context) { await this.client.delete(`projects/${context.projectName}/repositories/${context.repositoryName}/artifacts/${context.tagOrDigest || "latest"}`); } }; //#endregion //#region src/domains/project-repository-artifact-label/module.ts var ProjectRepositoryArtifactLabelAPI = class extends BaseAPI { constructor(context) { super(context); } async create(options) { await this.client.post(`projects/${options.projectName}/repositories/${options.repositoryName}/artifacts/${options.tagOrDigest || "latest"}/labels`, { id: options.labelId }); } async delete(options) { await this.client.delete(`projects/${options.projectName}/repositories/${options.repositoryName}/artifacts/${options.tagOrDigest || "latest"}/labels/${options.labelId}`); } }; //#endregion //#region src/domains/project-webhook-policy/module.ts var ProjectWebhookPolicyAPI = class extends BaseAPI { constructor(context) { super(context); } async create(context) { const headers = {}; if (context.isProjectName) headers["X-Is-Resource-Name"] = true; return { id: extractResourceIDOfResponse(await this.client.post(`projects/${context.projectIdOrName}/webhook/policies`, this.extendPayload(context.data), { headers })) }; } async getMany(context) { const headers = {}; if (context.isProjectName) headers["X-Is-Resource-Name"] = true; const response = await this.client.get(`projects/${context.projectIdOrName}/webhook/policies${buildQueryString(context.query)}`, { headers }); return { data: response.data, meta: extractResourceMetaOfResponse(response) }; } async getOne(context) { const headers = {}; if (context.isProjectName) headers["X-Is-Resource-Name"] = true; return (await this.client.get(`projects/${context.projectIdOrName}/webhook/policies/${context.id}`, { headers })).data; } async findOne(context) { return (await this.getMany({ projectIdOrName: context.projectIdOrName, isProjectName: context.isProjectName, query: { q: { name: context.name }, page_size: 1 } })).data.pop(); } async update(context) { const headers = {}; if (context.isProjectName) headers["X-Is-Resource-Name"] = true; await this.client.put(`projects/${context.projectIdOrName}/webhook/policies/${context.id}`, this.extendPayload({ ...context.data, id: context.id }), { headers }); } async deleteByName(context) { const webhook = await this.findOne(context); if (webhook) await this.delete({ isProjectName: false, projectIdOrName: webhook.project_id, id: webhook.id }); } async delete(context) { const headers = {}; if (context.isProjectName) headers["X-Is-Resource-Name"] = true; await this.client.delete(`projects/${context.projectIdOrName}/webhook/policies/${context.id}`, { headers }); } extendPayload(data) { data.name = data.name || (Math.random() + 1).toString(36).substring(7); if (typeof data.enabled === "undefined") data.enabled = true; if (typeof data.targets === "undefined") data.targets = []; if (typeof data.event_types === "undefined") data.event_types = ["PUSH_ARTIFACT"]; else data.event_types = merge(["PUSH_ARTIFACT"], data.event_types); return data; } }; //#endregion //#region src/domains/robot/constants.ts let RobotPermissionResource = /* @__PURE__ */ function(RobotPermissionResource) { RobotPermissionResource["ARTIFACT"] = "artifact"; RobotPermissionResource["ARTIFACT_LABEL"] = "artifact-label"; RobotPermissionResource["IMMUTABLE_TAG"] = "immutable-tag"; RobotPermissionResource["LABEL"] = "label"; RobotPermissionResource["LOG"] = "log"; RobotPermissionResource["METADATA"] = "metadata"; RobotPermissionResource["NOTIFICATION_POLICY"] = "notification-policy"; RobotPermissionResource["PREHEAT_POLICY"] = "preheat-policy"; RobotPermissionResource["PROJECT"] = "project"; RobotPermissionResource["REPOSITORY"] = "repository"; RobotPermissionResource["SCAN"] = "scan"; RobotPermissionResource["TAG"] = "tag"; return RobotPermissionResource; }({}); let RobotPermissionAction = /* @__PURE__ */ function(RobotPermissionAction) { RobotPermissionAction["CREATE"] = "create"; RobotPermissionAction["DELETE"] = "delete"; RobotPermissionAction["READ"] = "read"; RobotPermissionAction["UPDATE"] = "update"; RobotPermissionAction["PULL"] = "pull"; RobotPermissionAction["PUSH"] = "push"; RobotPermissionAction["LIST"] = "list"; RobotPermissionAction["STOP"] = "stop"; return RobotPermissionAction; }({}); //#endregion //#region src/domains/robot/module.ts var RobotAPI = class extends BaseAPI { constructor(context) { super(context); } async create(data) { return merge((await this.client.post("robots", this.extendPayload(data))).data, data); } async getMany(context) { const response = await this.client.get(`robots${buildQueryString(context.query)}`); return { data: response.data, meta: extractResourceMetaOfResponse(response) }; } async getOne(id) { return (await this.client.get(`robots/${id}`)).data; } /** * Update harbor project robot account. * If no "secret" provided, a new secret is generated. * * @param id * @param secret */ async updateSecret(id, secret) { const payload = { ...secret ? { secret } : {} }; const { data } = await this.client.patch(`robots/${id}`, payload); if (typeof payload.secret !== "undefined") data.secret = payload.secret; return data; } async update(id, data) { await this.client.put(`robots/${id}`, this.extendPayload({ ...data, id })); } async delete(id) { await this.client.delete(`robots/${id}`); } extendPayload(data) { return merge(data || {}, { description: "", duration: -1, level: "system", editable: true, disable: false, permissions: [] }); } }; //#endregion //#region src/domains/robot/utils.ts /** * Create robot permission to access all resources. * * @param namespace (e.g. * or project name) */ function buildRobotPermissionForAllResources(namespace) { return { access: [ { resource: "artifact", action: "create" }, { resource: "artifact", action: "delete" }, { resource: "artifact", action: "list" }, { resource: "artifact", action: "read" }, { resource: "artifact-label", action: "create" }, { resource: "artifact-label", action: "delete" }, { resource: "immutable-tag", action: "create" }, { resource: "immutable-tag", action: "delete" }, { resource: "immutable-tag", action: "list" }, { resource: "immutable-tag", action: "update" }, { resource: "repository", action: "list" }, { resource: "repository", action: "pull" }, { resource: "repository", action: "push" }, { resource: "repository", action: "update" }, { resource: "repository", action: "delete" }, { resource: "tag", action: "create" }, { resource: "tag", action: "delete" }, { resource: "tag", action: "list" }, { resource: "scan", action: "read" }, { resource: "scan", action: "stop" }, { resource: "scan", action: "create" }, { resource: "label", action: "create" }, { resource: "label", action: "delete" }, { resource: "label", action: "list" }, { resource: "label", action: "read" }, { resource: "label", action: "update" }, { resource: "metadata", action: "create" }, { resource: "metadata", action: "delete" }, { resource: "metadata", action: "list" }, { resource: "metadata", action: "read" }, { resource: "metadata", action: "update" }, { resource: "log", action: "list" }, { resource: "notification-policy", action: "create" }, { resource: "notification-policy", action: "delete" }, { resource: "notification-policy", action: "list" }, { resource: "notification-policy", action: "read" }, { resource: "notification-policy", action: "update" }, { resource: "preheat-policy", action: "create" }, { resource: "preheat-policy", action: "delete" }, { resource: "preheat-policy", action: "list" }, { resource: "preheat-policy", action: "read" }, { resource: "preheat-policy", action: "update" }, { resource: "project", action: "delete" }, { resource: "project", action: "read" }, { resource: "project", action: "update" } ], kind: "project", namespace }; } //#endregion //#region src/module.ts const HARBOR_CLIENT_INSTANCE = Symbol.for("@hapic/harbor/HarborClient"); var HarborClient = class extends Client { project; projectRepositoryArtifact; projectRepositoryArtifactLabel; projectRepository; projectWebhookPolicy; robot; constructor(input = {}) { super(input.request); markInstanceof(this, HARBOR_CLIENT_INSTANCE); this.project = new ProjectAPI({ client: this }); this.projectRepository = new ProjectRepositoryAPI({ client: this }); this.projectRepositoryArtifact = new ProjectRepositoryArtifactAPI({ client: this }); this.projectRepositoryArtifactLabel = new ProjectRepositoryArtifactLabelAPI({ client: this }); this.projectWebhookPolicy = new ProjectWebhookPolicyAPI({ client: this }); this.robot = new RobotAPI({ client: this }); this.applyConfig(input); } applyConfig(input = {}) { const connectionOptions = resolveConnectionOptions(input, parseConnectionString); if (connectionOptions) { this.setBaseURL(connectionOptions.host); this.setAuthorizationHeader({ type: "Basic", username: connectionOptions.user, password: connectionOptions.password }); } } async search(q) { const { data } = await this.get(`search?q=${q}`); return data; } }; //#endregion //#region src/instance.ts const { hasClient, setClient, useClient, unsetClient, createClient, isClient } = createClientRegistry({ create: (input) => new HarborClient(input), id: HARBOR_CLIENT_INSTANCE }); //#endregion //#region src/index.ts const client = createClient(); //#endregion export { CONNECTION_STRING_PARSE_ERROR_INSTANCE, ConnectionStringParseError, HARBOR_CLIENT_INSTANCE, HarborClient, HeaderName, ProjectAPI, ProjectRepositoryAPI, ProjectRepositoryArtifactAPI, ProjectRepositoryArtifactLabelAPI, ProjectWebhookPolicyAPI, RobotAPI, RobotPermissionAction, RobotPermissionResource, buildProjectRepositoryLongName, buildQueryString, buildRobotPermissionForAllResources, createClient, client as default, extractResourceIDOfResponse, extractResourceMetaOfResponse, hasClient, isClient, isClientError, isClientErrorDueNetworkIssue, isClientErrorWithStatusCode, isConnectionStringParseError, isHapicError, isHttpResponseError, isNetworkError, parseConnectionString, parseLongProjectRepositoryName, setClient, unsetClient, useClient }; //# sourceMappingURL=index.mjs.map