@hapic/harbor
Version:
A harbor http api client.
893 lines (870 loc) • 30.4 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var hapic = require('hapic');
var smob = require('smob');
/*
* Copyright (c) 2023.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/ var HeaderName = /*#__PURE__*/ function(HeaderName) {
HeaderName["LOCATION"] = "location";
HeaderName["IS_RESOURCE_NAME"] = "X-Is-Resource-Name";
HeaderName["TOTAL_COUNT"] = "X-Total-Count";
return HeaderName;
}({});
/*
* Copyright (c) 2021-2021.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/ class ConnectionStringParseError extends Error {
static connectionStringMissing(serviceName) {
const parts = [
'The'
];
if (typeof serviceName === 'string') {
parts.push(serviceName);
}
parts.push('connection string is not specified.');
return new this(parts.join(' '));
}
static connectionStringInvalid(serviceName) {
const parts = [
'The'
];
if (typeof serviceName === 'string') {
parts.push(serviceName);
}
parts.push('connection string is not valid.');
return new this(parts.join(' '));
}
}
function parseConnectionString(connectionString) {
const parts = connectionString.split('@');
if (parts.length !== 2) {
throw new ConnectionStringParseError('Harbor connection string must be in the following format: user:password@host');
}
const host = parts[1];
const authParts = parts[0].split(':');
if (authParts.length !== 2) {
throw new ConnectionStringParseError('Harbor connection string must be in the following format: user:password@host');
}
return {
host,
user: authParts[0],
password: authParts[1]
};
}
function buildQueryString(input, withQuestionMark = true) {
if (typeof input === 'undefined') {
return '';
}
const searchParams = new URLSearchParams();
const keys = Object.keys(input);
for(let i = 0; i < keys.length; i++){
const value = input[keys[i]];
if (smob.isObject(value)) {
const childKeys = Object.keys(value);
if (childKeys.length > 0) {
const childSearchParams = new URLSearchParams();
for(let j = 0; j < childKeys.length; j++){
childSearchParams.append(childKeys[j], `${encodeURIComponent(value[childKeys[j]])}`);
}
searchParams.append(keys[i], childSearchParams.toString());
}
} else if (Array.isArray(value)) {
searchParams.append(keys[i], `${value.join(',')}`);
} else {
searchParams.append(keys[i], `${value}`);
}
}
const queryString = searchParams.toString();
if (queryString.length > 0 && withQuestionMark) {
return `?${queryString}`;
}
return '';
}
function extractResourceIDOfResponse(response) {
if (response && response.headers && response.headers.has(HeaderName.LOCATION)) {
const value = response.headers.get(HeaderName.LOCATION);
if (value) {
const id = parseInt(value.substring(value.lastIndexOf('/') + 1), 10);
if (!Number.isNaN(id)) {
return id;
}
}
}
return undefined;
}
function extractResourceMetaOfResponse(response) {
const meta = {};
if (response.headers && response.headers.has(HeaderName.TOTAL_COUNT)) {
const total = parseInt(response.headers.get(HeaderName.TOTAL_COUNT) || '0', 10);
if (!Number.isNaN(total)) {
meta.total = total;
}
}
return meta;
}
class BaseAPI {
// -----------------------------------------------------------------------------------
setClient(input) {
if (hapic.isClient(input)) {
this.client = input;
} else {
this.client = hapic.createClient(input);
}
}
// -----------------------------------------------------------------------------------
constructor(context){
context = context || {};
this.setClient(context.client);
}
}
class ProjectAPI extends BaseAPI {
async create(data) {
const response = await this.client.post('projects', data);
return {
id: extractResourceIDOfResponse(response)
};
}
async delete(id, isProjectName = false) {
const headers = {};
if (isProjectName) {
headers[HeaderName.IS_RESOURCE_NAME] = true;
}
await this.client.delete(`projects/${id}`, headers);
}
async update(id, data, isProjectName = false) {
const headers = {};
if (isProjectName) {
headers[HeaderName.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[HeaderName.IS_RESOURCE_NAME] = true;
}
const { data } = await this.client.get(`projects/${id}`, headers);
return data;
}
// eslint-disable-next-line no-useless-constructor,@typescript-eslint/no-useless-constructor
constructor(context){
super(context);
}
}
/*
* Copyright (c) 2022.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/ 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.indexOf('@') !== -1) {
const index = repositoryName.indexOf('@');
artifactDigest = repositoryName.substring(index + 1);
repositoryName = repositoryName.substring(0, index);
}
let artifactTag;
if (repositoryName.indexOf(':') !== -1) {
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;
}
class ProjectRepositoryAPI extends BaseAPI {
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}`);
}
// eslint-disable-next-line no-useless-constructor,@typescript-eslint/no-useless-constructor
constructor(context){
super(context);
}
}
class ProjectRepositoryArtifactAPI extends BaseAPI {
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'}`);
}
// eslint-disable-next-line no-useless-constructor,@typescript-eslint/no-useless-constructor
constructor(context){
super(context);
}
}
class ProjectRepositoryArtifactLabelAPI extends BaseAPI {
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}`);
}
// eslint-disable-next-line no-useless-constructor,@typescript-eslint/no-useless-constructor
constructor(context){
super(context);
}
}
class ProjectWebhookPolicyAPI extends BaseAPI {
async create(context) {
const headers = {};
if (context.isProjectName) {
headers[HeaderName.IS_RESOURCE_NAME] = true;
}
const response = await this.client.post(`projects/${context.projectIdOrName}/webhook/policies`, this.extendPayload(context.data), headers);
return {
id: extractResourceIDOfResponse(response)
};
}
async getMany(context) {
const headers = {};
if (context.isProjectName) {
headers[HeaderName.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[HeaderName.IS_RESOURCE_NAME] = true;
}
const response = await this.client.get(`projects/${context.projectIdOrName}/webhook/policies/${context.id}`, headers);
return response.data;
}
async findOne(context) {
const response = await this.getMany({
projectIdOrName: context.projectIdOrName,
isProjectName: context.isProjectName,
query: {
q: {
name: context.name
},
page_size: 1
}
});
return response.data.pop();
}
async update(context) {
const headers = {};
if (context.isProjectName) {
headers[HeaderName.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[HeaderName.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 = smob.merge([
'PUSH_ARTIFACT'
], data.event_types);
}
return data;
}
// eslint-disable-next-line no-useless-constructor,@typescript-eslint/no-useless-constructor
constructor(context){
super(context);
}
}
/*
* Copyright (c) 2024.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/ var 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;
}({});
var 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;
}({});
class RobotAPI extends BaseAPI {
async create(data) {
const response = await this.client.post('robots', this.extendPayload(data));
return smob.merge(response.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) {
const response = await this.client.get(`robots/${id}`);
return response.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 smob.merge(data || {}, {
description: '',
duration: -1,
level: 'system',
editable: true,
disable: false,
permissions: []
});
}
// eslint-disable-next-line no-useless-constructor,@typescript-eslint/no-useless-constructor
constructor(context){
super(context);
}
}
/**
* Create robot permission to access all resources.
*
* @param namespace (e.g. * or project name)
*/ function buildRobotPermissionForAllResources(namespace) {
return {
access: [
{
resource: RobotPermissionResource.ARTIFACT,
action: RobotPermissionAction.CREATE
},
{
resource: RobotPermissionResource.ARTIFACT,
action: RobotPermissionAction.DELETE
},
{
resource: RobotPermissionResource.ARTIFACT,
action: RobotPermissionAction.LIST
},
{
resource: RobotPermissionResource.ARTIFACT,
action: RobotPermissionAction.READ
},
{
resource: RobotPermissionResource.ARTIFACT_LABEL,
action: RobotPermissionAction.CREATE
},
{
resource: RobotPermissionResource.ARTIFACT_LABEL,
action: RobotPermissionAction.DELETE
},
{
resource: RobotPermissionResource.IMMUTABLE_TAG,
action: RobotPermissionAction.CREATE
},
{
resource: RobotPermissionResource.IMMUTABLE_TAG,
action: RobotPermissionAction.DELETE
},
{
resource: RobotPermissionResource.IMMUTABLE_TAG,
action: RobotPermissionAction.LIST
},
{
resource: RobotPermissionResource.IMMUTABLE_TAG,
action: RobotPermissionAction.UPDATE
},
{
resource: RobotPermissionResource.REPOSITORY,
action: RobotPermissionAction.LIST
},
{
resource: RobotPermissionResource.REPOSITORY,
action: RobotPermissionAction.PULL
},
{
resource: RobotPermissionResource.REPOSITORY,
action: RobotPermissionAction.PUSH
},
{
resource: RobotPermissionResource.REPOSITORY,
action: RobotPermissionAction.UPDATE
},
{
resource: RobotPermissionResource.REPOSITORY,
action: RobotPermissionAction.DELETE
},
{
resource: RobotPermissionResource.TAG,
action: RobotPermissionAction.CREATE
},
{
resource: RobotPermissionResource.TAG,
action: RobotPermissionAction.DELETE
},
{
resource: RobotPermissionResource.TAG,
action: RobotPermissionAction.LIST
},
{
resource: RobotPermissionResource.SCAN,
action: RobotPermissionAction.READ
},
{
resource: RobotPermissionResource.SCAN,
action: RobotPermissionAction.STOP
},
{
resource: RobotPermissionResource.SCAN,
action: RobotPermissionAction.CREATE
},
{
resource: RobotPermissionResource.LABEL,
action: RobotPermissionAction.CREATE
},
{
resource: RobotPermissionResource.LABEL,
action: RobotPermissionAction.DELETE
},
{
resource: RobotPermissionResource.LABEL,
action: RobotPermissionAction.LIST
},
{
resource: RobotPermissionResource.LABEL,
action: RobotPermissionAction.READ
},
{
resource: RobotPermissionResource.LABEL,
action: RobotPermissionAction.UPDATE
},
{
resource: RobotPermissionResource.METADATA,
action: RobotPermissionAction.CREATE
},
{
resource: RobotPermissionResource.METADATA,
action: RobotPermissionAction.DELETE
},
{
resource: RobotPermissionResource.METADATA,
action: RobotPermissionAction.LIST
},
{
resource: RobotPermissionResource.METADATA,
action: RobotPermissionAction.READ
},
{
resource: RobotPermissionResource.METADATA,
action: RobotPermissionAction.UPDATE
},
{
resource: RobotPermissionResource.LOG,
action: RobotPermissionAction.LIST
},
{
resource: RobotPermissionResource.NOTIFICATION_POLICY,
action: RobotPermissionAction.CREATE
},
{
resource: RobotPermissionResource.NOTIFICATION_POLICY,
action: RobotPermissionAction.DELETE
},
{
resource: RobotPermissionResource.NOTIFICATION_POLICY,
action: RobotPermissionAction.LIST
},
{
resource: RobotPermissionResource.NOTIFICATION_POLICY,
action: RobotPermissionAction.READ
},
{
resource: RobotPermissionResource.NOTIFICATION_POLICY,
action: RobotPermissionAction.UPDATE
},
{
resource: RobotPermissionResource.PREHEAT_POLICY,
action: RobotPermissionAction.CREATE
},
{
resource: RobotPermissionResource.PREHEAT_POLICY,
action: RobotPermissionAction.DELETE
},
{
resource: RobotPermissionResource.PREHEAT_POLICY,
action: RobotPermissionAction.LIST
},
{
resource: RobotPermissionResource.PREHEAT_POLICY,
action: RobotPermissionAction.READ
},
{
resource: RobotPermissionResource.PREHEAT_POLICY,
action: RobotPermissionAction.UPDATE
},
{
resource: RobotPermissionResource.PROJECT,
action: RobotPermissionAction.DELETE
},
{
resource: RobotPermissionResource.PROJECT,
action: RobotPermissionAction.READ
},
{
resource: RobotPermissionResource.PROJECT,
action: RobotPermissionAction.UPDATE
}
],
kind: 'project',
namespace
};
}
class HarborClient extends hapic.Client {
// -----------------------------------------------------------------------------------
applyConfig(input) {
input = input || {};
let connectionOptions;
if (input.connectionString) {
connectionOptions = parseConnectionString(input.connectionString);
}
if (input.connectionOptions) {
connectionOptions = input.connectionOptions;
}
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;
}
// -----------------------------------------------------------------------------------
constructor(input){
input = input || {};
super(input.request);
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);
}
}
const instances = {};
/**
* Verify if a harbor client singleton instance exists.
*
* @param key
*/ function hasClient(key) {
return hapic.hasOwnProperty(instances, key || 'default');
}
/**
* Set the harbor client singleton instance.
*
* @param client
* @param key
*/ function setClient(client, key) {
key = key || 'default';
instances[key] = client;
return client;
}
/**
* Receive a harbor singleton instance.
*
* @param key
*/ function useClient(key) {
key = key || 'default';
if (Object.prototype.hasOwnProperty.call(instances, key)) {
return instances[key];
}
const instance = createClient();
instances[key] = instance;
return instance;
}
/**
* Unset a harbor client singleton instance.
*
* @param key
*/ function unsetClient(key) {
key = key || 'default';
if (hapic.hasOwnProperty(instances, key)) {
delete instances[key];
}
}
/**
* Create a harbor client.
*
* @param input
*/ function createClient(input) {
return new HarborClient(input);
}
/**
* Check if the argument is of instance Client.
*
* @param input
*/ function isClient(input) {
if (input instanceof HarborClient) {
return true;
}
return hapic.verifyInstanceBySymbol(input, 'HarborClient');
}
const client = createClient();
Object.defineProperty(exports, "isClientError", {
enumerable: true,
get: function () { return hapic.isClientError; }
});
Object.defineProperty(exports, "isClientErrorDueNetworkIssue", {
enumerable: true,
get: function () { return hapic.isClientErrorDueNetworkIssue; }
});
Object.defineProperty(exports, "isClientErrorWithStatusCode", {
enumerable: true,
get: function () { return hapic.isClientErrorWithStatusCode; }
});
exports.HarborClient = HarborClient;
exports.HeaderName = HeaderName;
exports.ProjectAPI = ProjectAPI;
exports.ProjectRepositoryAPI = ProjectRepositoryAPI;
exports.ProjectRepositoryArtifactAPI = ProjectRepositoryArtifactAPI;
exports.ProjectRepositoryArtifactLabelAPI = ProjectRepositoryArtifactLabelAPI;
exports.ProjectWebhookPolicyAPI = ProjectWebhookPolicyAPI;
exports.RobotAPI = RobotAPI;
exports.RobotPermissionAction = RobotPermissionAction;
exports.RobotPermissionResource = RobotPermissionResource;
exports.buildProjectRepositoryLongName = buildProjectRepositoryLongName;
exports.buildQueryString = buildQueryString;
exports.buildRobotPermissionForAllResources = buildRobotPermissionForAllResources;
exports.createClient = createClient;
exports.default = client;
exports.extractResourceIDOfResponse = extractResourceIDOfResponse;
exports.extractResourceMetaOfResponse = extractResourceMetaOfResponse;
exports.hasClient = hasClient;
exports.isClient = isClient;
exports.parseConnectionString = parseConnectionString;
exports.parseLongProjectRepositoryName = parseLongProjectRepositoryName;
exports.setClient = setClient;
exports.unsetClient = unsetClient;
exports.useClient = useClient;
module.exports = Object.assign(exports.default, exports);
//# sourceMappingURL=index.cjs.map