From 930d9e562837f7a924829b6935111a378e31c71d Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Tue, 13 Dec 2022 22:56:47 +0200 Subject: [PATCH] feat(edge/stacks): use namespace in manifest [EE-4507] (#8145) --- .../handler/edgestacks/edgestack_create.go | 15 +++++-- .../handler/edgestacks/edgestack_update.go | 4 ++ .../endpoint_edgestack_inspect.go | 10 +++++ api/internal/edge/edgestacks/service.go | 19 +++++---- api/kubernetes/contants.go | 6 +++ api/portainer.go | 2 + .../editEdgeStackForm.html | 42 ++++++++++++------- .../editEdgeStackFormController.js | 7 ++++ .../create-edge-stack-view.controller.js | 10 +++-- .../kube-manifest-form.controller.js | 14 ++++++- .../kube-manifest-form.html | 11 +++++ .../editEdgeStackViewController.js | 3 +- .../SwitchField/SwitchField.module.css | 10 ----- .../SwitchField/SwitchField.tsx | 6 +-- 14 files changed, 112 insertions(+), 47 deletions(-) create mode 100644 api/kubernetes/contants.go diff --git a/api/http/handler/edgestacks/edgestack_create.go b/api/http/handler/edgestacks/edgestack_create.go index 13a498033..b06b412e4 100644 --- a/api/http/handler/edgestacks/edgestack_create.go +++ b/api/http/handler/edgestacks/edgestack_create.go @@ -92,6 +92,8 @@ type swarmStackFromFileContentPayload struct { DeploymentType portainer.EdgeStackDeploymentType `example:"0" enums:"0,1,2"` // List of Registries to use for this stack Registries []portainer.RegistryID + // Uses the manifest's namespaces instead of the default one + UseManifestNamespaces bool } func (payload *swarmStackFromFileContentPayload) Validate(r *http.Request) error { @@ -114,7 +116,7 @@ func (handler *Handler) createSwarmStackFromFileContent(r *http.Request, dryrun return nil, err } - stack, err := handler.edgeStacksService.BuildEdgeStack(payload.Name, payload.DeploymentType, payload.EdgeGroups, payload.Registries) + stack, err := handler.edgeStacksService.BuildEdgeStack(payload.Name, payload.DeploymentType, payload.EdgeGroups, payload.Registries, payload.UseManifestNamespaces) if err != nil { return nil, errors.Wrap(err, "failed to create Edge stack object") } @@ -197,6 +199,8 @@ type swarmStackFromGitRepositoryPayload struct { DeploymentType portainer.EdgeStackDeploymentType `example:"0" enums:"0,1,2"` // List of Registries to use for this stack Registries []portainer.RegistryID + // Uses the manifest's namespaces instead of the default one + UseManifestNamespaces bool } func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) error { @@ -230,7 +234,7 @@ func (handler *Handler) createSwarmStackFromGitRepository(r *http.Request, dryru return nil, err } - stack, err := handler.edgeStacksService.BuildEdgeStack(payload.Name, payload.DeploymentType, payload.EdgeGroups, payload.Registries) + stack, err := handler.edgeStacksService.BuildEdgeStack(payload.Name, payload.DeploymentType, payload.EdgeGroups, payload.Registries, payload.UseManifestNamespaces) if err != nil { return nil, errors.Wrap(err, "failed to create edge stack object") } @@ -268,6 +272,8 @@ type swarmStackFromFileUploadPayload struct { // nomad deploytype is enabled only for nomad environments(endpoints) DeploymentType portainer.EdgeStackDeploymentType `example:"0" enums:"0,1,2"` Registries []portainer.RegistryID + // Uses the manifest's namespaces instead of the default one + UseManifestNamespaces bool } func (payload *swarmStackFromFileUploadPayload) Validate(r *http.Request) error { @@ -303,6 +309,9 @@ func (payload *swarmStackFromFileUploadPayload) Validate(r *http.Request) error } payload.Registries = registries + useManifestNamespaces, _ := request.RetrieveBooleanMultiPartFormValue(r, "UseManifestNamespaces", true) + payload.UseManifestNamespaces = useManifestNamespaces + return nil } @@ -313,7 +322,7 @@ func (handler *Handler) createSwarmStackFromFileUpload(r *http.Request, dryrun b return nil, err } - stack, err := handler.edgeStacksService.BuildEdgeStack(payload.Name, payload.DeploymentType, payload.EdgeGroups, payload.Registries) + stack, err := handler.edgeStacksService.BuildEdgeStack(payload.Name, payload.DeploymentType, payload.EdgeGroups, payload.Registries, payload.UseManifestNamespaces) if err != nil { return nil, errors.Wrap(err, "failed to create edge stack object") } diff --git a/api/http/handler/edgestacks/edgestack_update.go b/api/http/handler/edgestacks/edgestack_update.go index 9bc690f31..f9cd768ed 100644 --- a/api/http/handler/edgestacks/edgestack_update.go +++ b/api/http/handler/edgestacks/edgestack_update.go @@ -19,6 +19,8 @@ type updateEdgeStackPayload struct { Version *int EdgeGroups []portainer.EdgeGroupID DeploymentType portainer.EdgeStackDeploymentType + // Uses the manifest's namespaces instead of the default one + UseManifestNamespaces bool } func (payload *updateEdgeStackPayload) Validate(r *http.Request) error { @@ -168,6 +170,8 @@ func (handler *Handler) edgeStackUpdate(w http.ResponseWriter, r *http.Request) stack.ManifestPath = filesystem.ManifestFileDefaultName } + stack.UseManifestNamespaces = payload.UseManifestNamespaces + hasDockerEndpoint, err := hasDockerEndpoint(handler.DataStore.Endpoint(), relatedEndpointIds) if err != nil { return httperror.InternalServerError("Unable to check for existence of docker environment", err) diff --git a/api/http/handler/endpointedge/endpoint_edgestack_inspect.go b/api/http/handler/endpointedge/endpoint_edgestack_inspect.go index 5958f2c4d..ed874c0fc 100644 --- a/api/http/handler/endpointedge/endpoint_edgestack_inspect.go +++ b/api/http/handler/endpointedge/endpoint_edgestack_inspect.go @@ -10,11 +10,14 @@ import ( portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/http/middlewares" "github.com/portainer/portainer/api/internal/endpointutils" + "github.com/portainer/portainer/api/kubernetes" ) type configResponse struct { StackFileContent string Name string + // Namespace to use for Kubernetes manifests, leave empty to use the namespaces defined in the manifest + Namespace string } // @summary Inspect an Edge Stack for an Environment(Endpoint) @@ -59,12 +62,18 @@ func (handler *Handler) endpointEdgeStackInspect(w http.ResponseWriter, r *http. } } + namespace := "" + if !edgeStack.UseManifestNamespaces { + namespace = kubernetes.DefaultNamespace + } + if endpointutils.IsKubernetesEndpoint(endpoint) { fileName = edgeStack.ManifestPath if fileName == "" { return httperror.BadRequest("Kubernetes is not supported by this stack", errors.New("Kubernetes is not supported by this stack")) } + } stackFileContent, err := handler.FileService.GetFileContent(edgeStack.ProjectPath, fileName) @@ -75,5 +84,6 @@ func (handler *Handler) endpointEdgeStackInspect(w http.ResponseWriter, r *http. return response.JSON(w, configResponse{ StackFileContent: string(stackFileContent), Name: edgeStack.Name, + Namespace: namespace, }) } diff --git a/api/internal/edge/edgestacks/service.go b/api/internal/edge/edgestacks/service.go index b08e4ae01..702d5e5f5 100644 --- a/api/internal/edge/edgestacks/service.go +++ b/api/internal/edge/edgestacks/service.go @@ -30,7 +30,9 @@ func NewService(dataStore dataservices.DataStore) *Service { func (service *Service) BuildEdgeStack(name string, deploymentType portainer.EdgeStackDeploymentType, edgeGroups []portainer.EdgeGroupID, - registries []portainer.RegistryID) (*portainer.EdgeStack, error) { + registries []portainer.RegistryID, + useManifestNamespaces bool, +) (*portainer.EdgeStack, error) { edgeStacksService := service.dataStore.EdgeStack() err := validateUniqueName(edgeStacksService.EdgeStacks, name) @@ -40,13 +42,14 @@ func (service *Service) BuildEdgeStack(name string, stackID := edgeStacksService.GetNextIdentifier() return &portainer.EdgeStack{ - ID: portainer.EdgeStackID(stackID), - Name: name, - DeploymentType: deploymentType, - CreationDate: time.Now().Unix(), - EdgeGroups: edgeGroups, - Status: make(map[portainer.EndpointID]portainer.EdgeStackStatus), - Version: 1, + ID: portainer.EdgeStackID(stackID), + Name: name, + DeploymentType: deploymentType, + CreationDate: time.Now().Unix(), + EdgeGroups: edgeGroups, + Status: make(map[portainer.EndpointID]portainer.EdgeStackStatus), + Version: 1, + UseManifestNamespaces: useManifestNamespaces, }, nil } diff --git a/api/kubernetes/contants.go b/api/kubernetes/contants.go new file mode 100644 index 000000000..a2d934a42 --- /dev/null +++ b/api/kubernetes/contants.go @@ -0,0 +1,6 @@ +package kubernetes + +const ( + // DefaultNamespace is the default namespace used when no namespace is specified + DefaultNamespace = "default" +) diff --git a/api/portainer.go b/api/portainer.go index 7b6c45da1..2c7b72aa0 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -281,6 +281,8 @@ type ( Version int `json:"Version"` ManifestPath string DeploymentType EdgeStackDeploymentType + // Uses the manifest's namespaces instead of the default one + UseManifestNamespaces bool // Deprecated Prune bool `json:"Prune"` diff --git a/app/edge/components/edit-edge-stack-form/editEdgeStackForm.html b/app/edge/components/edit-edge-stack-form/editEdgeStackForm.html index 73a5e3a10..4f737ba8e 100644 --- a/app/edge/components/edit-edge-stack-form/editEdgeStackForm.html +++ b/app/edge/components/edit-edge-stack-form/editEdgeStackForm.html @@ -48,21 +48,33 @@ - - -

- You can get more information about Kubernetes file format in the - official documentation. -

-
-
+
+
+
+ +
+
+ + + +

+ You can get more information about Kubernetes file format in the + official documentation. +

+
+
+
Actions
diff --git a/app/edge/components/edit-edge-stack-form/editEdgeStackFormController.js b/app/edge/components/edit-edge-stack-form/editEdgeStackFormController.js index 00586c689..07be5e94f 100644 --- a/app/edge/components/edit-edge-stack-form/editEdgeStackFormController.js +++ b/app/edge/components/edit-edge-stack-form/editEdgeStackFormController.js @@ -22,6 +22,13 @@ export class EditEdgeStackFormController { this.onChangeDeploymentType = this.onChangeDeploymentType.bind(this); this.removeLineBreaks = this.removeLineBreaks.bind(this); this.onChangeFileContent = this.onChangeFileContent.bind(this); + this.onChangeUseManifestNamespaces = this.onChangeUseManifestNamespaces.bind(this); + } + + onChangeUseManifestNamespaces(value) { + this.$scope.$evalAsync(() => { + this.model.UseManifestNamespaces = value; + }); } hasKubeEndpoint() { diff --git a/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.controller.js b/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.controller.js index a9b2a469a..6d7e01295 100644 --- a/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.controller.js +++ b/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.controller.js @@ -16,6 +16,7 @@ export default class CreateEdgeStackViewController { ComposeFilePathInRepository: '', Groups: [], DeploymentType: 0, + UseManifestNamespaces: false, }; this.state = { @@ -166,30 +167,32 @@ export default class CreateEdgeStackViewController { } createStackFromFileContent(name) { - const { StackFileContent, Groups, DeploymentType } = this.formValues; + const { StackFileContent, Groups, DeploymentType, UseManifestNamespaces } = this.formValues; return this.EdgeStackService.createStackFromFileContent({ name, StackFileContent, EdgeGroups: Groups, DeploymentType, + UseManifestNamespaces, }); } createStackFromFileUpload(name) { - const { StackFile, Groups, DeploymentType } = this.formValues; + const { StackFile, Groups, DeploymentType, UseManifestNamespaces } = this.formValues; return this.EdgeStackService.createStackFromFileUpload( { Name: name, EdgeGroups: Groups, DeploymentType, + UseManifestNamespaces, }, StackFile ); } createStackFromGitRepository(name) { - const { Groups, DeploymentType } = this.formValues; + const { Groups, DeploymentType, UseManifestNamespaces } = this.formValues; const repositoryOptions = { RepositoryURL: this.formValues.RepositoryURL, RepositoryReferenceName: this.formValues.RepositoryReferenceName, @@ -203,6 +206,7 @@ export default class CreateEdgeStackViewController { name, EdgeGroups: Groups, DeploymentType, + UseManifestNamespaces, }, repositoryOptions ); diff --git a/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/kube-manifest-form.controller.js b/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/kube-manifest-form.controller.js index f60476d3f..821e6377d 100644 --- a/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/kube-manifest-form.controller.js +++ b/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/kube-manifest-form.controller.js @@ -11,10 +11,20 @@ class KubeManifestFormController { this.onChangeFormValues = this.onChangeFormValues.bind(this); this.onChangeFile = this.onChangeFile.bind(this); this.onChangeMethod = this.onChangeMethod.bind(this); + this.onChangeUseManifestNamespaces = this.onChangeUseManifestNamespaces.bind(this); } - onChangeFormValues(values) { - this.formValues = values; + onChangeFormValues(newValues) { + return this.$async(async () => { + this.formValues = { + ...this.formValues, + ...newValues, + }; + }); + } + + onChangeUseManifestNamespaces(value) { + this.onChangeFormValues({ UseManifestNamespaces: value }); } onChangeFileContent(value) { diff --git a/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/kube-manifest-form.html b/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/kube-manifest-form.html index 7db960055..e13ff4aa7 100644 --- a/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/kube-manifest-form.html +++ b/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/kube-manifest-form.html @@ -1,3 +1,14 @@ +
+
+ +
+
+
Build method
diff --git a/app/edge/views/edge-stacks/editEdgeStackView/editEdgeStackViewController.js b/app/edge/views/edge-stacks/editEdgeStackView/editEdgeStackViewController.js index 9110610c5..a71323c0d 100644 --- a/app/edge/views/edge-stacks/editEdgeStackView/editEdgeStackViewController.js +++ b/app/edge/views/edge-stacks/editEdgeStackView/editEdgeStackViewController.js @@ -39,6 +39,7 @@ export class EditEdgeStackViewController { this.formValues = { StackFileContent: file, EdgeGroups: this.stack.EdgeGroups, + UseManifestNamespaces: this.stack.UseManifestNamespaces, DeploymentType: this.stack.DeploymentType, }; this.oldFileContent = this.formValues.StackFileContent; @@ -79,7 +80,7 @@ export class EditEdgeStackViewController { async deployStackAsync() { this.state.actionInProgress = true; try { - if (this.originalFileContent != this.formValues.StackFileContent) { + if (this.originalFileContent != this.formValues.StackFileContent || this.formValues.UseManifestNamespaces !== this.stack.UseManifestNamespaces) { this.formValues.Version = this.stack.Version + 1; } await this.EdgeStackService.updateStack(this.stack.Id, this.formValues); diff --git a/app/react/components/form-components/SwitchField/SwitchField.module.css b/app/react/components/form-components/SwitchField/SwitchField.module.css index 858758265..9223d50a0 100644 --- a/app/react/components/form-components/SwitchField/SwitchField.module.css +++ b/app/react/components/form-components/SwitchField/SwitchField.module.css @@ -3,13 +3,3 @@ align-items: center; margin: 0; } - -.label { - padding: 0; -} - -.switchValues { - font-style: normal; - font-weight: 500; - margin-left: 5px; -} diff --git a/app/react/components/form-components/SwitchField/SwitchField.tsx b/app/react/components/form-components/SwitchField/SwitchField.tsx index 6ddf6cfbf..e9373e172 100644 --- a/app/react/components/form-components/SwitchField/SwitchField.tsx +++ b/app/react/components/form-components/SwitchField/SwitchField.tsx @@ -42,11 +42,7 @@ export function SwitchField({ return (