mirror of https://github.com/k3s-io/k3s
Minor fixes
parent
5949154ec5
commit
6b2e4682fe
|
@ -176,7 +176,8 @@ func collectResourcePaths(t *testing.T, resourcename string, path *field.Path, n
|
|||
case reflect.Ptr:
|
||||
resourcePaths.Insert(collectResourcePaths(t, resourcename, path, name, tp.Elem()).List()...)
|
||||
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" {
|
||||
break
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ func TestDefaulting(t *testing.T) {
|
|||
{Group: "", Version: "v1", Kind: "ConfigMap"}: {},
|
||||
{Group: "", Version: "v1", Kind: "ConfigMapList"}: {},
|
||||
{Group: "", Version: "v1", Kind: "Endpoints"}: {},
|
||||
{Group: "", Version: "v1", Kind: "EndpointsList"}: {},
|
||||
{Group: "", Version: "v1", Kind: "Namespace"}: {},
|
||||
{Group: "", Version: "v1", Kind: "NamespaceList"}: {},
|
||||
{Group: "", Version: "v1", Kind: "Node"}: {},
|
||||
|
|
|
@ -247,6 +247,8 @@ func collectSecretPaths(t *testing.T, path *field.Path, name string, tp reflect.
|
|||
case reflect.Ptr:
|
||||
secretPaths.Insert(collectSecretPaths(t, path, name, tp.Elem()).List()...)
|
||||
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" {
|
||||
break
|
||||
}
|
||||
|
|
|
@ -340,7 +340,8 @@ func collectResourcePaths(t *testing.T, resourcename string, path *field.Path, n
|
|||
case reflect.Ptr:
|
||||
resourcePaths.Insert(collectResourcePaths(t, resourcename, path, name, tp.Elem()).List()...)
|
||||
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" {
|
||||
break
|
||||
}
|
||||
|
|
|
@ -206,6 +206,16 @@ message ExportOptions {
|
|||
// To understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff
|
||||
message Fields {
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ type Object interface {
|
|||
GetClusterName() string
|
||||
SetClusterName(clusterName string)
|
||||
GetManagedFields() []ManagedFieldsEntry
|
||||
SetManagedFields(lastApplied []ManagedFieldsEntry)
|
||||
SetManagedFields(managedFields []ManagedFieldsEntry)
|
||||
}
|
||||
|
||||
// ListMetaAccessor retrieves the list interface from an object
|
||||
|
@ -168,16 +168,9 @@ func (meta *ObjectMeta) GetOwnerReferences() []OwnerReference { return m
|
|||
func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) {
|
||||
meta.OwnerReferences = references
|
||||
}
|
||||
func (meta *ObjectMeta) GetClusterName() string { return meta.ClusterName }
|
||||
func (meta *ObjectMeta) SetClusterName(clusterName string) { meta.ClusterName = clusterName }
|
||||
|
||||
func (meta *ObjectMeta) GetManagedFields() []ManagedFieldsEntry {
|
||||
return meta.ManagedFields
|
||||
}
|
||||
|
||||
func (meta *ObjectMeta) SetManagedFields(ManagedFields []ManagedFieldsEntry) {
|
||||
meta.ManagedFields = make([]ManagedFieldsEntry, len(ManagedFields))
|
||||
for i, value := range ManagedFields {
|
||||
meta.ManagedFields[i] = value
|
||||
}
|
||||
func (meta *ObjectMeta) GetClusterName() string { return meta.ClusterName }
|
||||
func (meta *ObjectMeta) SetClusterName(clusterName string) { meta.ClusterName = clusterName }
|
||||
func (meta *ObjectMeta) GetManagedFields() []ManagedFieldsEntry { return meta.ManagedFields }
|
||||
func (meta *ObjectMeta) SetManagedFields(managedFields []ManagedFieldsEntry) {
|
||||
meta.ManagedFields = managedFields
|
||||
}
|
||||
|
|
|
@ -1079,5 +1079,15 @@ const (
|
|||
// To understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff
|
||||
type Fields struct {
|
||||
// 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"`
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ go_library(
|
|||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//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/runtime:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"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
|
||||
// object.
|
||||
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)
|
||||
|
||||
// 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 {
|
||||
managed, err = internal.DecodeObjectManagedFields(liveObj)
|
||||
if err != nil {
|
||||
|
@ -99,13 +110,8 @@ func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (r
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert live object to proper version: %v", err)
|
||||
}
|
||||
if err := internal.RemoveObjectManagedFields(liveObjVersioned); err != nil {
|
||||
return nil, fmt.Errorf("failed to remove managed fields from live obj: %v", err)
|
||||
}
|
||||
if err := internal.RemoveObjectManagedFields(newObjVersioned); err != nil {
|
||||
return nil, fmt.Errorf("failed to remove managed fields from new obj: %v", err)
|
||||
}
|
||||
|
||||
internal.RemoveObjectManagedFields(liveObjVersioned)
|
||||
internal.RemoveObjectManagedFields(newObjVersioned)
|
||||
newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned)
|
||||
if err != nil {
|
||||
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
|
||||
// object and update the managed fields.
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
|
||||
}
|
||||
// We can assume that patchObj is already on the proper version:
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert live object to proper version: %v", err)
|
||||
}
|
||||
if err := internal.RemoveObjectManagedFields(liveObjVersioned); err != nil {
|
||||
return nil, fmt.Errorf("failed to remove managed fields from live obj: %v", err)
|
||||
}
|
||||
internal.RemoveObjectManagedFields(liveObjVersioned)
|
||||
|
||||
patchObjTyped, err := f.typeConverter.YAMLToTyped(patch)
|
||||
if err != nil {
|
||||
|
@ -211,5 +221,5 @@ func (f *FieldManager) buildManagerInfo(prefix string, operation metav1.ManagedF
|
|||
if managerInfo.Manager == "" {
|
||||
managerInfo.Manager = "unknown"
|
||||
}
|
||||
return internal.DecodeManager(&managerInfo)
|
||||
return internal.BuildManagerIdentifier(&managerInfo)
|
||||
}
|
||||
|
|
|
@ -30,13 +30,12 @@ import (
|
|||
// RemoveObjectManagedFields removes the ManagedFields from the object
|
||||
// before we merge so that it doesn't appear in the ManagedFields
|
||||
// recursively.
|
||||
func RemoveObjectManagedFields(obj runtime.Object) error {
|
||||
func RemoveObjectManagedFields(obj runtime.Object) {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't get accessor: %v", err)
|
||||
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||
}
|
||||
accessor.SetManagedFields(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
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())
|
||||
|
@ -60,7 +59,7 @@ func DecodeObjectManagedFields(from runtime.Object) (fieldpath.ManagedFields, er
|
|||
func EncodeObjectManagedFields(obj runtime.Object, fields fieldpath.ManagedFields) error {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
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)
|
||||
|
@ -77,7 +76,7 @@ func EncodeObjectManagedFields(obj runtime.Object, fields fieldpath.ManagedField
|
|||
func decodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (managedFields fieldpath.ManagedFields, err error) {
|
||||
managedFields = make(map[string]*fieldpath.VersionedSet, len(encodedManagedFields))
|
||||
for _, encodedVersionedSet := range encodedManagedFields {
|
||||
manager, err := DecodeManager(&encodedVersionedSet)
|
||||
manager, err := BuildManagerIdentifier(&encodedVersionedSet)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// DecodeManager creates a manager identifier string from a ManagedFieldsEntry
|
||||
func DecodeManager(encodedManager *metav1.ManagedFieldsEntry) (manager string, err error) {
|
||||
// BuildManagerIdentifier creates a manager identifier string from a ManagedFieldsEntry
|
||||
func BuildManagerIdentifier(encodedManager *metav1.ManagedFieldsEntry) (manager string, err error) {
|
||||
encodedManagerCopy := *encodedManager
|
||||
|
||||
// Never include the fields in the manager identifier
|
||||
|
|
|
@ -25,8 +25,8 @@ import (
|
|||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// TestRoundTripManagedFields will roundtrip ManagedFields from the format used by
|
||||
// sigs.k8s.io/structured-merge-diff to the wire format (api format) and back
|
||||
// TestRoundTripManagedFields will roundtrip ManagedFields from the wire format
|
||||
// (api format) to the format used by sigs.k8s.io/structured-merge-diff and back
|
||||
func TestRoundTripManagedFields(t *testing.T) {
|
||||
tests := []string{
|
||||
`- apiVersion: v1
|
||||
|
@ -153,7 +153,7 @@ func TestRoundTripManagedFields(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDecodeManager(t *testing.T) {
|
||||
func TestBuildManagerIdentifier(t *testing.T) {
|
||||
tests := []struct {
|
||||
managedFieldsEntry string
|
||||
expected string
|
||||
|
@ -188,7 +188,7 @@ time: "2001-02-03T04:05:06Z"
|
|||
if err := yaml.Unmarshal([]byte(test.managedFieldsEntry), &unmarshaled); err != nil {
|
||||
t.Fatalf("did not expect yaml unmarshalling error but got: %v", err)
|
||||
}
|
||||
decoded, err := DecodeManager(&unmarshaled)
|
||||
decoded, err := BuildManagerIdentifier(&unmarshaled)
|
||||
if err != nil {
|
||||
t.Fatalf("did not expect decoding error but got: %v", err)
|
||||
}
|
||||
|
|
|
@ -20,20 +20,20 @@ import "testing"
|
|||
|
||||
func TestPathElementRoundTrip(t *testing.T) {
|
||||
tests := []string{
|
||||
"i:0",
|
||||
"i:1234",
|
||||
"f:",
|
||||
"f:spec",
|
||||
"f:more-complicated-string",
|
||||
"k:{\"name\":\"my-container\"}",
|
||||
"k:{\"port\":\"8080\",\"protocol\":\"TCP\"}",
|
||||
"k:{\"optionalField\":null}",
|
||||
"k:{\"jsonField\":{\"A\":1,\"B\":null,\"C\":\"D\",\"E\":{\"F\":\"G\"}}}",
|
||||
"k:{\"listField\":[\"1\",\"2\",\"3\"]}",
|
||||
"v:null",
|
||||
"v:\"some-string\"",
|
||||
"v:1234",
|
||||
"v:{\"some\":\"json\"}",
|
||||
`i:0`,
|
||||
`i:1234`,
|
||||
`f:`,
|
||||
`f:spec`,
|
||||
`f:more-complicated-string`,
|
||||
`k:{"name":"my-container"}`,
|
||||
`k:{"port":"8080","protocol":"TCP"}`,
|
||||
`k:{"optionalField":null}`,
|
||||
`k:{"jsonField":{"A":1,"B":null,"C":"D","E":{"F":"G"}}}`,
|
||||
`k:{"listField":["1","2","3"]}`,
|
||||
`v:null`,
|
||||
`v:"some-string"`,
|
||||
`v:1234`,
|
||||
`v:{"some":"json"}`,
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
@ -62,15 +62,15 @@ func TestPathElementIgnoreUnknown(t *testing.T) {
|
|||
|
||||
func TestNewPathElementError(t *testing.T) {
|
||||
tests := []string{
|
||||
"",
|
||||
"no-colon",
|
||||
"i:index is not a number",
|
||||
"i:1.23",
|
||||
"i:",
|
||||
"v:invalid json",
|
||||
"v:",
|
||||
"k:invalid json",
|
||||
"k:{\"name\":invalid}",
|
||||
``,
|
||||
`no-colon`,
|
||||
`i:index is not a number`,
|
||||
`i:1.23`,
|
||||
`i:`,
|
||||
`v:invalid json`,
|
||||
`v:`,
|
||||
`k:invalid json`,
|
||||
`k:{"name":invalid}`,
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
|
|
@ -43,6 +43,7 @@ type TypeConverter interface {
|
|||
//
|
||||
// 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).
|
||||
// TODO(jennybuckley): Use the schema provided by a CRD if it exists.
|
||||
type DeducedTypeConverter struct{}
|
||||
|
||||
var _ TypeConverter = DeducedTypeConverter{}
|
||||
|
|
|
@ -17,8 +17,10 @@ limitations under the License.
|
|||
package internal_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
|
@ -30,7 +32,7 @@ import (
|
|||
|
||||
var fakeSchema = prototesting.Fake{
|
||||
Path: filepath.Join(
|
||||
"..", "..", "..", "..", "..", "..", "..", "..", "..",
|
||||
strings.Repeat(".."+string(filepath.Separator), 9),
|
||||
"api", "openapi-spec", "swagger.json"),
|
||||
}
|
||||
|
||||
|
@ -49,7 +51,15 @@ func TestTypeConverter(t *testing.T) {
|
|||
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
|
||||
kind: Deployment
|
||||
metadata:
|
||||
|
@ -69,8 +79,61 @@ spec:
|
|||
containers:
|
||||
- name: nginx
|
||||
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{}{}}
|
||||
if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil {
|
||||
t.Fatalf("Failed to parse yaml object: %v", err)
|
||||
|
@ -90,12 +153,18 @@ Original object:
|
|||
Final object:
|
||||
%#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))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert yaml to typed: %v", err)
|
||||
}
|
||||
newObj, err = tc.TypedToObject(yamlTyped)
|
||||
newObj, err := tc.TypedToObject(yamlTyped)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert typed to object: %v", err)
|
||||
}
|
||||
|
|
|
@ -795,9 +795,6 @@ func TestPatchWithVersionConflictThenAdmissionFailure(t *testing.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) {
|
||||
testcases := []struct {
|
||||
obj runtime.Object
|
||||
|
|
|
@ -25,10 +25,7 @@ import (
|
|||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
genericapitesting "k8s.io/apiserver/pkg/endpoints/testing"
|
||||
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||
"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) {
|
||||
|
@ -152,124 +149,3 @@ func TestPatchRequiresMatchingName(t *testing.T) {
|
|||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ const (
|
|||
DryRun utilfeature.Feature = "DryRun"
|
||||
|
||||
// owner: @apelisse, @lavalamp
|
||||
// alpha: v1.11
|
||||
// alpha: v1.14
|
||||
//
|
||||
// Server-side apply. Merging happens on the server.
|
||||
ServerSideApply utilfeature.Feature = "ServerSideApply"
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
||||
"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},
|
||||
},
|
||||
},
|
||||
// {
|
||||
// bootstrapRoles: bootstrapRoles{
|
||||
// clusterRoles: []rbacapi.ClusterRole{
|
||||
// {
|
||||
// ObjectMeta: metav1.ObjectMeta{Name: "allow-all"},
|
||||
// Rules: []rbacapi.PolicyRule{ruleAllowAll},
|
||||
// },
|
||||
// {
|
||||
// ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"},
|
||||
// Rules: []rbacapi.PolicyRule{
|
||||
// rbacapi.NewRule("patch").Groups("").Resources("limitranges").RuleOrDie(),
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// clusterRoleBindings: []rbacapi.ClusterRoleBinding{
|
||||
// {
|
||||
// ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"},
|
||||
// Subjects: []rbacapi.Subject{
|
||||
// {Kind: "User", Name: "limitrange-patcher"},
|
||||
// },
|
||||
// RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "patch-limitranges"},
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// requests: []request{
|
||||
// // Create the namespace used later in the test
|
||||
// {superUser, "POST", "", "namespaces", "", "", limitRangeNamespace, http.StatusCreated},
|
||||
{
|
||||
bootstrapRoles: bootstrapRoles{
|
||||
clusterRoles: []rbacapi.ClusterRole{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "allow-all"},
|
||||
Rules: []rbacapi.PolicyRule{ruleAllowAll},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"},
|
||||
Rules: []rbacapi.PolicyRule{
|
||||
rbacapi.NewRule("patch").Groups("").Resources("limitranges").RuleOrDie(),
|
||||
},
|
||||
},
|
||||
},
|
||||
clusterRoleBindings: []rbacapi.ClusterRoleBinding{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"},
|
||||
Subjects: []rbacapi.Subject{
|
||||
{Kind: "User", Name: "limitrange-patcher"},
|
||||
},
|
||||
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "patch-limitranges"},
|
||||
},
|
||||
},
|
||||
},
|
||||
requests: []request{
|
||||
// Create the namespace used later in the test
|
||||
{superUser, "POST", "", "namespaces", "", "", limitRangeNamespace, http.StatusCreated},
|
||||
|
||||
// {"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.StatusOK},
|
||||
// {"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
|
||||
// },
|
||||
// },
|
||||
{"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.StatusOK},
|
||||
{"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
|
@ -535,6 +536,7 @@ func TestRBAC(t *testing.T) {
|
|||
"limitrange-patcher": {Name: "limitrange-patcher"},
|
||||
"user-with-no-permissions": {Name: "user-with-no-permissions"},
|
||||
}))
|
||||
masterConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig()
|
||||
_, s, closeFn := framework.RunAMaster(masterConfig)
|
||||
defer closeFn()
|
||||
|
||||
|
@ -569,11 +571,10 @@ func TestRBAC(t *testing.T) {
|
|||
}
|
||||
|
||||
req, err := http.NewRequest(r.verb, s.URL+path, body)
|
||||
// TODO: Un-comment this when Apply works again
|
||||
// if r.verb == "PATCH" {
|
||||
// // For patch operations, use the apply content type
|
||||
// req.Header.Add("Content-Type", string(types.ApplyPatchType))
|
||||
// }
|
||||
if r.verb == "PATCH" {
|
||||
// For patch operations, use the apply content type
|
||||
req.Header.Add("Content-Type", string(types.ApplyPatchType))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", err)
|
||||
|
|
Loading…
Reference in New Issue