/* Copyright 2022 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package mockkubeapiserver import ( "fmt" "io" "net/http" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" ) // patchResource is a request to patch a single resource type patchResource struct { resourceRequestBase } // Run serves the http request func (req *patchResource) Run(s *MockKubeAPIServer) error { gr := schema.GroupResource{Group: req.Group, Resource: req.Resource} id := types.NamespacedName{Namespace: req.Namespace, Name: req.Name} var existing *unstructured.Unstructured objects := s.objects[gr] if objects != nil { existing = objects.Objects[id] } bodyBytes, err := io.ReadAll(req.r.Body) if err != nil { return err } body := &unstructured.Unstructured{} if err := body.UnmarshalJSON(bodyBytes); err != nil { return fmt.Errorf("failed to parse payload: %w", err) } // TODO: We need to implement patch properly klog.Infof("patch request %#v", string(bodyBytes)) if existing == nil { // TODO: Only if server-side-apply if objects == nil { objects = &objectList{ GroupResource: gr, Objects: make(map[types.NamespacedName]*unstructured.Unstructured), } s.objects[gr] = objects } if req.SubResource != "" { // TODO: Is this correct for server-side-apply? return req.writeErrorResponse(http.StatusNotFound) } patched := body objects.Objects[id] = patched s.objectChanged(patched) return req.writeResponse(patched) } if req.SubResource == "" { if err := applyPatch(existing.Object, body.Object); err != nil { klog.Warningf("error from patch: %v", err) return err } } else { // TODO: We need to implement put properly return fmt.Errorf("unknown subresource %q", req.SubResource) } objects.Objects[id] = existing s.objectChanged(existing) return req.writeResponse(existing) } func applyPatch(existing, patch map[string]interface{}) error { for k, patchValue := range patch { existingValue := existing[k] switch patchValue := patchValue.(type) { case string, int64: existing[k] = patchValue case map[string]interface{}: if existingValue == nil { existing[k] = patchValue } else { existingMap, ok := existingValue.(map[string]interface{}) if !ok { return fmt.Errorf("unexpected type mismatch, expected map got %T", existingValue) } if err := applyPatch(existingMap, patchValue); err != nil { return err } } default: return fmt.Errorf("type %T not handled in patch", patchValue) } } return nil }