Implement kubectl rollout history and undo for DaemonSet

pull/6/head
Janet Kuo 2017-05-18 15:46:20 -07:00
parent dbd1503b65
commit edabdac094
13 changed files with 323 additions and 34 deletions

View File

@ -41,6 +41,8 @@ IMAGE_NGINX="gcr.io/google-containers/nginx:1.7.9"
IMAGE_DEPLOYMENT_R1="gcr.io/google-containers/nginx:test-cmd" # deployment-revision1.yaml
IMAGE_DEPLOYMENT_R2="$IMAGE_NGINX" # deployment-revision2.yaml
IMAGE_PERL="gcr.io/google-containers/perl"
IMAGE_DAEMONSET_R1="gcr.io/google-containers/pause:2.0"
IMAGE_DAEMONSET_R2="gcr.io/google-containers/pause:latest"
# Expose kubectl directly for readability
PATH="${KUBE_OUTPUT_HOSTBIN}":$PATH
@ -71,6 +73,7 @@ subjectaccessreviews="subjectaccessreviews"
thirdpartyresources="thirdpartyresources"
customresourcedefinitions="customresourcedefinitions"
daemonsets="daemonsets"
controllerrevisions="controllerrevisions"
# Stops the running kubectl proxy, if there is one.
@ -2868,6 +2871,39 @@ run_daemonset_tests() {
kubectl apply -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]}"
# Template Generation should stay 1
kube::test::get_object_assert 'daemonsets bind' "{{${template_generation_field}}}" '1'
# Clean up
kubectl delete -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]}"
}
run_daemonset_history_tests() {
kube::log::status "Testing kubectl(v1:daemonsets, v1:controllerrevisions)"
### Test rolling back a DaemonSet
# Pre-condition: no DaemonSet or its pods exists
kube::test::get_object_assert daemonsets "{{range.items}}{{$id_field}}:{{end}}" ''
# Command
# Create a DaemonSet (revision 1)
kubectl apply -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]}"
# Rollback to revision 1 - should be no-op
kubectl rollout undo daemonset --to-revision=1 "${kube_flags[@]}"
kube::test::get_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R1}:"
# Update the DaemonSet (revision 2)
kubectl apply -f hack/testdata/rollingupdate-daemonset-rv2.yaml "${kube_flags[@]}"
kube::test::wait_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R2}:"
# Rollback to revision 1 with dry-run - should be no-op
kubectl rollout undo daemonset --dry-run=true "${kube_flags[@]}"
kube::test::get_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R2}:"
# Rollback to revision 1
kubectl rollout undo daemonset --to-revision=1 "${kube_flags[@]}"
kube::test::wait_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R1}:"
# Rollback to revision 1000000 - should fail
output_message=$(! kubectl rollout undo daemonset --to-revision=1000000 "${kube_flags[@]}" 2>&1)
kube::test::if_has_string "${output_message}" "unable to find specified revision"
kube::test::get_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R1}:"
# Rollback to last revision
kubectl rollout undo daemonset "${kube_flags[@]}"
kube::test::wait_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R2}:"
# Clean up
kubectl delete -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]}"
}
@ -3103,6 +3139,7 @@ runTests() {
pdb_min_available=".spec.minAvailable"
pdb_max_unavailable=".spec.maxUnavailable"
template_generation_field=".spec.templateGeneration"
daemonset_image_field="(index .spec.template.spec.containers 0).image"
# Make sure "default" namespace exists.
if kube::test::if_supports_resource "${namespaces}" ; then
@ -3555,6 +3592,9 @@ runTests() {
if kube::test::if_supports_resource "${daemonsets}" ; then
run_daemonset_tests
if kube::test::if_supports_resource "${controllerrevisions}"; then
run_daemonset_history_tests
fi
fi
###########################

View File

@ -0,0 +1,27 @@
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: bind
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 10%
template:
metadata:
labels:
service: bind
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "service"
operator: "In"
values: ["bind"]
topologyKey: "kubernetes.io/hostname"
namespaces: []
containers:
- name: kubernetes-pause
image: gcr.io/google-containers/pause:latest

View File

@ -48,6 +48,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",

View File

@ -17,8 +17,6 @@ limitations under the License.
package daemon
import (
"bytes"
"encoding/json"
"fmt"
"sort"
@ -29,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
intstrutil "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
@ -297,23 +296,18 @@ func (dsc *DaemonSetsController) controlledHistories(ds *extensions.DaemonSet) (
// Match check if ds template is semantically equal to the template stored in history
func Match(template *v1.PodTemplateSpec, history *apps.ControllerRevision) (bool, error) {
t, err := decodeHistory(history)
t, err := DecodeHistory(history)
return apiequality.Semantic.DeepEqual(template, t), err
}
func decodeHistory(history *apps.ControllerRevision) (*v1.PodTemplateSpec, error) {
raw := history.Data.Raw
decoder := json.NewDecoder(bytes.NewBuffer(raw))
func DecodeHistory(history *apps.ControllerRevision) (*v1.PodTemplateSpec, error) {
template := v1.PodTemplateSpec{}
err := decoder.Decode(&template)
err := json.Unmarshal(history.Data.Raw, &template)
return &template, err
}
func encodeTemplate(template *v1.PodTemplateSpec) ([]byte, error) {
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
err := encoder.Encode(template)
return buffer.Bytes(), err
return json.Marshal(template)
}
func (dsc *DaemonSetsController) snapshot(ds *extensions.DaemonSet, revision int64) (*apps.ControllerRevision, error) {

View File

@ -66,6 +66,7 @@ go_library(
"//pkg/apis/policy:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library",
"//pkg/client/clientset_generated/clientset/typed/apps/v1beta1:go_default_library",
"//pkg/client/clientset_generated/clientset/typed/core/v1:go_default_library",
"//pkg/client/clientset_generated/clientset/typed/extensions/v1beta1:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
@ -76,6 +77,7 @@ go_library(
"//pkg/client/retry:go_default_library",
"//pkg/client/unversioned:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/controller/daemon:go_default_library",
"//pkg/controller/deployment/util:go_default_library",
"//pkg/credentialprovider:go_default_library",
"//pkg/kubectl/resource:go_default_library",
@ -88,6 +90,7 @@ go_library(
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",

View File

@ -28,15 +28,20 @@ import (
var (
rollout_long = templates.LongDesc(`
Manage a deployment using subcommands like "kubectl rollout undo deployment/abc"`)
Manage the rollout of a resource.` + rollout_valid_resources)
rollout_example = templates.Examples(`
# Rollback to the previous deployment
kubectl rollout undo deployment/abc`)
kubectl rollout undo deployment/abc
# Check the rollout status of a daemonset
kubectl rollout status daemonset/foo`)
rollout_valid_resources = dedent.Dedent(`
Valid resource types include:
* deployments
* daemonsets
`)
)
@ -44,7 +49,7 @@ func NewCmdRollout(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "rollout SUBCOMMAND",
Short: i18n.T("Manage a deployment rollout"),
Short: i18n.T("Manage the rollout of a resource"),
Long: rollout_long,
Example: rollout_example,
Run: cmdutil.DefaultSubCommandRun(errOut),
@ -54,7 +59,6 @@ func NewCmdRollout(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
cmd.AddCommand(NewCmdRolloutPause(f, out))
cmd.AddCommand(NewCmdRolloutResume(f, out))
cmd.AddCommand(NewCmdRolloutUndo(f, out))
cmd.AddCommand(NewCmdRolloutStatus(f, out))
return cmd

View File

@ -37,14 +37,14 @@ var (
# View the rollout history of a deployment
kubectl rollout history deployment/abc
# View the details of deployment revision 3
kubectl rollout history deployment/abc --revision=3`)
# View the details of daemonset revision 3
kubectl rollout history daemonset/abc --revision=3`)
)
func NewCmdRolloutHistory(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &resource.FilenameOptions{}
validArgs := []string{"deployment"}
validArgs := []string{"deployment", "daemonset"}
argAliases := kubectl.ResourceAliases(validArgs)
cmd := &cobra.Command{

View File

@ -53,7 +53,7 @@ var (
Mark the provided resource as paused
Paused resources will not be reconciled by a controller.
Use \"kubectl rollout resume\" to resume a paused resource.
Use "kubectl rollout resume" to resume a paused resource.
Currently only deployments support being paused.`)
pause_example = templates.Examples(`

View File

@ -50,7 +50,7 @@ var (
func NewCmdRolloutStatus(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &resource.FilenameOptions{}
validArgs := []string{"deployment"}
validArgs := []string{"deployment", "daemonset"}
argAliases := kubectl.ResourceAliases(validArgs)
cmd := &cobra.Command{

View File

@ -54,8 +54,8 @@ var (
# Rollback to the previous deployment
kubectl rollout undo deployment/abc
# Rollback to deployment revision 3
kubectl rollout undo deployment/abc --to-revision=3
# Rollback to daemonset revision 3
kubectl rollout undo daemonset/abc --to-revision=3
# Rollback to the previous deployment with dry-run
kubectl rollout undo --dry-run=true deployment/abc`)
@ -64,7 +64,7 @@ var (
func NewCmdRolloutUndo(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &UndoOptions{}
validArgs := []string{"deployment"}
validArgs := []string{"deployment", "daemonset"}
argAliases := kubectl.ResourceAliases(validArgs)
cmd := &cobra.Command{

View File

@ -29,8 +29,11 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apis/apps"
appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
"k8s.io/kubernetes/pkg/apis/extensions"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/controller/daemon"
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
sliceutil "k8s.io/kubernetes/pkg/util/slice"
@ -49,6 +52,8 @@ func HistoryViewerFor(kind schema.GroupKind, c clientset.Interface) (HistoryView
switch kind {
case extensions.Kind("Deployment"), apps.Kind("Deployment"):
return &DeploymentHistoryViewer{c}, nil
case extensions.Kind("DaemonSet"):
return &DaemonSetHistoryViewer{c}, nil
}
return nil, fmt.Errorf("no history viewer has been implemented for %q", kind)
}
@ -100,14 +105,7 @@ func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision i
if !ok {
return "", fmt.Errorf("unable to find the specified revision")
}
buf := bytes.NewBuffer([]byte{})
internalTemplate := &api.PodTemplateSpec{}
if err := v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(template, internalTemplate, nil); err != nil {
return "", fmt.Errorf("failed to convert podtemplate, %v", err)
}
w := printersinternal.NewPrefixWriter(buf)
printersinternal.DescribePodTemplate(internalTemplate, w)
return buf.String(), nil
return printTemplate(template)
}
// Sort the revisionToChangeCause map by revision
@ -131,6 +129,101 @@ func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision i
})
}
func printTemplate(template *v1.PodTemplateSpec) (string, error) {
buf := bytes.NewBuffer([]byte{})
internalTemplate := &api.PodTemplateSpec{}
if err := v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(template, internalTemplate, nil); err != nil {
return "", fmt.Errorf("failed to convert podtemplate, %v", err)
}
w := printersinternal.NewPrefixWriter(buf)
printersinternal.DescribePodTemplate(internalTemplate, w)
return buf.String(), nil
}
type DaemonSetHistoryViewer struct {
c clientset.Interface
}
// ViewHistory returns a revision-to-history map as the revision history of a deployment
// TODO: this should be a describer
func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
ds, err := h.c.Extensions().DaemonSets(namespace).Get(name, metav1.GetOptions{})
if err != nil {
return "", fmt.Errorf("failed to retrieve DaemonSet %s: %v", name, err)
}
allHistory, err := controlledHistories(h.c, ds)
if err != nil {
return "", fmt.Errorf("unable to find history controlled by DaemonSet %s: %v", ds.Name, err)
}
historyInfo := make(map[int64]*appsv1beta1.ControllerRevision)
for _, history := range allHistory {
// TODO: for now we assume revisions don't overlap, we may need to handle it
historyInfo[history.Revision] = history
}
if len(historyInfo) == 0 {
return "No rollout history found.", nil
}
// Print details of a specific revision
if revision > 0 {
history, ok := historyInfo[revision]
if !ok {
return "", fmt.Errorf("unable to find the specified revision")
}
template, err := daemon.DecodeHistory(history)
if err != nil {
return "", fmt.Errorf("unable to decode history %s", history.Name)
}
return printTemplate(template)
}
// Print an overview of all Revisions
// Sort the revisionToChangeCause map by revision
revisions := make([]int64, 0, len(historyInfo))
for r := range historyInfo {
revisions = append(revisions, r)
}
sliceutil.SortInts64(revisions)
return tabbedString(func(out io.Writer) error {
fmt.Fprintf(out, "REVISION\tCHANGE-CAUSE\n")
for _, r := range revisions {
// Find the change-cause of revision r
changeCause := historyInfo[r].Annotations[ChangeCauseAnnotation]
if len(changeCause) == 0 {
changeCause = "<none>"
}
fmt.Fprintf(out, "%d\t%s\n", r, changeCause)
}
return nil
})
}
// controlledHistories returns all ControllerRevisions controlled by the given DaemonSet
// TODO: Use external version DaemonSet instead when #3955 is fixed
func controlledHistories(c clientset.Interface, ds *extensions.DaemonSet) ([]*appsv1beta1.ControllerRevision, error) {
var result []*appsv1beta1.ControllerRevision
selector, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
if err != nil {
return nil, err
}
versionedClient := versionedClientsetForDaemonSet(c)
historyList, err := versionedClient.AppsV1beta1().ControllerRevisions(ds.Namespace).List(metav1.ListOptions{LabelSelector: selector.String()})
if err != nil {
return nil, err
}
for i := range historyList.Items {
history := historyList.Items[i]
// Skip history that doesn't belong to the DaemonSet
if controllerRef := controller.GetControllerOf(&history); controllerRef == nil || controllerRef.UID != ds.UID {
continue
}
result = append(result, &history)
}
return result, nil
}
// TODO: copied here until this becomes a describer
func tabbedString(f func(io.Writer) error) (string, error) {
out := new(tabwriter.Writer)

View File

@ -21,8 +21,10 @@ import (
"fmt"
"os"
"os/signal"
"sort"
"syscall"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -30,14 +32,22 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apis/apps"
appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
"k8s.io/kubernetes/pkg/apis/extensions"
externalextensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/retry"
"k8s.io/kubernetes/pkg/controller/daemon"
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
sliceutil "k8s.io/kubernetes/pkg/util/slice"
)
const (
rollbackSuccess = "rolled back"
rollbackSkipped = "skipped rollback"
)
// Rollbacker provides an interface for resources that can be rolled back.
type Rollbacker interface {
Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error)
@ -47,6 +57,8 @@ func RollbackerFor(kind schema.GroupKind, c clientset.Interface) (Rollbacker, er
switch kind {
case extensions.Kind("Deployment"), apps.Kind("Deployment"):
return &DeploymentRollbacker{c}, nil
case extensions.Kind("DaemonSet"):
return &DaemonSetRollbacker{c}, nil
}
return nil, fmt.Errorf("no rollbacker has been implemented for %q", kind)
}
@ -126,9 +138,9 @@ func isRollbackEvent(e *api.Event) (bool, string) {
for _, reason := range rollbackEventReasons {
if e.Reason == reason {
if reason == deploymentutil.RollbackDone {
return true, "rolled back"
return true, rollbackSuccess
}
return true, fmt.Sprintf("skipped rollback (%s: %s)", e.Reason, e.Message)
return true, fmt.Sprintf("%s (%s: %s)", rollbackSkipped, e.Reason, e.Message)
}
}
return false, ""
@ -165,7 +177,7 @@ func simpleDryRun(deployment *extensions.Deployment, c clientset.Interface, toRe
if toRevision > 0 {
template, ok := revisionToSpec[toRevision]
if !ok {
return "", fmt.Errorf("unable to find specified revision")
return "", revisionNotFoundErr(toRevision)
}
buf := bytes.NewBuffer([]byte{})
internalTemplate := &api.PodTemplateSpec{}
@ -195,3 +207,108 @@ func simpleDryRun(deployment *extensions.Deployment, c clientset.Interface, toRe
printersinternal.DescribePodTemplate(internalTemplate, w)
return buf.String(), nil
}
type DaemonSetRollbacker struct {
c clientset.Interface
}
func (r *DaemonSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error) {
if toRevision < 0 {
return "", revisionNotFoundErr(toRevision)
}
ds, ok := obj.(*extensions.DaemonSet)
if !ok {
return "", fmt.Errorf("passed object is not a DaemonSet: %#v", obj)
}
allHistory, err := controlledHistories(r.c, ds)
if err != nil {
return "", fmt.Errorf("unable to find history controlled by DaemonSet %s: %v", ds.Name, err)
}
if toRevision == 0 && len(allHistory) <= 1 {
return "", fmt.Errorf("no last revision to roll back to")
}
// Find the history to rollback to
var toHistory *appsv1beta1.ControllerRevision
if toRevision == 0 {
// If toRevision == 0, find the latest revision (2nd max)
sort.Sort(historiesByRevision(allHistory))
toHistory = allHistory[len(allHistory)-2]
} else {
for _, h := range allHistory {
if h.Revision == toRevision {
// If toRevision != 0, find the history with matching revision
toHistory = h
break
}
}
}
if toHistory == nil {
return "", revisionNotFoundErr(toRevision)
}
// Get the template of the history to rollback to
toTemplate, err := getInternalTemplate(toHistory)
if err != nil {
return "", err
}
if dryRun {
content := bytes.NewBuffer([]byte{})
w := printersinternal.NewPrefixWriter(content)
printersinternal.DescribePodTemplate(toTemplate, w)
return fmt.Sprintf("will roll back to %s", content.String()), nil
}
// Update DaemonSet template, and retry on conflict
skipUpdate := false
retryErr := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
var err error
ds, err = r.c.Extensions().DaemonSets(ds.Namespace).Get(ds.Name, metav1.GetOptions{})
if err != nil {
return err
}
if apiequality.Semantic.DeepEqual(toTemplate, &ds.Spec.Template) {
skipUpdate = true
return nil
}
ds.Spec.Template = *toTemplate
_, err = r.c.Extensions().DaemonSets(ds.Namespace).Update(ds)
return err
})
if retryErr != nil {
return "", retryErr
}
if skipUpdate {
return fmt.Sprintf("%s (current template already matches revision %d)", rollbackSkipped, toRevision), nil
}
return rollbackSuccess, nil
}
func getInternalTemplate(toHistory *appsv1beta1.ControllerRevision) (*api.PodTemplateSpec, error) {
template, err := daemon.DecodeHistory(toHistory)
if err != nil {
return nil, err
}
internalTemplate := &api.PodTemplateSpec{}
if err := v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(template, internalTemplate, nil); err != nil {
return nil, fmt.Errorf("failed to convert podtemplate, %v", err)
}
return internalTemplate, nil
}
func revisionNotFoundErr(r int64) error {
return fmt.Errorf("unable to find specified revision %v in history", r)
}
// TODO: copied from daemon controller, should extract to a library
type historiesByRevision []*appsv1beta1.ControllerRevision
func (h historiesByRevision) Len() int { return len(h) }
func (h historiesByRevision) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h historiesByRevision) Less(i, j int) bool {
return h[i].Revision < h[j].Revision
}

View File

@ -18,6 +18,7 @@ package kubectl
import (
externalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
apps "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/apps/v1beta1"
core "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1"
extensions "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/extensions/v1beta1"
internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
@ -32,3 +33,12 @@ func versionedClientsetForDeployment(internalClient internalclientset.Interface)
ExtensionsV1beta1Client: extensions.New(internalClient.Extensions().RESTClient()),
}
}
func versionedClientsetForDaemonSet(internalClient internalclientset.Interface) externalclientset.Interface {
if internalClient == nil {
return &externalclientset.Clientset{}
}
return &externalclientset.Clientset{
AppsV1beta1Client: apps.New(internalClient.Apps().RESTClient()),
}
}