Skip to content
Draft
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
16 changes: 16 additions & 0 deletions internal/handlers/idler/helpers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package idler

import (
"strings"

"k8s.io/apimachinery/pkg/labels"
)

Expand All @@ -17,3 +19,17 @@ func generateLabelRequirements(selectors []idlerSelector) []labels.Requirement {
}
return labelRequirements
}

func addStatusCode(codes string, code string) *string {
if codes == "" {
return &code
}
parts := strings.Split(codes, ",")
for _, c := range parts {
if c == code {
return &codes
}
}
newCodes := codes + "," + code
return &newCodes
}
15 changes: 13 additions & 2 deletions internal/handlers/idler/service-kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ func (h *Idler) KubernetesServiceIdler(ctx context.Context, opLog logr.Logger, n
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// get the number of requests to any ingress in the exported namespace by status code
// @TODO: traefik_service_requests_total{exported_service="test-develop-nginx-http@kubernetes"}
promQuery := fmt.Sprintf(
`round(sum(increase(nginx_ingress_controller_requests{exported_namespace="%s",status=~"2[0-9x]{2}"}[%s])) by (status))`,
namespace.Name,
Expand Down Expand Up @@ -241,14 +242,24 @@ func (h *Idler) patchIngress(ctx context.Context, opLog logr.Logger, namespace c
for _, ingress := range ingressList.Items {
if !h.DryRun {
ingressCopy := ingress.DeepCopy()
var ingressCodes, traefikMiddlewares *string
ingressValue, ok := ingress.Annotations["nginx.ingress.kubernetes.io/custom-http-errors"]
if ok {
ingressCodes = addStatusCode(ingressValue, "503")
}
traefikValue, ok := ingress.Annotations["traefik.ingress.kubernetes.io/router.middlewares"]
if ok {
traefikMiddlewares = addStatusCode(traefikValue, fmt.Sprintf("%s-aergia@kubernetescrd", ingress.Namespace))
}
mergePatch, _ := json.Marshal(map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]string{
"idling.amazee.io/idled": "true",
},
"annotations": map[string]string{
"annotations": map[string]interface{}{
// add the custom-http-errors annotation so that the unidler knows to handle this ingress
"nginx.ingress.kubernetes.io/custom-http-errors": "503",
"nginx.ingress.kubernetes.io/custom-http-errors": ingressCodes,
"traefik.ingress.kubernetes.io/router.middlewares": traefikMiddlewares,
},
},
})
Expand Down
61 changes: 38 additions & 23 deletions internal/handlers/unidler/checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,39 +65,54 @@ func (h *Unidler) removeCodeFromIngress(ctx context.Context, ns string, opLog lo
})
ingresses := &networkv1.IngressList{}
if err := h.Client.List(ctx, ingresses, listOption); err != nil {
opLog.Info(fmt.Sprintf("Unable to get any deployments - %s", ns))
opLog.Info(fmt.Sprintf("Unable to get any ingress - %s", ns))
return
}
for _, ingress := range ingresses.Items {
// if the nginx.ingress.kubernetes.io/custom-http-errors annotation is set
// then strip out the 503 error code that is there so that
// users will see their application errors rather than the loading page
if value, ok := ingress.Annotations["nginx.ingress.kubernetes.io/custom-http-errors"]; ok {
newVals := removeStatusCode(value, "503")
var ingressCodes, traefikMiddlewares *string
patch := false
ingressValue, ok := ingress.Annotations["nginx.ingress.kubernetes.io/custom-http-errors"]
if ok {
ingressCodes = removeStatusCode(ingressValue, "503")
patch = true
}
traefikValue, ok := ingress.Annotations["traefik.ingress.kubernetes.io/router.middlewares"]
if ok {
traefikMiddlewares = removeStatusCode(traefikValue, fmt.Sprintf("%s-aergia@kubernetescrd", ingress.Namespace))
patch = true
}
if patch {
// if the 503 code was removed from the annotation
// then patch it
if newVals == nil || *newVals != value {
mergePatch, _ := json.Marshal(map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"idling.amazee.io/idled": "false",
},
"annotations": map[string]interface{}{
"nginx.ingress.kubernetes.io/custom-http-errors": newVals,
"idling.amazee.io/idled-at": nil,
},
annotations := map[string]interface{}{
"idling.amazee.io/idled-at": nil,
}
if ingressCodes == nil || *ingressCodes != ingressValue {
annotations["nginx.ingress.kubernetes.io/custom-http-errors"] = ingressCodes
}
if traefikMiddlewares == nil || *traefikMiddlewares != traefikValue {
annotations["traefik.ingress.kubernetes.io/router.middlewares"] = traefikMiddlewares
}
mergePatch, _ := json.Marshal(map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"idling.amazee.io/idled": "false",
},
})
patchIngress := ingress.DeepCopy()
if err := h.Client.Patch(ctx, patchIngress, ctrlClient.RawPatch(types.MergePatchType, mergePatch)); err != nil {
// log it but try and patch the rest of the ingressses anyway (some is better than none?)
opLog.Info(fmt.Sprintf("Error patching custom-http-errors on ingress %s - %s", ingress.Name, ns))
"annotations": annotations,
},
})
patchIngress := ingress.DeepCopy()
if err := h.Client.Patch(ctx, patchIngress, ctrlClient.RawPatch(types.MergePatchType, mergePatch)); err != nil {
// log it but try and patch the rest of the ingressses anyway (some is better than none?)
opLog.Info(fmt.Sprintf("Error patching custom-http-errors on ingress %s - %s", ingress.Name, ns))
} else {
if ingressCodes == nil {
opLog.Info(fmt.Sprintf("Ingress %s custom-http-errors annotation removed - %s", ingress.Name, ns))
} else {
if newVals == nil {
opLog.Info(fmt.Sprintf("Ingress %s custom-http-errors annotation removed - %s", ingress.Name, ns))
} else {
opLog.Info(fmt.Sprintf("Ingress %s custom-http-errors annotation patched with %s - %s", ingress.Name, *newVals, ns))
}
opLog.Info(fmt.Sprintf("Ingress %s custom-http-errors annotation patched with %s - %s", ingress.Name, *ingressCodes, ns))
}
}
}
Expand Down
57 changes: 47 additions & 10 deletions internal/handlers/unidler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"net/url"
"os"
"strconv"
"strings"
Expand All @@ -15,6 +16,7 @@ import (
corev1 "k8s.io/api/core/v1"
networkv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/types"
ctrlClient "sigs.k8s.io/controller-runtime/pkg/client"
)

func (h *Unidler) ingressHandler(path string) func(http.ResponseWriter, *http.Request) {
Expand Down Expand Up @@ -56,6 +58,21 @@ func (h *Unidler) ingressHandler(path string) func(http.ResponseWriter, *http.Re
w.WriteHeader(code)
ns := r.Header.Get(Namespace)
ingressName := r.Header.Get(IngressName)
hostname := ""
nsParam := r.URL.Query().Get("namespace")
if nsParam != "" {
opLog.Info(fmt.Sprintf("Namespace Param: %s", nsParam))
ns = nsParam
}
urlParam := r.URL.Query().Get("url")
if urlParam != "" {
url, err := url.Parse(urlParam)
if err != nil {
opLog.Info(fmt.Sprintf("URL Param err: %v", err))
}
opLog.Info(fmt.Sprintf("URL Param: %s", urlParam))
hostname = url.Hostname()
}
// check if the namespace exists so we know this is somewhat legitimate request
if ns != "" {
namespace := &corev1.Namespace{}
Expand All @@ -66,14 +83,34 @@ func (h *Unidler) ingressHandler(path string) func(http.ResponseWriter, *http.Re
return
}
ingress := &networkv1.Ingress{}
if err := h.Client.Get(ctx, types.NamespacedName{
Namespace: ns,
Name: ingressName,
}, ingress); err != nil {
opLog.Info(fmt.Sprintf("Unable to get the ingress %s in %s", ingressName, ns))
h.genericError(w, r, opLog, format, path, 400)
h.setMetrics(r, start)
return
if ingressName != "" {
if err := h.Client.Get(ctx, types.NamespacedName{
Namespace: ns,
Name: ingressName,
}, ingress); err != nil {
opLog.Info(fmt.Sprintf("Unable to get the ingress %s in %s", ingressName, ns))
h.genericError(w, r, opLog, format, path, 400)
h.setMetrics(r, start)
return
}
} else {
listOption := (&ctrlClient.ListOptions{}).ApplyOptions([]ctrlClient.ListOption{
ctrlClient.InNamespace(ns),
})
ingresses := &networkv1.IngressList{}
if err := h.Client.List(ctx, ingresses, listOption); err != nil {
opLog.Info(fmt.Sprintf("Unable to get any ingress - %s", ns))
return
}
for _, ingressss := range ingresses.Items {
for _, rule := range ingressss.Spec.Rules {
for _, host := range rule.Host {
if string(host) == hostname {
ingress = ingressss.DeepCopy()
}
}
}
}
}
// if hmac verification is enabled, perform the verification of the request
signedNamespace, verfied := h.verifyRequest(r, namespace, ingress)
Expand Down Expand Up @@ -129,8 +166,8 @@ func (h *Unidler) ingressHandler(path string) func(http.ResponseWriter, *http.Re
CodeHeader: r.Header.Get(CodeHeader),
ContentType: r.Header.Get(ContentType),
OriginalURI: r.Header.Get(OriginalURI),
Namespace: r.Header.Get(Namespace),
IngressName: r.Header.Get(IngressName),
Namespace: ns,
IngressName: ingress.Name,
ServiceName: r.Header.Get(ServiceName),
ServicePort: r.Header.Get(ServicePort),
RequestID: r.Header.Get(RequestID),
Expand Down
Loading