Minor fixes

This commit is contained in:
jennybuckley 2019-02-01 11:55:18 -08:00 committed by Antoine Pelisse
parent 5949154ec5
commit 6b2e4682fe
18 changed files with 204 additions and 232 deletions

View File

@ -176,7 +176,8 @@ func collectResourcePaths(t *testing.T, resourcename string, path *field.Path, n
case reflect.Ptr: case reflect.Ptr:
resourcePaths.Insert(collectResourcePaths(t, resourcename, path, name, tp.Elem()).List()...) resourcePaths.Insert(collectResourcePaths(t, resourcename, path, name, tp.Elem()).List()...)
case reflect.Struct: case reflect.Struct:
// Specifically skip ObjectMeta because it has recursive types // ObjectMeta is generic and therefore should never have a field with a specific resource's name;
// it contains cycles so it's easiest to just skip it.
if name == "ObjectMeta" { if name == "ObjectMeta" {
break break
} }

View File

@ -49,6 +49,7 @@ func TestDefaulting(t *testing.T) {
{Group: "", Version: "v1", Kind: "ConfigMap"}: {}, {Group: "", Version: "v1", Kind: "ConfigMap"}: {},
{Group: "", Version: "v1", Kind: "ConfigMapList"}: {}, {Group: "", Version: "v1", Kind: "ConfigMapList"}: {},
{Group: "", Version: "v1", Kind: "Endpoints"}: {}, {Group: "", Version: "v1", Kind: "Endpoints"}: {},
{Group: "", Version: "v1", Kind: "EndpointsList"}: {},
{Group: "", Version: "v1", Kind: "Namespace"}: {}, {Group: "", Version: "v1", Kind: "Namespace"}: {},
{Group: "", Version: "v1", Kind: "NamespaceList"}: {}, {Group: "", Version: "v1", Kind: "NamespaceList"}: {},
{Group: "", Version: "v1", Kind: "Node"}: {}, {Group: "", Version: "v1", Kind: "Node"}: {},

View File

@ -247,6 +247,8 @@ func collectSecretPaths(t *testing.T, path *field.Path, name string, tp reflect.
case reflect.Ptr: case reflect.Ptr:
secretPaths.Insert(collectSecretPaths(t, path, name, tp.Elem()).List()...) secretPaths.Insert(collectSecretPaths(t, path, name, tp.Elem()).List()...)
case reflect.Struct: case reflect.Struct:
// ObjectMeta should not have any field with the word "secret" in it;
// it contains cycles so it's easiest to just skip it.
if name == "ObjectMeta" { if name == "ObjectMeta" {
break break
} }

View File

@ -340,7 +340,8 @@ func collectResourcePaths(t *testing.T, resourcename string, path *field.Path, n
case reflect.Ptr: case reflect.Ptr:
resourcePaths.Insert(collectResourcePaths(t, resourcename, path, name, tp.Elem()).List()...) resourcePaths.Insert(collectResourcePaths(t, resourcename, path, name, tp.Elem()).List()...)
case reflect.Struct: case reflect.Struct:
// Specifically skip ObjectMeta because it has recursive types // ObjectMeta is generic and therefore should never have a field with a specific resource's name;
// it contains cycles so it's easiest to just skip it.
if name == "ObjectMeta" { if name == "ObjectMeta" {
break break
} }

View File

@ -206,6 +206,16 @@ message ExportOptions {
// To understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff // To understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff
message Fields { message Fields {
// Map stores a set of fields in a data structure like a Trie. // Map stores a set of fields in a data structure like a Trie.
//
// Each key is either a '.' representing the field itself, and will always map to an empty set,
// or a string representing a sub-field or item. The string will follow one of these four formats:
// 'f:<name>', where <name> is the name of a field in a struct, or key in a map
// 'v:<value>', where <value> is the exact json formatted value of a list item
// 'i:<index>', where <index> is position of a item in a list
// 'k:<keys>', where <keys> is a map of a list item's key fields to their unique values
// If a key maps to an empty Fields value, the field that key represents is part of the set.
//
// The exact format is defined in k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal
map<string, Fields> map = 1; map<string, Fields> map = 1;
} }

View File

@ -64,7 +64,7 @@ type Object interface {
GetClusterName() string GetClusterName() string
SetClusterName(clusterName string) SetClusterName(clusterName string)
GetManagedFields() []ManagedFieldsEntry GetManagedFields() []ManagedFieldsEntry
SetManagedFields(lastApplied []ManagedFieldsEntry) SetManagedFields(managedFields []ManagedFieldsEntry)
} }
// ListMetaAccessor retrieves the list interface from an object // ListMetaAccessor retrieves the list interface from an object
@ -170,14 +170,7 @@ func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) {
} }
func (meta *ObjectMeta) GetClusterName() string { return meta.ClusterName } func (meta *ObjectMeta) GetClusterName() string { return meta.ClusterName }
func (meta *ObjectMeta) SetClusterName(clusterName string) { meta.ClusterName = clusterName } func (meta *ObjectMeta) SetClusterName(clusterName string) { meta.ClusterName = clusterName }
func (meta *ObjectMeta) GetManagedFields() []ManagedFieldsEntry { return meta.ManagedFields }
func (meta *ObjectMeta) GetManagedFields() []ManagedFieldsEntry { func (meta *ObjectMeta) SetManagedFields(managedFields []ManagedFieldsEntry) {
return meta.ManagedFields meta.ManagedFields = managedFields
}
func (meta *ObjectMeta) SetManagedFields(ManagedFields []ManagedFieldsEntry) {
meta.ManagedFields = make([]ManagedFieldsEntry, len(ManagedFields))
for i, value := range ManagedFields {
meta.ManagedFields[i] = value
}
} }

View File

@ -1079,5 +1079,15 @@ const (
// To understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff // To understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff
type Fields struct { type Fields struct {
// Map stores a set of fields in a data structure like a Trie. // Map stores a set of fields in a data structure like a Trie.
//
// Each key is either a '.' representing the field itself, and will always map to an empty set,
// or a string representing a sub-field or item. The string will follow one of these four formats:
// 'f:<name>', where <name> is the name of a field in a struct, or key in a map
// 'v:<value>', where <value> is the exact json formatted value of a list item
// 'i:<index>', where <index> is position of a item in a list
// 'k:<keys>', where <keys> is a map of a list item's key fields to their unique values
// If a key maps to an empty Fields value, the field that key represents is part of the set.
//
// The exact format is defined in k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal
Map map[string]Fields `json:",inline" protobuf:"bytes,1,rep,name=map"` Map map[string]Fields `json:",inline" protobuf:"bytes,1,rep,name=map"`
} }

View File

@ -8,6 +8,7 @@ go_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",

View File

@ -21,6 +21,7 @@ import (
"time" "time"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
@ -82,9 +83,19 @@ func NewCRDFieldManager(objectConverter runtime.ObjectConvertor, objectDefaulter
// use-case), and simply updates the managed fields in the output // use-case), and simply updates the managed fields in the output
// object. // object.
func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (runtime.Object, error) { func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (runtime.Object, error) {
// If the object doesn't have metadata, we should just return without trying to
// set the managedFields at all, so creates/updates/patches will work normally.
if _, err := meta.Accessor(newObj); err != nil {
return newObj, nil
}
// First try to decode the managed fields provided in the update,
// This is necessary to allow directly updating managed fields.
managed, err := internal.DecodeObjectManagedFields(newObj) managed, err := internal.DecodeObjectManagedFields(newObj)
// If the managed field is empty or we failed to decode it, // If the managed field is empty or we failed to decode it,
// let's try the live object // let's try the live object. This is to prevent clients who
// don't understand managedFields from deleting it accidentally.
if err != nil || len(managed) == 0 { if err != nil || len(managed) == 0 {
managed, err = internal.DecodeObjectManagedFields(liveObj) managed, err = internal.DecodeObjectManagedFields(liveObj)
if err != nil { if err != nil {
@ -99,13 +110,8 @@ func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (r
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to convert live object to proper version: %v", err) return nil, fmt.Errorf("failed to convert live object to proper version: %v", err)
} }
if err := internal.RemoveObjectManagedFields(liveObjVersioned); err != nil { internal.RemoveObjectManagedFields(liveObjVersioned)
return nil, fmt.Errorf("failed to remove managed fields from live obj: %v", err) internal.RemoveObjectManagedFields(newObjVersioned)
}
if err := internal.RemoveObjectManagedFields(newObjVersioned); err != nil {
return nil, fmt.Errorf("failed to remove managed fields from new obj: %v", err)
}
newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned) newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create typed new object: %v", err) return nil, fmt.Errorf("failed to create typed new object: %v", err)
@ -135,19 +141,23 @@ func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (r
// Apply is used when server-side apply is called, as it merges the // Apply is used when server-side apply is called, as it merges the
// object and update the managed fields. // object and update the managed fields.
func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, force bool) (runtime.Object, error) { func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, force bool) (runtime.Object, error) {
// If the object doesn't have metadata, apply isn't allowed.
if _, err := meta.Accessor(liveObj); err != nil {
return nil, fmt.Errorf("couldn't get accessor: %v", err)
}
managed, err := internal.DecodeObjectManagedFields(liveObj) managed, err := internal.DecodeObjectManagedFields(liveObj)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to decode managed fields: %v", err) return nil, fmt.Errorf("failed to decode managed fields: %v", err)
} }
// We can assume that patchObj is already on the proper version: // We can assume that patchObj is already on the proper version:
// it shouldn't have to be converted so that it's not defaulted. // it shouldn't have to be converted so that it's not defaulted.
// TODO (jennybuckley): Explicitly checkt that patchObj is in the proper version.
liveObjVersioned, err := f.toVersioned(liveObj) liveObjVersioned, err := f.toVersioned(liveObj)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to convert live object to proper version: %v", err) return nil, fmt.Errorf("failed to convert live object to proper version: %v", err)
} }
if err := internal.RemoveObjectManagedFields(liveObjVersioned); err != nil { internal.RemoveObjectManagedFields(liveObjVersioned)
return nil, fmt.Errorf("failed to remove managed fields from live obj: %v", err)
}
patchObjTyped, err := f.typeConverter.YAMLToTyped(patch) patchObjTyped, err := f.typeConverter.YAMLToTyped(patch)
if err != nil { if err != nil {
@ -211,5 +221,5 @@ func (f *FieldManager) buildManagerInfo(prefix string, operation metav1.ManagedF
if managerInfo.Manager == "" { if managerInfo.Manager == "" {
managerInfo.Manager = "unknown" managerInfo.Manager = "unknown"
} }
return internal.DecodeManager(&managerInfo) return internal.BuildManagerIdentifier(&managerInfo)
} }

View File

@ -30,13 +30,12 @@ import (
// RemoveObjectManagedFields removes the ManagedFields from the object // RemoveObjectManagedFields removes the ManagedFields from the object
// before we merge so that it doesn't appear in the ManagedFields // before we merge so that it doesn't appear in the ManagedFields
// recursively. // recursively.
func RemoveObjectManagedFields(obj runtime.Object) error { func RemoveObjectManagedFields(obj runtime.Object) {
accessor, err := meta.Accessor(obj) accessor, err := meta.Accessor(obj)
if err != nil { if err != nil {
return fmt.Errorf("couldn't get accessor: %v", err) panic(fmt.Sprintf("couldn't get accessor: %v", err))
} }
accessor.SetManagedFields(nil) accessor.SetManagedFields(nil)
return nil
} }
// DecodeObjectManagedFields extracts and converts the objects ManagedFields into a fieldpath.ManagedFields. // DecodeObjectManagedFields extracts and converts the objects ManagedFields into a fieldpath.ManagedFields.
@ -46,7 +45,7 @@ func DecodeObjectManagedFields(from runtime.Object) (fieldpath.ManagedFields, er
} }
accessor, err := meta.Accessor(from) accessor, err := meta.Accessor(from)
if err != nil { if err != nil {
return nil, fmt.Errorf("couldn't get accessor: %v", err) panic(fmt.Sprintf("couldn't get accessor: %v", err))
} }
managed, err := decodeManagedFields(accessor.GetManagedFields()) managed, err := decodeManagedFields(accessor.GetManagedFields())
@ -60,7 +59,7 @@ func DecodeObjectManagedFields(from runtime.Object) (fieldpath.ManagedFields, er
func EncodeObjectManagedFields(obj runtime.Object, fields fieldpath.ManagedFields) error { func EncodeObjectManagedFields(obj runtime.Object, fields fieldpath.ManagedFields) error {
accessor, err := meta.Accessor(obj) accessor, err := meta.Accessor(obj)
if err != nil { if err != nil {
return fmt.Errorf("couldn't get accessor: %v", err) panic(fmt.Sprintf("couldn't get accessor: %v", err))
} }
managed, err := encodeManagedFields(fields) managed, err := encodeManagedFields(fields)
@ -77,7 +76,7 @@ func EncodeObjectManagedFields(obj runtime.Object, fields fieldpath.ManagedField
func decodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (managedFields fieldpath.ManagedFields, err error) { func decodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (managedFields fieldpath.ManagedFields, err error) {
managedFields = make(map[string]*fieldpath.VersionedSet, len(encodedManagedFields)) managedFields = make(map[string]*fieldpath.VersionedSet, len(encodedManagedFields))
for _, encodedVersionedSet := range encodedManagedFields { for _, encodedVersionedSet := range encodedManagedFields {
manager, err := DecodeManager(&encodedVersionedSet) manager, err := BuildManagerIdentifier(&encodedVersionedSet)
if err != nil { if err != nil {
return nil, fmt.Errorf("error decoding manager from %v: %v", encodedVersionedSet, err) return nil, fmt.Errorf("error decoding manager from %v: %v", encodedVersionedSet, err)
} }
@ -89,8 +88,8 @@ func decodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (mana
return managedFields, nil return managedFields, nil
} }
// DecodeManager creates a manager identifier string from a ManagedFieldsEntry // BuildManagerIdentifier creates a manager identifier string from a ManagedFieldsEntry
func DecodeManager(encodedManager *metav1.ManagedFieldsEntry) (manager string, err error) { func BuildManagerIdentifier(encodedManager *metav1.ManagedFieldsEntry) (manager string, err error) {
encodedManagerCopy := *encodedManager encodedManagerCopy := *encodedManager
// Never include the fields in the manager identifier // Never include the fields in the manager identifier

View File

@ -25,8 +25,8 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
// TestRoundTripManagedFields will roundtrip ManagedFields from the format used by // TestRoundTripManagedFields will roundtrip ManagedFields from the wire format
// sigs.k8s.io/structured-merge-diff to the wire format (api format) and back // (api format) to the format used by sigs.k8s.io/structured-merge-diff and back
func TestRoundTripManagedFields(t *testing.T) { func TestRoundTripManagedFields(t *testing.T) {
tests := []string{ tests := []string{
`- apiVersion: v1 `- apiVersion: v1
@ -153,7 +153,7 @@ func TestRoundTripManagedFields(t *testing.T) {
} }
} }
func TestDecodeManager(t *testing.T) { func TestBuildManagerIdentifier(t *testing.T) {
tests := []struct { tests := []struct {
managedFieldsEntry string managedFieldsEntry string
expected string expected string
@ -188,7 +188,7 @@ time: "2001-02-03T04:05:06Z"
if err := yaml.Unmarshal([]byte(test.managedFieldsEntry), &unmarshaled); err != nil { if err := yaml.Unmarshal([]byte(test.managedFieldsEntry), &unmarshaled); err != nil {
t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) t.Fatalf("did not expect yaml unmarshalling error but got: %v", err)
} }
decoded, err := DecodeManager(&unmarshaled) decoded, err := BuildManagerIdentifier(&unmarshaled)
if err != nil { if err != nil {
t.Fatalf("did not expect decoding error but got: %v", err) t.Fatalf("did not expect decoding error but got: %v", err)
} }

View File

@ -20,20 +20,20 @@ import "testing"
func TestPathElementRoundTrip(t *testing.T) { func TestPathElementRoundTrip(t *testing.T) {
tests := []string{ tests := []string{
"i:0", `i:0`,
"i:1234", `i:1234`,
"f:", `f:`,
"f:spec", `f:spec`,
"f:more-complicated-string", `f:more-complicated-string`,
"k:{\"name\":\"my-container\"}", `k:{"name":"my-container"}`,
"k:{\"port\":\"8080\",\"protocol\":\"TCP\"}", `k:{"port":"8080","protocol":"TCP"}`,
"k:{\"optionalField\":null}", `k:{"optionalField":null}`,
"k:{\"jsonField\":{\"A\":1,\"B\":null,\"C\":\"D\",\"E\":{\"F\":\"G\"}}}", `k:{"jsonField":{"A":1,"B":null,"C":"D","E":{"F":"G"}}}`,
"k:{\"listField\":[\"1\",\"2\",\"3\"]}", `k:{"listField":["1","2","3"]}`,
"v:null", `v:null`,
"v:\"some-string\"", `v:"some-string"`,
"v:1234", `v:1234`,
"v:{\"some\":\"json\"}", `v:{"some":"json"}`,
} }
for _, test := range tests { for _, test := range tests {
@ -62,15 +62,15 @@ func TestPathElementIgnoreUnknown(t *testing.T) {
func TestNewPathElementError(t *testing.T) { func TestNewPathElementError(t *testing.T) {
tests := []string{ tests := []string{
"", ``,
"no-colon", `no-colon`,
"i:index is not a number", `i:index is not a number`,
"i:1.23", `i:1.23`,
"i:", `i:`,
"v:invalid json", `v:invalid json`,
"v:", `v:`,
"k:invalid json", `k:invalid json`,
"k:{\"name\":invalid}", `k:{"name":invalid}`,
} }
for _, test := range tests { for _, test := range tests {

View File

@ -43,6 +43,7 @@ type TypeConverter interface {
// //
// Note that this is not going to be sufficient for converting to/from // Note that this is not going to be sufficient for converting to/from
// CRDs that have a schema defined (we don't support that schema yet). // CRDs that have a schema defined (we don't support that schema yet).
// TODO(jennybuckley): Use the schema provided by a CRD if it exists.
type DeducedTypeConverter struct{} type DeducedTypeConverter struct{}
var _ TypeConverter = DeducedTypeConverter{} var _ TypeConverter = DeducedTypeConverter{}

View File

@ -17,8 +17,10 @@ limitations under the License.
package internal_test package internal_test
import ( import (
"fmt"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strings"
"testing" "testing"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
@ -30,7 +32,7 @@ import (
var fakeSchema = prototesting.Fake{ var fakeSchema = prototesting.Fake{
Path: filepath.Join( Path: filepath.Join(
"..", "..", "..", "..", "..", "..", "..", "..", "..", strings.Repeat(".."+string(filepath.Separator), 9),
"api", "openapi-spec", "swagger.json"), "api", "openapi-spec", "swagger.json"),
} }
@ -49,7 +51,15 @@ func TestTypeConverter(t *testing.T) {
t.Fatalf("Failed to build TypeConverter: %v", err) t.Fatalf("Failed to build TypeConverter: %v", err)
} }
y := ` dtc := internal.DeducedTypeConverter{}
testCases := []struct {
name string
yaml string
}{
{
name: "apps/v1.Deployment",
yaml: `
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
@ -69,8 +79,61 @@ spec:
containers: containers:
- name: nginx - name: nginx
image: nginx:1.15.4 image: nginx:1.15.4
` `,
}, {
name: "extensions/v1beta1.Deployment",
yaml: `
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.15.4
`,
}, {
name: "v1.Pod",
yaml: `
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.15.4
`,
},
}
for _, testCase := range testCases {
t.Run(fmt.Sprintf("%v ObjectToTyped with TypeConverter", testCase.name), func(t *testing.T) {
testObjectToTyped(t, tc, testCase.yaml)
})
t.Run(fmt.Sprintf("%v YAMLToTyped with TypeConverter", testCase.name), func(t *testing.T) {
testYAMLToTyped(t, tc, testCase.yaml)
})
t.Run(fmt.Sprintf("%v ObjectToTyped with DeducedTypeConverter", testCase.name), func(t *testing.T) {
testObjectToTyped(t, dtc, testCase.yaml)
})
}
}
func testObjectToTyped(t *testing.T, tc internal.TypeConverter, y string) {
obj := &unstructured.Unstructured{Object: map[string]interface{}{}} obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil { if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil {
t.Fatalf("Failed to parse yaml object: %v", err) t.Fatalf("Failed to parse yaml object: %v", err)
@ -90,12 +153,18 @@ Original object:
Final object: Final object:
%#v`, obj, newObj) %#v`, obj, newObj)
} }
}
func testYAMLToTyped(t *testing.T, tc internal.TypeConverter, y string) {
obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil {
t.Fatalf("Failed to parse yaml object: %v", err)
}
yamlTyped, err := tc.YAMLToTyped([]byte(y)) yamlTyped, err := tc.YAMLToTyped([]byte(y))
if err != nil { if err != nil {
t.Fatalf("Failed to convert yaml to typed: %v", err) t.Fatalf("Failed to convert yaml to typed: %v", err)
} }
newObj, err = tc.TypedToObject(yamlTyped) newObj, err := tc.TypedToObject(yamlTyped)
if err != nil { if err != nil {
t.Fatalf("Failed to convert typed to object: %v", err) t.Fatalf("Failed to convert typed to object: %v", err)
} }

View File

@ -795,9 +795,6 @@ func TestPatchWithVersionConflictThenAdmissionFailure(t *testing.T) {
tc.Run(t) tc.Run(t)
} }
// TODO: Add test case for "apply with existing uid" verify it gives a conflict error,
// not a creation or an authz creation forbidden message
func TestHasUID(t *testing.T) { func TestHasUID(t *testing.T) {
testcases := []struct { testcases := []struct {
obj runtime.Object obj runtime.Object

View File

@ -25,10 +25,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
genericapitesting "k8s.io/apiserver/pkg/endpoints/testing" genericapitesting "k8s.io/apiserver/pkg/endpoints/testing"
genericfeatures "k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/registry/rest"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
) )
func TestPatch(t *testing.T) { func TestPatch(t *testing.T) {
@ -152,124 +149,3 @@ func TestPatchRequiresMatchingName(t *testing.T) {
t.Errorf("Unexpected response %#v", response) t.Errorf("Unexpected response %#v", response)
} }
} }
func TestPatchApply(t *testing.T) {
t.Skip("apply is being refactored")
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
storage := map[string]rest.Storage{}
item := &genericapitesting.Simple{
ObjectMeta: metav1.ObjectMeta{
Name: "id",
Namespace: "",
UID: "uid",
},
Other: "bar",
}
simpleStorage := SimpleRESTStorage{item: *item}
storage["simple"] = &simpleStorage
handler := handle(storage)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
request, err := http.NewRequest(
"PATCH",
server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/id",
bytes.NewReader([]byte(`{"metadata":{"name":"id"}, "labels": {"test": "yes"}}`)),
)
request.Header.Set("Content-Type", "application/apply-patch+yaml")
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusOK {
t.Errorf("Unexpected response %#v", response)
}
if simpleStorage.updated.Labels["test"] != "yes" {
t.Errorf(`Expected labels to have "test": "yes", found %q`, simpleStorage.updated.Labels["test"])
}
if simpleStorage.updated.Other != "bar" {
t.Errorf(`Merge should have kept initial "bar" value for Other: %v`, simpleStorage.updated.Other)
}
if len(simpleStorage.updated.ObjectMeta.ManagedFields) == 0 {
t.Errorf(`Expected managedFields field to be set, but is empty`)
}
}
func TestApplyAddsGVK(t *testing.T) {
t.Skip("apply is being refactored")
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
storage := map[string]rest.Storage{}
item := &genericapitesting.Simple{
ObjectMeta: metav1.ObjectMeta{
Name: "id",
Namespace: "",
UID: "uid",
},
Other: "bar",
}
simpleStorage := SimpleRESTStorage{item: *item}
storage["simple"] = &simpleStorage
handler := handle(storage)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
request, err := http.NewRequest(
"PATCH",
server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/id",
bytes.NewReader([]byte(`{"metadata":{"name":"id"}, "labels": {"test": "yes"}}`)),
)
request.Header.Set("Content-Type", "application/apply-patch+yaml")
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusOK {
t.Errorf("Unexpected response %#v", response)
}
// TODO: Need to fix this
expected := `{"apiVersion":"test.group/version","kind":"Simple","labels":{"test":"yes"},"metadata":{"name":"id"}}`
if simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion != expected {
t.Errorf(
`Expected managedFields field to be %q, got %q`,
expected,
simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion,
)
}
}
func TestApplyCreatesWithManagedFields(t *testing.T) {
t.Skip("apply is being refactored")
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{}
storage["simple"] = &simpleStorage
handler := handle(storage)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
request, err := http.NewRequest(
"PATCH",
server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/id",
bytes.NewReader([]byte(`{"metadata":{"name":"id"}, "labels": {"test": "yes"}}`)),
)
request.Header.Set("Content-Type", "application/apply-patch+yaml")
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusOK {
t.Errorf("Unexpected response %#v", response)
}
// TODO: Need to fix this
expected := `{"apiVersion":"test.group/version","kind":"Simple","labels":{"test":"yes"},"metadata":{"name":"id"}}`
if simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion != expected {
t.Errorf(
`Expected managedFields field to be %q, got %q`,
expected,
simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion,
)
}
}

View File

@ -84,7 +84,7 @@ const (
DryRun utilfeature.Feature = "DryRun" DryRun utilfeature.Feature = "DryRun"
// owner: @apelisse, @lavalamp // owner: @apelisse, @lavalamp
// alpha: v1.11 // alpha: v1.14
// //
// Server-side apply. Merging happens on the server. // Server-side apply. Merging happens on the server.
ServerSideApply utilfeature.Feature = "ServerSideApply" ServerSideApply utilfeature.Feature = "ServerSideApply"

View File

@ -30,6 +30,7 @@ import (
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch" "k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/authentication/request/bearertoken" "k8s.io/apiserver/pkg/authentication/request/bearertoken"
"k8s.io/apiserver/pkg/authentication/token/tokenfile" "k8s.io/apiserver/pkg/authentication/token/tokenfile"
@ -482,40 +483,40 @@ func TestRBAC(t *testing.T) {
{"limitrange-updater", "PUT", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK}, {"limitrange-updater", "PUT", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
}, },
}, },
// { {
// bootstrapRoles: bootstrapRoles{ bootstrapRoles: bootstrapRoles{
// clusterRoles: []rbacapi.ClusterRole{ clusterRoles: []rbacapi.ClusterRole{
// { {
// ObjectMeta: metav1.ObjectMeta{Name: "allow-all"}, ObjectMeta: metav1.ObjectMeta{Name: "allow-all"},
// Rules: []rbacapi.PolicyRule{ruleAllowAll}, Rules: []rbacapi.PolicyRule{ruleAllowAll},
// }, },
// { {
// ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"}, ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"},
// Rules: []rbacapi.PolicyRule{ Rules: []rbacapi.PolicyRule{
// rbacapi.NewRule("patch").Groups("").Resources("limitranges").RuleOrDie(), rbacapi.NewRule("patch").Groups("").Resources("limitranges").RuleOrDie(),
// }, },
// }, },
// }, },
// clusterRoleBindings: []rbacapi.ClusterRoleBinding{ clusterRoleBindings: []rbacapi.ClusterRoleBinding{
// { {
// ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"}, ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"},
// Subjects: []rbacapi.Subject{ Subjects: []rbacapi.Subject{
// {Kind: "User", Name: "limitrange-patcher"}, {Kind: "User", Name: "limitrange-patcher"},
// }, },
// RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "patch-limitranges"}, RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "patch-limitranges"},
// }, },
// }, },
// }, },
// requests: []request{ requests: []request{
// // Create the namespace used later in the test // Create the namespace used later in the test
// {superUser, "POST", "", "namespaces", "", "", limitRangeNamespace, http.StatusCreated}, {superUser, "POST", "", "namespaces", "", "", limitRangeNamespace, http.StatusCreated},
// {"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusForbidden}, {"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusForbidden},
// {superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusCreated}, {superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusCreated},
// {superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK}, {superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
// {"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK}, {"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
// }, },
// }, },
} }
for i, tc := range tests { for i, tc := range tests {
@ -535,6 +536,7 @@ func TestRBAC(t *testing.T) {
"limitrange-patcher": {Name: "limitrange-patcher"}, "limitrange-patcher": {Name: "limitrange-patcher"},
"user-with-no-permissions": {Name: "user-with-no-permissions"}, "user-with-no-permissions": {Name: "user-with-no-permissions"},
})) }))
masterConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig()
_, s, closeFn := framework.RunAMaster(masterConfig) _, s, closeFn := framework.RunAMaster(masterConfig)
defer closeFn() defer closeFn()
@ -569,11 +571,10 @@ func TestRBAC(t *testing.T) {
} }
req, err := http.NewRequest(r.verb, s.URL+path, body) req, err := http.NewRequest(r.verb, s.URL+path, body)
// TODO: Un-comment this when Apply works again if r.verb == "PATCH" {
// if r.verb == "PATCH" { // For patch operations, use the apply content type
// // For patch operations, use the apply content type req.Header.Add("Content-Type", string(types.ApplyPatchType))
// req.Header.Add("Content-Type", string(types.ApplyPatchType)) }
// }
if err != nil { if err != nil {
t.Fatalf("failed to create request: %v", err) t.Fatalf("failed to create request: %v", err)