diff --git a/Gopkg.lock b/Gopkg.lock index 7c8b888a..b1fa4578 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -37,12 +37,12 @@ revision = "3a771d992973f24aa725d07868b467d1ddfceafb" [[projects]] - digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39" + digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" name = "github.com/davecgh/go-spew" packages = ["spew"] pruneopts = "UT" - revision = "346938d642f2ec3594ed81d874461961cd0faa76" - version = "v1.1.0" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" [[projects]] digest = "1:2cd7915ab26ede7d95b8749e6b1f933f1c6d5398030684e6505940a10f31cfda" @@ -72,7 +72,15 @@ revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" [[projects]] - digest = "1:17fe264ee908afc795734e8c4e63db2accabaf57326dbf21763a7d6b86096260" + branch = "master" + digest = "1:3fb07f8e222402962fa190eb060608b34eddfb64562a18e2167df2de0ece85d8" + name = "github.com/golang/groupcache" + packages = ["lru"] + pruneopts = "UT" + revision = "24b0969c4cb722950103eed87108c8d291a8df00" + +[[projects]] + digest = "1:4c0989ca0bcd10799064318923b9bc2db6b4d6338dd75f3f2d86c3511aaaf5cf" name = "github.com/golang/protobuf" packages = [ "proto", @@ -82,16 +90,16 @@ "ptypes/timestamp", ] pruneopts = "UT" - revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265" - version = "v1.1.0" + revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" + version = "v1.2.0" [[projects]] branch = "master" - digest = "1:9887333bbef17574b1db5f9893ea137ac44107235d624408a3ac9e0b98fbb2cb" + digest = "1:0bfbe13936953a98ae3cfe8ed6670d396ad81edf069a806d2f6515d7bb6950df" name = "github.com/google/btree" packages = ["."] pruneopts = "UT" - revision = "e89373fe6b4a7413d7acd6da1725b83ef713e6e4" + revision = "4030bb1f1f0c35b30ca7009e9ebd06849dd45306" [[projects]] digest = "1:d2754cafcab0d22c13541618a8029a70a8959eb3525ff201fe971637e2274cd0" @@ -139,23 +147,23 @@ revision = "9cad4c3443a7200dd6400aef47183728de563a38" [[projects]] - branch = "master" - digest = "1:cf296baa185baae04a9a7004efee8511d08e2f5f51d4cbe5375da89722d681db" + digest = "1:8ec8d88c248041a6df5f6574b87bc00e7e0b493881dad2e7ef47b11dc69093b5" name = "github.com/hashicorp/golang-lru" packages = [ ".", "simplelru", ] pruneopts = "UT" - revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3" + revision = "20f1fb78b0740ba8c3cb143a61e86ba5c8669768" + version = "v0.5.0" [[projects]] - digest = "1:3e260afa138eab6492b531a3b3d10ab4cb70512d423faa78b8949dec76e66a21" + digest = "1:8eb1de8112c9924d59bf1d3e5c26f5eaa2bfc2a5fcbb92dc1c2e4546d695f277" name = "github.com/imdario/mergo" packages = ["."] pruneopts = "UT" - revision = "9316a62528ac99aaecb4e47eadd6dc8aa6533d58" - version = "v0.3.5" + revision = "9f23e2d6bd2a77f959b2bf6acdbefd708a83a4a4" + version = "v0.3.6" [[projects]] digest = "1:bb3cc4c1b21ea18cfa4e3e47440fc74d316ab25b0cf42927e8c1274917bd9891" @@ -233,7 +241,7 @@ [[projects]] branch = "master" - digest = "1:e469cd65badf7694aeb44874518606d93c1d59e7735d3754ad442782437d3cc3" + digest = "1:63b68062b8968092eb86bedc4e68894bd096ea6b24920faca8b9dcf451f54bb5" name = "github.com/prometheus/common" packages = [ "expfmt", @@ -241,11 +249,11 @@ "model", ] pruneopts = "UT" - revision = "7600349dcfe1abd18d72d3a1770870d9800a7801" + revision = "c7de2306084e37d54b8be01f3541a8464345e9a5" [[projects]] branch = "master" - digest = "1:20d9bb50dbee172242f9bcd6ec24a917dd7a5bb17421bf16a79c33111dea7db1" + digest = "1:8c49953a1414305f2ff5465147ee576dd705487c35b15918fcd4efdc0cb7a290" name = "github.com/prometheus/procfs" packages = [ ".", @@ -254,23 +262,23 @@ "xfs", ] pruneopts = "UT" - revision = "ae68e2d4c00fed4943b5f6698d504a5fe083da8a" + revision = "05ee40e3a273f7245e8777337fc7b46e533a9a92" [[projects]] - digest = "1:9e9193aa51197513b3abcb108970d831fbcf40ef96aa845c4f03276e1fa316d2" + digest = "1:d867dfa6751c8d7a435821ad3b736310c2ed68945d05b50fb9d23aee0540c8cc" name = "github.com/sirupsen/logrus" packages = ["."] pruneopts = "UT" - revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc" - version = "v1.0.5" + revision = "3e01752db0189b9157070a0e1668a620f9a85da2" + version = "v1.0.6" [[projects]] - digest = "1:9424f440bba8f7508b69414634aef3b2b3a877e522d8a4624692412805407bb7" + digest = "1:dab83a1bbc7ad3d7a6ba1a1cc1760f25ac38cdf7d96a5cdd55cd915a4f5ceaf9" name = "github.com/spf13/pflag" packages = ["."] pruneopts = "UT" - revision = "583c0c0531f06d5278b7d917446061adc344b5cd" - version = "v1.0.1" + revision = "9a97c102cda95a86cec2345a6f09f55a939babf5" + version = "v1.0.2" [[projects]] digest = "1:18752d0b95816a1b777505a97f71c7467a8445b8ffb55631a7bf779f6ba4fa83" @@ -286,11 +294,11 @@ name = "golang.org/x/crypto" packages = ["ssh/terminal"] pruneopts = "UT" - revision = "a2144134853fc9a27a7b1e3eb4f19f1a76df13c9" + revision = "0e37d006457bf46f9e6692014ba72ef82c33022c" [[projects]] branch = "master" - digest = "1:6825aeffd9e88e2da7fe95bf767528e0c813d81dab2423d2a4e16ab3acdf05bf" + digest = "1:d1209fa9ed40a3a24de8498a9bead59eb3333f16a73fa23ba1fc6677a3cfc9d7" name = "golang.org/x/net" packages = [ "context", @@ -300,7 +308,7 @@ "idna", ] pruneopts = "UT" - revision = "a680a1efc54dd51c040b3b5ce4939ea3cf2ea0d1" + revision = "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2" [[projects]] branch = "master" @@ -312,14 +320,14 @@ [[projects]] branch = "master" - digest = "1:3364d01296ce7eeca363e3d530ae63a2092d6f8efb85fb3d101e8f6d7de83452" + digest = "1:374fc90fcb026e9a367e3fad29e988e5dd944b68ca3f24a184d77abc5307dda4" name = "golang.org/x/sys" packages = [ "unix", "windows", ] pruneopts = "UT" - revision = "ac767d655b305d4e9612f5f6e33120b9176c4ad4" + revision = "d0be0721c37eeb5299f245a996a483160fc36940" [[projects]] digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" @@ -378,7 +386,7 @@ [[projects]] branch = "master" - digest = "1:3b4f29485072d6af75536e5dd9d5480400e083a4ff685638d6793c27eab8dcd3" + digest = "1:9a5742021de9e9efc565db89f34f6fb114eea6ed1d5c1fcc549291943461c9b0" name = "k8s.io/api" packages = [ "admissionregistration/v1alpha1", @@ -412,7 +420,7 @@ "storage/v1beta1", ] pruneopts = "UT" - revision = "183f3326a9353bd6d41430fc80f96259331d029c" + revision = "f456898a08e4bbc5891694118f3819f324de12ff" [[projects]] digest = "1:02a0c216426652d43d2692b7e363b22198597022a05bc780f08db157e1a9705e" @@ -465,42 +473,72 @@ revision = "103fd098999dc9c0c88536f5c9ad2e5da39373ae" [[projects]] - digest = "1:b8509f91be64147570516a1da9f16f30addebae75744064a7bc0b52afe82f20c" + digest = "1:79cf4c5cf3e201879a15cfb4146adeb33301c0c241bbd3cf70de08bb25cd57f8" name = "k8s.io/client-go" packages = [ "discovery", "discovery/fake", "kubernetes", + "kubernetes/fake", "kubernetes/scheme", "kubernetes/typed/admissionregistration/v1alpha1", + "kubernetes/typed/admissionregistration/v1alpha1/fake", "kubernetes/typed/admissionregistration/v1beta1", + "kubernetes/typed/admissionregistration/v1beta1/fake", "kubernetes/typed/apps/v1", + "kubernetes/typed/apps/v1/fake", "kubernetes/typed/apps/v1beta1", + "kubernetes/typed/apps/v1beta1/fake", "kubernetes/typed/apps/v1beta2", + "kubernetes/typed/apps/v1beta2/fake", "kubernetes/typed/authentication/v1", + "kubernetes/typed/authentication/v1/fake", "kubernetes/typed/authentication/v1beta1", + "kubernetes/typed/authentication/v1beta1/fake", "kubernetes/typed/authorization/v1", + "kubernetes/typed/authorization/v1/fake", "kubernetes/typed/authorization/v1beta1", + "kubernetes/typed/authorization/v1beta1/fake", "kubernetes/typed/autoscaling/v1", + "kubernetes/typed/autoscaling/v1/fake", "kubernetes/typed/autoscaling/v2beta1", + "kubernetes/typed/autoscaling/v2beta1/fake", "kubernetes/typed/batch/v1", + "kubernetes/typed/batch/v1/fake", "kubernetes/typed/batch/v1beta1", + "kubernetes/typed/batch/v1beta1/fake", "kubernetes/typed/batch/v2alpha1", + "kubernetes/typed/batch/v2alpha1/fake", "kubernetes/typed/certificates/v1beta1", + "kubernetes/typed/certificates/v1beta1/fake", "kubernetes/typed/core/v1", + "kubernetes/typed/core/v1/fake", "kubernetes/typed/events/v1beta1", + "kubernetes/typed/events/v1beta1/fake", "kubernetes/typed/extensions/v1beta1", + "kubernetes/typed/extensions/v1beta1/fake", "kubernetes/typed/networking/v1", + "kubernetes/typed/networking/v1/fake", "kubernetes/typed/policy/v1beta1", + "kubernetes/typed/policy/v1beta1/fake", "kubernetes/typed/rbac/v1", + "kubernetes/typed/rbac/v1/fake", "kubernetes/typed/rbac/v1alpha1", + "kubernetes/typed/rbac/v1alpha1/fake", "kubernetes/typed/rbac/v1beta1", + "kubernetes/typed/rbac/v1beta1/fake", "kubernetes/typed/scheduling/v1alpha1", + "kubernetes/typed/scheduling/v1alpha1/fake", "kubernetes/typed/scheduling/v1beta1", + "kubernetes/typed/scheduling/v1beta1/fake", "kubernetes/typed/settings/v1alpha1", + "kubernetes/typed/settings/v1alpha1/fake", "kubernetes/typed/storage/v1", + "kubernetes/typed/storage/v1/fake", "kubernetes/typed/storage/v1alpha1", + "kubernetes/typed/storage/v1alpha1/fake", "kubernetes/typed/storage/v1beta1", + "kubernetes/typed/storage/v1beta1/fake", "pkg/apis/clientauthentication", "pkg/apis/clientauthentication/v1alpha1", "pkg/apis/clientauthentication/v1beta1", @@ -517,6 +555,7 @@ "tools/clientcmd/api/v1", "tools/metrics", "tools/pager", + "tools/record", "tools/reference", "transport", "util/buffer", @@ -537,7 +576,7 @@ name = "k8s.io/kube-openapi" packages = ["pkg/util/proto"] pruneopts = "UT" - revision = "d8ea2fe547a448256204cfc68dfee7b26c720acb" + revision = "e3762e86a74c878ffed47484592986685639c2cd" [solve-meta] analyzer-name = "dep" @@ -571,6 +610,8 @@ "k8s.io/client-go/discovery", "k8s.io/client-go/discovery/fake", "k8s.io/client-go/kubernetes", + "k8s.io/client-go/kubernetes/fake", + "k8s.io/client-go/kubernetes/scheme", "k8s.io/client-go/kubernetes/typed/apps/v1", "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1", "k8s.io/client-go/kubernetes/typed/core/v1", @@ -579,6 +620,7 @@ "k8s.io/client-go/testing", "k8s.io/client-go/tools/cache", "k8s.io/client-go/tools/clientcmd", + "k8s.io/client-go/tools/record", "k8s.io/client-go/transport", "k8s.io/client-go/util/flowcontrol", ] diff --git a/controller/ingress.go b/controller/ingress.go index 00f67357..b2d7d5fe 100644 --- a/controller/ingress.go +++ b/controller/ingress.go @@ -11,11 +11,14 @@ import ( log "github.com/sirupsen/logrus" zv1 "github.com/zalando-incubator/stackset-controller/pkg/apis/zalando/v1" "github.com/zalando-incubator/stackset-controller/pkg/clientset" + "github.com/zalando-incubator/stackset-controller/pkg/recorder" + apiv1 "k8s.io/api/core/v1" v1beta1 "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/api/equality" apiErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + kube_record "k8s.io/client-go/tools/record" ) const ( @@ -30,8 +33,9 @@ var ( // ingressReconciler is able to bring Ingresses of a StackSet to the desired // state. type ingressReconciler struct { - logger *log.Entry - client clientset.Interface + logger *log.Entry + client clientset.Interface + recorder kube_record.EventRecorder } // ReconcileIngress brings Ingresses of a StackSet to the desired state. @@ -44,7 +48,8 @@ func (c *StackSetController) ReconcileIngress(sc StackSetContainer) error { "namespace": sc.StackSet.Namespace, }, ), - client: c.client, + client: c.client, + recorder: recorder.CreateEventRecorder(c.client), } return ir.reconcile(sc) } @@ -55,7 +60,9 @@ func (c *ingressReconciler) reconcile(sc StackSetContainer) error { // cleanup Ingress if ingress is disabled. if sc.StackSet.Spec.Ingress == nil { if sc.Ingress != nil { - c.logger.Infof( + c.recorder.Eventf(&sc.StackSet, + apiv1.EventTypeNormal, + "DeleteIngress", "Deleting obsolete Ingress %s/%s for StackSet %s/%s", sc.Ingress.Namespace, sc.Ingress.Name, @@ -64,21 +71,23 @@ func (c *ingressReconciler) reconcile(sc StackSetContainer) error { ) err := c.client.ExtensionsV1beta1().Ingresses(sc.Ingress.Namespace).Delete(sc.Ingress.Name, nil) if err != nil { - return fmt.Errorf( - "failed to delete Ingress %s/%s for StackSet %s/%s: %v", + c.recorder.Eventf(&sc.StackSet, + apiv1.EventTypeWarning, + "DeleteIngress", + "Failed to delete Ingress %s/%s for StackSet %s/%s: %v", sc.Ingress.Namespace, sc.Ingress.Name, sc.StackSet.Namespace, sc.StackSet.Name, err, ) + return err } // cleanup any per stack ingresses. for _, stack := range stacks { err := c.gcStackIngress(stack) if err != nil { - log.Error(err) continue } } @@ -88,19 +97,42 @@ func (c *ingressReconciler) reconcile(sc StackSetContainer) error { stackStatuses, err := c.getStackStatuses(sc.StackContainers) if err != nil { - return fmt.Errorf("failed to get Stack statuses for StackSet %s/%s: %v", sc.StackSet.Namespace, sc.StackSet.Name, err) - } - - ingress, err := ingressForStackSet(&sc.StackSet, sc.Ingress, stackStatuses) + c.recorder.Eventf(&sc.StackSet, + apiv1.EventTypeWarning, + "GetStackStatus", + "Failed to get Stack statuses for StackSet %s/%s: %v", + sc.StackSet.Namespace, + sc.StackSet.Name, + err, + ) + return err + } + + ingress, err := c.ingressForStackSet(&sc.StackSet, sc.Ingress, stackStatuses) if err != nil { if err == errNoPaths { return nil } - return fmt.Errorf("failed to generate Ingress for StackSet %s/%s: %v", sc.StackSet.Namespace, sc.StackSet.Name, err) + c.recorder.Eventf(&sc.StackSet, + apiv1.EventTypeWarning, + "GenerateIngress", + "Failed to generate Ingress for StackSet %s/%s: %v", + sc.StackSet.Namespace, + sc.StackSet.Name, + err, + ) + return err } if sc.Ingress == nil { - c.logger.Infof("Creating Ingress %s/%s with %d service backend(s).", ingress.Namespace, ingress.Name, len(stacks)) + c.recorder.Eventf(&sc.StackSet, + apiv1.EventTypeNormal, + "CreateIngress", + "Creating Ingress %s/%s with %d service backend(s).", + ingress.Namespace, + ingress.Name, + len(stacks), + ) _, err := c.client.ExtensionsV1beta1().Ingresses(ingress.Namespace).Create(ingress) if err != nil { return err @@ -109,7 +141,14 @@ func (c *ingressReconciler) reconcile(sc StackSetContainer) error { sc.Ingress.Status = v1beta1.IngressStatus{} if !equality.Semantic.DeepEqual(sc.Ingress, ingress) { c.logger.Debugf("Ingress %s/%s changed: %s", ingress.Namespace, ingress.Name, cmp.Diff(sc.Ingress, ingress)) - c.logger.Infof("Updating Ingress %s/%s with %d service backend(s).", ingress.Namespace, ingress.Name, len(stacks)) + c.recorder.Eventf(&sc.StackSet, + apiv1.EventTypeNormal, + "UpdateIngress", + "Updating Ingress %s/%s with %d service backend(s).", + ingress.Namespace, + ingress.Name, + len(stacks), + ) _, err := c.client.ExtensionsV1beta1().Ingresses(ingress.Namespace).Update(ingress) if err != nil { return err @@ -129,7 +168,6 @@ func (c *ingressReconciler) reconcile(sc StackSetContainer) error { for _, stack := range stacks { err := c.stackIngress(sc.StackSet, stack) if err != nil { - log.Error(err) continue } } @@ -170,29 +208,47 @@ func (c *ingressReconciler) getStackStatuses(stacks map[types.UID]*StackContaine } func (c *ingressReconciler) stackIngress(stackset zv1.StackSet, stack zv1.Stack) error { - ingress, err := ingressForStack(&stackset, &stack) + ingress, err := c.ingressForStack(&stackset, &stack) if err != nil { - return fmt.Errorf("failed generate Ingress for Stack %s/%s: %s", stack.Namespace, stack.Name, err) + c.recorder.Eventf(&stack, + apiv1.EventTypeWarning, + "GenerateIngress", + "Failed generate Ingress for Stack %s/%s: %s", stack.Namespace, stack.Name, err) + return err } ing, err := c.client.ExtensionsV1beta1().Ingresses(ingress.Namespace).Get(ingress.Name, metav1.GetOptions{}) if err != nil { if !apiErrors.IsNotFound(err) { - return fmt.Errorf("failed to get Ingress %s/%s: %s", ingress.Namespace, ingress.Name, err) + c.recorder.Eventf(&stack, + apiv1.EventTypeWarning, + "GetIngress", + "Failed to get Ingress %s/%s: %s", ingress.Namespace, ingress.Name, err) + return err } ing = nil } if ing == nil { - c.logger.Infof("Creating Ingress %s/%s", ingress.Namespace, ingress.Name) + c.recorder.Eventf(&stack, + apiv1.EventTypeNormal, + "CreateIngress", + "Creating Ingress %s/%s", ingress.Namespace, ingress.Name) _, err := c.client.ExtensionsV1beta1().Ingresses(ingress.Namespace).Create(ingress) if err != nil { - return fmt.Errorf("failed to create Ingress %s/%s: %s", ingress.Namespace, ingress.Name, err) + c.recorder.Eventf(&stack, + apiv1.EventTypeWarning, + "Failed to create Ingress %s/%s: %s", ingress.Namespace, ingress.Name, err) + return err } } else { // check if ingress is already owned by a different resource. if !isOwnedReference(stack.TypeMeta, stack.ObjectMeta, ing.ObjectMeta) { - return fmt.Errorf("Ingress %s/%s already has a different owner: %v", ing.Namespace, ing.Name, ing.ObjectMeta.OwnerReferences) + c.recorder.Eventf(&stack, + apiv1.EventTypeWarning, + "IngressDifferentOwner", + "Ingress %s/%s already has a different owner: %v", ing.Namespace, ing.Name, ing.ObjectMeta.OwnerReferences) + return err } // add objectMeta from existing ingress @@ -205,10 +261,17 @@ func (c *ingressReconciler) stackIngress(stackset zv1.StackSet, stack zv1.Stack) if !equality.Semantic.DeepEqual(ing, ingress) { c.logger.Debugf("Ingress %s/%s changed: %s", ingress.Namespace, ingress.Name, cmp.Diff(ing, ingress)) - c.logger.Infof("Updating Ingress %s/%s.", ingress.Namespace, ingress.Name) + c.recorder.Eventf(&stack, + apiv1.EventTypeNormal, + "UpdateIngress", + "Updating Ingress %s/%s.", ingress.Namespace, ingress.Name) _, err := c.client.ExtensionsV1beta1().Ingresses(ingress.Namespace).Update(ingress) if err != nil { - return fmt.Errorf("failed to update Ingress %s/%s: %v", ingress.Namespace, ingress.Name, err) + c.recorder.Eventf(&stack, + apiv1.EventTypeWarning, + "UpdateIngress", + "Failed to update Ingress %s/%s: %v", ingress.Namespace, ingress.Name, err) + return err } } } @@ -220,27 +283,42 @@ func (c *ingressReconciler) gcStackIngress(stack zv1.Stack) error { ing, err := c.client.ExtensionsV1beta1().Ingresses(stack.Namespace).Get(stack.Name, metav1.GetOptions{}) if err != nil { if !apiErrors.IsNotFound(err) { - return fmt.Errorf("failed to get Ingress %s/%s: %s", stack.Namespace, stack.Name, err) + c.recorder.Eventf(&stack, + apiv1.EventTypeWarning, + "GetIngress", + "Failed to get Ingress %s/%s: %v", stack.Namespace, stack.Name, err) + return err } return nil } // check if ingress is already owned by a different resource. if !isOwnedReference(stack.TypeMeta, stack.ObjectMeta, ing.ObjectMeta) { - return fmt.Errorf("Ingress %s/%s already has a different owner: %v", ing.Namespace, ing.Name, ing.ObjectMeta.OwnerReferences) + c.recorder.Eventf(&stack, + apiv1.EventTypeWarning, + "IngressDifferentOwner", + "Ingress %s/%s already has a different owner: %v", ing.Namespace, ing.Name, ing.ObjectMeta.OwnerReferences) + return err } - c.logger.Infof("Deleting obsolete Ingress %s/%s.", ing.Namespace, ing.Name) + c.recorder.Eventf(&stack, + apiv1.EventTypeNormal, + "DeleteIngress", + "Deleting obsolete Ingress %s/%s.", ing.Namespace, ing.Name) err = c.client.ExtensionsV1beta1().Ingresses(ing.Namespace).Delete(ing.Name, nil) if err != nil { - return fmt.Errorf("failed to delete Ingress %s/%s: %v", ing.Namespace, ing.Name, err) + c.recorder.Eventf(&stack, + apiv1.EventTypeWarning, + "DeleteIngress", + "Failed to delete Ingress %s/%s: %v", ing.Namespace, ing.Name, err) + return err } return nil } // ingressForStack generates an ingress object based on a stack. -func ingressForStack(stackset *zv1.StackSet, stack *zv1.Stack) (*v1beta1.Ingress, error) { +func (c *ingressReconciler) ingressForStack(stackset *zv1.StackSet, stack *zv1.Stack) (*v1beta1.Ingress, error) { ingress := &v1beta1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: stack.Name, @@ -291,7 +369,11 @@ func ingressForStack(stackset *zv1.StackSet, stack *zv1.Stack) (*v1beta1.Ingress r := rule newHost, err := createSubdomain(host, stack.Name) if err != nil { - return nil, fmt.Errorf("failed to create domain name: %s", err) + c.recorder.Eventf(stack, + apiv1.EventTypeWarning, + "CreateDomainName", + "Failed to create domain name: %s", err) + return nil, err } r.Host = newHost ingress.Spec.Rules = append(ingress.Spec.Rules, r) @@ -301,7 +383,7 @@ func ingressForStack(stackset *zv1.StackSet, stack *zv1.Stack) (*v1beta1.Ingress } // ingressForStackSet -func ingressForStackSet(stackset *zv1.StackSet, origIngress *v1beta1.Ingress, stackStatuses []stackStatus) (*v1beta1.Ingress, error) { +func (c *ingressReconciler) ingressForStackSet(stackset *zv1.StackSet, origIngress *v1beta1.Ingress, stackStatuses []stackStatus) (*v1beta1.Ingress, error) { heritageLabels := map[string]string{ stacksetHeritageLabelKey: stackset.Name, } @@ -361,7 +443,11 @@ func ingressForStackSet(stackset *zv1.StackSet, origIngress *v1beta1.Ingress, st if weights, ok := origIngress.Annotations[stackTrafficWeightsAnnotationKey]; ok { err := json.Unmarshal([]byte(weights), ¤tWeights) if err != nil { - return nil, fmt.Errorf("failed to get current Stack traffic weights: %v", err) + c.recorder.Eventf(stackset, + apiv1.EventTypeWarning, + "StackTrafficWeights", + "Failed to get current Stack traffic weights: %v", err) + return nil, err } } } diff --git a/controller/stack.go b/controller/stack.go index 72432acf..fb69ee36 100644 --- a/controller/stack.go +++ b/controller/stack.go @@ -9,13 +9,16 @@ import ( log "github.com/sirupsen/logrus" zv1 "github.com/zalando-incubator/stackset-controller/pkg/apis/zalando/v1" "github.com/zalando-incubator/stackset-controller/pkg/clientset" + "github.com/zalando-incubator/stackset-controller/pkg/recorder" appsv1 "k8s.io/api/apps/v1" autoscaling "k8s.io/api/autoscaling/v2beta1" "k8s.io/api/core/v1" + apiv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" + kube_record "k8s.io/client-go/tools/record" ) const ( @@ -26,8 +29,9 @@ const ( // desired state. This includes managing, Deployment, Service and HPA resources // of the Stacks. type stacksReconciler struct { - logger *log.Entry - client clientset.Interface + logger *log.Entry + recorder kube_record.EventRecorder + client clientset.Interface } // ReconcileStacks brings a set of Stacks of a StackSet to the desired state. @@ -40,7 +44,8 @@ func (c *StackSetController) ReconcileStacks(ssc StackSetContainer) error { "namespace": ssc.StackSet.Namespace, }, ), - client: c.client, + client: c.client, + recorder: recorder.CreateEventRecorder(c.client), } return sr.reconcile(ssc) } @@ -158,7 +163,9 @@ func (c *stacksReconciler) manageDeployment(sc StackContainer, ssc StackSetConta var err error if createDeployment { - c.logger.Infof( + c.recorder.Eventf(&stack, + apiv1.EventTypeNormal, + "CreateDeployment", "Creating Deployment %s/%s for StackSet stack %s/%s", deployment.Namespace, deployment.Name, @@ -182,7 +189,9 @@ func (c *stacksReconciler) manageDeployment(sc StackContainer, ssc StackSetConta cmpopts.IgnoreUnexported(resource.Quantity{}), ), ) - c.logger.Infof( + c.recorder.Eventf(&stack, + apiv1.EventTypeNormal, + "UpdateDeployment", "Updating Deployment %s/%s for StackSet stack %s/%s", deployment.Namespace, deployment.Name, @@ -228,7 +237,9 @@ func (c *stacksReconciler) manageDeployment(sc StackContainer, ssc StackSetConta } if !equality.Semantic.DeepEqual(newStatus, stack.Status) { - c.logger.Infof( + c.recorder.Eventf(&stack, + apiv1.EventTypeNormal, + "UpdateStackStatus", "Status changed for Stack %s/%s: %#v -> %#v", stack.Namespace, stack.Name, @@ -261,7 +272,9 @@ func (c *stacksReconciler) manageAutoscaling(sc StackContainer, deployment *apps // cleanup HPA if autoscaling is disabled or the stack has 0 traffic. if stack.Spec.HorizontalPodAutoscaler == nil || (ssc.Traffic != nil && ssc.Traffic[stack.Name].Weight() <= 0) { if hpa != nil { - c.logger.Infof( + c.recorder.Eventf(&stack, + apiv1.EventTypeNormal, + "DeleteHPA", "Deleting obsolete HPA %s/%s for Deployment %s/%s", hpa.Namespace, hpa.Name, @@ -308,7 +321,9 @@ func (c *stacksReconciler) manageAutoscaling(sc StackContainer, deployment *apps var err error if createHPA { - c.logger.Infof( + c.recorder.Eventf(&stack, + apiv1.EventTypeNormal, + "CreateHPA", "Creating HPA %s/%s for Deployment %s/%s", hpa.Namespace, hpa.Name, @@ -322,7 +337,9 @@ func (c *stacksReconciler) manageAutoscaling(sc StackContainer, deployment *apps } else { if !equality.Semantic.DeepEqual(origHPA, hpa) { c.logger.Debugf("HPA %s/%s changed: %s", hpa.Namespace, hpa.Name, cmp.Diff(origHPA, hpa)) - c.logger.Infof( + c.recorder.Eventf(&stack, + apiv1.EventTypeNormal, + "UpdateHPA", "Updating HPA %s/%s for Deployment %s/%s", hpa.Namespace, hpa.Name, @@ -382,7 +399,9 @@ func (c *stacksReconciler) manageService(sc StackContainer, deployment *appsv1.D service.Spec.Ports = servicePorts if createService { - c.logger.Infof( + c.recorder.Eventf(&stack, + apiv1.EventTypeNormal, + "CreateService", "Creating Service %s/%s for StackSet stack %s/%s", service.Namespace, service.Name, @@ -396,7 +415,9 @@ func (c *stacksReconciler) manageService(sc StackContainer, deployment *appsv1.D } else { if !equality.Semantic.DeepEqual(origService, service) { c.logger.Debugf("Service %s/%s changed: %s", service.Namespace, service.Name, cmp.Diff(origService, service)) - c.logger.Infof( + c.recorder.Eventf(&stack, + apiv1.EventTypeNormal, + "UpdateService", "Updating Service %s/%s for StackSet stack %s/%s", service.Namespace, service.Name, diff --git a/controller/stackset.go b/controller/stackset.go index ba8dd857..d93b7fad 100644 --- a/controller/stackset.go +++ b/controller/stackset.go @@ -14,10 +14,12 @@ import ( log "github.com/sirupsen/logrus" zv1 "github.com/zalando-incubator/stackset-controller/pkg/apis/zalando/v1" "github.com/zalando-incubator/stackset-controller/pkg/clientset" + "github.com/zalando-incubator/stackset-controller/pkg/recorder" "golang.org/x/sync/errgroup" appsv1 "k8s.io/api/apps/v1" autoscaling "k8s.io/api/autoscaling/v2beta1" "k8s.io/api/core/v1" + apiv1 "k8s.io/api/core/v1" v1beta1 "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/resource" @@ -26,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/tools/cache" + kube_record "k8s.io/client-go/tools/record" ) const ( @@ -51,6 +54,7 @@ type StackSetController struct { interval time.Duration stacksetEvents chan stacksetEvent stacksetStore map[types.UID]zv1.StackSet + recorder kube_record.EventRecorder sync.Mutex } @@ -61,6 +65,7 @@ type stacksetEvent struct { // NewStackSetController initializes a new StackSetController. func NewStackSetController(client clientset.Interface, controllerID string, interval time.Duration) *StackSetController { + return &StackSetController{ logger: log.WithFields(log.Fields{"controller": "stackset"}), client: client, @@ -68,6 +73,7 @@ func NewStackSetController(client clientset.Interface, controllerID string, inte stacksetEvents: make(chan stacksetEvent, 1), stacksetStore: make(map[types.UID]zv1.StackSet), interval: interval, + recorder: recorder.CreateEventRecorder(client), } } @@ -142,7 +148,7 @@ func (c *StackSetController) Run(ctx context.Context) { // update/delete existing entry if _, ok := c.stacksetStore[stackset.UID]; ok { if e.Deleted || !c.hasOwnership(&stackset) { - c.logger.Infof("StackSet '%s/%s' deleted, removing references", stackset.Namespace, stackset.Name) + c.recorder.Eventf(e.StackSet, apiv1.EventTypeNormal, "DeleteStackSet", "StackSet '%s/%s' deleted, removing references", stackset.Namespace, stackset.Name) delete(c.stacksetStore, stackset.UID) continue } @@ -331,7 +337,11 @@ func (c *StackSetController) collectResources() (map[types.UID]*StackSetContaine traffic, err := getIngressTraffic(ssc.Ingress) if err != nil { // TODO: can fail for all!!! - return nil, fmt.Errorf("failed to get Ingress traffic for StackSet %s/%s: %v", ssc.StackSet.Namespace, ssc.StackSet.Name, err) + c.recorder.Eventf(&ssc.StackSet, + apiv1.EventTypeWarning, + "GetIngressTraffic", + "Failed to get Ingress traffic for StackSet %s/%s: %v", ssc.StackSet.Namespace, ssc.StackSet.Name, err) + return nil, err } ssc.Traffic = traffic } @@ -571,7 +581,9 @@ func (c *StackSetController) ReconcileStackSetStatus(ssc StackSetContainer) erro } if !equality.Semantic.DeepEqual(newStatus, stackset.Status) { - c.logger.Infof( + c.recorder.Eventf(&stackset, + apiv1.EventTypeNormal, + "UpdateStackSetStatus", "Status changed for StackSet %s/%s: %#v -> %#v", stackset.Namespace, stackset.Name, @@ -607,7 +619,9 @@ func readyStacks(stacks []zv1.Stack) int32 { func (c *StackSetController) StackSetGC(ssc StackSetContainer) error { stackset := ssc.StackSet for _, stack := range c.getStacksToGC(ssc) { - c.logger.Infof( + c.recorder.Eventf(&stackset, + apiv1.EventTypeNormal, + "DeleteExcessStack", "Deleting excess stack %s/%s for StackSet %s/%s", stack.Namespace, stack.Name, @@ -656,7 +670,9 @@ func (c *StackSetController) getStacksToGC(ssc StackSetContainer) []zv1.Stack { }) excessStacks := len(stacks) - historyLimit - c.logger.Infof( + c.recorder.Eventf(&stackset, + apiv1.EventTypeNormal, + "ExeedStackHistoryLimit", "Found %d Stack(s) exeeding the StackHistoryLimit (%d) for StackSet %s/%s. %d candidate(s) for GC", excessStacks, historyLimit, @@ -734,7 +750,9 @@ func (c *StackSetController) ReconcileStack(ssc StackSetContainer) error { } if createStack { - c.logger.Infof( + c.recorder.Eventf(&stackset, + apiv1.EventTypeNormal, + "CreateStackSetStack", "Creating StackSet stack %s/%s for StackSet %s/%s", stack.Namespace, stack.Name, stackset.Namespace, @@ -755,7 +773,10 @@ func (c *StackSetController) ReconcileStack(ssc StackSetContainer) error { cmpopts.IgnoreUnexported(resource.Quantity{}), ), ) - c.logger.Infof( + + c.recorder.Eventf(&stackset, + apiv1.EventTypeNormal, + "UpdateStackSetStack", "Updating StackSet stack %s/%s for StackSet %s/%s", stack.Namespace, stack.Name, diff --git a/controller/stackset_test.go b/controller/stackset_test.go index c014467e..53c72c1c 100644 --- a/controller/stackset_test.go +++ b/controller/stackset_test.go @@ -7,10 +7,12 @@ import ( log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" zv1 "github.com/zalando-incubator/stackset-controller/pkg/apis/zalando/v1" + "github.com/zalando-incubator/stackset-controller/pkg/recorder" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/kubernetes/fake" ) func TestGetStacksToGC(tt *testing.T) { @@ -128,6 +130,7 @@ func TestGetStacksToGC(tt *testing.T) { "test": "yes", }, ), + recorder: recorder.CreateEventRecorder(fake.NewSimpleClientset()), } stacks := c.getStacksToGC(tc.stackSetContainer) diff --git a/pkg/clientset/unified.go b/pkg/clientset/unified.go index 05868ca9..bdbf4ff9 100644 --- a/pkg/clientset/unified.go +++ b/pkg/clientset/unified.go @@ -3,31 +3,115 @@ package clientset import ( stackset "github.com/zalando-incubator/stackset-controller/pkg/client/clientset/versioned" zalandov1 "github.com/zalando-incubator/stackset-controller/pkg/client/clientset/versioned/typed/zalando/v1" + discovery "k8s.io/client-go/discovery" "k8s.io/client-go/kubernetes" + admissionregistrationv1alpha1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1alpha1" + admissionregistrationv1beta1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1" appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1" + appsv1beta1 "k8s.io/client-go/kubernetes/typed/apps/v1beta1" + appsv1beta2 "k8s.io/client-go/kubernetes/typed/apps/v1beta2" + authenticationv1 "k8s.io/client-go/kubernetes/typed/authentication/v1" + authenticationv1beta1 "k8s.io/client-go/kubernetes/typed/authentication/v1beta1" + authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" + authorizationv1beta1 "k8s.io/client-go/kubernetes/typed/authorization/v1beta1" + autoscalingv1 "k8s.io/client-go/kubernetes/typed/autoscaling/v1" autoscalingv2beta1 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1" + batchv1 "k8s.io/client-go/kubernetes/typed/batch/v1" + batchv1beta1 "k8s.io/client-go/kubernetes/typed/batch/v1beta1" + batchv2alpha1 "k8s.io/client-go/kubernetes/typed/batch/v2alpha1" + certificatesv1beta1 "k8s.io/client-go/kubernetes/typed/certificates/v1beta1" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + eventsv1beta1 "k8s.io/client-go/kubernetes/typed/events/v1beta1" extensionsv1beta1 "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" + networkingv1 "k8s.io/client-go/kubernetes/typed/networking/v1" + policyv1beta1 "k8s.io/client-go/kubernetes/typed/policy/v1beta1" + rbacv1 "k8s.io/client-go/kubernetes/typed/rbac/v1" + rbacv1alpha1 "k8s.io/client-go/kubernetes/typed/rbac/v1alpha1" + rbacv1beta1 "k8s.io/client-go/kubernetes/typed/rbac/v1beta1" + schedulingv1alpha1 "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1" + schedulingv1beta1 "k8s.io/client-go/kubernetes/typed/scheduling/v1beta1" + settingsv1alpha1 "k8s.io/client-go/kubernetes/typed/settings/v1alpha1" + storagev1 "k8s.io/client-go/kubernetes/typed/storage/v1" + storagev1alpha1 "k8s.io/client-go/kubernetes/typed/storage/v1alpha1" + storagev1beta1 "k8s.io/client-go/kubernetes/typed/storage/v1beta1" rest "k8s.io/client-go/rest" ) type Interface interface { + Discovery() discovery.DiscoveryInterface + AdmissionregistrationV1alpha1() admissionregistrationv1alpha1.AdmissionregistrationV1alpha1Interface + AdmissionregistrationV1beta1() admissionregistrationv1beta1.AdmissionregistrationV1beta1Interface + // Deprecated: please explicitly pick a version if possible. + Admissionregistration() admissionregistrationv1beta1.AdmissionregistrationV1beta1Interface + AppsV1beta1() appsv1beta1.AppsV1beta1Interface + AppsV1beta2() appsv1beta2.AppsV1beta2Interface AppsV1() appsv1.AppsV1Interface + // Deprecated: please explicitly pick a version if possible. + Apps() appsv1.AppsV1Interface + AuthenticationV1() authenticationv1.AuthenticationV1Interface + // Deprecated: please explicitly pick a version if possible. + Authentication() authenticationv1.AuthenticationV1Interface + AuthenticationV1beta1() authenticationv1beta1.AuthenticationV1beta1Interface + AuthorizationV1() authorizationv1.AuthorizationV1Interface + // Deprecated: please explicitly pick a version if possible. + Authorization() authorizationv1.AuthorizationV1Interface + AuthorizationV1beta1() authorizationv1beta1.AuthorizationV1beta1Interface + AutoscalingV1() autoscalingv1.AutoscalingV1Interface + // Deprecated: please explicitly pick a version if possible. + Autoscaling() autoscalingv1.AutoscalingV1Interface AutoscalingV2beta1() autoscalingv2beta1.AutoscalingV2beta1Interface + BatchV1() batchv1.BatchV1Interface + // Deprecated: please explicitly pick a version if possible. + Batch() batchv1.BatchV1Interface + BatchV1beta1() batchv1beta1.BatchV1beta1Interface + BatchV2alpha1() batchv2alpha1.BatchV2alpha1Interface + CertificatesV1beta1() certificatesv1beta1.CertificatesV1beta1Interface + // Deprecated: please explicitly pick a version if possible. + Certificates() certificatesv1beta1.CertificatesV1beta1Interface CoreV1() corev1.CoreV1Interface + // Deprecated: please explicitly pick a version if possible. + Core() corev1.CoreV1Interface + EventsV1beta1() eventsv1beta1.EventsV1beta1Interface + // Deprecated: please explicitly pick a version if possible. + Events() eventsv1beta1.EventsV1beta1Interface ExtensionsV1beta1() extensionsv1beta1.ExtensionsV1beta1Interface + // Deprecated: please explicitly pick a version if possible. + Extensions() extensionsv1beta1.ExtensionsV1beta1Interface + NetworkingV1() networkingv1.NetworkingV1Interface + // Deprecated: please explicitly pick a version if possible. + Networking() networkingv1.NetworkingV1Interface + PolicyV1beta1() policyv1beta1.PolicyV1beta1Interface + // Deprecated: please explicitly pick a version if possible. + Policy() policyv1beta1.PolicyV1beta1Interface + RbacV1() rbacv1.RbacV1Interface + // Deprecated: please explicitly pick a version if possible. + Rbac() rbacv1.RbacV1Interface + RbacV1beta1() rbacv1beta1.RbacV1beta1Interface + RbacV1alpha1() rbacv1alpha1.RbacV1alpha1Interface + SchedulingV1alpha1() schedulingv1alpha1.SchedulingV1alpha1Interface + SchedulingV1beta1() schedulingv1beta1.SchedulingV1beta1Interface + // Deprecated: please explicitly pick a version if possible. + Scheduling() schedulingv1beta1.SchedulingV1beta1Interface + SettingsV1alpha1() settingsv1alpha1.SettingsV1alpha1Interface + // Deprecated: please explicitly pick a version if possible. + Settings() settingsv1alpha1.SettingsV1alpha1Interface + StorageV1beta1() storagev1beta1.StorageV1beta1Interface + StorageV1() storagev1.StorageV1Interface + // Deprecated: please explicitly pick a version if possible. + Storage() storagev1.StorageV1Interface + StorageV1alpha1() storagev1alpha1.StorageV1alpha1Interface ZalandoV1() zalandov1.ZalandoV1Interface } type Clientset struct { - kubernetes kubernetes.Interface - stackset stackset.Interface + kubernetes.Interface + stackset stackset.Interface } func NewClientset(kubernetes kubernetes.Interface, stackset stackset.Interface) *Clientset { return &Clientset{ - kubernetes: kubernetes, - stackset: stackset, + kubernetes, + stackset, } } @@ -42,23 +126,7 @@ func NewForConfig(kubeconfig *rest.Config) (*Clientset, error) { return nil, err } - return &Clientset{kubernetes: kubeClient, stackset: stacksetClient}, nil -} - -func (c *Clientset) AppsV1() appsv1.AppsV1Interface { - return c.kubernetes.AppsV1() -} - -func (c *Clientset) AutoscalingV2beta1() autoscalingv2beta1.AutoscalingV2beta1Interface { - return c.kubernetes.AutoscalingV2beta1() -} - -func (c *Clientset) CoreV1() corev1.CoreV1Interface { - return c.kubernetes.CoreV1() -} - -func (c *Clientset) ExtensionsV1beta1() extensionsv1beta1.ExtensionsV1beta1Interface { - return c.kubernetes.ExtensionsV1beta1() + return &Clientset{kubeClient, stacksetClient}, nil } func (c *Clientset) ZalandoV1() zalandov1.ZalandoV1Interface { diff --git a/pkg/recorder/recorder.go b/pkg/recorder/recorder.go new file mode 100644 index 00000000..fd9d6b1c --- /dev/null +++ b/pkg/recorder/recorder.go @@ -0,0 +1,22 @@ +package recorder + +import ( + clientv1 "k8s.io/api/core/v1" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/kubernetes/scheme" + v1core "k8s.io/client-go/kubernetes/typed/core/v1" + kube_record "k8s.io/client-go/tools/record" + + "github.com/sirupsen/logrus" +) + +// CreateEventRecorder creates an event recorder to send custom events to Kubernetes to be recorded for targeted Kubernetes objects +func CreateEventRecorder(kubeClient clientset.Interface) kube_record.EventRecorder { + eventBroadcaster := kube_record.NewBroadcaster() + eventBroadcaster.StartLogging(logrus.Infof) + if _, isfake := kubeClient.(*fake.Clientset); !isfake { + eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")}) + } + return eventBroadcaster.NewRecorder(scheme.Scheme, clientv1.EventSource{Component: "stackset-controller"}) +}