Skip to content

Commit

Permalink
Merge pull request #7 from amazeeio/retry
Browse files Browse the repository at this point in the history
Add retry cron task to controller to attempt retry paused
  • Loading branch information
shreddedbacon authored Sep 23, 2021
2 parents 8b6b779 + edfe3cb commit aacd301
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 5 deletions.
13 changes: 10 additions & 3 deletions controllers/ingress_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (r *IngressReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// }
serviceID := ingress.ObjectMeta.Annotations["fastly.amazee.io/service-id"]
paused := "false"
if ingress.ObjectMeta.Annotations["fastly.amazee.io/paused"] == "true" {
if ingress.ObjectMeta.Labels["fastly.amazee.io/paused"] == "true" {
paused = "true"
}
// deleteexternal prevents the controller from deleting anything in fastly or in cluster
Expand Down Expand Up @@ -591,7 +591,10 @@ func (r *IngressReconciler) patchSecret(
annotations := map[string]interface{}{
"fastly.amazee.io/service-id": fastlyConfig.ServiceID,
"fastly.amazee.io/watch": "true",
"fastly.amazee.io/paused": fmt.Sprintf("%v", paused),
"fastly.amazee.io/paused": nil,
}
labels := map[string]interface{}{
"fastly.amazee.io/paused": fmt.Sprintf("%v", paused),
}
// add the custom api secret to this ingress secret if one is provided by the ingress
// this is so that actions on the ingress secret can be taken against the fastly api if not using the default
Expand All @@ -602,6 +605,7 @@ func (r *IngressReconciler) patchSecret(
mergePatch, err := json.Marshal(map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": annotations,
"labels": labels,
},
})
if err != nil {
Expand Down Expand Up @@ -629,10 +633,13 @@ func (r *IngressReconciler) patchPausedStatus(
mergePatch, err := json.Marshal(map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": map[string]interface{}{
"fastly.amazee.io/paused": fmt.Sprintf("%v", paused),
"fastly.amazee.io/paused": nil,
"fastly.amazee.io/paused-reason": reason,
"fastly.amazee.io/paused-at": time.Now().UTC().Format("2006-01-02 15:04:05"),
},
"labels": map[string]interface{}{
"fastly.amazee.io/paused": fmt.Sprintf("%v", paused),
},
},
})
if err != nil {
Expand Down
7 changes: 5 additions & 2 deletions controllers/ingresssecret_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (r *IngressSecretReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
// pausing prevents the controller from acting on this object
// it prevents anything happening in fastly
paused := "false"
if ingressSecret.ObjectMeta.Annotations["fastly.amazee.io/paused"] == "true" {
if ingressSecret.ObjectMeta.Labels["fastly.amazee.io/paused"] == "true" {
paused = "true"
}
// deleteexternal prevents the controller from deleting anything in fastly or in cluster
Expand Down Expand Up @@ -424,10 +424,13 @@ func (r *IngressSecretReconciler) patchPausedStatus(
mergePatch, err := json.Marshal(map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": map[string]interface{}{
"fastly.amazee.io/paused": fmt.Sprintf("%v", paused),
"fastly.amazee.io/paused": nil,
"fastly.amazee.io/paused-reason": reason,
"fastly.amazee.io/paused-at": time.Now().UTC().Format("2006-01-02 15:04:05"),
},
"labels": map[string]interface{}{
"fastly.amazee.io/paused": fmt.Sprintf("%v", paused),
},
},
})
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.13
require (
github.com/fastly/go-fastly v1.18.0
github.com/go-logr/logr v0.1.0
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5
k8s.io/api v0.0.0-20190918155943-95b840bb6a1f
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,8 @@ gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5 h1:E846t8CnR+lv5nE+VuiKTDG/v1U2stad0QzddfJC7kY=
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5/go.mod h1:hiOFpYm0ZJbusNj2ywpbrXowU3G8U6GIQzqn2mw1UIE=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
Expand Down
105 changes: 105 additions & 0 deletions handlers/paused.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package handlers

import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"

corev1 "k8s.io/api/core/v1"
networkv1beta1 "k8s.io/api/networking/v1beta1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type cleanup interface {
CheckPausedCertStatus()
}

// Cleanup is used for cleaning up old pods or resources.
type Cleanup struct {
Client client.Client
EnableDebug bool
}

// NewCleanup returns a cleanup with controller-runtime client.
func NewCleanup(client client.Client, enableDebug bool) *Cleanup {
return &Cleanup{
Client: client,
EnableDebug: enableDebug,
}
}

// CheckPausedCertStatus is a cronjob that will periodically check for paused status on ingresses that have failed to
// upload their certificate to fastly and will unpause them to allow them to retry
// after 5 attempts it will give up
func (h *Cleanup) CheckPausedCertStatus() {
fmt.Println("paused")
opLog := ctrl.Log.WithName("handlers").WithName("PausedCertStatusCheck")
namespaces := &corev1.NamespaceList{}
if err := h.Client.List(context.Background(), namespaces); err != nil {
opLog.Error(err, fmt.Sprintf("Unable to list namespaces created by Lagoon, there may be none or something went wrong"))
return
}
for _, ns := range namespaces.Items {
opLog.Info(fmt.Sprintf("Checking LagoonBuilds in namespace %s", ns.ObjectMeta.Name))
ingresses := &networkv1beta1.IngressList{}
listOption := (&client.ListOptions{}).ApplyOptions([]client.ListOption{
client.InNamespace(ns.ObjectMeta.Name),
client.MatchingLabels(map[string]string{
"fastly.amazee.io/paused": "true",
}),
})
if err := h.Client.List(context.Background(), ingresses, listOption); err != nil {
opLog.Error(err, fmt.Sprintf("Unable to list Ingress resource in namespace, there may be none or something went wrong"))
return
}
for _, ingress := range ingresses.Items {
// check if a reason exists
if reason, ok := ingress.ObjectMeta.Annotations["fastly.amazee.io/paused-reason"]; ok {
// always set retryCount to 0
retryCount := 0
// then read the value in from the annotation if it is present and set the value to it
if retryValue, ok := ingress.ObjectMeta.Annotations["fastly.amazee.io/paused-retry-count"]; ok {
if i, err := strconv.Atoi(retryValue); err == nil {
retryCount = i
}
}
// and if the reason is unable to find a secret
if strings.Contains(reason, "Unable to find secret of") {
// then attempt the process to fix it, but give up after 5 attempts
if retryCount <= 5 {
//increment the retry count by 1
retryCount = retryCount + 1

// set the paused status to false, and upsert the retry-count into the annotations
mergePatch, err := json.Marshal(map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": map[string]interface{}{
"fastly.amazee.io/paused": nil,
"fastly.amazee.io/paused-retry-count": fmt.Sprintf("%d", retryCount),
},
"labels": map[string]interface{}{
"fastly.amazee.io/paused": "false",
},
},
})
if err != nil {
opLog.Info(fmt.Sprintf("Unable to create mergepatch for %s, error was: %v", ingress.ObjectMeta.Name, err))
continue
}
// patch the ingress so that the controller will attemp to run through its process
if err := h.Client.Patch(context.Background(), &ingress, client.ConstantPatch(types.MergePatchType, mergePatch)); err != nil {
opLog.Info(fmt.Sprintf("Unable to patch ingress %s, error was: %v", ingress.ObjectMeta.Name, err))
continue
}
opLog.Info(fmt.Sprintf("Patched ingress %s", ingress.ObjectMeta.Name))
}
}
}
}
}
return
}
37 changes: 37 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@ import (
"flag"
"fmt"
"os"
"strconv"

"github.com/amazeeio/fastly-controller/controllers"
"github.com/amazeeio/fastly-controller/handlers"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log/zap"

"gopkg.in/robfig/cron.v2"
// +kubebuilder:scaffold:imports
)

Expand All @@ -46,6 +50,9 @@ func main() {
var fastlyAPIToken string
var fastlyPlatformTLSConfiguration string
var clusterName string
var enablePausedStatusCron bool
var pausedStatusCron string

flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
Expand All @@ -55,12 +62,18 @@ func main() {
"The default Fastly PlatformTLS ID to use.")
flag.StringVar(&clusterName, "cluster-name", "",
"The name of the cluster the controller is deployed in.")
flag.BoolVar(&enablePausedStatusCron, "enable-paused-status-cron", false,
"Enable the paused status cron check for ingresses.")
flag.StringVar(&pausedStatusCron, "paused-status-cron", "*/5 * * * *",
"The cron definition for checking paused ingresses.")
flag.Parse()

// set a global API token for all requests, otherwise annotation will be used
fastlyAPIToken = getEnv("FASTLY_API_TOKEN", fastlyAPIToken)
fastlyPlatformTLSConfiguration = getEnv("FASTLY_PLATFORM_TLS_CONFIGURATION_ID", fastlyPlatformTLSConfiguration)
clusterName = getEnv("CLUSTER_NAME", clusterName)
enablePausedStatusCron = getEnvBool("ENABEL_PAUSED_STATUS_CRON", enablePausedStatusCron)
pausedStatusCron = getEnv("PAUSED_STATUS_CRON", pausedStatusCron)

if fastlyAPIToken == "" {
setupLog.Error(fmt.Errorf("%s", "Environment variable FASTLY_API_TOKEN not set"), "unable to start manager")
Expand Down Expand Up @@ -90,6 +103,20 @@ func main() {
os.Exit(1)
}

resourceCleanup := handlers.NewCleanup(
mgr.GetClient(),
true,
)
c := cron.New()
setupLog.Info("setting paused status check cron") // use cron to run a paused status check
// this will check any `Ingress` resources for the paused status
// and attempt to unpause them
if enablePausedStatusCron {
c.AddFunc(pausedStatusCron, func() {
resourceCleanup.CheckPausedCertStatus()
})
}

// +kubebuilder:scaffold:builder

// start the ingress monitor controller
Expand Down Expand Up @@ -131,3 +158,13 @@ func getEnv(key, fallback string) string {
}
return fallback
}

// accepts fallback values 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False
// anything else is false.
func getEnvBool(key string, fallback bool) bool {
if value, ok := os.LookupEnv(key); ok {
rVal, _ := strconv.ParseBool(value)
return rVal
}
return fallback
}

0 comments on commit aacd301

Please sign in to comment.