Files
odo/pkg/kclient/namespace.go
Philippe Martin 93d1330182 Patch Pods to pass pod security admission (#6602)
* Bump devfile/library

* Use generator.GetPodTemplateSpec instead of Get[Init]Containers

* Fix deprecated pointer.*

* Fix more deprecated

* Workaround for ServerPreferredResources panicking

* Pass policy to GetPodTemplateSpec

* Integration test

* document behaviour

* Review
2023-03-20 10:16:14 -04:00

215 lines
6.1 KiB
Go

package kclient
import (
"context"
"errors"
"fmt"
"time"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/klog"
"k8s.io/pod-security-admission/api"
psaApi "k8s.io/pod-security-admission/api"
)
const (
// timeout for waiting for namespace deletion
waitForNamespaceDeletionTimeOut = 3 * time.Minute
// timeout for getting the default service account
getDefaultServiceAccTimeout = 1 * time.Minute
)
// GetNamespaces return list of existing namespaces that user has access to.
func (c *Client) GetNamespaces() ([]string, error) {
namespaces, err := c.KubeClient.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("unable to list namespaces: %w", err)
}
var names []string
for _, p := range namespaces.Items {
names = append(names, p.Name)
}
return names, nil
}
// GetNamespace returns Namespace based on its name
// Errors related to project not being found or forbidden are translated to nil project for compatibility
func (c *Client) GetNamespace(name string) (*corev1.Namespace, error) {
ns, err := c.KubeClient.CoreV1().Namespaces().Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
istatus, ok := err.(kerrors.APIStatus)
if ok {
status := istatus.Status()
if status.Reason == metav1.StatusReasonNotFound || status.Reason == metav1.StatusReasonForbidden {
return nil, nil
}
} else {
return nil, err
}
}
return ns, err
}
// GetNamespace returns Namespace based on its name
func (c *Client) GetNamespaceNormal(name string) (*corev1.Namespace, error) {
return c.KubeClient.CoreV1().Namespaces().Get(context.TODO(), name, metav1.GetOptions{})
}
// CreateNamespace creates new namespace
func (c *Client) CreateNamespace(name string) (*corev1.Namespace, error) {
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}
newNamespace, err := c.KubeClient.CoreV1().Namespaces().Create(context.TODO(), namespace, metav1.CreateOptions{FieldManager: FieldManager})
if err != nil {
return nil, fmt.Errorf("unable to create Namespace %s: %w", namespace.ObjectMeta.Name, err)
}
return newNamespace, nil
}
// DeleteNamespace deletes namespace
// if wait=true , it will wait for deletion
func (c *Client) DeleteNamespace(name string, wait bool) error {
var watcher watch.Interface
var err error
if wait {
watcher, err = c.KubeClient.CoreV1().Namespaces().Watch(context.TODO(), metav1.ListOptions{
FieldSelector: fields.Set{"metadata.name": name}.AsSelector().String(),
})
if err != nil {
return fmt.Errorf("unable to watch namespace: %w", err)
}
defer watcher.Stop()
}
err = c.KubeClient.CoreV1().Namespaces().Delete(context.TODO(), name, metav1.DeleteOptions{})
if err != nil {
return fmt.Errorf("unable to delete Namespace %s: %w", name, err)
}
if watcher != nil {
namespaceChannel := make(chan *corev1.Namespace)
watchErrorChannel := make(chan error)
go func() {
for {
val, ok := <-watcher.ResultChan()
if !ok {
watchErrorChannel <- fmt.Errorf("watch channel was closed unexpectedly: %+v", val)
break
}
klog.V(3).Infof("Watch event.Type '%s'.", val.Type)
if namespaceStatus, ok := val.Object.(*corev1.Namespace); ok {
klog.V(3).Infof("Status of delete of namespace %s is '%s'.", name, namespaceStatus.Status.Phase)
if val.Type == watch.Deleted {
namespaceChannel <- namespaceStatus
break
}
if val.Type == watch.Error {
watchErrorChannel <- fmt.Errorf("failed watching the deletion of namespace %s", name)
break
}
}
}
close(namespaceChannel)
close(watchErrorChannel)
}()
select {
case val := <-namespaceChannel:
klog.V(3).Infof("Namespace %s deleted", val.Name)
return nil
case err := <-watchErrorChannel:
return err
case <-time.After(waitForNamespaceDeletionTimeOut):
return fmt.Errorf("waited %s but couldn't delete namespace %s in time", waitForNamespaceDeletionTimeOut, name)
}
}
return nil
}
// SetCurrentNamespace change current namespace in kubeconfig
func (c *Client) SetCurrentNamespace(namespace string) error {
rawConfig, err := c.KubeConfig.RawConfig()
if err != nil {
return fmt.Errorf("unable to switch to %s project: %w", namespace, err)
}
rawConfig.Contexts[rawConfig.CurrentContext].Namespace = namespace
err = clientcmd.ModifyConfig(clientcmd.NewDefaultClientConfigLoadingRules(), rawConfig, true)
if err != nil {
return fmt.Errorf("unable to switch to %s project: %w", namespace, err)
}
c.Namespace = namespace
return nil
}
func (c *Client) GetCurrentNamespace() string {
return c.Namespace
}
func (c *Client) SetNamespace(ns string) {
c.Namespace = ns
}
// WaitForServiceAccountInNamespace waits for the given service account to be ready
func (c *Client) WaitForServiceAccountInNamespace(namespace, serviceAccountName string) error {
if namespace == "" || serviceAccountName == "" {
return errors.New("namespace and serviceAccountName cannot be empty")
}
watcher, err := c.KubeClient.CoreV1().ServiceAccounts(namespace).Watch(context.TODO(), metav1.SingleObject(metav1.ObjectMeta{Name: serviceAccountName}))
if err != nil {
return err
}
timeout := time.After(getDefaultServiceAccTimeout)
if watcher != nil {
defer watcher.Stop()
for {
select {
case val, ok := <-watcher.ResultChan():
if !ok {
break
}
if serviceAccount, ok := val.Object.(*corev1.ServiceAccount); ok {
if serviceAccount.Name == serviceAccountName {
klog.V(3).Infof("Status of creation of service account %s is ready", serviceAccount)
return nil
}
}
case <-timeout:
return errors.New("timed out waiting for service to be ready")
}
}
}
return nil
}
func (c *Client) GetCurrentNamespacePolicy() (psaApi.Policy, error) {
ns, err := c.GetNamespaceNormal(c.GetCurrentNamespace())
if err != nil {
return psaApi.Policy{}, err
}
labels := ns.GetLabels()
policy, _ := api.PolicyToEvaluate(labels, api.Policy{})
return policy, nil
}