1
16
17 package upgrade
18
19 import (
20 "fmt"
21 "os"
22 "path/filepath"
23 "strings"
24 "time"
25
26 "github.com/pkg/errors"
27
28 utilerrors "k8s.io/apimachinery/pkg/util/errors"
29 "k8s.io/apimachinery/pkg/util/version"
30 clientset "k8s.io/client-go/kubernetes"
31 "k8s.io/klog/v2"
32
33 kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
34 "k8s.io/kubernetes/cmd/kubeadm/app/constants"
35 certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
36 "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/renewal"
37 "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
38 etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd"
39 kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
40 "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
41 dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
42 etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd"
43 "k8s.io/kubernetes/cmd/kubeadm/app/util/image"
44 "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
45 )
46
47
48 type StaticPodPathManager interface {
49
50 MoveFile(oldPath, newPath string) error
51
52 KubernetesDir() string
53
54 PatchesDir() string
55
56 RealManifestPath(component string) string
57
58 RealManifestDir() string
59
60 TempManifestPath(component string) string
61
62 TempManifestDir() string
63
64 BackupManifestPath(component string) string
65
66 BackupManifestDir() string
67
68 BackupEtcdDir() string
69
70 CleanupDirs() error
71 }
72
73
74 type KubeStaticPodPathManager struct {
75 kubernetesDir string
76 patchesDir string
77 realManifestDir string
78 tempManifestDir string
79 backupManifestDir string
80 backupEtcdDir string
81
82 keepManifestDir bool
83 keepEtcdDir bool
84 }
85
86
87 func NewKubeStaticPodPathManager(kubernetesDir, patchesDir, tempDir, backupDir, backupEtcdDir string, keepManifestDir, keepEtcdDir bool) StaticPodPathManager {
88 return &KubeStaticPodPathManager{
89 kubernetesDir: kubernetesDir,
90 patchesDir: patchesDir,
91 realManifestDir: filepath.Join(kubernetesDir, constants.ManifestsSubDirName),
92 tempManifestDir: tempDir,
93 backupManifestDir: backupDir,
94 backupEtcdDir: backupEtcdDir,
95 keepManifestDir: keepManifestDir,
96 keepEtcdDir: keepEtcdDir,
97 }
98 }
99
100
101 func NewKubeStaticPodPathManagerUsingTempDirs(kubernetesDir, patchesDir string, saveManifestsDir, saveEtcdDir bool) (StaticPodPathManager, error) {
102
103 upgradedManifestsDir, err := constants.CreateTempDirForKubeadm(kubernetesDir, "kubeadm-upgraded-manifests")
104 if err != nil {
105 return nil, err
106 }
107 backupManifestsDir, err := constants.CreateTimestampDirForKubeadm(kubernetesDir, "kubeadm-backup-manifests")
108 if err != nil {
109 return nil, err
110 }
111 backupEtcdDir, err := constants.CreateTimestampDirForKubeadm(kubernetesDir, "kubeadm-backup-etcd")
112 if err != nil {
113 return nil, err
114 }
115
116 return NewKubeStaticPodPathManager(kubernetesDir, patchesDir, upgradedManifestsDir, backupManifestsDir, backupEtcdDir, saveManifestsDir, saveEtcdDir), nil
117 }
118
119
120 func (spm *KubeStaticPodPathManager) MoveFile(oldPath, newPath string) error {
121 return kubeadmutil.MoveFile(oldPath, newPath)
122 }
123
124
125 func (spm *KubeStaticPodPathManager) KubernetesDir() string {
126 return spm.kubernetesDir
127 }
128
129
130 func (spm *KubeStaticPodPathManager) PatchesDir() string {
131 return spm.patchesDir
132 }
133
134
135 func (spm *KubeStaticPodPathManager) RealManifestPath(component string) string {
136 return constants.GetStaticPodFilepath(component, spm.realManifestDir)
137 }
138
139
140 func (spm *KubeStaticPodPathManager) RealManifestDir() string {
141 return spm.realManifestDir
142 }
143
144
145 func (spm *KubeStaticPodPathManager) TempManifestPath(component string) string {
146 return constants.GetStaticPodFilepath(component, spm.tempManifestDir)
147 }
148
149
150 func (spm *KubeStaticPodPathManager) TempManifestDir() string {
151 return spm.tempManifestDir
152 }
153
154
155 func (spm *KubeStaticPodPathManager) BackupManifestPath(component string) string {
156 return constants.GetStaticPodFilepath(component, spm.backupManifestDir)
157 }
158
159
160 func (spm *KubeStaticPodPathManager) BackupManifestDir() string {
161 return spm.backupManifestDir
162 }
163
164
165 func (spm *KubeStaticPodPathManager) BackupEtcdDir() string {
166 return spm.backupEtcdDir
167 }
168
169
170 func (spm *KubeStaticPodPathManager) CleanupDirs() error {
171 var errlist []error
172 if err := os.RemoveAll(spm.TempManifestDir()); err != nil {
173 errlist = append(errlist, err)
174 }
175 if !spm.keepManifestDir {
176 if err := os.RemoveAll(spm.BackupManifestDir()); err != nil {
177 errlist = append(errlist, err)
178 }
179 }
180
181 if !spm.keepEtcdDir {
182 if err := os.RemoveAll(spm.BackupEtcdDir()); err != nil {
183 errlist = append(errlist, err)
184 }
185 }
186
187 return utilerrors.NewAggregate(errlist)
188 }
189
190 func upgradeComponent(component string, certsRenewMgr *renewal.Manager, waiter apiclient.Waiter, pathMgr StaticPodPathManager, cfg *kubeadmapi.InitConfiguration, beforePodHash string, recoverManifests map[string]string) error {
191
192
193 recoverEtcd := false
194 if component == constants.Etcd {
195 recoverEtcd = true
196 }
197
198 fmt.Printf("[upgrade/staticpods] Preparing for %q upgrade\n", component)
199
200
201 currentManifestPath := pathMgr.RealManifestPath(component)
202
203 newManifestPath := pathMgr.TempManifestPath(component)
204
205
206 backupManifestPath := pathMgr.BackupManifestPath(component)
207
208
209 recoverManifests[component] = backupManifestPath
210
211
212 equal, diff, err := staticpod.ManifestFilesAreEqual(currentManifestPath, newManifestPath)
213 if err != nil {
214 return err
215 }
216 if equal {
217 fmt.Printf("[upgrade/staticpods] Current and new manifests of %s are equal, skipping upgrade\n", component)
218 return nil
219 } else {
220 klog.V(4).Infof("Pod manifest files diff:\n%s\n", diff)
221 }
222
223
224 if certsRenewMgr != nil {
225
226 if err := renewCertsByComponent(cfg, component, certsRenewMgr); err != nil {
227 return rollbackOldManifests(recoverManifests, errors.Wrapf(err, "failed to renew certificates for component %q", component), pathMgr, recoverEtcd)
228 }
229 }
230
231
232 if err := pathMgr.MoveFile(currentManifestPath, backupManifestPath); err != nil {
233 return rollbackOldManifests(recoverManifests, err, pathMgr, recoverEtcd)
234 }
235
236
237 if err := pathMgr.MoveFile(newManifestPath, currentManifestPath); err != nil {
238 return rollbackOldManifests(recoverManifests, err, pathMgr, recoverEtcd)
239 }
240
241 fmt.Printf("[upgrade/staticpods] Moved new manifest to %q and backed up old manifest to %q\n", currentManifestPath, backupManifestPath)
242
243 fmt.Println("[upgrade/staticpods] Waiting for the kubelet to restart the component")
244 fmt.Printf("[upgrade/staticpods] This can take up to %v\n", kubeadmapi.GetActiveTimeouts().UpgradeManifests.Duration)
245
246
247
248
249
250 if err := waiter.WaitForStaticPodHashChange(cfg.NodeRegistration.Name, component, beforePodHash); err != nil {
251 return rollbackOldManifests(recoverManifests, err, pathMgr, recoverEtcd)
252 }
253
254
255 if err := waiter.WaitForPodsWithLabel("component=" + component); err != nil {
256 return rollbackOldManifests(recoverManifests, err, pathMgr, recoverEtcd)
257 }
258
259 fmt.Printf("[upgrade/staticpods] Component %q upgraded successfully!\n", component)
260
261 return nil
262 }
263
264
265 func performEtcdStaticPodUpgrade(certsRenewMgr *renewal.Manager, client clientset.Interface, waiter apiclient.Waiter, pathMgr StaticPodPathManager, cfg *kubeadmapi.InitConfiguration, recoverManifests map[string]string, oldEtcdClient, newEtcdClient etcdutil.ClusterInterrogator) (bool, error) {
266
267 if cfg.Etcd.External != nil {
268 return false, errors.New("external etcd detected, won't try to change any etcd state")
269 }
270
271
272 err := oldEtcdClient.CheckClusterHealth()
273 if err != nil {
274 return true, errors.Wrap(err, "etcd cluster is not healthy")
275 }
276
277
278 backupEtcdDir := pathMgr.BackupEtcdDir()
279 runningEtcdDir := cfg.Etcd.Local.DataDir
280 output, err := kubeadmutil.CopyDir(runningEtcdDir, backupEtcdDir)
281 if err != nil {
282 return true, errors.Wrapf(err, "failed to back up etcd data, output: %q", output)
283 }
284
285
286
287 var desiredEtcdVersion *version.Version
288 if cfg.Etcd.Local.ImageTag != "" {
289 desiredEtcdVersion, err = version.ParseSemantic(
290 convertImageTagMetadataToSemver(cfg.Etcd.Local.ImageTag))
291 if err != nil {
292 return true, errors.Wrapf(err, "failed to parse tag %q as a semantic version", cfg.Etcd.Local.ImageTag)
293 }
294 } else {
295
296 var warning error
297 desiredEtcdVersion, warning, err = constants.EtcdSupportedVersion(constants.SupportedEtcdVersion, cfg.KubernetesVersion)
298 if err != nil {
299 return true, errors.Wrap(err, "failed to retrieve an etcd version for the target Kubernetes version")
300 }
301 if warning != nil {
302 klog.V(1).Infof("[upgrade/etcd] WARNING: %v", warning)
303 }
304 }
305
306
307 currentEtcdVersionStr, err := GetEtcdImageTagFromStaticPod(pathMgr.RealManifestDir())
308 if err != nil {
309 return true, errors.Wrap(err, "failed to retrieve the current etcd version")
310 }
311
312 cmpResult, err := desiredEtcdVersion.Compare(currentEtcdVersionStr)
313 if err != nil {
314 return true, errors.Wrapf(err, "failed comparing the current etcd version %q to the desired one %q", currentEtcdVersionStr, desiredEtcdVersion)
315 }
316 if cmpResult < 0 {
317 return false, errors.Errorf("the desired etcd version %q is older than the currently installed %q. Skipping etcd upgrade", desiredEtcdVersion, currentEtcdVersionStr)
318 }
319
320 beforeEtcdPodHash, err := waiter.WaitForStaticPodSingleHash(cfg.NodeRegistration.Name, constants.Etcd)
321 if err != nil {
322 return true, err
323 }
324
325
326
327 if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(pathMgr.TempManifestDir(), pathMgr.PatchesDir(), cfg.NodeRegistration.Name, &cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint, false ); err != nil {
328 return true, errors.Wrap(err, "error creating local etcd static pod manifest file")
329 }
330
331 retries := 10
332 retryInterval := 15 * time.Second
333
334
335 if err := upgradeComponent(constants.Etcd, certsRenewMgr, waiter, pathMgr, cfg, beforeEtcdPodHash, recoverManifests); err != nil {
336 fmt.Printf("[upgrade/etcd] Failed to upgrade etcd: %v\n", err)
337
338
339 fmt.Println("[upgrade/etcd] Waiting for previous etcd to become available")
340 if _, err := oldEtcdClient.WaitForClusterAvailable(retries, retryInterval); err != nil {
341 fmt.Printf("[upgrade/etcd] Failed to healthcheck previous etcd: %v\n", err)
342
343
344 fmt.Println("[upgrade/etcd] Rolling back etcd data")
345 if err := rollbackEtcdData(cfg, pathMgr); err != nil {
346
347 return true, errors.Errorf("fatal error rolling back local etcd cluster datadir: %v, the backup of etcd database is stored here:(%s)", err, backupEtcdDir)
348 }
349 fmt.Println("[upgrade/etcd] Etcd data rollback successful")
350
351
352 fmt.Println("[upgrade/etcd] Waiting for previous etcd to become available")
353 if _, err := oldEtcdClient.WaitForClusterAvailable(retries, retryInterval); err != nil {
354 fmt.Printf("[upgrade/etcd] Failed to healthcheck previous etcd: %v\n", err)
355
356 return true, errors.Wrapf(err, "fatal error rolling back local etcd cluster manifest, the backup of etcd database is stored here:(%s)", backupEtcdDir)
357 }
358
359
360 }
361 fmt.Println("[upgrade/etcd] Etcd was rolled back and is now available")
362
363
364 return true, errors.Wrap(err, "fatal error when trying to upgrade the etcd cluster, rolled the state back to pre-upgrade state")
365 }
366
367
368 if newEtcdClient == nil {
369 etcdClient, err := etcdutil.NewFromCluster(client, cfg.CertificatesDir)
370 if err != nil {
371 return true, errors.Wrap(err, "fatal error creating etcd client")
372 }
373 newEtcdClient = etcdClient
374 }
375
376
377 fmt.Println("[upgrade/etcd] Waiting for etcd to become available")
378 if _, err = newEtcdClient.WaitForClusterAvailable(retries, retryInterval); err != nil {
379 fmt.Printf("[upgrade/etcd] Failed to healthcheck etcd: %v\n", err)
380
381
382 fmt.Println("[upgrade/etcd] Rolling back etcd data")
383 if err := rollbackEtcdData(cfg, pathMgr); err != nil {
384
385 return true, errors.Wrapf(err, "fatal error rolling back local etcd cluster datadir, the backup of etcd database is stored here:(%s)", backupEtcdDir)
386 }
387 fmt.Println("[upgrade/etcd] Etcd data rollback successful")
388
389
390 fmt.Println("[upgrade/etcd] Rolling back etcd manifest")
391 rollbackOldManifests(recoverManifests, err, pathMgr, true)
392
393
394
395 fmt.Println("[upgrade/etcd] Waiting for previous etcd to become available")
396 if _, err := oldEtcdClient.WaitForClusterAvailable(retries, retryInterval); err != nil {
397 fmt.Printf("[upgrade/etcd] Failed to healthcheck previous etcd: %v\n", err)
398
399 return true, errors.Wrapf(err, "fatal error rolling back local etcd cluster manifest, the backup of etcd database is stored here:(%s)", backupEtcdDir)
400 }
401 fmt.Println("[upgrade/etcd] Etcd was rolled back and is now available")
402
403
404 return true, errors.Wrap(err, "fatal error upgrading local etcd cluster, rolled the state back to pre-upgrade state")
405 }
406
407 return false, nil
408 }
409
410
411 func StaticPodControlPlane(client clientset.Interface, waiter apiclient.Waiter, pathMgr StaticPodPathManager, cfg *kubeadmapi.InitConfiguration, etcdUpgrade, renewCerts bool, oldEtcdClient, newEtcdClient etcdutil.ClusterInterrogator) error {
412 recoverManifests := map[string]string{}
413 var isExternalEtcd bool
414
415 beforePodHashMap, err := waiter.WaitForStaticPodControlPlaneHashes(cfg.NodeRegistration.Name)
416 if err != nil {
417 return err
418 }
419
420 if oldEtcdClient == nil {
421 if cfg.Etcd.External != nil {
422
423 isExternalEtcd = true
424 etcdClient, err := etcdutil.New(
425 cfg.Etcd.External.Endpoints,
426 cfg.Etcd.External.CAFile,
427 cfg.Etcd.External.CertFile,
428 cfg.Etcd.External.KeyFile,
429 )
430 if err != nil {
431 return errors.Wrap(err, "failed to create etcd client for external etcd")
432 }
433 oldEtcdClient = etcdClient
434
435 if newEtcdClient == nil {
436 newEtcdClient = etcdClient
437 }
438 } else {
439
440 etcdClient, err := etcdutil.NewFromCluster(client, cfg.CertificatesDir)
441 if err != nil {
442 return errors.Wrap(err, "failed to create etcd client")
443 }
444 oldEtcdClient = etcdClient
445 }
446 }
447
448 var certsRenewMgr *renewal.Manager
449 if renewCerts {
450 certsRenewMgr, err = renewal.NewManager(&cfg.ClusterConfiguration, pathMgr.KubernetesDir())
451 if err != nil {
452 return errors.Wrap(err, "failed to create the certificate renewal manager")
453 }
454 }
455
456
457 if !isExternalEtcd && etcdUpgrade {
458
459 fmt.Printf("[upgrade/etcd] Upgrading to TLS for %s\n", constants.Etcd)
460
461
462 fatal, err := performEtcdStaticPodUpgrade(certsRenewMgr, client, waiter, pathMgr, cfg, recoverManifests, oldEtcdClient, newEtcdClient)
463 if err != nil {
464 if fatal {
465 return err
466 }
467 fmt.Printf("[upgrade/etcd] Non fatal issue encountered during upgrade: %v\n", err)
468 }
469 }
470
471
472 fmt.Printf("[upgrade/staticpods] Writing new Static Pod manifests to %q\n", pathMgr.TempManifestDir())
473 err = controlplane.CreateInitStaticPodManifestFiles(pathMgr.TempManifestDir(), pathMgr.PatchesDir(), cfg, false )
474 if err != nil {
475 return errors.Wrap(err, "error creating init static pod manifest files")
476 }
477
478 for _, component := range constants.ControlPlaneComponents {
479 if err = upgradeComponent(component, certsRenewMgr, waiter, pathMgr, cfg, beforePodHashMap[component], recoverManifests); err != nil {
480 return err
481 }
482 }
483
484 if renewCerts {
485
486 renewed, err := certsRenewMgr.RenewUsingLocalCA(constants.AdminKubeConfigFileName)
487 if err != nil {
488 return rollbackOldManifests(recoverManifests, errors.Wrapf(err, "failed to upgrade the %s certificates", constants.AdminKubeConfigFileName), pathMgr, false)
489 }
490
491 if !renewed {
492
493 fmt.Printf("[upgrade/staticpods] External CA detected, %s certificate can't be renewed\n", constants.AdminKubeConfigFileName)
494 }
495
496
497 if _, err := os.Stat(filepath.Join(pathMgr.KubernetesDir(), constants.SuperAdminKubeConfigFileName)); err == nil {
498
499 renewed, err := certsRenewMgr.RenewUsingLocalCA(constants.SuperAdminKubeConfigFileName)
500 if err != nil {
501 return rollbackOldManifests(recoverManifests, errors.Wrapf(err, "failed to upgrade the %s certificates", constants.SuperAdminKubeConfigFileName), pathMgr, false)
502 }
503
504 if !renewed {
505
506 fmt.Printf("[upgrade/staticpods] External CA detected, %s certificate can't be renewed\n", constants.SuperAdminKubeConfigFileName)
507 }
508 }
509 }
510
511
512
513
514 return pathMgr.CleanupDirs()
515 }
516
517
518
519 func rollbackOldManifests(oldManifests map[string]string, origErr error, pathMgr StaticPodPathManager, restoreEtcd bool) error {
520 errs := []error{origErr}
521 for component, backupPath := range oldManifests {
522
523 if component == constants.Etcd && !restoreEtcd {
524 continue
525 }
526
527 realManifestPath := pathMgr.RealManifestPath(component)
528
529
530 err := pathMgr.MoveFile(backupPath, realManifestPath)
531 if err != nil {
532 errs = append(errs, err)
533 }
534 }
535
536 return errors.Wrap(utilerrors.NewAggregate(errs),
537 "couldn't upgrade control plane. kubeadm has tried to recover everything into the earlier state. Errors faced")
538 }
539
540
541
542 func rollbackEtcdData(cfg *kubeadmapi.InitConfiguration, pathMgr StaticPodPathManager) error {
543 backupEtcdDir := pathMgr.BackupEtcdDir()
544 runningEtcdDir := cfg.Etcd.Local.DataDir
545
546 output, err := kubeadmutil.CopyDir(backupEtcdDir, runningEtcdDir)
547 if err != nil {
548
549 return errors.Wrapf(err, "couldn't recover etcd database with error, the location of etcd backup: %s, output: %q", backupEtcdDir, output)
550 }
551
552 return nil
553 }
554
555
556
557 func renewCertsByComponent(cfg *kubeadmapi.InitConfiguration, component string, certsRenewMgr *renewal.Manager) error {
558 var certificates []string
559
560
561 if component == constants.Etcd {
562 if cfg.Etcd.Local != nil {
563 certificates = []string{
564 certsphase.KubeadmCertEtcdServer().Name,
565 certsphase.KubeadmCertEtcdPeer().Name,
566 certsphase.KubeadmCertEtcdHealthcheck().Name,
567 }
568 }
569 }
570
571
572
573 if component == constants.KubeAPIServer {
574 certificates = []string{
575 certsphase.KubeadmCertAPIServer().Name,
576 certsphase.KubeadmCertKubeletClient().Name,
577 certsphase.KubeadmCertFrontProxyClient().Name,
578 }
579 if cfg.Etcd.Local != nil {
580 certificates = append(certificates, certsphase.KubeadmCertEtcdAPIClient().Name)
581 }
582 }
583
584
585 if component == constants.KubeControllerManager {
586 certificates = []string{
587 constants.ControllerManagerKubeConfigFileName,
588 }
589 }
590
591
592 if component == constants.KubeScheduler {
593 certificates = []string{
594 constants.SchedulerKubeConfigFileName,
595 }
596 }
597
598
599 for _, cert := range certificates {
600 fmt.Printf("[upgrade/staticpods] Renewing %s certificate\n", cert)
601 renewed, err := certsRenewMgr.RenewUsingLocalCA(cert)
602 if err != nil {
603 return err
604 }
605 if !renewed {
606
607 fmt.Printf("[upgrade/staticpods] External CA detected, %s certificate can't be renewed\n", cert)
608 }
609 }
610
611 return nil
612 }
613
614
615 func GetPathManagerForUpgrade(kubernetesDir, patchesDir string, internalcfg *kubeadmapi.InitConfiguration, etcdUpgrade bool) (StaticPodPathManager, error) {
616 isExternalEtcd := internalcfg.Etcd.External != nil
617 return NewKubeStaticPodPathManagerUsingTempDirs(kubernetesDir, patchesDir, true, etcdUpgrade && !isExternalEtcd)
618 }
619
620
621 func PerformStaticPodUpgrade(client clientset.Interface, waiter apiclient.Waiter, internalcfg *kubeadmapi.InitConfiguration, etcdUpgrade, renewCerts bool, patchesDir string) error {
622 pathManager, err := GetPathManagerForUpgrade(constants.KubernetesDir, patchesDir, internalcfg, etcdUpgrade)
623 if err != nil {
624 return err
625 }
626
627
628 return StaticPodControlPlane(client, waiter, pathManager, internalcfg, etcdUpgrade, renewCerts, nil, nil)
629 }
630
631
632 func DryRunStaticPodUpgrade(patchesDir string, internalcfg *kubeadmapi.InitConfiguration) error {
633
634 dryRunManifestDir, err := constants.CreateTempDirForKubeadm("", "kubeadm-upgrade-dryrun")
635 if err != nil {
636 return err
637 }
638 defer os.RemoveAll(dryRunManifestDir)
639 if err := controlplane.CreateInitStaticPodManifestFiles(dryRunManifestDir, patchesDir, internalcfg, true ); err != nil {
640 return err
641 }
642
643
644 files := []dryrunutil.FileToPrint{}
645 for _, component := range constants.ControlPlaneComponents {
646 realPath := constants.GetStaticPodFilepath(component, dryRunManifestDir)
647 outputPath := constants.GetStaticPodFilepath(component, constants.GetStaticPodDirectory())
648 files = append(files, dryrunutil.NewFileToPrint(realPath, outputPath))
649 }
650
651 return dryrunutil.PrintDryRunFiles(files, os.Stdout)
652 }
653
654
655 func GetEtcdImageTagFromStaticPod(manifestDir string) (string, error) {
656 realPath := constants.GetStaticPodFilepath(constants.Etcd, manifestDir)
657 pod, err := staticpod.ReadStaticPodFromDisk(realPath)
658 if err != nil {
659 return "", err
660 }
661
662 return convertImageTagMetadataToSemver(image.TagFromImage(pod.Spec.Containers[0].Image)), nil
663 }
664
665
666 func convertImageTagMetadataToSemver(tag string) string {
667
668
669
670
671 return strings.Replace(tag, "_", "+", 1)
672 }
673
View as plain text