Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Controller helpers #56

Merged
merged 6 commits into from
May 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions runtime/controller/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
Copyright 2020 The Flux 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 controller offers embeddable structs for putting in your
// controller, to help with conforming to GitOps Toolkit conventions.
package controller
98 changes: 98 additions & 0 deletions runtime/controller/events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
Copyright 2020 The Flux 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 controller

import (
"context"

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
kuberecorder "k8s.io/client-go/tools/record"
"k8s.io/client-go/tools/reference"
ctrl "sigs.k8s.io/controller-runtime"

"github.com/fluxcd/pkg/runtime/events"
)

// Events is a helper struct that adds the capability of sending
// events to the Kubernetes API and to the GitOps Toolkit notification
// controller. You use it by embedding it in your reconciler struct:
//
// type MyTypeReconciler {
// client.Client
// // ... etc.
// controller.Events
// }
//
// You initialise a suitable value with MakeEvents; in most cases the
// value only needs to be initialized once per controller, as the
// specialised logger and object reference data are gathered from the
// arguments provided to the Eventf method.
type Events struct {
Scheme *runtime.Scheme
EventRecorder kuberecorder.EventRecorder
ExternalEventRecorder *events.Recorder
}

func MakeEvents(mgr ctrl.Manager, controllerName string, ext *events.Recorder) Events {
return Events{
Scheme: mgr.GetScheme(),
EventRecorder: mgr.GetEventRecorderFor(controllerName),
ExternalEventRecorder: ext,
}
}

type runtimeAndMetaObject interface {
runtime.Object
metav1.Object
}

// Event emits a Kubernetes event, and forwards the event to the
// notification controller if configured.
func (e Events) Event(ctx context.Context, obj runtimeAndMetaObject, metadata map[string]string, severity, reason, msg string) {
e.Eventf(ctx, obj, metadata, severity, reason, msg)
}

// Eventf emits a Kubernetes event, and forwards the event to the
// notification controller if configured.
func (e Events) Eventf(ctx context.Context, obj runtimeAndMetaObject, metadata map[string]string, severity, reason, msgFmt string, args ...interface{}) {
if e.EventRecorder != nil {
e.EventRecorder.Eventf(obj, severityToEventType(severity), reason, msgFmt, args...)
}
if e.ExternalEventRecorder != nil {
ref, err := reference.GetReference(e.Scheme, obj)
if err != nil {
logr.FromContextOrDiscard(ctx).Error(err, "unable to get object reference to send event")
return
}
if err := e.ExternalEventRecorder.Eventf(*ref, metadata, severity, reason, msgFmt, args...); err != nil {
logr.FromContextOrDiscard(ctx).Error(err, "unable to send event")
return
}
}
}

func severityToEventType(severity string) string {
switch severity {
case events.EventSeverityError:
return corev1.EventTypeWarning
default:
return corev1.EventTypeNormal
}
}
116 changes: 116 additions & 0 deletions runtime/controller/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
Copyright 2020 The Flux 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 controller

import (
"context"
"time"

"github.com/go-logr/logr"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/reference"
ctrl "sigs.k8s.io/controller-runtime"
crtlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"

"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/metrics"
)

// Metrics adds the capability for recording GOTK-standard metrics to
// a reconciler. Use by embedding into the reconciler struct:
//
// type MyTypeReconciler struct {
// client.Client
// // ...
// controller.Metrics
// }
//
// then you can call either or both of RecordDuration and
// RecordReadinessMetric. API types used in GOTK will usually
// already be suitable for passing (as a pointer) as the second
// argument to `RecordReadinessMetric`.
//
// When initialising controllers in main.go, use MustMakeMetrics to
// create a working Metrics value; you can supply the same value to
// all reconcilers.
type Metrics struct {
Scheme *runtime.Scheme
MetricsRecorder *metrics.Recorder
}

func MustMakeMetrics(mgr ctrl.Manager) Metrics {
metricsRecorder := metrics.NewRecorder()
crtlmetrics.Registry.MustRegister(metricsRecorder.Collectors()...)

return Metrics{
Scheme: mgr.GetScheme(),
MetricsRecorder: metricsRecorder,
}
}

func (m Metrics) RecordDuration(ctx context.Context, obj readinessMetricsable, startTime time.Time) {
if m.MetricsRecorder != nil {
ref, err := reference.GetReference(m.Scheme, obj)
if err != nil {
logr.FromContextOrDiscard(ctx).Error(err, "unable to get object reference to record duration")
return
}
m.MetricsRecorder.RecordDuration(*ref, startTime)
}
}

func (m Metrics) RecordSuspend(ctx context.Context, obj readinessMetricsable, suspend bool) {
if m.MetricsRecorder != nil {
ref, err := reference.GetReference(m.Scheme, obj)
if err != nil {
logr.FromContextOrDiscard(ctx).Error(err, "unable to get object reference to record suspend")
return
}
m.MetricsRecorder.RecordSuspend(*ref, suspend)
}
}

type readinessMetricsable interface {
runtime.Object
metav1.Object
meta.ObjectWithStatusConditions
}

func (m Metrics) RecordReadinessMetric(ctx context.Context, obj readinessMetricsable) {
m.RecordConditionMetric(ctx, obj, meta.ReadyCondition)
}

func (m Metrics) RecordConditionMetric(ctx context.Context, obj readinessMetricsable, conditionType string) {
if m.MetricsRecorder == nil {
return
}
ref, err := reference.GetReference(m.Scheme, obj)
if err != nil {
logr.FromContextOrDiscard(ctx).Error(err, "unable to get object reference to record condition metric")
return
}
rc := apimeta.FindStatusCondition(*obj.GetStatusConditions(), conditionType)
if rc == nil {
rc = &metav1.Condition{
Type: conditionType,
Status: metav1.ConditionUnknown,
}
}
m.MetricsRecorder.RecordCondition(*ref, *rc, !obj.GetDeletionTimestamp().IsZero())
}
10 changes: 7 additions & 3 deletions runtime/events/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import (

// Valid values for event severity.
const (
// Information only and will not cause any problems.
// EventSeverityInfo represents an informational event, usually
// informing about changes.
EventSeverityInfo string = "info"
// These events are to warn that something might go wrong.
// EventSeverityError represent an error event, usually a warning
// that something goes wrong.
EventSeverityError string = "error"
)

Expand All @@ -37,6 +39,7 @@ type Event struct {
InvolvedObject corev1.ObjectReference `json:"involvedObject"`

// Severity type of this event (info, error)
// +kubebuilder:validation:Enum=info;error
// +required
Severity string `json:"severity"`

Expand All @@ -45,7 +48,8 @@ type Event struct {
Timestamp metav1.Time `json:"timestamp"`

// A human-readable description of this event.
// Maximum length 39,000 characters
// Maximum length 39,000 characters.
// +kubebuilder:validation:MaxLength=39000
// +required
Message string `json:"message"`

Expand Down