mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-21 00:33:15 +00:00
convert lastapplied to ownerclaims
This commit is contained in:
@@ -138,7 +138,7 @@ func AsPartialObjectMetadata(m metav1.Object) *metav1beta1.PartialObjectMetadata
|
||||
Finalizers: m.GetFinalizers(),
|
||||
ClusterName: m.GetClusterName(),
|
||||
Initializers: m.GetInitializers(),
|
||||
LastApplied: m.GetLastApplied(),
|
||||
ManagedFields: m.GetManagedFields(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ type Object interface {
|
||||
SetOwnerReferences([]OwnerReference)
|
||||
GetClusterName() string
|
||||
SetClusterName(clusterName string)
|
||||
GetLastApplied() map[string]string
|
||||
SetLastApplied(lastApplied map[string]string)
|
||||
GetManagedFields() map[string]VersionedFieldSet
|
||||
SetManagedFields(lastApplied map[string]VersionedFieldSet)
|
||||
}
|
||||
|
||||
// ListMetaAccessor retrieves the list interface from an object
|
||||
@@ -171,13 +171,13 @@ func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) {
|
||||
func (meta *ObjectMeta) GetClusterName() string { return meta.ClusterName }
|
||||
func (meta *ObjectMeta) SetClusterName(clusterName string) { meta.ClusterName = clusterName }
|
||||
|
||||
func (meta *ObjectMeta) GetLastApplied() map[string]string {
|
||||
return meta.LastApplied
|
||||
func (meta *ObjectMeta) GetManagedFields() map[string]VersionedFieldSet {
|
||||
return meta.ManagedFields
|
||||
}
|
||||
|
||||
func (meta *ObjectMeta) SetLastApplied(lastApplied map[string]string) {
|
||||
meta.LastApplied = make(map[string]string, len(lastApplied))
|
||||
for key, value := range lastApplied {
|
||||
meta.LastApplied[key] = value
|
||||
func (meta *ObjectMeta) SetManagedFields(ManagedFields map[string]VersionedFieldSet) {
|
||||
meta.ManagedFields = make(map[string]VersionedFieldSet, len(ManagedFields))
|
||||
for key, value := range ManagedFields {
|
||||
meta.ManagedFields[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,14 +250,79 @@ type ObjectMeta struct {
|
||||
// +optional
|
||||
ClusterName string `json:"clusterName,omitempty" protobuf:"bytes,15,opt,name=clusterName"`
|
||||
|
||||
// LastApplied is a map of workflow-id to last applied
|
||||
// configuration. A workflow can be the user's name, a
|
||||
// ManagedFields is a map of workflow-id to the set of fields
|
||||
// that are managed by that workflow. This is mostly for internal
|
||||
// housekeeping, and users typically shouldn't need to set or
|
||||
// understand this field. A workflow can be the user's name, a
|
||||
// controller's name, or the name of a specific apply path like
|
||||
// "ci-cd". It keeps track of the ownership of fields. The last
|
||||
// applied configuration is always in the same version as the
|
||||
// parent object. It's used to keep track of the intent of the
|
||||
// workflow-id that submitted that configuration.
|
||||
LastApplied map[string]string `json:"lastApplied,omitempty" protobuf:"bytes,17,rep,name=lastApplied"`
|
||||
// "ci-cd". The set of fields is always in the version that the
|
||||
// workflow used when modifying the object.
|
||||
//
|
||||
// An example value of this field for a deployment with a
|
||||
// single container managed by a user:
|
||||
// {
|
||||
// "user-workflow-id": {
|
||||
// "apiVersion": "extensions/v1beta",
|
||||
// "fields": {
|
||||
// "children": [{
|
||||
// "pathElement": {
|
||||
// "fieldName": "spec"
|
||||
// },
|
||||
// "set": {
|
||||
// "children": [{
|
||||
// "pathElement": {
|
||||
// "fieldName": "template"
|
||||
// },
|
||||
// "set": {
|
||||
// "children": [{
|
||||
// "pathElement": {
|
||||
// "fieldName": "spec"
|
||||
// },
|
||||
// "set": {
|
||||
// "children": [{
|
||||
// "pathElement": {
|
||||
// "fieldName": "containers"
|
||||
// },
|
||||
// "set": {
|
||||
// "members": [{
|
||||
// "key": [{
|
||||
// "name": "name",
|
||||
// "value": {
|
||||
// "stringValue": "my-container"
|
||||
// }
|
||||
// }]
|
||||
// }],
|
||||
// "children": [{
|
||||
// "pathElement": {
|
||||
// "key": [{
|
||||
// "name": "name",
|
||||
// "value": {
|
||||
// "stringValue": "my-container"
|
||||
// }
|
||||
// }]
|
||||
// },
|
||||
// "set": {
|
||||
// "members": [{
|
||||
// "fieldName": "name"
|
||||
// },
|
||||
// {
|
||||
// "fieldName": "image"
|
||||
// }]
|
||||
// }
|
||||
// }]
|
||||
// }
|
||||
// }]
|
||||
// }
|
||||
// }]
|
||||
// }
|
||||
// }]
|
||||
// }
|
||||
// }]
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// +optional
|
||||
ManagedFields map[string]VersionedFieldSet `json:"managedFields,omitempty" protobuf:"bytes,17,rep,name=managedFields"`
|
||||
}
|
||||
|
||||
// Initializers tracks the progress of initialization.
|
||||
@@ -1005,3 +1070,86 @@ const (
|
||||
LabelSelectorOpExists LabelSelectorOperator = "Exists"
|
||||
LabelSelectorOpDoesNotExist LabelSelectorOperator = "DoesNotExist"
|
||||
)
|
||||
|
||||
// VersionedFieldSet is a pair of a FieldSet and the group version of the resource
|
||||
// that the fieldset applies to.
|
||||
type VersionedFieldSet struct {
|
||||
// APIVersion defines the version of this resource that this field set
|
||||
// applies to. The format is "group/version" just like the top-level
|
||||
// APIVersion field. It is necessary to track the version of a field
|
||||
// set because it cannot be automatically converted.
|
||||
APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,1,opt,name=apiVersion"`
|
||||
// Fields identifies a set of fields.
|
||||
Fields FieldSet `json:"fields,omitempty" protobuf:"bytes,2,opt,name=fields,casttype=FieldSet"`
|
||||
}
|
||||
|
||||
// FieldSet stores a set of fields in a data structure like a Trie.
|
||||
// To understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff
|
||||
type FieldSet struct {
|
||||
// Members lists fields that are part of the set.
|
||||
Members []FieldPathElement `json:"members" protobuf:"bytes,1,rep,name=members"`
|
||||
// Children lists child fields which themselves have children that are
|
||||
// members of the set. Appearance in this list does not imply membership,
|
||||
// although it does imply some descendant field is a member.
|
||||
// Note: this is a tree, not an arbitrary graph.
|
||||
Children []FieldSetNode `json:"children" protobuf:"bytes,2,rep,name=children"`
|
||||
}
|
||||
|
||||
// FieldPathElement describes how to select a child field given a containing object.
|
||||
type FieldPathElement struct {
|
||||
// Exactly one of the following fields should be non-nil.
|
||||
// FieldName selects a single field from a map (reminder: this is also
|
||||
// how structs are represented). The containing object must be a map.
|
||||
// +optional
|
||||
FieldName *string `json:"fieldName,omitempty" protobuf:"bytes,1,opt,name=fieldName"`
|
||||
// Key selects the list element which has fields matching those given.
|
||||
// The containing object must be an associative list with map typed
|
||||
// elements.
|
||||
// +optional
|
||||
Key []FieldNameValuePair `json:"key,omitempty" protobuf:"bytes,2,rep,name=key"`
|
||||
// Value selects the list element with the given value. The containing
|
||||
// object must be an associative list with a primitive typed element
|
||||
// (i.e., a set).
|
||||
// +optional
|
||||
Value *FieldValue `json:"value,omitempty" protobuf:"bytes,3,opt,name=value,casttype=FieldValue"`
|
||||
// Index selects a list element by its index number. The containing
|
||||
// object must be an atomic list.
|
||||
// +optional
|
||||
Index *int32 `json:"index,omitempty" protobuf:"varint,4,opt,name=index"`
|
||||
}
|
||||
|
||||
// FieldSetNode is a pair of FieldPathElement / FieldSet, for the purpose of expressing
|
||||
// nested set membership.
|
||||
type FieldSetNode struct {
|
||||
// PathElement identifies which field this node expresses child membership for.
|
||||
PathElement FieldPathElement `json:"pathElement" protobuf:"bytes,1,opt,name=pathElement,casttype=FieldPathElement"`
|
||||
// Set identifies which child fields of this node are part of the FieldSet.
|
||||
Set *FieldSet `json:"set" protobuf:"bytes,2,opt,name=set,casttype=FieldSet"`
|
||||
}
|
||||
|
||||
// FieldNameValuePair is an individual key-value pair.
|
||||
type FieldNameValuePair struct {
|
||||
// Name is the field's name.
|
||||
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||
// Value is the field's value.
|
||||
Value FieldValue `json:"value" protobuf:"bytes,2,opt,name=value,casttype=FieldValue"`
|
||||
}
|
||||
|
||||
// FieldValue represents a concrete value, either for a key-value pair or identifying an item in a set.
|
||||
type FieldValue struct {
|
||||
// Exactly one of the following fields should be set.
|
||||
// FloatValue is a primitive float value.
|
||||
// +optional
|
||||
FloatValue *float64 `json:"floatValue,omitempty" protobuf:"bytes,1,opt,name=floatValue"`
|
||||
// IntValue is a primitive int value.
|
||||
// +optional
|
||||
IntValue *int32 `json:"intValue,omitempty" protobuf:"varint,2,opt,name=intValue"`
|
||||
// StringValue is a primitive string value.
|
||||
// +optional
|
||||
StringValue *string `json:"stringValue,omitempty" protobuf:"bytes,3,opt,name=stringValue"`
|
||||
// BooleanValue is a primitive boolean value.
|
||||
// +optional
|
||||
BooleanValue *bool `json:"booleanValue,omitempty" protobuf:"varint,4,opt,name=booleanValue"`
|
||||
// Null represents an explicit `"foo" = null`
|
||||
Null bool `json:"null" protobuf:"varint,5,opt,name=null"`
|
||||
}
|
||||
|
||||
@@ -398,11 +398,15 @@ func (u *Unstructured) SetClusterName(clusterName string) {
|
||||
u.setNestedField(clusterName, "metadata", "clusterName")
|
||||
}
|
||||
|
||||
func (u *Unstructured) GetLastApplied() map[string]string {
|
||||
val, _, _ := NestedStringMap(u.Object, "metadata", "lastApplied")
|
||||
return val
|
||||
func (u *Unstructured) GetManagedFields() map[string]metav1.VersionedFieldSet {
|
||||
unstructuredManagedFields, _, _ := NestedStringMap(u.Object, "metadata", "managedFields")
|
||||
managedFields := make(map[string]metav1.VersionedFieldSet, len(unstructuredManagedFields))
|
||||
// TODO: convert from unstructured
|
||||
return managedFields
|
||||
}
|
||||
|
||||
func (u *Unstructured) SetLastApplied(lastApplied map[string]string) {
|
||||
u.setNestedMap(lastApplied, "metadata", "lastApplied")
|
||||
func (u *Unstructured) SetManagedFields(managedFields map[string]metav1.VersionedFieldSet) {
|
||||
unstructuredManagedFields := make(map[string]string, len(managedFields))
|
||||
// TODO: convert to unstructured
|
||||
u.setNestedMap(unstructuredManagedFields, "metadata", "managedFields")
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/apply"
|
||||
"k8s.io/apimachinery/pkg/apply/parse"
|
||||
@@ -56,9 +57,10 @@ func (p *applyPatcher) extractLastIntent(obj runtime.Object, workflow string) (m
|
||||
return nil, fmt.Errorf("couldn't get accessor: %v", err)
|
||||
}
|
||||
last := make(map[string]interface{})
|
||||
if accessor.GetLastApplied()[workflow] != "" {
|
||||
if err := json.Unmarshal([]byte(accessor.GetLastApplied()[workflow]), &last); err != nil {
|
||||
return nil, fmt.Errorf("couldn't unmarshal last applied field: %v", err)
|
||||
// TODO: use the managedFields correctly
|
||||
if _, ok := accessor.GetManagedFields()[workflow]; ok {
|
||||
if err := json.Unmarshal([]byte(accessor.GetManagedFields()[workflow].APIVersion), &last); err != nil {
|
||||
return nil, fmt.Errorf("couldn't unmarshal managedFields field: %v", err)
|
||||
}
|
||||
}
|
||||
return last, nil
|
||||
@@ -105,12 +107,15 @@ func (p *applyPatcher) saveNewIntent(patch map[string]interface{}, workflow stri
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't get accessor: %v", err)
|
||||
}
|
||||
m := accessor.GetLastApplied()
|
||||
m := accessor.GetManagedFields()
|
||||
if m == nil {
|
||||
m = make(map[string]string)
|
||||
m = make(map[string]metav1.VersionedFieldSet)
|
||||
}
|
||||
m[workflow] = string(j)
|
||||
accessor.SetLastApplied(m)
|
||||
// TODO: save the managedFields correctly
|
||||
m[workflow] = metav1.VersionedFieldSet{
|
||||
APIVersion: string(j),
|
||||
}
|
||||
accessor.SetManagedFields(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -147,7 +152,7 @@ func (p *applyPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (
|
||||
return nil, fmt.Errorf("failed to save last intent: %v", err)
|
||||
}
|
||||
|
||||
// TODO(apelisse): Check for conflicts with other lastApplied
|
||||
// TODO(apelisse): Check for conflicts with other managedFields
|
||||
// and report actionable errors to users.
|
||||
|
||||
return output, nil
|
||||
|
||||
@@ -139,8 +139,8 @@ func TestPatchApply(t *testing.T) {
|
||||
if simpleStorage.updated.Other != "bar" {
|
||||
t.Errorf(`Merge should have kept initial "bar" value for Other: %v`, simpleStorage.updated.Other)
|
||||
}
|
||||
if simpleStorage.updated.ObjectMeta.LastApplied["default"] == "" {
|
||||
t.Errorf(`Expected lastApplied field to be set, but is empty`)
|
||||
if _, ok := simpleStorage.updated.ObjectMeta.ManagedFields["default"]; !ok {
|
||||
t.Errorf(`Expected managedFields field to be set, but is empty`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,17 +177,18 @@ func TestApplyAddsGVK(t *testing.T) {
|
||||
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.LastApplied["default"] != expected {
|
||||
if simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion != expected {
|
||||
t.Errorf(
|
||||
`Expected lastApplied field to be %q, got %q`,
|
||||
`Expected managedFields field to be %q, got %q`,
|
||||
expected,
|
||||
simpleStorage.updated.ObjectMeta.LastApplied["default"],
|
||||
simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCreatesWithLastApplied(t *testing.T) {
|
||||
func TestApplyCreatesWithManagedFields(t *testing.T) {
|
||||
if err := utilfeature.DefaultFeatureGate.Set(string(genericfeatures.ServerSideApply) + "=true"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -212,12 +213,13 @@ func TestApplyCreatesWithLastApplied(t *testing.T) {
|
||||
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.LastApplied["default"] != expected {
|
||||
if simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion != expected {
|
||||
t.Errorf(
|
||||
`Expected lastApplied field to be %q, got %q`,
|
||||
`Expected managedFields field to be %q, got %q`,
|
||||
expected,
|
||||
simpleStorage.updated.ObjectMeta.LastApplied["default"],
|
||||
simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,9 +97,9 @@ func BeforeCreate(strategy RESTCreateStrategy, ctx context.Context, obj runtime.
|
||||
objectMeta.SetInitializers(nil)
|
||||
}
|
||||
|
||||
// Ensure LastApplied is not set unless the feature is enabled
|
||||
// Ensure managedFields is not set unless the feature is enabled
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
|
||||
objectMeta.SetLastApplied(nil)
|
||||
objectMeta.SetManagedFields(nil)
|
||||
}
|
||||
|
||||
// ClusterName is ignored and should not be saved
|
||||
|
||||
@@ -108,10 +108,10 @@ func BeforeUpdate(strategy RESTUpdateStrategy, ctx context.Context, obj, old run
|
||||
objectMeta.SetInitializers(nil)
|
||||
}
|
||||
|
||||
// Ensure lastApplied state is removed unless ServerSideApply is enabled
|
||||
// Ensure managedFields state is removed unless ServerSideApply is enabled
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
|
||||
oldMeta.SetLastApplied(map[string]string{})
|
||||
objectMeta.SetLastApplied(map[string]string{})
|
||||
oldMeta.SetManagedFields(nil)
|
||||
objectMeta.SetManagedFields(nil)
|
||||
}
|
||||
|
||||
strategy.PrepareForUpdate(ctx, obj, old)
|
||||
|
||||
Reference in New Issue
Block a user