Make kubectl apply create resources if not found

This commit is contained in:
Janet Kuo 2015-11-04 18:29:56 -08:00
parent c095e35f1b
commit 37f35d9342
6 changed files with 81 additions and 5 deletions

View File

@ -14,6 +14,7 @@ kubectl apply \- Apply a configuration to a resource by filename or stdin
.SH DESCRIPTION
.PP
Apply a configuration to a resource by filename or stdin.
The resource will be created if it doesn't exist yet.
.PP
JSON and YAML formats are accepted.

View File

@ -39,6 +39,7 @@ Apply a configuration to a resource by filename or stdin
Apply a configuration to a resource by filename or stdin.
The resource will be created if it doesn't exist yet.
JSON and YAML formats are accepted.
@ -97,7 +98,7 @@ $ cat pod.json | kubectl apply -f -
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-10-01 05:36:57.66914652 +0000 UTC
###### Auto generated by spf13/cobra on 4-Nov-2015
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_apply.md?pixel)]()

View File

@ -635,6 +635,18 @@ runTests() {
# Clean up
kubectl delete rc,hpa frontend
## kubectl apply should create the resource that doesn't exist yet
# Pre-Condition: no POD is running
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
# Command: apply a pod "test-pod" (doesn't exist) should create this pod
kubectl apply -f hack/testdata/pod.yaml "${kube_flags[@]}"
# Post-Condition: pod "test-pod" is running
kube::test::get_object_assert 'pods test-pod' "{{${labels_field}.name}}" 'test-pod-label'
# Post-Condition: pod "test-pod" has configuration annotation
[[ "$(kubectl get pods test-pod -o yaml "${kube_flags[@]}" | grep kubectl.kubernetes.io/last-applied-configuration)" ]]
# Clean up
kubectl delete pods test-pod "${kube_flags[@]}"
##############
# Namespaces #
##############

View File

@ -23,6 +23,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
@ -37,6 +38,7 @@ type ApplyOptions struct {
const (
apply_long = `Apply a configuration to a resource by filename or stdin.
The resource will be created if it doesn't exist yet.
JSON and YAML formats are accepted.`
apply_example = `# Apply the configuration in pod.json to a pod.
@ -119,7 +121,21 @@ func RunApply(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *Ap
}
if err := info.Get(); err != nil {
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err)
if !errors.IsNotFound(err) {
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err)
}
// Create the resource if it doesn't exist
// First, update the annotation used by kubectl apply
if err := kubectl.CreateApplyAnnotation(info); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
// Then create the resource and skip the three-way merge
if err := createAndRefresh(info); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
count++
cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "created")
return nil
}
// Serialize the current configuration of the object from the server.

View File

@ -28,6 +28,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/client/unversioned/fake"
"k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
@ -207,6 +208,43 @@ func TestApplyObject(t *testing.T) {
}
}
func TestApplyNonExistObject(t *testing.T) {
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers"
pathNameRC := pathRC + "/" + nameRC
f, tf, codec := NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
Codec: codec,
Client: fake.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == pathNameRC && m == "GET":
return &http.Response{StatusCode: 404}, errors.NewNotFound("ReplicationController", "")
case p == pathRC && m == "POST":
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 201, Body: bodyRC}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply(f, buf)
cmd.Flags().Set("filename", filenameRC)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
// uses the name from the file, not the response
expectRC := "replicationcontroller/" + nameRC + "\n"
if buf.String() != expectRC {
t.Errorf("unexpected output: %s\nexpected: %s", buf.String(), expectRC)
}
}
func TestApplyMultipleObject(t *testing.T) {
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC

View File

@ -111,13 +111,11 @@ func RunCreate(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *C
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
if err != nil {
if err := createAndRefresh(info); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
count++
info.Refresh(obj, true)
shortOutput := cmdutil.GetFlagString(cmd, "output") == "name"
if !shortOutput {
printObjectSpecificMessage(info.Object, out)
@ -164,3 +162,13 @@ func makePortsString(ports []api.ServicePort, useNodePort bool) string {
}
return strings.Join(pieces, ",")
}
// createAndRefresh creates an object from input info and refreshes info with that object
func createAndRefresh(info *resource.Info) error {
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
if err != nil {
return err
}
info.Refresh(obj, true)
return nil
}