...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package preflight
16
17 import (
18 "context"
19 "fmt"
20
21 "github.com/GoogleCloudPlatform/k8s-config-connector/operator/pkg/k8s"
22 "github.com/GoogleCloudPlatform/k8s-config-connector/operator/pkg/manifest"
23
24 "github.com/blang/semver"
25 corev1 "k8s.io/api/core/v1"
26 apierrors "k8s.io/apimachinery/pkg/api/errors"
27 "k8s.io/apimachinery/pkg/types"
28 ctrl "sigs.k8s.io/controller-runtime"
29 "sigs.k8s.io/controller-runtime/pkg/client"
30 "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative"
31 )
32
33 var (
34 ulog = ctrl.Log.WithName("UpgradeChecker")
35 )
36
37
38
39
40 func NewUpgradeChecker(client client.Client, repo manifest.Repository) *UpgradeChecker {
41 return &UpgradeChecker{client: client, repo: repo}
42 }
43
44 type UpgradeChecker struct {
45 client client.Client
46 repo manifest.Repository
47 }
48
49 func (u *UpgradeChecker) Preflight(ctx context.Context, o declarative.DeclarativeObject) error {
50 ulog.Info("preflight check before reconciling the object", "kind", o.GetObjectKind().GroupVersionKind().Kind, "name", o.GetName(), "namespace", o.GetNamespace())
51 if !o.GetDeletionTimestamp().IsZero() {
52 return nil
53 }
54 ns := &corev1.Namespace{}
55 if err := u.client.Get(ctx, types.NamespacedName{Name: k8s.CNRMSystemNamespace}, ns); err != nil {
56 if apierrors.IsNotFound(err) {
57 ulog.Info(fmt.Sprintf("%v namespace is not found. Continue the reconciliation.", k8s.CNRMSystemNamespace))
58 return nil
59 }
60 return err
61 }
62
63 currentVersionRaw := ns.GetAnnotations()[k8s.VersionAnnotation]
64 if currentVersionRaw == "" {
65 ulog.Info(fmt.Sprintf("WARNING: No ConfigConnector version is annotated with '%v' namespace. Attempt to deploy ConfigConnector bundle with best effort", k8s.CNRMSystemNamespace))
66 return nil
67 }
68
69 channel, err := u.repo.LoadChannel(ctx, k8s.StableChannel)
70 if err != nil {
71 return fmt.Errorf("preflight check failed loading the channel %v: %v", k8s.StableChannel, err)
72 }
73 version, err := channel.Latest(ctx, k8s.ConfigConnectorComponentName)
74 if err != nil {
75 return fmt.Errorf("preflight check failed resolving the version to deploy: %v", err)
76 }
77 if version == nil {
78 return fmt.Errorf("could not find the latest version in channel %v", k8s.StableChannel)
79 }
80 versionToDeployRaw := version.Version
81 currentVersion, err := semver.ParseTolerant(currentVersionRaw)
82 if err != nil {
83 return fmt.Errorf("current version %v is not a valid semantic version: %v", currentVersionRaw, err)
84 }
85 ulog.Info("Checking version", "current version", currentVersion)
86 versionToDeploy, err := semver.ParseTolerant(versionToDeployRaw)
87 if err != nil {
88 return fmt.Errorf("the version to deploy %v is not a valid semantic version: %v", versionToDeployRaw, err)
89 }
90 ulog.Info("Checking version", "version to deploy", versionToDeploy)
91 if compareMajorOnly(currentVersion, versionToDeploy) != 0 {
92 return fmt.Errorf("incompatible version: stop reconciling the existing ConfigConnector of version %v to version %v since it's a major version change. Please kubectl delete the existing ConfigConnector object and recreate it", currentVersion, versionToDeploy)
93 }
94 return nil
95 }
96
97 func compareMajorOnly(v, w semver.Version) int {
98 if v.Major != w.Major {
99 if v.Major > w.Major {
100 return 1
101 }
102 return -1
103 }
104 return 0
105 }
106
View as plain text