Skip to content

Commit 93836bc

Browse files
committed
(feat) block cluster upgrade when installed incompatible operators
1 parent dd9a588 commit 93836bc

File tree

8 files changed

+369
-11
lines changed

8 files changed

+369
-11
lines changed

cmd/cluster-olm-operator/main.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"k8s.io/component-base/cli"
2121
utilflag "k8s.io/component-base/cli/flag"
2222

23+
"github.com/openshift/cluster-olm-operator/internal/utils"
2324
"github.com/openshift/cluster-olm-operator/pkg/clients"
2425
"github.com/openshift/cluster-olm-operator/pkg/controller"
2526
"github.com/openshift/cluster-olm-operator/pkg/version"
@@ -105,13 +106,28 @@ func runOperator(ctx context.Context, cc *controllercmd.ControllerContext) error
105106
deploymentControllerList = append(deploymentControllerList, controller)
106107
}
107108

109+
operatorImageVersion := status.VersionForOperatorFromEnv()
110+
nextOCPMinorVersion, err := utils.GetNextOCPMinorVersion(operatorImageVersion)
111+
if err != nil {
112+
return err
113+
}
114+
108115
upgradeableConditionController := controller.NewStaticUpgradeableConditionController(
109116
"OLMStaticUpgradeableConditionController",
110117
cl.OperatorClient,
111118
cc.EventRecorder.ForComponent("OLMStaticUpgradeableConditionController"),
112119
controllerNames,
113120
)
114121

122+
incompatibleOperatorController := controller.NewIncompatibleOperatorController(
123+
"OLMIncompatibleOperatorController",
124+
nextOCPMinorVersion,
125+
cl.KubeClient,
126+
cl.ClusterExtensionClient,
127+
cl.OperatorClient,
128+
cc.EventRecorder.ForComponent("OLMIncompatibleOperatorController"),
129+
)
130+
115131
versionGetter := status.NewVersionGetter()
116132
versionGetter.SetVersion("operator", status.VersionForOperatorFromEnv())
117133

@@ -129,7 +145,7 @@ func runOperator(ctx context.Context, cc *controllercmd.ControllerContext) error
129145

130146
cl.StartInformers(ctx)
131147

132-
for _, c := range append(staticResourceControllerList, upgradeableConditionController, clusterOperatorController, operatorLoggingController) {
148+
for _, c := range append(staticResourceControllerList, upgradeableConditionController, incompatibleOperatorController, clusterOperatorController, operatorLoggingController) {
133149
go func(c factory.Controller) {
134150
defer runtime.HandleCrash()
135151
c.Run(ctx, 1)

internal/utils/utils.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package utils
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
7+
8+
semver "github.com/blang/semver/v4"
9+
)
10+
11+
func GetNextOCPMinorVersion(versionString string) (*semver.Version, error) {
12+
v, err := semver.Parse(versionString)
13+
if err != nil {
14+
return &v, err
15+
}
16+
v.Build = nil // Builds are irrelevant
17+
v.Pre = nil // Next Y release
18+
return &v, v.IncrementMinor() // Sets Y=Y+1 and Z=0
19+
}
20+
21+
func ToAllowedSemver(data []byte) (*semver.Version, error) {
22+
var raw interface{}
23+
if err := json.Unmarshal(data, &raw); err != nil {
24+
return nil, err
25+
}
26+
27+
var versionStr string
28+
29+
switch v := raw.(type) {
30+
case float64:
31+
versionStr = fmt.Sprintf("%d.%d", int(v), int((v-float64(int(v)))*100)+1)
32+
case string:
33+
versionStr = v
34+
default:
35+
return nil, fmt.Errorf("invalid type %T for olm.maxOpenshiftVersion: %s", v, string(data))
36+
}
37+
38+
if !strings.Contains(versionStr, ".") || strings.Count(versionStr, ".") != 1 {
39+
return nil, fmt.Errorf("invalid version format")
40+
}
41+
42+
if strings.HasPrefix(versionStr, "v") || strings.Count(versionStr, ".") != 1 {
43+
return nil, fmt.Errorf("invalid version format")
44+
}
45+
46+
// So it accepts only Major.Minor without Patch
47+
version, err := semver.ParseTolerant(versionStr)
48+
if err != nil {
49+
return nil, err
50+
}
51+
52+
return &version, nil
53+
}

internal/utils/utils_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package utils
2+
3+
import (
4+
"testing"
5+
6+
"github.com/blang/semver/v4"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestParseSemver(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
jsonInput string
14+
want *semver.Version
15+
wantErr bool
16+
}{
17+
{
18+
name: "valid float version",
19+
jsonInput: `4.18`,
20+
want: &semver.Version{Major: 4, Minor: 18, Patch: 0},
21+
wantErr: false,
22+
},
23+
{
24+
name: "valid string version",
25+
jsonInput: `"4.18"`,
26+
want: &semver.Version{Major: 4, Minor: 18, Patch: 0},
27+
wantErr: false,
28+
},
29+
{
30+
name: "invalid float version with patch",
31+
jsonInput: `4.18.0`,
32+
wantErr: true,
33+
},
34+
{
35+
name: "invalid string version with patch",
36+
jsonInput: `"4.18.0"`,
37+
wantErr: true,
38+
},
39+
{
40+
name: "invalid string with v prefix",
41+
jsonInput: `"v4.18"`,
42+
wantErr: true,
43+
},
44+
{
45+
name: "invalid string with v prefix and patch",
46+
jsonInput: `"v4.18.0"`,
47+
wantErr: true,
48+
},
49+
}
50+
51+
for _, tt := range tests {
52+
t.Run(tt.name, func(t *testing.T) {
53+
got, err := ToAllowedSemver([]byte(tt.jsonInput))
54+
if tt.wantErr {
55+
assert.Error(t, err, "expected an error but got none")
56+
} else {
57+
assert.NoError(t, err, "expected no error but got one")
58+
assert.Equal(t, tt.want, got, "unexpected semver version")
59+
}
60+
})
61+
}
62+
}

manifests/0000_51_olm_02_operator_clusterrole.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ rules:
7272
- get
7373
- list
7474
- watch
75+
- apiGroups:
76+
- ""
77+
resources:
78+
- secrets
79+
verbs:
80+
- get
81+
- list
82+
- watch
7583
- apiGroups:
7684
- ""
7785
resources:
@@ -210,6 +218,7 @@ rules:
210218
verbs:
211219
- get
212220
- list
221+
- watch
213222
- apiGroups:
214223
- ""
215224
resources:

manifests/0000_51_olm_06_deployment.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ spec:
5858
- /cluster-olm-operator
5959
args:
6060
- start
61-
- -v=2
6261
imagePullPolicy: IfNotPresent
6362
env:
6463
- name: OPERATOR_NAME

pkg/clients/clients.go

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/openshift/library-go/pkg/controller/controllercmd"
1818
"github.com/openshift/library-go/pkg/operator/resource/resourceapply"
1919
"github.com/openshift/library-go/pkg/operator/v1helpers"
20+
ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
2021
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
2122
"k8s.io/apimachinery/pkg/api/meta"
2223
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -26,6 +27,7 @@ import (
2627
"k8s.io/apimachinery/pkg/types"
2728
"k8s.io/apimachinery/pkg/util/sets"
2829
"k8s.io/client-go/dynamic"
30+
"k8s.io/client-go/dynamic/dynamicinformer"
2931
"k8s.io/client-go/informers"
3032
"k8s.io/client-go/kubernetes"
3133
"k8s.io/client-go/rest"
@@ -43,6 +45,7 @@ type Clients struct {
4345
RESTMapper meta.RESTMapper
4446
OperatorClient *OperatorClient
4547
OperatorInformers operatorinformers.SharedInformerFactory
48+
ClusterExtensionClient *ClusterExtensionClient
4649
ConfigClient configclient.Interface
4750
KubeInformerFactory informers.SharedInformerFactory
4851
ConfigInformerFactory configinformer.SharedInformerFactory
@@ -91,23 +94,34 @@ func New(cc *controllercmd.ControllerContext) (*Clients, error) {
9194
return nil, err
9295
}
9396

97+
infFact := dynamicinformer.NewDynamicSharedInformerFactory(dynClient, defaultResyncPeriod)
98+
clusterExtensionGVR := ocv1alpha1.GroupVersion.WithResource("clusterextensions")
99+
inf := infFact.ForResource(clusterExtensionGVR)
100+
101+
ceClient := &ClusterExtensionClient{
102+
factory: infFact,
103+
informer: inf,
104+
}
105+
94106
return &Clients{
95-
KubeClient: kubeClient,
96-
APIExtensionsClient: apiExtensionsClient,
97-
DynamicClient: dynClient,
98-
RESTMapper: rm,
99-
OperatorClient: opClient,
100-
OperatorInformers: operatorInformersFactory,
101-
ConfigClient: configClient,
102-
KubeInformerFactory: informers.NewSharedInformerFactory(kubeClient, defaultResyncPeriod),
103-
ConfigInformerFactory: configinformer.NewSharedInformerFactory(configClient, defaultResyncPeriod),
107+
KubeClient: kubeClient,
108+
APIExtensionsClient: apiExtensionsClient,
109+
DynamicClient: dynClient,
110+
RESTMapper: rm,
111+
OperatorClient: opClient,
112+
OperatorInformers: operatorInformersFactory,
113+
ClusterExtensionClient: ceClient,
114+
ConfigClient: configClient,
115+
KubeInformerFactory: informers.NewSharedInformerFactory(kubeClient, defaultResyncPeriod),
116+
ConfigInformerFactory: configinformer.NewSharedInformerFactory(configClient, defaultResyncPeriod),
104117
}, nil
105118
}
106119

107120
func (c *Clients) StartInformers(ctx context.Context) {
108121
c.KubeInformerFactory.Start(ctx.Done())
109122
c.ConfigInformerFactory.Start(ctx.Done())
110123
c.OperatorInformers.Start(ctx.Done())
124+
c.ClusterExtensionClient.factory.Start(ctx.Done())
111125
if c.KubeInformersForNamespaces != nil {
112126
c.KubeInformersForNamespaces.Start(ctx.Done())
113127
}
@@ -131,6 +145,15 @@ const (
131145
fieldManager = "cluster-olm-operator"
132146
)
133147

148+
type ClusterExtensionClient struct {
149+
factory dynamicinformer.DynamicSharedInformerFactory
150+
informer informers.GenericInformer
151+
}
152+
153+
func (ce ClusterExtensionClient) Informer() informers.GenericInformer {
154+
return ce.informer
155+
}
156+
134157
type OperatorClient struct {
135158
clientset operatorclient.Interface
136159
informers operatorinformers.SharedInformerFactory

0 commit comments

Comments
 (0)