1 package kclient
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "time"
8
9 corev1 "k8s.io/api/core/v1"
10 kerrors "k8s.io/apimachinery/pkg/api/errors"
11 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12 "k8s.io/apimachinery/pkg/fields"
13 "k8s.io/apimachinery/pkg/watch"
14 "k8s.io/client-go/tools/clientcmd"
15 "k8s.io/klog"
16 "k8s.io/pod-security-admission/api"
17 psaApi "k8s.io/pod-security-admission/api"
18 )
19
20 const (
21
22 waitForNamespaceDeletionTimeOut = 3 * time.Minute
23
24
25 getDefaultServiceAccTimeout = 1 * time.Minute
26 )
27
28
29 func (c *Client) GetNamespaces() ([]string, error) {
30 namespaces, err := c.KubeClient.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
31 if err != nil {
32 return nil, fmt.Errorf("unable to list namespaces: %w", err)
33 }
34
35 var names []string
36 for _, p := range namespaces.Items {
37 names = append(names, p.Name)
38 }
39 return names, nil
40 }
41
42
43
44 func (c *Client) GetNamespace(name string) (*corev1.Namespace, error) {
45 ns, err := c.KubeClient.CoreV1().Namespaces().Get(context.TODO(), name, metav1.GetOptions{})
46 if err != nil {
47 istatus, ok := err.(kerrors.APIStatus)
48 if ok {
49 status := istatus.Status()
50 if status.Reason == metav1.StatusReasonNotFound || status.Reason == metav1.StatusReasonForbidden {
51 return nil, nil
52 }
53 } else {
54 return nil, err
55 }
56
57 }
58 return ns, err
59
60 }
61
62
63 func (c *Client) GetNamespaceNormal(name string) (*corev1.Namespace, error) {
64 return c.KubeClient.CoreV1().Namespaces().Get(context.TODO(), name, metav1.GetOptions{})
65 }
66
67
68 func (c *Client) CreateNamespace(name string) (*corev1.Namespace, error) {
69 namespace := &corev1.Namespace{
70 ObjectMeta: metav1.ObjectMeta{
71 Name: name,
72 },
73 }
74
75 newNamespace, err := c.KubeClient.CoreV1().Namespaces().Create(context.TODO(), namespace, metav1.CreateOptions{FieldManager: FieldManager})
76 if err != nil {
77 return nil, fmt.Errorf("unable to create Namespace %s: %w", namespace.ObjectMeta.Name, err)
78 }
79 return newNamespace, nil
80 }
81
82
83
84 func (c *Client) DeleteNamespace(name string, wait bool) error {
85 var watcher watch.Interface
86 var err error
87 if wait {
88 watcher, err = c.KubeClient.CoreV1().Namespaces().Watch(context.TODO(), metav1.ListOptions{
89 FieldSelector: fields.Set{"metadata.name": name}.AsSelector().String(),
90 })
91 if err != nil {
92 return fmt.Errorf("unable to watch namespace: %w", err)
93 }
94 defer watcher.Stop()
95 }
96
97 err = c.KubeClient.CoreV1().Namespaces().Delete(context.TODO(), name, metav1.DeleteOptions{})
98 if err != nil {
99 return fmt.Errorf("unable to delete Namespace %s: %w", name, err)
100 }
101
102 if watcher != nil {
103 namespaceChannel := make(chan *corev1.Namespace)
104 watchErrorChannel := make(chan error)
105
106 go func() {
107 for {
108 val, ok := <-watcher.ResultChan()
109 if !ok {
110 watchErrorChannel <- fmt.Errorf("watch channel was closed unexpectedly: %+v", val)
111 break
112 }
113 klog.V(3).Infof("Watch event.Type '%s'.", val.Type)
114
115 if namespaceStatus, ok := val.Object.(*corev1.Namespace); ok {
116 klog.V(3).Infof("Status of delete of namespace %s is '%s'.", name, namespaceStatus.Status.Phase)
117 if val.Type == watch.Deleted {
118 namespaceChannel <- namespaceStatus
119 break
120 }
121 if val.Type == watch.Error {
122 watchErrorChannel <- fmt.Errorf("failed watching the deletion of namespace %s", name)
123 break
124 }
125
126 }
127
128 }
129 close(namespaceChannel)
130 close(watchErrorChannel)
131 }()
132
133 select {
134 case val := <-namespaceChannel:
135 klog.V(3).Infof("Namespace %s deleted", val.Name)
136 return nil
137 case err := <-watchErrorChannel:
138 return err
139 case <-time.After(waitForNamespaceDeletionTimeOut):
140 return fmt.Errorf("waited %s but couldn't delete namespace %s in time", waitForNamespaceDeletionTimeOut, name)
141 }
142
143 }
144 return nil
145 }
146
147
148 func (c *Client) SetCurrentNamespace(namespace string) error {
149 rawConfig, err := c.KubeConfig.RawConfig()
150 if err != nil {
151 return fmt.Errorf("unable to switch to %s project: %w", namespace, err)
152 }
153
154 rawConfig.Contexts[rawConfig.CurrentContext].Namespace = namespace
155
156 err = clientcmd.ModifyConfig(clientcmd.NewDefaultClientConfigLoadingRules(), rawConfig, true)
157 if err != nil {
158 return fmt.Errorf("unable to switch to %s project: %w", namespace, err)
159 }
160
161 c.Namespace = namespace
162 return nil
163 }
164
165 func (c *Client) GetCurrentNamespace() string {
166 return c.Namespace
167 }
168
169 func (c *Client) SetNamespace(ns string) {
170 c.Namespace = ns
171 }
172
173
174 func (c *Client) WaitForServiceAccountInNamespace(namespace, serviceAccountName string) error {
175 if namespace == "" || serviceAccountName == "" {
176 return errors.New("namespace and serviceAccountName cannot be empty")
177 }
178 watcher, err := c.KubeClient.CoreV1().ServiceAccounts(namespace).Watch(context.TODO(), metav1.SingleObject(metav1.ObjectMeta{Name: serviceAccountName}))
179 if err != nil {
180 return err
181 }
182
183 timeout := time.After(getDefaultServiceAccTimeout)
184 if watcher != nil {
185 defer watcher.Stop()
186 for {
187 select {
188 case val, ok := <-watcher.ResultChan():
189 if !ok {
190 break
191 }
192 if serviceAccount, ok := val.Object.(*corev1.ServiceAccount); ok {
193 if serviceAccount.Name == serviceAccountName {
194 klog.V(3).Infof("Status of creation of service account %s is ready", serviceAccount)
195 return nil
196 }
197 }
198 case <-timeout:
199 return errors.New("timed out waiting for service to be ready")
200 }
201 }
202 }
203 return nil
204 }
205
206 func (c *Client) GetCurrentNamespacePolicy() (psaApi.Policy, error) {
207 ns, err := c.GetNamespaceNormal(c.GetCurrentNamespace())
208 if err != nil {
209 return psaApi.Policy{}, err
210 }
211 labels := ns.GetLabels()
212 policy, _ := api.PolicyToEvaluate(labels, api.Policy{})
213 return policy, nil
214 }
215
View as plain text