mirror of https://github.com/portainer/portainer
feat(stack/swarm): add prune option for swarm stack redeployment [EE-2678] (#7025)
parent
d7306fb22e
commit
7275d23e4b
|
@ -790,6 +790,7 @@
|
||||||
"IsComposeFormat": false,
|
"IsComposeFormat": false,
|
||||||
"Name": "alpine",
|
"Name": "alpine",
|
||||||
"Namespace": "",
|
"Namespace": "",
|
||||||
|
"Option": null,
|
||||||
"ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/2",
|
"ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/2",
|
||||||
"ResourceControl": null,
|
"ResourceControl": null,
|
||||||
"Status": 1,
|
"Status": 1,
|
||||||
|
@ -812,6 +813,7 @@
|
||||||
"IsComposeFormat": false,
|
"IsComposeFormat": false,
|
||||||
"Name": "redis",
|
"Name": "redis",
|
||||||
"Namespace": "",
|
"Namespace": "",
|
||||||
|
"Option": null,
|
||||||
"ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/5",
|
"ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/5",
|
||||||
"ResourceControl": null,
|
"ResourceControl": null,
|
||||||
"Status": 1,
|
"Status": 1,
|
||||||
|
@ -834,6 +836,7 @@
|
||||||
"IsComposeFormat": false,
|
"IsComposeFormat": false,
|
||||||
"Name": "nginx",
|
"Name": "nginx",
|
||||||
"Namespace": "",
|
"Namespace": "",
|
||||||
|
"Option": null,
|
||||||
"ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/6",
|
"ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/6",
|
||||||
"ResourceControl": null,
|
"ResourceControl": null,
|
||||||
"Status": 1,
|
"Status": 1,
|
||||||
|
|
|
@ -11,7 +11,7 @@ require (
|
||||||
github.com/coreos/go-semver v0.3.0
|
github.com/coreos/go-semver v0.3.0
|
||||||
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9
|
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9
|
||||||
github.com/docker/cli v20.10.9+incompatible
|
github.com/docker/cli v20.10.9+incompatible
|
||||||
github.com/docker/docker v20.10.9+incompatible
|
github.com/docker/docker v20.10.16+incompatible
|
||||||
github.com/fvbommel/sortorder v1.0.2
|
github.com/fvbommel/sortorder v1.0.2
|
||||||
github.com/fxamacker/cbor/v2 v2.3.0
|
github.com/fxamacker/cbor/v2 v2.3.0
|
||||||
github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814
|
github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814
|
||||||
|
|
|
@ -332,6 +332,8 @@ github.com/docker/distribution v2.8.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc
|
||||||
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker v20.10.9+incompatible h1:JlsVnETOjM2RLQa0Cc1XCIspUdXW3Zenq9P54uXBm6k=
|
github.com/docker/docker v20.10.9+incompatible h1:JlsVnETOjM2RLQa0Cc1XCIspUdXW3Zenq9P54uXBm6k=
|
||||||
github.com/docker/docker v20.10.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v20.10.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/docker v20.10.16+incompatible h1:2Db6ZR/+FUR3hqPMwnogOPHFn405crbpxvWzKovETOQ=
|
||||||
|
github.com/docker/docker v20.10.16+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
|
@ -807,8 +809,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/portainer/docker-compose-wrapper v0.0.0-20220526210722-e1574867298e h1:gW1Ooaj7RZ9YkwHxesnNEyOB5nUD71FlZ7cdb5h63vw=
|
|
||||||
github.com/portainer/docker-compose-wrapper v0.0.0-20220526210722-e1574867298e/go.mod h1:WxDlJWZxCnicdLCPnLNEv7/gRhjeIVuCGmsv+iOPH3c=
|
|
||||||
github.com/portainer/docker-compose-wrapper v0.0.0-20220531190153-c597b853e410 h1:LjxLd8UGR8ae73ov/vLrt/0jedj/nh98XnONkr8DJj8=
|
github.com/portainer/docker-compose-wrapper v0.0.0-20220531190153-c597b853e410 h1:LjxLd8UGR8ae73ov/vLrt/0jedj/nh98XnONkr8DJj8=
|
||||||
github.com/portainer/docker-compose-wrapper v0.0.0-20220531190153-c597b853e410/go.mod h1:WxDlJWZxCnicdLCPnLNEv7/gRhjeIVuCGmsv+iOPH3c=
|
github.com/portainer/docker-compose-wrapper v0.0.0-20220531190153-c597b853e410/go.mod h1:WxDlJWZxCnicdLCPnLNEv7/gRhjeIVuCGmsv+iOPH3c=
|
||||||
github.com/portainer/libcrypto v0.0.0-20210422035235-c652195c5c3a h1:qY8TbocN75n5PDl16o0uVr5MevtM5IhdwSelXEd4nFM=
|
github.com/portainer/libcrypto v0.0.0-20210422035235-c652195c5c3a h1:qY8TbocN75n5PDl16o0uVr5MevtM5IhdwSelXEd4nFM=
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
type stackGitUpdatePayload struct {
|
type stackGitUpdatePayload struct {
|
||||||
AutoUpdate *portainer.StackAutoUpdate
|
AutoUpdate *portainer.StackAutoUpdate
|
||||||
Env []portainer.Pair
|
Env []portainer.Pair
|
||||||
|
Prune bool
|
||||||
RepositoryReferenceName string
|
RepositoryReferenceName string
|
||||||
RepositoryAuthentication bool
|
RepositoryAuthentication bool
|
||||||
RepositoryUsername string
|
RepositoryUsername string
|
||||||
|
@ -131,6 +132,12 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
|
||||||
stack.UpdatedBy = user.Username
|
stack.UpdatedBy = user.Username
|
||||||
stack.UpdateDate = time.Now().Unix()
|
stack.UpdateDate = time.Now().Unix()
|
||||||
|
|
||||||
|
if stack.Type == portainer.DockerSwarmStack {
|
||||||
|
stack.Option = &portainer.StackOption{
|
||||||
|
Prune: payload.Prune,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if payload.RepositoryAuthentication {
|
if payload.RepositoryAuthentication {
|
||||||
password := payload.RepositoryPassword
|
password := payload.RepositoryPassword
|
||||||
if password == "" && stack.GitConfig != nil && stack.GitConfig.Authentication != nil {
|
if password == "" && stack.GitConfig != nil && stack.GitConfig.Authentication != nil {
|
||||||
|
|
|
@ -24,6 +24,7 @@ type stackGitRedployPayload struct {
|
||||||
RepositoryUsername string
|
RepositoryUsername string
|
||||||
RepositoryPassword string
|
RepositoryPassword string
|
||||||
Env []portainer.Pair
|
Env []portainer.Pair
|
||||||
|
Prune bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (payload *stackGitRedployPayload) Validate(r *http.Request) error {
|
func (payload *stackGitRedployPayload) Validate(r *http.Request) error {
|
||||||
|
@ -118,6 +119,11 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
||||||
|
|
||||||
stack.GitConfig.ReferenceName = payload.RepositoryReferenceName
|
stack.GitConfig.ReferenceName = payload.RepositoryReferenceName
|
||||||
stack.Env = payload.Env
|
stack.Env = payload.Env
|
||||||
|
if stack.Type == portainer.DockerSwarmStack {
|
||||||
|
stack.Option = &portainer.StackOption{
|
||||||
|
Prune: payload.Prune,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
backupProjectPath := fmt.Sprintf("%s-old", stack.ProjectPath)
|
backupProjectPath := fmt.Sprintf("%s-old", stack.ProjectPath)
|
||||||
err = filesystem.MoveDirectory(stack.ProjectPath, backupProjectPath)
|
err = filesystem.MoveDirectory(stack.ProjectPath, backupProjectPath)
|
||||||
|
@ -187,7 +193,11 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
||||||
func (handler *Handler) deployStack(r *http.Request, stack *portainer.Stack, endpoint *portainer.Endpoint) *httperror.HandlerError {
|
func (handler *Handler) deployStack(r *http.Request, stack *portainer.Stack, endpoint *portainer.Endpoint) *httperror.HandlerError {
|
||||||
switch stack.Type {
|
switch stack.Type {
|
||||||
case portainer.DockerSwarmStack:
|
case portainer.DockerSwarmStack:
|
||||||
config, httpErr := handler.createSwarmDeployConfig(r, stack, endpoint, false)
|
prune := false
|
||||||
|
if stack.Option != nil {
|
||||||
|
prune = stack.Option.Prune
|
||||||
|
}
|
||||||
|
config, httpErr := handler.createSwarmDeployConfig(r, stack, endpoint, prune)
|
||||||
if httpErr != nil {
|
if httpErr != nil {
|
||||||
return httpErr
|
return httpErr
|
||||||
}
|
}
|
||||||
|
|
|
@ -922,6 +922,8 @@ type (
|
||||||
AdditionalFiles []string `json:"AdditionalFiles"`
|
AdditionalFiles []string `json:"AdditionalFiles"`
|
||||||
// The auto update settings of a git stack
|
// The auto update settings of a git stack
|
||||||
AutoUpdate *StackAutoUpdate `json:"AutoUpdate"`
|
AutoUpdate *StackAutoUpdate `json:"AutoUpdate"`
|
||||||
|
// The stack deployment option
|
||||||
|
Option *StackOption `json:"Option"`
|
||||||
// The git config of this stack
|
// The git config of this stack
|
||||||
GitConfig *gittypes.RepoConfig
|
GitConfig *gittypes.RepoConfig
|
||||||
// Whether the stack is from a app template
|
// Whether the stack is from a app template
|
||||||
|
@ -942,6 +944,12 @@ type (
|
||||||
JobID string `example:"15"`
|
JobID string `example:"15"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StackOption represents the options for stack deployment
|
||||||
|
StackOption struct {
|
||||||
|
// Prune services that are no longer referenced
|
||||||
|
Prune bool `example:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
// StackID represents a stack identifier (it must be composed of Name + "_" + SwarmID to create a unique identifier)
|
// StackID represents a stack identifier (it must be composed of Name + "_" + SwarmID to create a unique identifier)
|
||||||
StackID int
|
StackID int
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,9 @@ class StackRedeployGitFormController {
|
||||||
RepositoryUsername: '',
|
RepositoryUsername: '',
|
||||||
RepositoryPassword: '',
|
RepositoryPassword: '',
|
||||||
Env: [],
|
Env: [],
|
||||||
|
Option: {
|
||||||
|
Prune: false,
|
||||||
|
},
|
||||||
// auto update
|
// auto update
|
||||||
AutoUpdate: {
|
AutoUpdate: {
|
||||||
RepositoryAutomaticUpdates: false,
|
RepositoryAutomaticUpdates: false,
|
||||||
|
@ -41,6 +44,7 @@ class StackRedeployGitFormController {
|
||||||
this.onChangeRef = this.onChangeRef.bind(this);
|
this.onChangeRef = this.onChangeRef.bind(this);
|
||||||
this.onChangeAutoUpdate = this.onChangeAutoUpdate.bind(this);
|
this.onChangeAutoUpdate = this.onChangeAutoUpdate.bind(this);
|
||||||
this.onChangeEnvVar = this.onChangeEnvVar.bind(this);
|
this.onChangeEnvVar = this.onChangeEnvVar.bind(this);
|
||||||
|
this.onChangeOption = this.onChangeOption.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildAnalyticsProperties() {
|
buildAnalyticsProperties() {
|
||||||
|
@ -88,6 +92,15 @@ class StackRedeployGitFormController {
|
||||||
this.onChange({ Env: value });
|
this.onChange({ Env: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChangeOption(values) {
|
||||||
|
this.onChange({
|
||||||
|
Option: {
|
||||||
|
...this.formValues.Option,
|
||||||
|
...values,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
const tplCrop =
|
const tplCrop =
|
||||||
'<div>Any changes to this stack or application made locally in Portainer will be overridden, which may cause service interruption.</div>' +
|
'<div>Any changes to this stack or application made locally in Portainer will be overridden, which may cause service interruption.</div>' +
|
||||||
|
@ -101,7 +114,13 @@ class StackRedeployGitFormController {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.state.redeployInProgress = true;
|
this.state.redeployInProgress = true;
|
||||||
await this.StackService.updateGit(this.stack.Id, this.stack.EndpointId, this.FormHelper.removeInvalidEnvVars(this.formValues.Env), false, this.formValues);
|
await this.StackService.updateGit(
|
||||||
|
this.stack.Id,
|
||||||
|
this.stack.EndpointId,
|
||||||
|
this.FormHelper.removeInvalidEnvVars(this.formValues.Env),
|
||||||
|
this.formValues.Option.Prune,
|
||||||
|
this.formValues
|
||||||
|
);
|
||||||
this.Notifications.success('Pulled and redeployed stack successfully');
|
this.Notifications.success('Pulled and redeployed stack successfully');
|
||||||
this.$state.reload();
|
this.$state.reload();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -148,6 +167,9 @@ class StackRedeployGitFormController {
|
||||||
$onInit() {
|
$onInit() {
|
||||||
this.formValues.RefName = this.model.ReferenceName;
|
this.formValues.RefName = this.model.ReferenceName;
|
||||||
this.formValues.Env = this.stack.Env;
|
this.formValues.Env = this.stack.Env;
|
||||||
|
if (this.stack.Option) {
|
||||||
|
this.formValues.Option = this.stack.Option;
|
||||||
|
}
|
||||||
|
|
||||||
// Init auto update
|
// Init auto update
|
||||||
if (this.stack.AutoUpdate && (this.stack.AutoUpdate.Interval || this.stack.AutoUpdate.Webhook)) {
|
if (this.stack.AutoUpdate && (this.stack.AutoUpdate.Interval || this.stack.AutoUpdate.Webhook)) {
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
explanation="These values will be used as substitutions in the stack file"
|
explanation="These values will be used as substitutions in the stack file"
|
||||||
on-change="($ctrl.onChangeEnvVar)"
|
on-change="($ctrl.onChangeEnvVar)"
|
||||||
></environment-variables-panel>
|
></environment-variables-panel>
|
||||||
|
<option-panel ng-model="$ctrl.formValues.Option" on-change="($ctrl.onChangeOption)"></option-panel>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-primary"
|
class="btn btn-sm btn-primary"
|
||||||
ng-click="$ctrl.submit()"
|
ng-click="$ctrl.submit()"
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import angular from 'angular';
|
||||||
|
|
||||||
|
import controller from './option-panel.controller.js';
|
||||||
|
|
||||||
|
angular.module('portainer.app').component('optionPanel', {
|
||||||
|
templateUrl: './option-panel.html',
|
||||||
|
controller,
|
||||||
|
bindings: {
|
||||||
|
ngModel: '<',
|
||||||
|
explanation: '@',
|
||||||
|
onChange: '<',
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,10 @@
|
||||||
|
export default class OptionPanelController {
|
||||||
|
/* @ngInject */
|
||||||
|
constructor() {
|
||||||
|
this.switchPruneService = this.switchPruneService.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
switchPruneService() {
|
||||||
|
this.onChange(this.ngModel);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
<ng-form class="form-horizontal" name="$ctrl.optionForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-12 form-section-title" style="margin-top: 10px; margin-left: 15px; width: 98%"> Options </div>
|
||||||
|
|
||||||
|
<!-- Prune service -->
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<label class="control-label text-left">
|
||||||
|
Prune services
|
||||||
|
<portainer-tooltip position="top" message="Prune services that are no longer referenced."></portainer-tooltip>
|
||||||
|
</label>
|
||||||
|
<label class="switch" style="margin-left: 20px"> <input type="checkbox" ng-model="$ctrl.ngModel.Prune" ng-change="$ctrl.switchPruneService()" /><i></i> </label>
|
||||||
|
</div>
|
||||||
|
<!-- !Prune service -->
|
||||||
|
</div>
|
||||||
|
</ng-form>
|
|
@ -7,6 +7,7 @@ export function StackViewModel(data) {
|
||||||
this.EndpointId = data.EndpointId;
|
this.EndpointId = data.EndpointId;
|
||||||
this.SwarmId = data.SwarmId;
|
this.SwarmId = data.SwarmId;
|
||||||
this.Env = data.Env ? data.Env : [];
|
this.Env = data.Env ? data.Env : [];
|
||||||
|
this.Option = data.Option;
|
||||||
this.IsComposeFormat = data.IsComposeFormat;
|
this.IsComposeFormat = data.IsComposeFormat;
|
||||||
if (data.ResourceControl && data.ResourceControl.Id !== 0) {
|
if (data.ResourceControl && data.ResourceControl.Id !== 0) {
|
||||||
this.ResourceControl = new ResourceControlViewModel(data.ResourceControl);
|
this.ResourceControl = new ResourceControlViewModel(data.ResourceControl);
|
||||||
|
@ -44,6 +45,7 @@ export function OrphanedStackViewModel(data) {
|
||||||
this.EndpointId = data.EndpointId;
|
this.EndpointId = data.EndpointId;
|
||||||
this.SwarmId = data.SwarmId;
|
this.SwarmId = data.SwarmId;
|
||||||
this.Env = data.Env ? data.Env : [];
|
this.Env = data.Env ? data.Env : [];
|
||||||
|
this.Option = data.Option;
|
||||||
if (data.ResourceControl && data.ResourceControl.Id !== 0) {
|
if (data.ResourceControl && data.ResourceControl.Id !== 0) {
|
||||||
this.ResourceControl = new ResourceControlViewModel(data.ResourceControl);
|
this.ResourceControl = new ResourceControlViewModel(data.ResourceControl);
|
||||||
}
|
}
|
||||||
|
|
|
@ -484,6 +484,7 @@ angular.module('portainer.app').factory('StackService', [
|
||||||
RepositoryAuthentication: gitConfig.RepositoryAuthentication,
|
RepositoryAuthentication: gitConfig.RepositoryAuthentication,
|
||||||
RepositoryUsername: gitConfig.RepositoryUsername,
|
RepositoryUsername: gitConfig.RepositoryUsername,
|
||||||
RepositoryPassword: gitConfig.RepositoryPassword,
|
RepositoryPassword: gitConfig.RepositoryPassword,
|
||||||
|
Prune: gitConfig.Option.Prune,
|
||||||
}
|
}
|
||||||
).$promise;
|
).$promise;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue