1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-05 05:45:22 +02:00

feat(edgestacks): support kubernetes edge stacks (#5276) [EE-393]

This commit is contained in:
Chaim Lev-Ari 2021-09-09 11:38:34 +03:00 committed by GitHub
parent 79ca51c92e
commit 5c8450c4c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 1466 additions and 521 deletions

View file

@ -1,4 +1,12 @@
<ui-select multiple ng-model="$ctrl.model" close-on-select="false" data-cy="edgeGroupCreate-edgeGroupsSelector">
<!-- on-select/on-remove are called with model because ui-select uses 2-way-binding -->
<ui-select
multiple
ng-model="$ctrl.model"
close-on-select="false"
on-select="$ctrl.onChange($ctrl.model)"
on-remove="$ctrl.onChange($ctrl.model)"
data-cy="edgeGroupCreate-edgeGroupsSelector"
>
<ui-select-match placeholder="Select one or multiple group(s)">
<span>
{{ $item.Name }}

View file

@ -3,7 +3,8 @@ import angular from 'angular';
angular.module('portainer.edge').component('edgeGroupsSelector', {
templateUrl: './edgeGroupsSelector.html',
bindings: {
model: '=',
model: '<',
items: '<',
onChange: '<',
},
});

View file

@ -0,0 +1,21 @@
export default class EdgeStackDeploymentTypeSelectorController {
/* @ngInject */
constructor() {
this.deploymentOptions = [
{ id: 'deployment_compose', icon: 'fab fa-docker', label: 'Compose', description: 'docker-compose format', value: 0 },
{
id: 'deployment_kube',
icon: 'fa fa-cubes',
label: 'Kubernetes',
description: 'Kubernetes manifest format',
value: 1,
disabled: () => {
return this.hasDockerEndpoint();
},
tooltip: () => {
return this.hasDockerEndpoint() ? 'Cannot use this option with Edge Docker endpoints' : '';
},
},
];
}
}

View file

@ -0,0 +1,4 @@
<div class="col-sm-12 form-section-title">
Deployment type
</div>
<box-selector radio-name="deploymentType" ng-model="$ctrl.value" options="$ctrl.deploymentOptions" on-change="($ctrl.onChange)"></box-selector>

View file

@ -0,0 +1,15 @@
import angular from 'angular';
import controller from './edge-stack-deployment-type-selector.controller.js';
export const edgeStackDeploymentTypeSelector = {
templateUrl: './edge-stack-deployment-type-selector.html',
controller,
bindings: {
value: '<',
onChange: '<',
hasDockerEndpoint: '<',
},
};
angular.module('portainer.edge').component('edgeStackDeploymentTypeSelector', edgeStackDeploymentTypeSelector);

View file

@ -4,52 +4,70 @@
</div>
<div class="form-group">
<div class="col-sm-12">
<edge-groups-selector model="$ctrl.model.EdgeGroups" items="$ctrl.edgeGroups"></edge-groups-selector>
<edge-groups-selector model="$ctrl.model.EdgeGroups" items="$ctrl.edgeGroups" on-change="($ctrl.onChangeGroups)"></edge-groups-selector>
</div>
</div>
<!-- web-editor -->
<div class="col-sm-12 form-section-title">
Web editor
</div>
<div class="form-group">
<span class="col-sm-12 text-muted small">
You can get more information about Compose file format in the
<a href="https://docs.docker.com/compose/compose-file/" target="_blank">
official documentation
</a>
.
</span>
</div>
<div class="form-group">
<div class="form-group" ng-if="!$ctrl.validateEndpointsForDeployment()">
<div class="col-sm-12">
<code-editor
value="$ctrl.model.StackFileContent"
identifier="stack-creation-editor"
placeholder="# Define or paste the content of your docker-compose file here"
yml="true"
on-change="($ctrl.editorUpdate)"
></code-editor>
<div class="small text-muted space-right text-warning">
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true"></i>
One or more of the selected Edge group contains Edge Docker endpoints that cannot be used with a Kubernetes Edge stack.
</div>
</div>
</div>
<!-- !web-editor -->
<div class="col-sm-12 form-section-title">
Options
</div>
<div class="form-group">
<edge-stack-deployment-type-selector
value="$ctrl.model.DeploymentType"
has-docker-endpoint="$ctrl.hasDockerEndpoint"
on-change="($ctrl.onChangeDeploymentType)"
></edge-stack-deployment-type-selector>
<div class="form-group" ng-if="$ctrl.model.DeploymentType === 0 && $ctrl.hasKubeEndpoint()">
<div class="col-sm-12">
<label for="EnablePrune" class="control-label text-left">
Prune services
<portainer-tooltip position="bottom" message="Prune services that are not longer referenced in the stack file."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" name="EnablePrune" ng-model="$ctrl.model.Prune" />
<i></i>
</label>
<div class="small text-muted space-right">
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true"></i>
Portainer uses <a href="https://kompose.io/" target="_blank">Kompose</a> to convert your Compose manifest to a Kubernetes compliant manifest. Be wary that not all the
Compose format options are supported by Kompose at the moment.
</div>
</div>
</div>
<web-editor-form
ng-if="$ctrl.model.DeploymentType === 0"
value="$ctrl.model.StackFileContent"
yml="true"
identifier="compose-editor"
placeholder="# Define or paste the content of your docker-compose file here"
on-change="($ctrl.onChangeComposeConfig)"
>
<editor-description>
<div>
You can get more information about Compose file format in the
<a href="https://docs.docker.com/compose/compose-file/" target="_blank">
official documentation
</a>
.
</div>
</editor-description>
</web-editor-form>
<web-editor-form
ng-if="$ctrl.model.DeploymentType === 1"
value="$ctrl.model.StackFileContent"
yml="true"
identifier="kube-manifest-editor"
placeholder="# Define or paste the content of your manifest here"
on-change="($ctrl.onChangeKubeManifest)"
>
<editor-description>
<p>
You can get more information about Kubernetes file format in the
<a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/" target="_blank">official documentation</a>.
</p>
</editor-description>
</web-editor-form>
<!-- actions -->
<div class="col-sm-12 form-section-title">
Actions
@ -59,9 +77,7 @@
<button
type="button"
class="btn btn-primary btn-sm"
ng-disabled="$ctrl.actionInProgress
|| !$ctrl.model.EdgeGroups.length
|| !$ctrl.model.StackFileContent"
ng-disabled="$ctrl.actionInProgress || !$ctrl.isFormValid()"
ng-click="$ctrl.submitAction()"
button-spinner="$ctrl.actionInProgress"
>

View file

@ -1,13 +1,83 @@
import { PortainerEndpointTypes } from '@/portainer/models/endpoint/models';
export class EditEdgeStackFormController {
/* @ngInject */
constructor() {
this.editorUpdate = this.editorUpdate.bind(this);
this.state = {
endpointTypes: [],
};
this.fileContents = {
0: '',
1: '',
};
this.onChangeGroups = this.onChangeGroups.bind(this);
this.onChangeFileContent = this.onChangeFileContent.bind(this);
this.onChangeComposeConfig = this.onChangeComposeConfig.bind(this);
this.onChangeKubeManifest = this.onChangeKubeManifest.bind(this);
this.hasDockerEndpoint = this.hasDockerEndpoint.bind(this);
this.hasKubeEndpoint = this.hasKubeEndpoint.bind(this);
this.onChangeDeploymentType = this.onChangeDeploymentType.bind(this);
this.removeLineBreaks = this.removeLineBreaks.bind(this);
this.onChangeFileContent = this.onChangeFileContent.bind(this);
}
editorUpdate(cm) {
if (this.model.StackFileContent.replace(/(\r\n|\n|\r)/gm, '') !== cm.getValue().replace(/(\r\n|\n|\r)/gm, '')) {
this.model.StackFileContent = cm.getValue();
hasKubeEndpoint() {
return this.state.endpointTypes.includes(PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment);
}
hasDockerEndpoint() {
return this.state.endpointTypes.includes(PortainerEndpointTypes.EdgeAgentOnDockerEnvironment);
}
onChangeGroups(groups) {
this.model.EdgeGroups = groups;
this.checkEndpointTypes(groups);
}
isFormValid() {
return this.model.EdgeGroups.length && this.model.StackFileContent && this.validateEndpointsForDeployment();
}
checkEndpointTypes(groups) {
const edgeGroups = groups.map((id) => this.edgeGroups.find((e) => e.Id === id));
this.state.endpointTypes = edgeGroups.flatMap((group) => group.EndpointTypes);
}
removeLineBreaks(value) {
return value.replace(/(\r\n|\n|\r)/gm, '');
}
onChangeFileContent(type, value) {
const oldValue = this.fileContents[type];
if (this.removeLineBreaks(oldValue) !== this.removeLineBreaks(value)) {
this.model.StackFileContent = value;
this.fileContents[type] = value;
this.isEditorDirty = true;
}
}
onChangeKubeManifest(value) {
this.onChangeFileContent(1, value);
}
onChangeComposeConfig(value) {
this.onChangeFileContent(0, value);
}
onChangeDeploymentType(deploymentType) {
this.model.DeploymentType = deploymentType;
this.model.StackFileContent = this.fileContents[deploymentType];
}
validateEndpointsForDeployment() {
return this.model.DeploymentType == 0 || !this.hasDockerEndpoint();
}
$onInit() {
this.checkEndpointTypes(this.model.EdgeGroups);
}
}

View file

@ -49,7 +49,7 @@ export class EdgeGroupFormController {
async getDynamicEndpointsAsync() {
const { pageNumber, limit, search } = this.endpoints.state;
const start = (pageNumber - 1) * limit + 1;
const query = { search, types: [4], tagIds: this.model.TagIds, tagsPartialMatch: this.model.PartialMatch };
const query = { search, types: [4, 7], tagIds: this.model.TagIds, tagsPartialMatch: this.model.PartialMatch };
const response = await this.EndpointService.endpoints(start, limit, query);