kops/pkg/applylib/mocks/mockkubeapiserver/patchresource.go

118 lines
3.1 KiB
Go

/*
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
}