Merge pull request #40823 from liggitt/edit-tests

Automatic merge from submit-queue (batch tested with PRs 38796, 40823, 40756, 41083, 41105)

Add unit tests for interactive edit command

Before updating edit to use unstructured objects and use generic JSON patching, we need better test coverage of the existing paths. This adds unit tests for the interactive edit scenarios. 

This PR adds:
* Simple framework for recording tests for interactive edit:
  * record.go is a tiny test server that records editor and API inputs as test expectations, and editor and API outputs as playback stubs
  * record_editor.sh is a shell script that sends the before/after of an interactive `vi` edit to the test server
  * record_testcase.sh (see README) starts up the test server, sets up a kubeconfig to proxy to the test server, sets EDITOR to invoke record_editor.sh, then opens a shell that lets you use `kubectl edit` normally
* Adds test cases for the following scenarios:
  - [x] no-op edit (open and close without making changes)
  - [x] try to edit a missing object
  - [x] edit single item successfully
  - [x] edit list of items successfully
  - [x] edit a single item, submit with an error, re-edit, submit fixed successfully
  - [x] edit list of items, submit some with errors and some good, re-edit errors, submit fixed
  - [x] edit trying to change immutable things like name/version/kind, ensure preconditions prevent submission
  - [x] edit in "create mode" successfully (`kubectl create -f ... --edit`)
  - [x] edit in "create mode" introducing errors (`kubectl create -f ... --edit`)
* Fixes a bug with edit printing errors to stdout (caught when testing stdout/stderr against expected output)

Follow-ups:
- [ ] clean up edit code path
- [ ] switch edit to use unstructured objects
- [ ] make edit fall back to jsonmerge for objects without registered go structs (TPR, unknown versions of pods, etc)
- [ ] add tests:
  - [ ] edit TPR
  - [ ] edit mix of TPR and known objects
  - [ ] edit known object with extra field from server
  - [ ] edit known object with new version from server
This commit is contained in:
Kubernetes Submit Queue 2017-02-08 00:49:46 -08:00 committed by GitHub
commit 79e333afe0
96 changed files with 2746 additions and 9 deletions

View File

@ -150,6 +150,7 @@ go_test(
"delete_test.go",
"describe_test.go",
"drain_test.go",
"edit_test.go",
"exec_test.go",
"expose_test.go",
"get_test.go",
@ -166,6 +167,7 @@ go_test(
"top_test.go",
],
data = [
"testdata",
"//examples:config",
"//test/fixtures",
],
@ -191,6 +193,7 @@ go_test(
"//pkg/util/term:go_default_library",
"//vendor:github.com/spf13/cobra",
"//vendor:github.com/stretchr/testify/assert",
"//vendor:gopkg.in/yaml.v2",
"//vendor:k8s.io/apimachinery/pkg/api/equality",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/api/meta",
@ -201,7 +204,9 @@ go_test(
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/json",
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/streaming",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/diff",
"//vendor:k8s.io/apimachinery/pkg/util/intstr",
"//vendor:k8s.io/apimachinery/pkg/util/sets",
"//vendor:k8s.io/apimachinery/pkg/util/strategicpatch",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/apimachinery/pkg/watch",
@ -228,6 +233,7 @@ filegroup(
"//pkg/kubectl/cmd/rollout:all-srcs",
"//pkg/kubectl/cmd/set:all-srcs",
"//pkg/kubectl/cmd/templates:all-srcs",
"//pkg/kubectl/cmd/testdata/edit:all-srcs",
"//pkg/kubectl/cmd/testing:all-srcs",
"//pkg/kubectl/cmd/util:all-srcs",
],

View File

@ -240,7 +240,7 @@ func runEdit(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args
file: file,
}
containsError = true
fmt.Fprintln(out, results.addError(errors.NewInvalid(api.Kind(""), "", field.ErrorList{field.Invalid(nil, "The edited file failed validation", fmt.Sprintf("%v", err))}), infos[0]))
fmt.Fprintln(errOut, results.addError(errors.NewInvalid(api.Kind(""), "", field.ErrorList{field.Invalid(nil, "The edited file failed validation", fmt.Sprintf("%v", err))}), infos[0]))
continue
}
@ -505,7 +505,7 @@ func visitToPatch(
results.version = defaultVersion
patched, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch)
if err != nil {
fmt.Fprintln(out, results.addError(err, info))
fmt.Fprintln(errOut, results.addError(err, info))
return nil
}
info.Refresh(patched, true)

View File

@ -0,0 +1,281 @@
/*
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 cmd
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/spf13/cobra"
yaml "gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
)
type EditTestCase struct {
Description string `yaml:"description"`
// create or edit
Mode string `yaml:"mode"`
Args []string `yaml:"args"`
Filename string `yaml:"filename"`
Output string `yaml:"outputFormat"`
Namespace string `yaml:"namespace"`
ExpectedStdout []string `yaml:"expectedStdout"`
ExpectedStderr []string `yaml:"expectedStderr"`
ExpectedExitCode int `yaml:"expectedExitCode"`
Steps []EditStep `yaml:"steps"`
}
type EditStep struct {
// edit or request
StepType string `yaml:"type"`
// only applies to request
RequestMethod string `yaml:"expectedMethod,omitempty"`
RequestPath string `yaml:"expectedPath,omitempty"`
RequestContentType string `yaml:"expectedContentType,omitempty"`
Input string `yaml:"expectedInput"`
// only applies to request
ResponseStatusCode int `yaml:"resultingStatusCode,omitempty"`
Output string `yaml:"resultingOutput"`
}
func TestEdit(t *testing.T) {
var (
name string
testcase EditTestCase
i int
err error
)
const updateEnvVar = "UPDATE_EDIT_FIXTURE_DATA"
updateInputFixtures := os.Getenv(updateEnvVar) == "true"
reqResp := func(req *http.Request) (*http.Response, error) {
defer func() { i++ }()
if i > len(testcase.Steps)-1 {
t.Fatalf("%s, step %d: more requests than steps, got %s %s", name, i, req.Method, req.URL.Path)
}
step := testcase.Steps[i]
body := []byte{}
if req.Body != nil {
body, err = ioutil.ReadAll(req.Body)
if err != nil {
t.Fatalf("%s, step %d: %v", name, i, err)
}
}
inputFile := filepath.Join("testdata/edit", "testcase-"+name, step.Input)
expectedInput, err := ioutil.ReadFile(inputFile)
if err != nil {
t.Fatalf("%s, step %d: %v", name, i, err)
}
outputFile := filepath.Join("testdata/edit", "testcase-"+name, step.Output)
resultingOutput, err := ioutil.ReadFile(outputFile)
if err != nil {
t.Fatalf("%s, step %d: %v", name, i, err)
}
if req.Method == "POST" && req.URL.Path == "/callback" {
if step.StepType != "edit" {
t.Fatalf("%s, step %d: expected edit step, got %s %s", name, i, req.Method, req.URL.Path)
}
if bytes.Compare(body, expectedInput) != 0 {
if updateInputFixtures {
// Convenience to allow recapturing the input and persisting it here
ioutil.WriteFile(inputFile, body, os.FileMode(0644))
} else {
t.Errorf("%s, step %d: diff in edit content:\n%s", name, i, diff.StringDiff(string(body), string(expectedInput)))
t.Logf("If the change in input is expected, rerun tests with %s=true to update input fixtures", updateEnvVar)
}
}
return &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(resultingOutput))}, nil
} else {
if step.StepType != "request" {
t.Fatalf("%s, step %d: expected request step, got %s %s", name, i, req.Method, req.URL.Path)
}
body = tryIndent(body)
expectedInput = tryIndent(expectedInput)
if req.Method != step.RequestMethod || req.URL.Path != step.RequestPath || req.Header.Get("Content-Type") != step.RequestContentType {
t.Fatalf(
"%s, step %d: expected \n%s %s (content-type=%s)\ngot\n%s %s (content-type=%s)", name, i,
step.RequestMethod, step.RequestPath, step.RequestContentType,
req.Method, req.URL.Path, req.Header.Get("Content-Type"),
)
}
if bytes.Compare(body, expectedInput) != 0 {
if updateInputFixtures {
// Convenience to allow recapturing the input and persisting it here
ioutil.WriteFile(inputFile, body, os.FileMode(0644))
} else {
t.Errorf("%s, step %d: diff in edit content:\n%s", name, i, diff.StringDiff(string(body), string(expectedInput)))
t.Logf("If the change in input is expected, rerun tests with %s=true to update input fixtures", updateEnvVar)
}
}
return &http.Response{StatusCode: step.ResponseStatusCode, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader(resultingOutput))}, nil
}
}
handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
resp, _ := reqResp(req)
for k, vs := range resp.Header {
w.Header().Del(k)
for _, v := range vs {
w.Header().Add(k, v)
}
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
})
server := httptest.NewServer(handler)
defer server.Close()
os.Setenv("KUBE_EDITOR", "testdata/edit/test_editor.sh")
os.Setenv("KUBE_EDITOR_CALLBACK", server.URL+"/callback")
testcases := sets.NewString()
filepath.Walk("testdata/edit", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == "testdata/edit" {
return nil
}
name := filepath.Base(path)
if info.IsDir() {
if strings.HasPrefix(name, "testcase-") {
testcases.Insert(strings.TrimPrefix(name, "testcase-"))
}
return filepath.SkipDir
}
return nil
})
// sanity check that we found the right folder
if !testcases.Has("create-list") {
t.Fatalf("Error locating edit testcases")
}
for _, testcaseName := range testcases.List() {
t.Logf("Running testcase: %s", testcaseName)
i = 0
name = testcaseName
testcase = EditTestCase{}
testcaseDir := filepath.Join("testdata", "edit", "testcase-"+name)
testcaseData, err := ioutil.ReadFile(filepath.Join(testcaseDir, "test.yaml"))
if err != nil {
t.Fatalf("%s: %v", name, err)
}
if err := yaml.Unmarshal(testcaseData, &testcase); err != nil {
t.Fatalf("%s: %v", name, err)
}
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.ClientForMappingFunc = func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
versionedAPIPath := ""
if mapping.GroupVersionKind.Group == "" {
versionedAPIPath = "/api/" + mapping.GroupVersionKind.Version
} else {
versionedAPIPath = "/apis/" + mapping.GroupVersionKind.Group + "/" + mapping.GroupVersionKind.Version
}
return &fake.RESTClient{
APIRegistry: api.Registry,
VersionedAPIPath: versionedAPIPath,
NegotiatedSerializer: ns, //unstructuredSerializer,
Client: fake.CreateHTTPClient(reqResp),
}, nil
}
if len(testcase.Namespace) > 0 {
tf.Namespace = testcase.Namespace
}
tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
var cmd *cobra.Command
switch testcase.Mode {
case "edit":
cmd = NewCmdEdit(f, buf, errBuf)
case "create":
cmd = NewCmdCreate(f, buf, errBuf)
cmd.Flags().Set("edit", "true")
default:
t.Errorf("%s: unexpected mode %s", name, testcase.Mode)
continue
}
if len(testcase.Filename) > 0 {
cmd.Flags().Set("filename", filepath.Join(testcaseDir, testcase.Filename))
}
if len(testcase.Output) > 0 {
cmd.Flags().Set("output", testcase.Output)
}
cmdutil.BehaviorOnFatal(func(str string, code int) {
errBuf.WriteString(str)
if testcase.ExpectedExitCode != code {
t.Errorf("%s: expected exit code %d, got %d: %s", name, testcase.ExpectedExitCode, code, str)
}
})
cmd.Run(cmd, testcase.Args)
stdout := buf.String()
stderr := errBuf.String()
for _, s := range testcase.ExpectedStdout {
if !strings.Contains(stdout, s) {
t.Errorf("%s: expected to see '%s' in stdout\n\nstdout:\n%s\n\nstderr:\n%s", name, s, stdout, stderr)
}
}
for _, s := range testcase.ExpectedStderr {
if !strings.Contains(stderr, s) {
t.Errorf("%s: expected to see '%s' in stderr\n\nstdout:\n%s\n\nstderr:\n%s", name, s, stdout, stderr)
}
}
}
}
func tryIndent(data []byte) []byte {
indented := &bytes.Buffer{}
if err := json.Indent(indented, data, "", "\t"); err == nil {
return indented.Bytes()
}
return data
}

35
pkg/kubectl/cmd/testdata/edit/BUILD vendored Normal file
View File

@ -0,0 +1,35 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "edit",
library = ":go_default_library",
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["record.go"],
tags = ["automanaged"],
deps = ["//vendor:gopkg.in/yaml.v2"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

22
pkg/kubectl/cmd/testdata/edit/README vendored Normal file
View File

@ -0,0 +1,22 @@
This folder contains test cases for interactive edit, and helpers for recording new test cases
To record a new test:
1. Start a local cluster running unsecured on http://localhost:8080 (e.g. hack/local-up-cluster.sh)
2. Set up any pre-existing resources you want to be available on that server (namespaces, resources to edit, etc)
3. Run ./pkg/kubectl/cmd/testdata/edit/record_testcase.sh my-testcase
4. Run the desired `kubectl edit ...` command, and interact with the editor as desired until it completes.
* You can do things that cause errors to appear in the editor (change immutable fields, fail validation, etc)
* You can perform edit flows that invoke the editor multiple times
* You can make out-of-band changes to the server resources that cause conflict errors to be returned
* The API requests/responses and editor inputs/outputs are captured in your testcase folder
5. Type exit.
6. Inspect the captured requests/responses and inputs/outputs for sanity
7. Modify the generated test.yaml file:
* Set a description of what the test is doing
* Enter the args (if any) you invoked edit with
* Enter the filename (if any) you invoked edit with
* Enter the output format (if any) you invoked edit with
* Optionally specify substrings to look for in the stdout or stderr of the edit command
8. Add your new testcase name to the list of testcases in edit_test.go
9. Run `go test ./pkg/kubectl/cmd -run TestEdit -v` to run edit tests

169
pkg/kubectl/cmd/testdata/edit/record.go vendored Normal file
View File

@ -0,0 +1,169 @@
/*
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 main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
yaml "gopkg.in/yaml.v2"
)
type EditTestCase struct {
Description string `yaml:"description"`
// create or edit
Mode string `yaml:"mode"`
Args []string `yaml:"args"`
Filename string `yaml:"filename"`
Output string `yaml:"outputFormat"`
Namespace string `yaml:"namespace"`
ExpectedStdout []string `yaml:"expectedStdout"`
ExpectedStderr []string `yaml:"expectedStderr"`
ExpectedExitCode int `yaml:"expectedExitCode"`
Steps []EditStep `yaml:"steps"`
}
type EditStep struct {
// edit or request
StepType string `yaml:"type"`
// only applies to request
RequestMethod string `yaml:"expectedMethod,omitempty"`
RequestPath string `yaml:"expectedPath,omitempty"`
RequestContentType string `yaml:"expectedContentType,omitempty"`
Input string `yaml:"expectedInput"`
// only applies to request
ResponseStatusCode int `yaml:"resultingStatusCode,omitempty"`
Output string `yaml:"resultingOutput"`
}
func main() {
tc := &EditTestCase{
Description: "add a testcase description",
Mode: "edit",
Args: []string{"set", "args"},
ExpectedStdout: []string{"expected stdout substring"},
ExpectedStderr: []string{"expected stderr substring"},
}
var currentStep *EditStep
fmt.Println(http.ListenAndServe(":8081", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// Record non-discovery things
record := false
switch segments := strings.Split(strings.Trim(req.URL.Path, "/"), "/"); segments[0] {
case "api":
// api, version
record = len(segments) > 2
case "apis":
// apis, group, version
record = len(segments) > 3
case "callback":
record = true
}
body, err := ioutil.ReadAll(req.Body)
checkErr(err)
switch m, p := req.Method, req.URL.Path; {
case m == "POST" && p == "/callback/in":
if currentStep != nil {
panic("cannot post input with step already in progress")
}
filename := fmt.Sprintf("%d.original", len(tc.Steps))
checkErr(ioutil.WriteFile(filename, body, os.FileMode(0755)))
currentStep = &EditStep{StepType: "edit", Input: filename}
case m == "POST" && p == "/callback/out":
if currentStep == nil || currentStep.StepType != "edit" {
panic("cannot post output without posting input first")
}
filename := fmt.Sprintf("%d.edited", len(tc.Steps))
checkErr(ioutil.WriteFile(filename, body, os.FileMode(0755)))
currentStep.Output = filename
tc.Steps = append(tc.Steps, *currentStep)
currentStep = nil
default:
if currentStep != nil {
panic("cannot make request with step already in progress")
}
urlCopy := *req.URL
urlCopy.Host = "localhost:8080"
urlCopy.Scheme = "http"
proxiedReq, err := http.NewRequest(req.Method, urlCopy.String(), bytes.NewReader(body))
checkErr(err)
proxiedReq.Header = req.Header
resp, err := http.DefaultClient.Do(proxiedReq)
checkErr(err)
defer resp.Body.Close()
bodyOut, err := ioutil.ReadAll(resp.Body)
checkErr(err)
for k, vs := range resp.Header {
for _, v := range vs {
w.Header().Add(k, v)
}
}
w.WriteHeader(resp.StatusCode)
w.Write(bodyOut)
if record {
infile := fmt.Sprintf("%d.request", len(tc.Steps))
outfile := fmt.Sprintf("%d.response", len(tc.Steps))
checkErr(ioutil.WriteFile(infile, tryIndent(body), os.FileMode(0755)))
checkErr(ioutil.WriteFile(outfile, tryIndent(bodyOut), os.FileMode(0755)))
tc.Steps = append(tc.Steps, EditStep{
StepType: "request",
Input: infile,
Output: outfile,
RequestContentType: req.Header.Get("Content-Type"),
RequestMethod: req.Method,
RequestPath: req.URL.Path,
ResponseStatusCode: resp.StatusCode,
})
}
}
tcData, err := yaml.Marshal(tc)
checkErr(err)
checkErr(ioutil.WriteFile("test.yaml", tcData, os.FileMode(0755)))
})))
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
func tryIndent(data []byte) []byte {
indented := &bytes.Buffer{}
if err := json.Indent(indented, data, "", "\t"); err == nil {
return indented.Bytes()
}
return data
}

View File

@ -0,0 +1,26 @@
#!/bin/bash
# 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.
set -o errexit
set -o nounset
set -o pipefail
# send the original content to the server
curl -s -k -XPOST "http://localhost:8081/callback/in" --data-binary "@${1}"
# allow the user to edit the file
vi "${1}"
# send the resulting content to the server
curl -s -k -XPOST "http://localhost:8081/callback/out" --data-binary "@${1}"

View File

@ -0,0 +1,70 @@
#!/bin/bash
# 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.
set -o errexit
set -o nounset
set -o pipefail
if [[ -z "${1-}" ]]; then
echo "Usage: record_testcase.sh testcase-name"
exit 1
fi
# Clean up the test server
function cleanup {
if [[ ! -z "${pid-}" ]]; then
echo "Stopping recording server (${pid})"
# kill the process `go run` launched
pkill -P "${pid}"
# kill the `go run` process itself
kill -9 "${pid}"
fi
}
testcase="${1}"
test_root="$(dirname "${BASH_SOURCE}")"
testcase_dir="${test_root}/testcase-${testcase}"
mkdir -p "${testcase_dir}"
pushd "${testcase_dir}"
export EDITOR="../record_editor.sh"
go run "../record.go" &
pid=$!
trap cleanup EXIT
echo "Started recording server (${pid})"
# Make a kubeconfig that makes kubectl talk to our test server
edit_kubeconfig="${TMP:-/tmp}/edit_test.kubeconfig"
echo "apiVersion: v1
clusters:
- cluster:
server: http://localhost:8081
name: test
contexts:
- context:
cluster: test
user: test
name: test
current-context: test
kind: Config
users: []
" > "${edit_kubeconfig}"
export KUBECONFIG="${edit_kubeconfig}"
echo "Starting subshell. Type exit when finished."
bash
popd

32
pkg/kubectl/cmd/testdata/edit/test_editor.sh vendored Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
# 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.
set -o errexit
set -o nounset
set -o pipefail
# Send the file content to the server
if command -v curl &>/dev/null; then
curl -s -k -XPOST --data-binary "@${1}" -o "${1}.result" "${KUBE_EDITOR_CALLBACK}"
elif command -v wget &>/dev/null; then
wget --post-file="${1}" -O "${1}.result" "${KUBE_EDITOR_CALLBACK}"
else
echo "curl and wget are unavailable" >&2
exit 1
fi
# Use the response as the edited version
mv "${1}.result" "${1}"

View File

@ -0,0 +1,28 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-03T06:44:47Z
labels:
app: svc1modified
name: svc1
namespace: edit-test
resourceVersion: "2942"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 4149f70e-e9dc-11e6-8c3b-acbc32c1ca87
spec:
clusterIP: 10.0.0.118
ports:
- name: "81"
port: 82
protocol: TCP
targetPort: 81
selector:
app: svc1
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

View File

@ -0,0 +1,28 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-03T06:44:47Z
labels:
app: svc1
name: svc1
namespace: edit-test
resourceVersion: "2942"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 4149f70e-e9dc-11e6-8c3b-acbc32c1ca87
spec:
clusterIP: 10.0.0.118
ports:
- name: "81"
port: 81
protocol: TCP
targetPort: 81
selector:
app: svc1
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

View File

@ -0,0 +1,33 @@
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/services/svc1",
"uid": "4149f70e-e9dc-11e6-8c3b-acbc32c1ca87",
"creationTimestamp": "2017-02-03T06:44:47Z",
"labels": {
"app": "svc1modified"
}
},
"spec": {
"ports": [
{
"name": "81",
"protocol": "TCP",
"port": 82,
"targetPort": 81
}
],
"selector": {
"app": "svc1"
},
"clusterIP": "10.0.0.118",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,34 @@
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/services/svc1",
"uid": "c07152b8-e9dc-11e6-8c3b-acbc32c1ca87",
"resourceVersion": "3171",
"creationTimestamp": "2017-02-03T06:48:21Z",
"labels": {
"app": "svc1modified"
}
},
"spec": {
"ports": [
{
"name": "81",
"protocol": "TCP",
"port": 82,
"targetPort": 81
}
],
"selector": {
"app": "svc1"
},
"clusterIP": "10.0.0.118",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,28 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-03T06:44:43Z
labels:
app: svc2modified
name: svc2
namespace: edit-test
resourceVersion: "2936"
selfLink: /api/v1/namespaces/edit-test/services/svc2
uid: 3e9b10db-e9dc-11e6-8c3b-acbc32c1ca87
spec:
clusterIP: 10.0.0.182.1
ports:
- name: "80"
port: 80
protocol: VHF
targetPort: 80
selector:
app: svc2
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

View File

@ -0,0 +1,28 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-03T06:44:43Z
labels:
app: svc2
name: svc2
namespace: edit-test
resourceVersion: "2936"
selfLink: /api/v1/namespaces/edit-test/services/svc2
uid: 3e9b10db-e9dc-11e6-8c3b-acbc32c1ca87
spec:
clusterIP: 10.0.0.182
ports:
- name: "80"
port: 80
protocol: TCP
targetPort: 80
selector:
app: svc2
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

View File

@ -0,0 +1,33 @@
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc2",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/services/svc2",
"uid": "3e9b10db-e9dc-11e6-8c3b-acbc32c1ca87",
"creationTimestamp": "2017-02-03T06:44:43Z",
"labels": {
"app": "svc2modified"
}
},
"spec": {
"ports": [
{
"name": "80",
"protocol": "VHF",
"port": 80,
"targetPort": 80
}
],
"selector": {
"app": "svc2"
},
"clusterIP": "10.0.0.182.1",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,25 @@
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "Service \"svc2\" is invalid: [spec.ports[0].protocol: Unsupported value: \"VHF\": supported values: TCP, UDP, spec.clusterIP: Invalid value: \"10.0.0.182.1\": must be empty, 'None', or a valid IP address]",
"reason": "Invalid",
"details": {
"name": "svc2",
"kind": "Service",
"causes": [
{
"reason": "FieldValueNotSupported",
"message": "Unsupported value: \"VHF\": supported values: TCP, UDP",
"field": "spec.ports[0].protocol"
},
{
"reason": "FieldValueInvalid",
"message": "Invalid value: \"10.0.0.182.1\": must be empty, 'None', or a valid IP address",
"field": "spec.clusterIP"
}
]
},
"code": 422
}

View File

@ -0,0 +1,54 @@
apiVersion: v1
items:
- apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-03T06:44:47Z
labels:
app: svc1
name: svc1
namespace: edit-test
resourceVersion: "2942"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 4149f70e-e9dc-11e6-8c3b-acbc32c1ca87
spec:
clusterIP: 10.0.0.118
ports:
- name: "81"
port: 81
protocol: TCP
targetPort: 81
selector:
app: svc1
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
- apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-03T06:44:43Z
labels:
app: svc2
name: svc2
namespace: edit-test
resourceVersion: "2936"
selfLink: /api/v1/namespaces/edit-test/services/svc2
uid: 3e9b10db-e9dc-11e6-8c3b-acbc32c1ca87
spec:
clusterIP: 10.0.0.182
ports:
- name: "80"
port: 80
protocol: TCP
targetPort: 80
selector:
app: svc2
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
kind: List
metadata: {}
resourceVersion: ""
selfLink: ""

View File

@ -0,0 +1,30 @@
description: create list with errors
mode: create
filename: "svc.yaml"
namespace: "edit-test"
expectedStdout:
- "service \"svc1\" created"
expectedStderr:
- "\"svc2\" is invalid"
expectedExitCode: 1
steps:
- type: edit
expectedInput: 0.original
resultingOutput: 0.edited
- type: request
expectedMethod: POST
expectedPath: /api/v1/namespaces/edit-test/services
expectedContentType: application/json
expectedInput: 1.request
resultingStatusCode: 201
resultingOutput: 1.response
- type: edit
expectedInput: 2.original
resultingOutput: 2.edited
- type: request
expectedMethod: POST
expectedPath: /api/v1/namespaces/edit-test/services
expectedContentType: application/json
expectedInput: 3.request
resultingStatusCode: 422
resultingOutput: 3.response

View File

@ -0,0 +1,22 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
labels:
app: svc1
new-label: new-value
name: svc1
namespace: edit-test
spec:
ports:
- name: "81"
port: 82
protocol: TCP
targetPort: 81
selector:
app: svc1
sessionAffinity: None
type: ClusterIP

View File

@ -0,0 +1,21 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
labels:
app: svc1
name: svc1
namespace: edit-test
spec:
ports:
- name: "81"
port: 81
protocol: TCP
targetPort: 81
selector:
app: svc1
sessionAffinity: None
type: ClusterIP

View File

@ -0,0 +1,31 @@
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc1",
"namespace": "edit-test",
"creationTimestamp": null,
"labels": {
"app": "svc1",
"new-label": "new-value"
}
},
"spec": {
"ports": [
{
"name": "81",
"protocol": "TCP",
"port": 82,
"targetPort": 81
}
],
"selector": {
"app": "svc1"
},
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,35 @@
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/services/svc1",
"uid": "208b27ed-ea5b-11e6-9b42-acbc32c1ca87",
"resourceVersion": "1437",
"creationTimestamp": "2017-02-03T21:52:59Z",
"labels": {
"app": "svc1",
"new-label": "new-value"
}
},
"spec": {
"ports": [
{
"name": "81",
"protocol": "TCP",
"port": 82,
"targetPort": 81
}
],
"selector": {
"app": "svc1"
},
"clusterIP": "10.0.0.15",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,22 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
labels:
app: svc2
name: svc2
namespace: edit-test
spec:
ports:
- name: "80"
port: 80
protocol: TCP
targetPort: 81
selector:
app: svc2
new-label: new-value
sessionAffinity: None
type: ClusterIP

View File

@ -0,0 +1,21 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
labels:
app: svc2
name: svc2
namespace: edit-test
spec:
ports:
- name: "80"
port: 80
protocol: TCP
targetPort: 80
selector:
app: svc2
sessionAffinity: None
type: ClusterIP

View File

@ -0,0 +1,31 @@
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc2",
"namespace": "edit-test",
"creationTimestamp": null,
"labels": {
"app": "svc2"
}
},
"spec": {
"ports": [
{
"name": "80",
"protocol": "TCP",
"port": 80,
"targetPort": 81
}
],
"selector": {
"app": "svc2",
"new-label": "new-value"
},
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,35 @@
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc2",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/services/svc2",
"uid": "31a1b8ae-ea5b-11e6-9b42-acbc32c1ca87",
"resourceVersion": "1470",
"creationTimestamp": "2017-02-03T21:53:27Z",
"labels": {
"app": "svc2"
}
},
"spec": {
"ports": [
{
"name": "80",
"protocol": "TCP",
"port": 80,
"targetPort": 81
}
],
"selector": {
"app": "svc2",
"new-label": "new-value"
},
"clusterIP": "10.0.0.55",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,39 @@
apiVersion: v1
items:
- apiVersion: v1
kind: Service
metadata:
labels:
app: svc1
name: svc1
spec:
ports:
- name: "81"
port: 81
protocol: TCP
targetPort: 81
selector:
app: svc1
sessionAffinity: None
type: ClusterIP
- apiVersion: v1
kind: Service
metadata:
labels:
app: svc2
name: svc2
namespace: edit-test
spec:
ports:
- name: "80"
port: 80
protocol: TCP
targetPort: 80
selector:
app: svc2
sessionAffinity: None
type: ClusterIP
kind: List
metadata: {}
resourceVersion: ""
selfLink: ""

View File

@ -0,0 +1,29 @@
description: edit while creating from a list
mode: create
filename: "svc.yaml"
namespace: "edit-test"
expectedStdout:
- service "svc1" created
- service "svc2" created
expectedExitCode: 0
steps:
- type: edit
expectedInput: 0.original
resultingOutput: 0.edited
- type: request
expectedMethod: POST
expectedPath: /api/v1/namespaces/edit-test/services
expectedContentType: application/json
expectedInput: 1.request
resultingStatusCode: 201
resultingOutput: 1.response
- type: edit
expectedInput: 2.original
resultingOutput: 2.edited
- type: request
expectedMethod: POST
expectedPath: /api/v1/namespaces/edit-test/services
expectedContentType: application/json
expectedInput: 3.request
resultingStatusCode: 201
resultingOutput: 3.response

View File

@ -0,0 +1,35 @@
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/services/svc1",
"uid": "5f7da8db-e8c3-11e6-b7e2-acbc32c1ca87",
"resourceVersion": "20820",
"creationTimestamp": "2017-02-01T21:14:09Z",
"labels": {
"app": "svc1",
"new-label": "new-value"
}
},
"spec": {
"ports": [
{
"name": "80",
"protocol": "TCP",
"port": 81,
"targetPort": 80
}
],
"selector": {
"app": "svc1"
},
"clusterIP": "10.0.0.146",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,29 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-01T21:14:09Z
labels:
app: svc1
new-label: new-value
name: svc1
namespace: edit-test
resourceVersion: "20820"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 5f7da8db-e8c3-11e6-b7e2-acbc32c1ca87
spec:
clusterIP: 10.0.0.146.1
ports:
- name: "80"
port: 81
protocol: TCP
targetPort: 80
selector:
app: svc1
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

View File

@ -0,0 +1,29 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-01T21:14:09Z
labels:
app: svc1
new-label: new-value
name: svc1
namespace: edit-test
resourceVersion: "20820"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 5f7da8db-e8c3-11e6-b7e2-acbc32c1ca87
spec:
clusterIP: 10.0.0.146
ports:
- name: "80"
port: 81
protocol: TCP
targetPort: 80
selector:
app: svc1
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

View File

@ -0,0 +1,5 @@
{
"spec": {
"clusterIP": "10.0.0.146.1"
}
}

View File

@ -0,0 +1,25 @@
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "Service \"svc1\" is invalid: [spec.clusterIP: Invalid value: \"10.0.0.146.1\": field is immutable, spec.clusterIP: Invalid value: \"10.0.0.146.1\": must be empty, 'None', or a valid IP address]",
"reason": "Invalid",
"details": {
"name": "svc1",
"kind": "Service",
"causes": [
{
"reason": "FieldValueInvalid",
"message": "Invalid value: \"10.0.0.146.1\": field is immutable",
"field": "spec.clusterIP"
},
{
"reason": "FieldValueInvalid",
"message": "Invalid value: \"10.0.0.146.1\": must be empty, 'None', or a valid IP address",
"field": "spec.clusterIP"
}
]
},
"code": 422
}

View File

@ -0,0 +1,33 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
# services "svc1" was not valid:
# * spec.clusterIP: Invalid value: "10.0.0.146.1": field is immutable
# * spec.clusterIP: Invalid value: "10.0.0.146.1": must be empty, 'None', or a valid IP address
#
apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-01T21:14:09Z
labels:
app: svc1
new-label: new-value
name: svc1
namespace: edit-test
resourceVersion: "20820"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 5f7da8db-e8c3-11e6-b7e2-acbc32c1ca87
spec:
clusterIP: 10.0.0.146
ports:
- name: "80"
port: 82
protocol: TCP
targetPort: 80
selector:
app: svc1
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

View File

@ -0,0 +1,33 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
# services "svc1" was not valid:
# * spec.clusterIP: Invalid value: "10.0.0.146.1": field is immutable
# * spec.clusterIP: Invalid value: "10.0.0.146.1": must be empty, 'None', or a valid IP address
#
apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-01T21:14:09Z
labels:
app: svc1
new-label: new-value
name: svc1
namespace: edit-test
resourceVersion: "20820"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 5f7da8db-e8c3-11e6-b7e2-acbc32c1ca87
spec:
clusterIP: 10.0.0.146.1
ports:
- name: "80"
port: 81
protocol: TCP
targetPort: 80
selector:
app: svc1
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

View File

@ -0,0 +1,16 @@
{
"spec": {
"ports": [
{
"$patch": "delete",
"port": 81
},
{
"name": "80",
"port": 82,
"protocol": "TCP",
"targetPort": 80
}
]
}
}

View File

@ -0,0 +1,35 @@
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/services/svc1",
"uid": "5f7da8db-e8c3-11e6-b7e2-acbc32c1ca87",
"resourceVersion": "21361",
"creationTimestamp": "2017-02-01T21:14:09Z",
"labels": {
"app": "svc1",
"new-label": "new-value"
}
},
"spec": {
"ports": [
{
"name": "80",
"protocol": "TCP",
"port": 82,
"targetPort": 80
}
],
"selector": {
"app": "svc1"
},
"clusterIP": "10.0.0.146",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,38 @@
description: add a testcase description
mode: edit
args:
- service
- svc1
namespace: edit-test
expectedStdout:
- service "svc1" edited
expectedStderr:
- "error: services \"svc1\" is invalid"
expectedExitCode: 0
steps:
- type: request
expectedMethod: GET
expectedPath: /api/v1/namespaces/edit-test/services/svc1
expectedInput: 0.request
resultingStatusCode: 200
resultingOutput: 0.response
- type: edit
expectedInput: 1.original
resultingOutput: 1.edited
- type: request
expectedMethod: PATCH
expectedPath: /api/v1/namespaces/edit-test/services/svc1
expectedContentType: application/strategic-merge-patch+json
expectedInput: 2.request
resultingStatusCode: 422
resultingOutput: 2.response
- type: edit
expectedInput: 3.original
resultingOutput: 3.edited
- type: request
expectedMethod: PATCH
expectedPath: /api/v1/namespaces/edit-test/services/svc1
expectedContentType: application/strategic-merge-patch+json
expectedInput: 4.request
resultingStatusCode: 200
resultingOutput: 4.response

View File

@ -0,0 +1,24 @@
{
"kind": "ConfigMapList",
"apiVersion": "v1",
"metadata": {
"selfLink": "/api/v1/namespaces/edit-test/configmaps",
"resourceVersion": "2308"
},
"items": [
{
"metadata": {
"name": "cm1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/configmaps/cm1",
"uid": "b09bffab-e9d7-11e6-8c3b-acbc32c1ca87",
"resourceVersion": "2071",
"creationTimestamp": "2017-02-03T06:12:07Z"
},
"data": {
"baz": "qux",
"foo": "changed-value2"
}
}
]
}

View File

@ -0,0 +1,16 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
baz: qux
foo: changed-value2
kind: ConfigMap
metadata:
creationTimestamp: 2017-02-03T06:12:07Z
name: cm1-modified
namespace: edit-test
resourceVersion: "2071"
selfLink: /api/v1/namespaces/edit-test/configmaps/cm1
uid: b09bffab-e9d7-11e6-8c3b-acbc32c1ca87

View File

@ -0,0 +1,16 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
baz: qux
foo: changed-value2
kind: ConfigMap
metadata:
creationTimestamp: 2017-02-03T06:12:07Z
name: cm1
namespace: edit-test
resourceVersion: "2071"
selfLink: /api/v1/namespaces/edit-test/configmaps/cm1
uid: b09bffab-e9d7-11e6-8c3b-acbc32c1ca87

View File

@ -0,0 +1,18 @@
description: try to mutate a fixed field
mode: edit
args:
- configmap
namespace: "edit-test"
expectedStderr:
- At least one of apiVersion, kind and name was changed
expectedExitCode: 1
steps:
- type: request
expectedMethod: GET
expectedPath: /api/v1/namespaces/edit-test/configmaps
expectedInput: 0.request
resultingStatusCode: 200
resultingOutput: 0.response
- type: edit
expectedInput: 1.original
resultingOutput: 1.edited

View File

View File

@ -0,0 +1,24 @@
{
"kind": "ConfigMapList",
"apiVersion": "v1",
"metadata": {
"selfLink": "/api/v1/namespaces/edit-test/configmaps",
"resourceVersion": "1934"
},
"items": [
{
"metadata": {
"name": "cm1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/configmaps/cm1",
"uid": "b09bffab-e9d7-11e6-8c3b-acbc32c1ca87",
"resourceVersion": "1903",
"creationTimestamp": "2017-02-03T06:12:07Z"
},
"data": {
"baz": "qux",
"foo": "changed-value"
}
}
]
}

View File

View File

@ -0,0 +1,39 @@
{
"kind": "ServiceList",
"apiVersion": "v1",
"metadata": {
"selfLink": "/api/v1/namespaces/edit-test/services",
"resourceVersion": "1934"
},
"items": [
{
"metadata": {
"name": "svc1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/services/svc1",
"uid": "9bec82be-e9d7-11e6-8c3b-acbc32c1ca87",
"resourceVersion": "1904",
"creationTimestamp": "2017-02-03T06:11:32Z",
"labels": {
"app": "svc1"
}
},
"spec": {
"ports": [
{
"name": "80",
"protocol": "TCP",
"port": 82,
"targetPort": 81
}
],
"clusterIP": "10.0.0.248",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}
]
}

View File

@ -0,0 +1,5 @@
{
"data": {
"foo": "changed-value2"
}
}

View File

@ -0,0 +1,16 @@
{
"kind": "ConfigMap",
"apiVersion": "v1",
"metadata": {
"name": "cm1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/configmaps/cm1",
"uid": "b09bffab-e9d7-11e6-8c3b-acbc32c1ca87",
"resourceVersion": "2071",
"creationTimestamp": "2017-02-03T06:12:07Z"
},
"data": {
"baz": "qux",
"foo": "changed-value2"
}
}

View File

@ -0,0 +1,42 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
items:
- apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-03T06:11:32Z
labels:
app: svc1
name: svc1
namespace: edit-test
resourceVersion: "1904"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 9bec82be-e9d7-11e6-8c3b-acbc32c1ca87
spec:
clusterIP: 10.0.0.10
ports:
- name: "80"
port: 82
protocol: VHF
targetPort: 81
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
- apiVersion: v1
data:
baz: qux
foo: changed-value2
kind: ConfigMap
metadata:
creationTimestamp: 2017-02-03T06:12:07Z
name: cm1
namespace: edit-test
resourceVersion: "1903"
selfLink: /api/v1/namespaces/edit-test/configmaps/cm1
uid: b09bffab-e9d7-11e6-8c3b-acbc32c1ca87
kind: List
metadata: {}

View File

@ -0,0 +1,42 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
items:
- apiVersion: v1
data:
baz: qux
foo: changed-value
kind: ConfigMap
metadata:
creationTimestamp: 2017-02-03T06:12:07Z
name: cm1
namespace: edit-test
resourceVersion: "1903"
selfLink: /api/v1/namespaces/edit-test/configmaps/cm1
uid: b09bffab-e9d7-11e6-8c3b-acbc32c1ca87
- apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-03T06:11:32Z
labels:
app: svc1
name: svc1
namespace: edit-test
resourceVersion: "1904"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 9bec82be-e9d7-11e6-8c3b-acbc32c1ca87
spec:
clusterIP: 10.0.0.248
ports:
- name: "80"
port: 82
protocol: TCP
targetPort: 81
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
kind: List
metadata: {}

View File

@ -0,0 +1,11 @@
{
"spec": {
"clusterIP": "10.0.0.10",
"ports": [
{
"port": 82,
"protocol": "VHF"
}
]
}
}

View File

@ -0,0 +1,25 @@
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "Service \"svc1\" is invalid: [spec.clusterIP: Invalid value: \"10.0.0.10\": field is immutable, spec.ports[0].protocol: Unsupported value: \"VHF\": supported values: TCP, UDP]",
"reason": "Invalid",
"details": {
"name": "svc1",
"kind": "Service",
"causes": [
{
"reason": "FieldValueInvalid",
"message": "Invalid value: \"10.0.0.10\": field is immutable",
"field": "spec.clusterIP"
},
{
"reason": "FieldValueNotSupported",
"message": "Unsupported value: \"VHF\": supported values: TCP, UDP",
"field": "spec.ports[0].protocol"
}
]
},
"code": 422
}

View File

@ -0,0 +1,5 @@
{
"data": {
"foo": "changed-value2"
}
}

View File

@ -0,0 +1,16 @@
{
"kind": "ConfigMap",
"apiVersion": "v1",
"metadata": {
"name": "cm1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/configmaps/cm1",
"uid": "b09bffab-e9d7-11e6-8c3b-acbc32c1ca87",
"resourceVersion": "2017",
"creationTimestamp": "2017-02-03T06:12:07Z"
},
"data": {
"baz": "qux",
"foo": "changed-value2"
}
}

View File

@ -0,0 +1,47 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
# services "svc1" was not valid:
# * spec.clusterIP: Invalid value: "10.0.0.10": field is immutable
# * spec.ports[0].protocol: Unsupported value: "VHF": supported values: TCP, UDP
#
apiVersion: v1
items:
- apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-03T06:11:32Z
labels:
app: svc1
newvalue: modified
name: svc1
namespace: edit-test
resourceVersion: "1904"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 9bec82be-e9d7-11e6-8c3b-acbc32c1ca87
spec:
clusterIP: 10.0.0.248
ports:
- name: "80"
port: 83
protocol: VHF
targetPort: 81
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
- apiVersion: v1
data:
baz: qux
foo: changed-value2
kind: ConfigMap
metadata:
creationTimestamp: 2017-02-03T06:12:07Z
name: cm1
namespace: edit-test
resourceVersion: "1903"
selfLink: /api/v1/namespaces/edit-test/configmaps/cm1
uid: b09bffab-e9d7-11e6-8c3b-acbc32c1ca87
kind: List
metadata: {}

View File

@ -0,0 +1,46 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
# services "svc1" was not valid:
# * spec.clusterIP: Invalid value: "10.0.0.10": field is immutable
# * spec.ports[0].protocol: Unsupported value: "VHF": supported values: TCP, UDP
#
apiVersion: v1
items:
- apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-03T06:11:32Z
labels:
app: svc1
name: svc1
namespace: edit-test
resourceVersion: "1904"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 9bec82be-e9d7-11e6-8c3b-acbc32c1ca87
spec:
clusterIP: 10.0.0.10
ports:
- name: "80"
port: 82
protocol: VHF
targetPort: 81
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
- apiVersion: v1
data:
baz: qux
foo: changed-value2
kind: ConfigMap
metadata:
creationTimestamp: 2017-02-03T06:12:07Z
name: cm1
namespace: edit-test
resourceVersion: "1903"
selfLink: /api/v1/namespaces/edit-test/configmaps/cm1
uid: b09bffab-e9d7-11e6-8c3b-acbc32c1ca87
kind: List
metadata: {}

View File

@ -0,0 +1,21 @@
{
"metadata": {
"labels": {
"newvalue": "modified"
}
},
"spec": {
"ports": [
{
"$patch": "delete",
"port": 82
},
{
"name": "80",
"port": 83,
"protocol": "VHF",
"targetPort": 81
}
]
}
}

View File

@ -0,0 +1,20 @@
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "Service \"svc1\" is invalid: spec.ports[0].protocol: Unsupported value: \"VHF\": supported values: TCP, UDP",
"reason": "Invalid",
"details": {
"name": "svc1",
"kind": "Service",
"causes": [
{
"reason": "FieldValueNotSupported",
"message": "Unsupported value: \"VHF\": supported values: TCP, UDP",
"field": "spec.ports[0].protocol"
}
]
},
"code": 422
}

View File

@ -0,0 +1,5 @@
{
"data": {
"foo": "changed-value2"
}
}

View File

@ -0,0 +1,16 @@
{
"kind": "ConfigMap",
"apiVersion": "v1",
"metadata": {
"name": "cm1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/configmaps/cm1",
"uid": "b09bffab-e9d7-11e6-8c3b-acbc32c1ca87",
"resourceVersion": "2017",
"creationTimestamp": "2017-02-03T06:12:07Z"
},
"data": {
"baz": "qux",
"foo": "changed-value2"
}
}

View File

@ -0,0 +1,46 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
# services "svc1" was not valid:
# * spec.ports[0].protocol: Unsupported value: "VHF": supported values: TCP, UDP
#
apiVersion: v1
items:
- apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-03T06:11:32Z
labels:
app: svc1
newvalue: modified
name: svc1
namespace: edit-test
resourceVersion: "1904"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 9bec82be-e9d7-11e6-8c3b-acbc32c1ca87
spec:
clusterIP: 10.0.0.248
ports:
- name: "80"
port: 83
protocol: TCP
targetPort: 81
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
- apiVersion: v1
data:
baz: qux
foo: changed-value2
kind: ConfigMap
metadata:
creationTimestamp: 2017-02-03T06:12:07Z
name: cm1
namespace: edit-test
resourceVersion: "1903"
selfLink: /api/v1/namespaces/edit-test/configmaps/cm1
uid: b09bffab-e9d7-11e6-8c3b-acbc32c1ca87
kind: List
metadata: {}

View File

@ -0,0 +1,46 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
# services "svc1" was not valid:
# * spec.ports[0].protocol: Unsupported value: "VHF": supported values: TCP, UDP
#
apiVersion: v1
items:
- apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-03T06:11:32Z
labels:
app: svc1
newvalue: modified
name: svc1
namespace: edit-test
resourceVersion: "1904"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 9bec82be-e9d7-11e6-8c3b-acbc32c1ca87
spec:
clusterIP: 10.0.0.248
ports:
- name: "80"
port: 83
protocol: VHF
targetPort: 81
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
- apiVersion: v1
data:
baz: qux
foo: changed-value2
kind: ConfigMap
metadata:
creationTimestamp: 2017-02-03T06:12:07Z
name: cm1
namespace: edit-test
resourceVersion: "1903"
selfLink: /api/v1/namespaces/edit-test/configmaps/cm1
uid: b09bffab-e9d7-11e6-8c3b-acbc32c1ca87
kind: List
metadata: {}

View File

@ -0,0 +1,21 @@
{
"metadata": {
"labels": {
"newvalue": "modified"
}
},
"spec": {
"ports": [
{
"$patch": "delete",
"port": 82
},
{
"name": "80",
"port": 83,
"protocol": "TCP",
"targetPort": 81
}
]
}
}

View File

@ -0,0 +1,32 @@
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/services/svc1",
"uid": "9bec82be-e9d7-11e6-8c3b-acbc32c1ca87",
"resourceVersion": "2070",
"creationTimestamp": "2017-02-03T06:11:32Z",
"labels": {
"app": "svc1",
"newvalue": "modified"
}
},
"spec": {
"ports": [
{
"name": "80",
"protocol": "TCP",
"port": 83,
"targetPort": 81
}
],
"clusterIP": "10.0.0.248",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,73 @@
description: edit lists with errors and resubmit
mode: edit
args:
- configmaps,services
namespace: "edit-test"
expectedStdout:
- configmap "cm1" edited
- service "svc1" edited
expectedExitCode: 0
steps:
- type: request
expectedMethod: GET
expectedPath: /api/v1/namespaces/edit-test/configmaps
expectedInput: 0.request
resultingStatusCode: 200
resultingOutput: 0.response
- type: request
expectedMethod: GET
expectedPath: /api/v1/namespaces/edit-test/services
expectedInput: 1.request
resultingStatusCode: 200
resultingOutput: 1.response
- type: edit
expectedInput: 2.original
resultingOutput: 2.edited
- type: request
expectedMethod: PATCH
expectedPath: /api/v1/namespaces/edit-test/services/svc1
expectedContentType: application/strategic-merge-patch+json
expectedInput: 3.request
resultingStatusCode: 422
resultingOutput: 3.response
- type: request
expectedMethod: PATCH
expectedPath: /api/v1/namespaces/edit-test/configmaps/cm1
expectedContentType: application/strategic-merge-patch+json
expectedInput: 4.request
resultingStatusCode: 200
resultingOutput: 4.response
- type: edit
expectedInput: 5.original
resultingOutput: 5.edited
- type: request
expectedMethod: PATCH
expectedPath: /api/v1/namespaces/edit-test/services/svc1
expectedContentType: application/strategic-merge-patch+json
expectedInput: 6.request
resultingStatusCode: 422
resultingOutput: 6.response
- type: request
expectedMethod: PATCH
expectedPath: /api/v1/namespaces/edit-test/configmaps/cm1
expectedContentType: application/strategic-merge-patch+json
expectedInput: 7.request
resultingStatusCode: 200
resultingOutput: 7.response
- type: edit
expectedInput: 8.original
resultingOutput: 8.edited
- type: request
expectedMethod: PATCH
expectedPath: /api/v1/namespaces/edit-test/services/svc1
expectedContentType: application/strategic-merge-patch+json
expectedInput: 9.request
resultingStatusCode: 200
resultingOutput: 9.response
- type: request
expectedMethod: PATCH
expectedPath: /api/v1/namespaces/edit-test/configmaps/cm1
expectedContentType: application/strategic-merge-patch+json
expectedInput: 10.request
resultingStatusCode: 200
resultingOutput: 10.response

View File

View File

@ -0,0 +1,18 @@
{
"kind": "ConfigMap",
"apiVersion": "v1",
"metadata": {
"name": "cm1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/configmaps/cm1",
"uid": "b09bffab-e9d7-11e6-8c3b-acbc32c1ca87",
"resourceVersion": "1414",
"creationTimestamp": "2017-02-03T06:12:07Z"
},
"data": {
"baz": "qux",
"foo": "changed-value",
"new-data": "new-value",
"new-data2": "new-value"
}
}

View File

View File

@ -0,0 +1,32 @@
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/services/svc1",
"uid": "9bec82be-e9d7-11e6-8c3b-acbc32c1ca87",
"resourceVersion": "1064",
"creationTimestamp": "2017-02-03T06:11:32Z",
"labels": {
"app": "svc1",
"new-label": "foo"
}
},
"spec": {
"ports": [
{
"name": "80",
"protocol": "TCP",
"port": 81,
"targetPort": 81
}
],
"clusterIP": "10.0.0.248",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,47 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
items:
- apiVersion: v1
data:
baz: qux
foo: changed-value
new-data: new-value
new-data2: new-value
new-data3: newivalue
kind: ConfigMap
metadata:
creationTimestamp: 2017-02-03T06:12:07Z
name: cm1
namespace: edit-test
resourceVersion: "1414"
selfLink: /api/v1/namespaces/edit-test/configmaps/cm1
uid: b09bffab-e9d7-11e6-8c3b-acbc32c1ca87
- apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-03T06:11:32Z
labels:
app: svc1
new-label: foo
new-label2: foo2
name: svc1
namespace: edit-test
resourceVersion: "1064"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 9bec82be-e9d7-11e6-8c3b-acbc32c1ca87
spec:
clusterIP: 10.0.0.248
ports:
- name: "80"
port: 82
protocol: TCP
targetPort: 81
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
kind: List
metadata: {}

View File

@ -0,0 +1,45 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
items:
- apiVersion: v1
data:
baz: qux
foo: changed-value
new-data: new-value
new-data2: new-value
kind: ConfigMap
metadata:
creationTimestamp: 2017-02-03T06:12:07Z
name: cm1
namespace: edit-test
resourceVersion: "1414"
selfLink: /api/v1/namespaces/edit-test/configmaps/cm1
uid: b09bffab-e9d7-11e6-8c3b-acbc32c1ca87
- apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-03T06:11:32Z
labels:
app: svc1
new-label: foo
name: svc1
namespace: edit-test
resourceVersion: "1064"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 9bec82be-e9d7-11e6-8c3b-acbc32c1ca87
spec:
clusterIP: 10.0.0.248
ports:
- name: "80"
port: 81
protocol: TCP
targetPort: 81
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
kind: List
metadata: {}

View File

@ -0,0 +1,5 @@
{
"data": {
"new-data3": "newivalue"
}
}

View File

@ -0,0 +1,19 @@
{
"kind": "ConfigMap",
"apiVersion": "v1",
"metadata": {
"name": "cm1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/configmaps/cm1",
"uid": "b09bffab-e9d7-11e6-8c3b-acbc32c1ca87",
"resourceVersion": "1465",
"creationTimestamp": "2017-02-03T06:12:07Z"
},
"data": {
"baz": "qux",
"foo": "changed-value",
"new-data": "new-value",
"new-data2": "new-value",
"new-data3": "newivalue"
}
}

View File

@ -0,0 +1,21 @@
{
"metadata": {
"labels": {
"new-label2": "foo2"
}
},
"spec": {
"ports": [
{
"$patch": "delete",
"port": 81
},
{
"name": "80",
"port": 82,
"protocol": "TCP",
"targetPort": 81
}
]
}
}

View File

@ -0,0 +1,33 @@
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/services/svc1",
"uid": "9bec82be-e9d7-11e6-8c3b-acbc32c1ca87",
"resourceVersion": "1466",
"creationTimestamp": "2017-02-03T06:11:32Z",
"labels": {
"app": "svc1",
"new-label": "foo",
"new-label2": "foo2"
}
},
"spec": {
"ports": [
{
"name": "80",
"protocol": "TCP",
"port": 82,
"targetPort": 81
}
],
"clusterIP": "10.0.0.248",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,40 @@
description: add a testcase description
mode: edit
args:
- configmaps/cm1
- service/svc1
namespace: "edit-test"
expectedStdout:
- configmap "cm1" edited
- service "svc1" edited
expectedExitCode: 0
steps:
- type: request
expectedMethod: GET
expectedPath: /api/v1/namespaces/edit-test/configmaps/cm1
expectedInput: 0.request
resultingStatusCode: 200
resultingOutput: 0.response
- type: request
expectedMethod: GET
expectedPath: /api/v1/namespaces/edit-test/services/svc1
expectedInput: 1.request
resultingStatusCode: 200
resultingOutput: 1.response
- type: edit
expectedInput: 2.original
resultingOutput: 2.edited
- type: request
expectedMethod: PATCH
expectedPath: /api/v1/namespaces/edit-test/configmaps/cm1
expectedContentType: application/strategic-merge-patch+json
expectedInput: 3.request
resultingStatusCode: 200
resultingOutput: 3.response
- type: request
expectedMethod: PATCH
expectedPath: /api/v1/namespaces/edit-test/services/svc1
expectedContentType: application/strategic-merge-patch+json
expectedInput: 4.request
resultingStatusCode: 200
resultingOutput: 4.response

View File

@ -0,0 +1,13 @@
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "services \"missing\" not found",
"reason": "NotFound",
"details": {
"name": "missing",
"kind": "services"
},
"code": 404
}

View File

@ -0,0 +1,15 @@
description: add a testcase description
mode: edit
args:
- service/missing
namespace: "default"
expectedStderr:
- services "missing" not found
expectedExitCode: 1
steps:
- type: request
expectedMethod: GET
expectedPath: /api/v1/namespaces/default/services/missing
expectedInput: 0.request
resultingStatusCode: 404
resultingOutput: 0.response

View File

View File

@ -0,0 +1,12 @@
{
"kind": "ConfigMap",
"apiVersion": "v1",
"metadata": {
"name": "mymap",
"namespace": "default",
"selfLink": "/api/v1/namespaces/default/configmaps/mymap",
"uid": "dbde42e9-e9d5-11e6-8c3b-acbc32c1ca87",
"resourceVersion": "149",
"creationTimestamp": "2017-02-03T05:59:00Z"
}
}

View File

@ -0,0 +1,13 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp: 2017-02-03T05:59:00Z
name: mymap
namespace: default
resourceVersion: "149"
selfLink: /api/v1/namespaces/default/configmaps/mymap
uid: dbde42e9-e9d5-11e6-8c3b-acbc32c1ca87

View File

@ -0,0 +1,13 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp: 2017-02-03T05:59:00Z
name: mymap
namespace: default
resourceVersion: "149"
selfLink: /api/v1/namespaces/default/configmaps/mymap
uid: dbde42e9-e9d5-11e6-8c3b-acbc32c1ca87

View File

@ -0,0 +1,18 @@
description: no-op edit
mode: edit
args:
- configmap/mymap
namespace: "default"
expectedStderr:
- Edit cancelled, no changes made.
expectedExitCode: 0
steps:
- type: request
expectedMethod: GET
expectedPath: /api/v1/namespaces/default/configmaps/mymap
expectedInput: 0.request
resultingStatusCode: 200
resultingOutput: 0.response
- type: edit
expectedInput: 1.original
resultingOutput: 1.edited

View File

@ -0,0 +1,34 @@
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/services/svc1",
"uid": "5f7da8db-e8c3-11e6-b7e2-acbc32c1ca87",
"resourceVersion": "20715",
"creationTimestamp": "2017-02-01T21:14:09Z",
"labels": {
"app": "svc1"
}
},
"spec": {
"ports": [
{
"name": "80",
"protocol": "TCP",
"port": 80,
"targetPort": 80
}
],
"selector": {
"app": "svc1"
},
"clusterIP": "10.0.0.146",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,29 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-01T21:14:09Z
labels:
app: svc1
new-label: new-value
name: svc1
namespace: edit-test
resourceVersion: "20715"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 5f7da8db-e8c3-11e6-b7e2-acbc32c1ca87
spec:
clusterIP: 10.0.0.146
ports:
- name: "80"
port: 81
protocol: TCP
targetPort: 80
selector:
app: svc1
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

View File

@ -0,0 +1,28 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2017-02-01T21:14:09Z
labels:
app: svc1
name: svc1
namespace: edit-test
resourceVersion: "20715"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: 5f7da8db-e8c3-11e6-b7e2-acbc32c1ca87
spec:
clusterIP: 10.0.0.146
ports:
- name: "80"
port: 80
protocol: TCP
targetPort: 80
selector:
app: svc1
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

View File

@ -0,0 +1,21 @@
{
"metadata": {
"labels": {
"new-label": "new-value"
}
},
"spec": {
"ports": [
{
"$patch": "delete",
"port": 80
},
{
"name": "80",
"port": 81,
"protocol": "TCP",
"targetPort": 80
}
]
}
}

View File

@ -0,0 +1,35 @@
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/services/svc1",
"uid": "5f7da8db-e8c3-11e6-b7e2-acbc32c1ca87",
"resourceVersion": "20820",
"creationTimestamp": "2017-02-01T21:14:09Z",
"labels": {
"app": "svc1",
"new-label": "new-value"
}
},
"spec": {
"ports": [
{
"name": "80",
"protocol": "TCP",
"port": 81,
"targetPort": 80
}
],
"selector": {
"app": "svc1"
},
"clusterIP": "10.0.0.146",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,29 @@
# kubectl create namespace edit-test
# kubectl create service clusterip svc1 --tcp 80 --namespace=edit-test
# kubectl edit service svc1 --namespace=edit-test
description: edit a single service, add a label and change a port
mode: edit
args:
- service
- svc1
namespace: edit-test
expectedStdout:
- service "svc1" edited
expectedExitCode: 0
steps:
- type: request
expectedMethod: GET
expectedPath: /api/v1/namespaces/edit-test/services/svc1
expectedInput: 0.request
resultingStatusCode: 200
resultingOutput: 0.response
- type: edit
expectedInput: 1.original
resultingOutput: 1.edited
- type: request
expectedMethod: PATCH
expectedPath: /api/v1/namespaces/edit-test/services/svc1
expectedContentType: application/strategic-merge-patch+json
expectedInput: 2.request
resultingStatusCode: 200
resultingOutput: 2.response

View File

@ -216,6 +216,9 @@ type TestFactory struct {
Namespace string
ClientConfig *restclient.Config
Err error
ClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
UnstructuredClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
}
type FakeFactory struct {
@ -294,7 +297,10 @@ func (f *FakeFactory) BareClientConfig() (*restclient.Config, error) {
return f.tf.ClientConfig, f.tf.Err
}
func (f *FakeFactory) ClientForMapping(*meta.RESTMapping) (resource.RESTClient, error) {
func (f *FakeFactory) ClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) {
if f.tf.ClientForMappingFunc != nil {
return f.tf.ClientForMappingFunc(mapping)
}
return f.tf.Client, f.tf.Err
}
@ -311,7 +317,10 @@ func (f *FakeFactory) ClientConfigForVersion(requiredVersion *schema.GroupVersio
return nil, nil
}
func (f *FakeFactory) UnstructuredClientForMapping(*meta.RESTMapping) (resource.RESTClient, error) {
func (f *FakeFactory) UnstructuredClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) {
if f.tf.UnstructuredClientForMappingFunc != nil {
return f.tf.UnstructuredClientForMappingFunc(mapping)
}
return f.tf.UnstructuredClient, f.tf.Err
}
@ -481,6 +490,9 @@ func (f *fakeMixedFactory) ClientForMapping(m *meta.RESTMapping) (resource.RESTC
if m.ObjectConvertor == api.Scheme {
return f.apiClient, f.tf.Err
}
if f.tf.ClientForMappingFunc != nil {
return f.tf.ClientForMappingFunc(m)
}
return f.tf.Client, f.tf.Err
}
@ -553,11 +565,17 @@ func (f *fakeAPIFactory) ClientConfig() (*restclient.Config, error) {
return f.tf.ClientConfig, f.tf.Err
}
func (f *fakeAPIFactory) ClientForMapping(*meta.RESTMapping) (resource.RESTClient, error) {
func (f *fakeAPIFactory) ClientForMapping(m *meta.RESTMapping) (resource.RESTClient, error) {
if f.tf.ClientForMappingFunc != nil {
return f.tf.ClientForMappingFunc(m)
}
return f.tf.Client, f.tf.Err
}
func (f *fakeAPIFactory) UnstructuredClientForMapping(*meta.RESTMapping) (resource.RESTClient, error) {
func (f *fakeAPIFactory) UnstructuredClientForMapping(m *meta.RESTMapping) (resource.RESTClient, error) {
if f.tf.UnstructuredClientForMappingFunc != nil {
return f.tf.UnstructuredClientForMappingFunc(m)
}
return f.tf.UnstructuredClient, f.tf.Err
}

View File

@ -48,6 +48,7 @@ type RESTClient struct {
NegotiatedSerializer runtime.NegotiatedSerializer
GroupName string
APIRegistry *registered.APIRegistrationManager
VersionedAPIPath string
Req *http.Request
Resp *http.Response
@ -62,8 +63,8 @@ func (c *RESTClient) Put() *restclient.Request {
return c.request("PUT")
}
func (c *RESTClient) Patch(_ types.PatchType) *restclient.Request {
return c.request("PATCH")
func (c *RESTClient) Patch(pt types.PatchType) *restclient.Request {
return c.request("PATCH").SetHeader("Content-Type", string(pt))
}
func (c *RESTClient) Post() *restclient.Request {
@ -110,7 +111,7 @@ func (c *RESTClient) request(verb string) *restclient.Request {
serializers.StreamingSerializer = info.StreamSerializer.Serializer
serializers.Framer = info.StreamSerializer.Framer
}
return restclient.NewRequest(c, verb, &url.URL{Host: "localhost"}, "", config, serializers, nil, nil)
return restclient.NewRequest(c, verb, &url.URL{Host: "localhost"}, c.VersionedAPIPath, config, serializers, nil, nil)
}
func (c *RESTClient) Do(req *http.Request) (*http.Response, error) {