vendor files

This commit is contained in:
Serguei Bezverkhi
2018-01-09 13:57:14 -05:00
parent 558bc6c02a
commit 7b24313bd6
16547 changed files with 4527373 additions and 0 deletions

35
vendor/k8s.io/kubernetes/pkg/kubectl/apply/BUILD generated vendored Normal file
View File

@ -0,0 +1,35 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"element.go",
"empty_element.go",
"list_element.go",
"map_element.go",
"primitive_element.go",
"type_element.go",
"visitor.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/apply",
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/kubectl/apply/parse:all-srcs",
"//pkg/kubectl/apply/strategy:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

34
vendor/k8s.io/kubernetes/pkg/kubectl/apply/doc.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
/*
Copyright 2017 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 apply
// This package is used for creating and applying patches generated
// from a last recorded config, local config, remote object.
// Example usage for a test:
//
//fakeSchema := tst.Fake{Path: swaggerPath}
//s, err := fakeSchema.OpenAPISchema()
//Expect(err).To(BeNil())
//resources, err := openapi.NewOpenAPIData(s)
//Expect(err).To(BeNil())
//elementParser := parse.Factory{resources}
//
//obj, err := parser.CreateElement(recorded, local, remote)
//Expect(err).Should(Not(HaveOccurred()))
//
//merged, err := obj.Merge(strategy.Create(strategy.Options{}))
//

421
vendor/k8s.io/kubernetes/pkg/kubectl/apply/element.go generated vendored Normal file
View File

@ -0,0 +1,421 @@
/*
Copyright 2017 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 apply
import (
"fmt"
)
// Element contains the record, local, and remote value for a field in an object
// and metadata about the field read from openapi.
// Calling Merge on an element will apply the passed in strategy to Element -
// e.g. either replacing the whole element with the local copy or merging each
// of the recorded, local and remote fields of the element.
type Element interface {
// FieldMeta specifies which merge strategy to use for this element
FieldMeta
// Merge merges the recorded, local and remote values in the element using the Strategy
// provided as an argument. Calls the type specific method on the Strategy - following the
// "Accept" method from the "Visitor" pattern.
// e.g. Merge on a ListElement will call Strategy.MergeList(self)
// Returns the Result of the merged elements
Merge(Strategy) (Result, error)
// HasRecorded returns true if the field was explicitly
// present in the recorded source. This is to differentiate between
// undefined and set to null
HasRecorded() bool
// GetRecorded returns the field value from the recorded source of the object
GetRecorded() interface{}
// HasLocal returns true if the field was explicitly
// present in the recorded source. This is to differentiate between
// undefined and set to null
HasLocal() bool
// GetLocal returns the field value from the local source of the object
GetLocal() interface{}
// HasRemote returns true if the field was explicitly
// present in the remote source. This is to differentiate between
// undefined and set to null
HasRemote() bool
// GetRemote returns the field value from the remote source of the object
GetRemote() interface{}
}
// FieldMeta defines the strategy used to apply a Patch for an element
type FieldMeta interface {
// GetFieldMergeType returns the type of merge strategy to use for this field
// maybe "merge", "replace" or "retainkeys"
// TODO: There maybe multiple strategies, so this may need to be a slice, map, or struct
// Address this in a follow up in the PR to introduce retainkeys strategy
GetFieldMergeType() string
// GetFieldMergeKeys returns the merge key to use when the MergeType is "merge" and underlying type is a list
GetFieldMergeKeys() MergeKeys
// GetFieldType returns the openapi field type - e.g. primitive, array, map, type, reference
GetFieldType() string
}
// FieldMetaImpl implements FieldMeta
type FieldMetaImpl struct {
// MergeType is the type of merge strategy to use for this field
// maybe "merge", "replace" or "retainkeys"
MergeType string
// MergeKeys are the merge keys to use when the MergeType is "merge" and underlying type is a list
MergeKeys MergeKeys
// Type is the openapi type of the field - "list", "primitive", "map"
Type string
// Name contains of the field
Name string
}
// GetFieldMergeType implements FieldMeta.GetFieldMergeType
func (s FieldMetaImpl) GetFieldMergeType() string {
return s.MergeType
}
// GetFieldMergeKeys implements FieldMeta.GetFieldMergeKeys
func (s FieldMetaImpl) GetFieldMergeKeys() MergeKeys {
return s.MergeKeys
}
// GetFieldType implements FieldMeta.GetFieldType
func (s FieldMetaImpl) GetFieldType() string {
return s.Type
}
// MergeKeyValue records the value of the mergekey for an item in a list
type MergeKeyValue map[string]string
// Equal returns true if the MergeKeyValues share the same value,
// representing the same item in a list
func (v MergeKeyValue) Equal(o MergeKeyValue) bool {
if len(v) != len(o) {
return false
}
for key, v1 := range v {
if v2, found := o[key]; !found || v1 != v2 {
return false
}
}
return true
}
// MergeKeys is the set of fields on an object that uniquely identify
// and is used when merging lists to identify the "same" object
// independent of the ordering of the objects
type MergeKeys []string
// GetMergeKeyValue parses the MergeKeyValue from an item in a list
func (mk MergeKeys) GetMergeKeyValue(i interface{}) (MergeKeyValue, error) {
result := MergeKeyValue{}
if len(mk) <= 0 {
return result, fmt.Errorf("merge key must have at least 1 value to merge")
}
m, ok := i.(map[string]interface{})
if !ok {
return result, fmt.Errorf("cannot use mergekey %v for primitive item in list %v", mk, i)
}
for _, field := range mk {
if value, found := m[field]; !found {
result[field] = ""
} else {
result[field] = fmt.Sprintf("%v", value)
}
}
return result, nil
}
type source int
const (
recorded source = iota
local
remote
)
// CombinedPrimitiveSlice implements a slice of primitives
type CombinedPrimitiveSlice struct {
Items []*PrimitiveListItem
}
// PrimitiveListItem represents a single value in a slice of primitives
type PrimitiveListItem struct {
// Value is the value of the primitive, should match recorded, local and remote
Value interface{}
RawElementData
}
// Contains returns true if the slice contains the l
func (s *CombinedPrimitiveSlice) lookup(l interface{}) *PrimitiveListItem {
val := fmt.Sprintf("%v", l)
for _, i := range s.Items {
if fmt.Sprintf("%v", i.Value) == val {
return i
}
}
return nil
}
func (s *CombinedPrimitiveSlice) upsert(l interface{}) *PrimitiveListItem {
// Return the item if it exists
if item := s.lookup(l); item != nil {
return item
}
// Otherwise create a new item and append to the list
item := &PrimitiveListItem{
Value: l,
}
s.Items = append(s.Items, item)
return item
}
// UpsertRecorded adds l to the slice. If there is already a value of l in the
// slice for either the local or remote, set on that value as the recorded value
// Otherwise append a new item to the list with the recorded value.
func (s *CombinedPrimitiveSlice) UpsertRecorded(l interface{}) {
v := s.upsert(l)
v.recorded = l
v.recordedSet = true
}
// UpsertLocal adds l to the slice. If there is already a value of l in the
// slice for either the recorded or remote, set on that value as the local value
// Otherwise append a new item to the list with the local value.
func (s *CombinedPrimitiveSlice) UpsertLocal(l interface{}) {
v := s.upsert(l)
v.local = l
v.localSet = true
}
// UpsertRemote adds l to the slice. If there is already a value of l in the
// slice for either the local or recorded, set on that value as the remote value
// Otherwise append a new item to the list with the remote value.
func (s *CombinedPrimitiveSlice) UpsertRemote(l interface{}) {
v := s.upsert(l)
v.remote = l
v.remoteSet = true
}
// ListItem represents a single value in a slice of maps or types
type ListItem struct {
// KeyValue is the merge key value of the item
KeyValue MergeKeyValue
// RawElementData contains the field values
RawElementData
}
// CombinedMapSlice is a slice of maps or types with merge keys
type CombinedMapSlice struct {
Items []*ListItem
}
// Lookup returns the ListItem matching the merge key, or nil if not found.
func (s *CombinedMapSlice) lookup(v MergeKeyValue) *ListItem {
for _, i := range s.Items {
if i.KeyValue.Equal(v) {
return i
}
}
return nil
}
func (s *CombinedMapSlice) upsert(key MergeKeys, l interface{}) (*ListItem, error) {
// Get the identity of the item
val, err := key.GetMergeKeyValue(l)
if err != nil {
return nil, err
}
// Return the item if it exists
if item := s.lookup(val); item != nil {
return item, nil
}
// Otherwise create a new item and append to the list
item := &ListItem{
KeyValue: val,
}
s.Items = append(s.Items, item)
return item, nil
}
// UpsertRecorded adds l to the slice. If there is already a value of l sharing
// l's merge key in the slice for either the local or remote, set l the recorded value
// Otherwise append a new item to the list with the recorded value.
func (s *CombinedMapSlice) UpsertRecorded(key MergeKeys, l interface{}) error {
item, err := s.upsert(key, l)
if err != nil {
return err
}
item.recorded = l
item.recordedSet = true
return nil
}
// UpsertLocal adds l to the slice. If there is already a value of l sharing
// l's merge key in the slice for either the recorded or remote, set l the local value
// Otherwise append a new item to the list with the local value.
func (s *CombinedMapSlice) UpsertLocal(key MergeKeys, l interface{}) error {
item, err := s.upsert(key, l)
if err != nil {
return err
}
item.local = l
item.localSet = true
return nil
}
// UpsertRemote adds l to the slice. If there is already a value of l sharing
// l's merge key in the slice for either the recorded or local, set l the remote value
// Otherwise append a new item to the list with the remote value.
func (s *CombinedMapSlice) UpsertRemote(key MergeKeys, l interface{}) error {
item, err := s.upsert(key, l)
if err != nil {
return err
}
item.remote = l
item.remoteSet = true
return nil
}
// IsDrop returns true if the field represented by e should be dropped from the merged object
func IsDrop(e Element) bool {
// Specified in the last value recorded value and since deleted from the local
removed := e.HasRecorded() && !e.HasLocal()
// Specified locally and explicitly set to null
setToNil := e.HasLocal() && e.GetLocal() == nil
return removed || setToNil
}
// IsAdd returns true if the field represented by e should have the local value directly
// added to the merged object instead of merging the recorded, local and remote values
func IsAdd(e Element) bool {
// If it isn't already present in the remote value and is present in the local value
return e.HasLocal() && !e.HasRemote()
}
// NewRawElementData returns a new RawElementData, setting IsSet to true for
// non-nil values, and leaving IsSet false for nil values.
// Note: use this only when you want a nil-value to be considered "unspecified"
// (ignore) and not "unset" (deleted).
func NewRawElementData(recorded, local, remote interface{}) RawElementData {
data := RawElementData{}
if recorded != nil {
data.SetRecorded(recorded)
}
if local != nil {
data.SetLocal(local)
}
if remote != nil {
data.SetRemote(remote)
}
return data
}
// RawElementData contains the raw recorded, local and remote data
// and metadata about whethere or not each was set
type RawElementData struct {
HasElementData
recorded interface{}
local interface{}
remote interface{}
}
// SetRecorded sets the recorded value
func (b *RawElementData) SetRecorded(value interface{}) {
b.recorded = value
b.recordedSet = true
}
// SetLocal sets the recorded value
func (b *RawElementData) SetLocal(value interface{}) {
b.local = value
b.localSet = true
}
// SetRemote sets the recorded value
func (b *RawElementData) SetRemote(value interface{}) {
b.remote = value
b.remoteSet = true
}
// GetRecorded implements Element.GetRecorded
func (b RawElementData) GetRecorded() interface{} {
// https://golang.org/doc/faq#nil_error
if b.recorded == nil {
return nil
}
return b.recorded
}
// GetLocal implements Element.GetLocal
func (b RawElementData) GetLocal() interface{} {
// https://golang.org/doc/faq#nil_error
if b.local == nil {
return nil
}
return b.local
}
// GetRemote implements Element.GetRemote
func (b RawElementData) GetRemote() interface{} {
// https://golang.org/doc/faq#nil_error
if b.remote == nil {
return nil
}
return b.remote
}
// HasElementData contains whether a field was set in the recorded, local and remote sources
type HasElementData struct {
recordedSet bool
localSet bool
remoteSet bool
}
// HasRecorded implements Element.HasRecorded
func (e HasElementData) HasRecorded() bool {
return e.recordedSet
}
// HasLocal implements Element.HasLocal
func (e HasElementData) HasLocal() bool {
return e.localSet
}
// HasRemote implements Element.HasRemote
func (e HasElementData) HasRemote() bool {
return e.remoteSet
}

View File

@ -0,0 +1,70 @@
/*
Copyright 2017 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 apply
// EmptyElement is a placeholder for when no value is set for a field so its type is unknown
type EmptyElement struct {
// FieldMetaImpl contains metadata about the field from openapi
FieldMetaImpl
}
// Merge implements Element.Merge
func (e EmptyElement) Merge(v Strategy) (Result, error) {
return v.MergeEmpty(e)
}
// IsAdd implements Element.IsAdd
func (e EmptyElement) IsAdd() bool {
return false
}
// IsDelete implements Element.IsDelete
func (e EmptyElement) IsDelete() bool {
return false
}
// GetRecorded implements Element.GetRecorded
func (e EmptyElement) GetRecorded() interface{} {
return nil
}
// GetLocal implements Element.GetLocal
func (e EmptyElement) GetLocal() interface{} {
return nil
}
// GetRemote implements Element.GetRemote
func (e EmptyElement) GetRemote() interface{} {
return nil
}
// HasRecorded implements Element.HasRecorded
func (e EmptyElement) HasRecorded() bool {
return false
}
// HasLocal implements Element.HasLocal
func (e EmptyElement) HasLocal() bool {
return false
}
// HasRemote implements Element.IsAdd
func (e EmptyElement) HasRemote() bool {
return false
}
var _ Element = &EmptyElement{}

View File

@ -0,0 +1,67 @@
/*
Copyright 2017 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 apply
// ListElement contains the recorded, local and remote values for a field
// of type list
type ListElement struct {
// FieldMetaImpl contains metadata about the field from openapi
FieldMetaImpl
ListElementData
// Values contains the combined recorded-local-remote value of each item in the list
// Present for lists that can be merged only. Contains the items
// from each of the 3 lists merged into single Elements using
// the merge-key.
Values []Element
}
// Merge implements Element.Merge
func (e ListElement) Merge(v Strategy) (Result, error) {
return v.MergeList(e)
}
var _ Element = &ListElement{}
// ListElementData contains the recorded, local and remote data for a list
type ListElementData struct {
RawElementData
}
// GetRecordedList returns the Recorded value as a list
func (e ListElementData) GetRecordedList() []interface{} {
return sliceCast(e.recorded)
}
// GetLocalList returns the Local value as a list
func (e ListElementData) GetLocalList() []interface{} {
return sliceCast(e.local)
}
// GetRemoteList returns the Remote value as a list
func (e ListElementData) GetRemoteList() []interface{} {
return sliceCast(e.remote)
}
// sliceCast casts i to a slice if it is non-nil, otherwise returns nil
func sliceCast(i interface{}) []interface{} {
if i == nil {
return nil
}
return i.([]interface{})
}

View File

@ -0,0 +1,72 @@
/*
Copyright 2017 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 apply
// MapElement contains the recorded, local and remote values for a field
// of type map
type MapElement struct {
// FieldMetaImpl contains metadata about the field from openapi
FieldMetaImpl
// MapElementData contains the value a field was set to
MapElementData
// Values contains the combined recorded-local-remote value of each item in the map
// Values contains the values in mapElement. Element must contain
// a Name matching its key in Values
Values map[string]Element
}
// Merge implements Element.Merge
func (e MapElement) Merge(v Strategy) (Result, error) {
return v.MergeMap(e)
}
// GetValues implements Element.GetValues
func (e MapElement) GetValues() map[string]Element {
return e.Values
}
var _ Element = &MapElement{}
// MapElementData contains the recorded, local and remote data for a map or type
type MapElementData struct {
RawElementData
}
// GetRecordedMap returns the Recorded value as a map
func (e MapElementData) GetRecordedMap() map[string]interface{} {
return mapCast(e.recorded)
}
// GetLocalMap returns the Local value as a map
func (e MapElementData) GetLocalMap() map[string]interface{} {
return mapCast(e.local)
}
// GetRemoteMap returns the Remote value as a map
func (e MapElementData) GetRemoteMap() map[string]interface{} {
return mapCast(e.remote)
}
// mapCast casts i to a map if it is non-nil, otherwise returns nil
func mapCast(i interface{}) map[string]interface{} {
if i == nil {
return nil
}
return i.(map[string]interface{})
}

53
vendor/k8s.io/kubernetes/pkg/kubectl/apply/parse/BUILD generated vendored Normal file
View File

@ -0,0 +1,53 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"factory.go",
"item.go",
"list_element.go",
"map_element.go",
"openapi.go",
"primitive_element.go",
"type_element.go",
"util.go",
"visitor.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/apply/parse",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubectl/apply:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
],
)
go_test(
name = "go_default_xtest",
srcs = ["suite_test.go"],
data = [
"//api/openapi-spec:swagger-spec",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/apply/parse_test",
deps = [
"//vendor/github.com/onsi/ginkgo:go_default_library",
"//vendor/github.com/onsi/ginkgo/config:go_default_library",
"//vendor/github.com/onsi/ginkgo/types:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,120 @@
/*
Copyright 2017 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 parse
import (
"fmt"
"reflect"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
)
// Factory creates an Element by combining object values from recorded, local and remote sources with
// the metadata from an openapi schema.
type Factory struct {
// Resources contains the openapi field metadata for the object models
Resources openapi.Resources
}
// CreateElement returns an Element by collating the recorded, local and remote field values
func (b *Factory) CreateElement(recorded, local, remote map[string]interface{}) (apply.Element, error) {
// Create an Item from the 3 values. Use empty name for field
visitor := &ElementBuildingVisitor{b.Resources}
gvk, err := getCommonGroupVersionKind(recorded, local, remote)
if err != nil {
return nil, err
}
// Get the openapi object metadata
s := visitor.resources.LookupResource(gvk)
oapiKind, err := getKind(s)
if err != nil {
return nil, err
}
data := apply.NewRawElementData(recorded, local, remote)
fieldName := ""
item, err := visitor.getItem(oapiKind, fieldName, data)
if err != nil {
return nil, err
}
// Collate each field of the item into a combined Element
return item.CreateElement(visitor)
}
// getItem returns the appropriate Item based on the underlying type of the arguments
func (v *ElementBuildingVisitor) getItem(s proto.Schema, name string, data apply.RawElementData) (Item, error) {
kind, err := getType(data.GetRecorded(), data.GetLocal(), data.GetRemote())
if err != nil {
return nil, err
}
if kind == nil {
// All of the items values are nil.
return &emptyItem{Name: name}, nil
}
// Create an item matching the type
switch kind.Kind() {
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint,
reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64,
reflect.String:
p, err := getPrimitive(s)
if err != nil {
return nil, fmt.Errorf("expected openapi Primitive, was %T for %v (%v)", s, kind, err)
}
return &primitiveItem{name, p, data}, nil
case reflect.Array, reflect.Slice:
a, err := getArray(s)
if err != nil {
return nil, fmt.Errorf("expected openapi Array, was %T for %v (%v)", s, kind, err)
}
return &listItem{
Name: name,
Array: a,
ListElementData: apply.ListElementData{
RawElementData: data,
},
}, nil
case reflect.Map:
if k, err := getKind(s); err == nil {
return &typeItem{
Name: name,
Type: k,
MapElementData: apply.MapElementData{
RawElementData: data,
},
}, nil
}
// If it looks like a map, and no openapi type is found, default to mapItem
m, err := getMap(s)
if err != nil {
return nil, fmt.Errorf("expected openapi Kind or Map, was %T for %v (%v)", s, kind, err)
}
return &mapItem{
Name: name,
Map: m,
MapElementData: apply.MapElementData{
RawElementData: data,
},
}, nil
}
return nil, fmt.Errorf("unsupported type type %v", kind)
}

View File

@ -0,0 +1,120 @@
/*
Copyright 2017 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 parse
import (
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// Item wraps values from 3 sources (recorded, local, remote).
// The values are not collated
type Item interface {
// CreateElement merges the values in the item into a combined Element
CreateElement(ItemVisitor) (apply.Element, error)
}
// primitiveItem contains a recorded, local, and remote value
type primitiveItem struct {
Name string
Primitive *proto.Primitive
apply.RawElementData
}
func (i *primitiveItem) CreateElement(v ItemVisitor) (apply.Element, error) {
return v.CreatePrimitiveElement(i)
}
func (i *primitiveItem) GetMeta() proto.Schema {
// https://golang.org/doc/faq#nil_error
if i.Primitive != nil {
return i.Primitive
}
return nil
}
// listItem contains a recorded, local, and remote list
type listItem struct {
Name string
Array *proto.Array
apply.ListElementData
}
func (i *listItem) CreateElement(v ItemVisitor) (apply.Element, error) {
return v.CreateListElement(i)
}
func (i *listItem) GetMeta() proto.Schema {
// https://golang.org/doc/faq#nil_error
if i.Array != nil {
return i.Array
}
return nil
}
// mapItem contains a recorded, local, and remote map
type mapItem struct {
Name string
Map *proto.Map
apply.MapElementData
}
func (i *mapItem) CreateElement(v ItemVisitor) (apply.Element, error) {
return v.CreateMapElement(i)
}
func (i *mapItem) GetMeta() proto.Schema {
// https://golang.org/doc/faq#nil_error
if i.Map != nil {
return i.Map
}
return nil
}
// mapItem contains a recorded, local, and remote map
type typeItem struct {
Name string
Type *proto.Kind
apply.MapElementData
}
func (i *typeItem) GetMeta() proto.Schema {
// https://golang.org/doc/faq#nil_error
if i.Type != nil {
return i.Type
}
return nil
}
func (i *typeItem) CreateElement(v ItemVisitor) (apply.Element, error) {
return v.CreateTypeElement(i)
}
// emptyItem contains no values
type emptyItem struct {
Name string
}
func (i *emptyItem) CreateElement(v ItemVisitor) (apply.Element, error) {
e := &apply.EmptyElement{}
e.Name = i.Name
return e, nil
}

View File

@ -0,0 +1,199 @@
/*
Copyright 2017 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 parse
import (
"fmt"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// Contains the heavy lifting for finding tuples of matching elements in lists based on the merge key
// and then uses the canonical order derived from the orders in the recorded, local and remote lists.
// replaceListElement builds a ListElement for a listItem.
// Uses the "merge" strategy to identify "same" elements across lists by a "merge key"
func (v ElementBuildingVisitor) mergeListElement(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
subtype := getSchemaType(item.Array.SubType)
switch subtype {
case "primitive":
return v.doPrimitiveList(meta, item)
case "map", "kind", "reference":
return v.doMapList(meta, item)
default:
return nil, fmt.Errorf("Cannot merge lists with subtype %s", subtype)
}
}
// doPrimitiveList merges 3 lists of primitives together
// tries to maintain ordering
func (v ElementBuildingVisitor) doPrimitiveList(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
result := &apply.ListElement{
FieldMetaImpl: apply.FieldMetaImpl{
MergeType: apply.MergeStrategy,
Name: item.Name,
},
ListElementData: item.ListElementData,
Values: []apply.Element{},
}
// Use locally defined order, then add remote, then add recorded.
orderedKeys := &apply.CombinedPrimitiveSlice{}
// Locally defined items come first and retain their order
// as defined locally
for _, l := range item.GetLocalList() {
orderedKeys.UpsertLocal(l)
}
// Mixin remote values, adding any that are not present locally
for _, l := range item.GetRemoteList() {
orderedKeys.UpsertRemote(l)
}
// Mixin recorded values, adding any that are not present locally
// or remotely
for _, l := range item.GetRecordedList() {
orderedKeys.UpsertRecorded(l)
}
for i, l := range orderedKeys.Items {
var s proto.Schema
if item.Array != nil && item.Array.SubType != nil {
s = item.Array.SubType
}
subitem, err := v.getItem(s, fmt.Sprintf("%d", i), l.RawElementData)
if err != nil {
return nil, err
}
// Convert the Item to an Element
newelem, err := subitem.CreateElement(v)
if err != nil {
return nil, err
}
// Append the element to the list
result.Values = append(result.Values, newelem)
}
return result, nil
}
// doMapList merges 3 lists of maps together by collating their values.
// tries to retain ordering
func (v ElementBuildingVisitor) doMapList(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
key := meta.GetFieldMergeKeys()
result := &apply.ListElement{
FieldMetaImpl: apply.FieldMetaImpl{
MergeType: apply.MergeStrategy,
MergeKeys: key,
Name: item.Name,
},
ListElementData: item.ListElementData,
Values: []apply.Element{},
}
// Use locally defined order, then add remote, then add recorded.
orderedKeys := &apply.CombinedMapSlice{}
// Locally defined items come first and retain their order
// as defined locally
for _, l := range item.GetLocalList() {
orderedKeys.UpsertLocal(key, l)
}
// Mixin remote values, adding any that are not present locally
for _, l := range item.GetRemoteList() {
orderedKeys.UpsertRemote(key, l)
}
// Mixin recorded values, adding any that are not present locally
// or remotely
for _, l := range item.GetRecordedList() {
orderedKeys.UpsertRecorded(key, l)
}
for i, l := range orderedKeys.Items {
var s proto.Schema
if item.Array != nil && item.Array.SubType != nil {
s = item.Array.SubType
}
subitem, err := v.getItem(s, fmt.Sprintf("%d", i), l.RawElementData)
if err != nil {
return nil, err
}
// Build the element fully
newelem, err := subitem.CreateElement(v)
if err != nil {
return nil, err
}
// Append the element to the list
result.Values = append(result.Values, newelem)
}
return result, nil
}
// replaceListElement builds a new ListElement from a listItem
// Uses the "replace" strategy and identify "same" elements across lists by their index
func (v ElementBuildingVisitor) replaceListElement(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
meta.Name = item.Name
result := &apply.ListElement{
FieldMetaImpl: meta,
ListElementData: item.ListElementData,
Values: []apply.Element{},
}
// Use the max length to iterate over the slices
for i := 0; i < max(len(item.GetRecordedList()), len(item.GetLocalList()), len(item.GetRemoteList())); i++ {
// Lookup the item from each list
data := apply.RawElementData{}
if recorded, recordedSet := boundsSafeLookup(i, item.GetRecordedList()); recordedSet {
data.SetRecorded(recorded)
}
if local, localSet := boundsSafeLookup(i, item.GetLocalList()); localSet {
data.SetLocal(local)
}
if remote, remoteSet := boundsSafeLookup(i, item.GetRemoteList()); remoteSet {
data.SetRemote(remote)
}
// Create the Item
var s proto.Schema
if item.Array != nil && item.Array.SubType != nil {
s = item.Array.SubType
}
subitem, err := v.getItem(s, fmt.Sprintf("%d", i), data)
if err != nil {
return nil, err
}
// Build the element
newelem, err := subitem.CreateElement(v)
if err != nil {
return nil, err
}
// Append the element to the list
result.Values = append(result.Values, newelem)
}
return result, nil
}

View File

@ -0,0 +1,89 @@
/*
Copyright 2017 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 parse
import (
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// mapElement builds a new mapElement from a mapItem
func (v ElementBuildingVisitor) mapElement(meta apply.FieldMetaImpl, item *mapItem) (*apply.MapElement, error) {
// Function to return schema type of the map values
var fn schemaFn = func(string) proto.Schema {
// All map values share the same schema
if item.Map != nil && item.Map.SubType != nil {
return item.Map.SubType
}
return nil
}
// Collect same fields from multiple maps into a map of elements
values, err := v.createMapValues(fn, meta, item.MapElementData)
if err != nil {
return nil, err
}
// Return the result
return &apply.MapElement{
FieldMetaImpl: meta,
MapElementData: item.MapElementData,
Values: values,
}, nil
}
// schemaFn returns the schema for a field or map value based on its name or key
type schemaFn func(key string) proto.Schema
// createMapValues combines the recorded, local and remote values from
// data into a map of elements.
func (v ElementBuildingVisitor) createMapValues(
schemaFn schemaFn,
meta apply.FieldMetaImpl,
data apply.MapElementData) (map[string]apply.Element, error) {
// Collate each key in the map
values := map[string]apply.Element{}
for _, key := range keysUnion(data.GetRecordedMap(), data.GetLocalMap(), data.GetRemoteMap()) {
combined := apply.RawElementData{}
if recorded, recordedSet := nilSafeLookup(key, data.GetRecordedMap()); recordedSet {
combined.SetRecorded(recorded)
}
if local, localSet := nilSafeLookup(key, data.GetLocalMap()); localSet {
combined.SetLocal(local)
}
if remote, remoteSet := nilSafeLookup(key, data.GetRemoteMap()); remoteSet {
combined.SetRemote(remote)
}
// Create an item for the field
field, err := v.getItem(schemaFn(key), key, combined)
if err != nil {
return nil, err
}
// Build the element for this field
element, err := field.CreateElement(v)
if err != nil {
return nil, err
}
// Add the field element to the map
values[key] = element
}
return values, nil
}

View File

@ -0,0 +1,227 @@
/*
Copyright 2017 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 parse
import (
"fmt"
"strings"
"k8s.io/kube-openapi/pkg/util/proto"
)
// Contains functions for casting openapi interfaces to their underlying types
// getSchemaType returns the string type of the schema - e.g. array, primitive, map, kind, reference
func getSchemaType(schema proto.Schema) string {
if schema == nil {
return ""
}
visitor := &baseSchemaVisitor{}
schema.Accept(visitor)
return visitor.Kind
}
// getKind converts schema to an *proto.Kind object
func getKind(schema proto.Schema) (*proto.Kind, error) {
if schema == nil {
return nil, nil
}
visitor := &kindSchemaVisitor{}
schema.Accept(visitor)
return visitor.Result, visitor.Err
}
// getArray converts schema to an *proto.Array object
func getArray(schema proto.Schema) (*proto.Array, error) {
if schema == nil {
return nil, nil
}
visitor := &arraySchemaVisitor{}
schema.Accept(visitor)
return visitor.Result, visitor.Err
}
// getMap converts schema to an *proto.Map object
func getMap(schema proto.Schema) (*proto.Map, error) {
if schema == nil {
return nil, nil
}
visitor := &mapSchemaVisitor{}
schema.Accept(visitor)
return visitor.Result, visitor.Err
}
// getPrimitive converts schema to an *proto.Primitive object
func getPrimitive(schema proto.Schema) (*proto.Primitive, error) {
if schema == nil {
return nil, nil
}
visitor := &primitiveSchemaVisitor{}
schema.Accept(visitor)
return visitor.Result, visitor.Err
}
type baseSchemaVisitor struct {
Err error
Kind string
}
// VisitArray implements openapi
func (v *baseSchemaVisitor) VisitArray(array *proto.Array) {
v.Kind = "array"
v.Err = fmt.Errorf("Array type not expected")
}
// MergeMap implements openapi
func (v *baseSchemaVisitor) VisitMap(*proto.Map) {
v.Kind = "map"
v.Err = fmt.Errorf("Map type not expected")
}
// MergePrimitive implements openapi
func (v *baseSchemaVisitor) VisitPrimitive(*proto.Primitive) {
v.Kind = "primitive"
v.Err = fmt.Errorf("Primitive type not expected")
}
// VisitKind implements openapi
func (v *baseSchemaVisitor) VisitKind(*proto.Kind) {
v.Kind = "kind"
v.Err = fmt.Errorf("Kind type not expected")
}
// VisitReference implements openapi
func (v *baseSchemaVisitor) VisitReference(reference proto.Reference) {
v.Kind = "reference"
v.Err = fmt.Errorf("Reference type not expected")
}
type kindSchemaVisitor struct {
baseSchemaVisitor
Result *proto.Kind
}
// VisitKind implements openapi
func (v *kindSchemaVisitor) VisitKind(result *proto.Kind) {
v.Result = result
v.Kind = "kind"
}
// VisitReference implements openapi
func (v *kindSchemaVisitor) VisitReference(reference proto.Reference) {
reference.SubSchema().Accept(v)
if v.Err == nil {
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
}
}
func copyExtensions(field string, from, to map[string]interface{}) error {
// Copy extensions from field to type for references
for key, val := range from {
if curr, found := to[key]; found {
// Don't allow the same extension to be defined both on the field and on the type
return fmt.Errorf("Cannot override value for extension %s on field %s from %v to %v",
key, field, curr, val)
}
to[key] = val
}
return nil
}
type mapSchemaVisitor struct {
baseSchemaVisitor
Result *proto.Map
}
// MergeMap implements openapi
func (v *mapSchemaVisitor) VisitMap(result *proto.Map) {
v.Result = result
v.Kind = "map"
}
// VisitReference implements openapi
func (v *mapSchemaVisitor) VisitReference(reference proto.Reference) {
reference.SubSchema().Accept(v)
if v.Err == nil {
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
}
}
type arraySchemaVisitor struct {
baseSchemaVisitor
Result *proto.Array
}
// VisitArray implements openapi
func (v *arraySchemaVisitor) VisitArray(result *proto.Array) {
v.Result = result
v.Kind = "array"
v.Err = copySubElementPatchStrategy(result.Path.String(), result.GetExtensions(), result.SubType.GetExtensions())
}
// copyPatchStrategy copies the strategies to subelements to the subtype
// e.g. PodTemplate.Volumes is a []Volume with "x-kubernetes-patch-strategy": "merge,retainKeys"
// the "retainKeys" strategy applies to merging Volumes, and must be copied to the sub element
func copySubElementPatchStrategy(field string, from, to map[string]interface{}) error {
// Check if the parent has a patch strategy extension
if ext, found := from["x-kubernetes-patch-strategy"]; found {
strategy, ok := ext.(string)
if !ok {
return fmt.Errorf("Expected string value for x-kubernetes-patch-strategy on %s, was %T",
field, ext)
}
// Check of the parent patch strategy has a sub patch strategy, and if so copy to the sub type
if strings.Contains(strategy, ",") {
strategies := strings.Split(strategy, ",")
if len(strategies) != 2 {
// Only 1 sub strategy is supported
return fmt.Errorf(
"Expected between 0 and 2 elements for x-kubernetes-patch-merge-strategy by got %v",
strategies)
}
to["x-kubernetes-patch-strategy"] = strategies[1]
}
}
return nil
}
// MergePrimitive implements openapi
func (v *arraySchemaVisitor) VisitReference(reference proto.Reference) {
reference.SubSchema().Accept(v)
if v.Err == nil {
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
}
}
type primitiveSchemaVisitor struct {
baseSchemaVisitor
Result *proto.Primitive
}
// MergePrimitive implements openapi
func (v *primitiveSchemaVisitor) VisitPrimitive(result *proto.Primitive) {
v.Result = result
v.Kind = "primitive"
}
// VisitReference implements openapi
func (v *primitiveSchemaVisitor) VisitReference(reference proto.Reference) {
reference.SubSchema().Accept(v)
if v.Err == nil {
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
}
}

View File

@ -0,0 +1,28 @@
/*
Copyright 2017 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 parse
import "k8s.io/kubernetes/pkg/kubectl/apply"
// primitiveElement builds a new primitiveElement from a PrimitiveItem
func (v ElementBuildingVisitor) primitiveElement(item *primitiveItem) (*apply.PrimitiveElement, error) {
meta := apply.FieldMetaImpl{Name: item.Name}
return &apply.PrimitiveElement{
FieldMetaImpl: meta,
RawElementData: item.RawElementData,
}, nil
}

View File

@ -0,0 +1,49 @@
/*
Copyright 2017 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 parse_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/config"
. "github.com/onsi/ginkgo/types"
. "github.com/onsi/gomega"
"fmt"
"testing"
)
func TestOpenapi(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t, "Openapi Suite", []Reporter{newlineReporter{}})
}
// Print a newline after the default newlineReporter due to issue
// https://github.com/jstemmer/go-junit-report/issues/31
type newlineReporter struct{}
func (newlineReporter) SpecSuiteWillBegin(config GinkgoConfigType, summary *SuiteSummary) {}
func (newlineReporter) BeforeSuiteDidRun(setupSummary *SetupSummary) {}
func (newlineReporter) AfterSuiteDidRun(setupSummary *SetupSummary) {}
func (newlineReporter) SpecWillRun(specSummary *SpecSummary) {}
func (newlineReporter) SpecDidComplete(specSummary *SpecSummary) {}
// SpecSuiteDidEnd Prints a newline between "35 Passed | 0 Failed | 0 Pending | 0 Skipped" and "--- PASS:"
func (newlineReporter) SpecSuiteDidEnd(summary *SuiteSummary) { fmt.Printf("\n") }

View File

@ -0,0 +1,46 @@
/*
Copyright 2017 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 parse
import (
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// typeElement builds a new mapElement from a typeItem
func (v ElementBuildingVisitor) typeElement(meta apply.FieldMetaImpl, item *typeItem) (*apply.TypeElement, error) {
// Function to get the schema of a field from its key
var fn schemaFn = func(key string) proto.Schema {
if item.Type != nil && item.Type.Fields != nil {
return item.Type.Fields[key]
}
return nil
}
// Collect same fields from multiple maps into a map of elements
values, err := v.createMapValues(fn, meta, item.MapElementData)
if err != nil {
return nil, err
}
// Return the result
return &apply.TypeElement{
FieldMetaImpl: meta,
MapElementData: item.MapElementData,
Values: values,
}, nil
}

View File

@ -0,0 +1,181 @@
/*
Copyright 2017 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 parse
import (
"fmt"
"reflect"
"strings"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// nilSafeLookup returns the value from the map if the map is non-nil
func nilSafeLookup(key string, from map[string]interface{}) (interface{}, bool) {
if from != nil {
value, found := from[key]
return value, found
}
// Not present
return nil, false
}
// boundsSafeLookup returns the value from the slice if the slice is non-nil and
// the index is in bounds.
func boundsSafeLookup(index int, from []interface{}) (interface{}, bool) {
if from != nil && len(from) > index {
return from[index], true
}
return nil, false
}
// keysUnion returns a slice containing the union of the keys present in the arguments
func keysUnion(maps ...map[string]interface{}) []string {
keys := map[string]interface{}{}
for _, m := range maps {
for k := range m {
keys[k] = nil
}
}
result := []string{}
for key := range keys {
result = append(result, key)
}
return result
}
// max returns the argument with the highest value
func max(values ...int) int {
v := 0
for _, i := range values {
if i > v {
v = i
}
}
return v
}
// getType returns the type of the arguments. If the arguments don't have matching
// types, getType returns an error. Nil types matching everything.
func getType(args ...interface{}) (reflect.Type, error) {
var last interface{}
for _, next := range args {
// Skip nil values
if next == nil {
continue
}
// Set the first non-nil value we find and continue
if last == nil {
last = next
continue
}
// Verify the types of the values match
if reflect.TypeOf(last).Kind() != reflect.TypeOf(next).Kind() {
return nil, fmt.Errorf("missmatching non-nil types for the same field: %T %T", last, next)
}
}
return reflect.TypeOf(last), nil
}
// getFieldMeta parses the metadata about the field from the openapi spec
func getFieldMeta(s proto.Schema, name string) (apply.FieldMetaImpl, error) {
m := apply.FieldMetaImpl{}
if s != nil {
ext := s.GetExtensions()
if e, found := ext["x-kubernetes-patch-strategy"]; found {
strategy, ok := e.(string)
if !ok {
return apply.FieldMetaImpl{}, fmt.Errorf("Expected string for x-kubernetes-patch-strategy by got %T", s)
}
// Take the first strategy if there are substrategies.
// Sub strategies are copied to sub types in openapi.go
strategies := strings.Split(strategy, ",")
if len(strategies) > 2 {
return apply.FieldMetaImpl{}, fmt.Errorf("Expected between 0 and 2 elements for x-kubernetes-patch-merge-strategy by got %v", strategies)
}
// For lists, choose the strategy for this type, not the subtype
m.MergeType = strategies[0]
}
if k, found := ext["x-kubernetes-patch-merge-key"]; found {
key, ok := k.(string)
if !ok {
return apply.FieldMetaImpl{}, fmt.Errorf("Expected string for x-kubernetes-patch-merge-key by got %T", k)
}
m.MergeKeys = apply.MergeKeys(strings.Split(key, ","))
}
}
m.Name = name
return m, nil
}
// getCommonGroupVersionKind verifies that the recorded, local and remote all share
// the same GroupVersionKind and returns the value
func getCommonGroupVersionKind(recorded, local, remote map[string]interface{}) (schema.GroupVersionKind, error) {
recordedGVK, err := getGroupVersionKind(recorded)
if err != nil {
return schema.GroupVersionKind{}, err
}
localGVK, err := getGroupVersionKind(local)
if err != nil {
return schema.GroupVersionKind{}, err
}
remoteGVK, err := getGroupVersionKind(remote)
if err != nil {
return schema.GroupVersionKind{}, err
}
if !reflect.DeepEqual(recordedGVK, localGVK) || !reflect.DeepEqual(localGVK, remoteGVK) {
return schema.GroupVersionKind{},
fmt.Errorf("group version kinds do not match (recorded: %v local: %v remote: %v)",
recordedGVK, localGVK, remoteGVK)
}
return recordedGVK, nil
}
// getGroupVersionKind returns the GroupVersionKind of the object
func getGroupVersionKind(config map[string]interface{}) (schema.GroupVersionKind, error) {
gvk := schema.GroupVersionKind{}
if gv, found := config["apiVersion"]; found {
casted, ok := gv.(string)
if !ok {
return gvk, fmt.Errorf("Expected string for apiVersion, found %T", gv)
}
s := strings.Split(casted, "/")
if len(s) != 1 {
gvk.Group = s[0]
}
gvk.Version = s[len(s)-1]
} else {
return gvk, fmt.Errorf("Missing apiVersion in Kind %v", config)
}
if k, found := config["kind"]; found {
casted, ok := k.(string)
if !ok {
return gvk, fmt.Errorf("Expected string for kind, found %T", k)
}
gvk.Kind = casted
} else {
return gvk, fmt.Errorf("Missing kind in Kind %v", config)
}
return gvk, nil
}

View File

@ -0,0 +1,79 @@
/*
Copyright 2017 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 parse
import (
"k8s.io/kubernetes/pkg/kubectl/apply"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
)
// ItemVisitor provides an interface for Items to Accept and call
// the Visit function that corresponds to its actual type.
type ItemVisitor interface {
// CreatePrimitiveElement builds an Element for a primitiveItem
CreatePrimitiveElement(*primitiveItem) (apply.Element, error)
// CreateListElement builds an Element for a listItem
CreateListElement(*listItem) (apply.Element, error)
// CreateMapElement builds an Element for a mapItem
CreateMapElement(*mapItem) (apply.Element, error)
// CreateTypeElement builds an Element for a typeItem
CreateTypeElement(*typeItem) (apply.Element, error)
}
// ElementBuildingVisitor creates an Elements from Items
// An Element combines the values from the Item with the field metadata.
type ElementBuildingVisitor struct {
resources openapi.Resources
}
// CreatePrimitiveElement creates a primitiveElement
func (v ElementBuildingVisitor) CreatePrimitiveElement(item *primitiveItem) (apply.Element, error) {
return v.primitiveElement(item)
}
// CreateListElement creates a ListElement
func (v ElementBuildingVisitor) CreateListElement(item *listItem) (apply.Element, error) {
meta, err := getFieldMeta(item.GetMeta(), item.Name)
if err != nil {
return nil, err
}
if meta.GetFieldMergeType() == apply.MergeStrategy {
return v.mergeListElement(meta, item)
}
return v.replaceListElement(meta, item)
}
// CreateMapElement creates a mapElement
func (v ElementBuildingVisitor) CreateMapElement(item *mapItem) (apply.Element, error) {
meta, err := getFieldMeta(item.GetMeta(), item.Name)
if err != nil {
return nil, err
}
return v.mapElement(meta, item)
}
// CreateTypeElement creates a typeElement
func (v ElementBuildingVisitor) CreateTypeElement(item *typeItem) (apply.Element, error) {
meta, err := getFieldMeta(item.GetMeta(), item.Name)
if err != nil {
return nil, err
}
return v.typeElement(meta, item)
}

View File

@ -0,0 +1,34 @@
/*
Copyright 2017 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 apply
// PrimitiveElement contains the recorded, local and remote values for a field
// of type primitive
type PrimitiveElement struct {
// FieldMetaImpl contains metadata about the field from openapi
FieldMetaImpl
// RawElementData contains the values the field was set to
RawElementData
}
// Merge implements Element.Merge
func (e PrimitiveElement) Merge(v Strategy) (Result, error) {
return v.MergePrimitive(e)
}
var _ Element = &PrimitiveElement{}

View File

@ -0,0 +1,71 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"merge.go",
"merge_visitor.go",
"replace_visitor.go",
"retain_keys_visitor.go",
"strategic_visitor.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/apply/strategy",
visibility = ["//visibility:public"],
deps = ["//pkg/kubectl/apply:go_default_library"],
)
go_test(
name = "go_default_xtest",
srcs = [
"merge_map_list_test.go",
"merge_map_test.go",
"merge_primitive_list_test.go",
"merge_primitive_test.go",
"replace_map_list_test.go",
"replace_map_test.go",
"replace_primitive_list_test.go",
"retain_keys_test.go",
"suite_test.go",
"utils_test.go",
],
data = [
":swagger-spec",
"//api/openapi-spec:swagger-spec",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/apply/strategy_test",
deps = [
":go_default_library",
"//pkg/kubectl/apply:go_default_library",
"//pkg/kubectl/apply/parse:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//pkg/kubectl/cmd/util/openapi/testing:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/onsi/ginkgo:go_default_library",
"//vendor/github.com/onsi/ginkgo/config:go_default_library",
"//vendor/github.com/onsi/ginkgo/types:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "swagger-spec",
srcs = glob([
"**/*.json",
]),
)

View File

@ -0,0 +1,17 @@
/*
Copyright 2017 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 strategy

View File

@ -0,0 +1,33 @@
/*
Copyright 2017 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 strategy
import "k8s.io/kubernetes/pkg/kubectl/apply"
// Options controls how a merge will be executed
type Options struct {
// FailOnConflict when true will fail patch creation if the recorded and remote
// have 2 fields set for the same value that cannot be merged.
// e.g. primitive values, list values with replace strategy, and map values with do
// strategy
FailOnConflict bool
}
// Create returns a new apply.Visitor for merging multiple objects together
func Create(options Options) apply.Strategy {
return createDelegatingStrategy(options)
}

View File

@ -0,0 +1,650 @@
/*
Copyright 2017 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 strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
)
var _ = Describe("Merging fields of type list-of-map with openapi", func() {
Context("where one of the items has been deleted resulting in the containers being empty", func() {
It("should set the containers field to null", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image
- name: item2
image: image2
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items has been deleted", func() {
It("should be deleted from the result", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item-keep
image: image-keep
- name: item-delete
image: image-delete
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item-keep
image: image-keep
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item-keep
image: image-keep
- name: item-delete
image: image-delete
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item-keep
image: image-keep
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items is only in the remote", func() {
It("should leave the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item2
image: image2
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item2
image: image2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item2
image: image2
- name: item
image: image
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items differs from the remote value and is missing from the recorded", func() {
It("should update the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:1
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items differs from the remote value but matches the recorded", func() {
It("should update the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:1
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items is missing from the remote but matches the recorded", func() {
It("should add the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items is missing from the remote and missing from the recorded ", func() {
It("should add the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where the order of the resolved, local and remote lists differs", func() {
It("should keep the order specified in local and append items appears only in remote", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: recorded-local
image: recorded:b
timeoutSeconds: 2
- name: recorded-remote
image: recorded:c
timeoutSeconds: 3
- name: recorded-local-remote
image: recorded:d
timeoutSeconds: 4
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: local
image: local:a
initialDelaySeconds: 15
- name: recorded-local-remote
image: local:b
initialDelaySeconds: 16
- name: local-remote
image: local:c
initialDelaySeconds: 17
- name: recorded-local
image: local:d
initialDelaySeconds: 18
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: remote
image: remote:a
imagePullPolicy: Always
- name: recorded-remote
image: remote:b
imagePullPolicy: Always
- name: local-remote
image: remote:c
imagePullPolicy: Always
- name: recorded-local-remote
image: remote:d
imagePullPolicy: Always
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: local
image: local:a
initialDelaySeconds: 15
- name: recorded-local-remote
image: local:b
imagePullPolicy: Always
initialDelaySeconds: 16
- name: local-remote
image: local:c
imagePullPolicy: Always
initialDelaySeconds: 17
- name: recorded-local
image: local:d
initialDelaySeconds: 18
- name: remote
image: remote:a
imagePullPolicy: Always
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})
var _ = Describe("Merging fields of type list-of-map with openapi containing a multi-field mergekey", func() {
var resources openapi.Resources
BeforeEach(func() {
resources = tst.NewFakeResources("test_swagger.json")
})
Context("where one of the items has been deleted", func() {
It("should delete the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
- containerPort: 8080
protocol: UDP
hostPort: 2022
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
- containerPort: 8080
protocol: UDP
hostPort: 2022
hostIP: "127.0.0.1"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
`)
runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, resources)
})
})
Context("where one of the items has been updated", func() {
It("should merge updates to the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
- containerPort: 8080
protocol: UDP
hostPort: 2021
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2023
- containerPort: 8080
protocol: UDP
hostPort: 2022
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
- containerPort: 8080
protocol: UDP
hostPort: 2021
hostIP: "127.0.0.1"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2023
- containerPort: 8080
protocol: UDP
hostPort: 2022
hostIP: "127.0.0.1"
`)
runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, resources)
})
})
Context("where one of the items has been added", func() {
It("should add the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
- containerPort: 8080
protocol: UDP
hostPort: 2022
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
hostIP: "127.0.0.1"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
hostIP: "127.0.0.1"
- containerPort: 8080
protocol: UDP
hostPort: 2022
`)
runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, resources)
})
})
})
var _ = Describe("Merging fields of type list-of-map with openapi", func() {
Context("containing a replace-keys sub strategy", func() {
It("should apply the replace-key strategy when merging the item", func() {
})
})
})

View File

@ -0,0 +1,164 @@
/*
Copyright 2017 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 strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
)
var _ = Describe("Merging fields of type map with openapi for some fields", func() {
Context("where a field has been deleted", func() {
It("should delete the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo1:
bar: "baz1"
image: "1"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo2: null
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo1:
bar: "baz1"
image: "1"
foo2:
bar: "baz2"
image: "2"
foo3:
bar: "baz3"
image: "3"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo3:
bar: "baz3"
image: "3"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been added", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
foo1:
bar: "baz1"
image: "1"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo1:
bar: "baz1"
image: "1"
foo2:
bar: "baz2"
image: "2"
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo1:
bar: "baz1"
image: "1"
foo2:
bar: "baz2"
image: "2"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been updated", func() {
It("should update the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
foo1:
bar: "baz1=1"
image: "1-1"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo1:
bar: "baz1-1"
image: "1-1"
foo2:
bar: "baz2-1"
image: "2-1"
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 2
foo1:
bar: "baz1-0"
image: "1-0"
foo2:
bar: "baz2-0"
image: "2-0"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo1:
bar: "baz1-1"
image: "1-1"
foo2:
bar: "baz2-1"
image: "2-1"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})

View File

@ -0,0 +1,189 @@
/*
Copyright 2017 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 strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
)
var _ = Describe("Merging fields of type list-of-primitive with openapi", func() {
Context("where one of the items has been deleted", func() {
It("should delete the deleted item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
- "c"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "c"
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
- "c"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "c"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items is only on the remote", func() {
It("should move the remote-only item to the end but keep it", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "c"
- "b"
- "a"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
- "c"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items is repeated", func() {
It("should de-duplicate the repeated items", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
- "a"
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where some items are deleted and others are on remote only", func() {
It("should retain the correct items in the correct order", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
- "c"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "c"
- "a"
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "d"
- "b"
- "c"
- "a"
- "e"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "c"
- "d"
- "e"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})

View File

@ -0,0 +1,540 @@
/*
Copyright 2017 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 strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
)
var _ = Describe("Merging fields of type map with openapi", func() {
Context("where a field has been deleted", func() {
It("should delete the field when it is the only field in the map", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - recorded/remote match
paused: true
# delete - recorded/remote differ
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - not present in recorded
replicas: null
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should delete the field when there are other fields in the map", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - recorded/remote match
paused: true
# delete - recorded/remote differ
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - not present in recorded
replicas: null
# keep
revisionHistoryLimit: 1
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
revisionHistoryLimit: 1
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
revisionHistoryLimit: 1
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been added", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
paused: true
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# Add this - it is missing from recorded and remote
replicas: 3
# Add this - it is missing from remote but matches recorded
paused: true
# Add this - it is missing from remote and differs from recorded
progressDeadlineSeconds: 2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been updated", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
paused: true
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# Missing from recorded
replicas: 3
# Matches the recorded
paused: true
# Differs from recorded
progressDeadlineSeconds: 2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 2
paused: false
progressDeadlineSeconds: 3
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should update the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 2
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 2
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})
var _ = Describe("Merging fields of type map without openapi", func() {
Context("where a field has been deleted", func() {
It("should delete the field when it is the only field in the map", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
# delete - recorded/remote match
paused: true
# delete - recorded/remote differ
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
# delete - not present in recorded
replicas: null
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should delete the field when there are other fields in the map", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
# delete - recorded/remote match
paused: true
# delete - recorded/remote differ
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
# delete - not present in recorded
replicas: null
# keep
revisionHistoryLimit: 1
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
revisionHistoryLimit: 1
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
revisionHistoryLimit: 1
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been added", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
paused: true
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
# Add this - it is missing from recorded and remote
replicas: 3
# Add this - it is missing from remote but matches recorded
paused: true
# Add this - it is missing from remote and differs from recorded
progressDeadlineSeconds: 2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been updated", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
paused: true
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
# Matches recorded
replicas: 3
# Matches the recorded
paused: true
# Differs from recorded
progressDeadlineSeconds: 2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 2
paused: false
progressDeadlineSeconds: 3
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should update the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 2
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 3
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 2
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 3
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})
var _ = Describe("Merging fields of type map with openapi", func() {
Context("where a field has been deleted", func() {
It("should delete the field when it is the only field in the map", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - recorded/remote match
foo: true
# delete - recorded/remote differ
bar: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - not present in recorded
baz: null
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
baz: 3
foo: true
bar: 2
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should delete the field when there are other fields in the map", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - recorded/remote match
foo: true
# delete - recorded/remote differ
bar: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - not present in recorded
baz: null
# keep
biz: 1
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
bar: 3
foo: true
baz: 2
biz: 1
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
biz: 1
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been added", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
foo: true
biz: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# Add this - it is missing from recorded and remote
baz: 3
# Add this - it is missing from remote but matches recorded
foo: true
# Add this - it is missing from remote and differs from recorded
biz: 2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
baz: 3
foo: true
biz: 2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been updated", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
foo: true
baz: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# Missing from recorded
bar: 3
# Matches the recorded
foo: true
# Differs from recorded
baz: 2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
bar: 2
foo: false
baz: 3
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
bar: 3
foo: true
baz: 2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})

View File

@ -0,0 +1,148 @@
/*
Copyright 2017 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 strategy
import (
"fmt"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
func createMergeStrategy(options Options, strategic *delegatingStrategy) mergeStrategy {
return mergeStrategy{
strategic,
options,
}
}
// mergeStrategy merges the values in an Element into a single Result
type mergeStrategy struct {
strategic *delegatingStrategy
options Options
}
// MergeList merges the lists in a ListElement into a single Result
func (v mergeStrategy) MergeList(e apply.ListElement) (apply.Result, error) {
// No merge logic if adding or deleting a field
if result, done := v.doAddOrDelete(e); done {
return result, nil
}
// Merge each item in the list and append it to the list
merged := []interface{}{}
for _, value := range e.Values {
// Recursively merge the list element before adding the value to the list
m, err := value.Merge(v.strategic)
if err != nil {
return apply.Result{}, err
}
switch m.Operation {
case apply.SET:
// Keep the list item value
merged = append(merged, m.MergedResult)
case apply.DROP:
// Drop the list item value
default:
panic(fmt.Errorf("Unexpected result operation type %+v", m))
}
}
if len(merged) == 0 {
// If the list is empty, return a nil entry
return apply.Result{Operation: apply.SET, MergedResult: nil}, nil
}
// Return the merged list, and tell the caller to keep it
return apply.Result{Operation: apply.SET, MergedResult: merged}, nil
}
// MergeMap merges the maps in a MapElement into a single Result
func (v mergeStrategy) MergeMap(e apply.MapElement) (apply.Result, error) {
// No merge logic if adding or deleting a field
if result, done := v.doAddOrDelete(e); done {
return result, nil
}
return v.doMergeMap(e.GetValues())
}
// MergeMap merges the type instances in a TypeElement into a single Result
func (v mergeStrategy) MergeType(e apply.TypeElement) (apply.Result, error) {
// No merge logic if adding or deleting a field
if result, done := v.doAddOrDelete(e); done {
return result, nil
}
return v.doMergeMap(e.GetValues())
}
// do merges a recorded, local and remote map into a new object
func (v mergeStrategy) doMergeMap(e map[string]apply.Element) (apply.Result, error) {
// Merge each item in the list
merged := map[string]interface{}{}
for key, value := range e {
// Recursively merge the map element before adding the value to the map
result, err := value.Merge(v.strategic)
if err != nil {
return apply.Result{}, err
}
switch result.Operation {
case apply.SET:
// Keep the map item value
merged[key] = result.MergedResult
case apply.DROP:
// Drop the map item value
default:
panic(fmt.Errorf("Unexpected result operation type %+v", result))
}
}
// Return the merged map, and tell the caller to keep it
if len(merged) == 0 {
// Special case the empty map to set the field value to nil, but keep the field key
// This is how the tests expect the structures to look when parsed from yaml
return apply.Result{Operation: apply.SET, MergedResult: nil}, nil
}
return apply.Result{Operation: apply.SET, MergedResult: merged}, nil
}
func (v mergeStrategy) doAddOrDelete(e apply.Element) (apply.Result, bool) {
if apply.IsAdd(e) {
return apply.Result{Operation: apply.SET, MergedResult: e.GetLocal()}, true
}
// Delete the List
if apply.IsDrop(e) {
return apply.Result{Operation: apply.DROP}, true
}
return apply.Result{}, false
}
// MergePrimitive returns and error. Primitive elements can't be merged, only replaced.
func (v mergeStrategy) MergePrimitive(diff apply.PrimitiveElement) (apply.Result, error) {
return apply.Result{}, fmt.Errorf("Cannot merge primitive element %v", diff.Name)
}
// MergeEmpty returns an empty result
func (v mergeStrategy) MergeEmpty(diff apply.EmptyElement) (apply.Result, error) {
return apply.Result{Operation: apply.SET}, nil
}
var _ apply.Strategy = &mergeStrategy{}

View File

@ -0,0 +1,73 @@
/*
Copyright 2017 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 strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
)
var _ = Describe("Replacing fields of type list without openapi", func() {
Context("where a field is has been updated", func() {
It("should replace the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
bar:
- name: bar1
value: 1
- name: bar2
value: 2
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
bar:
- name: bar1
value: 1
- name: bar2
value: 2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
bar:
- name: bar1
value: 1
- name: bar3
value: 3
- name: bar4
value: 4
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
bar:
- name: bar1
value: 1
- name: bar2
value: 2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})

View File

@ -0,0 +1,80 @@
/*
Copyright 2017 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 strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
)
var _ = Describe("Replacing fields of type map with openapi for some fields", func() {
var resources openapi.Resources
BeforeEach(func() {
resources = tst.NewFakeResources("test_swagger.json")
})
Context("where a field is has been updated", func() {
It("should update the field", func() {
recorded := create(`
apiVersion: extensions/v1beta1
kind: ReplicaSet
spec:
template:
containers:
- name: container1
image: image1
`)
local := create(`
apiVersion: extensions/v1beta1
kind: ReplicaSet
spec:
template:
containers:
- name: container1
image: image1
`)
remote := create(`
apiVersion: extensions/v1beta1
kind: ReplicaSet
spec:
template:
containers:
- name: container1
image: image1
- name: container2
image: image2
- name: container3
image: image3
`)
expected := create(`
apiVersion: extensions/v1beta1
kind: ReplicaSet
spec:
template:
containers:
- name: container1
image: image1
`)
// Use modified swagger for ReplicaSet spec
runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, resources)
})
})
})

View File

@ -0,0 +1,723 @@
/*
Copyright 2017 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 strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
)
var _ = Describe("Replacing fields of type list with openapi", func() {
Context("where the field has been deleted", func() {
It("should delete the field if present in recorded and missing from local.", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- z
- "y"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should delete the field if missing in recorded and set to null in local.", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- z
- "y"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where the field is has been added", func() {
It("should add the field when missing from recorded", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should add the field when even when present in recorded", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should add the field when the parent field is missing as well", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been updated", func() {
It("should replace the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
- z
- "y"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should replace the field even if recorded matches", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
- z
- "y"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should replace the field even if the only change is ordering", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- e
- c
- f
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
spec:
template:
spec:
containers:
- name: container
command:
- f
- e
- c
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})
var _ = Describe("Replacing fields of type list with openapi for the type, but not the field", func() {
Context("where a field is has been updated", func() {
It("should replace the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
otherstuff:
- name: container1
command:
- e
- f
- g
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
- z
- "y"
otherstuff:
- name: container1
command:
- s
- d
- f
- name: container2
command:
- h
- i
- j
- name: container3
command:
- k
- l
- m
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
otherstuff:
- name: container1
command:
- e
- f
- g
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})
var _ = Describe("Replacing fields of type list without openapi", func() {
Context("where the field has been deleted", func() {
It("should delete the field.", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
command:
- a
- b
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
arguments:
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
command:
- a
- b
- z
- "y"
# explicitly delete this
arguments:
- a
- b
- z
- "y"
# keep this
env:
- a
- b
- z
- "y"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
env:
- a
- b
- z
- "y"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where the field is has been added", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
command:
- a
- b
- z
- "y"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
# missing from recorded - add
command:
- a
- b
- z
- "y"
# missing from recorded - add
arguments:
- c
- d
- q
- w
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
command:
- a
- b
- z
- "y"
arguments:
- c
- d
- q
- w
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been updated", func() {
It("should replace field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
command:
- a
- b
- c
env:
- s
- "t"
- u
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
command:
- a
- b
- z
- "y"
arguments:
- c
- d
- q
- w
env:
- s
- "t"
- u
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
spec:
template:
command:
- a
- b
- c
- z
- "y"
arguments:
- c
- d
- i
env:
- u
- s
- "t"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
command:
- a
- b
- z
- "y"
arguments:
- c
- d
- q
- w
env:
- s
- "t"
- u
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})

View File

@ -0,0 +1,100 @@
/*
Copyright 2017 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 strategy
import (
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// replaceVisitor creates a patch to replace a remote field value with a local field value
type replaceStrategy struct {
strategic *delegatingStrategy
options Options
}
func createReplaceStrategy(options Options, strategic *delegatingStrategy) replaceStrategy {
return replaceStrategy{
strategic,
options,
}
}
// MergeList returns a result by merging the recorded, local and remote values
// - replacing the remote value with the local value
func (v replaceStrategy) MergeList(e apply.ListElement) (apply.Result, error) {
return v.doReplace(e)
}
// MergeMap returns a result by merging the recorded, local and remote values
// - replacing the remote value with the local value
func (v replaceStrategy) MergeMap(e apply.MapElement) (apply.Result, error) {
return v.doReplace(e)
}
// MergeType returns a result by merging the recorded, local and remote values
// - replacing the remote value with the local value
func (v replaceStrategy) MergeType(e apply.TypeElement) (apply.Result, error) {
return v.doReplace(e)
}
// MergePrimitive returns a result by merging the recorded, local and remote values
// - replacing the remote value with the local value
func (v replaceStrategy) MergePrimitive(e apply.PrimitiveElement) (apply.Result, error) {
return v.doReplace(e)
}
// MergeEmpty
func (v replaceStrategy) MergeEmpty(e apply.EmptyElement) (apply.Result, error) {
return apply.Result{Operation: apply.SET}, nil
}
// replace returns the local value if specified, otherwise it returns the remote value
// this works regardless of the approach
func (v replaceStrategy) doReplace(e apply.Element) (apply.Result, error) {
// TODO: Check for conflicts
if result, done := v.doAddOrDelete(e); done {
return result, nil
}
if e.HasLocal() {
// Specified locally, set the local value
return apply.Result{Operation: apply.SET, MergedResult: e.GetLocal()}, nil
} else if e.HasRemote() {
// Not specified locally, set the remote value
return apply.Result{Operation: apply.SET, MergedResult: e.GetRemote()}, nil
} else {
// Only specified in the recorded, drop the field.
return apply.Result{Operation: apply.DROP, MergedResult: e.GetRemote()}, nil
}
}
// doAddOrDelete will check if the field should be either added or deleted. If either is true, it will
// true the operation and true. Otherwise it will return false.
func (v replaceStrategy) doAddOrDelete(e apply.Element) (apply.Result, bool) {
if apply.IsAdd(e) {
return apply.Result{Operation: apply.SET, MergedResult: e.GetLocal()}, true
}
// Delete the List
if apply.IsDrop(e) {
return apply.Result{Operation: apply.DROP}, true
}
return apply.Result{}, false
}
var _ apply.Strategy = &replaceStrategy{}

View File

@ -0,0 +1,195 @@
/*
Copyright 2017 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 strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
)
var _ = Describe("Merging fields with the retainkeys strategy", func() {
Context("where some fields are only defined remotely", func() {
It("should drop those fields ", func() {
recorded := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
`)
local := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
type: Recreate
`)
remote := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
`)
expected := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
type: Recreate
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where some fields are defined both locally and remotely", func() {
It("should merge those fields", func() {
recorded := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
`)
local := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 2
`)
remote := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
`)
expected := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 2
maxSurge: 1
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where the elements are in a list and some fields are only defined remotely", func() {
It("should drop those fields ", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
volumes:
- name: cache-volume
emptyDir:
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
volumes:
- name: cache-volume
hostPath:
path: /tmp/cache-volume
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
volumes:
- name: cache-volume
emptyDir:
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where the elements are in a list", func() {
It("the fields defined both locally and remotely should be merged", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
volumes:
- name: cache-volume
hostPath:
path: /tmp/cache-volume
emptyDir:
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
volumes:
- name: cache-volume
hostPath:
path: /tmp/cache-volume
type: Directory
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
volumes:
- name: cache-volume
hostPath:
path: /tmp/cache-volume
type: Directory
emptyDir:
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})

View File

@ -0,0 +1,77 @@
/*
Copyright 2017 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 strategy
import (
"fmt"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
func createRetainKeysStrategy(options Options, strategic *delegatingStrategy) retainKeysStrategy {
return retainKeysStrategy{
&mergeStrategy{strategic, options},
strategic,
options,
}
}
// retainKeysStrategy merges the values in an Element into a single Result,
// dropping any fields omitted from the local copy. (but merging values when
// defined locally and remotely)
type retainKeysStrategy struct {
merge *mergeStrategy
strategic *delegatingStrategy
options Options
}
// MergeMap merges the type instances in a TypeElement into a single Result
// keeping only the fields defined locally, but merging their values with
// the remote values.
func (v retainKeysStrategy) MergeType(e apply.TypeElement) (apply.Result, error) {
// No merge logic if adding or deleting a field
if result, done := v.merge.doAddOrDelete(&e); done {
return result, nil
}
elem := map[string]apply.Element{}
for key := range e.GetLocalMap() {
elem[key] = e.GetValues()[key]
}
return v.merge.doMergeMap(elem)
}
// MergeMap returns an error. Only TypeElements can have retainKeys.
func (v retainKeysStrategy) MergeMap(e apply.MapElement) (apply.Result, error) {
return apply.Result{}, fmt.Errorf("Cannot use retainkeys with map element %v", e.Name)
}
// MergeList returns an error. Only TypeElements can have retainKeys.
func (v retainKeysStrategy) MergeList(e apply.ListElement) (apply.Result, error) {
return apply.Result{}, fmt.Errorf("Cannot use retainkeys with list element %v", e.Name)
}
// MergePrimitive returns an error. Only TypeElements can have retainKeys.
func (v retainKeysStrategy) MergePrimitive(diff apply.PrimitiveElement) (apply.Result, error) {
return apply.Result{}, fmt.Errorf("Cannot use retainkeys with primitive element %v", diff.Name)
}
// MergeEmpty returns an empty result
func (v retainKeysStrategy) MergeEmpty(diff apply.EmptyElement) (apply.Result, error) {
return v.merge.MergeEmpty(diff)
}
var _ apply.Strategy = &retainKeysStrategy{}

View File

@ -0,0 +1,99 @@
/*
Copyright 2017 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 strategy
import (
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// delegatingStrategy delegates merging fields to other visitor implementations
// based on the merge strategy preferred by the field.
type delegatingStrategy struct {
options Options
merge mergeStrategy
replace replaceStrategy
retainKeys retainKeysStrategy
}
// createDelegatingStrategy returns a new delegatingStrategy
func createDelegatingStrategy(options Options) *delegatingStrategy {
v := &delegatingStrategy{
options: options,
}
v.replace = createReplaceStrategy(options, v)
v.merge = createMergeStrategy(options, v)
v.retainKeys = createRetainKeysStrategy(options, v)
return v
}
// MergeList delegates visiting a list based on the field patch strategy.
// Defaults to "replace"
func (v delegatingStrategy) MergeList(diff apply.ListElement) (apply.Result, error) {
switch diff.GetFieldMergeType() {
case apply.MergeStrategy:
return v.merge.MergeList(diff)
case apply.ReplaceStrategy:
return v.replace.MergeList(diff)
case apply.RetainKeysStrategy:
return v.retainKeys.MergeList(diff)
default:
return v.replace.MergeList(diff)
}
}
// MergeMap delegates visiting a map based on the field patch strategy.
// Defaults to "merge"
func (v delegatingStrategy) MergeMap(diff apply.MapElement) (apply.Result, error) {
switch diff.GetFieldMergeType() {
case apply.MergeStrategy:
return v.merge.MergeMap(diff)
case apply.ReplaceStrategy:
return v.replace.MergeMap(diff)
case apply.RetainKeysStrategy:
return v.retainKeys.MergeMap(diff)
default:
return v.merge.MergeMap(diff)
}
}
// MergeType delegates visiting a map based on the field patch strategy.
// Defaults to "merge"
func (v delegatingStrategy) MergeType(diff apply.TypeElement) (apply.Result, error) {
switch diff.GetFieldMergeType() {
case apply.MergeStrategy:
return v.merge.MergeType(diff)
case apply.ReplaceStrategy:
return v.replace.MergeType(diff)
case apply.RetainKeysStrategy:
return v.retainKeys.MergeType(diff)
default:
return v.merge.MergeType(diff)
}
}
// MergePrimitive delegates visiting a primitive to the ReplaceVisitorSingleton.
func (v delegatingStrategy) MergePrimitive(diff apply.PrimitiveElement) (apply.Result, error) {
// Always replace primitives
return v.replace.MergePrimitive(diff)
}
// MergeEmpty
func (v delegatingStrategy) MergeEmpty(diff apply.EmptyElement) (apply.Result, error) {
return v.merge.MergeEmpty(diff)
}
var _ apply.Strategy = &delegatingStrategy{}

View File

@ -0,0 +1,49 @@
/*
Copyright 2017 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 strategy_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/config"
. "github.com/onsi/ginkgo/types"
. "github.com/onsi/gomega"
"fmt"
"testing"
)
func TestOpenapi(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t, "Openapi Suite", []Reporter{newlineReporter{}})
}
// Print a newline after the default newlineReporter due to issue
// https://github.com/jstemmer/go-junit-report/issues/31
type newlineReporter struct{}
func (newlineReporter) SpecSuiteWillBegin(config GinkgoConfigType, summary *SuiteSummary) {}
func (newlineReporter) BeforeSuiteDidRun(setupSummary *SetupSummary) {}
func (newlineReporter) AfterSuiteDidRun(setupSummary *SetupSummary) {}
func (newlineReporter) SpecWillRun(specSummary *SpecSummary) {}
func (newlineReporter) SpecDidComplete(specSummary *SpecSummary) {}
// SpecSuiteDidEnd Prints a newline between "35 Passed | 0 Failed | 0 Pending | 0 Skipped" and "--- PASS:"
func (newlineReporter) SpecSuiteDidEnd(summary *SuiteSummary) { fmt.Printf("\n") }

View File

@ -0,0 +1,250 @@
{
"swagger": "2.0",
"info": {
"title": "Kubernetes",
"version": "v1.9.0"
},
"paths": {
},
"definitions": {
"io.k8s.api.core.v1.Container": {
"description": "A single application container that you want to run within a pod.",
"required": [
"name",
"image"
],
"properties": {
"image": {
"description": "Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images",
"type": "string"
},
"name": {
"description": "Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated.",
"type": "string"
},
"ports": {
"description": "List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default \"0.0.0.0\" address inside a container will be accessible from the network. Cannot be updated.",
"type": "array",
"items": {
"$ref": "#/definitions/io.k8s.api.core.v1.ContainerPort"
},
"x-kubernetes-patch-merge-key": "containerPort,protocol",
"x-kubernetes-patch-strategy": "merge"
}
}
},
"io.k8s.api.core.v1.ContainerPort": {
"description": "ContainerPort represents a network port in a single container.",
"required": [
"containerPort"
],
"properties": {
"containerPort": {
"description": "Number of port to expose on the pod's IP address. This must be a valid port number, 0 \u003c x \u003c 65536.",
"type": "integer",
"format": "int32"
},
"hostIP": {
"description": "What host IP to bind the external port to.",
"type": "string"
},
"hostPort": {
"description": "Number of port to expose on the host. If specified, this must be a valid port number, 0 \u003c x \u003c 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this.",
"type": "integer",
"format": "int32"
},
"name": {
"description": "If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services.",
"type": "string"
},
"protocol": {
"description": "Protocol for port. Must be UDP or TCP. Defaults to \"TCP\".",
"type": "string"
}
}
},
"io.k8s.api.apps.v1beta1.Deployment": {
"description": "DEPRECATED - This group version of Deployment is deprecated by apps/v1beta2/Deployment. See the release notes for more information. Deployment enables declarative updates for Pods and ReplicaSets.",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
"type": "string"
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"description": "Standard object metadata.",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
},
"spec": {
"description": "Specification of the desired behavior of the Deployment.",
"$ref": "#/definitions/io.k8s.api.apps.v1beta1.DeploymentSpec"
},
},
"x-kubernetes-group-version-kind": [
{
"group": "apps",
"kind": "Deployment",
"version": "v1beta1"
}
]
},
"io.k8s.api.apps.v1beta1.DeploymentSpec": {
"description": "DeploymentSpec is the specification of the desired behavior of the Deployment.",
"required": [
"template"
],
"properties": {
"minReadySeconds": {
"description": "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)",
"type": "integer",
"format": "int32"
},
"paused": {
"description": "Indicates that the deployment is paused.",
"type": "boolean"
},
"progressDeadlineSeconds": {
"description": "The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Once autoRollback is implemented, the deployment controller will automatically rollback failed deployments. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s.",
"type": "integer",
"format": "int32"
},
"replicas": {
"description": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.",
"type": "integer",
"format": "int32"
},
"revisionHistoryLimit": {
"description": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 2.",
"type": "integer",
"format": "int32"
},
"template": {
"description": "Template describes the pods that will be created.",
"$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec"
}
}
},
"io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta": {
"description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.",
"properties": {
"annotations": {
"description": "Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"labels": {
"description": "Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"name": {
"description": "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names",
"type": "string"
},
"namespace": {
"description": "Namespace defines the space within each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.\n\nMust be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces",
"type": "string"
}
}
},
"io.k8s.api.core.v1.PodTemplateSpec": {
"description": "PodTemplateSpec describes the data a pod should have when created from a template",
"properties": {
"metadata": {
"description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
},
"spec": {
"description": "Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status",
"$ref": "#/definitions/io.k8s.api.core.v1.PodSpec"
}
}
},
"io.k8s.api.core.v1.PodSpec": {
"description": "PodSpec is a description of a pod.",
"required": [
"containers"
],
"properties": {
"containers": {
"description": "List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated.",
"type": "array",
"items": {
"$ref": "#/definitions/io.k8s.api.core.v1.Container"
},
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
}
}
},
"io.k8s.api.extensions.v1beta1.ReplicaSet": {
"description": "DEPRECATED - This group version of ReplicaSet is deprecated by apps/v1beta2/ReplicaSet. See the release notes for more information. ReplicaSet represents the configuration of a ReplicaSet.",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
"type": "string"
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"description": "If the Labels of a ReplicaSet are empty, they are defaulted to be the same as the Pod(s) that the ReplicaSet manages. Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
},
"spec": {
"description": "Spec defines the specification of the desired behavior of the ReplicaSet. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status",
"$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSetSpec",
"x-kubernetes-patch-strategy": "replace"
}
},
"x-kubernetes-group-version-kind": [
{
"group": "extensions",
"kind": "ReplicaSet",
"version": "v1beta1"
}
]
},
"io.k8s.api.extensions.v1beta1.ReplicaSetSpec": {
"description": "ReplicaSetSpec is the specification of a ReplicaSet.",
"properties": {
"minReadySeconds": {
"description": "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)",
"type": "integer",
"format": "int32"
},
"replicas": {
"description": "Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller",
"type": "integer",
"format": "int32"
},
"template": {
"description": "Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template",
"$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec"
}
}
}
},
"securityDefinitions": {
"BearerToken": {
"description": "Bearer Token authentication",
"type": "apiKey",
"name": "authorization",
"in": "header"
}
},
"security": [
{
"BearerToken": []
}
]
}

View File

@ -0,0 +1,66 @@
/*
Copyright 2017 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 strategy_test
import (
. "github.com/onsi/gomega"
"fmt"
"path/filepath"
"strings"
"github.com/ghodss/yaml"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/kubernetes/pkg/kubectl/apply"
"k8s.io/kubernetes/pkg/kubectl/apply/parse"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
)
var fakeResources = tst.NewFakeResources(filepath.Join("..", "..", "..", "..", "api", "openapi-spec", "swagger.json"))
// run parses the openapi and runs the tests
func run(instance apply.Strategy, recorded, local, remote, expected map[string]interface{}) {
runWith(instance, recorded, local, remote, expected, fakeResources)
}
func runWith(instance apply.Strategy, recorded, local, remote, expected map[string]interface{}, resources openapi.Resources) {
parseFactory := parse.Factory{Resources: resources}
parsed, err := parseFactory.CreateElement(recorded, local, remote)
Expect(err).Should(Not(HaveOccurred()))
merged, err := parsed.Merge(instance)
Expect(err).ShouldNot(HaveOccurred())
Expect(merged.Operation).Should(Equal(apply.SET))
Expect(merged.MergedResult).Should(Equal(expected), diff.ObjectDiff(merged.MergedResult, expected))
}
// create parses the yaml string into a map[string]interface{}. Verifies that the string does not have
// any tab characters.
func create(config string) map[string]interface{} {
result := map[string]interface{}{}
// The yaml parser will throw an obscure error if there are tabs in the yaml. Check for this
Expect(strings.Contains(config, "\t")).To(
BeFalse(), fmt.Sprintf("Yaml %s cannot contain tabs", config))
Expect(yaml.Unmarshal([]byte(config), &result)).Should(
Not(HaveOccurred()), fmt.Sprintf("Could not parse config:\n\n%s\n", config))
return result
}

View File

@ -0,0 +1,43 @@
/*
Copyright 2017 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 apply
// TypeElement contains the recorded, local and remote values for a field
// that is a complex type
type TypeElement struct {
// FieldMetaImpl contains metadata about the field from openapi
FieldMetaImpl
MapElementData
// Values contains the combined recorded-local-remote value of each field in the type
// Values contains the values in mapElement. Element must contain
// a Name matching its key in Values
Values map[string]Element
}
// Merge implements Element.Merge
func (e TypeElement) Merge(v Strategy) (Result, error) {
return v.MergeType(e)
}
// GetValues implements Element.GetValues
func (e TypeElement) GetValues() map[string]Element {
return e.Values
}
var _ Element = &TypeElement{}

68
vendor/k8s.io/kubernetes/pkg/kubectl/apply/visitor.go generated vendored Normal file
View File

@ -0,0 +1,68 @@
/*
Copyright 2017 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 apply
// Strategy implements a strategy for merging recorded, local and remote values contained
// in an element and returns the merged result.
// Follows the visitor pattern
type Strategy interface {
// MergeList is invoked by ListElements when Merge is called
MergeList(ListElement) (Result, error)
// MergeMap is invoked by MapElements when Merge is called
MergeMap(MapElement) (Result, error)
// MergeType is invoked by TypeElements when Merge is called
MergeType(TypeElement) (Result, error)
// MergePrimitive is invoked by PrimitiveElements when Merge is called
MergePrimitive(PrimitiveElement) (Result, error)
// MergeEmpty is invoked by EmptyElements when Merge is called
MergeEmpty(EmptyElement) (Result, error)
}
// Operation records whether a field should be set or dropped
type Operation int
const (
// ERROR is an error during merge
ERROR Operation = iota
// SET sets the field on an object
SET
// DROP drops the field from an object
DROP
)
// Result is the result of merging fields
type Result struct {
// Operation is the operation that should be performed for the merged field
Operation Operation
// MergedResult is the new merged value
MergedResult interface{}
}
const (
// MergeStrategy is the strategy to merge the local and remote values
MergeStrategy = "merge"
// RetainKeysStrategy is the strategy to merge the local and remote values, but drop any fields not defined locally
RetainKeysStrategy = "retainKeys"
// ReplaceStrategy is the strategy to replace the remote value with the local value
ReplaceStrategy = "replace"
)