fix(k8s/application): check name unique in k8s cluster (#6610)

* EE-2353 Check unique name when creating new deployment in kubernetes

* EE-2353 fix warning from gofmt

* EE-2353 add miss methon in kubernetes_mock.go

* EE-2353 add missing space

* EE-2353 Use kubernetes cli to instead exec.command

* EE-2353 remove useless parameter

* EE-2353 remove unnecessary log in handle

* EE-2353 fix gofmt warning

* EE-2353 use ListOptions to filter the list

* EE-2353 add function description

* EE-2353 fix error

* Update api/kubernetes/cli/deploment.go

Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com>

* EE-2353 change function name to HasStackName

Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com>
pull/6664/head
Chao Geng 2022-03-16 08:32:12 +08:00 committed by GitHub
parent f8cbb54ba5
commit 07294c19bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 63 additions and 12 deletions

View File

@ -106,7 +106,7 @@ func (handler *Handler) createKubernetesStackFromFileContent(w http.ResponseWrit
if err != nil {
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to load user information from the database", Err: err}
}
isUnique, err := handler.checkUniqueStackName(endpoint, payload.StackName, 0)
isUnique, err := handler.checkUniqueStackNameInKubernetes(endpoint, payload.StackName, 0, payload.Namespace)
if err != nil {
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to check for name collision", Err: err}
}
@ -177,7 +177,7 @@ func (handler *Handler) createKubernetesStackFromGitRepository(w http.ResponseWr
if err != nil {
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to load user information from the database", Err: err}
}
isUnique, err := handler.checkUniqueStackName(endpoint, payload.StackName, 0)
isUnique, err := handler.checkUniqueStackNameInKubernetes(endpoint, payload.StackName, 0, payload.Namespace)
if err != nil {
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to check for name collision", Err: err}
}
@ -291,7 +291,7 @@ func (handler *Handler) createKubernetesStackFromManifestURL(w http.ResponseWrit
if err != nil {
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to load user information from the database", Err: err}
}
isUnique, err := handler.checkUniqueStackName(endpoint, payload.StackName, 0)
isUnique, err := handler.checkUniqueStackNameInKubernetes(endpoint, payload.StackName, 0, payload.Namespace)
if err != nil {
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to check for name collision", Err: err}
}

View File

@ -16,6 +16,7 @@ import (
"github.com/portainer/portainer/api/docker"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
"github.com/portainer/portainer/api/kubernetes/cli"
"github.com/portainer/portainer/api/scheduler"
"github.com/portainer/portainer/api/stacks"
)
@ -34,15 +35,16 @@ type Handler struct {
stackDeletionMutex *sync.Mutex
requestBouncer *security.RequestBouncer
*mux.Router
DataStore dataservices.DataStore
DockerClientFactory *docker.ClientFactory
FileService portainer.FileService
GitService portainer.GitService
SwarmStackManager portainer.SwarmStackManager
ComposeStackManager portainer.ComposeStackManager
KubernetesDeployer portainer.KubernetesDeployer
Scheduler *scheduler.Scheduler
StackDeployer stacks.StackDeployer
DataStore dataservices.DataStore
DockerClientFactory *docker.ClientFactory
FileService portainer.FileService
GitService portainer.GitService
SwarmStackManager portainer.SwarmStackManager
ComposeStackManager portainer.ComposeStackManager
KubernetesDeployer portainer.KubernetesDeployer
KubernetesClientFactory *cli.ClientFactory
Scheduler *scheduler.Scheduler
StackDeployer stacks.StackDeployer
}
func stackExistsError(name string) *httperror.HandlerError {
@ -148,6 +150,31 @@ func (handler *Handler) checkUniqueStackName(endpoint *portainer.Endpoint, name
return true, nil
}
func (handler *Handler) checkUniqueStackNameInKubernetes(endpoint *portainer.Endpoint, name string, stackID portainer.StackID, namespace string) (bool, error) {
isUniqueStackName, err := handler.checkUniqueStackName(endpoint, name, stackID)
if err != nil {
return false, err
}
if !isUniqueStackName {
// Check if this stack name is really used in the kubernetes.
// Because the stack with this name could be removed via kubectl cli outside and the datastore does not be informed of this action.
if namespace == "" {
namespace = "default"
}
kubeCli, err := handler.KubernetesClientFactory.GetKubeClient(endpoint)
if err != nil {
return false, err
}
isUniqueStackName, err = kubeCli.HasStackName(namespace, name)
if err != nil {
return false, err
}
}
return isUniqueStackName, nil
}
func (handler *Handler) checkUniqueStackNameInDocker(endpoint *portainer.Endpoint, name string, stackID portainer.StackID, swarmMode bool) (bool, error) {
isUniqueStackName, err := handler.checkUniqueStackName(endpoint, name, stackID)
if err != nil {

View File

@ -221,6 +221,7 @@ func (server *Server) Start() error {
stackHandler.DataStore = server.DataStore
stackHandler.DockerClientFactory = server.DockerClientFactory
stackHandler.FileService = server.FileService
stackHandler.KubernetesClientFactory = server.KubernetesClientFactory
stackHandler.KubernetesDeployer = server.KubernetesDeployer
stackHandler.GitService = server.GitService
stackHandler.Scheduler = server.Scheduler

View File

@ -0,0 +1,22 @@
package cli
import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
)
// HasStackName checks whether the given name is used in the given namespace.
func (kcl *KubeClient) HasStackName(namespace string, stackName string) (bool, error) {
querySet := labels.Set{"io.portainer.kubernetes.application.stack": stackName}
listOpts := metav1.ListOptions{LabelSelector: labels.SelectorFromSet(querySet).String()}
list, err := kcl.cli.AppsV1().Deployments(namespace).List(context.TODO(), listOpts)
if err != nil {
return false, err
}
if len(list.Items) > 0 {
return false, nil
}
return true, nil
}

View File

@ -1262,6 +1262,7 @@ type (
GetServiceAccountBearerToken(userID int) (string, error)
CreateUserShellPod(ctx context.Context, serviceAccountName, shellPodImage string) (*KubernetesShellPod, error)
StartExecProcess(token string, useAdminToken bool, namespace, podName, containerName string, command []string, stdin io.Reader, stdout io.Writer, errChan chan error)
HasStackName(namespace string, stackName string) (bool, error)
NamespaceAccessPoliciesDeleteNamespace(namespace string) error
GetNodesLimits() (K8sNodesLimits, error)
GetNamespaceAccessPolicies() (map[string]K8sNamespaceAccessPolicy, error)