Skip to content

Commit

Permalink
Merge pull request #10 from IMGARENA/BAU_ingress_controller
Browse files Browse the repository at this point in the history
Add Ingress Controller annotations feature
  • Loading branch information
akosveres authored Jun 27, 2022
2 parents 0ac242e + e4896fe commit e711bde
Show file tree
Hide file tree
Showing 8 changed files with 461 additions and 13 deletions.
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

A kubernetes operator for [checklyhq.com](https://checklyhq.com).

The operator can create checklyhq.com checks and groups based of kubernetes CRDs.
The operator can create checklyhq.com checks and groups based of kubernetes CRDs and Ingress object annotations.

## Development

Expand All @@ -27,11 +27,18 @@ Once you have all the files, you can just apply the generated manifest files:
kubectl apply -f install-(version).yaml
```

### Ingress configuration

We support reading configuration from ingress resources, take a look at the [samples](config/samples/) directory.

> **Warning**
> Groups have to exist before the Ingress object, so please at least apply 1 group CRD.
## Running locally

## Versions

We're using the following versions of packaes:
We're using the following versions of packages:
* operator-sdk 1.22.0
* golang 1.18

Expand Down
2 changes: 0 additions & 2 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ rules:
resources:
- ingresses
verbs:
- create
- delete
- get
- list
- patch
Expand Down
2 changes: 1 addition & 1 deletion config/samples/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Append samples you want in your CSV to this file as resources ##
resources:
# - networking_v1_ingress.yaml
- networking_v1_ingress.yaml
- checkly_v1alpha1_apicheck.yaml
- checkly_v1alpha1_group.yaml
#+kubebuilder:scaffold:manifestskustomizesamples
23 changes: 23 additions & 0 deletions config/samples/network_v1_ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-sample
annotations:
checkly.imgarena.com/enabled: "true"
checkly.imgarena.com/path: "/baz"
# checkly.imgarena.com/endpoint: "foo.baaz" - Default read from spec.rules[0].host
# checkly.imgarena.com/success: "200" - Default "200"
checkly.imgarena.com/group: "group-sample"
spec:
rules:
- host: "foo.bar"
http:
paths:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: test-service
port:
number: 8080
1 change: 0 additions & 1 deletion controllers/checkly/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ var _ = BeforeSuite(func() {
}

var err error
// cfg is defined in this file globally.
cfg, err := testEnv.Start()
Expect(err).NotTo(HaveOccurred())
Expect(cfg).NotTo(BeNil())
Expand Down
140 changes: 133 additions & 7 deletions controllers/networking/ingress_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ package networking

import (
"context"
"fmt"

checklyv1alpha1 "github.com/imgarena/checkly-operator/apis/checkly/v1alpha1"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand All @@ -32,23 +36,95 @@ type IngressReconciler struct {
Scheme *runtime.Scheme
}

//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;update;patch
//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses/finalizers,verbs=update
//+kubebuilder:rbac:groups=checkly.imgarena.com,resources=apichecks,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=checkly.imgarena.com,resources=apichecks/status,verbs=get;update;patch

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the Ingress object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
func (r *IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = log.FromContext(ctx)
logger := log.FromContext(ctx)
logger.Info("Reconciler started")

// TODO(user): your logic here
ingress := &networkingv1.Ingress{}
apiCheck := &checklyv1alpha1.ApiCheck{}

// Check if ingress object is still present
err := r.Get(ctx, req.NamespacedName, ingress)
if err != nil {
if errors.IsNotFound(err) {
logger.Info("Ingress got deleted")
return ctrl.Result{}, nil
}
logger.Error(err, "Can't read the Ingress object")
return ctrl.Result{}, err
}
logger.Info("Ingress Object found")

// Check if annotation is present on the object
checklyAnnotation := ingress.Annotations["checkly.imgarena.com/enabled"] == "true"
if !checklyAnnotation {
// Annotation may have been removed or updated, we have to determine if we need to delete a previously created ApiCheck resource
logger.Info("annotation is not present, checking if ApiCheck was created")
err = r.Get(ctx, req.NamespacedName, apiCheck)
if err != nil {
logger.Info("Apicheck not present")
return ctrl.Result{}, nil
}
logger.Info("ApiCheck is present, but we need to delete it")
err = r.Delete(ctx, apiCheck)
if err != nil {
logger.Info("Failed to delete ApiCheck")
return ctrl.Result{}, err
// }
}

return ctrl.Result{}, nil
}

// Gather data for the checkly check
apiCheckSpec, err := r.gatherApiCheckData(ingress)
if err != nil {
logger.Info("unable to gather data for the apiCheck resource")
return ctrl.Result{}, err
}

// Check and see if the ApiCheck has been created before
err = r.Get(ctx, req.NamespacedName, apiCheck)
if err == nil {
logger.Info("apiCheck exists, doing an update")
// We can reference the exiting apiCheck object that the server returned
apiCheck.Spec = apiCheckSpec
err = r.Update(ctx, apiCheck)
if err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}

// Create apiCheck
// We need to write the k8s spec resources as it is a new object
newApiCheck := &checklyv1alpha1.ApiCheck{
ObjectMeta: metav1.ObjectMeta{
Name: ingress.Name,
Namespace: ingress.Namespace,
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(ingress, networkingv1.SchemeGroupVersion.WithKind("ingress")),
},
},
Spec: apiCheckSpec,
}

err = r.Create(ctx, newApiCheck)
if err != nil {
logger.Info("Failed to create ApiCheck", "err", err)
return ctrl.Result{}, err
}

return ctrl.Result{}, nil
}
Expand All @@ -59,3 +135,53 @@ func (r *IngressReconciler) SetupWithManager(mgr ctrl.Manager) error {
For(&networkingv1.Ingress{}).
Complete(r)
}

func (r *IngressReconciler) gatherApiCheckData(ingress *networkingv1.Ingress) (apiCheckSpec checklyv1alpha1.ApiCheckSpec, err error) {

annotationHost := "checkly.imgarena.com"
annotationPath := fmt.Sprintf("%s/path", annotationHost)
annotationEndpoint := fmt.Sprintf("%s/endpoint", annotationHost)
annotationSuccess := fmt.Sprintf("%s/success", annotationHost)
annotationGroup := fmt.Sprintf("%s/group", annotationHost)

// Construct the endpoint
path := ""
if ingress.Annotations[annotationPath] != "" {
path = ingress.Annotations[annotationPath]
}

var host string
if ingress.Annotations[annotationEndpoint] == "" {
host = ingress.Spec.Rules[0].Host
} else {
host = ingress.Annotations[annotationEndpoint]
}

endpoint := fmt.Sprintf("https://%s%s", host, path)

// Expected success code
var success string
if ingress.Annotations[annotationSuccess] != "" {
success = ingress.Annotations[annotationSuccess]
} else {
success = "200"
}

// Group
var group string
if ingress.Annotations[annotationGroup] != "" {
group = ingress.Annotations[annotationGroup]
} else {
err = fmt.Errorf("could not find a value for the group annotation, can't continue without one")
}

apiCheckSpec = checklyv1alpha1.ApiCheckSpec{
Endpoint: endpoint,
Group: group,
Team: "group-test",
Success: success,
}

// Last return
return
}
Loading

0 comments on commit e711bde

Please sign in to comment.