From 2af8cff6d415cb1189089d9ad1208be35896657d Mon Sep 17 00:00:00 2001 From: Jan Willies Date: Fri, 11 Aug 2023 21:07:50 +0200 Subject: [PATCH 1/2] add client interfaces --- internal/clients/alertmanager/client.go | 26 ++++++ internal/clients/cortex.go | 18 ++-- internal/clients/rulegroups/rulegroup.go | 28 ++++++ .../controller/alertmanager/configuration.go | 12 ++- .../alertmanager/configuration_test.go | 74 ++++++++++++++++ internal/controller/rulegroup/rulegroup.go | 19 ++--- .../controller/rulegroup/rulegroup_test.go | 85 +++++++++++-------- ...ssplane.io_alertmanagerconfigurations.yaml | 10 +-- 8 files changed, 211 insertions(+), 61 deletions(-) create mode 100644 internal/clients/alertmanager/client.go create mode 100644 internal/clients/rulegroups/rulegroup.go create mode 100644 internal/controller/alertmanager/configuration_test.go diff --git a/internal/clients/alertmanager/client.go b/internal/clients/alertmanager/client.go new file mode 100644 index 0000000..303f295 --- /dev/null +++ b/internal/clients/alertmanager/client.go @@ -0,0 +1,26 @@ +/* +Copyright 2023 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package alertmanager + +import ( + "context" +) + +type AlertManagerClient interface { + GetAlertmanagerConfig(ctx context.Context) (string, map[string]string, error) + CreateAlertmanagerConfig(ctx context.Context, cfg string, templates map[string]string) error + DeleteAlermanagerConfig(ctx context.Context) error +} diff --git a/internal/clients/cortex.go b/internal/clients/cortex.go index df50296..eca14a7 100644 --- a/internal/clients/cortex.go +++ b/internal/clients/cortex.go @@ -13,9 +13,16 @@ import ( "github.com/crossplane/provider-cortex/apis/v1alpha1" ) +type Config struct { + cortexClient.Config +} + // NewClient creates new Cortex Client with provided Cortex Configurations. -func NewClient(config cortexClient.Config) *cortexClient.CortexClient { - client, err := cortexClient.New(config) +func NewClient(config Config) *cortexClient.CortexClient { + client, err := cortexClient.New(cortexClient.Config{ + Address: config.Address, + ID: config.ID, + }) if err != nil { fmt.Printf("Could not initialize cortex client: %v", err) @@ -24,7 +31,7 @@ func NewClient(config cortexClient.Config) *cortexClient.CortexClient { } // GetConfig constructs a Config that can be used to authenticate to Cortex -func GetConfig(ctx context.Context, c client.Client, mg resource.Managed) (*cortexClient.Config, error) { +func GetConfig(ctx context.Context, c client.Client, mg resource.Managed) (*Config, error) { switch { case mg.GetProviderConfigReference() != nil: return UseProviderConfig(ctx, c, mg) @@ -34,7 +41,7 @@ func GetConfig(ctx context.Context, c client.Client, mg resource.Managed) (*cort } // UseProviderConfig to produce a config that can be used to authenticate to Cortex. -func UseProviderConfig(ctx context.Context, c client.Client, mg resource.Managed) (*cortexClient.Config, error) { +func UseProviderConfig(ctx context.Context, c client.Client, mg resource.Managed) (*Config, error) { pc := &v1alpha1.ProviderConfig{} if err := c.Get(ctx, types.NamespacedName{Name: mg.GetProviderConfigReference().Name}, pc); err != nil { return nil, errors.Wrap(err, "cannot get referenced Provider") @@ -45,7 +52,8 @@ func UseProviderConfig(ctx context.Context, c client.Client, mg resource.Managed return nil, errors.Wrap(err, "cannot track ProviderConfig usage") } - return &cortexClient.Config{ID: pc.Spec.TenantID, Address: pc.Spec.Address}, nil + // return &Config{}, nil + return &Config{cortexClient.Config{ID: pc.Spec.TenantID, Address: pc.Spec.Address}}, nil // switch s := pc.Spec.Credentials.Source; s { //nolint:exhaustive // case xpv1.CredentialsSourceSecret: diff --git a/internal/clients/rulegroups/rulegroup.go b/internal/clients/rulegroups/rulegroup.go new file mode 100644 index 0000000..57e0bb5 --- /dev/null +++ b/internal/clients/rulegroups/rulegroup.go @@ -0,0 +1,28 @@ +/* +Copyright 2023 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package rulegroups + +import ( + "context" + + "github.com/cortexproject/cortex-tools/pkg/rules/rwrulefmt" +) + +type RuleGroupClient interface { + GetRuleGroup(ctx context.Context, namespace string, groupName string) (*rwrulefmt.RuleGroup, error) + CreateRuleGroup(ctx context.Context, namespace string, rg rwrulefmt.RuleGroup) error + DeleteRuleGroup(ctx context.Context, namespace string, groupName string) error +} diff --git a/internal/controller/alertmanager/configuration.go b/internal/controller/alertmanager/configuration.go index 925f30f..0a392c1 100644 --- a/internal/controller/alertmanager/configuration.go +++ b/internal/controller/alertmanager/configuration.go @@ -25,7 +25,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - cortexClient "github.com/cortexproject/cortex-tools/pkg/client" xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" "github.com/crossplane/crossplane-runtime/pkg/connection" "github.com/crossplane/crossplane-runtime/pkg/controller" @@ -37,6 +36,7 @@ import ( "github.com/crossplane/provider-cortex/apis/alerts/v1alpha1" apisv1alpha1 "github.com/crossplane/provider-cortex/apis/v1alpha1" xpClient "github.com/crossplane/provider-cortex/internal/clients" + "github.com/crossplane/provider-cortex/internal/clients/alertmanager" "github.com/crossplane/provider-cortex/internal/features" ) @@ -64,7 +64,7 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { managed.WithExternalConnecter(&connector{ kube: mgr.GetClient(), usage: resource.NewProviderConfigUsageTracker(mgr.GetClient(), &apisv1alpha1.ProviderConfigUsage{}), - newServiceFn: xpClient.NewClient}), + newServiceFn: newAlertManagerClient}), // managed.NewNameAsExternalName(c) managed.WithLogger(o.Logger.WithValues("controller", name)), managed.WithPollInterval(o.PollInterval), @@ -84,7 +84,11 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { type connector struct { kube client.Client usage resource.Tracker - newServiceFn func(config cortexClient.Config) *cortexClient.CortexClient + newServiceFn func(config xpClient.Config) alertmanager.AlertManagerClient +} + +func newAlertManagerClient(config xpClient.Config) alertmanager.AlertManagerClient { + return xpClient.NewClient(config) } // Connect typically produces an ExternalClient by: @@ -119,7 +123,7 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E // external resource to ensure it reflects the managed resource's desired state. type external struct { // A 'client' used to connect to the external resource API - service *cortexClient.CortexClient + service alertmanager.AlertManagerClient } func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { diff --git a/internal/controller/alertmanager/configuration_test.go b/internal/controller/alertmanager/configuration_test.go new file mode 100644 index 0000000..f5a2e6a --- /dev/null +++ b/internal/controller/alertmanager/configuration_test.go @@ -0,0 +1,74 @@ +/* +Copyright 2022 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package alertmanager + +import ( + "context" + "testing" + + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/crossplane/provider-cortex/internal/clients/alertmanager" + "github.com/google/go-cmp/cmp" +) + +// Unlike many Kubernetes projects Crossplane does not use third party testing +// libraries, per the common Go test review comments. Crossplane encourages the +// use of table driven unit tests. The tests of the crossplane-runtime project +// are representative of the testing style Crossplane encourages. +// +// https://github.com/golang/go/wiki/TestComments +// https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md#contributing-code + +func TestObserve(t *testing.T) { + type fields struct { + service alertmanager.AlertManagerClient + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + o managed.ExternalObservation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + // TODO: Add test cases. + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{service: tc.fields.service} + got, err := e.Observe(tc.args.ctx, tc.args.mg) + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("\n%s\ne.Observe(...): -want error, +got error:\n%s\n", tc.reason, diff) + } + if diff := cmp.Diff(tc.want.o, got); diff != "" { + t.Errorf("\n%s\ne.Observe(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} diff --git a/internal/controller/rulegroup/rulegroup.go b/internal/controller/rulegroup/rulegroup.go index 8eeba20..1a1f362 100644 --- a/internal/controller/rulegroup/rulegroup.go +++ b/internal/controller/rulegroup/rulegroup.go @@ -29,7 +29,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - cortexClient "github.com/cortexproject/cortex-tools/pkg/client" "github.com/cortexproject/cortex-tools/pkg/rules/rwrulefmt" xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" "github.com/crossplane/crossplane-runtime/pkg/connection" @@ -43,6 +42,7 @@ import ( "github.com/crossplane/provider-cortex/apis/rules/v1alpha1" apisv1alpha1 "github.com/crossplane/provider-cortex/apis/v1alpha1" xpClient "github.com/crossplane/provider-cortex/internal/clients" + "github.com/crossplane/provider-cortex/internal/clients/rulegroups" "github.com/crossplane/provider-cortex/internal/features" ) @@ -70,7 +70,8 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { managed.WithExternalConnecter(&connector{ kube: mgr.GetClient(), usage: resource.NewProviderConfigUsageTracker(mgr.GetClient(), &apisv1alpha1.ProviderConfigUsage{}), - newServiceFn: xpClient.NewClient}), + newServiceFn: newRuleGroupClient}), + // newServiceFn: xpClient.NewClient}), // managed.NewNameAsExternalName(c) managed.WithLogger(o.Logger.WithValues("controller", name)), managed.WithPollInterval(o.PollInterval), @@ -90,7 +91,11 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { type connector struct { kube client.Client usage resource.Tracker - newServiceFn func(config cortexClient.Config) *cortexClient.CortexClient + newServiceFn func(config xpClient.Config) rulegroups.RuleGroupClient +} + +func newRuleGroupClient(config xpClient.Config) rulegroups.RuleGroupClient { + return xpClient.NewClient(config) } // Connect typically produces an ExternalClient by: @@ -125,7 +130,7 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E // external resource to ensure it reflects the managed resource's desired state. type external struct { // A 'client' used to connect to the external resource API - service *cortexClient.CortexClient + service rulegroups.RuleGroupClient } func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { @@ -134,12 +139,6 @@ func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex return managed.ExternalObservation{}, errors.New(errNotRuleGroup) } - // if meta.GetExternalName(cr) == "" { - // return managed.ExternalObservation{ - // ResourceExists: false, - // }, nil - // } - observedRuleGroup, err := c.service.GetRuleGroup(ctx, cr.Spec.ForProvider.Namespace, meta.GetExternalName(cr)) if err != nil { switch { diff --git a/internal/controller/rulegroup/rulegroup_test.go b/internal/controller/rulegroup/rulegroup_test.go index f21b93b..ec2c77a 100644 --- a/internal/controller/rulegroup/rulegroup_test.go +++ b/internal/controller/rulegroup/rulegroup_test.go @@ -16,6 +16,17 @@ limitations under the License. package rulegroup +import ( + "context" + "testing" + + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/crossplane/provider-cortex/internal/clients/rulegroups" + "github.com/google/go-cmp/cmp" +) + // Unlike many Kubernetes projects Crossplane does not use third party testing // libraries, per the common Go test review comments. Crossplane encourages the // use of table driven unit tests. The tests of the crossplane-runtime project @@ -24,40 +35,40 @@ package rulegroup // https://github.com/golang/go/wiki/TestComments // https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md#contributing-code -// func TestObserve(t *testing.T) { -// type fields struct { -// service interface{} -// } - -// type args struct { -// ctx context.Context -// mg resource.Managed -// } - -// type want struct { -// o managed.ExternalObservation -// err error -// } - -// cases := map[string]struct { -// reason string -// fields fields -// args args -// want want -// }{ -// // TODO: Add test cases. -// } - -// for name, tc := range cases { -// t.Run(name, func(t *testing.T) { -// e := external{service: tc.fields.service} -// got, err := e.Observe(tc.args.ctx, tc.args.mg) -// if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { -// t.Errorf("\n%s\ne.Observe(...): -want error, +got error:\n%s\n", tc.reason, diff) -// } -// if diff := cmp.Diff(tc.want.o, got); diff != "" { -// t.Errorf("\n%s\ne.Observe(...): -want, +got:\n%s\n", tc.reason, diff) -// } -// }) -// } -// } +func TestObserve(t *testing.T) { + type fields struct { + service rulegroups.RuleGroupClient + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + o managed.ExternalObservation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + // TODO: Add test cases. + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{service: tc.fields.service} + got, err := e.Observe(tc.args.ctx, tc.args.mg) + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("\n%s\ne.Observe(...): -want error, +got error:\n%s\n", tc.reason, diff) + } + if diff := cmp.Diff(tc.want.o, got); diff != "" { + t.Errorf("\n%s\ne.Observe(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} diff --git a/package/crds/alerts.cortex.crossplane.io_alertmanagerconfigurations.yaml b/package/crds/alerts.cortex.crossplane.io_alertmanagerconfigurations.yaml index c23c316..b894aca 100644 --- a/package/crds/alerts.cortex.crossplane.io_alertmanagerconfigurations.yaml +++ b/package/crds/alerts.cortex.crossplane.io_alertmanagerconfigurations.yaml @@ -34,7 +34,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: A AlertManagerConfiguration is an example API type. + description: An AlertManagerConfiguration is an example API type. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -50,7 +50,7 @@ spec: type: object spec: description: A AlertManagerConfigurationSpec defines the desired state - of a AlertManagerConfiguration. + of an AlertManagerConfiguration. properties: deletionPolicy: default: Delete @@ -63,7 +63,7 @@ spec: type: string forProvider: description: AlertManagerConfigurationParameters are the configurable - fields of a AlertManagerConfiguration. + fields of an AlertManagerConfiguration. properties: alertmanager_config: description: Contains the alert manager configuration. This uses @@ -251,11 +251,11 @@ spec: type: object status: description: A AlertManagerConfigurationStatus represents the observed - state of a AlertManagerConfiguration. + state of an AlertManagerConfiguration. properties: atProvider: description: AlertManagerConfigurationObservation are the observable - fields of a AlertManagerConfiguration. + fields of an AlertManagerConfiguration. properties: data: type: string From 85a454d7ae29895d38fc53e980be0c063b7845b9 Mon Sep 17 00:00:00 2001 From: Jan Willies Date: Mon, 11 Sep 2023 15:24:57 +0200 Subject: [PATCH 2/2] ignore linter for isUpToDate --- internal/controller/rulegroup/rulegroup.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/controller/rulegroup/rulegroup.go b/internal/controller/rulegroup/rulegroup.go index 1a1f362..94fe531 100644 --- a/internal/controller/rulegroup/rulegroup.go +++ b/internal/controller/rulegroup/rulegroup.go @@ -279,6 +279,7 @@ func (c *external) Delete(ctx context.Context, mg resource.Managed) error { return errors.Wrap(err, "") } +//gocyclo:ignore func isUpToDate(cr *v1alpha1.RuleGroup, observedRuleGroup *rwrulefmt.RuleGroup) bool { if cr == nil || observedRuleGroup == nil { return false