github.com/spotahome/redis-operator@v1.2.4/service/k8s/pod.go (about) 1 package k8s 2 3 import ( 4 "context" 5 "encoding/json" 6 7 "k8s.io/apimachinery/pkg/types" 8 9 corev1 "k8s.io/api/core/v1" 10 "k8s.io/apimachinery/pkg/api/errors" 11 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 "k8s.io/client-go/kubernetes" 13 14 "github.com/spotahome/redis-operator/log" 15 "github.com/spotahome/redis-operator/metrics" 16 ) 17 18 // Pod the ServiceAccount service that knows how to interact with k8s to manage them 19 type Pod interface { 20 GetPod(namespace string, name string) (*corev1.Pod, error) 21 CreatePod(namespace string, pod *corev1.Pod) error 22 UpdatePod(namespace string, pod *corev1.Pod) error 23 CreateOrUpdatePod(namespace string, pod *corev1.Pod) error 24 DeletePod(namespace string, name string) error 25 ListPods(namespace string) (*corev1.PodList, error) 26 UpdatePodLabels(namespace, podName string, labels map[string]string) error 27 } 28 29 // PodService is the pod service implementation using API calls to kubernetes. 30 type PodService struct { 31 kubeClient kubernetes.Interface 32 logger log.Logger 33 metricsRecorder metrics.Recorder 34 } 35 36 // NewPodService returns a new Pod KubeService. 37 func NewPodService(kubeClient kubernetes.Interface, logger log.Logger, metricsRecorder metrics.Recorder) *PodService { 38 logger = logger.With("service", "k8s.pod") 39 return &PodService{ 40 kubeClient: kubeClient, 41 logger: logger, 42 metricsRecorder: metricsRecorder, 43 } 44 } 45 46 func (p *PodService) GetPod(namespace string, name string) (*corev1.Pod, error) { 47 pod, err := p.kubeClient.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{}) 48 recordMetrics(namespace, "Pod", name, "GET", err, p.metricsRecorder) 49 if err != nil { 50 return nil, err 51 } 52 return pod, err 53 } 54 55 func (p *PodService) CreatePod(namespace string, pod *corev1.Pod) error { 56 _, err := p.kubeClient.CoreV1().Pods(namespace).Create(context.TODO(), pod, metav1.CreateOptions{}) 57 recordMetrics(namespace, "Pod", pod.GetName(), "CREATE", err, p.metricsRecorder) 58 if err != nil { 59 return err 60 } 61 p.logger.WithField("namespace", namespace).WithField("pod", pod.Name).Debugf("pod created") 62 return nil 63 } 64 func (p *PodService) UpdatePod(namespace string, pod *corev1.Pod) error { 65 _, err := p.kubeClient.CoreV1().Pods(namespace).Update(context.TODO(), pod, metav1.UpdateOptions{}) 66 recordMetrics(namespace, "Pod", pod.GetName(), "UPDATE", err, p.metricsRecorder) 67 if err != nil { 68 return err 69 } 70 p.logger.WithField("namespace", namespace).WithField("pod", pod.Name).Debugf("pod updated") 71 return nil 72 } 73 func (p *PodService) CreateOrUpdatePod(namespace string, pod *corev1.Pod) error { 74 storedPod, err := p.GetPod(namespace, pod.Name) 75 if err != nil { 76 // If no resource we need to create. 77 if errors.IsNotFound(err) { 78 return p.CreatePod(namespace, pod) 79 } 80 return err 81 } 82 83 // Already exists, need to Update. 84 // Set the correct resource version to ensure we are on the latest version. This way the only valid 85 // namespace is our spec(https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency), 86 // we will replace the current namespace state. 87 pod.ResourceVersion = storedPod.ResourceVersion 88 return p.UpdatePod(namespace, pod) 89 } 90 91 func (p *PodService) DeletePod(namespace string, name string) error { 92 err := p.kubeClient.CoreV1().Pods(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) 93 recordMetrics(namespace, "Pod", name, "DELETE", err, p.metricsRecorder) 94 return err 95 } 96 97 func (p *PodService) ListPods(namespace string) (*corev1.PodList, error) { 98 pods, err := p.kubeClient.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{}) 99 recordMetrics(namespace, "Pod", metrics.NOT_APPLICABLE, "LIST", err, p.metricsRecorder) 100 return pods, err 101 } 102 103 // PatchStringValue specifies a patch operation for a string. 104 type PatchStringValue struct { 105 Op string `json:"op"` 106 Path string `json:"path"` 107 Value interface{} `json:"value"` 108 } 109 110 func (p *PodService) UpdatePodLabels(namespace, podName string, labels map[string]string) error { 111 p.logger.Infof("Update pod label, namespace: %s, pod name: %s, labels: %v", namespace, podName, labels) 112 113 var payloads []interface{} 114 for labelKey, labelValue := range labels { 115 payload := PatchStringValue{ 116 Op: "replace", 117 Path: "/metadata/labels/" + labelKey, 118 Value: labelValue, 119 } 120 payloads = append(payloads, payload) 121 } 122 payloadBytes, _ := json.Marshal(payloads) 123 124 _, err := p.kubeClient.CoreV1().Pods(namespace).Patch(context.TODO(), podName, types.JSONPatchType, payloadBytes, metav1.PatchOptions{}) 125 recordMetrics(namespace, "Pod", podName, "PATCH", err, p.metricsRecorder) 126 if err != nil { 127 p.logger.Errorf("Update pod labels failed, namespace: %s, pod name: %s, error: %v", namespace, podName, err) 128 } 129 return err 130 }