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 .SH DESCRIPTION
.PP .PP
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.
.PP .PP
JSON and YAML formats are accepted. 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. 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. 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 * [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 --> <!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_apply.md?pixel)]() [![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 # Clean up
kubectl delete rc,hpa frontend 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 # # Namespaces #
############## ##############

View File

@ -23,6 +23,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
@ -37,6 +38,7 @@ type ApplyOptions struct {
const ( const (
apply_long = `Apply a configuration to a resource by filename or stdin. 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.` JSON and YAML formats are accepted.`
apply_example = `# Apply the configuration in pod.json to a pod. 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 { 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. // Serialize the current configuration of the object from the server.

View File

@ -28,6 +28,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/client/unversioned/fake" "k8s.io/kubernetes/pkg/client/unversioned/fake"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" 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) { func TestApplyMultipleObject(t *testing.T) {
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC) nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC 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) return cmdutil.AddSourceToErr("creating", info.Source, err)
} }
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object) if err := createAndRefresh(info); err != nil {
if err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err) return cmdutil.AddSourceToErr("creating", info.Source, err)
} }
count++ count++
info.Refresh(obj, true)
shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" shortOutput := cmdutil.GetFlagString(cmd, "output") == "name"
if !shortOutput { if !shortOutput {
printObjectSpecificMessage(info.Object, out) printObjectSpecificMessage(info.Object, out)
@ -164,3 +162,13 @@ func makePortsString(ports []api.ServicePort, useNodePort bool) string {
} }
return strings.Join(pieces, ",") 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
}