diff --git a/contributors/devel/api-conventions.md b/contributors/devel/api-conventions.md index 100b64457..c00776f43 100644 --- a/contributors/devel/api-conventions.md +++ b/contributors/devel/api-conventions.md @@ -26,11 +26,6 @@ An introduction to using resources with kubectl can be found in [the object mana - [Verbs on Resources](#verbs-on-resources) - [PATCH operations](#patch-operations) - [Strategic Merge Patch](#strategic-merge-patch) - - [List Operations](#list-operations) - - [List of Maps](#list-of-maps) - - [List of Primitives](#list-of-primitives) - - [Unordered Set](#unordered-set) - - [Map Operations](#map-operations) - [Idempotency](#idempotency) - [Optional vs. Required](#optional-vs-required) - [Defaulting](#defaulting) @@ -555,172 +550,7 @@ below. #### Strategic Merge Patch -In the standard JSON merge patch, JSON objects are always merged but lists are -always replaced. Often that isn't what we want. Let's say we start with the -following Pod: - -```yaml -spec: - containers: - - name: nginx - image: nginx-1.0 -``` - -...and we POST that to the server (as JSON). Then let's say we want to *add* a -container to this Pod. - -```yaml -PATCH /api/v1/namespaces/default/pods/pod-name -spec: - containers: - - name: log-tailer - image: log-tailer-1.0 -``` - -If we were to use standard Merge Patch, the entire container list would be -replaced with the single log-tailer container. However, our intent is for the -container lists to merge together based on the `name` field. - -To solve this problem, Strategic Merge Patch uses the go struct tag of the API -objects to determine what lists should be merged and which ones should not. -Currently the metadata is available as struct tags on the API objects -themselves, but will become available to clients as Swagger annotations in the -future. In the above example, the `patchStrategy` metadata for the `containers` -field would be `merge` and the `patchMergeKey` would be `name`. - -Note: If the patch results in merging two lists of primitives, the primitives are -first deduplicated and then merged. - -Strategic Merge Patch also supports special operations as listed below. - -### List Operations - -#### List of Maps - -To override the container list to be strictly replaced, regardless of the -default: - -```yaml -containers: - - name: nginx - image: nginx-1.0 - - $patch: replace # any further $patch operations nested in this list will be ignored -``` - -To delete an element of a list that should be merged: - -```yaml -containers: - - name: nginx - image: nginx-1.0 - - $patch: delete - name: log-tailer # merge key and value goes here -``` - -Delete operation will delete the first entry in the list that match the merge key. -But there is validation to make sure no 2 entries with the same merge key will get in the list. - -#### List of Primitives - -We have two patch strategies for lists of primitives: replace and merge. -Replace is the default patch strategy, which will replace the whole list on update and it will preserve the order; -while merge strategy works as an unordered set. In this section, we call a primitive list with merge strategy an unordered set. -The patch strategy is defined in the go struct tag of the API objects, -e.g. `finalizers` uses `merge` as patch strategy. -```go -Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge" protobuf:"bytes,14,rep,name=finalizers"` -``` - -##### Unordered Set - -There are 3 operations: add, delete, replace. - -Suppose we have defined a `finalizers` and we call it the original finalizers: - -```yaml -finalizers: - - a - - b - - c -``` - -1) To add items in a set, we use the list name as the key. -e.g. to add items "d" and "e" in the original finalizers, the patch will be: - -```yaml -finalizers: - - d - - e -``` - -After applying the patch on the original finalizers, it will become: - -```yaml -finalizers: - - a - - b - - c - - d - - e -``` - -2) To delete items in a set, we use a parallel list with key: `$deleteFromPrimitiveList/\`. -e.g. to delete items "b" and "c" from the original finalizers, the patch will be: - -```yaml -$deleteFromPrimitiveList/finalizers: - - b - - c -``` - -After applying the patch on the original finalizers, it will become: - -```yaml -finalizers: - - a -``` - -In an erroneous case, the set may be created with duplicates. Deleting an item that has duplicates will delete all matching items. - -3) Replace can be fulfilled by an addition and a deletion. -e.g. to replace "a" with "x" in the original finalizers, the patch will be: - -```yaml -$deleteFromPrimitiveList/finalizers: - - a -finalizers: - - x -``` - -After applying the patch on the original finalizers, it will become: - -```yaml -finalizers: - - x - - b - - c -``` - -### Map Operations - -To indicate that a map should not be merged and instead should be taken literally: - -```yaml -$patch: replace # recursive and applies to all fields of the map it's in -containers: -- name: nginx - image: nginx-1.0 -``` - -To delete a field of a map: - -```yaml -name: nginx -image: nginx-1.0 -labels: - live: null # set the value of the map key to null -``` - +Details of Strategic Merge Patch are covered [here](../strategic-merge-patch.md). ## Idempotency diff --git a/contributors/devel/strategic-merge-patch.md b/contributors/devel/strategic-merge-patch.md new file mode 100644 index 000000000..5d1f3336d --- /dev/null +++ b/contributors/devel/strategic-merge-patch.md @@ -0,0 +1,223 @@ +Strategic Merge Patch +===================== + +# Background + +TODO: @pwittrock complete this section + +In the standard JSON merge patch, JSON objects are always merged but lists are +always replaced. Often that isn't what we want. Let's say we start with the +following Pod: + +```yaml +spec: + containers: + - name: nginx + image: nginx-1.0 +``` + +and we POST that to the server (as JSON). Then let's say we want to *add* a +container to this Pod. + +```yaml +PATCH /api/v1/namespaces/default/pods/pod-name +spec: + containers: + - name: log-tailer + image: log-tailer-1.0 +``` + +If we were to use standard Merge Patch, the entire container list would be +replaced with the single log-tailer container. However, our intent is for the +container lists to merge together based on the `name` field. + +To solve this problem, Strategic Merge Patch uses the go struct tag of the API +objects to determine what lists should be merged and which ones should not. +Currently the metadata is available as struct tags on the API objects +themselves, but will become available to clients as Swagger annotations in the +future. In the above example, the `patchStrategy` metadata for the `containers` +field would be `merge` and the `patchMergeKey` would be `name`. + + +# Basic Patch Format + +Strategic Merge Patch supports special operations through directives. + +There are multiple directives: + +- replace +- merge +- delete +- delete from primitive list + +`replace`, `merge` and `delete` are mutual exclusive. + +## `replace` Directive + +### Purpose + +`replace` directive indicates that the element that contains it should be replaced instead of being merged. + +### Syntax + +`replace` directive is used in both patch with directive marker and go struct tags. + +Example usage in the patch: + +``` +$patch: replace +``` + +### Example + +`replace` directive can be used on both map and list. + +#### Map + +To indicate that a map should not be merged and instead should be taken literally: + +```yaml +$patch: replace # recursive and applies to all fields of the map it's in +containers: +- name: nginx + image: nginx-1.0 +``` + +#### List of Maps + +To override the container list to be strictly replaced, regardless of the default: + +```yaml +containers: + - name: nginx + image: nginx-1.0 + - $patch: replace # any further $patch operations nested in this list will be ignored +``` + + +## `delete` Directive + +### Purpose + +`delete` directive indicates that the element that contains it should be deleted. + +### Syntax + +`delete` directive is used only in the patch with directive marker. +It can be used on both map and list of maps. +``` +$patch: delete +``` + +### Example + +#### List of Maps + +To delete an element of a list that should be merged: + +```yaml +containers: + - name: nginx + image: nginx-1.0 + - $patch: delete + name: log-tailer # merge key and value goes here +``` + +Note: Delete operation will delete all entries in the list that match the merge key. + +#### Maps + +One way to delete a map is using `delete` directive. +Applying this patch will delete the rollingUpdate map. +```yaml +rollingUpdate: + $patch: delete +``` + +An equivalent way to delete this map is +```yaml +rollingUpdate: null +``` + +## `merge` Directive + +### Purpose + +`merge` directive indicates that the element that contains it should be merged instead of being replaced. + +### Syntax + +`merge` directive is used only in the go struct tags. + + +## `deleteFromPrimitiveList` Directive + +### Purpose + +We have two patch strategies for lists of primitives: replace and merge. +Replace is the default patch strategy for list, which will replace the whole list on update and it will preserve the order; +while merge strategy works as an unordered set. We call a primitive list with merge strategy an unordered set. +The patch strategy is defined in the go struct tag of the API objects. + +`deleteFromPrimitiveList` directive indicates that the elements in this list should be deleted from the original primitive list. + +### Syntax + +It is used only as the prefix of the key in the patch. +``` +$deleteFromPrimitiveList/: [a primitive list] +``` + +### Example + +##### List of Primitives (Unordered Set) + +`finalizers` uses `merge` as patch strategy. +```go +Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge" protobuf:"bytes,14,rep,name=finalizers"` +``` + +Suppose we have defined a `finalizers` and we call it the original finalizers: + +```yaml +finalizers: + - a + - b + - c +``` + +To delete items "b" and "c" from the original finalizers, the patch will be: + +```yaml +# The directive includes the prefix $deleteFromPrimitiveList and +# followed by a '/' and the name of the list. +# The values in this list will be deleted after applying the patch. +$deleteFromPrimitiveList/finalizers: + - b + - c +``` + +After applying the patch on the original finalizers, it will become: + +```yaml +finalizers: + - a +``` + +Note: When merging two set, the primitives are first deduplicated and then merged. +In an erroneous case, the set may be created with duplicates. Deleting an +item that has duplicates will delete all matching items. + + +TODO: @pwittrock +# Changing Patch Format + +## Purpose + +## Requirement + +### Version Skew + +## Strategy + +## Example