mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			391 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			391 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2016 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 kubectl
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"text/tabwriter"
 | 
						|
 | 
						|
	appsv1 "k8s.io/api/apps/v1"
 | 
						|
	corev1 "k8s.io/api/core/v1"
 | 
						|
 | 
						|
	"k8s.io/apimachinery/pkg/api/meta"
 | 
						|
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						|
	"k8s.io/apimachinery/pkg/labels"
 | 
						|
	"k8s.io/apimachinery/pkg/runtime"
 | 
						|
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
						|
	"k8s.io/apimachinery/pkg/util/json"
 | 
						|
	"k8s.io/apimachinery/pkg/util/strategicpatch"
 | 
						|
	"k8s.io/client-go/kubernetes"
 | 
						|
	clientappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
 | 
						|
	kapps "k8s.io/kubernetes/pkg/kubectl/apps"
 | 
						|
	describe "k8s.io/kubernetes/pkg/kubectl/describe/versioned"
 | 
						|
	deploymentutil "k8s.io/kubernetes/pkg/kubectl/util/deployment"
 | 
						|
	sliceutil "k8s.io/kubernetes/pkg/kubectl/util/slice"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	ChangeCauseAnnotation = "kubernetes.io/change-cause"
 | 
						|
)
 | 
						|
 | 
						|
// HistoryViewer provides an interface for resources have historical information.
 | 
						|
type HistoryViewer interface {
 | 
						|
	ViewHistory(namespace, name string, revision int64) (string, error)
 | 
						|
}
 | 
						|
 | 
						|
type HistoryVisitor struct {
 | 
						|
	clientset kubernetes.Interface
 | 
						|
	result    HistoryViewer
 | 
						|
}
 | 
						|
 | 
						|
func (v *HistoryVisitor) VisitDeployment(elem kapps.GroupKindElement) {
 | 
						|
	v.result = &DeploymentHistoryViewer{v.clientset}
 | 
						|
}
 | 
						|
 | 
						|
func (v *HistoryVisitor) VisitStatefulSet(kind kapps.GroupKindElement) {
 | 
						|
	v.result = &StatefulSetHistoryViewer{v.clientset}
 | 
						|
}
 | 
						|
 | 
						|
func (v *HistoryVisitor) VisitDaemonSet(kind kapps.GroupKindElement) {
 | 
						|
	v.result = &DaemonSetHistoryViewer{v.clientset}
 | 
						|
}
 | 
						|
 | 
						|
func (v *HistoryVisitor) VisitJob(kind kapps.GroupKindElement)                   {}
 | 
						|
func (v *HistoryVisitor) VisitPod(kind kapps.GroupKindElement)                   {}
 | 
						|
func (v *HistoryVisitor) VisitReplicaSet(kind kapps.GroupKindElement)            {}
 | 
						|
func (v *HistoryVisitor) VisitReplicationController(kind kapps.GroupKindElement) {}
 | 
						|
func (v *HistoryVisitor) VisitCronJob(kind kapps.GroupKindElement)               {}
 | 
						|
 | 
						|
// HistoryViewerFor returns an implementation of HistoryViewer interface for the given schema kind
 | 
						|
func HistoryViewerFor(kind schema.GroupKind, c kubernetes.Interface) (HistoryViewer, error) {
 | 
						|
	elem := kapps.GroupKindElement(kind)
 | 
						|
	visitor := &HistoryVisitor{
 | 
						|
		clientset: c,
 | 
						|
	}
 | 
						|
 | 
						|
	// Determine which HistoryViewer we need here
 | 
						|
	err := elem.Accept(visitor)
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("error retrieving history for %q, %v", kind.String(), err)
 | 
						|
	}
 | 
						|
 | 
						|
	if visitor.result == nil {
 | 
						|
		return nil, fmt.Errorf("no history viewer has been implemented for %q", kind.String())
 | 
						|
	}
 | 
						|
 | 
						|
	return visitor.result, nil
 | 
						|
}
 | 
						|
 | 
						|
type DeploymentHistoryViewer struct {
 | 
						|
	c kubernetes.Interface
 | 
						|
}
 | 
						|
 | 
						|
// ViewHistory returns a revision-to-replicaset map as the revision history of a deployment
 | 
						|
// TODO: this should be a describer
 | 
						|
func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
 | 
						|
	versionedAppsClient := h.c.AppsV1()
 | 
						|
	deployment, err := versionedAppsClient.Deployments(namespace).Get(name, metav1.GetOptions{})
 | 
						|
	if err != nil {
 | 
						|
		return "", fmt.Errorf("failed to retrieve deployment %s: %v", name, err)
 | 
						|
	}
 | 
						|
	_, allOldRSs, newRS, err := deploymentutil.GetAllReplicaSets(deployment, versionedAppsClient)
 | 
						|
	if err != nil {
 | 
						|
		return "", fmt.Errorf("failed to retrieve replica sets from deployment %s: %v", name, err)
 | 
						|
	}
 | 
						|
	allRSs := allOldRSs
 | 
						|
	if newRS != nil {
 | 
						|
		allRSs = append(allRSs, newRS)
 | 
						|
	}
 | 
						|
 | 
						|
	historyInfo := make(map[int64]*corev1.PodTemplateSpec)
 | 
						|
	for _, rs := range allRSs {
 | 
						|
		v, err := deploymentutil.Revision(rs)
 | 
						|
		if err != nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		historyInfo[v] = &rs.Spec.Template
 | 
						|
		changeCause := getChangeCause(rs)
 | 
						|
		if historyInfo[v].Annotations == nil {
 | 
						|
			historyInfo[v].Annotations = make(map[string]string)
 | 
						|
		}
 | 
						|
		if len(changeCause) > 0 {
 | 
						|
			historyInfo[v].Annotations[ChangeCauseAnnotation] = changeCause
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if len(historyInfo) == 0 {
 | 
						|
		return "No rollout history found.", nil
 | 
						|
	}
 | 
						|
 | 
						|
	if revision > 0 {
 | 
						|
		// Print details of a specific revision
 | 
						|
		template, ok := historyInfo[revision]
 | 
						|
		if !ok {
 | 
						|
			return "", fmt.Errorf("unable to find the specified revision")
 | 
						|
		}
 | 
						|
		return printTemplate(template)
 | 
						|
	}
 | 
						|
 | 
						|
	// 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
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func printTemplate(template *corev1.PodTemplateSpec) (string, error) {
 | 
						|
	buf := bytes.NewBuffer([]byte{})
 | 
						|
	w := describe.NewPrefixWriter(buf)
 | 
						|
	describe.DescribePodTemplate(template, w)
 | 
						|
	return buf.String(), nil
 | 
						|
}
 | 
						|
 | 
						|
type DaemonSetHistoryViewer struct {
 | 
						|
	c kubernetes.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, history, err := daemonSetHistory(h.c.AppsV1(), namespace, name)
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	historyInfo := make(map[int64]*appsv1.ControllerRevision)
 | 
						|
	for _, history := range history {
 | 
						|
		// 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")
 | 
						|
		}
 | 
						|
		dsOfHistory, err := applyDaemonSetHistory(ds, history)
 | 
						|
		if err != nil {
 | 
						|
			return "", fmt.Errorf("unable to parse history %s", history.Name)
 | 
						|
		}
 | 
						|
		return printTemplate(&dsOfHistory.Spec.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
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
type StatefulSetHistoryViewer struct {
 | 
						|
	c kubernetes.Interface
 | 
						|
}
 | 
						|
 | 
						|
// ViewHistory returns a list of the revision history of a statefulset
 | 
						|
// TODO: this should be a describer
 | 
						|
// TODO: needs to implement detailed revision view
 | 
						|
func (h *StatefulSetHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
 | 
						|
	_, history, err := statefulSetHistory(h.c.AppsV1(), namespace, name)
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
 | 
						|
	if len(history) <= 0 {
 | 
						|
		return "No rollout history found.", nil
 | 
						|
	}
 | 
						|
	revisions := make([]int64, len(history))
 | 
						|
	for _, revision := range history {
 | 
						|
		revisions = append(revisions, revision.Revision)
 | 
						|
	}
 | 
						|
	sliceutil.SortInts64(revisions)
 | 
						|
 | 
						|
	return tabbedString(func(out io.Writer) error {
 | 
						|
		fmt.Fprintf(out, "REVISION\n")
 | 
						|
		for _, r := range revisions {
 | 
						|
			fmt.Fprintf(out, "%d\n", r)
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// controlledHistories returns all ControllerRevisions in namespace that selected by selector and owned by accessor
 | 
						|
// TODO: Rename this to controllerHistory when other controllers have been upgraded
 | 
						|
func controlledHistoryV1(
 | 
						|
	apps clientappsv1.AppsV1Interface,
 | 
						|
	namespace string,
 | 
						|
	selector labels.Selector,
 | 
						|
	accessor metav1.Object) ([]*appsv1.ControllerRevision, error) {
 | 
						|
	var result []*appsv1.ControllerRevision
 | 
						|
	historyList, err := apps.ControllerRevisions(namespace).List(metav1.ListOptions{LabelSelector: selector.String()})
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	for i := range historyList.Items {
 | 
						|
		history := historyList.Items[i]
 | 
						|
		// Only add history that belongs to the API object
 | 
						|
		if metav1.IsControlledBy(&history, accessor) {
 | 
						|
			result = append(result, &history)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return result, nil
 | 
						|
}
 | 
						|
 | 
						|
// controlledHistories returns all ControllerRevisions in namespace that selected by selector and owned by accessor
 | 
						|
func controlledHistory(
 | 
						|
	apps clientappsv1.AppsV1Interface,
 | 
						|
	namespace string,
 | 
						|
	selector labels.Selector,
 | 
						|
	accessor metav1.Object) ([]*appsv1.ControllerRevision, error) {
 | 
						|
	var result []*appsv1.ControllerRevision
 | 
						|
	historyList, err := apps.ControllerRevisions(namespace).List(metav1.ListOptions{LabelSelector: selector.String()})
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	for i := range historyList.Items {
 | 
						|
		history := historyList.Items[i]
 | 
						|
		// Only add history that belongs to the API object
 | 
						|
		if metav1.IsControlledBy(&history, accessor) {
 | 
						|
			result = append(result, &history)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return result, nil
 | 
						|
}
 | 
						|
 | 
						|
// daemonSetHistory returns the DaemonSet named name in namespace and all ControllerRevisions in its history.
 | 
						|
func daemonSetHistory(
 | 
						|
	apps clientappsv1.AppsV1Interface,
 | 
						|
	namespace, name string) (*appsv1.DaemonSet, []*appsv1.ControllerRevision, error) {
 | 
						|
	ds, err := apps.DaemonSets(namespace).Get(name, metav1.GetOptions{})
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("failed to retrieve DaemonSet %s: %v", name, err)
 | 
						|
	}
 | 
						|
	selector, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("failed to create selector for DaemonSet %s: %v", ds.Name, err)
 | 
						|
	}
 | 
						|
	accessor, err := meta.Accessor(ds)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("failed to create accessor for DaemonSet %s: %v", ds.Name, err)
 | 
						|
	}
 | 
						|
	history, err := controlledHistory(apps, ds.Namespace, selector, accessor)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("unable to find history controlled by DaemonSet %s: %v", ds.Name, err)
 | 
						|
	}
 | 
						|
	return ds, history, nil
 | 
						|
}
 | 
						|
 | 
						|
// statefulSetHistory returns the StatefulSet named name in namespace and all ControllerRevisions in its history.
 | 
						|
func statefulSetHistory(
 | 
						|
	apps clientappsv1.AppsV1Interface,
 | 
						|
	namespace, name string) (*appsv1.StatefulSet, []*appsv1.ControllerRevision, error) {
 | 
						|
	sts, err := apps.StatefulSets(namespace).Get(name, metav1.GetOptions{})
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("failed to retrieve Statefulset %s: %s", name, err.Error())
 | 
						|
	}
 | 
						|
	selector, err := metav1.LabelSelectorAsSelector(sts.Spec.Selector)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("failed to create selector for StatefulSet %s: %s", name, err.Error())
 | 
						|
	}
 | 
						|
	accessor, err := meta.Accessor(sts)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("failed to obtain accessor for StatefulSet %s: %s", name, err.Error())
 | 
						|
	}
 | 
						|
	history, err := controlledHistoryV1(apps, namespace, selector, accessor)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("unable to find history controlled by StatefulSet %s: %v", name, err)
 | 
						|
	}
 | 
						|
	return sts, history, nil
 | 
						|
}
 | 
						|
 | 
						|
// applyDaemonSetHistory returns a specific revision of DaemonSet by applying the given history to a copy of the given DaemonSet
 | 
						|
func applyDaemonSetHistory(ds *appsv1.DaemonSet, history *appsv1.ControllerRevision) (*appsv1.DaemonSet, error) {
 | 
						|
	clone := ds.DeepCopy()
 | 
						|
	cloneBytes, err := json.Marshal(clone)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	patched, err := strategicpatch.StrategicMergePatch(cloneBytes, history.Data.Raw, clone)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	err = json.Unmarshal(patched, clone)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return clone, nil
 | 
						|
}
 | 
						|
 | 
						|
// TODO: copied here until this becomes a describer
 | 
						|
func tabbedString(f func(io.Writer) error) (string, error) {
 | 
						|
	out := new(tabwriter.Writer)
 | 
						|
	buf := &bytes.Buffer{}
 | 
						|
	out.Init(buf, 0, 8, 2, ' ', 0)
 | 
						|
 | 
						|
	err := f(out)
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
 | 
						|
	out.Flush()
 | 
						|
	str := string(buf.String())
 | 
						|
	return str, nil
 | 
						|
}
 | 
						|
 | 
						|
// getChangeCause returns the change-cause annotation of the input object
 | 
						|
func getChangeCause(obj runtime.Object) string {
 | 
						|
	accessor, err := meta.Accessor(obj)
 | 
						|
	if err != nil {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	return accessor.GetAnnotations()[ChangeCauseAnnotation]
 | 
						|
}
 |