From ba78168d5cc15e7dfdb66f8681600fe463f681d7 Mon Sep 17 00:00:00 2001 From: Hui Date: Fri, 17 Sep 2021 15:15:31 +1200 Subject: [PATCH] feat(k8s): support delete kub stack --- api/exec/kubernetes_deploy.go | 30 +++++++++++-------- .../handler/stacks/create_kubernetes_stack.go | 5 ---- api/http/handler/stacks/stack_delete.go | 13 ++++---- api/http/handler/stacks/stack_migrate.go | 12 ++++---- api/portainer.go | 1 + 5 files changed, 32 insertions(+), 29 deletions(-) diff --git a/api/exec/kubernetes_deploy.go b/api/exec/kubernetes_deploy.go index 84e9bd3ef..7368861eb 100644 --- a/api/exec/kubernetes_deploy.go +++ b/api/exec/kubernetes_deploy.go @@ -75,12 +75,20 @@ func (deployer *KubernetesDeployer) getToken(userID portainer.UserID, endpoint * return token, nil } -// Deploy will deploy a Kubernetes manifest inside a specific namespace in a Kubernetes endpoint. -// Otherwise it will use kubectl to deploy the manifest. +// Deploy upserts Kubernetes resources defined in manifest(s) func (deployer *KubernetesDeployer) Deploy(userID portainer.UserID, endpoint *portainer.Endpoint, manifestFiles []string, namespace string) (string, error) { + return deployer.command("apply", userID, endpoint, manifestFiles, namespace) +} + +// Remove deletes Kubernetes resources defined in manifest(s) +func (deployer *KubernetesDeployer) Remove(userID portainer.UserID, endpoint *portainer.Endpoint, manifestFiles []string, namespace string) (string, error) { + return deployer.command("delete", userID, endpoint, manifestFiles, namespace) +} + +func (deployer *KubernetesDeployer) command(operation string, userID portainer.UserID, endpoint *portainer.Endpoint, manifestFiles []string, namespace string) (string, error) { token, err := deployer.getToken(userID, endpoint, endpoint.Type == portainer.KubernetesLocalEnvironment) if err != nil { - return "", err + return "", errors.Wrap(err, "failed generating a user token") } command := path.Join(deployer.binaryPath, "kubectl") @@ -88,7 +96,11 @@ func (deployer *KubernetesDeployer) Deploy(userID portainer.UserID, endpoint *po command = path.Join(deployer.binaryPath, "kubectl.exe") } - args := make([]string, 0) + args := []string{ + "--token", token, + "--namespace", namespace, + } + if endpoint.Type == portainer.AgentOnKubernetesEnvironment || endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment { url, proxy, err := deployer.getAgentURL(endpoint) if err != nil { @@ -100,16 +112,10 @@ func (deployer *KubernetesDeployer) Deploy(userID portainer.UserID, endpoint *po args = append(args, "--insecure-skip-tls-verify") } - args = append(args, "--token", token) - args = append(args, "--namespace", namespace) - - var fileArgs []string + args = append(args, operation) for _, path := range manifestFiles { - fileArgs = append(fileArgs, "-f") - fileArgs = append(fileArgs, strings.TrimSpace(path)) + args = append(args, "-f", strings.TrimSpace(path)) } - args = append(args, "apply") - args = append(args, fileArgs...) var stderr bytes.Buffer cmd := exec.Command(command, args...) diff --git a/api/http/handler/stacks/create_kubernetes_stack.go b/api/http/handler/stacks/create_kubernetes_stack.go index 13e75cc33..283873868 100644 --- a/api/http/handler/stacks/create_kubernetes_stack.go +++ b/api/http/handler/stacks/create_kubernetes_stack.go @@ -166,8 +166,6 @@ func (handler *Handler) createKubernetesStackFromFileContent(w http.ResponseWrit return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to persist the Kubernetes stack inside the database", Err: err} } - doCleanUp = false - resp := &createKubernetesStackResponse{ Output: output, } @@ -284,14 +282,11 @@ func (handler *Handler) createKubernetesStackFromGitRepository(w http.ResponseWr return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to persist the stack inside the database", Err: err} } - doCleanUp = false - resp := &createKubernetesStackResponse{ Output: output, } doCleanUp = false - return response.JSON(w, resp) } diff --git a/api/http/handler/stacks/stack_delete.go b/api/http/handler/stacks/stack_delete.go index 7dfa52b38..a77ac552a 100644 --- a/api/http/handler/stacks/stack_delete.go +++ b/api/http/handler/stacks/stack_delete.go @@ -2,11 +2,11 @@ package stacks import ( "context" - "errors" "fmt" "net/http" "strconv" + "github.com/pkg/errors" httperror "github.com/portainer/libhttp/error" "github.com/portainer/libhttp/request" "github.com/portainer/libhttp/response" @@ -105,7 +105,7 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt stopAutoupdate(stack.ID, stack.AutoUpdate.JobID, *handler.Scheduler) } - err = handler.deleteStack(stack, endpoint) + err = handler.deleteStack(securityContext.UserID, stack, endpoint) if err != nil { return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: err.Error(), Err: err} } @@ -165,7 +165,7 @@ func (handler *Handler) deleteExternalStack(r *http.Request, w http.ResponseWrit Type: portainer.DockerSwarmStack, } - err = handler.deleteStack(stack, endpoint) + err = handler.deleteStack(securityContext.UserID, stack, endpoint) if err != nil { return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to delete stack", Err: err} } @@ -173,7 +173,7 @@ func (handler *Handler) deleteExternalStack(r *http.Request, w http.ResponseWrit return response.Empty(w) } -func (handler *Handler) deleteStack(stack *portainer.Stack, endpoint *portainer.Endpoint) error { +func (handler *Handler) deleteStack(userID portainer.UserID, stack *portainer.Stack, endpoint *portainer.Endpoint) error { if stack.Type == portainer.DockerSwarmStack { return handler.SwarmStackManager.Remove(stack, endpoint) } @@ -181,7 +181,8 @@ func (handler *Handler) deleteStack(stack *portainer.Stack, endpoint *portainer. return handler.ComposeStackManager.Down(context.TODO(), stack, endpoint) } if stack.Type == portainer.KubernetesStack { - return nil + out, err := handler.KubernetesDeployer.Remove(userID, endpoint, stackutils.GetStackFilePaths(stack), stack.Namespace) + return errors.WithMessagef(err, "failed to remove kubernetes resources: %q", out) } - return fmt.Errorf("Unsupported stack type: %v", stack.Type) + return fmt.Errorf("unsupported stack type: %v", stack.Type) } diff --git a/api/http/handler/stacks/stack_migrate.go b/api/http/handler/stacks/stack_migrate.go index 46307df0d..980c872f4 100644 --- a/api/http/handler/stacks/stack_migrate.go +++ b/api/http/handler/stacks/stack_migrate.go @@ -78,17 +78,17 @@ func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *ht return &httperror.HandlerError{StatusCode: http.StatusForbidden, Message: "Permission denied to access endpoint", Err: err} } + securityContext, err := security.RetrieveRestrictedRequestContext(r) + if err != nil { + return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to retrieve info from request context", Err: err} + } + if stack.Type == portainer.DockerSwarmStack || stack.Type == portainer.DockerComposeStack { resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl) if err != nil { return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to retrieve a resource control associated to the stack", Err: err} } - securityContext, err := security.RetrieveRestrictedRequestContext(r) - if err != nil { - return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to retrieve info from request context", Err: err} - } - access, err := handler.userCanAccessStack(securityContext, endpoint.ID, resourceControl) if err != nil { return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to verify user authorizations to validate stack access", Err: err} @@ -144,7 +144,7 @@ func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *ht } stack.Name = oldName - err = handler.deleteStack(stack, endpoint) + err = handler.deleteStack(securityContext.UserID, stack, endpoint) if err != nil { return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: err.Error(), Err: err} } diff --git a/api/portainer.go b/api/portainer.go index 3798d0c75..03ae08887 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -1251,6 +1251,7 @@ type ( // KubernetesDeployer represents a service to deploy a manifest inside a Kubernetes endpoint KubernetesDeployer interface { Deploy(userID UserID, endpoint *Endpoint, manifestFiles []string, namespace string) (string, error) + Remove(userID UserID, endpoint *Endpoint, manifestFiles []string, namespace string) (string, error) ConvertCompose(data []byte) ([]byte, error) }