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:
shiywang 2017-02-09 01:42:31 +08:00
parent 1cb737fb97
commit 557c18694a
8 changed files with 327 additions and 3 deletions

View File

@ -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

View 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.

View 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.

View 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",

View File

@ -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
}

View File

@ -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)

View 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)
}
}

View 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