history.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package kubectl
  14. import (
  15. "bytes"
  16. "fmt"
  17. "io"
  18. "text/tabwriter"
  19. appsv1 "k8s.io/api/apps/v1"
  20. corev1 "k8s.io/api/core/v1"
  21. "k8s.io/apimachinery/pkg/api/meta"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/labels"
  24. "k8s.io/apimachinery/pkg/runtime"
  25. "k8s.io/apimachinery/pkg/runtime/schema"
  26. "k8s.io/apimachinery/pkg/util/json"
  27. "k8s.io/apimachinery/pkg/util/strategicpatch"
  28. "k8s.io/client-go/kubernetes"
  29. clientappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
  30. kapps "k8s.io/kubernetes/pkg/kubectl/apps"
  31. describe "k8s.io/kubernetes/pkg/kubectl/describe/versioned"
  32. deploymentutil "k8s.io/kubernetes/pkg/kubectl/util/deployment"
  33. sliceutil "k8s.io/kubernetes/pkg/kubectl/util/slice"
  34. )
  35. const (
  36. ChangeCauseAnnotation = "kubernetes.io/change-cause"
  37. )
  38. // HistoryViewer provides an interface for resources have historical information.
  39. type HistoryViewer interface {
  40. ViewHistory(namespace, name string, revision int64) (string, error)
  41. }
  42. type HistoryVisitor struct {
  43. clientset kubernetes.Interface
  44. result HistoryViewer
  45. }
  46. func (v *HistoryVisitor) VisitDeployment(elem kapps.GroupKindElement) {
  47. v.result = &DeploymentHistoryViewer{v.clientset}
  48. }
  49. func (v *HistoryVisitor) VisitStatefulSet(kind kapps.GroupKindElement) {
  50. v.result = &StatefulSetHistoryViewer{v.clientset}
  51. }
  52. func (v *HistoryVisitor) VisitDaemonSet(kind kapps.GroupKindElement) {
  53. v.result = &DaemonSetHistoryViewer{v.clientset}
  54. }
  55. func (v *HistoryVisitor) VisitJob(kind kapps.GroupKindElement) {}
  56. func (v *HistoryVisitor) VisitPod(kind kapps.GroupKindElement) {}
  57. func (v *HistoryVisitor) VisitReplicaSet(kind kapps.GroupKindElement) {}
  58. func (v *HistoryVisitor) VisitReplicationController(kind kapps.GroupKindElement) {}
  59. func (v *HistoryVisitor) VisitCronJob(kind kapps.GroupKindElement) {}
  60. // HistoryViewerFor returns an implementation of HistoryViewer interface for the given schema kind
  61. func HistoryViewerFor(kind schema.GroupKind, c kubernetes.Interface) (HistoryViewer, error) {
  62. elem := kapps.GroupKindElement(kind)
  63. visitor := &HistoryVisitor{
  64. clientset: c,
  65. }
  66. // Determine which HistoryViewer we need here
  67. err := elem.Accept(visitor)
  68. if err != nil {
  69. return nil, fmt.Errorf("error retrieving history for %q, %v", kind.String(), err)
  70. }
  71. if visitor.result == nil {
  72. return nil, fmt.Errorf("no history viewer has been implemented for %q", kind.String())
  73. }
  74. return visitor.result, nil
  75. }
  76. type DeploymentHistoryViewer struct {
  77. c kubernetes.Interface
  78. }
  79. // ViewHistory returns a revision-to-replicaset map as the revision history of a deployment
  80. // TODO: this should be a describer
  81. func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
  82. versionedAppsClient := h.c.AppsV1()
  83. deployment, err := versionedAppsClient.Deployments(namespace).Get(name, metav1.GetOptions{})
  84. if err != nil {
  85. return "", fmt.Errorf("failed to retrieve deployment %s: %v", name, err)
  86. }
  87. _, allOldRSs, newRS, err := deploymentutil.GetAllReplicaSets(deployment, versionedAppsClient)
  88. if err != nil {
  89. return "", fmt.Errorf("failed to retrieve replica sets from deployment %s: %v", name, err)
  90. }
  91. allRSs := allOldRSs
  92. if newRS != nil {
  93. allRSs = append(allRSs, newRS)
  94. }
  95. historyInfo := make(map[int64]*corev1.PodTemplateSpec)
  96. for _, rs := range allRSs {
  97. v, err := deploymentutil.Revision(rs)
  98. if err != nil {
  99. continue
  100. }
  101. historyInfo[v] = &rs.Spec.Template
  102. changeCause := getChangeCause(rs)
  103. if historyInfo[v].Annotations == nil {
  104. historyInfo[v].Annotations = make(map[string]string)
  105. }
  106. if len(changeCause) > 0 {
  107. historyInfo[v].Annotations[ChangeCauseAnnotation] = changeCause
  108. }
  109. }
  110. if len(historyInfo) == 0 {
  111. return "No rollout history found.", nil
  112. }
  113. if revision > 0 {
  114. // Print details of a specific revision
  115. template, ok := historyInfo[revision]
  116. if !ok {
  117. return "", fmt.Errorf("unable to find the specified revision")
  118. }
  119. return printTemplate(template)
  120. }
  121. // Sort the revisionToChangeCause map by revision
  122. revisions := make([]int64, 0, len(historyInfo))
  123. for r := range historyInfo {
  124. revisions = append(revisions, r)
  125. }
  126. sliceutil.SortInts64(revisions)
  127. return tabbedString(func(out io.Writer) error {
  128. fmt.Fprintf(out, "REVISION\tCHANGE-CAUSE\n")
  129. for _, r := range revisions {
  130. // Find the change-cause of revision r
  131. changeCause := historyInfo[r].Annotations[ChangeCauseAnnotation]
  132. if len(changeCause) == 0 {
  133. changeCause = "<none>"
  134. }
  135. fmt.Fprintf(out, "%d\t%s\n", r, changeCause)
  136. }
  137. return nil
  138. })
  139. }
  140. func printTemplate(template *corev1.PodTemplateSpec) (string, error) {
  141. buf := bytes.NewBuffer([]byte{})
  142. w := describe.NewPrefixWriter(buf)
  143. describe.DescribePodTemplate(template, w)
  144. return buf.String(), nil
  145. }
  146. type DaemonSetHistoryViewer struct {
  147. c kubernetes.Interface
  148. }
  149. // ViewHistory returns a revision-to-history map as the revision history of a deployment
  150. // TODO: this should be a describer
  151. func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
  152. ds, history, err := daemonSetHistory(h.c.AppsV1(), namespace, name)
  153. if err != nil {
  154. return "", err
  155. }
  156. historyInfo := make(map[int64]*appsv1.ControllerRevision)
  157. for _, history := range history {
  158. // TODO: for now we assume revisions don't overlap, we may need to handle it
  159. historyInfo[history.Revision] = history
  160. }
  161. if len(historyInfo) == 0 {
  162. return "No rollout history found.", nil
  163. }
  164. // Print details of a specific revision
  165. if revision > 0 {
  166. history, ok := historyInfo[revision]
  167. if !ok {
  168. return "", fmt.Errorf("unable to find the specified revision")
  169. }
  170. dsOfHistory, err := applyDaemonSetHistory(ds, history)
  171. if err != nil {
  172. return "", fmt.Errorf("unable to parse history %s", history.Name)
  173. }
  174. return printTemplate(&dsOfHistory.Spec.Template)
  175. }
  176. // Print an overview of all Revisions
  177. // Sort the revisionToChangeCause map by revision
  178. revisions := make([]int64, 0, len(historyInfo))
  179. for r := range historyInfo {
  180. revisions = append(revisions, r)
  181. }
  182. sliceutil.SortInts64(revisions)
  183. return tabbedString(func(out io.Writer) error {
  184. fmt.Fprintf(out, "REVISION\tCHANGE-CAUSE\n")
  185. for _, r := range revisions {
  186. // Find the change-cause of revision r
  187. changeCause := historyInfo[r].Annotations[ChangeCauseAnnotation]
  188. if len(changeCause) == 0 {
  189. changeCause = "<none>"
  190. }
  191. fmt.Fprintf(out, "%d\t%s\n", r, changeCause)
  192. }
  193. return nil
  194. })
  195. }
  196. type StatefulSetHistoryViewer struct {
  197. c kubernetes.Interface
  198. }
  199. // ViewHistory returns a list of the revision history of a statefulset
  200. // TODO: this should be a describer
  201. // TODO: needs to implement detailed revision view
  202. func (h *StatefulSetHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
  203. _, history, err := statefulSetHistory(h.c.AppsV1(), namespace, name)
  204. if err != nil {
  205. return "", err
  206. }
  207. if len(history) <= 0 {
  208. return "No rollout history found.", nil
  209. }
  210. revisions := make([]int64, len(history))
  211. for _, revision := range history {
  212. revisions = append(revisions, revision.Revision)
  213. }
  214. sliceutil.SortInts64(revisions)
  215. return tabbedString(func(out io.Writer) error {
  216. fmt.Fprintf(out, "REVISION\n")
  217. for _, r := range revisions {
  218. fmt.Fprintf(out, "%d\n", r)
  219. }
  220. return nil
  221. })
  222. }
  223. // controlledHistories returns all ControllerRevisions in namespace that selected by selector and owned by accessor
  224. // TODO: Rename this to controllerHistory when other controllers have been upgraded
  225. func controlledHistoryV1(
  226. apps clientappsv1.AppsV1Interface,
  227. namespace string,
  228. selector labels.Selector,
  229. accessor metav1.Object) ([]*appsv1.ControllerRevision, error) {
  230. var result []*appsv1.ControllerRevision
  231. historyList, err := apps.ControllerRevisions(namespace).List(metav1.ListOptions{LabelSelector: selector.String()})
  232. if err != nil {
  233. return nil, err
  234. }
  235. for i := range historyList.Items {
  236. history := historyList.Items[i]
  237. // Only add history that belongs to the API object
  238. if metav1.IsControlledBy(&history, accessor) {
  239. result = append(result, &history)
  240. }
  241. }
  242. return result, nil
  243. }
  244. // controlledHistories returns all ControllerRevisions in namespace that selected by selector and owned by accessor
  245. func controlledHistory(
  246. apps clientappsv1.AppsV1Interface,
  247. namespace string,
  248. selector labels.Selector,
  249. accessor metav1.Object) ([]*appsv1.ControllerRevision, error) {
  250. var result []*appsv1.ControllerRevision
  251. historyList, err := apps.ControllerRevisions(namespace).List(metav1.ListOptions{LabelSelector: selector.String()})
  252. if err != nil {
  253. return nil, err
  254. }
  255. for i := range historyList.Items {
  256. history := historyList.Items[i]
  257. // Only add history that belongs to the API object
  258. if metav1.IsControlledBy(&history, accessor) {
  259. result = append(result, &history)
  260. }
  261. }
  262. return result, nil
  263. }
  264. // daemonSetHistory returns the DaemonSet named name in namespace and all ControllerRevisions in its history.
  265. func daemonSetHistory(
  266. apps clientappsv1.AppsV1Interface,
  267. namespace, name string) (*appsv1.DaemonSet, []*appsv1.ControllerRevision, error) {
  268. ds, err := apps.DaemonSets(namespace).Get(name, metav1.GetOptions{})
  269. if err != nil {
  270. return nil, nil, fmt.Errorf("failed to retrieve DaemonSet %s: %v", name, err)
  271. }
  272. selector, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
  273. if err != nil {
  274. return nil, nil, fmt.Errorf("failed to create selector for DaemonSet %s: %v", ds.Name, err)
  275. }
  276. accessor, err := meta.Accessor(ds)
  277. if err != nil {
  278. return nil, nil, fmt.Errorf("failed to create accessor for DaemonSet %s: %v", ds.Name, err)
  279. }
  280. history, err := controlledHistory(apps, ds.Namespace, selector, accessor)
  281. if err != nil {
  282. return nil, nil, fmt.Errorf("unable to find history controlled by DaemonSet %s: %v", ds.Name, err)
  283. }
  284. return ds, history, nil
  285. }
  286. // statefulSetHistory returns the StatefulSet named name in namespace and all ControllerRevisions in its history.
  287. func statefulSetHistory(
  288. apps clientappsv1.AppsV1Interface,
  289. namespace, name string) (*appsv1.StatefulSet, []*appsv1.ControllerRevision, error) {
  290. sts, err := apps.StatefulSets(namespace).Get(name, metav1.GetOptions{})
  291. if err != nil {
  292. return nil, nil, fmt.Errorf("failed to retrieve Statefulset %s: %s", name, err.Error())
  293. }
  294. selector, err := metav1.LabelSelectorAsSelector(sts.Spec.Selector)
  295. if err != nil {
  296. return nil, nil, fmt.Errorf("failed to create selector for StatefulSet %s: %s", name, err.Error())
  297. }
  298. accessor, err := meta.Accessor(sts)
  299. if err != nil {
  300. return nil, nil, fmt.Errorf("failed to obtain accessor for StatefulSet %s: %s", name, err.Error())
  301. }
  302. history, err := controlledHistoryV1(apps, namespace, selector, accessor)
  303. if err != nil {
  304. return nil, nil, fmt.Errorf("unable to find history controlled by StatefulSet %s: %v", name, err)
  305. }
  306. return sts, history, nil
  307. }
  308. // applyDaemonSetHistory returns a specific revision of DaemonSet by applying the given history to a copy of the given DaemonSet
  309. func applyDaemonSetHistory(ds *appsv1.DaemonSet, history *appsv1.ControllerRevision) (*appsv1.DaemonSet, error) {
  310. clone := ds.DeepCopy()
  311. cloneBytes, err := json.Marshal(clone)
  312. if err != nil {
  313. return nil, err
  314. }
  315. patched, err := strategicpatch.StrategicMergePatch(cloneBytes, history.Data.Raw, clone)
  316. if err != nil {
  317. return nil, err
  318. }
  319. err = json.Unmarshal(patched, clone)
  320. if err != nil {
  321. return nil, err
  322. }
  323. return clone, nil
  324. }
  325. // TODO: copied here until this becomes a describer
  326. func tabbedString(f func(io.Writer) error) (string, error) {
  327. out := new(tabwriter.Writer)
  328. buf := &bytes.Buffer{}
  329. out.Init(buf, 0, 8, 2, ' ', 0)
  330. err := f(out)
  331. if err != nil {
  332. return "", err
  333. }
  334. out.Flush()
  335. str := string(buf.String())
  336. return str, nil
  337. }
  338. // getChangeCause returns the change-cause annotation of the input object
  339. func getChangeCause(obj runtime.Object) string {
  340. accessor, err := meta.Accessor(obj)
  341. if err != nil {
  342. return ""
  343. }
  344. return accessor.GetAnnotations()[ChangeCauseAnnotation]
  345. }