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

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
}