mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 05:45:22 +02:00
feat(registry): gitlab support (#3107)
* feat(api): gitlab registry type * feat(registries): early support for gitlab registries * feat(app): registry service selector * feat(registry): gitlab support : list repositories and tags - remove features missing * feat(registry): gitlab registry remove features * feat(registry): gitlab switch to registry V2 API for repositories and tags * feat(api): use development extension binary * fix(registry): avoid 401 on gitlab retrieve to disconnect the user * feat(registry): gitlab browse projects without extension * style(app): code cleaning * refactor(app): PR review changes + refactor on types * fix(gitlab): remove gitlab info from registrymanagementconfig and force gitlab type * style(api): go fmt * feat(api): update APIVersion and ExtensionDefinitionsURL * fix(api): fix invalid RM extension URL * feat(registry): PAT scope help * feat(registry): defaults on registry creation * style(registry-creation): update layout and text for Gitlab registry * feat(registry-creation): update gitlab notice
This commit is contained in:
parent
03d9d6afbb
commit
198e92c734
38 changed files with 1022 additions and 160 deletions
|
@ -30,7 +30,7 @@
|
|||
<tr ng-hide="$ctrl.loading" dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
|
||||
ng-class="{active: item.Checked}">
|
||||
<td>
|
||||
<a ui-sref="portainer.registries.registry.repository({repository: item.Name})" class="monospaced"
|
||||
<a ui-sref="portainer.registries.registry.repository({repository: item.Name})"
|
||||
title="{{ item.Name }}">{{ item.Name }}</a>
|
||||
</td>
|
||||
<td>{{ item.TagsCount }}</td>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import _ from 'lodash-es';
|
||||
import { RepositoryTagViewModel } from '../models/repositoryTag';
|
||||
|
||||
angular.module('portainer.extensions.registrymanagement')
|
||||
|
@ -7,7 +8,7 @@ angular.module('portainer.extensions.registrymanagement')
|
|||
var helper = {};
|
||||
|
||||
function historyRawToParsed(rawHistory) {
|
||||
return angular.fromJson(rawHistory[0].v1Compatibility);
|
||||
return _.map(rawHistory, (item) => angular.fromJson(item.v1Compatibility));
|
||||
}
|
||||
|
||||
helper.manifestsToTag = function (manifests) {
|
||||
|
@ -16,7 +17,7 @@ angular.module('portainer.extensions.registrymanagement')
|
|||
|
||||
var history = historyRawToParsed(v1.history);
|
||||
var name = v1.tag;
|
||||
var os = history.os;
|
||||
var os = history[0].os;
|
||||
var arch = v1.architecture;
|
||||
var size = v2.layers.reduce(function (a, b) {
|
||||
return {
|
||||
|
@ -26,7 +27,7 @@ angular.module('portainer.extensions.registrymanagement')
|
|||
var imageId = v2.config.digest;
|
||||
var imageDigest = v2.digest;
|
||||
|
||||
return new RepositoryTagViewModel(name, os, arch, size, imageDigest, imageId, v2);
|
||||
return new RepositoryTagViewModel(name, os, arch, size, imageDigest, imageId, v2, history);
|
||||
};
|
||||
|
||||
return helper;
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
export function RegistryGitlabProject(project) {
|
||||
this.Id = project.id;
|
||||
this.Description = project.description;
|
||||
this.Name = project.name;
|
||||
this.Namespace = project.namespace ? project.namespace.name : '';
|
||||
this.PathWithNamespace = project.path_with_namespace;
|
||||
this.RegistryEnabled = project.container_registry_enabled;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import _ from 'lodash-es';
|
||||
export default function RegistryRepositoryViewModel(item) {
|
||||
export function RegistryRepositoryViewModel(item) {
|
||||
if (item.name && item.tags) {
|
||||
this.Name = item.name;
|
||||
this.TagsCount = _.without(item.tags, null).length;
|
||||
|
@ -7,4 +7,9 @@ export default function RegistryRepositoryViewModel(item) {
|
|||
this.Name = item;
|
||||
this.TagsCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function RegistryRepositoryGitlabViewModel(data) {
|
||||
this.Name = data.path;
|
||||
this.TagsCount = data.tags.length;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export const RegistryTypes = Object.freeze({
|
||||
'QUAY': 1,
|
||||
'AZURE': 2,
|
||||
'CUSTOM': 3,
|
||||
'GITLAB': 4
|
||||
})
|
|
@ -1,4 +1,4 @@
|
|||
export function RepositoryTagViewModel(name, os, arch, size, imageDigest, imageId, v2) {
|
||||
export function RepositoryTagViewModel(name, os, arch, size, imageDigest, imageId, v2, history) {
|
||||
this.Name = name;
|
||||
this.Os = os || '';
|
||||
this.Architecture = arch || '';
|
||||
|
@ -6,6 +6,7 @@ export function RepositoryTagViewModel(name, os, arch, size, imageDigest, imageI
|
|||
this.ImageDigest = imageDigest || '';
|
||||
this.ImageId = imageId || '';
|
||||
this.ManifestV2 = v2 || {};
|
||||
this.History = history || [];
|
||||
}
|
||||
|
||||
export function RepositoryShortTag(name, imageId, imageDigest, manifest) {
|
||||
|
@ -13,4 +14,9 @@ export function RepositoryShortTag(name, imageId, imageDigest, manifest) {
|
|||
this.ImageId = imageId;
|
||||
this.ImageDigest = imageDigest;
|
||||
this.ManifestV2 = manifest;
|
||||
}
|
||||
}
|
||||
|
||||
export function RepositoryAddTagPayload(tag, manifest) {
|
||||
this.Tag = tag;
|
||||
this.Manifest = manifest;
|
||||
}
|
||||
|
|
33
app/extensions/registry-management/rest/gitlab.js
Normal file
33
app/extensions/registry-management/rest/gitlab.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import gitlabResponseGetLink from './transform/gitlabResponseGetLink'
|
||||
|
||||
angular.module('portainer.extensions.registrymanagement')
|
||||
.factory('Gitlab', ['$resource', 'API_ENDPOINT_REGISTRIES',
|
||||
function GitlabFactory($resource, API_ENDPOINT_REGISTRIES) {
|
||||
'use strict';
|
||||
return function(env) {
|
||||
const headers = {};
|
||||
if (env) {
|
||||
headers['Private-Token'] = env.token;
|
||||
headers['X-Gitlab-Domain'] = env.url
|
||||
}
|
||||
|
||||
const baseUrl = API_ENDPOINT_REGISTRIES + '/:id/proxies/gitlab/api/v4/projects';
|
||||
|
||||
return $resource(baseUrl, {id:'@id'},
|
||||
{
|
||||
projects: {
|
||||
method: 'GET',
|
||||
params: { membership: 'true' },
|
||||
transformResponse: gitlabResponseGetLink,
|
||||
headers: headers
|
||||
},
|
||||
repositories :{
|
||||
method: 'GET',
|
||||
url: baseUrl + '/:projectId/registry/repositories',
|
||||
params: { tags: true },
|
||||
headers: headers,
|
||||
transformResponse: gitlabResponseGetLink
|
||||
}
|
||||
});
|
||||
};
|
||||
}]);
|
|
@ -0,0 +1,10 @@
|
|||
export default function gitlabResponseGetLink(data, headers) {
|
||||
let response = {};
|
||||
try {
|
||||
response.data = angular.fromJson(data);
|
||||
response.next = headers('X-Next-Page');
|
||||
} catch (error) {
|
||||
response = data;
|
||||
}
|
||||
return response;
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
import _ from 'lodash-es';
|
||||
import { RegistryGitlabProject } from '../models/gitlabRegistry';
|
||||
import { RegistryRepositoryGitlabViewModel } from '../models/registryRepository';
|
||||
|
||||
angular.module('portainer.extensions.registrymanagement')
|
||||
.factory('RegistryGitlabService', ['$async', 'Gitlab',
|
||||
function RegistryGitlabServiceFactory($async, Gitlab) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
/**
|
||||
* PROJECTS
|
||||
*/
|
||||
|
||||
async function _getProjectsPage(env, params, projects) {
|
||||
const response = await Gitlab(env).projects(params).$promise;
|
||||
projects = _.concat(projects, response.data);
|
||||
if (response.next) {
|
||||
params.page = response.next;
|
||||
projects = await _getProjectsPage(env, params, projects);
|
||||
}
|
||||
return projects;
|
||||
}
|
||||
|
||||
async function projectsAsync(url, token) {
|
||||
try {
|
||||
const data = await _getProjectsPage({url: url, token: token}, {page: 1}, []);
|
||||
return _.map(data, (project) => new RegistryGitlabProject(project));
|
||||
} catch (error) {
|
||||
throw {msg: 'Unable to retrieve projects', err: error};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* END PROJECTS
|
||||
*/
|
||||
|
||||
/**
|
||||
* REPOSITORIES
|
||||
*/
|
||||
|
||||
async function _getRepositoriesPage(params, repositories) {
|
||||
const response = await Gitlab().repositories(params).$promise;
|
||||
repositories = _.concat(repositories, response.data);
|
||||
if (response.next) {
|
||||
params.page = response.next;
|
||||
repositories = await _getRepositoriesPage(params, repositories);
|
||||
}
|
||||
return repositories;
|
||||
}
|
||||
|
||||
async function repositoriesAsync(registry) {
|
||||
try {
|
||||
const params = {
|
||||
id: registry.Id,
|
||||
projectId: registry.Gitlab.ProjectId,
|
||||
page: 1
|
||||
};
|
||||
const data = await _getRepositoriesPage(params, []);
|
||||
return _.map(data, (r) => new RegistryRepositoryGitlabViewModel(r));
|
||||
} catch (error) {
|
||||
throw {msg: 'Unable to retrieve repositories', err: error};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* END REPOSITORIES
|
||||
*/
|
||||
|
||||
/**
|
||||
* SERVICE FUNCTIONS DECLARATION
|
||||
*/
|
||||
|
||||
function projects(url, token) {
|
||||
return $async(projectsAsync, url, token);
|
||||
}
|
||||
|
||||
function repositories(registry) {
|
||||
return $async(repositoriesAsync, registry);
|
||||
}
|
||||
|
||||
service.projects = projects;
|
||||
service.repositories = repositories;
|
||||
return service;
|
||||
}
|
||||
]);
|
|
@ -0,0 +1,82 @@
|
|||
import { RegistryTypes } from 'Extensions/registry-management/models/registryTypes';
|
||||
|
||||
angular.module('portainer.extensions.registrymanagement')
|
||||
.factory('RegistryServiceSelector', ['$q', 'RegistryV2Service', 'RegistryGitlabService',
|
||||
function RegistryServiceSelector($q, RegistryV2Service, RegistryGitlabService) {
|
||||
'use strict';
|
||||
const service = {};
|
||||
|
||||
service.ping = ping;
|
||||
service.repositories = repositories;
|
||||
service.getRepositoriesDetails = getRepositoriesDetails;
|
||||
service.tags = tags;
|
||||
service.getTagsDetails = getTagsDetails;
|
||||
service.tag = tag;
|
||||
service.addTag = addTag;
|
||||
service.deleteManifest = deleteManifest;
|
||||
|
||||
service.shortTagsWithProgress = shortTagsWithProgress;
|
||||
service.deleteTagsWithProgress = deleteTagsWithProgress;
|
||||
service.retagWithProgress = retagWithProgress;
|
||||
|
||||
function ping(registry, forceNewConfig) {
|
||||
let service = RegistryV2Service;
|
||||
return service.ping(registry, forceNewConfig)
|
||||
}
|
||||
|
||||
function repositories(registry) {
|
||||
let service = RegistryV2Service;
|
||||
if (registry.Type === RegistryTypes.GITLAB) {
|
||||
service = RegistryGitlabService;
|
||||
}
|
||||
return service.repositories(registry);
|
||||
}
|
||||
|
||||
function getRepositoriesDetails(registry, repositories) {
|
||||
let service = RegistryV2Service;
|
||||
return service.getRepositoriesDetails(registry, repositories);
|
||||
}
|
||||
|
||||
function tags(registry, repository) {
|
||||
let service = RegistryV2Service;
|
||||
return service.tags(registry, repository);
|
||||
}
|
||||
|
||||
function getTagsDetails(registry, repository, tags) {
|
||||
let service = RegistryV2Service;
|
||||
return service.getTagsDetails(registry, repository, tags);
|
||||
}
|
||||
|
||||
function tag(registry, repository, tag) {
|
||||
let service = RegistryV2Service;
|
||||
return service.tag(registry, repository, tag);
|
||||
}
|
||||
|
||||
function addTag(registry, repository, tag, manifest) {
|
||||
let service = RegistryV2Service;
|
||||
return service.addTag(registry, repository, tag, manifest);
|
||||
}
|
||||
|
||||
function deleteManifest(registry, repository, digest) {
|
||||
let service = RegistryV2Service;
|
||||
return service.deleteManifest(registry, repository, digest);
|
||||
}
|
||||
|
||||
function shortTagsWithProgress(registry, repository, tagsList) {
|
||||
let service = RegistryV2Service;
|
||||
return service.shortTagsWithProgress(registry, repository, tagsList);
|
||||
}
|
||||
|
||||
function deleteTagsWithProgress(registry, repository, modifiedDigests, impactedTags) {
|
||||
let service = RegistryV2Service;
|
||||
return service.deleteTagsWithProgress(registry, repository, modifiedDigests, impactedTags);
|
||||
}
|
||||
|
||||
function retagWithProgress(registry, repository, modifiedTags, modifiedDigests, impactedTags) {
|
||||
let service = RegistryV2Service;
|
||||
return service.retagWithProgress(registry, repository, modifiedTags, modifiedDigests, impactedTags);
|
||||
}
|
||||
|
||||
return service;
|
||||
}
|
||||
]);
|
|
@ -1,6 +1,7 @@
|
|||
import _ from 'lodash-es';
|
||||
import { RepositoryShortTag } from '../models/repositoryTag';
|
||||
import RegistryRepositoryViewModel from '../models/registryRepository';
|
||||
import { RepositoryAddTagPayload } from '../models/repositoryTag'
|
||||
import { RegistryRepositoryViewModel } from '../models/registryRepository';
|
||||
import genericAsyncGenerator from './genericAsyncGenerator';
|
||||
|
||||
angular.module('portainer.extensions.registrymanagement')
|
||||
|
@ -9,12 +10,24 @@ function RegistryV2ServiceFactory($q, $async, RegistryCatalog, RegistryTags, Reg
|
|||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.ping = function(id, forceNewConfig) {
|
||||
/**
|
||||
* PING
|
||||
*/
|
||||
function ping(registry, forceNewConfig) {
|
||||
const id = registry.Id;
|
||||
if (forceNewConfig) {
|
||||
return RegistryCatalog.pingWithForceNew({ id: id }).$promise;
|
||||
}
|
||||
return RegistryCatalog.ping({ id: id }).$promise;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* END PING
|
||||
*/
|
||||
|
||||
/**
|
||||
* REPOSITORIES
|
||||
*/
|
||||
|
||||
function _getCatalogPage(params, deferred, repositories) {
|
||||
RegistryCatalog.get(params).$promise.then(function(data) {
|
||||
|
@ -27,7 +40,7 @@ function RegistryV2ServiceFactory($q, $async, RegistryCatalog, RegistryTags, Reg
|
|||
});
|
||||
}
|
||||
|
||||
function getCatalog(id) {
|
||||
function _getCatalog(id) {
|
||||
var deferred = $q.defer();
|
||||
var repositories = [];
|
||||
|
||||
|
@ -35,13 +48,12 @@ function RegistryV2ServiceFactory($q, $async, RegistryCatalog, RegistryTags, Reg
|
|||
return deferred.promise;
|
||||
}
|
||||
|
||||
service.catalog = function (id) {
|
||||
var deferred = $q.defer();
|
||||
function repositories(registry) {
|
||||
const deferred = $q.defer();
|
||||
const id = registry.Id;
|
||||
|
||||
getCatalog(id).then(function success(data) {
|
||||
var repositories = data.map(function (repositoryName) {
|
||||
return new RegistryRepositoryViewModel(repositoryName);
|
||||
});
|
||||
_getCatalog(id).then(function success(data) {
|
||||
const repositories = _.map(data, (repositoryName) => new RegistryRepositoryViewModel(repositoryName));
|
||||
deferred.resolve(repositories);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
|
@ -52,14 +64,37 @@ function RegistryV2ServiceFactory($q, $async, RegistryCatalog, RegistryTags, Reg
|
|||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
}
|
||||
|
||||
service.tags = function (id, repository) {
|
||||
var deferred = $q.defer();
|
||||
function getRepositoriesDetails(registry, repositories) {
|
||||
const deferred = $q.defer();
|
||||
const promises = _.map(repositories, (repository) => tags(registry, repository.Name));
|
||||
|
||||
$q.all(promises)
|
||||
.then(function success(data) {
|
||||
var repositories = data.map(function (item) {
|
||||
return new RegistryRepositoryViewModel(item);
|
||||
});
|
||||
repositories = _.without(repositories, undefined);
|
||||
deferred.resolve(repositories);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({
|
||||
msg: 'Unable to retrieve repositories',
|
||||
err: err
|
||||
});
|
||||
});
|
||||
|
||||
_getTagsPage({id: id, repository: repository}, deferred, {tags:[]});
|
||||
return deferred.promise;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* END REPOSITORIES
|
||||
*/
|
||||
|
||||
/**
|
||||
* TAGS
|
||||
*/
|
||||
|
||||
function _getTagsPage(params, deferred, previousTags) {
|
||||
RegistryTags.get(params).$promise.then(function(data) {
|
||||
|
@ -78,45 +113,23 @@ function RegistryV2ServiceFactory($q, $async, RegistryCatalog, RegistryTags, Reg
|
|||
});
|
||||
}
|
||||
|
||||
service.getRepositoriesDetails = function (id, repositories) {
|
||||
var deferred = $q.defer();
|
||||
var promises = [];
|
||||
for (var i = 0; i < repositories.length; i++) {
|
||||
var repository = repositories[i].Name;
|
||||
promises.push(service.tags(id, repository));
|
||||
}
|
||||
|
||||
$q.all(promises)
|
||||
.then(function success(data) {
|
||||
var repositories = data.map(function (item) {
|
||||
return new RegistryRepositoryViewModel(item);
|
||||
});
|
||||
repositories = _.without(repositories, undefined);
|
||||
deferred.resolve(repositories);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({
|
||||
msg: 'Unable to retrieve repositories',
|
||||
err: err
|
||||
});
|
||||
});
|
||||
function tags(registry, repository) {
|
||||
const deferred = $q.defer();
|
||||
const id = registry.Id;
|
||||
|
||||
_getTagsPage({id: id, repository: repository}, deferred, {tags:[]});
|
||||
return deferred.promise;
|
||||
};
|
||||
}
|
||||
|
||||
service.getTagsDetails = function (id, repository, tags) {
|
||||
var promises = [];
|
||||
|
||||
for (var i = 0; i < tags.length; i++) {
|
||||
var tag = tags[i].Name;
|
||||
promises.push(service.tag(id, repository, tag));
|
||||
}
|
||||
function getTagsDetails(registry, repository, tags) {
|
||||
const promises = _.map(tags, (t) => tag(registry, repository, t.Name));
|
||||
|
||||
return $q.all(promises);
|
||||
};
|
||||
}
|
||||
|
||||
service.tag = function (id, repository, tag) {
|
||||
var deferred = $q.defer();
|
||||
function tag(registry, repository, tag) {
|
||||
const deferred = $q.defer();
|
||||
const id = registry.Id;
|
||||
|
||||
var promises = {
|
||||
v1: RegistryManifestsJquery.get({
|
||||
|
@ -142,35 +155,33 @@ function RegistryV2ServiceFactory($q, $async, RegistryCatalog, RegistryTags, Reg
|
|||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
}
|
||||
|
||||
service.addTag = function (id, repository, {tag, manifest}) {
|
||||
/**
|
||||
* END TAGS
|
||||
*/
|
||||
|
||||
/**
|
||||
* ADD TAG
|
||||
*/
|
||||
|
||||
// tag: RepositoryAddTagPayload
|
||||
function _addTagFromGenerator(registry, repository, tag) {
|
||||
return addTag(registry, repository, tag.Tag, tag.Manifest);
|
||||
}
|
||||
|
||||
function addTag(registry, repository, tag, manifest) {
|
||||
const id = registry.Id;
|
||||
delete manifest.digest;
|
||||
return RegistryManifestsJquery.put({
|
||||
id: id,
|
||||
repository: repository,
|
||||
tag: tag
|
||||
}, manifest);
|
||||
};
|
||||
}
|
||||
|
||||
service.deleteManifest = function (id, repository, imageDigest) {
|
||||
return RegistryManifestsJquery.delete({
|
||||
id: id,
|
||||
repository: repository,
|
||||
tag: imageDigest
|
||||
});
|
||||
};
|
||||
|
||||
service.shortTag = function(id, repository, tag) {
|
||||
return new Promise ((resolve, reject) => {
|
||||
RegistryManifestsJquery.getV2({id:id, repository: repository, tag: tag})
|
||||
.then((data) => resolve(new RepositoryShortTag(tag, data.config.digest, data.digest, data)))
|
||||
.catch((err) => reject(err))
|
||||
});
|
||||
};
|
||||
|
||||
async function* addTagsWithProgress(id, repository, tagsList, progression = 0) {
|
||||
for await (const partialResult of genericAsyncGenerator($q, tagsList, service.addTag, [id, repository])) {
|
||||
async function* _addTagsWithProgress(registry, repository, tagsList, progression = 0) {
|
||||
for await (const partialResult of genericAsyncGenerator($q, tagsList, _addTagFromGenerator, [registry, repository])) {
|
||||
if (typeof partialResult === 'number') {
|
||||
yield progression + partialResult;
|
||||
} else {
|
||||
|
@ -179,36 +190,110 @@ function RegistryV2ServiceFactory($q, $async, RegistryCatalog, RegistryTags, Reg
|
|||
}
|
||||
}
|
||||
|
||||
service.shortTagsWithProgress = async function* (id, repository, tagsList) {
|
||||
yield* genericAsyncGenerator($q, tagsList, service.shortTag, [id, repository]);
|
||||
/**
|
||||
* END ADD TAG
|
||||
*/
|
||||
|
||||
/**
|
||||
* DELETE MANIFEST
|
||||
*/
|
||||
|
||||
function deleteManifest(registry, repository, imageDigest) {
|
||||
const id = registry.Id;
|
||||
return RegistryManifestsJquery.delete({
|
||||
id: id,
|
||||
repository: repository,
|
||||
tag: imageDigest
|
||||
});
|
||||
}
|
||||
|
||||
async function* deleteManifestsWithProgress(id, repository, manifests) {
|
||||
for await (const partialResult of genericAsyncGenerator($q, manifests, service.deleteManifest, [id, repository])) {
|
||||
async function* _deleteManifestsWithProgress(registry, repository, manifests) {
|
||||
for await (const partialResult of genericAsyncGenerator($q, manifests, deleteManifest, [registry, repository])) {
|
||||
yield partialResult;
|
||||
}
|
||||
}
|
||||
|
||||
service.retagWithProgress = async function* (id, repository, modifiedTags, modifiedDigests, impactedTags){
|
||||
yield* deleteManifestsWithProgress(id, repository, modifiedDigests);
|
||||
/**
|
||||
* END DELETE MANIFEST
|
||||
*/
|
||||
|
||||
/**
|
||||
* SHORT TAG
|
||||
*/
|
||||
|
||||
function _shortTagFromGenerator(id, repository, tag) {
|
||||
return new Promise ((resolve, reject) => {
|
||||
RegistryManifestsJquery.getV2({id:id, repository: repository, tag: tag})
|
||||
.then((data) => resolve(new RepositoryShortTag(tag, data.config.digest, data.digest, data)))
|
||||
.catch((err) => reject(err))
|
||||
});
|
||||
}
|
||||
|
||||
async function* shortTagsWithProgress(registry, repository, tagsList) {
|
||||
const id = registry.Id;
|
||||
yield* genericAsyncGenerator($q, tagsList, _shortTagFromGenerator, [id, repository]);
|
||||
}
|
||||
|
||||
/**
|
||||
* END SHORT TAG
|
||||
*/
|
||||
|
||||
/**
|
||||
* RETAG
|
||||
*/
|
||||
async function* retagWithProgress(registry, repository, modifiedTags, modifiedDigests, impactedTags){
|
||||
yield* _deleteManifestsWithProgress(registry, repository, modifiedDigests);
|
||||
|
||||
const newTags = _.map(impactedTags, (item) => {
|
||||
const tagFromTable = _.find(modifiedTags, { 'Name': item.Name });
|
||||
const name = tagFromTable && tagFromTable.Name !== tagFromTable.NewName ? tagFromTable.NewName : item.Name;
|
||||
return { tag: name, manifest: item.ManifestV2 };
|
||||
return new RepositoryAddTagPayload(name, item.ManifestV2);
|
||||
});
|
||||
|
||||
yield* addTagsWithProgress(id, repository, newTags, modifiedDigests.length);
|
||||
yield* _addTagsWithProgress(registry, repository, newTags, modifiedDigests.length);
|
||||
}
|
||||
|
||||
service.deleteTagsWithProgress = async function* (id, repository, modifiedDigests, impactedTags) {
|
||||
yield* deleteManifestsWithProgress(id, repository, modifiedDigests);
|
||||
/**
|
||||
* END RETAG
|
||||
*/
|
||||
|
||||
const newTags = _.map(impactedTags, (item) => {return {tag: item.Name, manifest: item.ManifestV2}})
|
||||
/**
|
||||
* DELETE TAGS
|
||||
*/
|
||||
|
||||
yield* addTagsWithProgress(id, repository, newTags, modifiedDigests.length);
|
||||
async function* deleteTagsWithProgress(registry, repository, modifiedDigests, impactedTags) {
|
||||
yield* _deleteManifestsWithProgress(registry, repository, modifiedDigests);
|
||||
|
||||
const newTags = _.map(impactedTags, (item) => new RepositoryAddTagPayload(item.Name, item.ManifestV2));
|
||||
|
||||
yield* _addTagsWithProgress(registry, repository, newTags, modifiedDigests.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* END DELETE TAGS
|
||||
*/
|
||||
|
||||
/**
|
||||
* SERVICE FUNCTIONS DECLARATION
|
||||
*/
|
||||
|
||||
service.ping = ping;
|
||||
|
||||
service.repositories = repositories;
|
||||
service.getRepositoriesDetails = getRepositoriesDetails;
|
||||
|
||||
service.tags = tags;
|
||||
service.tag = tag;
|
||||
service.getTagsDetails = getTagsDetails;
|
||||
|
||||
service.shortTagsWithProgress = shortTagsWithProgress;
|
||||
|
||||
service.addTag = addTag;
|
||||
service.deleteManifest = deleteManifest;
|
||||
|
||||
service.deleteTagsWithProgress = deleteTagsWithProgress;
|
||||
service.retagWithProgress = retagWithProgress;
|
||||
|
||||
return service;
|
||||
}
|
||||
]);
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { RegistryManagementConfigurationDefaultModel } from '../../../../portainer/models/registry';
|
||||
import { RegistryTypes } from 'Extensions/registry-management/models/registryTypes';
|
||||
|
||||
angular.module('portainer.extensions.registrymanagement')
|
||||
.controller('ConfigureRegistryController', ['$scope', '$state', '$transition$', 'RegistryService', 'RegistryV2Service', 'Notifications',
|
||||
function ($scope, $state, $transition$, RegistryService, RegistryV2Service, Notifications) {
|
||||
.controller('ConfigureRegistryController', ['$scope', '$state', '$transition$', 'RegistryService', 'RegistryServiceSelector', 'Notifications',
|
||||
function ($scope, $state, $transition$, RegistryService, RegistryServiceSelector, Notifications) {
|
||||
|
||||
$scope.state = {
|
||||
testInProgress: false,
|
||||
|
@ -18,7 +19,7 @@ function ($scope, $state, $transition$, RegistryService, RegistryV2Service, Noti
|
|||
|
||||
RegistryService.configureRegistry($scope.registry.Id, $scope.model)
|
||||
.then(function success() {
|
||||
return RegistryV2Service.ping($scope.registry.Id, true);
|
||||
return RegistryServiceSelector.ping($scope.registry, true);
|
||||
})
|
||||
.then(function success() {
|
||||
Notifications.success('Success', 'Valid management configuration');
|
||||
|
@ -50,6 +51,7 @@ function ($scope, $state, $transition$, RegistryService, RegistryV2Service, Noti
|
|||
|
||||
function initView() {
|
||||
var registryId = $transition$.params().id;
|
||||
$scope.RegistryTypes = RegistryTypes;
|
||||
|
||||
RegistryService.registry(registryId)
|
||||
.then(function success(data) {
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
</div>
|
||||
<!-- !registry-url-input -->
|
||||
<!-- authentication-checkbox -->
|
||||
<div class="form-group" ng-if="registry.Type === 3">
|
||||
<div class="form-group" ng-if="registry.Type === RegistryTypes.CUSTOM || registry.Type === RegistryTypes.GITLAB">
|
||||
<div class="col-sm-12">
|
||||
<label for="registry_auth" class="control-label text-left">
|
||||
Authentication
|
||||
|
@ -65,7 +65,7 @@
|
|||
</div>
|
||||
<!-- !authentication-credentials -->
|
||||
<!-- tls -->
|
||||
<div ng-if="registry.Type === 3">
|
||||
<div ng-if="registry.Type === RegistryTypes.CUSTOM || registry.Type === RegistryTypes.GITLAB">
|
||||
<!-- tls-checkbox -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -48,6 +48,8 @@
|
|||
<td>Repository</td>
|
||||
<td>
|
||||
{{ repository.Name }}
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-xs btn-danger" ng-if="!state.tagsRetrieval.running && state.tagsRetrieval.progression !== 0" ng-click="removeRepository()">
|
||||
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this repository
|
||||
</button>
|
||||
|
@ -56,10 +58,12 @@
|
|||
<tr>
|
||||
<td>Tags count</td>
|
||||
<td>{{ repository.Tags.length }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr ng-if="short.Images.length">
|
||||
<td>Images count</td>
|
||||
<td>{{ short.Images.length }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -2,8 +2,8 @@ import _ from 'lodash-es';
|
|||
import { RepositoryTagViewModel, RepositoryShortTag } from '../../../models/repositoryTag';
|
||||
|
||||
angular.module('portainer.app')
|
||||
.controller('RegistryRepositoryController', ['$q', '$async', '$scope', '$uibModal', '$interval', '$transition$', '$state', 'RegistryV2Service', 'RegistryService', 'ModalService', 'Notifications', 'ImageHelper',
|
||||
function ($q, $async, $scope, $uibModal, $interval, $transition$, $state, RegistryV2Service, RegistryService, ModalService, Notifications, ImageHelper) {
|
||||
.controller('RegistryRepositoryController', ['$q', '$async', '$scope', '$uibModal', '$interval', '$transition$', '$state', 'RegistryServiceSelector', 'RegistryService', 'ModalService', 'Notifications', 'ImageHelper',
|
||||
function ($q, $async, $scope, $uibModal, $interval, $transition$, $state, RegistryServiceSelector, RegistryService, ModalService, Notifications, ImageHelper) {
|
||||
|
||||
$scope.state = {
|
||||
actionInProgress: false,
|
||||
|
@ -63,7 +63,7 @@ angular.module('portainer.app')
|
|||
|
||||
$scope.paginationAction = function (tags) {
|
||||
$scope.state.loading = true;
|
||||
RegistryV2Service.getTagsDetails($scope.registryId, $scope.repository.Name, tags)
|
||||
RegistryServiceSelector.getTagsDetails($scope.registry, $scope.repository.Name, tags)
|
||||
.then(function success(data) {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var idx = _.findIndex($scope.tags, {'Name': data[i].Name});
|
||||
|
@ -86,7 +86,7 @@ angular.module('portainer.app')
|
|||
|
||||
function createRetrieveAsyncGenerator() {
|
||||
$scope.state.tagsRetrieval.asyncGenerator =
|
||||
RegistryV2Service.shortTagsWithProgress($scope.registryId, $scope.repository.Name, $scope.repository.Tags);
|
||||
RegistryServiceSelector.shortTagsWithProgress($scope.registry, $scope.repository.Name, $scope.repository.Tags);
|
||||
}
|
||||
|
||||
function resetTagsRetrievalState() {
|
||||
|
@ -151,7 +151,7 @@ angular.module('portainer.app')
|
|||
}
|
||||
const tag = $scope.short.Tags.find((item) => item.ImageId === $scope.formValues.SelectedImage);
|
||||
const manifest = tag.ManifestV2;
|
||||
await RegistryV2Service.addTag($scope.registryId, $scope.repository.Name, {tag: $scope.formValues.Tag, manifest: manifest})
|
||||
await RegistryServiceSelector.addTag($scope.registry, $scope.repository.Name, $scope.formValues.Tag, manifest)
|
||||
|
||||
Notifications.success('Success', 'Tag successfully added');
|
||||
$scope.short.Tags.push(new RepositoryShortTag($scope.formValues.Tag, tag.ImageId, tag.ImageDigest, tag.ManifestV2));
|
||||
|
@ -182,7 +182,7 @@ angular.module('portainer.app')
|
|||
|
||||
function createRetagAsyncGenerator(modifiedTags, modifiedDigests, impactedTags) {
|
||||
$scope.state.tagsRetag.asyncGenerator =
|
||||
RegistryV2Service.retagWithProgress($scope.registryId, $scope.repository.Name, modifiedTags, modifiedDigests, impactedTags);
|
||||
RegistryServiceSelector.retagWithProgress($scope.registry, $scope.repository.Name, modifiedTags, modifiedDigests, impactedTags);
|
||||
}
|
||||
|
||||
async function retagActionAsync() {
|
||||
|
@ -252,7 +252,7 @@ angular.module('portainer.app')
|
|||
|
||||
function createDeleteAsyncGenerator(modifiedDigests, impactedTags) {
|
||||
$scope.state.tagsDelete.asyncGenerator =
|
||||
RegistryV2Service.deleteTagsWithProgress($scope.registryId, $scope.repository.Name, modifiedDigests, impactedTags);
|
||||
RegistryServiceSelector.deleteTagsWithProgress($scope.registry, $scope.repository.Name, modifiedDigests, impactedTags);
|
||||
}
|
||||
|
||||
async function removeTagsAsync(selectedTags) {
|
||||
|
@ -289,7 +289,7 @@ angular.module('portainer.app')
|
|||
Notifications.success('Success', 'Tags successfully deleted');
|
||||
|
||||
if ($scope.short.Tags.length === 0) {
|
||||
$state.go('portainer.registries.registry.repositories', {id: $scope.registryId}, {reload: true});
|
||||
$state.go('portainer.registries.registry.repositories', {id: $scope.registry.Id}, {reload: true});
|
||||
}
|
||||
await loadRepositoryDetails();
|
||||
} catch (err) {
|
||||
|
@ -322,10 +322,10 @@ angular.module('portainer.app')
|
|||
try {
|
||||
const digests = _.uniqBy($scope.short.Tags, 'ImageDigest');
|
||||
const promises = [];
|
||||
_.map(digests, (item) => promises.push(RegistryV2Service.deleteManifest($scope.registryId, $scope.repository.Name, item.ImageDigest)));
|
||||
_.map(digests, (item) => promises.push(RegistryServiceSelector.deleteManifest($scope.registry, $scope.repository.Name, item.ImageDigest)));
|
||||
await Promise.all(promises);
|
||||
Notifications.success('Success', 'Repository sucessfully removed');
|
||||
$state.go('portainer.registries.registry.repositories', {id: $scope.registryId}, {reload: true});
|
||||
$state.go('portainer.registries.registry.repositories', {id: $scope.registry.Id}, {reload: true});
|
||||
} catch (err) {
|
||||
Notifications.error('Failure', err, 'Unable to delete repository');
|
||||
}
|
||||
|
@ -351,9 +351,9 @@ angular.module('portainer.app')
|
|||
*/
|
||||
async function loadRepositoryDetails() {
|
||||
try {
|
||||
const registryId = $scope.registryId;
|
||||
const registry = $scope.registry;
|
||||
const repository = $scope.repository.Name;
|
||||
const tags = await RegistryV2Service.tags(registryId, repository);
|
||||
const tags = await RegistryServiceSelector.tags(registry, repository);
|
||||
$scope.tags = [];
|
||||
$scope.repository.Tags = [];
|
||||
$scope.repository.Tags = _.sortBy(_.concat($scope.repository.Tags, _.without(tags.tags, null)));
|
||||
|
@ -365,7 +365,7 @@ angular.module('portainer.app')
|
|||
|
||||
async function initView() {
|
||||
try {
|
||||
const registryId = $scope.registryId = $transition$.params().id;
|
||||
const registryId = $transition$.params().id;
|
||||
$scope.repository.Name = $transition$.params().repository;
|
||||
$scope.state.loading = true;
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
import { RegistryTypes } from 'Extensions/registry-management/models/registryTypes';
|
||||
|
||||
angular.module('portainer.extensions.registrymanagement')
|
||||
.controller('RegistryRepositoriesController', ['$transition$', '$scope', 'RegistryService', 'RegistryV2Service', 'Notifications', 'Authentication',
|
||||
function ($transition$, $scope, RegistryService, RegistryV2Service, Notifications, Authentication) {
|
||||
.controller('RegistryRepositoriesController', ['$transition$', '$scope', 'RegistryService', 'RegistryServiceSelector', 'Notifications', 'Authentication',
|
||||
function ($transition$, $scope, RegistryService, RegistryServiceSelector, Notifications, Authentication) {
|
||||
|
||||
$scope.state = {
|
||||
displayInvalidConfigurationMessage: false,
|
||||
|
@ -10,8 +12,11 @@ function ($transition$, $scope, RegistryService, RegistryV2Service, Notification
|
|||
};
|
||||
|
||||
$scope.paginationAction = function (repositories) {
|
||||
if ($scope.registry.Type === RegistryTypes.GITLAB) {
|
||||
return;
|
||||
}
|
||||
$scope.state.loading = true;
|
||||
RegistryV2Service.getRepositoriesDetails($scope.state.registryId, repositories)
|
||||
RegistryServiceSelector.getRepositoriesDetails($scope.registry, repositories)
|
||||
.then(function success(data) {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var idx = _.findIndex($scope.repositories, {'Name': data[i].Name});
|
||||
|
@ -30,20 +35,19 @@ function ($transition$, $scope, RegistryService, RegistryV2Service, Notification
|
|||
};
|
||||
|
||||
function initView() {
|
||||
$scope.state.registryId = $transition$.params().id;
|
||||
const registryId = $transition$.params().id;
|
||||
|
||||
var authenticationEnabled = $scope.applicationState.application.authentication;
|
||||
if (authenticationEnabled) {
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
}
|
||||
|
||||
RegistryService.registry($scope.state.registryId)
|
||||
RegistryService.registry(registryId)
|
||||
.then(function success(data) {
|
||||
$scope.registry = data;
|
||||
|
||||
RegistryV2Service.ping($scope.state.registryId, false)
|
||||
RegistryServiceSelector.ping($scope.registry, false)
|
||||
.then(function success() {
|
||||
return RegistryV2Service.catalog($scope.state.registryId);
|
||||
return RegistryServiceSelector.repositories($scope.registry);
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.repositories = data;
|
||||
|
|
|
@ -6,12 +6,12 @@ import { RegistryImageDetailsViewModel } from 'Extensions/registry-management/mo
|
|||
class RegistryRepositoryTagController {
|
||||
|
||||
/* @ngInject */
|
||||
constructor($transition$, $async, Notifications, RegistryService, RegistryV2Service, imagelayercommandFilter) {
|
||||
constructor($transition$, $async, Notifications, RegistryService, RegistryServiceSelector, imagelayercommandFilter) {
|
||||
this.$transition$ = $transition$;
|
||||
this.$async = $async;
|
||||
this.Notifications = Notifications;
|
||||
this.RegistryService = RegistryService;
|
||||
this.RegistryV2Service = RegistryV2Service;
|
||||
this.RegistryServiceSelector = RegistryServiceSelector;
|
||||
this.imagelayercommandFilter = imagelayercommandFilter;
|
||||
|
||||
this.context = {};
|
||||
|
@ -39,7 +39,7 @@ class RegistryRepositoryTagController {
|
|||
}
|
||||
try {
|
||||
this.registry = await this.RegistryService.registry(this.context.registryId);
|
||||
this.tag = await this.RegistryV2Service.tag(this.context.registryId, this.context.repository, this.context.tag);
|
||||
this.tag = await this.RegistryServiceSelector.tag(this.registry, this.context.repository, this.context.tag);
|
||||
const length = this.tag.History.length;
|
||||
this.history = _.map(this.tag.History, (layer, idx) => new RegistryImageLayerViewModel(length - idx, layer));
|
||||
_.forEach(this.history, (item) => item.CreatedBy = this.imagelayercommandFilter(item.CreatedBy))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue