Skip to content

Commit e879838

Browse files
susana-garciazugao
authored andcommitted
add available versions validation
1 parent c28723d commit e879838

File tree

11 files changed

+242
-64
lines changed

11 files changed

+242
-64
lines changed

generate_sample.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ func newKafkaSample() *exoscalev1.Kafka {
299299
},
300300
IPFilter: exoscalev1.IPFilter{"0.0.0.0/0"},
301301
},
302+
Version: "3.2",
302303
KafkaSettings: runtime.RawExtension{Raw: []byte(`{"connections_max_idle_ms": 60000}`)},
303304
},
304305
},

operator/kafkacontroller/webhook.go

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,93 @@ package kafkacontroller
33
import (
44
"context"
55
"fmt"
6+
"github.com/exoscale/egoscale/v2/oapi"
67
"strings"
78

9+
exoscalesdk "github.com/exoscale/egoscale/v2"
810
exoscalev1 "github.com/vshn/provider-exoscale/apis/exoscale/v1"
11+
"github.com/vshn/provider-exoscale/operator/pipelineutil"
912
"github.com/vshn/provider-exoscale/operator/webhook"
1013
ctrl "sigs.k8s.io/controller-runtime"
14+
"sigs.k8s.io/controller-runtime/pkg/client"
1115

1216
"github.com/go-logr/logr"
1317
"k8s.io/apimachinery/pkg/runtime"
1418
)
1519

16-
var admittedVersions = []string{"3.2"}
20+
const serviceType = "kafka"
1721

1822
// SetupWebhook adds a webhook for kafka resources.
1923
func SetupWebhook(mgr ctrl.Manager) error {
2024
return ctrl.NewWebhookManagedBy(mgr).
2125
For(&exoscalev1.Kafka{}).
2226
WithValidator(&Validator{
23-
log: mgr.GetLogger().WithName("webhook").WithName(strings.ToLower(exoscalev1.KafkaKind)),
27+
log: mgr.GetLogger().WithName("webhook").WithName(strings.ToLower(exoscalev1.KafkaKind)),
28+
kube: mgr.GetClient(),
2429
}).
2530
Complete()
2631
}
2732

2833
// Validator validates kafka admission requests.
2934
type Validator struct {
30-
log logr.Logger
35+
log logr.Logger
36+
kube client.Client
3137
}
3238

3339
// ValidateCreate validates the spec of a created kafka resource.
34-
func (v *Validator) ValidateCreate(_ context.Context, obj runtime.Object) error {
40+
func (v *Validator) ValidateCreate(ctx context.Context, obj runtime.Object) error {
41+
instance := obj.(*exoscalev1.Kafka)
42+
v.log.V(1).Info("get kafka available versions")
43+
exo, err := pipelineutil.OpenExoscaleClient(ctx, v.kube, instance.GetProviderConfigName(), exoscalesdk.ClientOptWithAPIEndpoint(fmt.Sprintf("https://api-%s.exoscale.com", instance.Spec.ForProvider.Zone)))
44+
if err != nil {
45+
return fmt.Errorf("open exoscale client failed: %w", err)
46+
}
47+
return v.validateCreateWithExoClient(ctx, obj, exo.Exoscale)
48+
}
49+
50+
func (v *Validator) validateCreateWithExoClient(ctx context.Context, obj runtime.Object, exo oapi.ClientWithResponsesInterface) error {
3551
instance, ok := obj.(*exoscalev1.Kafka)
3652
if !ok {
3753
return fmt.Errorf("invalid managed resource type %T for kafka webhook", obj)
3854
}
3955
v.log.V(2).WithValues("instance", instance).Info("validate create")
4056

57+
availableVersions, err := v.getAvailableVersions(ctx, exo)
58+
if err != nil {
59+
return err
60+
}
61+
62+
err = v.validateVersion(ctx, obj, *availableVersions)
63+
if err != nil {
64+
return err
65+
}
66+
4167
return validateSpec(instance.Spec.ForProvider)
4268
}
4369

70+
func (v *Validator) getAvailableVersions(ctx context.Context, exo oapi.ClientWithResponsesInterface) (*[]string, error) {
71+
// get kafka available versions
72+
resp, err := exo.GetDbaasServiceTypeWithResponse(ctx, serviceType)
73+
if err != nil {
74+
return nil, fmt.Errorf("get DBaaS service type failed: %w", err)
75+
}
76+
77+
v.log.V(1).Info("DBaaS service type", "body", string(resp.Body))
78+
79+
serviceType := *resp.JSON200
80+
if serviceType.AvailableVersions == nil {
81+
return nil, fmt.Errorf("kafka available versions not found")
82+
}
83+
return serviceType.AvailableVersions, nil
84+
}
85+
86+
func (v *Validator) validateVersion(_ context.Context, obj runtime.Object, availableVersions []string) error {
87+
instance := obj.(*exoscalev1.Kafka)
88+
89+
v.log.V(1).Info("validate version")
90+
return webhook.ValidateVersions(instance.Spec.ForProvider.Version, availableVersions)
91+
}
92+
4493
// ValidateUpdate validates the spec of an updated kafka resource and checks that no immutable field has been modified.
4594
func (v *Validator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) error {
4695
newInstance, ok := newObj.(*exoscalev1.Kafka)
@@ -67,11 +116,7 @@ func (v *Validator) ValidateDelete(_ context.Context, obj runtime.Object) error
67116
}
68117

69118
func validateSpec(params exoscalev1.KafkaParameters) error {
70-
err := validateVersion(params)
71-
if err != nil {
72-
return err
73-
}
74-
err = validateIpFilter(params)
119+
err := validateIpFilter(params)
75120
if err != nil {
76121
return err
77122
}
@@ -82,10 +127,6 @@ func validateSpec(params exoscalev1.KafkaParameters) error {
82127
return validateKafkaSettings(params)
83128
}
84129

85-
func validateVersion(obj exoscalev1.KafkaParameters) error {
86-
return webhook.ValidateVersions(obj.Version, admittedVersions)
87-
}
88-
89130
func validateIpFilter(params exoscalev1.KafkaParameters) error {
90131
if len(params.IPFilter) == 0 {
91132
return fmt.Errorf("IP filter cannot be empty")
@@ -121,10 +162,6 @@ func compareVersion(oldInst, newInst exoscalev1.Kafka) error {
121162
if oldInst.Spec.ForProvider.Version == newInst.Spec.ForProvider.Version {
122163
return nil
123164
}
124-
if newInst.Spec.ForProvider.Version == "" {
125-
// Setting version to empty string should always be fine
126-
return nil
127-
}
128165
if oldInst.Spec.ForProvider.Version == "" {
129166
// Fall back to reported version if no version was set before
130167
oldInst.Spec.ForProvider.Version = oldInst.Status.AtProvider.Version

operator/kafkacontroller/webhook_test.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package kafkacontroller
22

33
import (
44
"context"
5+
"github.com/exoscale/egoscale/v2/oapi"
6+
"github.com/stretchr/testify/mock"
7+
"github.com/vshn/provider-exoscale/internal/operatortest"
58
"testing"
69

710
"github.com/go-logr/logr"
@@ -15,26 +18,34 @@ func TestWebhook_Create(t *testing.T) {
1518
log: logr.Discard(),
1619
}
1720

21+
exoMock := &operatortest.ClientWithResponsesInterface{}
22+
mockAvailableVersionsCall(exoMock)
1823
base := sampleKafka("foo")
1924

2025
t.Run("valid", func(t *testing.T) {
21-
err := v.ValidateCreate(ctx, &base)
26+
err := v.validateCreateWithExoClient(ctx, &base, exoMock)
2227
assert.NoError(t, err)
2328
})
2429
t.Run("invalid empty", func(t *testing.T) {
25-
err := v.ValidateCreate(ctx, &exoscalev1.Kafka{})
30+
err := v.validateCreateWithExoClient(ctx, &exoscalev1.Kafka{}, exoMock)
2631
assert.Error(t, err)
2732
})
2833
t.Run("invalid no ipfilter", func(t *testing.T) {
2934
inst := base
3035
inst.Spec.ForProvider.IPFilter = nil
31-
err := v.ValidateCreate(ctx, &inst)
36+
err := v.validateCreateWithExoClient(ctx, &inst, exoMock)
3237
assert.Error(t, err)
3338
})
3439
t.Run("invalid no time", func(t *testing.T) {
3540
inst := base
3641
inst.Spec.ForProvider.Maintenance.TimeOfDay = ""
37-
err := v.ValidateCreate(ctx, &inst)
42+
err := v.validateCreateWithExoClient(ctx, &inst, exoMock)
43+
assert.Error(t, err)
44+
})
45+
t.Run("invalid version", func(t *testing.T) {
46+
inst := base
47+
inst.Spec.ForProvider.Version = "non-valid-version"
48+
err := v.validateCreateWithExoClient(ctx, &inst, exoMock)
3849
assert.Error(t, err)
3950
})
4051
}
@@ -91,3 +102,12 @@ func TestWebhook_Update(t *testing.T) {
91102
})
92103

93104
}
105+
106+
func mockAvailableVersionsCall(m *operatortest.ClientWithResponsesInterface) {
107+
m.On("GetDbaasServiceTypeWithResponse", mock.Anything, mock.Anything).
108+
Return(&oapi.GetDbaasServiceTypeResponse{
109+
JSON200: &oapi.DbaasServiceType{
110+
AvailableVersions: &[]string{"3.2", "4.3"},
111+
},
112+
}, nil)
113+
}

operator/mysqlcontroller/setup.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ func SetupWebhook(mgr ctrl.Manager) error {
4141
return ctrl.NewWebhookManagedBy(mgr).
4242
For(&exoscalev1.MySQL{}).
4343
WithValidator(&Validator{
44-
log: mgr.GetLogger().WithName("webhook").WithName(strings.ToLower(exoscalev1.MySQLKind)),
44+
log: mgr.GetLogger().WithName("webhook").WithName(strings.ToLower(exoscalev1.MySQLKind)),
45+
kube: mgr.GetClient(),
4546
}).
4647
Complete()
4748
}

operator/mysqlcontroller/webhook.go

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,76 @@ import (
44
"context"
55
"fmt"
66

7+
exoscalesdk "github.com/exoscale/egoscale/v2"
78
"github.com/go-logr/logr"
89
exoscalev1 "github.com/vshn/provider-exoscale/apis/exoscale/v1"
10+
"github.com/vshn/provider-exoscale/operator/pipelineutil"
911
"github.com/vshn/provider-exoscale/operator/webhook"
1012
"k8s.io/apimachinery/pkg/runtime"
13+
"sigs.k8s.io/controller-runtime/pkg/client"
1114
)
1215

13-
var admittedVersions = []string{"8"}
16+
const serviceType = "mysql"
1417

1518
// Validator validates admission requests.
1619
type Validator struct {
17-
log logr.Logger
20+
log logr.Logger
21+
kube client.Client
1822
}
1923

2024
// ValidateCreate implements admission.CustomValidator.
21-
func (v *Validator) ValidateCreate(_ context.Context, obj runtime.Object) error {
22-
mySQLInstance := obj.(*exoscalev1.MySQL)
25+
func (v *Validator) ValidateCreate(ctx context.Context, obj runtime.Object) error {
26+
mySQLInstance, ok := obj.(*exoscalev1.MySQL)
27+
if !ok {
28+
return fmt.Errorf("invalid managed resource type %T for mysql webhook", obj)
29+
}
30+
2331
v.log.V(1).Info("validate create")
2432

33+
availableVersions, err := v.getAvailableVersions(ctx, obj)
34+
if err != nil {
35+
return err
36+
}
37+
38+
err = v.validateVersion(ctx, obj, *availableVersions)
39+
if err != nil {
40+
return err
41+
}
42+
2543
return validateSpec(mySQLInstance)
2644
}
2745

46+
func (v *Validator) getAvailableVersions(ctx context.Context, obj runtime.Object) (*[]string, error) {
47+
mySQLInstance := obj.(*exoscalev1.MySQL)
48+
49+
v.log.V(1).Info("get mysql available versions")
50+
exo, err := pipelineutil.OpenExoscaleClient(ctx, v.kube, mySQLInstance.GetProviderConfigName(), exoscalesdk.ClientOptWithAPIEndpoint(fmt.Sprintf("https://api-%s.exoscale.com", mySQLInstance.Spec.ForProvider.Zone)))
51+
if err != nil {
52+
return nil, fmt.Errorf("open exoscale client failed: %w", err)
53+
}
54+
55+
// get mysql available versions
56+
resp, err := exo.Exoscale.GetDbaasServiceTypeWithResponse(ctx, serviceType)
57+
if err != nil {
58+
return nil, fmt.Errorf("get DBaaS service type failed: %w", err)
59+
}
60+
61+
v.log.V(1).Info("DBaaS service type", "body", string(resp.Body))
62+
63+
serviceType := *resp.JSON200
64+
if serviceType.AvailableVersions == nil {
65+
return nil, fmt.Errorf("mysql available versions not found")
66+
}
67+
return serviceType.AvailableVersions, nil
68+
}
69+
70+
func (v *Validator) validateVersion(ctx context.Context, obj runtime.Object, availableVersions []string) error {
71+
mySQLInstance := obj.(*exoscalev1.MySQL)
72+
73+
v.log.V(1).Info("validate version")
74+
return webhook.ValidateVersions(mySQLInstance.Spec.ForProvider.Version, availableVersions)
75+
}
76+
2877
// ValidateUpdate implements admission.CustomValidator.
2978
func (v *Validator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) error {
3079
newInstance, ok := newObj.(*exoscalev1.MySQL)
@@ -52,7 +101,6 @@ func (v *Validator) ValidateDelete(_ context.Context, obj runtime.Object) error
52101

53102
func validateSpec(obj *exoscalev1.MySQL) error {
54103
for _, validatorFn := range []func(exoscalev1.MySQLParameters) error{
55-
validateVersion,
56104
validateIpFilter,
57105
validateMaintenanceSchedule,
58106
validateSettings,
@@ -64,10 +112,6 @@ func validateSpec(obj *exoscalev1.MySQL) error {
64112
return nil
65113
}
66114

67-
func validateVersion(obj exoscalev1.MySQLParameters) error {
68-
return webhook.ValidateVersions(obj.Version, admittedVersions)
69-
}
70-
71115
func validateIpFilter(obj exoscalev1.MySQLParameters) error {
72116
if len(obj.IPFilter) == 0 {
73117
return fmt.Errorf("IP filter cannot be empty")
@@ -105,10 +149,6 @@ func compareVersion(oldInst, newInst exoscalev1.MySQL) error {
105149
if oldInst.Spec.ForProvider.Version == newInst.Spec.ForProvider.Version {
106150
return nil
107151
}
108-
if newInst.Spec.ForProvider.Version == "" {
109-
// Setting version to empty string should always be fine
110-
return nil
111-
}
112152
if oldInst.Spec.ForProvider.Version == "" {
113153
// Fall back to reported version if no version was set before
114154
oldInst.Spec.ForProvider.Version = oldInst.Status.AtProvider.Version

operator/opensearchcontroller/setup.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ func SetupWebhook(mgr ctrl.Manager) error {
4040
return ctrl.NewWebhookManagedBy(mgr).
4141
For(&exoscalev1.OpenSearch{}).
4242
WithValidator(&Validator{
43-
log: mgr.GetLogger().WithName("webhook").WithName(strings.ToLower(exoscalev1.OpenSearchKind)),
43+
log: mgr.GetLogger().WithName("webhook").WithName(strings.ToLower(exoscalev1.OpenSearchKind)),
44+
kube: mgr.GetClient(),
4445
}).
4546
Complete()
4647
}

0 commit comments

Comments
 (0)