@hapic/harbor
Version:
A harbor http api client.
650 lines (649 loc) • 19.7 kB
JavaScript
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