Make kubectl apply create resources if not found
This commit is contained in:
parent
c095e35f1b
commit
37f35d9342
@ -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.
|
||||
|
@ -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 -->
|
||||
[]()
|
||||
|
@ -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 #
|
||||
##############
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user