Add apply view last-applied subcommand
change to GetOriginalConfiguration add bazel refactor apply view-last-applied command update some changes minor change add unit tests, update update some codes and genreate docs update LongDesc
This commit is contained in:
parent
1cb737fb97
commit
557c18694a
@ -12,6 +12,7 @@ docs/man/man1/kube-proxy.1
|
||||
docs/man/man1/kube-scheduler.1
|
||||
docs/man/man1/kubectl-annotate.1
|
||||
docs/man/man1/kubectl-api-versions.1
|
||||
docs/man/man1/kubectl-apply-view-last-applied.1
|
||||
docs/man/man1/kubectl-apply.1
|
||||
docs/man/man1/kubectl-attach.1
|
||||
docs/man/man1/kubectl-autoscale.1
|
||||
@ -97,6 +98,7 @@ docs/user-guide/kubectl/kubectl.md
|
||||
docs/user-guide/kubectl/kubectl_annotate.md
|
||||
docs/user-guide/kubectl/kubectl_api-versions.md
|
||||
docs/user-guide/kubectl/kubectl_apply.md
|
||||
docs/user-guide/kubectl/kubectl_apply_view-last-applied.md
|
||||
docs/user-guide/kubectl/kubectl_attach.md
|
||||
docs/user-guide/kubectl/kubectl_autoscale.md
|
||||
docs/user-guide/kubectl/kubectl_certificate.md
|
||||
|
3
docs/man/man1/kubectl-apply-view-last-applied.1
Normal file
3
docs/man/man1/kubectl-apply-view-last-applied.1
Normal file
@ -0,0 +1,3 @@
|
||||
This file is autogenerated, but we've stopped checking such files into the
|
||||
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
|
||||
populate this file.
|
@ -0,0 +1,3 @@
|
||||
This file is autogenerated, but we've stopped checking such files into the
|
||||
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
|
||||
populate this file.
|
@ -14,6 +14,7 @@ go_library(
|
||||
"annotate.go",
|
||||
"apiversions.go",
|
||||
"apply.go",
|
||||
"apply_view_last_applied.go",
|
||||
"attach.go",
|
||||
"autoscale.go",
|
||||
"certificates.go",
|
||||
@ -98,6 +99,7 @@ go_library(
|
||||
"//vendor:github.com/docker/distribution/reference",
|
||||
"//vendor:github.com/docker/docker/pkg/term",
|
||||
"//vendor:github.com/evanphx/json-patch",
|
||||
"//vendor:github.com/ghodss/yaml",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:github.com/jonboulle/clockwork",
|
||||
"//vendor:github.com/renstrom/dedent",
|
||||
|
@ -128,6 +128,10 @@ func NewCmdApply(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
|
||||
cmdutil.AddPrinterFlags(cmd)
|
||||
cmdutil.AddRecordFlag(cmd)
|
||||
cmdutil.AddInclude3rdPartyFlags(cmd)
|
||||
|
||||
// apply subcommands
|
||||
cmd.AddCommand(NewCmdApplyViewLastApplied(f, out, errOut))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -59,9 +59,10 @@ func validateApplyArgs(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
const (
|
||||
filenameRC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc.yaml"
|
||||
filenameSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/service.yaml"
|
||||
filenameRCSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-service.yaml"
|
||||
filenameRC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc.yaml"
|
||||
filenameRCLASTAPPLIED = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-lastapplied.yaml"
|
||||
filenameSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/service.yaml"
|
||||
filenameRCSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-service.yaml"
|
||||
)
|
||||
|
||||
func readBytesFromFile(t *testing.T, filename string) []byte {
|
||||
@ -193,6 +194,129 @@ func walkMapPath(t *testing.T, start map[string]interface{}, path []string) map[
|
||||
return finish
|
||||
}
|
||||
|
||||
func TestRunApplyViewLastApplied(t *testing.T) {
|
||||
_, rcBytesWithConfig := readReplicationController(t, filenameRCLASTAPPLIED)
|
||||
nameRC, rcBytes := readReplicationController(t, filenameRC)
|
||||
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
|
||||
|
||||
tests := []struct {
|
||||
name, nameRC, pathRC, filePath, outputFormat, expectedErr, expectedOut, selector string
|
||||
args []string
|
||||
respBytes []byte
|
||||
}{
|
||||
{
|
||||
name: "view with file",
|
||||
filePath: filenameRC,
|
||||
outputFormat: "",
|
||||
expectedErr: "",
|
||||
expectedOut: "test: 1234\n",
|
||||
selector: "",
|
||||
args: []string{},
|
||||
respBytes: rcBytesWithConfig,
|
||||
},
|
||||
{
|
||||
name: "view with file json format",
|
||||
filePath: filenameRC,
|
||||
outputFormat: "json",
|
||||
expectedErr: "",
|
||||
expectedOut: "{\n \"test\": 1234\n}\n",
|
||||
selector: "",
|
||||
args: []string{},
|
||||
respBytes: rcBytesWithConfig,
|
||||
},
|
||||
{
|
||||
name: "view resource/name invalid format",
|
||||
filePath: "",
|
||||
outputFormat: "wide",
|
||||
expectedErr: "error: Unexpected -o output mode: wide, the flag 'output' must be one of yaml|json\nSee 'view-last-applied -h' for help and examples.",
|
||||
expectedOut: "",
|
||||
selector: "",
|
||||
args: []string{"rc", "test-rc"},
|
||||
respBytes: rcBytesWithConfig,
|
||||
},
|
||||
{
|
||||
name: "view resource with label",
|
||||
filePath: "",
|
||||
outputFormat: "",
|
||||
expectedErr: "",
|
||||
expectedOut: "test: 1234\n",
|
||||
selector: "name=test-rc",
|
||||
args: []string{"rc"},
|
||||
respBytes: rcBytesWithConfig,
|
||||
},
|
||||
{
|
||||
name: "view resource without annotations",
|
||||
filePath: "",
|
||||
outputFormat: "",
|
||||
expectedErr: "error: no last-applied-configuration annotation found on resource: test-rc",
|
||||
expectedOut: "",
|
||||
selector: "",
|
||||
args: []string{"rc", "test-rc"},
|
||||
respBytes: rcBytes,
|
||||
},
|
||||
{
|
||||
name: "view resource no match",
|
||||
filePath: "",
|
||||
outputFormat: "",
|
||||
expectedErr: "Error from server (NotFound): the server could not find the requested resource (get replicationcontrollers no-match)",
|
||||
expectedOut: "",
|
||||
selector: "",
|
||||
args: []string{"rc", "no-match"},
|
||||
respBytes: nil,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
f, tf, codec, _ := cmdtesting.NewAPIFactory()
|
||||
tf.Printer = &testPrinter{}
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
APIRegistry: api.Registry,
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == pathRC && m == "GET":
|
||||
bodyRC := ioutil.NopCloser(bytes.NewReader(test.respBytes))
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
|
||||
case p == "/namespaces/test/replicationcontrollers" && m == "GET":
|
||||
bodyRC := ioutil.NopCloser(bytes.NewReader(test.respBytes))
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
|
||||
case p == "/namespaces/test/replicationcontrollers/no-match" && m == "GET":
|
||||
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, &api.Pod{})}, nil
|
||||
case p == "/api/v1/namespaces/test" && m == "GET":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.Namespace{})}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
tf.Namespace = "test"
|
||||
tf.ClientConfig = defaultClientConfig()
|
||||
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
|
||||
|
||||
cmdutil.BehaviorOnFatal(func(str string, code int) {
|
||||
if str != test.expectedErr {
|
||||
t.Errorf("%s: unexpected error: %s\nexpected: %s", test.name, str, test.expectedErr)
|
||||
}
|
||||
})
|
||||
|
||||
cmd := NewCmdApplyViewLastApplied(f, buf, errBuf)
|
||||
if test.filePath != "" {
|
||||
cmd.Flags().Set("filename", test.filePath)
|
||||
}
|
||||
if test.outputFormat != "" {
|
||||
cmd.Flags().Set("output", test.outputFormat)
|
||||
}
|
||||
if test.selector != "" {
|
||||
cmd.Flags().Set("selector", test.selector)
|
||||
}
|
||||
|
||||
cmd.Run(cmd, test.args)
|
||||
if buf.String() != test.expectedOut {
|
||||
t.Fatalf("%s: unexpected output: %s\nexpected: %s", test.name, buf.String(), test.expectedOut)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyObjectWithoutAnnotation(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
nameRC, rcBytes := readReplicationController(t, filenameRC)
|
||||
|
165
pkg/kubectl/cmd/apply_view_last_applied.go
Normal file
165
pkg/kubectl/cmd/apply_view_last_applied.go
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/resource"
|
||||
)
|
||||
|
||||
type ViewLastAppliedOptions struct {
|
||||
FilenameOptions resource.FilenameOptions
|
||||
Selector string
|
||||
LastAppliedConfigurationList []string
|
||||
OutputFormat string
|
||||
Factory cmdutil.Factory
|
||||
Out io.Writer
|
||||
ErrOut io.Writer
|
||||
}
|
||||
|
||||
var (
|
||||
applyViewLastAppliedLong = templates.LongDesc(`
|
||||
View the latest last-applied-configuration annotations by type/name or file.
|
||||
|
||||
The default output will be printed to stdout in YAML format. One can use -o option
|
||||
to change output format.`)
|
||||
|
||||
applyViewLastAppliedExample = templates.Examples(`
|
||||
# View the last-applied-configuration annotations by type/name in YAML.
|
||||
kubectl apply view-last-applied deployment/nginx
|
||||
|
||||
# View the last-applied-configuration annotations by file in JSON
|
||||
kubectl apply view-last-applied -f deploy.yaml -o json`)
|
||||
)
|
||||
|
||||
func NewCmdApplyViewLastApplied(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
|
||||
options := &ViewLastAppliedOptions{Out: out, ErrOut: err}
|
||||
cmd := &cobra.Command{
|
||||
Use: "view-last-applied (TYPE [NAME | -l label] | TYPE/NAME | -f FILENAME)",
|
||||
Short: "View latest last-applied-configuration annotations of a resource/object",
|
||||
Long: applyViewLastAppliedLong,
|
||||
Example: applyViewLastAppliedExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.ValidateOutputArgs(cmd))
|
||||
cmdutil.CheckErr(options.Complete(f, args))
|
||||
cmdutil.CheckErr(options.Validate(cmd))
|
||||
cmdutil.CheckErr(options.RunApplyViewLastApplied())
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringP("output", "o", "", "Output format. Must be one of yaml|json")
|
||||
cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.")
|
||||
usage := "that contains the last-applied-configuration annotations"
|
||||
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ViewLastAppliedOptions) Complete(f cmdutil.Factory, args []string) error {
|
||||
mapper, typer, err := f.UnstructuredObject()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
|
||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||
FilenameParam(enforceNamespace, &o.FilenameOptions).
|
||||
ResourceTypeOrNameArgs(enforceNamespace, args...).
|
||||
SelectorParam(o.Selector).
|
||||
Latest().
|
||||
Flatten().
|
||||
Do()
|
||||
err = r.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = r.Visit(func(info *resource.Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configString, err := kubectl.GetOriginalConfiguration(info.Mapping, info.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if configString == nil {
|
||||
return cmdutil.AddSourceToErr(fmt.Sprintf("no last-applied-configuration annotation found on resource: %s\n", info.Name), info.Source, err)
|
||||
}
|
||||
o.LastAppliedConfigurationList = append(o.LastAppliedConfigurationList, string(configString))
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ViewLastAppliedOptions) Validate(cmd *cobra.Command) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ViewLastAppliedOptions) RunApplyViewLastApplied() error {
|
||||
for _, str := range o.LastAppliedConfigurationList {
|
||||
yamlOutput, err := yaml.JSONToYAML([]byte(str))
|
||||
switch o.OutputFormat {
|
||||
case "json":
|
||||
jsonBuffer := &bytes.Buffer{}
|
||||
err = json.Indent(jsonBuffer, []byte(str), "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(o.Out, string(jsonBuffer.Bytes()))
|
||||
case "yaml":
|
||||
fmt.Fprintf(o.Out, string(yamlOutput))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ViewLastAppliedOptions) ValidateOutputArgs(cmd *cobra.Command) error {
|
||||
format := cmdutil.GetFlagString(cmd, "output")
|
||||
switch format {
|
||||
case "json":
|
||||
o.OutputFormat = "json"
|
||||
return nil
|
||||
// If flag -o is not specified, use yaml as default
|
||||
case "yaml", "":
|
||||
o.OutputFormat = "yaml"
|
||||
return nil
|
||||
default:
|
||||
return cmdutil.UsageError(cmd, "Unexpected -o output mode: %s, the flag 'output' must be one of yaml|json", format)
|
||||
}
|
||||
}
|
21
test/fixtures/pkg/kubectl/cmd/apply/rc-lastapplied.yaml
vendored
Normal file
21
test/fixtures/pkg/kubectl/cmd/apply/rc-lastapplied.yaml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
annotations:
|
||||
kubectl.kubernetes.io/last-applied-configuration: |
|
||||
{"test":1234}
|
||||
name: test-rc
|
||||
labels:
|
||||
name: test-rc
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: test-rc
|
||||
spec:
|
||||
containers:
|
||||
- name: test-rc
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
Loading…
Reference in New Issue
Block a user