1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-19 05:19:39 +02:00

refactor(namespace): migrate namespace edit to react [r8s-125] (#38)

This commit is contained in:
Ali 2024-12-11 10:15:46 +13:00 committed by GitHub
parent 40c7742e46
commit ce7e0d8d60
108 changed files with 3183 additions and 2194 deletions

View file

@ -47,7 +47,9 @@ func (kcl *KubeClient) GetNamespaces() (map[string]portainer.K8sNamespaceInfo, e
// fetchNamespacesForNonAdmin gets the namespaces in the current k8s environment(endpoint) for the non-admin user.
func (kcl *KubeClient) fetchNamespacesForNonAdmin() (map[string]portainer.K8sNamespaceInfo, error) {
log.Debug().Msgf("Fetching namespaces for non-admin user: %v", kcl.NonAdminNamespaces)
log.Debug().
Str("context", "fetchNamespacesForNonAdmin").
Msg("Fetching namespaces for non-admin user")
if len(kcl.NonAdminNamespaces) == 0 {
return nil, nil
@ -75,6 +77,11 @@ func (kcl *KubeClient) fetchNamespacesForNonAdmin() (map[string]portainer.K8sNam
func (kcl *KubeClient) fetchNamespaces() (map[string]portainer.K8sNamespaceInfo, error) {
namespaces, err := kcl.cli.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
if err != nil {
log.Error().
Str("context", "fetchNamespaces").
Err(err).
Msg("Failed to list namespaces")
return nil, fmt.Errorf("an error occurred during the fetchNamespacesForAdmin operation, unable to list namespaces for the admin user: %w", err)
}
@ -92,6 +99,7 @@ func parseNamespace(namespace *corev1.Namespace) portainer.K8sNamespaceInfo {
Id: string(namespace.UID),
Name: namespace.Name,
Status: namespace.Status,
Annotations: namespace.Annotations,
CreationDate: namespace.CreationTimestamp.Format(time.RFC3339),
NamespaceOwner: namespace.Labels[namespaceOwnerLabel],
IsSystem: isSystemNamespace(namespace),
@ -103,13 +111,18 @@ func parseNamespace(namespace *corev1.Namespace) portainer.K8sNamespaceInfo {
func (kcl *KubeClient) GetNamespace(name string) (portainer.K8sNamespaceInfo, error) {
namespace, err := kcl.cli.CoreV1().Namespaces().Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
log.Error().
Str("context", "GetNamespace").
Str("namespace", name).
Err(err).
Msg("Failed to get namespace")
return portainer.K8sNamespaceInfo{}, err
}
return parseNamespace(namespace), nil
}
// CreateNamespace creates a new ingress in a given namespace in a k8s endpoint.
// CreateNamespace creates a new namespace in a k8s endpoint.
func (kcl *KubeClient) CreateNamespace(info models.K8sNamespaceDetails) (*corev1.Namespace, error) {
portainerLabels := map[string]string{
namespaceNameLabel: stackutils.SanitizeLabel(info.Name),
@ -125,52 +138,127 @@ func (kcl *KubeClient) CreateNamespace(info models.K8sNamespaceDetails) (*corev1
if err != nil {
log.Error().
Err(err).
Str("context", "CreateNamespace").
Str("Namespace", info.Name).
Msg("Failed to create the namespace")
return nil, err
}
if info.ResourceQuota != nil && info.ResourceQuota.Enabled {
log.Info().Msgf("Creating resource quota for namespace %s", info.Name)
log.Debug().Msgf("Creating resource quota with details: %+v", info.ResourceQuota)
resourceQuota := &corev1.ResourceQuota{
ObjectMeta: metav1.ObjectMeta{
Name: "portainer-rq-" + info.Name,
Namespace: info.Name,
Labels: portainerLabels,
},
Spec: corev1.ResourceQuotaSpec{
Hard: corev1.ResourceList{},
},
}
if info.ResourceQuota.Enabled {
memory := resource.MustParse(info.ResourceQuota.Memory)
cpu := resource.MustParse(info.ResourceQuota.CPU)
if memory.Value() > 0 {
memQuota := memory
resourceQuota.Spec.Hard[corev1.ResourceLimitsMemory] = memQuota
resourceQuota.Spec.Hard[corev1.ResourceRequestsMemory] = memQuota
}
if cpu.Value() > 0 {
cpuQuota := cpu
resourceQuota.Spec.Hard[corev1.ResourceLimitsCPU] = cpuQuota
resourceQuota.Spec.Hard[corev1.ResourceRequestsCPU] = cpuQuota
}
}
_, err := kcl.cli.CoreV1().ResourceQuotas(info.Name).Create(context.Background(), resourceQuota, metav1.CreateOptions{})
if err != nil {
log.Error().Msgf("Failed to create resource quota for namespace %s: %s", info.Name, err)
return nil, err
}
if err := kcl.createOrUpdateNamespaceResourceQuota(info, portainerLabels); err != nil {
log.Error().
Err(err).
Str("context", "CreateNamespace").
Str("name", info.Name).
Msg("failed to create or update resource quota for namespace")
return nil, err
}
return namespace, nil
}
// UpdateIngress updates an ingress in a given namespace in a k8s endpoint.
func (kcl *KubeClient) UpdateNamespace(info models.K8sNamespaceDetails) (*corev1.Namespace, error) {
portainerLabels := map[string]string{
namespaceNameLabel: stackutils.SanitizeLabel(info.Name),
namespaceOwnerLabel: stackutils.SanitizeLabel(info.Owner),
}
namespace := corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: info.Name,
Annotations: info.Annotations,
},
}
updatedNamespace, err := kcl.cli.CoreV1().Namespaces().Update(context.Background(), &namespace, metav1.UpdateOptions{})
if err != nil {
log.Error().
Str("context", "UpdateNamespace").
Str("namespace", info.Name).
Err(err).
Msg("Failed to update namespace")
return nil, err
}
if err := kcl.createOrUpdateNamespaceResourceQuota(info, portainerLabels); err != nil {
log.Error().
Err(err).
Str("context", "UpdateNamespace").
Str("name", info.Name).
Msg("failed to create or update resource quota for namespace")
return nil, err
}
return updatedNamespace, nil
}
func (kcl *KubeClient) createOrUpdateNamespaceResourceQuota(info models.K8sNamespaceDetails, portainerLabels map[string]string) error {
if !info.ResourceQuota.Enabled {
if err := kcl.deleteNamespaceResourceQuota(info.Name); err != nil {
log.Debug().Err(err).Str("context", "createOrUpdateNamespaceResourceQuota").Str("name", info.Name).Msg("failed to delete resource quota for namespace")
}
return nil
}
resourceQuota := &corev1.ResourceQuota{
ObjectMeta: metav1.ObjectMeta{
Name: "portainer-rq-" + info.Name,
Namespace: info.Name,
Labels: portainerLabels,
},
Spec: corev1.ResourceQuotaSpec{
Hard: corev1.ResourceList{},
},
}
if info.ResourceQuota.Enabled {
memory := resource.MustParse(info.ResourceQuota.Memory)
cpu := resource.MustParse(info.ResourceQuota.CPU)
if memory.Value() > 0 {
memQuota := memory
resourceQuota.Spec.Hard[corev1.ResourceLimitsMemory] = memQuota
resourceQuota.Spec.Hard[corev1.ResourceRequestsMemory] = memQuota
}
if cpu.Value() > 0 {
cpuQuota := cpu
resourceQuota.Spec.Hard[corev1.ResourceLimitsCPU] = cpuQuota
resourceQuota.Spec.Hard[corev1.ResourceRequestsCPU] = cpuQuota
}
}
_, err := kcl.cli.CoreV1().ResourceQuotas(info.Name).Update(context.Background(), resourceQuota, metav1.UpdateOptions{})
if err != nil {
if k8serrors.IsNotFound(err) {
log.Warn().
Str("context", "createOrUpdateNamespaceResourceQuota").
Str("name", info.Name).
Msg("resource quota not found, creating")
_, err = kcl.cli.CoreV1().ResourceQuotas(info.Name).Create(context.Background(), resourceQuota, metav1.CreateOptions{})
}
}
return err
}
func (kcl *KubeClient) deleteNamespaceResourceQuota(namespaceName string) error {
err := kcl.cli.CoreV1().ResourceQuotas(namespaceName).Delete(context.Background(), "portainer-rq-"+namespaceName, metav1.DeleteOptions{})
if err != nil && !k8serrors.IsNotFound(err) {
log.Error().
Str("context", "deleteNamespaceResourceQuota").
Str("name", namespaceName).
Err(err).
Msg("failed to delete resource quota for namespace")
return err
}
log.Warn().
Str("context", "deleteNamespaceResourceQuota").
Str("name", namespaceName).
Msg("resource quota to delete not found")
return nil
}
func isSystemNamespace(namespace *corev1.Namespace) bool {
systemLabelValue, hasSystemLabel := namespace.Labels[systemNamespaceLabel]
if hasSystemLabel {
@ -180,7 +268,6 @@ func isSystemNamespace(namespace *corev1.Namespace) bool {
systemNamespaces := defaultSystemNamespaces()
_, isSystem := systemNamespaces[namespace.Name]
return isSystem
}
@ -201,10 +288,13 @@ func (kcl *KubeClient) ToggleSystemState(namespaceName string, isSystem bool) er
return nil
}
nsService := kcl.cli.CoreV1().Namespaces()
namespace, err := nsService.Get(context.TODO(), namespaceName, metav1.GetOptions{})
namespace, err := kcl.cli.CoreV1().Namespaces().Get(context.TODO(), namespaceName, metav1.GetOptions{})
if err != nil {
log.Error().
Str("context", "ToggleSystemState").
Str("namespace", namespaceName).
Err(err).
Msg("failed to get namespace")
return errors.Wrap(err, "failed fetching namespace object")
}
@ -218,8 +308,12 @@ func (kcl *KubeClient) ToggleSystemState(namespaceName string, isSystem bool) er
namespace.Labels[systemNamespaceLabel] = strconv.FormatBool(isSystem)
_, err = nsService.Update(context.TODO(), namespace, metav1.UpdateOptions{})
if err != nil {
if _, err := kcl.cli.CoreV1().Namespaces().Update(context.TODO(), namespace, metav1.UpdateOptions{}); err != nil {
log.Error().
Str("context", "ToggleSystemState").
Str("namespace", namespaceName).
Err(err).
Msg("failed updating namespace object")
return errors.Wrap(err, "failed updating namespace object")
}
@ -228,29 +322,26 @@ func (kcl *KubeClient) ToggleSystemState(namespaceName string, isSystem bool) er
}
return nil
}
// UpdateIngress updates an ingress in a given namespace in a k8s endpoint.
func (kcl *KubeClient) UpdateNamespace(info models.K8sNamespaceDetails) (*corev1.Namespace, error) {
namespace := corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: info.Name,
Annotations: info.Annotations,
},
}
return kcl.cli.CoreV1().Namespaces().Update(context.Background(), &namespace, metav1.UpdateOptions{})
}
func (kcl *KubeClient) DeleteNamespace(namespaceName string) (*corev1.Namespace, error) {
namespace, err := kcl.cli.CoreV1().Namespaces().Get(context.Background(), namespaceName, metav1.GetOptions{})
if err != nil {
log.Error().
Str("context", "DeleteNamespace").
Str("namespace", namespaceName).
Err(err).
Msg("failed fetching namespace object")
return nil, err
}
err = kcl.cli.CoreV1().Namespaces().Delete(context.Background(), namespaceName, metav1.DeleteOptions{})
if err != nil {
log.Error().
Str("context", "DeleteNamespace").
Str("namespace", namespaceName).
Err(err).
Msg("failed deleting namespace object")
return nil, err
}
@ -261,6 +352,10 @@ func (kcl *KubeClient) DeleteNamespace(namespaceName string) (*corev1.Namespace,
func (kcl *KubeClient) CombineNamespacesWithResourceQuotas(namespaces map[string]portainer.K8sNamespaceInfo, w http.ResponseWriter) *httperror.HandlerError {
resourceQuotas, err := kcl.GetResourceQuotas("")
if err != nil && !k8serrors.IsNotFound(err) {
log.Error().
Str("context", "CombineNamespacesWithResourceQuotas").
Err(err).
Msg("unable to retrieve resource quotas from the Kubernetes for an admin user")
return httperror.InternalServerError("an error occurred during the CombineNamespacesWithResourceQuotas operation, unable to retrieve resource quotas from the Kubernetes for an admin user. Error: ", err)
}
@ -275,6 +370,11 @@ func (kcl *KubeClient) CombineNamespacesWithResourceQuotas(namespaces map[string
func (kcl *KubeClient) CombineNamespaceWithResourceQuota(namespace portainer.K8sNamespaceInfo, w http.ResponseWriter) *httperror.HandlerError {
resourceQuota, err := kcl.GetPortainerResourceQuota(namespace.Name)
if err != nil && !k8serrors.IsNotFound(err) {
log.Error().
Str("context", "CombineNamespaceWithResourceQuota").
Str("namespace", namespace.Name).
Err(err).
Msg("unable to retrieve the resource quota associated with the namespace")
return httperror.InternalServerError(fmt.Sprintf("an error occurred during the CombineNamespaceWithResourceQuota operation, unable to retrieve the resource quota associated with the namespace: %s for a non-admin user. Error: ", namespace.Name), err)
}