mirror of https://github.com/portainer/portainer
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
parent
f8cbb54ba5
commit
07294c19bb
|
@ -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}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue