1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-08 07:15:23 +02:00

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 97ec2ddd62.

* 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
This commit is contained in:
xAt0mZ 2019-11-27 23:36:39 +01:00 committed by Anthony Lapenna
parent 61c38534a7
commit e19bc8abc7
32 changed files with 525 additions and 349 deletions

View file

@ -1,58 +1,6 @@
angular.module('portainer.app').component('templateForm', {
templateUrl: './templateForm.html',
controller: function() {
this.state = {
collapseTemplate: false,
collapseContainer: false,
collapseStack: false,
collapseEnv: false
};
this.addPortBinding = function() {
this.model.Ports.push({ containerPort: '', protocol: 'tcp' });
};
this.removePortBinding = function(index) {
this.model.Ports.splice(index, 1);
};
this.addVolume = function () {
this.model.Volumes.push({ container: '', bind: '', readonly: false, type: 'auto' });
};
this.removeVolume = function(index) {
this.model.Volumes.splice(index, 1);
};
this.addLabel = function () {
this.model.Labels.push({ name: '', value: ''});
};
this.removeLabel = function(index) {
this.model.Labels.splice(index, 1);
};
this.addEnvVar = function() {
this.model.Env.push({ type: 1, name: '', label: '', description: '', default: '', preset: true, select: [] });
};
this.removeEnvVar = function(index) {
this.model.Env.splice(index, 1);
};
this.addEnvVarValue = function(env) {
env.select = env.select || [];
env.select.push({ name: '', value: '' });
};
this.removeEnvVarValue = function(env, index) {
env.select.splice(index, 1);
};
this.changeEnvVarType = function(env) {
env.preset = env.type === 1;
};
},
controller: 'TemplateFormController',
bindings: {
model: '=',
categories: '<',

View file

@ -203,10 +203,9 @@
<!-- container-details -->
<div uib-collapse="$ctrl.state.collapseContainer">
<por-image-registry
image="$ctrl.model.Image"
registry="$ctrl.model.Registry"
model="$ctrl.model.RegistryModel"
auto-complete="true"
label-class="col-sm-2" input-class="col-sm-10 col-md-4"
label-class="col-sm-1" input-class="col-sm-11"
></por-image-registry>
<!-- command -->
<div class="form-group">

View file

@ -0,0 +1,54 @@
angular.module('portainer.app')
.controller('TemplateFormController', [function() {
this.state = {
collapseTemplate: false,
collapseContainer: false,
collapseStack: false,
collapseEnv: false
};
this.addPortBinding = function() {
this.model.Ports.push({ containerPort: '', protocol: 'tcp' });
};
this.removePortBinding = function(index) {
this.model.Ports.splice(index, 1);
};
this.addVolume = function () {
this.model.Volumes.push({ container: '', bind: '', readonly: false, type: 'auto' });
};
this.removeVolume = function(index) {
this.model.Volumes.splice(index, 1);
};
this.addLabel = function () {
this.model.Labels.push({ name: '', value: ''});
};
this.removeLabel = function(index) {
this.model.Labels.splice(index, 1);
};
this.addEnvVar = function() {
this.model.Env.push({ type: 1, name: '', label: '', description: '', default: '', preset: true, select: [] });
};
this.removeEnvVar = function(index) {
this.model.Env.splice(index, 1);
};
this.addEnvVarValue = function(env) {
env.select = env.select || [];
env.select.push({ name: '', value: '' });
};
this.removeEnvVarValue = function(env, index) {
env.select.splice(index, 1);
};
this.changeEnvVarType = function(env) {
env.preset = env.type === 1;
};
}]);

View file

@ -1,18 +0,0 @@
angular.module('portainer.app')
.factory('RegistryHelper', [function RegistryHelperFactory() {
'use strict';
var helper = {};
helper.getRegistryByURL = function(registries, url) {
for (var i = 0; i < registries.length; i++) {
if (registries[i].URL === url) {
return registries[i];
}
}
return null;
};
return helper;
}]);

View file

@ -59,7 +59,8 @@ export function RegistryCreateRequest(model) {
if (model.Type === RegistryTypes.GITLAB) {
this.Gitlab = {
ProjectId: model.Gitlab.ProjectId,
InstanceURL: model.Gitlab.InstanceURL
InstanceURL: model.Gitlab.InstanceURL,
ProjectPath: model.Gitlab.ProjectPath
}
}
}

View file

@ -1,17 +1,17 @@
import _ from 'lodash-es';
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
export function TemplateDefaultModel() {
this.Type = 1;
this.AdministratorOnly = false;
this.Title = '';
this.Image = '';
this.Description = '';
this.Volumes = [];
this.Ports = [];
this.Env = [];
this.Labels = [];
this.RestartPolicy = 'always';
this.Registry = {};
this.RegistryModel = new PorImageRegistryModel();
}
export function TemplateCreateRequest(model) {
@ -24,8 +24,8 @@ export function TemplateCreateRequest(model) {
this.Categories = model.Categories;
this.Platform = model.Platform;
this.Logo = model.Logo;
this.Image = model.Image;
this.Registry = model.Registry.URL;
this.Image = model.RegistryModel.Image;
this.Registry = model.RegistryModel.Registry.URL;
this.Command = model.Command;
this.Network = model.Network && model.Network.Name;
this.Privileged = model.Privileged;
@ -66,9 +66,9 @@ export function TemplateViewModel(data) {
this.Logo = data.logo;
this.Repository = data.repository;
this.Hostname = data.hostname;
this.Registry = data.registry ? { URL: data.registry } : {};
this.Image = data.image;
this.Registry = data.registry ? data.registry : '';
this.RegistryModel = new PorImageRegistryModel();
this.RegistryModel.Image = data.image;
this.RegistryModel.Registry.URL = data.registry || '';
this.Command = data.command ? data.command : '';
this.Network = data.network ? data.network : '';
this.Privileged = data.privileged ? data.privileged : false;

View file

@ -1,8 +1,11 @@
import _ from 'lodash-es';
import { RegistryViewModel, RegistryCreateRequest } from '../../models/registry';
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
import { RegistryTypes } from 'Extensions/registry-management/models/registryTypes';
angular.module('portainer.app')
.factory('RegistryService', ['$q', 'Registries', 'DockerHubService', 'RegistryHelper', 'ImageHelper', 'FileUploadService', function RegistryServiceFactory($q, Registries, DockerHubService, RegistryHelper, ImageHelper, FileUploadService) {
.factory('RegistryService', ['$q', '$async', 'Registries', 'DockerHubService', 'ImageHelper', 'FileUploadService',
function RegistryServiceFactory($q, $async, Registries, DockerHubService, ImageHelper, FileUploadService) {
'use strict';
var service = {};
@ -71,6 +74,7 @@ angular.module('portainer.app')
_.forEach(projects, (p) => {
const m = model;
m.Name = p.PathWithNamespace;
m.Gitlab.ProjectPath = _.toLower(p.PathWithNamespace);
m.Gitlab.ProjectId = p.Id;
m.Password = m.Token;
const payload = new RegistryCreateRequest(m);
@ -79,23 +83,52 @@ angular.module('portainer.app')
return $q.all(promises);
};
service.retrieveRegistryFromRepository = function(repository) {
var deferred = $q.defer();
service.retrievePorRegistryModelFromRepositoryWithRegistries = retrievePorRegistryModelFromRepositoryWithRegistries;
var imageDetails = ImageHelper.extractImageAndRegistryFromRepository(repository);
$q.when(imageDetails.registry ? service.registries() : DockerHubService.dockerhub())
.then(function success(data) {
var registry = data;
if (imageDetails.registry) {
registry = RegistryHelper.getRegistryByURL(data, imageDetails.registry);
function getURL(reg) {
let url = reg.URL;
if (reg.Type === RegistryTypes.GITLAB) {
url = reg.URL + '/' + reg.Gitlab.ProjectPath;
}
return url;
}
function retrievePorRegistryModelFromRepositoryWithRegistries(repository, registries, dockerhub) {
const model = new PorImageRegistryModel();
const registry = _.find(registries, (reg) => _.includes(repository, getURL(reg)));
if (registry) {
const url = getURL(registry);
const lastIndex = repository.lastIndexOf(url) + url.length;
let image = repository.substring(lastIndex);
if (!_.startsWith(image, ':')) {
image = image.substring(1);
}
deferred.resolve(registry);
})
.catch(function error(err) {
deferred.reject({ msg: 'Unable to retrieve the registry associated to the repository', err: err });
});
model.Registry = registry;
model.Image = image;
} else {
if (ImageHelper.imageContainsURL(repository)) {
model.UseRegistry = false;
}
model.Registry = dockerhub;
model.Image = repository;
}
return model;
}
return deferred.promise;
async function retrievePorRegistryModelFromRepositoryAsync(repository) {
try {
let [registries, dockerhub] = await Promise.all([
service.registries(),
DockerHubService.dockerhub()
]);
return retrievePorRegistryModelFromRepositoryWithRegistries(repository, registries, dockerhub);
} catch (err) {
throw { msg: 'Unable to retrieve the registry associated to the repository', err: err }
}
}
service.retrievePorRegistryModelFromRepository = function(repository) {
return $async(retrievePorRegistryModelFromRepositoryAsync, repository)
};
return service;

View file

@ -5,18 +5,26 @@ import {
} from '../../models/template';
angular.module('portainer.app')
.factory('TemplateService', ['$q', 'Templates', 'TemplateHelper', 'ImageHelper', 'ContainerHelper',
function TemplateServiceFactory($q, Templates, TemplateHelper, ImageHelper, ContainerHelper) {
.factory('TemplateService', ['$q', 'Templates', 'TemplateHelper', 'RegistryService', 'DockerHubService', 'ImageHelper', 'ContainerHelper',
function TemplateServiceFactory($q, Templates, TemplateHelper, RegistryService, DockerHubService, ImageHelper, ContainerHelper) {
'use strict';
var service = {};
service.templates = function() {
var deferred = $q.defer();
Templates.query().$promise
$q.all({
templates: Templates.query().$promise,
registries: RegistryService.registries(),
dockerhub: DockerHubService.dockerhub()
})
.then(function success(data) {
var templates = data.map(function (item) {
return new TemplateViewModel(item);
const templates = data.templates.map(function (item) {
const res = new TemplateViewModel(item);
const registry = RegistryService.retrievePorRegistryModelFromRepositoryWithRegistries(res.RegistryModel.Registry.URL, data.registries, data.dockerhub);
registry.Image = res.RegistryModel.Image;
res.RegistryModel = registry;
return res;
});
deferred.resolve(templates);
})
@ -29,10 +37,15 @@ function TemplateServiceFactory($q, Templates, TemplateHelper, ImageHelper, Cont
service.template = function(id) {
var deferred = $q.defer();
let template;
Templates.get({ id: id }).$promise
.then(function success(data) {
var template = new TemplateViewModel(data);
template = new TemplateViewModel(data);
return RegistryService.retrievePorRegistryModelFromRepository(template.RegistryModel.Registry.URL);
})
.then((registry) => {
registry.Image = template.RegistryModel.Image;
template.RegistryModel = registry;
deferred.resolve(template);
})
.catch(function error(err) {
@ -58,13 +71,13 @@ function TemplateServiceFactory($q, Templates, TemplateHelper, ImageHelper, Cont
};
service.createTemplateConfiguration = function(template, containerName, network) {
var imageConfiguration = ImageHelper.createImageConfigForContainer(template.Image, template.Registry);
var containerConfiguration = service.createContainerConfiguration(template, containerName, network);
containerConfiguration.Image = imageConfiguration.fromImage + ':' + imageConfiguration.tag;
var imageConfiguration = ImageHelper.createImageConfigForContainer(template.RegistryModel);
var containerConfiguration = createContainerConfiguration(template, containerName, network);
containerConfiguration.Image = imageConfiguration.fromImage;
return containerConfiguration;
};
service.createContainerConfiguration = function(template, containerName, network) {
function createContainerConfiguration(template, containerName, network) {
var configuration = TemplateHelper.getDefaultContainerConfiguration();
configuration.HostConfig.NetworkMode = network.Name;
configuration.HostConfig.Privileged = template.Privileged;
@ -72,7 +85,6 @@ function TemplateServiceFactory($q, Templates, TemplateHelper, ImageHelper, Cont
configuration.HostConfig.ExtraHosts = template.Hosts ? template.Hosts : [];
configuration.name = containerName;
configuration.Hostname = template.Hostname;
configuration.Image = template.Image;
configuration.Env = TemplateHelper.EnvToStringArray(template.Env);
configuration.Cmd = ContainerHelper.commandStringToArray(template.Command);
var portConfiguration = TemplateHelper.portArrayToPortConfiguration(template.Ports);
@ -83,7 +95,7 @@ function TemplateServiceFactory($q, Templates, TemplateHelper, ImageHelper, Cont
configuration.Tty = consoleConfiguration.tty;
configuration.Labels = TemplateHelper.updateContainerConfigurationWithLabels(template.Labels);
return configuration;
};
}
service.updateContainerConfigurationWithVolumes = function(configuration, template, generatedVolumesPile) {
var volumes = template.Volumes;

View file

@ -73,7 +73,7 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima
generatedVolumeIds.push(volumeId);
});
TemplateService.updateContainerConfigurationWithVolumes(templateConfiguration, template, data);
return ImageService.pullImage(template.Image, { URL: template.Registry }, true);
return ImageService.pullImage(template.RegistryModel, true);
})
.then(function success() {
return ContainerService.createAndStartContainer(templateConfiguration);
@ -112,7 +112,7 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima
var endpointId = EndpointProvider.endpointID();
StackService.createComposeStackFromGitRepository(stackName, repositoryOptions, template.Env, endpointId)
.then(function success(data) {
const resourceControl = data.Portainer.ResourceControl;
const resourceControl = data.ResourceControl;
return ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl);
})
.then(function success() {
@ -150,7 +150,7 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima
var endpointId = EndpointProvider.endpointID();
StackService.createSwarmStackFromGitRepository(stackName, repositoryOptions, env, endpointId)
.then(function success(data) {
const resourceControl = data.Portainer.ResourceControl;
const resourceControl = data.ResourceControl;
return ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl);
})
.then(function success() {
@ -158,7 +158,7 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima
$state.go('portainer.stacks');
})
.catch(function error(err) {
Notifications.warning('Deployment error', err.err.data.err);
Notifications.warning('Deployment error', err.data.err);
})
.finally(function final() {
$scope.state.actionInProgress = false;