import _ from 'lodash-es'; import { RepositoryShortTag } from '../models/repositoryTag'; import { RepositoryAddTagPayload } from '../models/repositoryTag'; import { RegistryRepositoryViewModel } from '../models/registryRepository'; import genericAsyncGenerator from './genericAsyncGenerator'; angular.module('portainer.extensions.registrymanagement').factory('RegistryV2Service', [ '$q', '$async', 'RegistryCatalog', 'RegistryTags', 'RegistryManifestsJquery', 'RegistryV2Helper', function RegistryV2ServiceFactory($q, $async, RegistryCatalog, RegistryTags, RegistryManifestsJquery, RegistryV2Helper) { 'use strict'; var service = {}; /** * 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) { repositories = _.concat(repositories, data.repositories); if (data.last && data.n) { _getCatalogPage({ id: params.id, n: data.n, last: data.last }, deferred, repositories); } else { deferred.resolve(repositories); } }); } function _getCatalog(id) { var deferred = $q.defer(); var repositories = []; _getCatalogPage({ id: id }, deferred, repositories); return deferred.promise; } function repositories(registry) { const deferred = $q.defer(); const id = registry.Id; _getCatalog(id) .then(function success(data) { const repositories = _.map(data, (repositoryName) => new RegistryRepositoryViewModel(repositoryName)); deferred.resolve(repositories); }) .catch(function error(err) { deferred.reject({ msg: 'Unable to retrieve repositories', err: err, }); }); return deferred.promise; } 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, }); }); return deferred.promise; } /** * END REPOSITORIES */ /** * TAGS */ function _getTagsPage(params, deferred, previousTags) { RegistryTags.get(params) .$promise.then(function (data) { previousTags.name = data.name; previousTags.tags = _.concat(previousTags.tags, data.tags); if (data.last && data.n) { _getTagsPage({ id: params.id, repository: params.repository, n: data.n, last: data.last }, deferred, previousTags); } else { deferred.resolve(previousTags); } }) .catch(function error(err) { deferred.reject({ msg: 'Unable to retrieve tags', err: err, }); }); } function tags(registry, repository) { const deferred = $q.defer(); const id = registry.Id; _getTagsPage({ id: id, repository: repository }, deferred, { tags: [] }); return deferred.promise; } function getTagsDetails(registry, repository, tags) { const promises = _.map(tags, (t) => tag(registry, repository, t.Name)); return $q.all(promises); } function tag(registry, repository, tag) { const deferred = $q.defer(); const id = registry.Id; var promises = { v1: RegistryManifestsJquery.get({ id: id, repository: repository, tag: tag, }), v2: RegistryManifestsJquery.getV2({ id: id, repository: repository, tag: tag, }), }; $q.all(promises) .then(function success(data) { var tag = RegistryV2Helper.manifestsToTag(data); deferred.resolve(tag); }) .catch(function error(err) { deferred.reject({ msg: 'Unable to retrieve tag ' + tag, err: err, }); }); return deferred.promise; } /** * 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 ); } 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 { yield partialResult; } } } /** * 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(registry, repository, manifests) { for await (const partialResult of genericAsyncGenerator($q, manifests, deleteManifest, [registry, repository])) { yield partialResult; } } /** * 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 new RepositoryAddTagPayload(name, item.ManifestV2); }); yield* _addTagsWithProgress(registry, repository, newTags, modifiedDigests.length); } /** * END RETAG */ /** * DELETE TAGS */ 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; }, ]);