Merge pull request #153 from lburgazzoli/lint

Enable more linters
This commit is contained in:
Luca Burgazzoli 2024-05-17 16:48:39 +02:00 committed by GitHub
commit 53e52ba39a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 466 additions and 375 deletions

View File

@ -24,52 +24,56 @@ linters-settings:
rules:
- name: dot-imports
disabled: true
mnd:
checks:
- argument
- case
- operation
- return
- assign
ignored-functions:
- '^len\.'
- '^strings\.SplitN$'
- '^make$'
wsl:
allow-cuddle-declarations: true
allow-separated-leading-comment: true
nlreturn:
block-size: 2
testpackage:
skip-regexp: test/e2e/...
issues:
exclude-rules:
# Exclude some linters from running on tests files.
- path: test
linters:
- wrapcheck
linters:
enable-all: true
disable:
- cyclop
- dupl
- exhaustive
# abandoned
- deadcode
- golint
- ifshort
- varcheck
- structcheck
- scopelint
- nosnakecase
- maligned
- exhaustivestruct
- exhaustruct
- forbidigo
- funlen
- interfacer
# keep
- varnamelen
- godox # there are a bunch of TODOs that must be solved
- gci
- gofumpt
- exhaustruct
- paralleltest
- ireturn
- funlen
- gochecknoglobals
- gochecknoinits
- gocognit
- goconst
- gocyclo
- godox
- goerr113
- gofumpt
- golint
- gomnd
- gomoddirectives
- interfacer
- ireturn
- lll
- maligned
- nakedret
- nestif
- nilnil
- nlreturn
- nosnakecase
- paralleltest
- rowserrcheck
- scopelint
- structcheck
- sqlclosecheck
- tagliatelle
- tenv
- testpackage
- varnamelen
- wastedassign
- whitespace
- wrapcheck
- wsl
- varcheck
- deadcode
- ifshort
- inamedparam
- depguard
# validate
- cyclop
- gocognit

View File

@ -18,8 +18,11 @@ package v1alpha1
import (
"encoding/json"
"errors"
"fmt"
)
var ErrUnmarshalOnNil = errors.New("UnmarshalJSON on nil pointer")
// RawMessage is a raw encoded JSON value.
// It implements Marshaler and Unmarshaler and can
// be used to delay JSON decoding or precompute a JSON encoding.
@ -40,15 +43,18 @@ func (m RawMessage) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
return m, nil
}
// UnmarshalJSON sets *m to a copy of data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
return fmt.Errorf("json.RawMessage: %w", ErrUnmarshalOnNil)
}
*m = append((*m)[0:0], data...)
return nil
}
@ -57,10 +63,12 @@ func (m *RawMessage) String() string {
if m == nil {
return ""
}
b, err := m.MarshalJSON()
if err != nil {
return ""
}
return string(b)
}

View File

@ -29,8 +29,7 @@ func init() {
}
func NewRunCmd() *cobra.Command {
controllerOpts := controller.Options{
co := controller.Options{
MetricsAddr: ":8080",
ProbeAddr: ":8081",
PprofAddr: "",
@ -53,7 +52,7 @@ func NewRunCmd() *cobra.Command {
return fmt.Errorf("unable to compute cache's watch selector: %w", err)
}
controllerOpts.WatchSelectors = map[rtclient.Object]rtcache.ByObject{
co.WatchSelectors = map[rtclient.Object]rtcache.ByObject{
// k8s
&rbacv1.ClusterRole{}: {Label: selector},
&rbacv1.ClusterRoleBinding{}: {Label: selector},
@ -67,7 +66,7 @@ func NewRunCmd() *cobra.Command {
&appsv1.Deployment{}: {Label: selector},
}
return controller.Start(controllerOpts, func(manager manager.Manager, opts controller.Options) error {
return controller.Start(co, func(manager manager.Manager, opts controller.Options) error {
if _, err := controlplane.NewReconciler(cmd.Context(), manager, helmOpts); err != nil {
return fmt.Errorf("unable to set-up DaprControlPlane reconciler: %w", err)
}
@ -81,16 +80,24 @@ func NewRunCmd() *cobra.Command {
},
}
cmd.Flags().StringVar(&controllerOpts.LeaderElectionID, "leader-election-id", controllerOpts.LeaderElectionID, "The leader election ID of the operator.")
cmd.Flags().StringVar(&controllerOpts.LeaderElectionNamespace, "leader-election-namespace", controllerOpts.LeaderElectionNamespace, "The leader election namespace.")
cmd.Flags().BoolVar(&controllerOpts.EnableLeaderElection, "leader-election", controllerOpts.EnableLeaderElection, "Enable leader election for controller manager.")
cmd.Flags().BoolVar(&controllerOpts.ReleaseLeaderElectionOnCancel, "leader-election-release", controllerOpts.ReleaseLeaderElectionOnCancel, "If the leader should step down voluntarily.")
cmd.Flags().StringVar(
&co.LeaderElectionID, "leader-election-id", co.LeaderElectionID, "The leader election ID of the operator.")
cmd.Flags().StringVar(
&co.LeaderElectionNamespace, "leader-election-namespace", co.LeaderElectionNamespace, "The leader election namespace.")
cmd.Flags().BoolVar(
&co.EnableLeaderElection, "leader-election", co.EnableLeaderElection, "Enable leader election for controller manager.")
cmd.Flags().BoolVar(
&co.ReleaseLeaderElectionOnCancel, "leader-election-release", co.ReleaseLeaderElectionOnCancel, "If the leader should step down voluntarily.")
cmd.Flags().StringVar(&controllerOpts.MetricsAddr, "metrics-bind-address", controllerOpts.MetricsAddr, "The address the metric endpoint binds to.")
cmd.Flags().StringVar(&controllerOpts.ProbeAddr, "health-probe-bind-address", controllerOpts.ProbeAddr, "The address the probe endpoint binds to.")
cmd.Flags().StringVar(&controllerOpts.PprofAddr, "pprof-bind-address", controllerOpts.PprofAddr, "The address the pprof endpoint binds to.")
cmd.Flags().StringVar(
&co.MetricsAddr, "metrics-bind-address", co.MetricsAddr, "The address the metric endpoint binds to.")
cmd.Flags().StringVar(
&co.ProbeAddr, "health-probe-bind-address", co.ProbeAddr, "The address the probe endpoint binds to.")
cmd.Flags().StringVar(
&co.PprofAddr, "pprof-bind-address", co.PprofAddr, "The address the pprof endpoint binds to.")
cmd.Flags().StringVar(&helmOpts.ChartsDir, "helm-charts-dir", helmOpts.ChartsDir, "Helm charts dir.")
cmd.Flags().StringVar(
&helmOpts.ChartsDir, "helm-charts-dir", helmOpts.ChartsDir, "Helm charts dir.")
return &cmd
}

View File

@ -18,6 +18,9 @@ package controlplane
import (
"context"
"fmt"
"github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/openshift"
"github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/helm"
@ -41,6 +44,7 @@ import (
func NewReconciler(ctx context.Context, manager ctrlRt.Manager, o helm.Options) (*Reconciler, error) {
c, err := client.NewClient(manager.GetConfig(), manager.GetScheme(), manager.GetClient())
if err != nil {
//nolint:wrapcheck
return nil, err
}
@ -52,10 +56,12 @@ func NewReconciler(ctx context.Context, manager ctrlRt.Manager, o helm.Options)
rec.manager = manager
rec.recorder = manager.GetEventRecorderFor(controller.FieldManager)
isOpenshift, err := c.IsOpenShift()
isOpenshift, err := openshift.IsOpenShift(c.Discovery)
if err != nil {
//nolint: wrapcheck
return nil, err
}
if isOpenshift {
rec.ClusterType = controller.ClusterTypeOpenShift
}
@ -65,7 +71,7 @@ func NewReconciler(ctx context.Context, manager ctrlRt.Manager, o helm.Options)
hc, err := loader.Load(o.ChartsDir)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to load chart from dir %s: %w", o.ChartsDir, err)
}
rec.c = hc
@ -111,6 +117,7 @@ func (r *Reconciler) init(ctx context.Context) error {
for i := range r.actions {
b, err := r.actions[i].Configure(ctx, r.Client, c)
if err != nil {
//nolint:wrapcheck
return err
}
@ -119,7 +126,7 @@ func (r *Reconciler) init(ctx context.Context) error {
ct, err := c.Build(r)
if err != nil {
return err
return fmt.Errorf("failure building the application controller for DaprControlPlane resource: %w", err)
}
r.controller = ct

View File

@ -2,6 +2,7 @@ package controlplane
import (
"context"
"fmt"
"github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/controller"
@ -61,7 +62,11 @@ func (a *ApplyAction) Run(ctx context.Context, rr *ReconciliationRequest) error
FieldManager: controller.FieldManager,
})
return err
if err != nil {
return fmt.Errorf("failure to apply changes to %s: %w", rr.NamespacedName, err)
}
return nil
}
func (a *ApplyAction) Cleanup(_ context.Context, _ *ReconciliationRequest) error {

View File

@ -2,6 +2,7 @@ package controlplane
import (
"context"
"fmt"
"github.com/dapr-sandbox/dapr-kubernetes-operator/internal/controller/operator/instance"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
@ -44,7 +45,7 @@ func (a *StatusAction) Run(ctx context.Context, rr *ReconciliationRequest) error
)
if err != nil && !k8serrors.IsNotFound(err) {
return err
return fmt.Errorf("failure to lookup resource %s: %w", rr.NamespacedName, err)
}
for i := range di.Status.Conditions {

View File

@ -89,32 +89,30 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
return ctrl.Result{Requeue: true}, nil
}
return ctrl.Result{}, err
return ctrl.Result{}, fmt.Errorf("error updating DaprControlPlane resource: %w", err)
}
//nolint:nestif
if rr.Resource.ObjectMeta.DeletionTimestamp.IsZero() {
//
// Add finalizer
//
if ctrlutil.AddFinalizer(rr.Resource, DaprControlPlaneFinalizerName) {
if err := r.Update(ctx, rr.Resource); err != nil {
if k8serrors.IsConflict(err) {
return ctrl.Result{}, err
return ctrl.Result{}, fmt.Errorf("conflict when adding finalizer to %s: %w", req.NamespacedName, err)
}
return ctrl.Result{}, fmt.Errorf("failure adding finalizer to connector cluster %s: %w", req.NamespacedName, err)
return ctrl.Result{}, fmt.Errorf("failure adding finalizer to %s: %w", req.NamespacedName, err)
}
}
} else {
//
// Cleanup leftovers if needed
//
for i := len(r.actions) - 1; i >= 0; i-- {
if err := r.actions[i].Cleanup(ctx, &rr); err != nil {
//nolint:wrapcheck
return ctrl.Result{}, err
}
}
@ -122,11 +120,10 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
//
// Handle finalizer
//
if ctrlutil.RemoveFinalizer(rr.Resource, DaprControlPlaneFinalizerName) {
if err := r.Update(ctx, rr.Resource); err != nil {
if k8serrors.IsConflict(err) {
return ctrl.Result{}, err
return ctrl.Result{}, fmt.Errorf("conflict when removing finalizer to %s: %w", req.NamespacedName, err)
}
return ctrl.Result{}, fmt.Errorf("failure removing finalizer from %s: %w", req.NamespacedName, err)
@ -149,6 +146,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
}
errs := make([]error, 0, len(r.actions)+1)
for i := range r.actions {
if err := r.actions[i].Run(ctx, &rr); err != nil {
errs = append(errs, err)

View File

@ -25,7 +25,7 @@ type ReconciliationRequest struct {
}
type Action interface {
Configure(context.Context, *client.Client, *builder.Builder) (*builder.Builder, error)
Run(context.Context, *ReconciliationRequest) error
Cleanup(context.Context, *ReconciliationRequest) error
Configure(ctx context.Context, c *client.Client, b *builder.Builder) (*builder.Builder, error)
Run(ctx context.Context, rc *ReconciliationRequest) error
Cleanup(ctx context.Context, rc *ReconciliationRequest) error
}

View File

@ -18,6 +18,9 @@ package instance
import (
"context"
"fmt"
"github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/openshift"
"github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/helm"
@ -47,6 +50,7 @@ import (
func NewReconciler(ctx context.Context, manager ctrlRt.Manager, o helm.Options) (*Reconciler, error) {
c, err := client.NewClient(manager.GetConfig(), manager.GetScheme(), manager.GetClient())
if err != nil {
//nolint:wrapcheck
return nil, err
}
@ -58,10 +62,12 @@ func NewReconciler(ctx context.Context, manager ctrlRt.Manager, o helm.Options)
rec.manager = manager
rec.recorder = manager.GetEventRecorderFor(controller.FieldManager)
isOpenshift, err := c.IsOpenShift()
isOpenshift, err := openshift.IsOpenShift(c.Discovery)
if err != nil {
//nolint:wrapcheck
return nil, err
}
if isOpenshift {
rec.ClusterType = controller.ClusterTypeOpenShift
}
@ -73,7 +79,7 @@ func NewReconciler(ctx context.Context, manager ctrlRt.Manager, o helm.Options)
hc, err := loader.Load(o.ChartsDir)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to load chart from dir %s: %w", o.ChartsDir, err)
}
rec.c = hc
@ -143,6 +149,7 @@ func (r *Reconciler) init(ctx context.Context) error {
for i := range r.actions {
b, err := r.actions[i].Configure(ctx, r.Client, c)
if err != nil {
//nolint:wrapcheck
return err
}
@ -151,7 +158,7 @@ func (r *Reconciler) init(ctx context.Context) error {
ct, err := c.Build(r)
if err != nil {
return err
return fmt.Errorf("failure building the application controller for DaprInstance resource: %w", err)
}
r.controller = ct
@ -160,7 +167,7 @@ func (r *Reconciler) init(ctx context.Context) error {
}
func (r *Reconciler) Watch(obj ctrlCli.Object, eh handler.EventHandler, predicates ...predicate.Predicate) error {
return r.controller.Watch(
err := r.controller.Watch(
source.Kind(
r.manager.GetCache(),
obj,
@ -168,6 +175,16 @@ func (r *Reconciler) Watch(obj ctrlCli.Object, eh handler.EventHandler, predicat
predicates...,
),
)
if err != nil {
return fmt.Errorf(
"error configuring watcher for resource %s:%s, reson: %w",
obj.GetObjectKind().GroupVersionKind().String(),
obj.GetName(),
err)
}
return nil
}
func (r *Reconciler) EnqueueRequestForOwner(owner ctrlCli.Object, opts ...handler.OwnerOption) handler.EventHandler {

View File

@ -72,11 +72,12 @@ func (a *ApplyCRDsAction) Run(ctx context.Context, rc *ReconciliationRequest) er
})
apply := rc.Resource.Generation != rc.Resource.Status.ObservedGeneration
_, err = dc.Get(ctx, obj.GetName(), metav1.GetOptions{})
_, err = dc.Get(ctx, obj.GetName(), metav1.GetOptions{})
if err != nil && !k8serrors.IsNotFound(err) {
return fmt.Errorf("cannot determine if CRD %s exists: %w", resources.Ref(&obj), err)
}
if err != nil && k8serrors.IsNotFound(err) {
apply = true
}
@ -105,7 +106,6 @@ func (a *ApplyCRDsAction) Run(ctx context.Context, rc *ReconciliationRequest) er
"apply", "true",
"gen", rc.Resource.Generation,
"ref", resources.Ref(&obj))
}
}

View File

@ -107,7 +107,6 @@ func (a *ApplyResourcesAction) Run(ctx context.Context, rc *ReconciliationReques
})
switch dc.(type) {
//
// NamespacedResource: in this case, filtering with ownership can be implemented
// as all the namespaced resources created by this controller have the Dapr CR as
@ -120,7 +119,6 @@ func (a *ApplyResourcesAction) Run(ctx context.Context, rc *ReconciliationReques
r := gvk.GroupVersion().String() + ":" + gvk.Kind
if _, ok := a.subscriptions[r]; !ok {
a.l.Info("watch", "ref", r)
err = rc.Reconciler.Watch(
@ -152,7 +150,6 @@ func (a *ApplyResourcesAction) Run(ctx context.Context, rc *ReconciliationReques
r := gvk.GroupVersion().String() + ":" + gvk.Kind
if _, ok := a.subscriptions[r]; !ok {
a.l.Info("watch", "ref", r)
err = rc.Reconciler.Watch(
@ -196,7 +193,6 @@ func (a *ApplyResourcesAction) Run(ctx context.Context, rc *ReconciliationReques
// - https://github.com/dapr/dapr/issues/3968
// - https://github.com/dapr/dapr/issues/6500
//
a.l.Info("run",
"apply", "false",
"ref", resources.Ref(&obj),
@ -284,9 +280,11 @@ func (a *ApplyResourcesAction) watchForUpdates(gvk schema.GroupVersionKind) bool
if gvk.Group == "" && gvk.Version == "v1" && gvk.Kind == "Secret" {
return false
}
if gvk.Group == "admissionregistration.k8s.io" && gvk.Version == "v1" && gvk.Kind == "MutatingWebhookConfiguration" {
return false
}
if gvk.Group == "apiextensions.k8s.io" && gvk.Version == "v1" && gvk.Kind == "CustomResourceDefinition" {
return false
}
@ -306,9 +304,11 @@ func (a *ApplyResourcesAction) installOnly(gvk schema.GroupVersionKind) bool {
if gvk.Group == "" && gvk.Version == "v1" && gvk.Kind == "Secret" {
return true
}
if gvk.Group == "admissionregistration.k8s.io" && gvk.Version == "v1" && gvk.Kind == "MutatingWebhookConfiguration" {
return true
}
if gvk.Group == "apiextensions.k8s.io" && gvk.Version == "v1" && gvk.Kind == "CustomResourceDefinition" {
return true
}

View File

@ -4,6 +4,8 @@ import (
"context"
"fmt"
"helm.sh/helm/v3/pkg/chart"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/dapr-sandbox/dapr-kubernetes-operator/api/operator/v1alpha1"
@ -40,33 +42,11 @@ func (a *ChartAction) Run(ctx context.Context, rc *ReconciliationRequest) error
rc.Resource.Status.Chart = &v1alpha1.ChartMeta{}
}
rc.Resource.Status.Chart.Repo = ChartRepoEmbedded
// TODO: maybe cache the chart
if rc.Resource.Spec.Chart != nil {
opts := helm.ChartOptions{}
opts.Name = rc.Resource.Spec.Chart.Name
opts.RepoURL = rc.Resource.Spec.Chart.Repo
opts.Version = rc.Resource.Spec.Chart.Version
if rc.Resource.Spec.Chart.Secret != "" {
s, err := rc.Client.CoreV1().Secrets(rc.Resource.Namespace).Get(
ctx,
rc.Resource.Spec.Chart.Secret,
metav1.GetOptions{},
)
if err != nil {
return fmt.Errorf("unable to fetch secret %s, %w", rc.Resource.Spec.Chart.Secret, err)
}
if v, ok := s.Data[ChartRepoUsernameKey]; ok {
opts.Username = string(v)
}
if v, ok := s.Data[ChartRepoPasswordKey]; ok {
opts.Password = string(v)
}
}
c, err := a.engine.Load(opts)
c, err := a.loadChart(ctx, rc)
if err != nil {
return err
}
@ -74,8 +54,6 @@ func (a *ChartAction) Run(ctx context.Context, rc *ReconciliationRequest) error
rc.Chart = c
rc.Resource.Status.Chart.Repo = rc.Resource.Spec.Chart.Repo
} else {
rc.Resource.Status.Chart.Repo = ChartRepoEmbedded
}
rc.Resource.Status.Chart.Version = rc.Chart.Metadata.Version
@ -87,3 +65,38 @@ func (a *ChartAction) Run(ctx context.Context, rc *ReconciliationRequest) error
func (a *ChartAction) Cleanup(_ context.Context, _ *ReconciliationRequest) error {
return nil
}
func (a *ChartAction) loadChart(ctx context.Context, rc *ReconciliationRequest) (*chart.Chart, error) {
opts := helm.ChartOptions{}
opts.Name = rc.Resource.Spec.Chart.Name
opts.RepoURL = rc.Resource.Spec.Chart.Repo
opts.Version = rc.Resource.Spec.Chart.Version
if rc.Resource.Spec.Chart.Secret != "" {
s, err := rc.Client.CoreV1().Secrets(rc.Resource.Namespace).Get(
ctx,
rc.Resource.Spec.Chart.Secret,
metav1.GetOptions{},
)
if err != nil {
return nil, fmt.Errorf("unable to fetch secret %s, %w", rc.Resource.Spec.Chart.Secret, err)
}
if v, ok := s.Data[ChartRepoUsernameKey]; ok {
opts.Username = string(v)
}
if v, ok := s.Data[ChartRepoPasswordKey]; ok {
opts.Password = string(v)
}
}
c, err := a.engine.Load(opts)
if err != nil {
//nolint:wrapcheck
return nil, err
}
return c, nil
}

View File

@ -52,6 +52,7 @@ func (a *ConditionsAction) Run(ctx context.Context, rc *ReconciliationRequest) e
}
ready := 0
for i := range deployments.Items {
if conditions.ConditionStatus(deployments.Items[i], appsv1.DeploymentAvailable) == corev1.ConditionTrue {
ready++

View File

@ -96,32 +96,30 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
return ctrl.Result{Requeue: true}, nil
}
return ctrl.Result{}, err
return ctrl.Result{}, fmt.Errorf("error updating DaprInstance resource: %w", err)
}
//nolint:nestif
if rr.Resource.ObjectMeta.DeletionTimestamp.IsZero() {
//
// Add finalizer
//
if ctrlutil.AddFinalizer(rr.Resource, DaprInstanceFinalizerName) {
if err := r.Update(ctx, rr.Resource); err != nil {
if k8serrors.IsConflict(err) {
return ctrl.Result{}, err
return ctrl.Result{}, fmt.Errorf("conflict when adding finalizer to %s: %w", req.NamespacedName, err)
}
return ctrl.Result{}, fmt.Errorf("failure adding finalizer to connector cluster %s: %w", req.NamespacedName, err)
return ctrl.Result{}, fmt.Errorf("failure adding finalizer to %s: %w", req.NamespacedName, err)
}
}
} else {
//
// Cleanup leftovers if needed
//
for i := len(r.actions) - 1; i >= 0; i-- {
if err := r.actions[i].Cleanup(ctx, &rr); err != nil {
//nolint:wrapcheck
return ctrl.Result{}, err
}
}
@ -129,11 +127,10 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
//
// Handle finalizer
//
if ctrlutil.RemoveFinalizer(rr.Resource, DaprInstanceFinalizerName) {
if err := r.Update(ctx, rr.Resource); err != nil {
if k8serrors.IsConflict(err) {
return ctrl.Result{}, err
return ctrl.Result{}, fmt.Errorf("conflict when removing finalizer to %s: %w", req.NamespacedName, err)
}
return ctrl.Result{}, fmt.Errorf("failure removing finalizer from %s: %w", req.NamespacedName, err)
@ -156,6 +153,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
}
errs := make([]error, 0, len(r.actions)+1)
for i := range r.actions {
if err := r.actions[i].Run(ctx, &rr); err != nil {
errs = append(errs, err)
@ -184,6 +182,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
//
err = r.Status().Update(ctx, rr.Resource)
if err != nil && k8serrors.IsConflict(err) {
l.Info(err.Error())
return ctrl.Result{Requeue: true}, nil

View File

@ -18,7 +18,6 @@ import (
)
func gcSelector(rc *ReconciliationRequest) (labels.Selector, error) {
namespace, err := labels.NewRequirement(
helm.ReleaseNamespace,
selection.Equals,
@ -59,10 +58,12 @@ func labelsToRequest(_ context.Context, object ctrlCli.Object) []reconcile.Reque
if allLabels == nil {
return nil
}
name := allLabels[helm.ReleaseName]
if name == "" {
return nil
}
namespace := allLabels[helm.ReleaseNamespace]
if namespace == "" {
return nil

View File

@ -28,7 +28,7 @@ type ReconciliationRequest struct {
}
type Action interface {
Configure(context.Context, *client.Client, *builder.Builder) (*builder.Builder, error)
Run(context.Context, *ReconciliationRequest) error
Cleanup(context.Context, *ReconciliationRequest) error
Configure(ctx context.Context, c *client.Client, b *builder.Builder) (*builder.Builder, error)
Run(ctx context.Context, rc *ReconciliationRequest) error
Cleanup(ctx context.Context, rc *ReconciliationRequest) error
}

View File

@ -35,6 +35,7 @@ func Get(from Getter, t ConditionType) *metav1.Condition {
return &condition
}
}
return nil
}

View File

@ -1,9 +1,9 @@
package client
import (
"fmt"
"time"
"github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/openshift"
"golang.org/x/time/rate"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/meta"
@ -23,6 +23,10 @@ import (
daprClient "github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/client/clientset/versioned"
)
const (
DiscoveryLimiterBurst = 30
)
var scaleConverter = scale.NewScaleConverter()
var codecs = serializer.NewCodecFactory(scaleConverter.Scheme())
@ -44,30 +48,34 @@ type Client struct {
}
func NewClient(cfg *rest.Config, scheme *runtime.Scheme, cc ctrl.Client) (*Client, error) {
discoveryCl, err := discovery.NewDiscoveryClientForConfig(cfg)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to construct a Discovery client: %w", err)
}
kubeCl, err := kubernetes.NewForConfig(cfg)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to construct a Kubernetes client: %w", err)
}
restCl, err := newRESTClientForConfig(cfg)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to construct a REST client: %w", err)
}
dynCl, err := dynamic.NewForConfig(cfg)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to construct a Dynamic client: %w", err)
}
daprCl, err := daprClient.NewForConfig(cfg)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to construct a Dapr client: %w", err)
}
apiextCl, err := apiextv1.NewForConfig(cfg)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to construct an API Extension client: %w", err)
}
c := Client{
@ -82,7 +90,7 @@ func NewClient(cfg *rest.Config, scheme *runtime.Scheme, cc ctrl.Client) (*Clien
rest: restCl,
}
c.discoveryLimiter = rate.NewLimiter(rate.Every(time.Second), 30)
c.discoveryLimiter = rate.NewLimiter(rate.Every(time.Second), DiscoveryLimiterBurst)
c.discoveryCache = memory.NewMemCacheClient(discoveryCl)
c.mapper = restmapper.NewDeferredDiscoveryRESTMapper(c.discoveryCache)
@ -94,20 +102,17 @@ func newRESTClientForConfig(config *rest.Config) (*rest.RESTClient, error) {
// so that the RESTClientFor doesn't complain
cfg.GroupVersion = &schema.GroupVersion{}
cfg.NegotiatedSerializer = codecs.WithoutConversion()
if len(cfg.UserAgent) == 0 {
cfg.UserAgent = rest.DefaultKubernetesUserAgent()
}
return rest.RESTClientFor(cfg)
}
// IsOpenShift returns true if we are connected to a OpenShift cluster.
func (c *Client) IsOpenShift() (bool, error) {
if c.Discovery == nil {
return false, nil
rc, err := rest.RESTClientFor(cfg)
if err != nil {
return nil, fmt.Errorf("unable to construct a REST client: %w", err)
}
return openshift.IsOpenShift(c.Discovery)
return rc, nil
}
func (c *Client) Dynamic(namespace string, obj *unstructured.Unstructured) (dynamic.ResourceInterface, error) {
@ -119,7 +124,11 @@ func (c *Client) Dynamic(namespace string, obj *unstructured.Unstructured) (dyna
mapping, err := c.mapper.RESTMapping(obj.GroupVersionKind().GroupKind(), obj.GroupVersionKind().Version)
if err != nil {
return nil, err
return nil, fmt.Errorf(
"unable to identify preferred resource mapping for %s/%s: %w",
obj.GroupVersionKind().GroupKind(),
obj.GroupVersionKind().Version,
err)
}
var dr dynamic.ResourceInterface

View File

@ -28,6 +28,11 @@ var (
Log = ctrl.Log.WithName("controller")
)
const (
PprofReadTimeout = 10 * time.Second
PprofWriteTimeout = 10 * time.Second
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(Scheme))
}
@ -64,6 +69,7 @@ func Start(options Options, setup func(manager.Manager, Options) error) error {
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
return fmt.Errorf("unable to set up health check: %w", err)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
return fmt.Errorf("unable to set up readiness check: %w", err)
}
@ -78,8 +84,8 @@ func Start(options Options, setup func(manager.Manager, Options) error) error {
server := &http.Server{
Addr: options.PprofAddr,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
ReadTimeout: PprofReadTimeout,
WriteTimeout: PprofWriteTimeout,
Handler: mux,
}

View File

@ -51,7 +51,6 @@ func (gc *GC) Run(ctx context.Context, ns string, c *client.Client, selector lab
}
func (gc *GC) deleteEachOf(ctx context.Context, c *client.Client, selector labels.Selector) (int, error) {
deleted := 0
for GVK := range gc.collectableGVKs {
@ -68,11 +67,14 @@ func (gc *GC) deleteEachOf(ctx context.Context, c *client.Client, selector label
if err := c.List(ctx, &items, options...); err != nil {
if k8serrors.IsForbidden(err) {
gc.l.Info("cannot gc, forbidden", "gvk", GVK.String())
continue
}
if !k8serrors.IsNotFound(err) {
return 0, fmt.Errorf("cannot list child resources %s: %w", GVK.String(), err)
}
continue
}
@ -133,7 +135,7 @@ func (gc *GC) computeDeletableTypes(ctx context.Context, ns string, c *client.Cl
// an aggregated API for custom.metrics.k8s.io that requires special
// authentication scheme while discovering preferred resources.
if err != nil && !discovery.IsGroupDiscoveryFailedError(err) {
return err
return fmt.Errorf("failure retireving supported namespaced resources: %w", err)
}
// We only take types that support the "delete" verb,
@ -150,22 +152,25 @@ func (gc *GC) computeDeletableTypes(ctx context.Context, ns string, c *client.Cl
ssrr, err = c.AuthorizationV1().SelfSubjectRulesReviews().Create(ctx, ssrr, metav1.CreateOptions{})
if err != nil {
return err
return fmt.Errorf("unable to create SelfSubjectRulesReviews: %w", err)
}
GVKs := make(map[schema.GroupVersionKind]struct{})
for _, res := range apiResourceLists {
for i := range res.APIResources {
resourceGroup := res.APIResources[i].Group
if resourceGroup == "" {
// Empty implies the group of the containing resource list should be used
gv, err := schema.ParseGroupVersion(res.GroupVersion)
if err != nil {
return err
return fmt.Errorf("failure creating SelfSubjectRulesReview: %w", err)
}
resourceGroup = gv.Group
}
rule:
for _, rule := range ssrr.Status.ResourceRules {
if !slices.Contains(rule.Verbs, "delete") && !slices.Contains(rule.Verbs, "*") {
@ -179,6 +184,7 @@ func (gc *GC) computeDeletableTypes(ctx context.Context, ns string, c *client.Cl
if gc.canBeDeleted(ctx, GVK) {
GVKs[GVK] = struct{}{}
}
break rule
}
}

View File

@ -30,6 +30,7 @@ func (p AnnotationChanged) Update(e event.UpdateEvent) bool {
log.Error(nil, "Update event has no old object to update", "event", e)
return false
}
if e.ObjectOld.GetAnnotations() == nil {
log.Error(nil, "Update event has no old object annotations to update", "event", e)
return false

View File

@ -33,6 +33,7 @@ func (p HasLabel) test(obj client.Object) bool {
if obj == nil {
return false
}
if obj.GetLabels() == nil {
return false
}

View File

@ -33,6 +33,7 @@ func (p StatusChanged) Update(e event.UpdateEvent) bool {
log.Error(nil, "Update event has no old object to update", "event", e)
return false
}
if e.ObjectNew == nil {
log.Error(nil, "Update event has no new object to update", "event", e)
return false

View File

@ -1,6 +1,8 @@
package customizers
import (
"fmt"
"github.com/itchyny/gojq"
)
@ -8,7 +10,7 @@ func JQ(expression string) func(map[string]any) (map[string]any, error) {
return func(in map[string]any) (map[string]any, error) {
query, err := gojq.Parse(expression)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to parse expression %s: %w", expression, err)
}
it := query.Run(in)
@ -23,7 +25,7 @@ func JQ(expression string) func(map[string]any) (map[string]any, error) {
}
if r, ok := v.(map[string]any); ok {
return r, err
return r, nil
}
return in, nil

View File

@ -60,10 +60,15 @@ func (e *Engine) Customizer(customizer ValuesCustomizer, customizers ...ValuesCu
func (e *Engine) Load(options ChartOptions) (*chart.Chart, error) {
path, err := options.LocateChart(options.Name, e.env)
if err != nil {
return nil, fmt.Errorf("unable to load chart (repo: %s, name: %s, version: %s), reson: %w", options.RepoURL, options.Name, options.Version, err)
return nil, fmt.Errorf("unable to load chart (repo: %s, name: %s, version: %s): %w", options.RepoURL, options.Name, options.Version, err)
}
return loader.Load(path)
c, err := loader.Load(path)
if err != nil {
return nil, fmt.Errorf("unable to load chart (repo: %s, name: %s, version: %s): %w", options.RepoURL, options.Name, options.Version, err)
}
return c, nil
}
func (e *Engine) Render(c *chart.Chart, dapr *daprApi.DaprInstance, overrides map[string]interface{}) ([]unstructured.Unstructured, error) {
@ -78,6 +83,7 @@ func (e *Engine) Render(c *chart.Chart, dapr *daprApi.DaprInstance, overrides ma
}
keys := make([]string, 0, len(files))
for k := range files {
if !strings.HasSuffix(k, ".yaml") && !strings.HasSuffix(k, ".yml") {
continue
@ -92,10 +98,12 @@ func (e *Engine) Render(c *chart.Chart, dapr *daprApi.DaprInstance, overrides ma
for _, k := range keys {
v := files[k]
ul, err := resources.Decode(e.decoder, []byte(v))
if err != nil {
return nil, fmt.Errorf("cannot decode %s: %w", k, err)
}
if ul == nil {
continue
}
@ -152,5 +160,4 @@ func (e *Engine) renderValues(
}
return rv, nil
}

View File

@ -5,6 +5,7 @@ import (
"k8s.io/apimachinery/pkg/selection"
)
//nolint:wrapcheck
func ReleaseSelector() (labels.Selector, error) {
hasReleaseNameLabel, err := labels.NewRequirement(ReleaseName, selection.Exists, []string{})
if err != nil {

View File

@ -1,6 +1,8 @@
package openshift
import (
"fmt"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/discovery"
)
@ -14,8 +16,10 @@ func IsOpenShift(client discovery.DiscoveryInterface) (bool, error) {
_, err := client.ServerResourcesForGroupVersion("route.openshift.io/v1")
if err != nil && k8serrors.IsNotFound(err) {
return false, nil
} else if err != nil {
return false, err
}
if err != nil {
return false, fmt.Errorf("unable to determine if the Kubernetes distro is OpenShift: %w", err)
}
return true, nil

View File

@ -108,6 +108,7 @@ func ToUnstructured(s *runtime.Scheme, obj runtime.Object) (*unstructured.Unstru
if err != nil {
return nil, fmt.Errorf("failed to convert to unstructured - unable to get GVK %w", err)
}
apiv, k := gvks[0].ToAPIVersionAndKind()
u.SetAPIVersion(apiv)
@ -133,19 +134,20 @@ func Decode(decoder runtime.Decoder, content []byte) ([]unstructured.Unstructure
break
}
return nil, err
return nil, fmt.Errorf("unable to decode resource: %w", err)
}
if len(out) == 0 {
continue
}
if out["Kind"] == "" {
continue
}
encoded, err := yaml.Marshal(out)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to marshal resource: %w", err)
}
var obj unstructured.Unstructured
@ -155,11 +157,10 @@ func Decode(decoder runtime.Decoder, content []byte) ([]unstructured.Unstructure
continue
}
return nil, err
return nil, fmt.Errorf("unable to decode resource: %w", err)
}
results = append(results, obj)
}
return results, nil

View File

@ -10,6 +10,14 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)
const (
DefaultProbeInitialDelay = 5
DefaultProbePeriod = 10
DefaultProbeTimeout = 10
DefaultProbeFailureThreshold = 10
DefaultProbeSuccessThreshold = 1
)
func WithOwnerReference(object client.Object) *metav1ac.OwnerReferenceApplyConfiguration {
return metav1ac.OwnerReference().
WithAPIVersion(object.GetObjectKind().GroupVersionKind().GroupVersion().String()).
@ -22,11 +30,11 @@ func WithOwnerReference(object client.Object) *metav1ac.OwnerReferenceApplyConfi
func WithHTTPProbe(path string, port int32) *corev1ac.ProbeApplyConfiguration {
return corev1ac.Probe().
WithInitialDelaySeconds(5).
WithPeriodSeconds(10).
WithFailureThreshold(10).
WithSuccessThreshold(1).
WithTimeoutSeconds(10).
WithInitialDelaySeconds(DefaultProbeInitialDelay).
WithPeriodSeconds(DefaultProbePeriod).
WithFailureThreshold(DefaultProbeFailureThreshold).
WithSuccessThreshold(DefaultProbeSuccessThreshold).
WithTimeoutSeconds(DefaultProbeTimeout).
WithHTTPGet(corev1ac.HTTPGetAction().
WithPath(path).
WithPort(intstr.IntOrString{IntVal: port}).

View File

@ -1,8 +1,6 @@
package maputils
import (
"errors"
"fmt"
"maps"
)
@ -18,23 +16,9 @@ func Merge(dst map[string]interface{}, source map[string]interface{}) map[string
}
}
}
out[k] = v
}
return out
}
func Lookup(m map[string]interface{}, ks ...string) (interface{}, error) {
if len(ks) == 0 { // degenerate input
return nil, errors.New("lookup needs at least one key")
}
if rval, ok := m[ks[0]]; !ok {
return nil, fmt.Errorf("key not found; remaining keys: %v", ks)
} else if len(ks) == 1 { // we've reached the final key
return rval, nil
} else if m, ok := rval.(map[string]interface{}); !ok {
return nil, fmt.Errorf("malformed structure at %#v", rval)
} else { // 1+ more keys
return Lookup(m, ks[1:]...)
}
}

View File

@ -16,6 +16,10 @@ import (
corev1 "k8s.io/api/core/v1"
)
const (
IngressPort = 8081
)
func ValidateDaprApp(test support.Test, namespace string) {
test.T().Helper()
@ -52,7 +56,7 @@ func ValidateDaprApp(test support.Test, namespace string) {
test.T().Logf("Testing the app with name %s", appName)
base := fmt.Sprintf("http://localhost:%d/%s", 8081, appName)
base := fmt.Sprintf("http://localhost:%d/%s", IngressPort, appName)
value := xid.New().String()
//nolint:bodyclose

View File

@ -1,74 +0,0 @@
package operator
import (
"os"
"testing"
"github.com/dapr-sandbox/dapr-kubernetes-operator/internal/controller/operator/controlplane"
"github.com/dapr-sandbox/dapr-kubernetes-operator/test/support/dapr"
"github.com/dapr-sandbox/dapr-kubernetes-operator/test/support/olm"
daprAc "github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/client/applyconfiguration/operator/v1alpha1"
daprTC "github.com/dapr-sandbox/dapr-kubernetes-operator/test/e2e/common"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
. "github.com/dapr-sandbox/dapr-kubernetes-operator/test/support"
. "github.com/onsi/gomega"
)
func TestDaprDeployWithControlPlane(t *testing.T) {
test := With(t)
ns := test.NewTestNamespace()
image := os.Getenv("CATALOG_CONTAINER_IMAGE")
test.Expect(image).
ToNot(BeEmpty())
//
// Install Operator
//
olm.DeployOperator(test, ns, image)
//
// Control plane
//
test.Eventually(Deployment(test, "dapr-control-plane", ns.Name), TestTimeoutLong).Should(
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
//
// Dapr
//
res := dapr.DeployControlPlane(
test,
daprAc.DaprControlPlaneSpec().
WithValues(dapr.Values(test, map[string]interface{}{
// enable pod watchdog as sometimes the sidecar for some
// (yet) unknown reason is not injected when the pod is
// created, hence the dapr app won't properly start up
"dapr_operator": map[string]interface{}{
"watchInterval": "1s",
},
})),
dapr.WithControlPlaneName(controlplane.DaprControlPlaneResourceName),
dapr.WithControlPlaneNamespace(ns.Name),
)
test.Eventually(Deployment(test, "dapr-operator", res.Namespace), TestTimeoutLong).Should(
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
test.Eventually(Deployment(test, "dapr-sentry", res.Namespace), TestTimeoutLong).Should(
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
test.Eventually(Deployment(test, "dapr-sidecar-injector", res.Namespace), TestTimeoutLong).Should(
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
//
// Dapr Application
//
daprTC.ValidateDaprApp(test, res.Namespace)
}

View File

@ -1,74 +0,0 @@
package operator
import (
"os"
"testing"
"github.com/dapr-sandbox/dapr-kubernetes-operator/internal/controller/operator/instance"
"github.com/dapr-sandbox/dapr-kubernetes-operator/test/support/dapr"
"github.com/dapr-sandbox/dapr-kubernetes-operator/test/support/olm"
daprAc "github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/client/applyconfiguration/operator/v1alpha1"
daprTC "github.com/dapr-sandbox/dapr-kubernetes-operator/test/e2e/common"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
. "github.com/dapr-sandbox/dapr-kubernetes-operator/test/support"
. "github.com/onsi/gomega"
)
func TestDaprDeployWithInstance(t *testing.T) {
test := With(t)
ns := test.NewTestNamespace()
image := os.Getenv("CATALOG_CONTAINER_IMAGE")
test.Expect(image).
ToNot(BeEmpty())
//
// Install Operator
//
olm.DeployOperator(test, ns, image)
//
// Control plane
//
test.Eventually(Deployment(test, "dapr-control-plane", ns.Name), TestTimeoutLong).Should(
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
//
// Dapr
//
res := dapr.DeployInstance(
test,
daprAc.DaprInstanceSpec().
WithValues(dapr.Values(test, map[string]interface{}{
// enable pod watchdog as sometimes the sidecar for some
// (yet) unknown reason is not injected when the pod is
// created, hence the dapr app won't properly start up
"dapr_operator": map[string]interface{}{
"watchInterval": "1s",
},
})),
dapr.WithInstanceName(instance.DaprInstanceResourceName),
dapr.WithInstanceNamespace(ns.Name),
)
test.Eventually(Deployment(test, "dapr-operator", res.Namespace), TestTimeoutLong).Should(
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
test.Eventually(Deployment(test, "dapr-sentry", res.Namespace), TestTimeoutLong).Should(
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
test.Eventually(Deployment(test, "dapr-sidecar-injector", res.Namespace), TestTimeoutLong).Should(
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
//
// Dapr Application
//
daprTC.ValidateDaprApp(test, res.Namespace)
}

View File

@ -0,0 +1,109 @@
package operator
import (
"os"
"testing"
"github.com/dapr-sandbox/dapr-kubernetes-operator/internal/controller/operator/controlplane"
"github.com/dapr-sandbox/dapr-kubernetes-operator/internal/controller/operator/instance"
daprAc "github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/client/applyconfiguration/operator/v1alpha1"
"github.com/dapr-sandbox/dapr-kubernetes-operator/test/support/dapr"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/dapr-sandbox/dapr-kubernetes-operator/test/support/olm"
daprTC "github.com/dapr-sandbox/dapr-kubernetes-operator/test/e2e/common"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
. "github.com/dapr-sandbox/dapr-kubernetes-operator/test/support"
. "github.com/onsi/gomega"
)
func TestDaprDeploy(t *testing.T) {
t.Run("With ControlPlane", func(t *testing.T) {
testDaprDeploy(
With(t),
func(test Test, ns *corev1.Namespace) client.Object {
return dapr.DeployControlPlane(
test,
daprAc.DaprControlPlaneSpec().
WithValues(dapr.Values(test, map[string]interface{}{
// enable pod watchdog as sometimes the sidecar for some
// (yet) unknown reason is not injected when the pod is
// created, hence the dapr app won't properly start up
"dapr_operator": map[string]interface{}{
"watchInterval": "1s",
},
})),
dapr.WithControlPlaneName(controlplane.DaprControlPlaneResourceName),
dapr.WithControlPlaneNamespace(ns.Name),
)
},
)
})
t.Run("With Instance", func(t *testing.T) {
testDaprDeploy(
With(t),
func(test Test, ns *corev1.Namespace) client.Object {
return dapr.DeployInstance(
test,
daprAc.DaprInstanceSpec().
WithValues(dapr.Values(test, map[string]interface{}{
// enable pod watchdog as sometimes the sidecar for some
// (yet) unknown reason is not injected when the pod is
// created, hence the dapr app won't properly start up
"dapr_operator": map[string]interface{}{
"watchInterval": "1s",
},
})),
dapr.WithInstanceName(instance.DaprInstanceResourceName),
dapr.WithInstanceNamespace(ns.Name),
)
},
)
})
}
func testDaprDeploy(test Test, f func(t Test, ns *corev1.Namespace) client.Object) {
test.T().Helper()
ns := test.NewTestNamespace()
image := os.Getenv("CATALOG_CONTAINER_IMAGE")
test.Expect(image).
ToNot(BeEmpty())
//
// Install Operator
//
olm.DeployOperator(test, ns, image)
//
// Control plane
//
test.Eventually(Deployment(test, "dapr-control-plane", ns.Name), TestTimeoutLong).Should(
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
//
// Dapr
//
res := f(test, ns)
test.Eventually(Deployment(test, "dapr-operator", res.GetNamespace()), TestTimeoutLong).Should(
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
test.Eventually(Deployment(test, "dapr-sentry", res.GetNamespace()), TestTimeoutLong).Should(
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
test.Eventually(Deployment(test, "dapr-sidecar-injector", res.GetNamespace()), TestTimeoutLong).Should(
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
//
// Dapr Application
//
daprTC.ValidateDaprApp(test, res.GetNamespace())
}

View File

@ -69,5 +69,4 @@ func TestDaprControlPlaneDeployWrongCR(t *testing.T) {
WithTransform(ConditionStatus(conditions.TypeReconciled), Equal(corev1.ConditionFalse)))
test.Eventually(dapr.ControlPlane(test, instance), TestTimeoutLong).Should(
WithTransform(ConditionReason(conditions.TypeReconciled), Equal(conditions.ReasonUnsupportedConfiguration)))
}

View File

@ -128,7 +128,6 @@ func TestDaprInstanceDeployWithCustomSidecarImage(t *testing.T) {
jq.Match(`.items[0].spec.containers[0].env[] | select(.name == "SIDECAR_IMAGE_PULL_POLICY") | .value == "%s"`, corev1.PullAlways),
)),
)
}
func TestDaprInstanceDeployWithApp(t *testing.T) {

View File

@ -19,6 +19,10 @@ var (
once sync.Once
)
const (
HTTPReadHeaderTimeout = 3 * time.Second
)
func init() {
appPort = os.Getenv("APP_PORT")
if appPort == "" {
@ -110,7 +114,7 @@ func main() {
server := &http.Server{
Addr: ":" + appPort,
ReadHeaderTimeout: 3 * time.Second,
ReadHeaderTimeout: HTTPReadHeaderTimeout,
}
err := server.ListenAndServe()

View File

@ -39,34 +39,37 @@ func New(t *testing.T) (*Client, error) {
cfg, err := config.GetConfig()
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to get Kubernetes config: %w", err)
}
discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg)
if err != nil {
return nil, err
}
dynamicClient, err := dynamic.NewForConfig(cfg)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to construct a Discovery client: %w", err)
}
kubeClient, err := kubernetes.NewForConfig(cfg)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to construct a Kubernetes client: %w", err)
}
extClient, err := apiextClient.NewForConfig(cfg)
dynamicClient, err := dynamic.NewForConfig(cfg)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to construct a Dynamic client: %w", err)
}
dpClient, err := daprClient.NewForConfig(cfg)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to construct a Dapr client: %w", err)
}
extClient, err := apiextClient.NewForConfig(cfg)
if err != nil {
return nil, fmt.Errorf("unable to construct an API Extension client: %w", err)
}
oClient, err := olmAC.NewForConfig(cfg)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to construct an OLM client: %w", err)
}
c := Client{
@ -102,12 +105,12 @@ func (c *Client) Scheme() *runtime.Scheme {
func (c *Client) RESTMapper() (meta.RESTMapper, error) {
gr, err := restmapper.GetAPIGroupResources(c.Discovery())
if err != nil {
return nil, err
return nil, fmt.Errorf("error computing API/Group resources, %w", err)
}
return restmapper.NewDiscoveryRESTMapper(gr), nil
}
func (c *Client) ForResource(in *unstructured.Unstructured) (dynamic.ResourceInterface, error) {
gvk := in.GetObjectKind().GroupVersionKind()
gk := schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}

View File

@ -2,6 +2,7 @@ package dapr
import (
"encoding/json"
"strconv"
"strings"
"github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/pointer"
@ -25,6 +26,11 @@ import (
metav1ac "k8s.io/client-go/applyconfigurations/meta/v1"
)
const (
TestAppPort = 8080
TestAppServicePort = 80
)
func DeployTestApp(t support.Test, name string, namespace string) {
t.T().Helper()
t.T().Logf("Setting up Dapr Application %s in namespace %s", name, namespace)
@ -103,7 +109,7 @@ func DeployTestApp(t support.Test, name string, namespace string) {
}).
WithAnnotations(map[string]string{
"dapr.io/app-id": name,
"dapr.io/app-port": "8080",
"dapr.io/app-port": strconv.Itoa(TestAppPort),
"dapr.io/enabled": "true",
"dapr.io/enable-api-logging": "true",
}).
@ -112,9 +118,9 @@ func DeployTestApp(t support.Test, name string, namespace string) {
WithImage("kind.local/dapr-test-app:latest").
WithImagePullPolicy(corev1.PullNever).
WithName("app").
WithPorts(resources.WithPort("http", 8080)).
WithReadinessProbe(resources.WithHTTPProbe("/health/readiness", 8080)).
WithLivenessProbe(resources.WithHTTPProbe("/health/liveness", 8080)).
WithPorts(resources.WithPort("http", TestAppPort)).
WithReadinessProbe(resources.WithHTTPProbe("/health/readiness", TestAppPort)).
WithLivenessProbe(resources.WithHTTPProbe("/health/liveness", TestAppPort)).
WithEnv(resources.WithEnv("STATESTORE_NAME", name)),
),
),
@ -148,8 +154,8 @@ func DeployTestApp(t support.Test, name string, namespace string) {
WithPorts(corev1ac.ServicePort().
WithName("http").
WithProtocol(corev1.ProtocolTCP).
WithPort(80).
WithTargetPort(intstr.FromInt32(8080))).
WithPort(TestAppServicePort).
WithTargetPort(intstr.FromInt32(TestAppPort))).
WithSelector(map[string]string{
"app": name,
}).
@ -177,6 +183,7 @@ func DeployTestApp(t support.Test, name string, namespace string) {
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
if !strings.HasSuffix(path, "(/|$)(.*)") {
path += "(/|$)(.*)"
}
@ -198,7 +205,7 @@ func DeployTestApp(t support.Test, name string, namespace string) {
WithService(netv1ac.IngressServiceBackend().
WithName(name).
WithPort(netv1ac.ServiceBackendPort().
WithNumber(80),
WithNumber(TestAppServicePort),
),
),
),
@ -220,7 +227,6 @@ func DeployTestApp(t support.Test, name string, namespace string) {
t.Cleanup(func() []runtime.Object {
return []runtime.Object{ing}
})
}
func Values(t support.Test, in map[string]any) *daprAc.JSONApplyConfiguration {
@ -239,5 +245,4 @@ func Values(t support.Test, in map[string]any) *daprAc.JSONApplyConfiguration {
cfg.RawMessage = data
return cfg
}

View File

@ -20,7 +20,6 @@ func GET(t support.Test, url string) func(g gomega.Gomega) (*http.Response, erro
}
func POST(t support.Test, url string, contentType string, content []byte) func(g gomega.Gomega) (*http.Response, error) {
return func(g gomega.Gomega) (*http.Response, error) {
data := content
if data == nil {

View File

@ -1,6 +1,7 @@
package helm
import (
"fmt"
"os"
"helm.sh/helm/v3/pkg/action"
@ -22,7 +23,6 @@ func WithLog(value func(string, ...interface{})) ConfigurationOption {
}
func New(options ...ConfigurationOption) (*Helm, error) {
settings := cli.New()
config := action.Configuration{}
@ -44,7 +44,7 @@ func New(options ...ConfigurationOption) (*Helm, error) {
err = config.Init(settings.RESTClientGetter(), settings.Namespace(), "memory", config.Log)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to initialize action config: %w", err)
}
h := Helm{

View File

@ -11,6 +11,10 @@ import (
"helm.sh/helm/v3/pkg/release"
)
const (
DefaultInstallTimeout = 10 * time.Minute
)
type InstallOption func(*ReleaseOptions[action.Install])
func (h *Helm) Install(ctx context.Context, chart string, options ...InstallOption) (*release.Release, error) {
@ -21,7 +25,7 @@ func (h *Helm) Install(ctx context.Context, chart string, options ...InstallOpti
client.IncludeCRDs = true
client.Wait = true
client.Namespace = xid.New().String()
client.Timeout = 10 * time.Minute
client.Timeout = DefaultInstallTimeout
io := ReleaseOptions[action.Install]{
Client: client,

View File

@ -2,6 +2,7 @@ package helm
import (
"context"
"fmt"
"helm.sh/helm/v3/pkg/action"
)
@ -24,7 +25,7 @@ func (h *Helm) Uninstall(_ context.Context, name string, options ...UninstallOpt
_, err := client.Run(name)
if err != nil {
return err
return fmt.Errorf("unabele to uninstall release %s: %w", name, err)
}
return nil

View File

@ -17,11 +17,14 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
CatalogRegistryPollInterval = 10 * time.Minute
)
func DeployOperator(test support.Test, ns *corev1.Namespace, image string) {
//
// Install OperatorGroups
//
og, err := test.Client().OLM().OperatorsV1().OperatorGroups(ns.Name).Create(
test.Ctx(),
&olmV1.OperatorGroup{
@ -91,7 +94,7 @@ func DeployOperator(test support.Test, ns *corev1.Namespace, image string) {
UpdateStrategy: &olmV1Alpha1.UpdateStrategy{
RegistryPoll: &olmV1Alpha1.RegistryPoll{
Interval: &metav1.Duration{
Duration: 10 * time.Minute,
Duration: CatalogRegistryPollInterval,
},
},
},
@ -172,9 +175,11 @@ func OperatorGroup(t support.Test, name string, namespace string) func(g gomega.
name,
metav1.GetOptions{},
)
if err != nil && !k8serrors.IsNotFound(err) {
return nil, err
}
if err != nil && k8serrors.IsNotFound(err) {
return nil, nil
}

View File

@ -3,7 +3,6 @@ package support
import (
"encoding/json"
"fmt"
"time"
"github.com/onsi/gomega"
@ -14,13 +13,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
const (
TestTimeoutMini = 5 * time.Second
TestTimeoutShort = 1 * time.Minute
TestTimeoutMedium = 2 * time.Minute
TestTimeoutLong = 5 * time.Minute
)
func runCleanup(t Test, in runtime.Object) error {
un, err := resources.ToUnstructured(t.Client().Scheme(), in)
if err != nil {
@ -37,6 +29,7 @@ func runCleanup(t Test, in runtime.Object) error {
if err != nil && !k8serrors.IsNotFound(err) {
return fmt.Errorf("failed to get current object, %w", err)
}
if err != nil && k8serrors.IsNotFound(err) {
return nil
}

View File

@ -127,6 +127,7 @@ func Resource(t Test, ri dynamic.ResourceInterface, un *unstructured.Unstructure
if err != nil && !k8serrors.IsNotFound(err) {
return nil, err
}
if err != nil && k8serrors.IsNotFound(err) {
return nil, nil
}

View File

@ -1,6 +1,7 @@
package support
import (
"github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/pointer"
"github.com/onsi/gomega"
"github.com/rs/xid"
corev1 "k8s.io/api/core/v1"
@ -29,9 +30,10 @@ func createTestNamespace(t Test, options ...Option[*corev1.Namespace]) *corev1.N
func deleteTestNamespace(t Test, namespace *corev1.Namespace) {
t.T().Helper()
propagationPolicy := metav1.DeletePropagationBackground
err := t.Client().CoreV1().Namespaces().Delete(t.Ctx(), namespace.Name, metav1.DeleteOptions{
PropagationPolicy: &propagationPolicy,
PropagationPolicy: pointer.Any(metav1.DeletePropagationBackground),
})
t.Expect(err).NotTo(gomega.HaveOccurred())
}

View File

@ -28,13 +28,27 @@ import (
olmV1Alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
)
const (
TestTimeoutMini = 5 * time.Second
TestTimeoutShort = 1 * time.Minute
TestTimeoutMedium = 2 * time.Minute
TestTimeoutLong = 5 * time.Minute
DefaultEventuallyPollingInterval = 500 * time.Millisecond
DefaultEventuallyTimeout = TestTimeoutLong
DefaultConsistentlyDuration = 500 * time.Millisecond
DefaultConsistentlyPollingInterval = 500 * time.Millisecond
)
func init() {
if err := daprApi.AddToScheme(scheme.Scheme); err != nil {
panic(err)
}
if err := olmV1.AddToScheme(scheme.Scheme); err != nil {
panic(err)
}
if err := olmV1Alpha1.AddToScheme(scheme.Scheme); err != nil {
panic(err)
}
@ -47,13 +61,13 @@ type Test interface {
Ctx() context.Context
ID() string
Cleanup(func() []runtime.Object)
Cleanup(f func() []runtime.Object)
Client() *supportclient.Client
Helm() *helmsupport.Helm
HTTPClient() *http.Client
NewTestNamespace(...Option[*corev1.Namespace]) *corev1.Namespace
NewTestNamespace(opts ...Option[*corev1.Namespace]) *corev1.Namespace
}
type Option[T any] interface {
@ -70,8 +84,10 @@ func With(t *testing.T) Test {
if deadline, ok := t.Deadline(); ok {
withDeadline, cancel := context.WithDeadline(ctx, deadline)
t.Cleanup(cancel)
ctx = withDeadline
}
answer := &T{
WithT: gomega.NewWithT(t),
id: xid.New().String(),
@ -81,10 +97,10 @@ func With(t *testing.T) Test {
cleanup: make([]func() []runtime.Object, 0),
}
answer.SetDefaultEventuallyPollingInterval(500 * time.Millisecond)
answer.SetDefaultEventuallyTimeout(TestTimeoutLong)
answer.SetDefaultConsistentlyDuration(500 * time.Millisecond)
answer.SetDefaultConsistentlyDuration(TestTimeoutLong)
answer.SetDefaultEventuallyPollingInterval(DefaultEventuallyPollingInterval)
answer.SetDefaultEventuallyTimeout(DefaultEventuallyTimeout)
answer.SetDefaultConsistentlyDuration(DefaultConsistentlyDuration)
answer.SetDefaultConsistentlyPollingInterval(DefaultConsistentlyPollingInterval)
t.Cleanup(func() {
t.Log("Run Test cleanup")
@ -146,8 +162,10 @@ func (t *T) Client() *supportclient.Client {
if err != nil {
t.T().Fatalf("Error creating client: %v", err)
}
t.client = c
})
return t.client
}