feat(services): rollback service capability (#3057)
* feat(services): rollback service capability * refactor(services): notification reword Co-Authored-By: William <william.conquest@portainer.io> * refactor(services): remove TODO comment + add note on rollback capability * fix(services): service update rpc error version out of sync * feat(services): confirmation modal on rollback * feat(services): rpc error no previous spec messagepull/3148/head
parent
ec19faaa24
commit
52704e681b
|
@ -14,19 +14,13 @@ function ServiceFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, Htt
|
|||
method: 'POST', params: {action: 'create'},
|
||||
headers: {
|
||||
'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader,
|
||||
// TODO: This is a temporary work-around that allows us to leverage digest pinning on
|
||||
// the Docker daemon side. It has been moved client-side since Docker API version > 1.29.
|
||||
// We should introduce digest pinning in Portainer as well.
|
||||
'version': '1.29'
|
||||
},
|
||||
ignoreLoadingBar: true
|
||||
},
|
||||
update: {
|
||||
method: 'POST', params: { id: '@id', action: 'update', version: '@version' },
|
||||
method: 'POST', params: { id: '@id', action: 'update', version: '@version', rollback: '@rollback' },
|
||||
headers: {
|
||||
// TODO: This is a temporary work-around that allows us to leverage digest pinning on
|
||||
// the Docker daemon side. It has been moved client-side since Docker API version > 1.29.
|
||||
// We should introduce digest pinning in Portainer as well.
|
||||
'version': '1.29'
|
||||
}
|
||||
},
|
||||
|
|
|
@ -58,8 +58,17 @@ function ServiceServiceFactory($q, Service, ServiceHelper, TaskService, Resource
|
|||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.update = function(service, config) {
|
||||
return Service.update({ id: service.Id, version: service.Version }, config).$promise;
|
||||
service.update = function(serv, config, rollback) {
|
||||
return service.service(serv.Id).then((data) => {
|
||||
const params = {
|
||||
id: serv.Id,
|
||||
version: data.Version
|
||||
};
|
||||
if (rollback) {
|
||||
params.rollback = rollback
|
||||
}
|
||||
return Service.update(params, config).$promise;
|
||||
});
|
||||
};
|
||||
|
||||
service.logs = function(id, stdout, stderr, timestamps, since, tail) {
|
||||
|
|
|
@ -91,11 +91,18 @@
|
|||
</tr>
|
||||
<tr authorization="DockerServiceLogs, DockerServiceUpdate, DockerServiceDelete">
|
||||
<td colspan="2">
|
||||
<p class="small text-muted">
|
||||
Note: you can only rollback one level of changes. Clicking the rollback button without making a new change will undo your previous rollback
|
||||
<p>
|
||||
<a authorization="DockerServiceLogs" ng-if="applicationState.endpoint.apiVersion >= 1.30" class="btn btn-primary btn-sm" type="button" ui-sref="docker.services.service.logs({id: service.Id})"><i class="fa fa-file-alt space-right" aria-hidden="true"></i>Service logs</a>
|
||||
<button authorization="DockerServiceUpdate" type="button" class="btn btn-primary btn-sm" ng-disabled="state.updateInProgress || isUpdating" ng-click="forceUpdateService(service)" button-spinner="state.updateInProgress" ng-if="applicationState.endpoint.apiVersion >= 1.25">
|
||||
<span ng-hide="state.updateInProgress"><i class="fa fa-sync space-right" aria-hidden="true"></i>Update the service</span>
|
||||
<span ng-show="state.updateInProgress">Update in progress...</span>
|
||||
</button>
|
||||
<button authorization="DockerServiceUpdate" type="button" class="btn btn-primary btn-sm" ng-disabled="state.rollbackInProgress || isUpdating" ng-click="rollbackService(service)" button-spinner="state.rollbackInProgress" ng-if="applicationState.endpoint.apiVersion >= 1.25">
|
||||
<span ng-hide="state.rollbackInProgress"><i class="fa fa-undo space-right" aria-hidden="true"></i>Rollback the service</span>
|
||||
<span ng-show="state.rollbackInProgress">Rollback in progress...</span>
|
||||
</button>
|
||||
<button authorization="DockerServiceDelete" type="button" class="btn btn-danger btn-sm" ng-disabled="state.deletionInProgress || isUpdating" ng-click="removeService()" button-spinner="state.deletionInProgress">
|
||||
<span ng-hide="state.deletionInProgress"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete the service</span>
|
||||
<span ng-show="state.deletionInProgress">Deletion in progress...</span>
|
||||
|
|
|
@ -22,7 +22,8 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
|||
|
||||
$scope.state = {
|
||||
updateInProgress: false,
|
||||
deletionInProgress: false
|
||||
deletionInProgress: false,
|
||||
rollbackInProgress: false,
|
||||
};
|
||||
|
||||
$scope.tasks = [];
|
||||
|
@ -281,7 +282,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
|||
return hasChanges;
|
||||
};
|
||||
|
||||
$scope.updateService = function updateService(service) {
|
||||
function buildChanges(service) {
|
||||
var config = ServiceHelper.serviceToConfig(service.Model);
|
||||
config.Name = service.Name;
|
||||
config.Labels = LabelHelper.fromKeyValueToLabelHash(service.ServiceLabels);
|
||||
|
@ -361,8 +362,55 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
|||
Mode: (config.EndpointSpec && config.EndpointSpec.Mode) || 'vip',
|
||||
Ports: service.Ports
|
||||
};
|
||||
return service, config;
|
||||
}
|
||||
|
||||
Service.update({ id: service.Id, version: service.Version }, config, function (data) {
|
||||
function rollbackService(service) {
|
||||
$scope.state.rollbackInProgress = true;
|
||||
let config = {};
|
||||
service, config = buildChanges(service);
|
||||
ServiceService.update(service, config, 'previous')
|
||||
.then(function (data) {
|
||||
if (data.message && data.message.match(/^rpc error:/)) {
|
||||
Notifications.error(data.message, 'Error');
|
||||
} else {
|
||||
Notifications.success('Success', 'Service successfully rolled back');
|
||||
$scope.cancelChanges({});
|
||||
initView();
|
||||
}
|
||||
}).catch(function (e) {
|
||||
if (e.data.message && e.data.message.includes('does not have a previous spec')) {
|
||||
Notifications.error('Failure', { message: 'No previous config to rollback to.' });
|
||||
} else {
|
||||
Notifications.error('Failure', e, 'Unable to rollback service');
|
||||
}
|
||||
}).finally(function () {
|
||||
$scope.state.rollbackInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.rollbackService = function(service) {
|
||||
ModalService.confirm({
|
||||
title: 'Rollback service',
|
||||
message: 'Are you sure you want to rollback?',
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Yes',
|
||||
className: 'btn-danger'
|
||||
}
|
||||
},
|
||||
callback: function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
rollbackService(service);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.updateService = function updateService(service) {
|
||||
let config = {};
|
||||
service, config = buildChanges(service);
|
||||
ServiceService.update(service, config)
|
||||
.then(function (data) {
|
||||
if (data.message && data.message.match(/^rpc error:/)) {
|
||||
Notifications.error(data.message, 'Error');
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue