From e19bc8abc769bea0a62c1cd11261fb6b144dbad3 Mon Sep 17 00:00:00 2001 From: xAt0mZ Date: Wed, 27 Nov 2019 23:36:39 +0100 Subject: [PATCH] fix(app): registry push-pull features overhaul (#3393) * feat(registry): registry or direct url selector * feat(app): push pull container creation * feat(app): push pull container duplicate * feat(app): push pull container details recreate * feat(app): push pull container details commit * feat(app): push pull images * feat(app): push pull image tag * feat(app): push pull image push * feat(app): push pull image pull * feat(app): push pull service creation * feat(app): push pull templates create container * feat(app): push pull templates create stacks * feat(app): push pull template edit * feat(app): push pull service details update * fix(app): refactor registry selector + registry auto select * feat(app): remove autocomplete on registry selector * style(image-registry): reword simple/advanced mode * Revert "feat(app): remove autocomplete on registry selector" This reverts commit 97ec2ddd62715405c4089bd2c0cdda4028263d94. * refactor(registry-selector): reverse registry and image fields * feat(app): autocomplete on registry selector * feat(registry-selector): change gitlab registry autocomplete * feat(registry-selector): autocomplete for dockerhub * feat(registry-selector): gitlab url based on locked value instead of name * fix(registry-selector): gitlab registries URL are not modified anymore * fix(registry-selector): change gitlab image autofill on duplicate * fix(registry-selector): gitlab registries now only suggest their own images and not all from gitlab * fix(registry-selector): psuh pull issues with gitlab registries * fix(registry-selector): dockerhub registry selection on duplicate for dockerhub images * fix(templates): registry retrieval for template * feat(images): add autocomplete on image pull panel * fix(registry-selector): add latest tag when no tag is specified * fix(registry-selector): latest tag now applied for non gitlab registries --- api/portainer.go | 1 + .../imageRegistry/por-image-registry.js | 4 +- .../imageRegistry/porImageRegistry.html | 69 ++++++++--- .../porImageRegistryController.js | 110 +++++++++++++----- app/docker/helpers/imageHelper.js | 94 +++++++-------- app/docker/models/porImageRegistry.js | 16 +++ app/docker/rest/commit.js | 2 +- app/docker/rest/image.js | 6 +- app/docker/services/imageService.js | 46 ++++++-- .../create/createContainerController.js | 24 ++-- .../containers/create/createcontainer.html | 16 +-- .../views/containers/edit/container.html | 7 +- .../containers/edit/containerController.js | 45 +++---- app/docker/views/images/edit/image.html | 7 +- .../views/images/edit/imageController.js | 29 +++-- app/docker/views/images/images.html | 16 +-- app/docker/views/images/imagesController.js | 11 +- .../create/createServiceController.js | 13 +-- .../views/services/create/createservice.html | 7 +- .../views/services/edit/includes/image.html | 32 +++++ app/docker/views/services/edit/service.html | 11 +- .../views/services/edit/serviceController.js | 49 ++++---- .../forms/template-form/template-form.js | 54 +-------- .../forms/template-form/templateForm.html | 5 +- .../template-form/templateFormController.js | 54 +++++++++ app/portainer/helpers/registryHelper.js | 18 --- app/portainer/models/registry.js | 3 +- app/portainer/models/template.js | 14 +-- app/portainer/services/api/registryService.js | 63 +++++++--- app/portainer/services/api/templateService.js | 38 +++--- .../views/templates/templatesController.js | 8 +- assets/css/app.css | 2 +- 32 files changed, 525 insertions(+), 349 deletions(-) create mode 100644 app/docker/models/porImageRegistry.js create mode 100644 app/docker/views/services/edit/includes/image.html create mode 100644 app/portainer/components/forms/template-form/templateFormController.js delete mode 100644 app/portainer/helpers/registryHelper.js diff --git a/api/portainer.go b/api/portainer.go index 783e6c8a3..9cb0d064f 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -196,6 +196,7 @@ type ( GitlabRegistryData struct { ProjectID int `json:"ProjectId"` InstanceURL string `json:"InstanceURL"` + ProjectPath string `json:"ProjectPath"` } // Registry represents a Docker registry with all the info required diff --git a/app/docker/components/imageRegistry/por-image-registry.js b/app/docker/components/imageRegistry/por-image-registry.js index 568b12ce5..39d6a5919 100644 --- a/app/docker/components/imageRegistry/por-image-registry.js +++ b/app/docker/components/imageRegistry/por-image-registry.js @@ -2,8 +2,8 @@ angular.module('portainer.docker').component('porImageRegistry', { templateUrl: './porImageRegistry.html', controller: 'porImageRegistryController', bindings: { - 'image': '=', - 'registry': '=', + 'model': '=', // must be of type PorImageRegistryModel + 'pullWarning': '<', 'autoComplete': '<', 'labelClass': '@', 'inputClass': '@' diff --git a/app/docker/components/imageRegistry/porImageRegistry.html b/app/docker/components/imageRegistry/porImageRegistry.html index aae64be66..05bc7ea07 100644 --- a/app/docker/components/imageRegistry/porImageRegistry.html +++ b/app/docker/components/imageRegistry/porImageRegistry.html @@ -1,21 +1,54 @@ -
- -
- -
- -
- -
-
-
-
-
-

Image name is required.

+ +
+
+ +
+ +
+ +
+
+ {{$ctrl.displayedRegistryURL()}} + +
+ + +
+
+ +
+ +
+
+
+ + +
+
+
+

Image name is required. Tag must be specified otherwise Portainer will pull all tags associated to the image.

+
+
+
+ + diff --git a/app/docker/components/imageRegistry/porImageRegistryController.js b/app/docker/components/imageRegistry/porImageRegistryController.js index 6c47fb80a..7f4e9cfbc 100644 --- a/app/docker/components/imageRegistry/porImageRegistryController.js +++ b/app/docker/components/imageRegistry/porImageRegistryController.js @@ -1,31 +1,89 @@ +import angular from 'angular'; import _ from 'lodash-es'; +import { DockerHubViewModel } from 'Portainer/models/dockerhub'; +import { RegistryTypes } from 'Extensions/registry-management/models/registryTypes'; -angular.module('portainer.docker') -.controller('porImageRegistryController', ['$q', 'RegistryService', 'DockerHubService', 'ImageService', 'Notifications', -function ($q, RegistryService, DockerHubService, ImageService, Notifications) { - var ctrl = this; +class porImageRegistryController { + /* @ngInject */ + constructor($async, $scope, ImageHelper, RegistryService, DockerHubService, ImageService, Notifications) { + this.$async = $async; + this.$scope = $scope; + this.ImageHelper = ImageHelper; + this.RegistryService = RegistryService; + this.DockerHubService = DockerHubService; + this.ImageService = ImageService; + this.Notifications = Notifications; - function initComponent() { - $q.all({ - registries: RegistryService.registries(), - dockerhub: DockerHubService.dockerhub(), - availableImages: ctrl.autoComplete ? ImageService.images() : [] - }) - .then(function success(data) { - var dockerhub = data.dockerhub; - var registries = data.registries; - ctrl.availableImages = ImageService.getUniqueTagListFromImages(data.availableImages); - ctrl.availableRegistries = [dockerhub].concat(registries); - if (!ctrl.registry.Id) { - ctrl.registry = dockerhub; - } else { - ctrl.registry = _.find(ctrl.availableRegistries, { 'Id': ctrl.registry.Id }); - } - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve registries'); - }); + this.onInit = this.onInit.bind(this); + this.onRegistryChange = this.onRegistryChange.bind(this); + + this.$scope.$watch(() => this.model.Registry, this.onRegistryChange); } - initComponent(); -}]); + isKnownRegistry(registry) { + return !(registry instanceof DockerHubViewModel) && registry.URL; + } + + getRegistryURL(registry) { + let url = registry.URL; + if (registry.Type === RegistryTypes.GITLAB) { + url = registry.URL + '/' + registry.Gitlab.ProjectPath; + } + return url; + } + + prepareAutocomplete() { + let images = []; + const registry = this.model.Registry; + if (this.isKnownRegistry(registry)) { + const url = this.getRegistryURL(registry); + const registryImages = _.filter(this.images, (image) => _.includes(image, url)); + images = _.map(registryImages, (image) => _.replace(image, new RegExp(url + '\/?'), '')); + } else { + const registries = _.filter(this.availableRegistries, (reg) => this.isKnownRegistry(reg)); + const registryImages = _.flatMap(registries, (registry) => _.filter(this.images, (image) => _.includes(image, registry.URL))); + const imagesWithoutKnown = _.difference(this.images, registryImages); + images = _.filter(imagesWithoutKnown, (image) => !this.ImageHelper.imageContainsURL(image)); + } + this.availableImages = images; + } + + onRegistryChange() { + this.prepareAutocomplete(); + if (this.model.Registry.Type === RegistryTypes.GITLAB && this.model.Image) { + this.model.Image = _.replace(this.model.Image, this.model.Registry.Gitlab.ProjectPath, ''); + } + } + + displayedRegistryURL() { + return this.getRegistryURL(this.model.Registry) || 'docker.io'; + } + + async onInit() { + try { + const [registries, dockerhub, images] = await Promise.all([ + this.RegistryService.registries(), + this.DockerHubService.dockerhub(), + this.autoComplete ? this.ImageService.images() : [] + ]); + this.images = this.ImageService.getUniqueTagListFromImages(images); + this.availableRegistries = _.concat(dockerhub, registries); + + const id = this.model.Registry.Id; + if (!id) { + this.model.Registry = dockerhub; + } else { + this.model.Registry = _.find(this.availableRegistries, { 'Id': id }); + } + } catch (err) { + this.Notifications.error('Failure', err, 'Unable to retrieve registries'); + } + } + + $onInit() { + return this.$async(this.onInit); + } +} + +export default porImageRegistryController; +angular.module('portainer.docker').controller('porImageRegistryController', porImageRegistryController); diff --git a/app/docker/helpers/imageHelper.js b/app/docker/helpers/imageHelper.js index 78091aa5e..b1bd8d78d 100644 --- a/app/docker/helpers/imageHelper.js +++ b/app/docker/helpers/imageHelper.js @@ -1,4 +1,5 @@ import _ from 'lodash-es'; +import { RegistryTypes } from 'Extensions/registry-management/models/registryTypes'; angular.module('portainer.docker') .factory('ImageHelper', [function ImageHelperFactory() { @@ -7,74 +8,65 @@ angular.module('portainer.docker') var helper = {}; helper.isValidTag = isValidTag; + helper.createImageConfigForContainer = createImageConfigForContainer; + helper.getImagesNamesForDownload = getImagesNamesForDownload; + helper.removeDigestFromRepository = removeDigestFromRepository; + helper.imageContainsURL = imageContainsURL; function isValidTag(tag) { return tag.match(/^(?![\.\-])([a-zA-Z0-9\_\.\-])+$/g); } - helper.extractImageAndRegistryFromRepository = function(repository) { - var slashCount = _.countBy(repository)['/']; - var registry = null; - var image = repository; - if (slashCount >= 1) { - // assume something/something[/...] - registry = repository.substr(0, repository.indexOf('/')); - // assume valid DNS name or IP (contains at least one '.') - if (_.countBy(registry)['.'] > 0) { - image = repository.substr(repository.indexOf('/') + 1); - } else { - registry = null; - } - } - - return { - registry: registry, - image: image - }; - }; - - helper.getImagesNamesForDownload = function(images) { + function getImagesNamesForDownload(images) { var names = images.map(function(image) { return image.RepoTags[0] !== ':' ? image.RepoTags[0] : image.Id; }); return { names: names }; - }; - - function extractNameAndTag(imageName, registry) { - var imageNameAndTag = imageName.split(':'); - var image = imageNameAndTag[0]; - var tag = imageNameAndTag[1] ? imageNameAndTag[1] : 'latest'; - if (registry) { - image = registry + '/' + imageNameAndTag[0]; - } - - return { - image: image, - tag: tag - }; } - helper.createImageConfigForCommit = function(imageName, registry) { - var imageAndTag = extractNameAndTag(imageName, registry); - return { - repo: imageAndTag.image, - tag: imageAndTag.tag + /** + * + * @param {PorImageRegistryModel} registry + */ + function createImageConfigForContainer(registry) { + const data = { + fromImage: '' }; - }; + let fullImageName = ''; - helper.createImageConfigForContainer = function (imageName, registry) { - var imageAndTag = extractNameAndTag(imageName, registry); - return { - fromImage: imageAndTag.image, - tag: imageAndTag.tag - }; - }; + if (registry.UseRegistry) { + if (registry.Registry.Type === RegistryTypes.GITLAB) { + const slash = _.startsWith(registry.Image, ':') ? '' : '/'; + fullImageName = registry.Registry.URL + '/' + registry.Registry.Gitlab.ProjectPath + slash + registry.Image; + } else { + const url = registry.Registry.URL ? registry.Registry.URL + '/' : ''; + fullImageName = url + registry.Image; + } + if (!_.includes(registry.Image, ':')) { + fullImageName += ':latest'; + } + } else { + fullImageName = registry.Image; + } - helper.removeDigestFromRepository = function(repository) { + data.fromImage = fullImageName; + return data; + } + + function imageContainsURL(image) { + const split = _.split(image, '/'); + const url = split[0]; + if (split.length > 1) { + return _.includes(url, '.') || _.includes(url, ':'); + } + return false; + } + + function removeDigestFromRepository(repository) { return repository.split('@sha')[0]; - }; + } return helper; }]); diff --git a/app/docker/models/porImageRegistry.js b/app/docker/models/porImageRegistry.js new file mode 100644 index 000000000..38ea4ad94 --- /dev/null +++ b/app/docker/models/porImageRegistry.js @@ -0,0 +1,16 @@ +/** + * This model should be used with por-image-registry component + * And bound to the 'model' attribute + * + * // viewController.js + * + * this.imageModel = new PorImageRegistryModel(); + * + * // view.html + * + */ +export function PorImageRegistryModel() { + this.UseRegistry = true; + this.Registry = {}; + this.Image = ''; +} \ No newline at end of file diff --git a/app/docker/rest/commit.js b/app/docker/rest/commit.js index f2cf6dc03..364610297 100644 --- a/app/docker/rest/commit.js +++ b/app/docker/rest/commit.js @@ -5,6 +5,6 @@ angular.module('portainer.docker') endpointId: EndpointProvider.endpointID }, { - commitContainer: {method: 'POST', params: {container: '@id', repo: '@repo', tag: '@tag'}, ignoreLoadingBar: true} + commitContainer: {method: 'POST', params: {container: '@id', repo: '@repo'}, ignoreLoadingBar: true} }); }]); diff --git a/app/docker/rest/image.js b/app/docker/rest/image.js index 928466802..a0cfd0cfc 100644 --- a/app/docker/rest/image.js +++ b/app/docker/rest/image.js @@ -15,16 +15,16 @@ function ImageFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, HttpR search: {method: 'GET', params: {action: 'search'}}, history: {method: 'GET', params: {action: 'history'}, isArray: true}, insert: {method: 'POST', params: {id: '@id', action: 'insert'}}, - tag: {method: 'POST', params: {id: '@id', action: 'tag', force: 0, repo: '@repo', tag: '@tag'}, ignoreLoadingBar: true}, + tag: {method: 'POST', params: {id: '@id', action: 'tag', force: 0, repo: '@repo'}, ignoreLoadingBar: true}, inspect: {method: 'GET', params: {id: '@id', action: 'json'}}, push: { - method: 'POST', params: {action: 'push', id: '@tag'}, + method: 'POST', params: {action: 'push', id: '@imageName'}, isArray: true, transformResponse: jsonObjectsToArrayHandler, headers: { 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader }, ignoreLoadingBar: true }, create: { - method: 'POST', params: {action: 'create', fromImage: '@fromImage', tag: '@tag'}, + method: 'POST', params: {action: 'create', fromImage: '@fromImage'}, isArray: true, transformResponse: jsonObjectsToArrayHandler, headers: { 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader }, ignoreLoadingBar: true diff --git a/app/docker/services/imageService.js b/app/docker/services/imageService.js index 21bf6dd41..b2731fe22 100644 --- a/app/docker/services/imageService.js +++ b/app/docker/services/imageService.js @@ -77,12 +77,20 @@ angular.module('portainer.docker') return deferred.promise; }; - service.pushImage = function(tag, registry) { + service.pushImage = pushImage; + /** + * + * @param {PorImageRegistryModel} registryModel + */ + function pushImage(registryModel) { var deferred = $q.defer(); - var authenticationDetails = registry.Authentication ? RegistryService.encodedCredentials(registry) : ''; + var authenticationDetails = registryModel.Registry.Authentication ? RegistryService.encodedCredentials(registryModel.Registry) : ''; HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails); - Image.push({tag: tag}).$promise + + const imageConfiguration = ImageHelper.createImageConfigForContainer(registryModel); + + Image.push({imageName: imageConfiguration.fromImage}).$promise .then(function success(data) { if (data[data.length - 1].error) { deferred.reject({ msg: data[data.length - 1].error }); @@ -94,7 +102,11 @@ angular.module('portainer.docker') deferred.reject({ msg: 'Unable to push image tag', err: err }); }); return deferred.promise; - }; + } + + /** + * PULL IMAGE + */ function pullImageAndIgnoreErrors(imageConfiguration) { var deferred = $q.defer(); @@ -127,21 +139,31 @@ angular.module('portainer.docker') return deferred.promise; } - service.pullImage = function(image, registry, ignoreErrors) { - var imageDetails = ImageHelper.extractImageAndRegistryFromRepository(image); - var imageConfiguration = ImageHelper.createImageConfigForContainer(imageDetails.image, registry.URL); - var authenticationDetails = registry.Authentication ? RegistryService.encodedCredentials(registry) : ''; + service.pullImage = pullImage; + + /** + * + * @param {PorImageRegistryModel} registry + * @param {bool} ignoreErrors + */ + function pullImage(registry, ignoreErrors) { + var authenticationDetails = registry.Registry.Authentication ? RegistryService.encodedCredentials(registry.Registry) : ''; HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails); + var imageConfiguration = ImageHelper.createImageConfigForContainer(registry); + if (ignoreErrors) { return pullImageAndIgnoreErrors(imageConfiguration); } return pullImageAndAcknowledgeErrors(imageConfiguration); - }; + } - service.tagImage = function(id, image, registry) { - var imageConfig = ImageHelper.createImageConfigForCommit(image, registry); - return Image.tag({id: id, tag: imageConfig.tag, repo: imageConfig.repo}).$promise; + /** + * ! PULL IMAGE + */ + + service.tagImage = function(id, image) { + return Image.tag({id: id, repo: image}).$promise; }; service.downloadImages = function(images) { diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js index aaf5cfdd5..16b0abed1 100644 --- a/app/docker/views/containers/create/createContainerController.js +++ b/app/docker/views/containers/create/createContainerController.js @@ -2,6 +2,7 @@ import _ from 'lodash-es'; import { ContainerCapabilities, ContainerCapability } from '../../../models/containerCapabilities'; import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel'; import { ContainerDetailsViewModel } from '../../../models/container'; +import { PorImageRegistryModel } from 'Docker/models/porImageRegistry'; angular.module('portainer.docker') @@ -27,7 +28,8 @@ function ($q, $scope, $async, $state, $timeout, $transition$, $filter, Container NodeName: null, capabilities: [], LogDriverName: '', - LogDriverOpts: [] + LogDriverOpts: [], + RegistryModel: new PorImageRegistryModel() }; $scope.extraNetworks = {}; @@ -130,11 +132,8 @@ function ($q, $scope, $async, $state, $timeout, $transition$, $filter, Container $scope.fromContainerMultipleNetworks = false; function prepareImageConfig(config) { - var image = config.Image; - var registry = $scope.formValues.Registry; - var imageConfig = ImageHelper.createImageConfigForContainer(image, registry.URL); - config.Image = imageConfig.fromImage + ':' + imageConfig.tag; - $scope.imageConfig = imageConfig; + const imageConfig = ImageHelper.createImageConfigForContainer($scope.formValues.RegistryModel); + config.Image = imageConfig.fromImage; } function preparePortBindings(config) { @@ -438,13 +437,9 @@ function ($q, $scope, $async, $state, $timeout, $transition$, $filter, Container } function loadFromContainerImageConfig() { - var imageInfo = ImageHelper.extractImageAndRegistryFromRepository($scope.config.Image); - RegistryService.retrieveRegistryFromRepository($scope.config.Image) - .then(function success(data) { - if (data) { - $scope.config.Image = imageInfo.image; - $scope.formValues.Registry = data; - } + RegistryService.retrievePorRegistryModelFromRepository($scope.config.Image) + .then((model) => { + $scope.formValues.RegistryModel = model; }) .catch(function error(err) { Notifications.error('Failure', err, 'Unable to retrive registry'); @@ -569,7 +564,6 @@ function ($q, $scope, $async, $state, $timeout, $transition$, $filter, Container loadFromContainerSpec(); } else { $scope.fromContainer = {}; - $scope.formValues.Registry = {}; $scope.formValues.capabilities = new ContainerCapabilities(); } }, function(e) { @@ -754,7 +748,7 @@ function ($q, $scope, $async, $state, $timeout, $transition$, $filter, Container function pullImageIfNeeded() { return $q.when($scope.formValues.alwaysPull && - ImageService.pullImage($scope.config.Image, $scope.formValues.Registry, true)); + ImageService.pullImage($scope.formValues.RegistryModel, true)); } function createNewContainer() { diff --git a/app/docker/views/containers/create/createcontainer.html b/app/docker/views/containers/create/createcontainer.html index 881053077..266f6d0d7 100644 --- a/app/docker/views/containers/create/createcontainer.html +++ b/app/docker/views/containers/create/createcontainer.html @@ -21,18 +21,18 @@
Image configuration
-
+
The Docker registry for the {{ config.Image }} image is not registered inside Portainer, you will not be able to create a container. Please register that registry first.
-
+
@@ -142,7 +142,9 @@
- diff --git a/app/docker/views/containers/edit/container.html b/app/docker/views/containers/edit/container.html index 6a843882a..b005f33f4 100644 --- a/app/docker/views/containers/edit/container.html +++ b/app/docker/views/containers/edit/container.html @@ -155,10 +155,9 @@ @@ -170,7 +169,7 @@
- +
diff --git a/app/docker/views/containers/edit/containerController.js b/app/docker/views/containers/edit/containerController.js index 860c2ccc0..150b99e44 100644 --- a/app/docker/views/containers/edit/containerController.js +++ b/app/docker/views/containers/edit/containerController.js @@ -1,14 +1,15 @@ import moment from 'moment'; +import { PorImageRegistryModel } from 'Docker/models/porImageRegistry'; angular.module('portainer.docker') -.controller('ContainerController', ['$q', '$scope', '$state','$transition$', '$filter', 'Commit', 'ContainerHelper', 'ContainerService', 'ImageHelper', 'NetworkService', 'Notifications', 'ModalService', 'ResourceControlService', 'RegistryService', 'ImageService', 'HttpRequestHelper', 'Authentication', -function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, ContainerService, ImageHelper, NetworkService, Notifications, ModalService, ResourceControlService, RegistryService, ImageService, HttpRequestHelper, Authentication) { +.controller('ContainerController', ['$q', '$scope', '$state','$transition$', '$filter', '$async', 'Commit', 'ContainerHelper', 'ContainerService', 'ImageHelper', 'NetworkService', 'Notifications', 'ModalService', 'ResourceControlService', 'RegistryService', 'ImageService', 'HttpRequestHelper', 'Authentication', +function ($q, $scope, $state, $transition$, $filter, $async, Commit, ContainerHelper, ContainerService, ImageHelper, NetworkService, Notifications, ModalService, ResourceControlService, RegistryService, ImageService, HttpRequestHelper, Authentication) { $scope.activityTime = 0; $scope.portBindings = []; $scope.config = { - Image: '', - Registry: '' + RegistryModel: new PorImageRegistryModel(), + commitInProgress: false }; $scope.state = { @@ -149,20 +150,23 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co }); }; - $scope.commit = function () { - var image = $scope.config.Image; - $scope.config.Image = ''; - var registry = $scope.config.Registry; - var imageConfig = ImageHelper.createImageConfigForCommit(image, registry.URL); - Commit.commitContainer({id: $transition$.params().id, tag: imageConfig.tag, repo: imageConfig.repo}, function () { - update(); + async function commitContainerAsync() { + $scope.config.commitInProgress = true; + const registryModel = $scope.config.RegistryModel; + const imageConfig = ImageHelper.createImageConfigForContainer(registryModel); + try { + await Commit.commitContainer({id: $transition$.params().id, repo: imageConfig.fromImage}).$promise; Notifications.success('Image created', $transition$.params().id); - }, function (e) { - update(); - Notifications.error('Failure', e, 'Unable to create image'); - }); - }; + $state.reload(); + } catch (err) { + Notifications.error('Failure', err, 'Unable to create image'); + $scope.config.commitInProgress = false; + } + } + $scope.commit = function () { + return $async(commitContainerAsync); + }; $scope.confirmRemove = function () { var title = 'You are about to remove a container.'; @@ -225,15 +229,12 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co if (!pullImage) { return $q.when(); } - return getRegistry().then(function pullImage(containerRegistery) { - return ImageService.pullImage(container.Config.Image, containerRegistery, true); + return RegistryService.retrievePorRegistryModelFromRepository(container.Config.Image) + .then(function pullImage(registryModel) { + return ImageService.pullImage(registryModel, true); }); } - function getRegistry() { - return RegistryService.retrieveRegistryFromRepository(container.Config.Image); - } - function setMainNetworkAndCreateContainer() { var networks = config.NetworkingConfig.EndpointsConfig; var networksNames = Object.keys(networks); diff --git a/app/docker/views/images/edit/image.html b/app/docker/views/images/edit/image.html index 8b6641548..5dcc3ba26 100644 --- a/app/docker/views/images/edit/image.html +++ b/app/docker/views/images/edit/image.html @@ -64,9 +64,8 @@
@@ -78,7 +77,7 @@
- +
diff --git a/app/docker/views/images/edit/imageController.js b/app/docker/views/images/edit/imageController.js index f090b6fdc..c13cd72ef 100644 --- a/app/docker/views/images/edit/imageController.js +++ b/app/docker/views/images/edit/imageController.js @@ -1,11 +1,11 @@ import _ from 'lodash-es'; +import { PorImageRegistryModel } from 'Docker/models/porImageRegistry'; angular.module('portainer.docker') -.controller('ImageController', ['$q', '$scope', '$transition$', '$state', '$timeout', 'ImageService', 'RegistryService', 'Notifications', 'HttpRequestHelper', 'ModalService', 'FileSaver', 'Blob', -function ($q, $scope, $transition$, $state, $timeout, ImageService, RegistryService, Notifications, HttpRequestHelper, ModalService, FileSaver, Blob) { +.controller('ImageController', ['$q', '$scope', '$transition$', '$state', '$timeout', 'ImageService', 'ImageHelper', 'RegistryService', 'Notifications', 'HttpRequestHelper', 'ModalService', 'FileSaver', 'Blob', +function ($q, $scope, $transition$, $state, $timeout, ImageService, ImageHelper, RegistryService, Notifications, HttpRequestHelper, ModalService, FileSaver, Blob) { $scope.formValues = { - Image: '', - Registry: '' + RegistryModel: new PorImageRegistryModel() }; $scope.state = { @@ -27,10 +27,11 @@ function ($q, $scope, $transition$, $state, $timeout, ImageService, RegistryServ }; $scope.tagImage = function() { - var image = $scope.formValues.Image; - var registry = $scope.formValues.Registry; + const registryModel = $scope.formValues.RegistryModel; - ImageService.tagImage($transition$.params().id, image, registry.URL) + const image = ImageHelper.createImageConfigForContainer(registryModel); + + ImageService.tagImage($transition$.params().id, image.fromImage) .then(function success() { Notifications.success('Image successfully tagged'); $state.go('docker.images.image', {id: $transition$.params().id}, {reload: true}); @@ -42,10 +43,9 @@ function ($q, $scope, $transition$, $state, $timeout, ImageService, RegistryServ $scope.pushTag = function(repository) { $('#uploadResourceHint').show(); - RegistryService.retrieveRegistryFromRepository(repository) - .then(function success(data) { - var registry = data; - return ImageService.pushImage(repository, registry); + RegistryService.retrievePorRegistryModelFromRepository(repository) + .then(function success(registryModel) { + return ImageService.pushImage(registryModel); }) .then(function success() { Notifications.success('Image successfully pushed', repository); @@ -60,10 +60,9 @@ function ($q, $scope, $transition$, $state, $timeout, ImageService, RegistryServ $scope.pullTag = function(repository) { $('#downloadResourceHint').show(); - RegistryService.retrieveRegistryFromRepository(repository) - .then(function success(data) { - var registry = data; - return ImageService.pullImage(repository, registry, false); + RegistryService.retrievePorRegistryModelFromRepository(repository) + .then(function success(registryModel) { + return ImageService.pullImage(registryModel, false); }) .then(function success() { Notifications.success('Image successfully pulled', repository); diff --git a/app/docker/views/images/images.html b/app/docker/views/images/images.html index d1cfba39a..d8d82e043 100644 --- a/app/docker/views/images/images.html +++ b/app/docker/views/images/images.html @@ -16,18 +16,12 @@
- -
-
- Note: if you don't specify the tag in the image name, latest will be used. -
-
-
Deployment @@ -40,7 +34,7 @@
- diff --git a/app/docker/views/images/imagesController.js b/app/docker/views/images/imagesController.js index 8369d0e67..aed6f8cf6 100644 --- a/app/docker/views/images/imagesController.js +++ b/app/docker/views/images/imagesController.js @@ -1,4 +1,5 @@ import _ from 'lodash-es'; +import { PorImageRegistryModel } from 'Docker/models/porImageRegistry'; angular.module('portainer.docker') .controller('ImagesController', ['$scope', '$state', 'ImageService', 'Notifications', 'ModalService', 'HttpRequestHelper', 'FileSaver', 'Blob', 'EndpointProvider', @@ -9,22 +10,20 @@ function ($scope, $state, ImageService, Notifications, ModalService, HttpRequest }; $scope.formValues = { - Image: '', - Registry: '', + RegistryModel: new PorImageRegistryModel(), NodeName: null }; $scope.pullImage = function() { - var image = $scope.formValues.Image; - var registry = $scope.formValues.Registry; + const registryModel = $scope.formValues.RegistryModel; var nodeName = $scope.formValues.NodeName; HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); $scope.state.actionInProgress = true; - ImageService.pullImage(image, registry, false) + ImageService.pullImage(registryModel, false) .then(function success() { - Notifications.success('Image successfully pulled', image); + Notifications.success('Image successfully pulled', registryModel.Image); $state.reload(); }) .catch(function error(err) { diff --git a/app/docker/views/services/create/createServiceController.js b/app/docker/views/services/create/createServiceController.js index 17ed83506..477fb6f65 100644 --- a/app/docker/views/services/create/createServiceController.js +++ b/app/docker/views/services/create/createServiceController.js @@ -1,5 +1,6 @@ import _ from 'lodash-es'; import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel'; +import { PorImageRegistryModel } from 'Docker/models/porImageRegistry'; require('./includes/update-restart.html') require('./includes/secret.html') @@ -12,8 +13,7 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C $scope.formValues = { Name: '', - Image: '', - Registry: {}, + RegistryModel: new PorImageRegistryModel(), Mode: 'replicated', Replicas: 1, Command: '', @@ -160,8 +160,8 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C }; function prepareImageConfig(config, input) { - var imageConfig = ImageHelper.createImageConfigForContainer(input.Image, input.Registry.URL); - config.TaskTemplate.ContainerSpec.Image = imageConfig.fromImage + ':' + imageConfig.tag; + var imageConfig = ImageHelper.createImageConfigForContainer(input.RegistryModel); + config.TaskTemplate.ContainerSpec.Image = imageConfig.fromImage; } function preparePortsConfig(config, input) { @@ -427,9 +427,8 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C } function createNewService(config, accessControlData) { - - var registry = $scope.formValues.Registry; - var authenticationDetails = registry.Authentication ? RegistryService.encodedCredentials(registry) : ''; + const registryModel = $scope.formValues.RegistryModel; + var authenticationDetails = registryModel.Registry.Authentication ? RegistryService.encodedCredentials(registryModel.Registry) : ''; HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails); Service.create(config).$promise diff --git a/app/docker/views/services/create/createservice.html b/app/docker/views/services/create/createservice.html index 9c0e98e13..458148975 100644 --- a/app/docker/views/services/create/createservice.html +++ b/app/docker/views/services/create/createservice.html @@ -23,10 +23,9 @@
@@ -126,7 +125,7 @@
- diff --git a/app/docker/views/services/edit/includes/image.html b/app/docker/views/services/edit/includes/image.html new file mode 100644 index 000000000..8aa91c9fd --- /dev/null +++ b/app/docker/views/services/edit/includes/image.html @@ -0,0 +1,32 @@ +
+ + + + + + + + + +

Image modification is disabled while service is updating.

+
+ + + +
+
diff --git a/app/docker/views/services/edit/service.html b/app/docker/views/services/edit/service.html index 13ef42d57..7f0737309 100644 --- a/app/docker/views/services/edit/service.html +++ b/app/docker/views/services/edit/service.html @@ -66,10 +66,7 @@ Image - - - + {{ service.Image }} @@ -118,12 +115,12 @@