From 5920890fa3d5c1249030ab30916c2cda003c4fa5 Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Fri, 6 Jul 2018 20:26:29 +0500 Subject: [PATCH 01/27] Added initial implementation to detect changes --- .gitignore | 10 + .version | 1 + Jenkinsfile | 8 + Makefile | 52 ++++ README.1.md | 23 ++ glide.yaml | 35 +++ internal/pkg/app/app.go | 9 + internal/pkg/client/client.go | 49 ++++ internal/pkg/cmd/reloader.go | 117 ++++++++ internal/pkg/controller/controller.go | 373 ++++++++++++++++++++++++++ internal/pkg/util/types.go | 51 ++++ main.go | 14 + pkg/kube/client.go | 29 ++ pkg/kube/resourcemapper.go | 26 ++ stk.yaml | 4 + 15 files changed, 801 insertions(+) create mode 100644 .gitignore create mode 100644 .version create mode 100644 Jenkinsfile create mode 100644 Makefile create mode 100644 README.1.md create mode 100644 glide.yaml create mode 100644 internal/pkg/app/app.go create mode 100644 internal/pkg/client/client.go create mode 100644 internal/pkg/cmd/reloader.go create mode 100644 internal/pkg/controller/controller.go create mode 100644 internal/pkg/util/types.go create mode 100644 main.go create mode 100644 pkg/kube/client.go create mode 100644 pkg/kube/resourcemapper.go create mode 100644 stk.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f83e763d --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.swp +_dist/ +.idea +golib +release +out/ +_gopath/ +.DS_Store +build +vendor \ No newline at end of file diff --git a/.version b/.version new file mode 100644 index 00000000..afaf360d --- /dev/null +++ b/.version @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..8d76442b --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,8 @@ +#!/usr/bin/groovy +@Library('github.com/stakater/fabric8-pipeline-library@v2.4.0') + +def dummy + +goBuildAndRelease { + +} diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..28e821b1 --- /dev/null +++ b/Makefile @@ -0,0 +1,52 @@ +# note: call scripts from /scripts + +.PHONY: default build builder-image binary-image test stop clean-images clean push apply deploy + +BUILDER ?= reloader-builder +BINARY ?= Reloader +DOCKER_IMAGE ?= stakater/reloader +# Default value "dev" +DOCKER_TAG ?= dev +REPOSITORY = ${DOCKER_IMAGE}:${DOCKER_TAG} + +VERSION=$(shell cat .version) +BUILD= + +GOCMD = go +GLIDECMD = glide +GOFLAGS ?= $(GOFLAGS:) +LDFLAGS = + +default: build test + +install: + "$(GLIDECMD)" install + +build: + "$(GOCMD)" build ${GOFLAGS} ${LDFLAGS} -o "${BINARY}" + +builder-image: + @docker build --network host -t "${BUILDER}" -f build/package/Dockerfile.build . + +binary-image: builder-image + @docker run --network host --rm "${BUILDER}" | docker build --network host -t "${REPOSITORY}" -f Dockerfile.run - + +test: + "$(GOCMD)" test -v ./... + +stop: + @docker stop "${BINARY}" + +clean-images: stop + @docker rmi "${BUILDER}" "${BINARY}" + +clean: + "$(GOCMD)" clean -i + +push: ## push the latest Docker image to DockerHub + docker push $(REPOSITORY) + +apply: + kubectl apply -f deployments/manifests/ + +deploy: binary-image push apply diff --git a/README.1.md b/README.1.md new file mode 100644 index 00000000..53146836 --- /dev/null +++ b/README.1.md @@ -0,0 +1,23 @@ +# configmapcontroller + +This controller watches for changes to `ConfigMap` and `Secret` objects and performs rolling upgrades on their associated deployments, deamonsets and statefulsets and updating dynamically. + +This is particularly useful if the `ConfigMap` is used to define environment variables - or your app cannot easily and reliably watch the `ConfigMap` and update itself on the fly. + +## How to use configmapcontroller + +For a `Deployment` called `foo` have a `ConfigMap` called `foo`. Then add this annotation to your `Deployment` + +```yaml +metadata: + annotations: + configmap.fabric8.io/update-on-change: "foo" +``` + +Then, providing `configmapcontroller` is running, whenever you edit the `ConfigMap` called `foo` the configmapcontroller will update the `Deployment` by adding the environment variable: + +``` +FABRICB_FOO_REVISION=${configMapRevision} +``` + +This then triggers a rolling upgrade of your deployment's pods to use the new configuration. diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 00000000..a7023327 --- /dev/null +++ b/glide.yaml @@ -0,0 +1,35 @@ +package: . +import: +- package: github.com/openshift/origin + version: v1.3.0 + subpackages: + - pkg/client +- package: github.com/spf13/cobra +- package: github.com/spf13/pflag +- package: k8s.io/kubernetes + version: 52492b4bff99ef3b8ca617d385a3ff0612f9402d + repo: git://github.com/openshift/kubernetes.git + vcs: git + subpackages: + - pkg/api + - pkg/api/resource + - pkg/api/unversioned + - pkg/client/unversioned + - pkg/fields + - pkg/kubectl/cmd + - pkg/kubectl/cmd/util + - pkg/labels + - pkg/runtime + - pkg/util +- package: github.com/opencontainers/runc + version: v0.0.7 + subpackages: + - libcontainer +- package: github.com/imdario/mergo + version: 6633656539c1639d9d78127b7d47c622b5d7b6dc +- package: github.com/Sirupsen/logrus + version: aaf92c95712104318fc35409745f1533aa5ff327 +- package: github.com/docker/distribution + version: 559433598c7be9d30d6cfc5cad5b5dfdb686725c + repo: git://github.com/openshift/docker-distribution.git + vcs: git diff --git a/internal/pkg/app/app.go b/internal/pkg/app/app.go new file mode 100644 index 00000000..319501a7 --- /dev/null +++ b/internal/pkg/app/app.go @@ -0,0 +1,9 @@ +package app + +import "github.com/stakater/Reloader/internal/pkg/cmd" + +// Run runs the command +func Run() error { + cmd := cmd.NewReloaderCommand() + return cmd.Execute() +} \ No newline at end of file diff --git a/internal/pkg/client/client.go b/internal/pkg/client/client.go new file mode 100644 index 00000000..5f00be62 --- /dev/null +++ b/internal/pkg/client/client.go @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * 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 client + +import ( + oclient "github.com/openshift/origin/pkg/client" + "github.com/pkg/errors" + "k8s.io/kubernetes/pkg/client/restclient" + client "k8s.io/kubernetes/pkg/client/unversioned" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" +) + +func NewClient(f *cmdutil.Factory) (*client.Client, *restclient.Config, error) { + var err error + cfg, err := f.ClientConfig() + if err != nil { + return nil, nil, errors.Wrap(err, "Could not initialise client") + } + c, err := client.New(cfg) + if err != nil { + return nil, nil, errors.Wrap(err, "Could not initialise client") + } + + return c, cfg, nil +} + +func NewOpenShiftClient(cfg *restclient.Config) (*oclient.Client, *restclient.Config, error) { + ocfg := *cfg + ocfg.APIPath = "" + c, err := oclient.New(&ocfg) + if err != nil { + return nil, nil, errors.Wrap(err, "Could not initialise an OpenShift client") + } + + return c, cfg, nil +} diff --git a/internal/pkg/cmd/reloader.go b/internal/pkg/cmd/reloader.go new file mode 100644 index 00000000..64ce1403 --- /dev/null +++ b/internal/pkg/cmd/reloader.go @@ -0,0 +1,117 @@ +package main + +import ( + "flag" + "fmt" + "net/http" + "net/http/pprof" + "os" + "os/signal" + "syscall" + "time" + + "github.com/spf13/cobra" + "github.com/stakater/Reloader/internal/pkg/client" + "github.com/stakater/Reloader/internal/pkg/controller" + "github.com/stakater/Reloader/internal/pkg/util" + "github.com/stakater/Reloader/pkg/kube" + "github.com/golang/glog" + oclient "github.com/openshift/origin/pkg/client" + "github.com/spf13/pflag" + "k8s.io/kubernetes/pkg/api" + kubectlutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" +) + +func NewReloaderCommand() *cobra.Command { + cmds := &cobra.Command{ + Use: "reloader", + Short: "A watcher for your Kubernetes cluster", + Run: startReloader, + } + return cmds +} + +const ( + healthPort = 10254 +) + +var ( + flags = pflag.NewFlagSet("", pflag.ExitOnError) + + resyncPeriod = flags.Duration("sync-period", 30*time.Second, + `Relist and confirm services this often.`) + + healthzPort = flags.Int("healthz-port", healthPort, "port for healthz endpoint.") + + profiling = flags.Bool("profiling", true, `Enable profiling via web interface host:port/debug/pprof/`) +) + +func startReloader(cmd *cobra.Command, args []string) { + glog.Println("Starting Reloader") + + // create the clientset + clientset, err := kube.GetClient() + if err != nil { + log.Fatal(err) + } + + // get the Controller config file + config := getControllerConfig() + + for k, v := range kube.ResourceMap { + c, err := controller.NewController(clientset, *resyncPeriod, config, v) + if err != nil { + glog.Fatalf("%s", err) + } + + go registerHandlers() + go handleSigterm(c) + + // Now let's start the controller + stop := make(chan struct{}) + defer close(stop) + + go c.Run(1, stop) + } + + // Wait forever + select {} +} + +func registerHandlers() { + mux := http.NewServeMux() + + if *profiling { + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + } + + server := &http.Server{ + Addr: fmt.Sprintf(":%v", *healthzPort), + Handler: mux, + } + glog.Fatal(server.ListenAndServe()) +} + +func handleSigterm(c *controller.Controller) { + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) + sig := <-signalChan + glog.Infof("Received %s, shutting down", sig) + c.Stop() +} + +// get the yaml configuration for the controller +func getControllerConfig() config.Config { + configFilePath := os.Getenv("CONFIG_FILE_PATH") + if len(configFilePath) == 0 { + //Default config file is placed in configs/ folder + configFilePath = "configs/config.yaml" + } + configuration, err := config.ReadConfig(configFilePath) + if err != nil { + log.Panic(err) + } + return configuration +} diff --git a/internal/pkg/controller/controller.go b/internal/pkg/controller/controller.go new file mode 100644 index 00000000..78bac1ef --- /dev/null +++ b/internal/pkg/controller/controller.go @@ -0,0 +1,373 @@ +package controller + +import ( + "bytes" + "strings" + "time" + "fmt" + + "github.com/stakater/Reloader/internal/pkg/actions" + "github.com/golang/glog" + "github.com/pkg/errors" + + "k8s.io/kubernetes/pkg/api" + + clientset "k8s.io/client-go/kubernetes" + informerruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/watch" + "k8s.io/apimachinery/pkg/util/wait" + errorHandler "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + + "sort" +) + +const ( + updateOnChangeAnnotation = "configmap.fabric8.io/update-on-change" + // AllNamespaces as our controller will be looking for events in all namespaces + AllNamespaces = "" +) + +// Event indicate the informerEvent +type Event struct { + key string + eventType string + namespace string + resourceType string +} + +// Controller for checking events +type Controller struct { + clientset clientset.Interface + indexer cache.Indexer + queue workqueue.RateLimitingInterface + informer cache.Controller + controllerConfig config.Controller + Actions []actions.Action + + stopCh chan struct{} +} + +// NewController for initializing a Controller +func NewController( + clientset clientset.Interface, + resyncPeriod time.Duration, controllerConfig config.Controller, objType informerruntime.Object) (*Controller, error) { + + c := Controller{ + clientset: clientset, + controllerConfig: controllerConfig, + stopCh: make(chan struct{}), + } + + queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) + listWatcher := cache.NewListWatchFromClient(clientset.CoreV1().RESTClient(), controllerConfig.Type, AllNamespaces, fields.Everything()) + + indexer, informer := cache.NewIndexerInformer(listWatcher, objType, 0, cache.ResourceEventHandlerFuncs { + AddFunc: c.Add, + UpdateFunc: c.Update, + DeleteFunc: c.Delete, + }, cache.Indexers{}) + + /*c.cmLister.Store, c.cmController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: configMapListFunc(c.client, namespace), + WatchFunc: configMapWatchFunc(c.client, namespace), + }, + &api.ConfigMap{}, + resyncPeriod, + framework.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + newCM := obj.(*api.ConfigMap) + //typeOfMaster, err := util.TypeOfMaster(kubeClient) + if err != nil { + glog.Fatalf("failed to create REST client config: %s", err) + } + err = rollingUpgradeDeployments(newCM, kubeClient) + if err != nil { + glog.Errorf("failed to update Deployment: %v", err) + } + + }, + UpdateFunc: func(oldObj interface{}, newObj interface{}) { + oldM := oldObj.(*api.ConfigMap) + newCM := newObj.(*api.ConfigMap) + + if oldM.ResourceVersion != newCM.ResourceVersion { + //typeOfMaster, err := util.TypeOfMaster(kubeClient) + if err != nil { + glog.Fatalf("failed to create REST client config: %s", err) + } + err = rollingUpgradeDeployments(newCM, kubeClient) + if err != nil { + glog.Errorf("failed to update Deployment: %v", err) + } + } + }, + }, + )*/ + return &c, nil +} + +// Run starts the controller. +/*func (c *Controller) Run() { + glog.Infof("starting reloader") + + <-c.stopCh +}*/ + +// Stop stops the controller. +/*func (c *Controller) Stop() { + glog.Infof("stopping reloader") + + close(c.stopCh) +}*/ + +// Add function to add a 'create' event to the queue in case of creating a pod +func (c *Controller) Add(obj interface{}) { + key, err := cache.MetaNamespaceKeyFunc(obj) + var event Event + + if err == nil { + event.key = key + event.eventType = "create" + c.queue.Add(event) + } +} + +// Update function to add an 'update' event to the queue in case of updating a pod +func (c *Controller) Update(old interface{}, new interface{}) { + key, err := cache.MetaNamespaceKeyFunc(new) + var event Event + + if err == nil { + c.queue.Add(event) + } +} + +// Delete function to add a 'delete' event to the queue in case of deleting a pod +func (c *Controller) Delete(obj interface{}) { + //In current scenario, we dont need to do anything when a pod is deleted so it is empty now +} + +//Run function for controller which handles the queue +func (c *Controller) Run(threadiness int, stopCh chan struct{}) { + + glog.Infof("Starting Controller for type ", c.controllerConfig.Type) + defer errorHandler.HandleCrash() + + // Let the workers stop when we are done + defer c.queue.ShutDown() + + go c.informer.Run(stopCh) + + // Wait for all involved caches to be synced, before processing items from the queue is started + if !cache.WaitForCacheSync(stopCh, c.informer.HasSynced) { + errorHandler.HandleError(fmt.Errorf("Timed out waiting for caches to sync")) + return + } + + for i := 0; i < threadiness; i++ { + go wait.Until(c.runWorker, time.Second, stopCh) + } + + <-stopCh + glog.Infof("Stopping Controller for type ", c.controllerConfig.Type) +} + +func (c *Controller) runWorker() { + for c.processNextItem() { + } +} + +func (c *Controller) processNextItem() bool { + // Wait until there is a new item in the working queue + event, quit := c.queue.Get() + if quit { + return false + } + // Tell the queue that we are done with processing this key. This unblocks the key for other workers + // This allows safe parallel processing because two events with the same key are never processed in + // parallel. + defer c.queue.Done(event) + + // Invoke the method containing the business logic + err := c.takeAction(event.(Event)) + // Handle the error if something went wrong during the execution of the business logic + c.handleErr(err, event) + return true +} + +// main business logic that acts bassed on the event or key +func (c *Controller) takeAction(event Event) error { + + obj, _, err := c.indexer.GetByKey(event.key) + if err != nil { + glog.Infof("Fetching object with key %s from store failed with %v", event.key, err) + } + if obj == nil { + glog.Infof("Error in Action") + } else { + glog.Infof("Detected changes in object %s", obj) + /*glog.Infof("Resource block not found, performing actions") + // process events based on its type + for index, action := range c.Actions { + glog.Infof("Performing '%s' action for controller of type '%s'", c.controllerConfig.Actions[index].Name, c.controllerConfig.Type) + switch event.eventType { + case "create": + action.ObjectCreated(obj) + case "update": + //TODO: Figure how to pass old and new object + action.ObjectUpdated(obj, nil) + case "delete": + action.ObjectDeleted(obj) + } + }*/ + } + return nil +} + +// handleErr checks if an error happened and makes sure we will retry later. +func (c *Controller) handleErr(err error, key interface{}) { + if err == nil { + // Forget about the #AddRateLimited history of the key on every successful synchronization. + // This ensures that future processing of updates for this key is not delayed because of + // an outdated error history. + c.queue.Forget(key) + return + } + + // This controller retries 5 times if something goes wrong. After that, it stops trying. + if c.queue.NumRequeues(key) < 5 { + log.Printf("Error syncing events %v: %v", key, err) + + // Re-enqueue the key rate limited. Based on the rate limiter on the + // queue and the re-enqueue history, the key will be processed later again. + c.queue.AddRateLimited(key) + return + } + + c.queue.Forget(key) + // Report to an external entity that, even after several retries, we could not successfully process this key + runtime.HandleError(err) + log.Printf("Dropping the key %q out of the queue: %v", key, err) +} + +/*func configMapListFunc(c *client.Client, ns string) func(api.ListOptions) (runtime.Object, error) { + return func(opts api.ListOptions) (runtime.Object, error) { + return c.ConfigMaps(ns).List(opts) + } +} + +func configMapWatchFunc(c *client.Client, ns string) func(options api.ListOptions) (watch.Interface, error) { + return func(options api.ListOptions) (watch.Interface, error) { + return c.ConfigMaps(ns).Watch(options) + } +} + +func rollingUpgradeDeployments(cm *api.ConfigMap, c *client.Client) error { + ns := cm.Namespace + configMapName := cm.Name + configMapVersion := convertConfigMapToToken(cm) + + deployments, err := c.Deployments(ns).List(api.ListOptions{}) + if err != nil { + return errors.Wrap(err, "failed to list deployments") + } + for _, d := range deployments.Items { + containers := d.Spec.Template.Spec.Containers + // match deployments with the correct annotation + annotationValue, _ := d.ObjectMeta.Annotations[updateOnChangeAnnotation] + if annotationValue != "" { + values := strings.Split(annotationValue, ",") + matches := false + for _, value := range values { + if value == configMapName { + matches = true + break + } + } + if matches { + updateContainers(containers, annotationValue, configMapVersion) + + // update the deployment + _, err := c.Deployments(ns).Update(&d) + if err != nil { + return errors.Wrap(err, "update deployment failed") + } + glog.Infof("Updated Deployment %s", d.Name) + } + } + } + return nil +}*/ + +// lets convert the configmap into a unique token based on the data values +func convertConfigMapToToken(cm *api.ConfigMap) string { + values := []string{} + for k, v := range cm.Data { + values = append(values, k+"="+v) + } + sort.Strings(values) + text := strings.Join(values, ";") + // we could zip and base64 encode + // but for now we could leave this easy to read so that its easier to diagnose when & why things changed + return text +} + +func updateContainers(containers []api.Container, annotationValue, configMapVersion string) bool { + // we can have multiple configmaps to update + answer := false + configmaps := strings.Split(annotationValue, ",") + for _, cmNameToUpdate := range configmaps { + configmapEnvar := "FABRIC8_" + convertToEnvVarName(cmNameToUpdate) + "_CONFIGMAP" + + for i := range containers { + envs := containers[i].Env + matched := false + for j := range envs { + if envs[j].Name == configmapEnvar { + matched = true + if envs[j].Value != configMapVersion { + glog.Infof("Updating %s to %s", configmapEnvar, configMapVersion) + envs[j].Value = configMapVersion + answer = true + } + } + } + // if no existing env var exists lets create one + if !matched { + e := api.EnvVar{ + Name: configmapEnvar, + Value: configMapVersion, + } + containers[i].Env = append(containers[i].Env, e) + answer = true + } + } + } + return answer +} + +// convertToEnvVarName converts the given text into a usable env var +// removing any special chars with '_' +func convertToEnvVarName(text string) string { + var buffer bytes.Buffer + lower := strings.ToUpper(text) + lastCharValid := false + for i := 0; i < len(lower); i++ { + ch := lower[i] + if (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') { + buffer.WriteString(string(ch)) + lastCharValid = true + } else { + if lastCharValid { + buffer.WriteString("_") + } + lastCharValid = false + } + } + return buffer.String() +} diff --git a/internal/pkg/util/types.go b/internal/pkg/util/types.go new file mode 100644 index 00000000..4b48de4c --- /dev/null +++ b/internal/pkg/util/types.go @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * 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 util + +import ( + "encoding/json" + + "github.com/pkg/errors" + + api "k8s.io/kubernetes/pkg/api/unversioned" + client "k8s.io/kubernetes/pkg/client/unversioned" +) + +type MasterType string + +const ( + OpenShift MasterType = "OpenShift" + Kubernetes MasterType = "Kubernetes" +) + +func TypeOfMaster(c *client.Client) (MasterType, error) { + res, err := c.Get().AbsPath("").DoRaw() + if err != nil { + return "", errors.Wrap(err, "could not discover the type of your installation") + } + + var rp api.RootPaths + err = json.Unmarshal(res, &rp) + if err != nil { + errors.Wrap(err, "could not discover the type of your installation") + } + for _, p := range rp.Paths { + if p == "/oapi" { + return OpenShift, nil + } + } + return Kubernetes, nil +} diff --git a/main.go b/main.go new file mode 100644 index 00000000..478976ba --- /dev/null +++ b/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/stakater/Reloader/internal/pkg/app" +) + +func main() { + if err := app.Run(); err != nil { + os.Exit(1) + } + os.Exit(0) +} \ No newline at end of file diff --git a/pkg/kube/client.go b/pkg/kube/client.go new file mode 100644 index 00000000..3596df28 --- /dev/null +++ b/pkg/kube/client.go @@ -0,0 +1,29 @@ +package kube + +import ( + "os" + + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +// gets the client for k8s, if ~/.kube/config exists so get that config else incluster config +func GetClient() (*kubernetes.Clientset, error) { + var config *rest.Config + var err error + kubeconfigPath := os.Getenv("KUBECONFIG") + if kubeconfigPath == "" { + kubeconfigPath = os.Getenv("HOME") + "/.kube/config" + } + //If file exists so use that config settings + if _, err := os.Stat(kubeconfigPath); err == nil { + config, err = clientcmd.BuildConfigFromFlags("", kubeconfigPath) + } else { //Use Incluster Configuration + config, err = rest.InClusterConfig() + } + if err != nil { + return nil, err + } + return kubernetes.NewForConfig(config) +} diff --git a/pkg/kube/resourcemapper.go b/pkg/kube/resourcemapper.go new file mode 100644 index 00000000..78571713 --- /dev/null +++ b/pkg/kube/resourcemapper.go @@ -0,0 +1,26 @@ +package kube + +import ( + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +const ( + DefaultResource = "default" +) + +// MapToRuntimeObject maps the resource type string to the actual resource +func MapToRuntimeObject(resourceType string) runtime.Object { + rType, ok := ResourceMap[resourceType] + if !ok { + return ResourceMap[DefaultResource] + } + return rType +} + +// ResourceMap are resources from where changes are going to be detected +var ResourceMap = map[string]runtime.Object{ + "configMaps": &v1.ConfigMap{}, + "secrets": &v1.Secret{}, + "default": nil, +} diff --git a/stk.yaml b/stk.yaml new file mode 100644 index 00000000..e30e7c8f --- /dev/null +++ b/stk.yaml @@ -0,0 +1,4 @@ +issues: + kind: 1 + url: https://aurorasolutions.atlassian.net + project: STK \ No newline at end of file From ee079a46848d8e2c34ddbb51de90f87a477ac79e Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Fri, 6 Jul 2018 20:28:23 +0500 Subject: [PATCH 02/27] Add build files --- .gitignore | 1 - build/package/Dockerfile | 5 +++++ build/package/Dockerfile.build | 21 +++++++++++++++++++++ build/package/Dockerfile.run | 8 ++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 build/package/Dockerfile create mode 100644 build/package/Dockerfile.build create mode 100644 build/package/Dockerfile.run diff --git a/.gitignore b/.gitignore index f83e763d..234d3a6d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,4 @@ release out/ _gopath/ .DS_Store -build vendor \ No newline at end of file diff --git a/build/package/Dockerfile b/build/package/Dockerfile new file mode 100644 index 00000000..aa806816 --- /dev/null +++ b/build/package/Dockerfile @@ -0,0 +1,5 @@ +FROM scratch + +ENTRYPOINT ["/reloader"] + +COPY ./reloader / diff --git a/build/package/Dockerfile.build b/build/package/Dockerfile.build new file mode 100644 index 00000000..517ee45e --- /dev/null +++ b/build/package/Dockerfile.build @@ -0,0 +1,21 @@ +FROM stakater/go-glide:1.9.3 +MAINTAINER "Stakater Team" + +RUN apk update + +RUN apk -v --update \ + add git build-base && \ + rm -rf /var/cache/apk/* && \ + mkdir -p "$GOPATH/src/github.com/stakater/Reloader" + +ADD . "$GOPATH/src/github.com/stakater/Reloader" + +RUN cd "$GOPATH/src/github.com/stakater/Reloader" && \ + glide update && \ + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a --installsuffix cgo --ldflags="-s" -o /Reloader + +COPY build/package/Dockerfile.run / + +# Running this image produces a tarball suitable to be piped into another +# Docker build command. +CMD tar -cf - -C / Dockerfile.run Reloader diff --git a/build/package/Dockerfile.run b/build/package/Dockerfile.run new file mode 100644 index 00000000..379698fb --- /dev/null +++ b/build/package/Dockerfile.run @@ -0,0 +1,8 @@ +FROM alpine:3.4 +MAINTAINER "Stakater Team" + +RUN apk add --update ca-certificates + +COPY Reloader /bin/Reloader + +ENTRYPOINT ["/bin/Reloader"] From ae4fabc635e8213e0686f766f66cdd87d73dfbae Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Mon, 9 Jul 2018 13:24:01 +0500 Subject: [PATCH 03/27] Remove duplicate readme --- README.1.md | 23 ----------------------- README.md | 24 +++++++++++++++++++++++- 2 files changed, 23 insertions(+), 24 deletions(-) delete mode 100644 README.1.md diff --git a/README.1.md b/README.1.md deleted file mode 100644 index 53146836..00000000 --- a/README.1.md +++ /dev/null @@ -1,23 +0,0 @@ -# configmapcontroller - -This controller watches for changes to `ConfigMap` and `Secret` objects and performs rolling upgrades on their associated deployments, deamonsets and statefulsets and updating dynamically. - -This is particularly useful if the `ConfigMap` is used to define environment variables - or your app cannot easily and reliably watch the `ConfigMap` and update itself on the fly. - -## How to use configmapcontroller - -For a `Deployment` called `foo` have a `ConfigMap` called `foo`. Then add this annotation to your `Deployment` - -```yaml -metadata: - annotations: - configmap.fabric8.io/update-on-change: "foo" -``` - -Then, providing `configmapcontroller` is running, whenever you edit the `ConfigMap` called `foo` the configmapcontroller will update the `Deployment` by adding the environment variable: - -``` -FABRICB_FOO_REVISION=${configMapRevision} -``` - -This then triggers a rolling upgrade of your deployment's pods to use the new configuration. diff --git a/README.md b/README.md index ba495473..fcddf26a 100644 --- a/README.md +++ b/README.md @@ -1 +1,23 @@ -# Reloader \ No newline at end of file +# Reloader + +This controller watches for changes to `ConfigMap` and `Secret` objects and performs rolling upgrades on their associated deployments, deamonsets and statefulsets and updating dynamically. + +This is particularly useful if the `ConfigMap` is used to define environment variables - or your app cannot easily and reliably watch the `ConfigMap` and update itself on the fly. + +## How to use Reloader + +For a `Deployment` called `foo` have a `ConfigMap` called `foo`. Then add this annotation to your `Deployment` + +```yaml +metadata: + annotations: + configmap.fabric8.io/update-on-change: "foo" +``` + +Then, providing `Reloader` is running, whenever you edit the `ConfigMap` called `foo` the Reloader will update the `Deployment` by adding the environment variable: + +``` +FABRICB_FOO_REVISION=${configMapRevision} +``` + +This then triggers a rolling upgrade of your deployment's pods to use the new configuration. From b79f90e70921761470fa40c2da7bffd233694569 Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Mon, 9 Jul 2018 20:23:15 +0500 Subject: [PATCH 04/27] Update controller to remove unnecessary code --- README.md | 8 +- build/package/Dockerfile | 4 +- configs/config.yaml | 2 + glide.yaml | 43 ++--- internal/pkg/client/client.go | 49 ------ internal/pkg/cmd/reloader.go | 72 ++------- internal/pkg/config/config.go | 62 +++++++ internal/pkg/controller/controller.go | 224 +++----------------------- internal/pkg/util/types.go | 51 ------ 9 files changed, 116 insertions(+), 399 deletions(-) create mode 100644 configs/config.yaml delete mode 100644 internal/pkg/client/client.go create mode 100644 internal/pkg/config/config.go delete mode 100644 internal/pkg/util/types.go diff --git a/README.md b/README.md index fcddf26a..d392a19e 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ This controller watches for changes to `ConfigMap` and `Secret` objects and perf This is particularly useful if the `ConfigMap` is used to define environment variables - or your app cannot easily and reliably watch the `ConfigMap` and update itself on the fly. +**NOTE:** This controller has been inspired from [configmapController](https://github.com/fabric8io/configmapcontroller) + ## How to use Reloader For a `Deployment` called `foo` have a `ConfigMap` called `foo`. Then add this annotation to your `Deployment` @@ -11,13 +13,13 @@ For a `Deployment` called `foo` have a `ConfigMap` called `foo`. Then add this a ```yaml metadata: annotations: - configmap.fabric8.io/update-on-change: "foo" + reloader.stakater.com/update-on-change: "foo" ``` Then, providing `Reloader` is running, whenever you edit the `ConfigMap` called `foo` the Reloader will update the `Deployment` by adding the environment variable: ``` -FABRICB_FOO_REVISION=${configMapRevision} +STAKATER_FOO_REVISION=${reloaderRevision} ``` -This then triggers a rolling upgrade of your deployment's pods to use the new configuration. +This then triggers a rolling upgrade of your deployment's pods to use the new configuration. \ No newline at end of file diff --git a/build/package/Dockerfile b/build/package/Dockerfile index aa806816..024cd720 100644 --- a/build/package/Dockerfile +++ b/build/package/Dockerfile @@ -1,5 +1,5 @@ FROM scratch -ENTRYPOINT ["/reloader"] +ENTRYPOINT ["/Reloader"] -COPY ./reloader / +COPY ./Reloader / diff --git a/configs/config.yaml b/configs/config.yaml new file mode 100644 index 00000000..185b4a16 --- /dev/null +++ b/configs/config.yaml @@ -0,0 +1,2 @@ +controllers: +- type: pods diff --git a/glide.yaml b/glide.yaml index a7023327..71c0a2fd 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,35 +1,14 @@ -package: . +package: github.com/stakater/Reloader import: -- package: github.com/openshift/origin - version: v1.3.0 - subpackages: - - pkg/client +- package: k8s.io/api + version: kubernetes-1.8.0 +- package: k8s.io/apimachinery + version: kubernetes-1.8.0 +- package: k8s.io/client-go + version: 5.0.0 - package: github.com/spf13/cobra + version: ef82de70bb3f60c65fb8eebacbb2d122ef517385 - package: github.com/spf13/pflag -- package: k8s.io/kubernetes - version: 52492b4bff99ef3b8ca617d385a3ff0612f9402d - repo: git://github.com/openshift/kubernetes.git - vcs: git - subpackages: - - pkg/api - - pkg/api/resource - - pkg/api/unversioned - - pkg/client/unversioned - - pkg/fields - - pkg/kubectl/cmd - - pkg/kubectl/cmd/util - - pkg/labels - - pkg/runtime - - pkg/util -- package: github.com/opencontainers/runc - version: v0.0.7 - subpackages: - - libcontainer -- package: github.com/imdario/mergo - version: 6633656539c1639d9d78127b7d47c622b5d7b6dc -- package: github.com/Sirupsen/logrus - version: aaf92c95712104318fc35409745f1533aa5ff327 -- package: github.com/docker/distribution - version: 559433598c7be9d30d6cfc5cad5b5dfdb686725c - repo: git://github.com/openshift/docker-distribution.git - vcs: git + version: 583c0c0531f06d5278b7d917446061adc344b5cd +- package: github.com/sirupsen/logrus + version: ~1.0.3 diff --git a/internal/pkg/client/client.go b/internal/pkg/client/client.go deleted file mode 100644 index 5f00be62..00000000 --- a/internal/pkg/client/client.go +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (C) 2015 Red Hat, Inc. - * - * 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 client - -import ( - oclient "github.com/openshift/origin/pkg/client" - "github.com/pkg/errors" - "k8s.io/kubernetes/pkg/client/restclient" - client "k8s.io/kubernetes/pkg/client/unversioned" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" -) - -func NewClient(f *cmdutil.Factory) (*client.Client, *restclient.Config, error) { - var err error - cfg, err := f.ClientConfig() - if err != nil { - return nil, nil, errors.Wrap(err, "Could not initialise client") - } - c, err := client.New(cfg) - if err != nil { - return nil, nil, errors.Wrap(err, "Could not initialise client") - } - - return c, cfg, nil -} - -func NewOpenShiftClient(cfg *restclient.Config) (*oclient.Client, *restclient.Config, error) { - ocfg := *cfg - ocfg.APIPath = "" - c, err := oclient.New(&ocfg) - if err != nil { - return nil, nil, errors.Wrap(err, "Could not initialise an OpenShift client") - } - - return c, cfg, nil -} diff --git a/internal/pkg/cmd/reloader.go b/internal/pkg/cmd/reloader.go index 64ce1403..b627d7d2 100644 --- a/internal/pkg/cmd/reloader.go +++ b/internal/pkg/cmd/reloader.go @@ -1,25 +1,13 @@ -package main +package cmd import ( - "flag" - "fmt" - "net/http" - "net/http/pprof" "os" - "os/signal" - "syscall" - "time" "github.com/spf13/cobra" - "github.com/stakater/Reloader/internal/pkg/client" "github.com/stakater/Reloader/internal/pkg/controller" - "github.com/stakater/Reloader/internal/pkg/util" "github.com/stakater/Reloader/pkg/kube" - "github.com/golang/glog" - oclient "github.com/openshift/origin/pkg/client" - "github.com/spf13/pflag" - "k8s.io/kubernetes/pkg/api" - kubectlutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "github.com/stakater/Reloader/internal/pkg/config" + "github.com/sirupsen/logrus" ) func NewReloaderCommand() *cobra.Command { @@ -31,42 +19,24 @@ func NewReloaderCommand() *cobra.Command { return cmds } -const ( - healthPort = 10254 -) - -var ( - flags = pflag.NewFlagSet("", pflag.ExitOnError) - - resyncPeriod = flags.Duration("sync-period", 30*time.Second, - `Relist and confirm services this often.`) - - healthzPort = flags.Int("healthz-port", healthPort, "port for healthz endpoint.") - - profiling = flags.Bool("profiling", true, `Enable profiling via web interface host:port/debug/pprof/`) -) - func startReloader(cmd *cobra.Command, args []string) { - glog.Println("Starting Reloader") + logrus.Info("Starting Reloader") // create the clientset clientset, err := kube.GetClient() if err != nil { - log.Fatal(err) + logrus.Fatal(err) } // get the Controller config file config := getControllerConfig() - for k, v := range kube.ResourceMap { - c, err := controller.NewController(clientset, *resyncPeriod, config, v) + for _, v := range kube.ResourceMap { + c, err := controller.NewController(clientset, config.Controllers[0], v) if err != nil { - glog.Fatalf("%s", err) + logrus.Fatalf("%s", err) } - go registerHandlers() - go handleSigterm(c) - // Now let's start the controller stop := make(chan struct{}) defer close(stop) @@ -78,30 +48,6 @@ func startReloader(cmd *cobra.Command, args []string) { select {} } -func registerHandlers() { - mux := http.NewServeMux() - - if *profiling { - mux.HandleFunc("/debug/pprof/", pprof.Index) - mux.HandleFunc("/debug/pprof/profile", pprof.Profile) - mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) - } - - server := &http.Server{ - Addr: fmt.Sprintf(":%v", *healthzPort), - Handler: mux, - } - glog.Fatal(server.ListenAndServe()) -} - -func handleSigterm(c *controller.Controller) { - signalChan := make(chan os.Signal, 1) - signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) - sig := <-signalChan - glog.Infof("Received %s, shutting down", sig) - c.Stop() -} - // get the yaml configuration for the controller func getControllerConfig() config.Config { configFilePath := os.Getenv("CONFIG_FILE_PATH") @@ -111,7 +57,7 @@ func getControllerConfig() config.Config { } configuration, err := config.ReadConfig(configFilePath) if err != nil { - log.Panic(err) + logrus.Panic(err) } return configuration } diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go new file mode 100644 index 00000000..f057c7d0 --- /dev/null +++ b/internal/pkg/config/config.go @@ -0,0 +1,62 @@ +package config + +import ( + "io/ioutil" + + yaml "gopkg.in/yaml.v2" +) + +// Config which would be read from the config.yaml +type Config struct { + Controllers []Controller +} + +// Controller which contains all the information for a specific controller +type Controller struct { + Type string + WatchCriterion Criterion + Actions []Action +} + +// Criterion as to what fields should the controller be looking at +type Criterion struct { + Operator string + Identifiers []string +} + +// Action that the controller will be taking based on the Parameters +type Action struct { + Name string + Params map[interface{}]interface{} +} + +// ReadConfig function that reads the yaml file +func ReadConfig(filePath string) (Config, error) { + var config Config + // Read YML + source, err := ioutil.ReadFile(filePath) + if err != nil { + return config, err + } + + // Unmarshall + err = yaml.Unmarshal(source, &config) + if err != nil { + return config, err + } + + return config, nil +} + +// WriteConfig function that can write to the yaml file +func WriteConfig(config Config, path string) error { + b, err := yaml.Marshal(config) + if err != nil { + return err + } + err = ioutil.WriteFile(path, b, 0644) + if err != nil { + return err + } + return nil +} diff --git a/internal/pkg/controller/controller.go b/internal/pkg/controller/controller.go index 78bac1ef..6c3089e5 100644 --- a/internal/pkg/controller/controller.go +++ b/internal/pkg/controller/controller.go @@ -1,32 +1,23 @@ package controller import ( - "bytes" - "strings" "time" "fmt" - "github.com/stakater/Reloader/internal/pkg/actions" - "github.com/golang/glog" - "github.com/pkg/errors" - - "k8s.io/kubernetes/pkg/api" - - clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes" + "k8s.io/apimachinery/pkg/util/runtime" informerruntime "k8s.io/apimachinery/pkg/runtime" + "github.com/stakater/Reloader/internal/pkg/config" "k8s.io/apimachinery/pkg/fields" - "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/watch" "k8s.io/apimachinery/pkg/util/wait" errorHandler "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" - - "sort" + "github.com/sirupsen/logrus" ) const ( - updateOnChangeAnnotation = "configmap.fabric8.io/update-on-change" + updateOnChangeAnnotation = "reloader.stakater.com.io/update-on-change" // AllNamespaces as our controller will be looking for events in all namespaces AllNamespaces = "" ) @@ -41,90 +32,40 @@ type Event struct { // Controller for checking events type Controller struct { - clientset clientset.Interface + client kubernetes.Interface indexer cache.Indexer queue workqueue.RateLimitingInterface informer cache.Controller controllerConfig config.Controller - Actions []actions.Action stopCh chan struct{} } // NewController for initializing a Controller func NewController( - clientset clientset.Interface, - resyncPeriod time.Duration, controllerConfig config.Controller, objType informerruntime.Object) (*Controller, error) { + client kubernetes.Interface, + controllerConfig config.Controller, objType informerruntime.Object) (*Controller, error) { c := Controller{ - clientset: clientset, + client: client, controllerConfig: controllerConfig, stopCh: make(chan struct{}), } queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) - listWatcher := cache.NewListWatchFromClient(clientset.CoreV1().RESTClient(), controllerConfig.Type, AllNamespaces, fields.Everything()) + listWatcher := cache.NewListWatchFromClient(client.CoreV1().RESTClient(), controllerConfig.Type, AllNamespaces, fields.Everything()) indexer, informer := cache.NewIndexerInformer(listWatcher, objType, 0, cache.ResourceEventHandlerFuncs { AddFunc: c.Add, UpdateFunc: c.Update, DeleteFunc: c.Delete, }, cache.Indexers{}) - - /*c.cmLister.Store, c.cmController = framework.NewInformer( - &cache.ListWatch{ - ListFunc: configMapListFunc(c.client, namespace), - WatchFunc: configMapWatchFunc(c.client, namespace), - }, - &api.ConfigMap{}, - resyncPeriod, - framework.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - newCM := obj.(*api.ConfigMap) - //typeOfMaster, err := util.TypeOfMaster(kubeClient) - if err != nil { - glog.Fatalf("failed to create REST client config: %s", err) - } - err = rollingUpgradeDeployments(newCM, kubeClient) - if err != nil { - glog.Errorf("failed to update Deployment: %v", err) - } - - }, - UpdateFunc: func(oldObj interface{}, newObj interface{}) { - oldM := oldObj.(*api.ConfigMap) - newCM := newObj.(*api.ConfigMap) - - if oldM.ResourceVersion != newCM.ResourceVersion { - //typeOfMaster, err := util.TypeOfMaster(kubeClient) - if err != nil { - glog.Fatalf("failed to create REST client config: %s", err) - } - err = rollingUpgradeDeployments(newCM, kubeClient) - if err != nil { - glog.Errorf("failed to update Deployment: %v", err) - } - } - }, - }, - )*/ + c.indexer = indexer + c.informer = informer + c.queue = queue return &c, nil } -// Run starts the controller. -/*func (c *Controller) Run() { - glog.Infof("starting reloader") - - <-c.stopCh -}*/ - -// Stop stops the controller. -/*func (c *Controller) Stop() { - glog.Infof("stopping reloader") - - close(c.stopCh) -}*/ - // Add function to add a 'create' event to the queue in case of creating a pod func (c *Controller) Add(obj interface{}) { key, err := cache.MetaNamespaceKeyFunc(obj) @@ -143,6 +84,8 @@ func (c *Controller) Update(old interface{}, new interface{}) { var event Event if err == nil { + event.key = key + event.eventType = "update" c.queue.Add(event) } } @@ -155,7 +98,7 @@ func (c *Controller) Delete(obj interface{}) { //Run function for controller which handles the queue func (c *Controller) Run(threadiness int, stopCh chan struct{}) { - glog.Infof("Starting Controller for type ", c.controllerConfig.Type) + logrus.Infof("Starting Controller for type ", c.controllerConfig.Type) defer errorHandler.HandleCrash() // Let the workers stop when we are done @@ -174,7 +117,7 @@ func (c *Controller) Run(threadiness int, stopCh chan struct{}) { } <-stopCh - glog.Infof("Stopping Controller for type ", c.controllerConfig.Type) + logrus.Infof("Stopping Controller for type ", c.controllerConfig.Type) } func (c *Controller) runWorker() { @@ -205,16 +148,16 @@ func (c *Controller) takeAction(event Event) error { obj, _, err := c.indexer.GetByKey(event.key) if err != nil { - glog.Infof("Fetching object with key %s from store failed with %v", event.key, err) + logrus.Infof("Fetching object with key %s from store failed with %v", event.key, err) } if obj == nil { - glog.Infof("Error in Action") + logrus.Infof("Error in Action") } else { - glog.Infof("Detected changes in object %s", obj) - /*glog.Infof("Resource block not found, performing actions") + logrus.Infof("Detected changes in object %s", obj) + /*logrus.Infof("Resource block not found, performing actions") // process events based on its type for index, action := range c.Actions { - glog.Infof("Performing '%s' action for controller of type '%s'", c.controllerConfig.Actions[index].Name, c.controllerConfig.Type) + gllogrusog.Infof("Performing '%s' action for controller of type '%s'", c.controllerConfig.Actions[index].Name, c.controllerConfig.Type) switch event.eventType { case "create": action.ObjectCreated(obj) @@ -241,7 +184,7 @@ func (c *Controller) handleErr(err error, key interface{}) { // This controller retries 5 times if something goes wrong. After that, it stops trying. if c.queue.NumRequeues(key) < 5 { - log.Printf("Error syncing events %v: %v", key, err) + logrus.Infof("Error syncing events %v: %v", key, err) // Re-enqueue the key rate limited. Based on the rate limiter on the // queue and the re-enqueue history, the key will be processed later again. @@ -252,122 +195,5 @@ func (c *Controller) handleErr(err error, key interface{}) { c.queue.Forget(key) // Report to an external entity that, even after several retries, we could not successfully process this key runtime.HandleError(err) - log.Printf("Dropping the key %q out of the queue: %v", key, err) -} - -/*func configMapListFunc(c *client.Client, ns string) func(api.ListOptions) (runtime.Object, error) { - return func(opts api.ListOptions) (runtime.Object, error) { - return c.ConfigMaps(ns).List(opts) - } -} - -func configMapWatchFunc(c *client.Client, ns string) func(options api.ListOptions) (watch.Interface, error) { - return func(options api.ListOptions) (watch.Interface, error) { - return c.ConfigMaps(ns).Watch(options) - } -} - -func rollingUpgradeDeployments(cm *api.ConfigMap, c *client.Client) error { - ns := cm.Namespace - configMapName := cm.Name - configMapVersion := convertConfigMapToToken(cm) - - deployments, err := c.Deployments(ns).List(api.ListOptions{}) - if err != nil { - return errors.Wrap(err, "failed to list deployments") - } - for _, d := range deployments.Items { - containers := d.Spec.Template.Spec.Containers - // match deployments with the correct annotation - annotationValue, _ := d.ObjectMeta.Annotations[updateOnChangeAnnotation] - if annotationValue != "" { - values := strings.Split(annotationValue, ",") - matches := false - for _, value := range values { - if value == configMapName { - matches = true - break - } - } - if matches { - updateContainers(containers, annotationValue, configMapVersion) - - // update the deployment - _, err := c.Deployments(ns).Update(&d) - if err != nil { - return errors.Wrap(err, "update deployment failed") - } - glog.Infof("Updated Deployment %s", d.Name) - } - } - } - return nil -}*/ - -// lets convert the configmap into a unique token based on the data values -func convertConfigMapToToken(cm *api.ConfigMap) string { - values := []string{} - for k, v := range cm.Data { - values = append(values, k+"="+v) - } - sort.Strings(values) - text := strings.Join(values, ";") - // we could zip and base64 encode - // but for now we could leave this easy to read so that its easier to diagnose when & why things changed - return text -} - -func updateContainers(containers []api.Container, annotationValue, configMapVersion string) bool { - // we can have multiple configmaps to update - answer := false - configmaps := strings.Split(annotationValue, ",") - for _, cmNameToUpdate := range configmaps { - configmapEnvar := "FABRIC8_" + convertToEnvVarName(cmNameToUpdate) + "_CONFIGMAP" - - for i := range containers { - envs := containers[i].Env - matched := false - for j := range envs { - if envs[j].Name == configmapEnvar { - matched = true - if envs[j].Value != configMapVersion { - glog.Infof("Updating %s to %s", configmapEnvar, configMapVersion) - envs[j].Value = configMapVersion - answer = true - } - } - } - // if no existing env var exists lets create one - if !matched { - e := api.EnvVar{ - Name: configmapEnvar, - Value: configMapVersion, - } - containers[i].Env = append(containers[i].Env, e) - answer = true - } - } - } - return answer -} - -// convertToEnvVarName converts the given text into a usable env var -// removing any special chars with '_' -func convertToEnvVarName(text string) string { - var buffer bytes.Buffer - lower := strings.ToUpper(text) - lastCharValid := false - for i := 0; i < len(lower); i++ { - ch := lower[i] - if (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') { - buffer.WriteString(string(ch)) - lastCharValid = true - } else { - if lastCharValid { - buffer.WriteString("_") - } - lastCharValid = false - } - } - return buffer.String() -} + logrus.Infof("Dropping the key %q out of the queue: %v", key, err) +} \ No newline at end of file diff --git a/internal/pkg/util/types.go b/internal/pkg/util/types.go deleted file mode 100644 index 4b48de4c..00000000 --- a/internal/pkg/util/types.go +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (C) 2015 Red Hat, Inc. - * - * 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 util - -import ( - "encoding/json" - - "github.com/pkg/errors" - - api "k8s.io/kubernetes/pkg/api/unversioned" - client "k8s.io/kubernetes/pkg/client/unversioned" -) - -type MasterType string - -const ( - OpenShift MasterType = "OpenShift" - Kubernetes MasterType = "Kubernetes" -) - -func TypeOfMaster(c *client.Client) (MasterType, error) { - res, err := c.Get().AbsPath("").DoRaw() - if err != nil { - return "", errors.Wrap(err, "could not discover the type of your installation") - } - - var rp api.RootPaths - err = json.Unmarshal(res, &rp) - if err != nil { - errors.Wrap(err, "could not discover the type of your installation") - } - for _, p := range rp.Paths { - if p == "/oapi" { - return OpenShift, nil - } - } - return Kubernetes, nil -} From acc0271dc67f6e92bc9d203a50df0739533865f2 Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Tue, 10 Jul 2018 13:14:55 +0500 Subject: [PATCH 05/27] Add chart in Reloader --- Makefile | 4 +- build/package/Dockerfile | 4 +- .../kubernetes/chart/reloader/Chart.yaml | 13 ++++++ .../chart/reloader/templates/_helpers.tpl | 33 +++++++++++++++ .../chart/reloader/templates/deployment.yaml | 42 +++++++++++++++++++ .../chart/reloader/templates/rbac.yaml | 40 ++++++++++++++++++ .../kubernetes/chart/reloader/values.yaml | 18 ++++++++ 7 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 deployments/kubernetes/chart/reloader/Chart.yaml create mode 100644 deployments/kubernetes/chart/reloader/templates/_helpers.tpl create mode 100644 deployments/kubernetes/chart/reloader/templates/deployment.yaml create mode 100644 deployments/kubernetes/chart/reloader/templates/rbac.yaml create mode 100644 deployments/kubernetes/chart/reloader/values.yaml diff --git a/Makefile b/Makefile index 28e821b1..55e2a25f 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ BUILDER ?= reloader-builder BINARY ?= Reloader DOCKER_IMAGE ?= stakater/reloader # Default value "dev" -DOCKER_TAG ?= dev +DOCKER_TAG ?= 1.0.0 REPOSITORY = ${DOCKER_IMAGE}:${DOCKER_TAG} VERSION=$(shell cat .version) @@ -47,6 +47,6 @@ push: ## push the latest Docker image to DockerHub docker push $(REPOSITORY) apply: - kubectl apply -f deployments/manifests/ + kubectl apply -f deployments/manifests/ -n temp-reloader deploy: binary-image push apply diff --git a/build/package/Dockerfile b/build/package/Dockerfile index 024cd720..aa806816 100644 --- a/build/package/Dockerfile +++ b/build/package/Dockerfile @@ -1,5 +1,5 @@ FROM scratch -ENTRYPOINT ["/Reloader"] +ENTRYPOINT ["/reloader"] -COPY ./Reloader / +COPY ./reloader / diff --git a/deployments/kubernetes/chart/reloader/Chart.yaml b/deployments/kubernetes/chart/reloader/Chart.yaml new file mode 100644 index 00000000..5677e79c --- /dev/null +++ b/deployments/kubernetes/chart/reloader/Chart.yaml @@ -0,0 +1,13 @@ +# Generated from deployments/kubernetes/templates/chart/Chart.yaml.tmpl + +apiVersion: v1 +name: reloader +description: Reloader chart that runs on kubernetes +version: 1.0.0 +keywords: + - Reloader + - kubernetes +home: https://github.com/stakater/Reloader +maintainers: +- name: Stakater + email: hello@stakater.com \ No newline at end of file diff --git a/deployments/kubernetes/chart/reloader/templates/_helpers.tpl b/deployments/kubernetes/chart/reloader/templates/_helpers.tpl new file mode 100644 index 00000000..85c17ae7 --- /dev/null +++ b/deployments/kubernetes/chart/reloader/templates/_helpers.tpl @@ -0,0 +1,33 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" | lower -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "labels.selector" -}} +app: {{ template "name" . }} +group: {{ .Values.reloader.labels.group }} +provider: {{ .Values.reloader.labels.provider }} +{{- end -}} + +{{- define "labels.stakater" -}} +{{ template "labels.selector" . }} +version: {{ .Values.reloader.labels.version }} +{{- end -}} + +{{- define "labels.chart" -}} +chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +release: {{ .Release.Name | quote }} +heritage: {{ .Release.Service | quote }} +{{- end -}} \ No newline at end of file diff --git a/deployments/kubernetes/chart/reloader/templates/deployment.yaml b/deployments/kubernetes/chart/reloader/templates/deployment.yaml new file mode 100644 index 00000000..ee857fb8 --- /dev/null +++ b/deployments/kubernetes/chart/reloader/templates/deployment.yaml @@ -0,0 +1,42 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + annotations: + configmap.fabric8.io/update-on-change: {{ template "name" . }} + labels: +{{ include "labels.stakater" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} + name: {{ template "name" . }} +spec: + replicas: 1 + revisionHistoryLimit: 2 + selector: + matchLabels: +{{ include "labels.selector" . | indent 6 }} + template: + metadata: + annotations: + configmap.fabric8.io/update-on-change: {{ template "name" . }} + labels: +{{ include "labels.selector" . | indent 8 }} + spec: + containers: + - env: + - name: KUBERNETES_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: CONFIG_FILE_PATH + value: {{ .Values.reloader.configFilePath }} + image: "{{ .Values.reloader.image.name }}:{{ .Values.reloader.image.tag }}" + imagePullPolicy: {{ .Values.reloader.image.pullPolicy }} + name: {{ template "name" . }} + volumeMounts: + - mountPath: /configs + name: config-volume + serviceAccountName: {{ template "name" . }} + volumes: + - configMap: + name: {{ template "name" . }} + name: config-volume + \ No newline at end of file diff --git a/deployments/kubernetes/chart/reloader/templates/rbac.yaml b/deployments/kubernetes/chart/reloader/templates/rbac.yaml new file mode 100644 index 00000000..90ee763f --- /dev/null +++ b/deployments/kubernetes/chart/reloader/templates/rbac.yaml @@ -0,0 +1,40 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: +{{ include "labels.stakater" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} + name: {{ template "name" . }} +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + labels: +{{ include "labels.stakater" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} + name: {{ template "name" . }}-role +rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - list + - get + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + labels: +{{ include "labels.stakater" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} + name: {{ template "name" . }}-role-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "name" . }}-role +subjects: + - kind: ServiceAccount + name: {{ template "name" . }} + namespace: .Release.Namespace \ No newline at end of file diff --git a/deployments/kubernetes/chart/reloader/values.yaml b/deployments/kubernetes/chart/reloader/values.yaml new file mode 100644 index 00000000..64165129 --- /dev/null +++ b/deployments/kubernetes/chart/reloader/values.yaml @@ -0,0 +1,18 @@ +# Generated from deployments/kubernetes/templates/chart/values.yaml.tmpl + +kubernetes: + host: https://kubernetes.default + +reloader: + labels: + provider: stakater + group: com.stakater.platform + version: 1.0.0 + image: + name: stakater/reloader + tag: "1.0.0" + pullPolicy: IfNotPresent + controllers: + - type: pods + + configFilePath: /configs/config.yaml \ No newline at end of file From 0004271e347a2b4a5da00676f1c527cd958e1a0a Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Tue, 10 Jul 2018 14:35:14 +0500 Subject: [PATCH 06/27] Remove unused config from reloader --- configs/config.yaml | 2 - .../chart/reloader/templates/deployment.yaml | 9 --- .../kubernetes/chart/reloader/values.yaml | 6 +- internal/pkg/cmd/reloader.go | 24 +------ internal/pkg/config/config.go | 62 ------------------- internal/pkg/controller/controller.go | 23 ++++--- 6 files changed, 14 insertions(+), 112 deletions(-) delete mode 100644 configs/config.yaml delete mode 100644 internal/pkg/config/config.go diff --git a/configs/config.yaml b/configs/config.yaml deleted file mode 100644 index 185b4a16..00000000 --- a/configs/config.yaml +++ /dev/null @@ -1,2 +0,0 @@ -controllers: -- type: pods diff --git a/deployments/kubernetes/chart/reloader/templates/deployment.yaml b/deployments/kubernetes/chart/reloader/templates/deployment.yaml index ee857fb8..8e8cc526 100644 --- a/deployments/kubernetes/chart/reloader/templates/deployment.yaml +++ b/deployments/kubernetes/chart/reloader/templates/deployment.yaml @@ -26,17 +26,8 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - - name: CONFIG_FILE_PATH - value: {{ .Values.reloader.configFilePath }} image: "{{ .Values.reloader.image.name }}:{{ .Values.reloader.image.tag }}" imagePullPolicy: {{ .Values.reloader.image.pullPolicy }} name: {{ template "name" . }} - volumeMounts: - - mountPath: /configs - name: config-volume serviceAccountName: {{ template "name" . }} - volumes: - - configMap: - name: {{ template "name" . }} - name: config-volume \ No newline at end of file diff --git a/deployments/kubernetes/chart/reloader/values.yaml b/deployments/kubernetes/chart/reloader/values.yaml index 64165129..0396ebe9 100644 --- a/deployments/kubernetes/chart/reloader/values.yaml +++ b/deployments/kubernetes/chart/reloader/values.yaml @@ -11,8 +11,4 @@ reloader: image: name: stakater/reloader tag: "1.0.0" - pullPolicy: IfNotPresent - controllers: - - type: pods - - configFilePath: /configs/config.yaml \ No newline at end of file + pullPolicy: IfNotPresent \ No newline at end of file diff --git a/internal/pkg/cmd/reloader.go b/internal/pkg/cmd/reloader.go index b627d7d2..6fe058b3 100644 --- a/internal/pkg/cmd/reloader.go +++ b/internal/pkg/cmd/reloader.go @@ -1,12 +1,9 @@ package cmd import ( - "os" - "github.com/spf13/cobra" "github.com/stakater/Reloader/internal/pkg/controller" "github.com/stakater/Reloader/pkg/kube" - "github.com/stakater/Reloader/internal/pkg/config" "github.com/sirupsen/logrus" ) @@ -28,11 +25,8 @@ func startReloader(cmd *cobra.Command, args []string) { logrus.Fatal(err) } - // get the Controller config file - config := getControllerConfig() - - for _, v := range kube.ResourceMap { - c, err := controller.NewController(clientset, config.Controllers[0], v) + for k, v := range kube.ResourceMap { + c, err := controller.NewController(clientset, k, v) if err != nil { logrus.Fatalf("%s", err) } @@ -47,17 +41,3 @@ func startReloader(cmd *cobra.Command, args []string) { // Wait forever select {} } - -// get the yaml configuration for the controller -func getControllerConfig() config.Config { - configFilePath := os.Getenv("CONFIG_FILE_PATH") - if len(configFilePath) == 0 { - //Default config file is placed in configs/ folder - configFilePath = "configs/config.yaml" - } - configuration, err := config.ReadConfig(configFilePath) - if err != nil { - logrus.Panic(err) - } - return configuration -} diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go deleted file mode 100644 index f057c7d0..00000000 --- a/internal/pkg/config/config.go +++ /dev/null @@ -1,62 +0,0 @@ -package config - -import ( - "io/ioutil" - - yaml "gopkg.in/yaml.v2" -) - -// Config which would be read from the config.yaml -type Config struct { - Controllers []Controller -} - -// Controller which contains all the information for a specific controller -type Controller struct { - Type string - WatchCriterion Criterion - Actions []Action -} - -// Criterion as to what fields should the controller be looking at -type Criterion struct { - Operator string - Identifiers []string -} - -// Action that the controller will be taking based on the Parameters -type Action struct { - Name string - Params map[interface{}]interface{} -} - -// ReadConfig function that reads the yaml file -func ReadConfig(filePath string) (Config, error) { - var config Config - // Read YML - source, err := ioutil.ReadFile(filePath) - if err != nil { - return config, err - } - - // Unmarshall - err = yaml.Unmarshal(source, &config) - if err != nil { - return config, err - } - - return config, nil -} - -// WriteConfig function that can write to the yaml file -func WriteConfig(config Config, path string) error { - b, err := yaml.Marshal(config) - if err != nil { - return err - } - err = ioutil.WriteFile(path, b, 0644) - if err != nil { - return err - } - return nil -} diff --git a/internal/pkg/controller/controller.go b/internal/pkg/controller/controller.go index 6c3089e5..d1dee587 100644 --- a/internal/pkg/controller/controller.go +++ b/internal/pkg/controller/controller.go @@ -7,7 +7,6 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/apimachinery/pkg/util/runtime" informerruntime "k8s.io/apimachinery/pkg/runtime" - "github.com/stakater/Reloader/internal/pkg/config" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/util/wait" errorHandler "k8s.io/apimachinery/pkg/util/runtime" @@ -33,10 +32,10 @@ type Event struct { // Controller for checking events type Controller struct { client kubernetes.Interface - indexer cache.Indexer - queue workqueue.RateLimitingInterface - informer cache.Controller - controllerConfig config.Controller + indexer cache.Indexer + queue workqueue.RateLimitingInterface + informer cache.Controller + resource string stopCh chan struct{} } @@ -44,16 +43,16 @@ type Controller struct { // NewController for initializing a Controller func NewController( client kubernetes.Interface, - controllerConfig config.Controller, objType informerruntime.Object) (*Controller, error) { + resource string, objType informerruntime.Object) (*Controller, error) { c := Controller{ - client: client, - controllerConfig: controllerConfig, - stopCh: make(chan struct{}), + client: client, + resource: resource, + stopCh: make(chan struct{}), } queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) - listWatcher := cache.NewListWatchFromClient(client.CoreV1().RESTClient(), controllerConfig.Type, AllNamespaces, fields.Everything()) + listWatcher := cache.NewListWatchFromClient(client.CoreV1().RESTClient(), resource, AllNamespaces, fields.Everything()) indexer, informer := cache.NewIndexerInformer(listWatcher, objType, 0, cache.ResourceEventHandlerFuncs { AddFunc: c.Add, @@ -98,7 +97,7 @@ func (c *Controller) Delete(obj interface{}) { //Run function for controller which handles the queue func (c *Controller) Run(threadiness int, stopCh chan struct{}) { - logrus.Infof("Starting Controller for type ", c.controllerConfig.Type) + logrus.Infof("Starting Controller for type ", c.resource) defer errorHandler.HandleCrash() // Let the workers stop when we are done @@ -117,7 +116,7 @@ func (c *Controller) Run(threadiness int, stopCh chan struct{}) { } <-stopCh - logrus.Infof("Stopping Controller for type ", c.controllerConfig.Type) + logrus.Infof("Stopping Controller for type ", c.resource) } func (c *Controller) runWorker() { From c05ef239b2f444237e7e8b89b08475b61f9cf33f Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Tue, 10 Jul 2018 15:16:52 +0500 Subject: [PATCH 07/27] Update rbac in reloader --- deployments/kubernetes/chart/reloader/templates/rbac.yaml | 3 ++- pkg/kube/resourcemapper.go | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deployments/kubernetes/chart/reloader/templates/rbac.yaml b/deployments/kubernetes/chart/reloader/templates/rbac.yaml index 90ee763f..e7381895 100644 --- a/deployments/kubernetes/chart/reloader/templates/rbac.yaml +++ b/deployments/kubernetes/chart/reloader/templates/rbac.yaml @@ -17,7 +17,8 @@ rules: - apiGroups: - "" resources: - - pods + - secrets + - configmaps verbs: - list - get diff --git a/pkg/kube/resourcemapper.go b/pkg/kube/resourcemapper.go index 78571713..17e7d121 100644 --- a/pkg/kube/resourcemapper.go +++ b/pkg/kube/resourcemapper.go @@ -22,5 +22,4 @@ func MapToRuntimeObject(resourceType string) runtime.Object { var ResourceMap = map[string]runtime.Object{ "configMaps": &v1.ConfigMap{}, "secrets": &v1.Secret{}, - "default": nil, } From dcc11b01335a6aa68c6266f3ce423e42701b26b4 Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Tue, 10 Jul 2018 15:54:32 +0500 Subject: [PATCH 08/27] Fix namespace issue in rbac --- deployments/kubernetes/chart/reloader/templates/rbac.yaml | 2 +- internal/pkg/controller/controller.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deployments/kubernetes/chart/reloader/templates/rbac.yaml b/deployments/kubernetes/chart/reloader/templates/rbac.yaml index e7381895..1fa583e9 100644 --- a/deployments/kubernetes/chart/reloader/templates/rbac.yaml +++ b/deployments/kubernetes/chart/reloader/templates/rbac.yaml @@ -38,4 +38,4 @@ roleRef: subjects: - kind: ServiceAccount name: {{ template "name" . }} - namespace: .Release.Namespace \ No newline at end of file + namespace: {{ .Release.Namespace }} \ No newline at end of file diff --git a/internal/pkg/controller/controller.go b/internal/pkg/controller/controller.go index d1dee587..f1f2b687 100644 --- a/internal/pkg/controller/controller.go +++ b/internal/pkg/controller/controller.go @@ -18,7 +18,7 @@ import ( const ( updateOnChangeAnnotation = "reloader.stakater.com.io/update-on-change" // AllNamespaces as our controller will be looking for events in all namespaces - AllNamespaces = "" + AllNamespaces = "temp-reloader" ) // Event indicate the informerEvent From 928125651ed228f417901f7a377c8f84423a01d4 Mon Sep 17 00:00:00 2001 From: Waseem Hassan Date: Tue, 10 Jul 2018 17:40:50 +0500 Subject: [PATCH 09/27] Append reloader to templates --- .../chart/reloader/templates/_helpers.tpl | 14 ++++++------ .../chart/reloader/templates/deployment.yaml | 18 +++++++-------- .../chart/reloader/templates/rbac.yaml | 22 +++++++++---------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/deployments/kubernetes/chart/reloader/templates/_helpers.tpl b/deployments/kubernetes/chart/reloader/templates/_helpers.tpl index 85c17ae7..82aa1448 100644 --- a/deployments/kubernetes/chart/reloader/templates/_helpers.tpl +++ b/deployments/kubernetes/chart/reloader/templates/_helpers.tpl @@ -2,7 +2,7 @@ {{/* Expand the name of the chart. */}} -{{- define "name" -}} +{{- define "reloader-name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" | lower -}} {{- end -}} @@ -10,23 +10,23 @@ Expand the name of the chart. Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). */}} -{{- define "fullname" -}} +{{- define "reloader-fullname" -}} {{- $name := default .Chart.Name .Values.nameOverride -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} -{{- define "labels.selector" -}} -app: {{ template "name" . }} +{{- define "reloader-labels.selector" -}} +app: {{ template "reloader-name" . }} group: {{ .Values.reloader.labels.group }} provider: {{ .Values.reloader.labels.provider }} {{- end -}} -{{- define "labels.stakater" -}} -{{ template "labels.selector" . }} +{{- define "reloader-labels.stakater" -}} +{{ template "reloader-labels.selector" . }} version: {{ .Values.reloader.labels.version }} {{- end -}} -{{- define "labels.chart" -}} +{{- define "reloader-labels.chart" -}} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: {{ .Release.Name | quote }} heritage: {{ .Release.Service | quote }} diff --git a/deployments/kubernetes/chart/reloader/templates/deployment.yaml b/deployments/kubernetes/chart/reloader/templates/deployment.yaml index 8e8cc526..ac008af8 100644 --- a/deployments/kubernetes/chart/reloader/templates/deployment.yaml +++ b/deployments/kubernetes/chart/reloader/templates/deployment.yaml @@ -2,23 +2,23 @@ apiVersion: extensions/v1beta1 kind: Deployment metadata: annotations: - configmap.fabric8.io/update-on-change: {{ template "name" . }} + configmap.fabric8.io/update-on-change: {{ template "reloader-name" . }} labels: -{{ include "labels.stakater" . | indent 4 }} -{{ include "labels.chart" . | indent 4 }} - name: {{ template "name" . }} +{{ include "reloader-labels.stakater" . | indent 4 }} +{{ include "reloader-labels.chart" . | indent 4 }} + name: {{ template "reloader-name" . }} spec: replicas: 1 revisionHistoryLimit: 2 selector: matchLabels: -{{ include "labels.selector" . | indent 6 }} +{{ include "reloader-labels.selector" . | indent 6 }} template: metadata: annotations: - configmap.fabric8.io/update-on-change: {{ template "name" . }} + configmap.fabric8.io/update-on-change: {{ template "reloader-name" . }} labels: -{{ include "labels.selector" . | indent 8 }} +{{ include "reloader-labels.selector" . | indent 8 }} spec: containers: - env: @@ -28,6 +28,6 @@ spec: fieldPath: metadata.namespace image: "{{ .Values.reloader.image.name }}:{{ .Values.reloader.image.tag }}" imagePullPolicy: {{ .Values.reloader.image.pullPolicy }} - name: {{ template "name" . }} - serviceAccountName: {{ template "name" . }} + name: {{ template "reloader-name" . }} + serviceAccountName: {{ template "reloader-name" . }} \ No newline at end of file diff --git a/deployments/kubernetes/chart/reloader/templates/rbac.yaml b/deployments/kubernetes/chart/reloader/templates/rbac.yaml index 1fa583e9..87b3fde0 100644 --- a/deployments/kubernetes/chart/reloader/templates/rbac.yaml +++ b/deployments/kubernetes/chart/reloader/templates/rbac.yaml @@ -2,17 +2,17 @@ apiVersion: v1 kind: ServiceAccount metadata: labels: -{{ include "labels.stakater" . | indent 4 }} -{{ include "labels.chart" . | indent 4 }} - name: {{ template "name" . }} +{{ include "reloader-labels.stakater" . | indent 4 }} +{{ include "reloader-labels.chart" . | indent 4 }} + name: {{ template "reloader-name" . }} --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: labels: -{{ include "labels.stakater" . | indent 4 }} -{{ include "labels.chart" . | indent 4 }} - name: {{ template "name" . }}-role +{{ include "reloader-labels.stakater" . | indent 4 }} +{{ include "reloader-labels.chart" . | indent 4 }} + name: {{ template "reloader-name" . }}-role rules: - apiGroups: - "" @@ -28,14 +28,14 @@ apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: labels: -{{ include "labels.stakater" . | indent 4 }} -{{ include "labels.chart" . | indent 4 }} - name: {{ template "name" . }}-role-binding +{{ include "reloader-labels.stakater" . | indent 4 }} +{{ include "reloader-labels.chart" . | indent 4 }} + name: {{ template "reloader-name" . }}-role-binding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: {{ template "name" . }}-role + name: {{ template "reloader-name" . }}-role subjects: - kind: ServiceAccount - name: {{ template "name" . }} + name: {{ template "reloader-name" . }} namespace: {{ .Release.Namespace }} \ No newline at end of file From a2af49c89d2e027594ded7e623ad7d4a479bc0a5 Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Tue, 10 Jul 2018 20:08:45 +0500 Subject: [PATCH 10/27] Implement actions for configmap upgrade --- internal/pkg/actions/action.go | 160 +++++++++++++++++++++ internal/pkg/controller/controller.go | 15 +- internal/pkg/controller/controller_test.go | 91 ++++++++++++ 3 files changed, 258 insertions(+), 8 deletions(-) create mode 100644 internal/pkg/actions/action.go create mode 100644 internal/pkg/controller/controller_test.go diff --git a/internal/pkg/actions/action.go b/internal/pkg/actions/action.go new file mode 100644 index 00000000..66b82211 --- /dev/null +++ b/internal/pkg/actions/action.go @@ -0,0 +1,160 @@ +package actions + +import ( + "bytes" + "sort" + "strings" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "k8s.io/api/core/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +const ( + updateOnChangeAnnotation = "reloader.stakater.com/update-on-change" +) + +// Action interface so that other actions like slack can implement this +type Action interface { + ObjectCreated(obj interface{}, client kubernetes.Interface) + ObjectDeleted(obj interface{}) + ObjectUpdated(oldObj interface{}, client kubernetes.Interface) +} + +// Default class with empty implementations for any action that we dont support currently +type Default struct { +} + +// ObjectCreated Do nothing for default handler +func (d *Default) ObjectCreated(obj interface{}, client kubernetes.Interface) { + message := "Configmap: `" + obj.(*v1.ConfigMap).Name + "`has been created in Namespace: `" + obj.(*v1.ConfigMap).Namespace + "`" + logrus.Infof(message) + err := rollingUpgradeDeployments(obj, client) + if err != nil { + logrus.Errorf("failed to update Deployment: %v", err) + } +} + +// ObjectDeleted Do nothing for default handler +func (d *Default) ObjectDeleted(obj interface{}) { + +} + +// ObjectUpdated Do nothing for default handler +func (d *Default) ObjectUpdated(oldObj interface{}, client kubernetes.Interface) { + message := "Configmap: `" + oldObj.(*v1.ConfigMap).Name + "`has been updated in Namespace: `" + oldObj.(*v1.ConfigMap).Namespace + "`" + logrus.Infof(message) + err := rollingUpgradeDeployments(oldObj, client) + if err != nil { + logrus.Errorf("failed to update Deployment: %v", err) + } +} + +// Implementation has been borrowed from fabric8io/configmapcontroller +// Method has been modified a little to use updated liberaries. +func rollingUpgradeDeployments(oldObj interface{}, client kubernetes.Interface) error { + ns := oldObj.(*v1.ConfigMap).Namespace + configMapName := oldObj.(*v1.ConfigMap).Name + configMapVersion := convertConfigMapToToken(oldObj.(*v1.ConfigMap)) + + deployments, err := client.Apps().Deployments(ns).List(meta_v1.ListOptions{}) + if err != nil { + return errors.Wrap(err, "failed to list deployments") + } + for _, d := range deployments.Items { + containers := d.Spec.Template.Spec.Containers + // match deployments with the correct annotation + annotationValue, _ := d.ObjectMeta.Annotations[updateOnChangeAnnotation] + if annotationValue != "" { + values := strings.Split(annotationValue, ",") + matches := false + for _, value := range values { + if value == configMapName { + matches = true + break + } + } + if matches { + updateContainers(containers, annotationValue, configMapVersion) + + // update the deployment + _, err := client.Apps().Deployments(ns).Update(&d) + if err != nil { + return errors.Wrap(err, "update deployment failed") + } + logrus.Infof("Updated Deployment %s", d.Name) + } + } + } + return nil +} + +func updateContainers(containers []v1.Container, annotationValue, configMapVersion string) bool { + // we can have multiple configmaps to update + answer := false + configmaps := strings.Split(annotationValue, ",") + for _, cmNameToUpdate := range configmaps { + configmapEnvar := "STAKATER_" + convertToEnvVarName(cmNameToUpdate) + "_CONFIGMAP" + + for i := range containers { + envs := containers[i].Env + matched := false + for j := range envs { + if envs[j].Name == configmapEnvar { + matched = true + if envs[j].Value != configMapVersion { + logrus.Infof("Updating %s to %s", configmapEnvar, configMapVersion) + envs[j].Value = configMapVersion + answer = true + } + } + } + // if no existing env var exists lets create one + if !matched { + e := v1.EnvVar{ + Name: configmapEnvar, + Value: configMapVersion, + } + containers[i].Env = append(containers[i].Env, e) + answer = true + } + } + } + return answer +} + +// convertToEnvVarName converts the given text into a usable env var +// removing any special chars with '_' +func convertToEnvVarName(text string) string { + var buffer bytes.Buffer + lower := strings.ToUpper(text) + lastCharValid := false + for i := 0; i < len(lower); i++ { + ch := lower[i] + if (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') { + buffer.WriteString(string(ch)) + lastCharValid = true + } else { + if lastCharValid { + buffer.WriteString("_") + } + lastCharValid = false + } + } + return buffer.String() +} + +// lets convert the configmap into a unique token based on the data values +func convertConfigMapToToken(cm *v1.ConfigMap) string { + values := []string{} + for k, v := range cm.Data { + values = append(values, k+"="+v) + } + sort.Strings(values) + text := strings.Join(values, ";") + // we could zip and base64 encode + // but for now we could leave this easy to read so that its easier to diagnose when & why things changed + return text +} diff --git a/internal/pkg/controller/controller.go b/internal/pkg/controller/controller.go index f1f2b687..272d0f37 100644 --- a/internal/pkg/controller/controller.go +++ b/internal/pkg/controller/controller.go @@ -13,10 +13,10 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" "github.com/sirupsen/logrus" + "github.com/stakater/Reloader/internal/pkg/actions" ) const ( - updateOnChangeAnnotation = "reloader.stakater.com.io/update-on-change" // AllNamespaces as our controller will be looking for events in all namespaces AllNamespaces = "temp-reloader" ) @@ -36,6 +36,7 @@ type Controller struct { queue workqueue.RateLimitingInterface informer cache.Controller resource string + Actions []actions.Action stopCh chan struct{} } @@ -153,20 +154,18 @@ func (c *Controller) takeAction(event Event) error { logrus.Infof("Error in Action") } else { logrus.Infof("Detected changes in object %s", obj) - /*logrus.Infof("Resource block not found, performing actions") // process events based on its type - for index, action := range c.Actions { - gllogrusog.Infof("Performing '%s' action for controller of type '%s'", c.controllerConfig.Actions[index].Name, c.controllerConfig.Type) + for _, action := range c.Actions { + logrus.Infof("Performing '%s' action for controller of type '%s'", event.eventType, c.resource) switch event.eventType { case "create": - action.ObjectCreated(obj) + action.ObjectCreated(obj, c.client) case "update": - //TODO: Figure how to pass old and new object - action.ObjectUpdated(obj, nil) + action.ObjectUpdated(obj, c.client) case "delete": action.ObjectDeleted(obj) } - }*/ + } } return nil } diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go new file mode 100644 index 00000000..4dd54ded --- /dev/null +++ b/internal/pkg/controller/controller_test.go @@ -0,0 +1,91 @@ +package controller + +import ( + "time" + "math/rand" + + "github.com/stakater/Reloader/pkg/kube" + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var ( + client, _ = kube.GetClient() + configmapNamePrefix = "testconfigmap-reloader" + letters = []rune("abcdefghijklmnopqrstuvwxyz") +) + +func randSeq(n int) string { + rand.Seed(time.Now().UnixNano()) + b := make([]rune, n) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + return string(b) +} + +// Creating a Controller for Updating Pod with Default Action without Resources so messages printed +/*func TestControllerForUpdatePodShouldUpdateDefaultAction(t *testing.T) { + controller, err := NewController(client, "configMaps", &v1.ConfigMap{}) + if err != nil { + logrus.Infof("Unable to create NewController error = %v", err) + return + } + stop := make(chan struct{}) + defer close(stop) + go controller.Run(1, stop) + time.Sleep(10 * time.Second) + namespace := "test" + configmapName := configmapNamePrefix + "-withoutresources-update-" + randSeq(5) + configmapClient := client.CoreV1().ConfigMaps(namespace) + configmap := initConfigmap(namespace, configmapName) + configmap, err = configmapClient.Create(configmap) + if err != nil { + panic(err) + } + logrus.Infof("Created Configmap %q.\n", configmap.GetObjectMeta().GetName()) + time.Sleep(10 * time.Second) + + logrus.Infof("Updating Configmap %q.\n", configmap.GetObjectMeta().GetName()) + retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { + configmap, err = configmapClient.Get(configmapName, metav1.GetOptions{}) + if err != nil { + + } + configmap = updateConfigmap(namespace, configmapName) + _, updateErr := configmapClient.Update(configmap) + return updateErr + }) + + + // TODO: Add functionality to verify reloader functionality here + + if retryErr != nil { + controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) + panic(retryErr) + } + time.Sleep(10 * time.Second) + logrus.Infof("Deleting Pod %q.\n", configmap.GetObjectMeta().GetName()) + controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) + time.Sleep(15 * time.Second) +}*/ + +func initConfigmap(namespace string, configmapName string) *v1.ConfigMap { + return &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: configmapName, + Namespace: namespace, + Labels: map[string]string{"firstLabel": "temp"}, + }, + } +} + +func updateConfigmap(namespace string, configmapName string) *v1.ConfigMap { + return &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: configmapName, + Namespace: namespace, + Labels: map[string]string{"firstLabel": "updated"}, + }, + } +} From 7944eb41fb799e4987ead12dd47e7ba09942464c Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Wed, 11 Jul 2018 15:47:36 +0500 Subject: [PATCH 11/27] Add action methods in controller --- internal/pkg/actions/action.go | 160 -------------------------- internal/pkg/controller/controller.go | 155 +++++++++++++++++++++++-- 2 files changed, 145 insertions(+), 170 deletions(-) delete mode 100644 internal/pkg/actions/action.go diff --git a/internal/pkg/actions/action.go b/internal/pkg/actions/action.go deleted file mode 100644 index 66b82211..00000000 --- a/internal/pkg/actions/action.go +++ /dev/null @@ -1,160 +0,0 @@ -package actions - -import ( - "bytes" - "sort" - "strings" - - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "k8s.io/api/core/v1" - meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" -) - -const ( - updateOnChangeAnnotation = "reloader.stakater.com/update-on-change" -) - -// Action interface so that other actions like slack can implement this -type Action interface { - ObjectCreated(obj interface{}, client kubernetes.Interface) - ObjectDeleted(obj interface{}) - ObjectUpdated(oldObj interface{}, client kubernetes.Interface) -} - -// Default class with empty implementations for any action that we dont support currently -type Default struct { -} - -// ObjectCreated Do nothing for default handler -func (d *Default) ObjectCreated(obj interface{}, client kubernetes.Interface) { - message := "Configmap: `" + obj.(*v1.ConfigMap).Name + "`has been created in Namespace: `" + obj.(*v1.ConfigMap).Namespace + "`" - logrus.Infof(message) - err := rollingUpgradeDeployments(obj, client) - if err != nil { - logrus.Errorf("failed to update Deployment: %v", err) - } -} - -// ObjectDeleted Do nothing for default handler -func (d *Default) ObjectDeleted(obj interface{}) { - -} - -// ObjectUpdated Do nothing for default handler -func (d *Default) ObjectUpdated(oldObj interface{}, client kubernetes.Interface) { - message := "Configmap: `" + oldObj.(*v1.ConfigMap).Name + "`has been updated in Namespace: `" + oldObj.(*v1.ConfigMap).Namespace + "`" - logrus.Infof(message) - err := rollingUpgradeDeployments(oldObj, client) - if err != nil { - logrus.Errorf("failed to update Deployment: %v", err) - } -} - -// Implementation has been borrowed from fabric8io/configmapcontroller -// Method has been modified a little to use updated liberaries. -func rollingUpgradeDeployments(oldObj interface{}, client kubernetes.Interface) error { - ns := oldObj.(*v1.ConfigMap).Namespace - configMapName := oldObj.(*v1.ConfigMap).Name - configMapVersion := convertConfigMapToToken(oldObj.(*v1.ConfigMap)) - - deployments, err := client.Apps().Deployments(ns).List(meta_v1.ListOptions{}) - if err != nil { - return errors.Wrap(err, "failed to list deployments") - } - for _, d := range deployments.Items { - containers := d.Spec.Template.Spec.Containers - // match deployments with the correct annotation - annotationValue, _ := d.ObjectMeta.Annotations[updateOnChangeAnnotation] - if annotationValue != "" { - values := strings.Split(annotationValue, ",") - matches := false - for _, value := range values { - if value == configMapName { - matches = true - break - } - } - if matches { - updateContainers(containers, annotationValue, configMapVersion) - - // update the deployment - _, err := client.Apps().Deployments(ns).Update(&d) - if err != nil { - return errors.Wrap(err, "update deployment failed") - } - logrus.Infof("Updated Deployment %s", d.Name) - } - } - } - return nil -} - -func updateContainers(containers []v1.Container, annotationValue, configMapVersion string) bool { - // we can have multiple configmaps to update - answer := false - configmaps := strings.Split(annotationValue, ",") - for _, cmNameToUpdate := range configmaps { - configmapEnvar := "STAKATER_" + convertToEnvVarName(cmNameToUpdate) + "_CONFIGMAP" - - for i := range containers { - envs := containers[i].Env - matched := false - for j := range envs { - if envs[j].Name == configmapEnvar { - matched = true - if envs[j].Value != configMapVersion { - logrus.Infof("Updating %s to %s", configmapEnvar, configMapVersion) - envs[j].Value = configMapVersion - answer = true - } - } - } - // if no existing env var exists lets create one - if !matched { - e := v1.EnvVar{ - Name: configmapEnvar, - Value: configMapVersion, - } - containers[i].Env = append(containers[i].Env, e) - answer = true - } - } - } - return answer -} - -// convertToEnvVarName converts the given text into a usable env var -// removing any special chars with '_' -func convertToEnvVarName(text string) string { - var buffer bytes.Buffer - lower := strings.ToUpper(text) - lastCharValid := false - for i := 0; i < len(lower); i++ { - ch := lower[i] - if (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') { - buffer.WriteString(string(ch)) - lastCharValid = true - } else { - if lastCharValid { - buffer.WriteString("_") - } - lastCharValid = false - } - } - return buffer.String() -} - -// lets convert the configmap into a unique token based on the data values -func convertConfigMapToToken(cm *v1.ConfigMap) string { - values := []string{} - for k, v := range cm.Data { - values = append(values, k+"="+v) - } - sort.Strings(values) - text := strings.Join(values, ";") - // we could zip and base64 encode - // but for now we could leave this easy to read so that its easier to diagnose when & why things changed - return text -} diff --git a/internal/pkg/controller/controller.go b/internal/pkg/controller/controller.go index 272d0f37..9c36c800 100644 --- a/internal/pkg/controller/controller.go +++ b/internal/pkg/controller/controller.go @@ -3,7 +3,10 @@ package controller import ( "time" "fmt" - + "strings" + "bytes" + "sort" + "k8s.io/client-go/kubernetes" "k8s.io/apimachinery/pkg/util/runtime" informerruntime "k8s.io/apimachinery/pkg/runtime" @@ -13,10 +16,13 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" "github.com/sirupsen/logrus" - "github.com/stakater/Reloader/internal/pkg/actions" + "github.com/pkg/errors" + "k8s.io/api/core/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( + updateOnChangeAnnotation = "reloader.stakater.com/update-on-change" // AllNamespaces as our controller will be looking for events in all namespaces AllNamespaces = "temp-reloader" ) @@ -36,7 +42,6 @@ type Controller struct { queue workqueue.RateLimitingInterface informer cache.Controller resource string - Actions []actions.Action stopCh chan struct{} } @@ -155,16 +160,14 @@ func (c *Controller) takeAction(event Event) error { } else { logrus.Infof("Detected changes in object %s", obj) // process events based on its type - for _, action := range c.Actions { - logrus.Infof("Performing '%s' action for controller of type '%s'", event.eventType, c.resource) - switch event.eventType { + logrus.Infof("Performing '%s' action for controller of type '%s'", event.eventType, c.resource) + switch event.eventType { case "create": - action.ObjectCreated(obj, c.client) + ObjectCreated(obj, c.client) case "update": - action.ObjectUpdated(obj, c.client) + ObjectUpdated(obj, c.client) case "delete": - action.ObjectDeleted(obj) - } + ObjectDeleted(obj) } } return nil @@ -194,4 +197,136 @@ func (c *Controller) handleErr(err error, key interface{}) { // Report to an external entity that, even after several retries, we could not successfully process this key runtime.HandleError(err) logrus.Infof("Dropping the key %q out of the queue: %v", key, err) +} + +// ObjectCreated Do nothing for default handler +func ObjectCreated(obj interface{}, client kubernetes.Interface) { + message := "Configmap: `" + obj.(*v1.ConfigMap).Name + "`has been created in Namespace: `" + obj.(*v1.ConfigMap).Namespace + "`" + logrus.Infof(message) + err := rollingUpgradeDeployments(obj, client) + if err != nil { + logrus.Errorf("failed to update Deployment: %v", err) + } +} + +// ObjectDeleted Do nothing for default handler +func ObjectDeleted(obj interface{}) { + +} + +// ObjectUpdated Do nothing for default handler +func ObjectUpdated(oldObj interface{}, client kubernetes.Interface) { + message := "Configmap: `" + oldObj.(*v1.ConfigMap).Name + "`has been updated in Namespace: `" + oldObj.(*v1.ConfigMap).Namespace + "`" + logrus.Infof(message) + err := rollingUpgradeDeployments(oldObj, client) + if err != nil { + logrus.Errorf("failed to update Deployment: %v", err) + } +} + +// Implementation has been borrowed from fabric8io/configmapcontroller +// Method has been modified a little to use updated liberaries. +func rollingUpgradeDeployments(oldObj interface{}, client kubernetes.Interface) error { + ns := oldObj.(*v1.ConfigMap).Namespace + configMapName := oldObj.(*v1.ConfigMap).Name + configMapVersion := convertConfigMapToToken(oldObj.(*v1.ConfigMap)) + + deployments, err := client.Apps().Deployments(ns).List(meta_v1.ListOptions{}) + if err != nil { + return errors.Wrap(err, "failed to list deployments") + } + for _, d := range deployments.Items { + containers := d.Spec.Template.Spec.Containers + // match deployments with the correct annotation + annotationValue, _ := d.ObjectMeta.Annotations[updateOnChangeAnnotation] + if annotationValue != "" { + values := strings.Split(annotationValue, ",") + matches := false + for _, value := range values { + if value == configMapName { + matches = true + break + } + } + if matches { + updateContainers(containers, annotationValue, configMapVersion) + + // update the deployment + _, err := client.Apps().Deployments(ns).Update(&d) + if err != nil { + return errors.Wrap(err, "update deployment failed") + } + logrus.Infof("Updated Deployment %s", d.Name) + } + } + } + return nil +} + +func updateContainers(containers []v1.Container, annotationValue, configMapVersion string) bool { + // we can have multiple configmaps to update + answer := false + configmaps := strings.Split(annotationValue, ",") + for _, cmNameToUpdate := range configmaps { + configmapEnvar := "STAKATER_" + convertToEnvVarName(cmNameToUpdate) + "_CONFIGMAP" + + for i := range containers { + envs := containers[i].Env + matched := false + for j := range envs { + if envs[j].Name == configmapEnvar { + matched = true + if envs[j].Value != configMapVersion { + logrus.Infof("Updating %s to %s", configmapEnvar, configMapVersion) + envs[j].Value = configMapVersion + answer = true + } + } + } + // if no existing env var exists lets create one + if !matched { + e := v1.EnvVar{ + Name: configmapEnvar, + Value: configMapVersion, + } + containers[i].Env = append(containers[i].Env, e) + answer = true + } + } + } + return answer +} + +// convertToEnvVarName converts the given text into a usable env var +// removing any special chars with '_' +func convertToEnvVarName(text string) string { + var buffer bytes.Buffer + lower := strings.ToUpper(text) + lastCharValid := false + for i := 0; i < len(lower); i++ { + ch := lower[i] + if (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') { + buffer.WriteString(string(ch)) + lastCharValid = true + } else { + if lastCharValid { + buffer.WriteString("_") + } + lastCharValid = false + } + } + return buffer.String() +} + +// lets convert the configmap into a unique token based on the data values +func convertConfigMapToToken(cm *v1.ConfigMap) string { + values := []string{} + for k, v := range cm.Data { + values = append(values, k+"="+v) + } + sort.Strings(values) + text := strings.Join(values, ";") + // we could zip and base64 encode + // but for now we could leave this easy to read so that its easier to diagnose when & why things changed + return text } \ No newline at end of file From ade1e48e3472a5ca4b146871cac6b9636e957126 Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Wed, 11 Jul 2018 16:52:55 +0500 Subject: [PATCH 12/27] Add temporary conidtion for configmaps --- internal/pkg/controller/controller.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/internal/pkg/controller/controller.go b/internal/pkg/controller/controller.go index 9c36c800..1071f4bc 100644 --- a/internal/pkg/controller/controller.go +++ b/internal/pkg/controller/controller.go @@ -161,13 +161,15 @@ func (c *Controller) takeAction(event Event) error { logrus.Infof("Detected changes in object %s", obj) // process events based on its type logrus.Infof("Performing '%s' action for controller of type '%s'", event.eventType, c.resource) - switch event.eventType { - case "create": - ObjectCreated(obj, c.client) - case "update": - ObjectUpdated(obj, c.client) - case "delete": - ObjectDeleted(obj) + if c.resource == "configMaps" { + switch event.eventType { + case "create": + ObjectCreated(obj, c.client) + case "update": + ObjectUpdated(obj, c.client) + case "delete": + ObjectDeleted(obj) + } } } return nil From 848745f40a50079221e3fd1e5e5aefdba9efc9b2 Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Wed, 11 Jul 2018 20:20:48 +0500 Subject: [PATCH 13/27] Implement PR-1 review comments --- README.md | 79 +++++++- .../chart/reloader/templates/deployment.yaml | 4 - glide.yaml | 4 +- internal/pkg/cmd/reloader.go | 12 +- internal/pkg/controller/controller.go | 177 ++---------------- internal/pkg/controller/controller_test.go | 2 +- internal/pkg/upgrader/upgrader.go | 157 ++++++++++++++++ pkg/kube/resourcemapper.go | 13 -- 8 files changed, 257 insertions(+), 191 deletions(-) create mode 100644 internal/pkg/upgrader/upgrader.go diff --git a/README.md b/README.md index d392a19e..8491d172 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,16 @@ -# Reloader +# RELOADER -This controller watches for changes to `ConfigMap` and `Secret` objects and performs rolling upgrades on their associated deployments, deamonsets and statefulsets and updating dynamically. +## WHY NAME RELOADER -This is particularly useful if the `ConfigMap` is used to define environment variables - or your app cannot easily and reliably watch the `ConfigMap` and update itself on the fly. +In english language, Reloader is a thing/tool that can reload certain stuff. So refereig to that meaning relaoder can reload + +## Problem + +We would like to watch if some change happens in `ConfigMap` and `Secret` objects and then perform certain upgrade on relavent `Deployment`, `Deamonset` and `Statefulset` + +## Solution + +Reloader can watch any changes in `ConfigMap` and `Secret` objects and then performs rolling upgrades on their associated `Deployments`, `Deamonsets` and `Statefulsets` and updating these dynamically. **NOTE:** This controller has been inspired from [configmapController](https://github.com/fabric8io/configmapcontroller) @@ -22,4 +30,67 @@ Then, providing `Reloader` is running, whenever you edit the `ConfigMap` called STAKATER_FOO_REVISION=${reloaderRevision} ``` -This then triggers a rolling upgrade of your deployment's pods to use the new configuration. \ No newline at end of file +This then triggers a rolling upgrade of your deployment's pods to use the new configuration. + +Same procedure can be followed to perform rolling upgrade on `Deamonsets` and `Statefulsets` as well. + +## Deploying to Kubernetes + +You can deploy Reloader by running the following kubectl commands: + +```bash +kubectl apply -f rbac.yaml -n +kubectl apply -f deployment.yaml -n +``` + +### Helm Charts + +Or alternatively if you configured `helm` on your cluster, you can deploy Reloader via helm chart located under `deployments/kubernetes/chart/reloader` folder. + +## Help + +**Got a question?** +File a GitHub [issue](https://github.com/stakater/Reloader/issues), or send us an [email](mailto:stakater@gmail.com). + +### Talk to us on Slack + +Join and talk to us on the #tools-imc channel for discussing Reloader + +[![Join Slack](https://stakater.github.io/README/stakater-join-slack-btn.png)](https://stakater-slack.herokuapp.com/) +[![Chat](https://stakater.github.io/README/stakater-chat-btn.png)](https://stakater.slack.com/messages/CAN960CTG/) + +## Contributing + +### Bug Reports & Feature Requests + +Please use the [issue tracker](https://github.com/stakater/Reloader/issues) to report any bugs or file feature requests. + +### Developing + +PRs are welcome. In general, we follow the "fork-and-pull" Git workflow. + + 1. **Fork** the repo on GitHub + 2. **Clone** the project to your own machine + 3. **Commit** changes to your own branch + 4. **Push** your work back up to your fork + 5. Submit a **Pull request** so that we can review your changes + +NOTE: Be sure to merge the latest from "upstream" before making a pull request! + +## Changelog + +View our closed [Pull Requests](https://github.com/stakater/Reloader/pulls?q=is%3Apr+is%3Aclosed). + +## License + +Apache2 © [Stakater](http://stakater.com) + +## About + +`Reloader` is maintained by [Stakater][website]. Like it? Please let us know at + +See [our other projects][community] +or contact us in case of professional services and queries on + + [website]: http://stakater.com/ + [community]: https://github.com/stakater/ diff --git a/deployments/kubernetes/chart/reloader/templates/deployment.yaml b/deployments/kubernetes/chart/reloader/templates/deployment.yaml index ac008af8..0b562a65 100644 --- a/deployments/kubernetes/chart/reloader/templates/deployment.yaml +++ b/deployments/kubernetes/chart/reloader/templates/deployment.yaml @@ -1,8 +1,6 @@ apiVersion: extensions/v1beta1 kind: Deployment metadata: - annotations: - configmap.fabric8.io/update-on-change: {{ template "reloader-name" . }} labels: {{ include "reloader-labels.stakater" . | indent 4 }} {{ include "reloader-labels.chart" . | indent 4 }} @@ -15,8 +13,6 @@ spec: {{ include "reloader-labels.selector" . | indent 6 }} template: metadata: - annotations: - configmap.fabric8.io/update-on-change: {{ template "reloader-name" . }} labels: {{ include "reloader-labels.selector" . | indent 8 }} spec: diff --git a/glide.yaml b/glide.yaml index 71c0a2fd..018d3683 100644 --- a/glide.yaml +++ b/glide.yaml @@ -7,8 +7,8 @@ import: - package: k8s.io/client-go version: 5.0.0 - package: github.com/spf13/cobra - version: ef82de70bb3f60c65fb8eebacbb2d122ef517385 + version: 0.0.3 - package: github.com/spf13/pflag - version: 583c0c0531f06d5278b7d917446061adc344b5cd + version: 1.0.1 - package: github.com/sirupsen/logrus version: ~1.0.3 diff --git a/internal/pkg/cmd/reloader.go b/internal/pkg/cmd/reloader.go index 6fe058b3..8f31d807 100644 --- a/internal/pkg/cmd/reloader.go +++ b/internal/pkg/cmd/reloader.go @@ -1,10 +1,13 @@ package cmd import ( + "os" + "github.com/spf13/cobra" "github.com/stakater/Reloader/internal/pkg/controller" "github.com/stakater/Reloader/pkg/kube" "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/apis/meta/v1" ) func NewReloaderCommand() *cobra.Command { @@ -18,6 +21,11 @@ func NewReloaderCommand() *cobra.Command { func startReloader(cmd *cobra.Command, args []string) { logrus.Info("Starting Reloader") + currentNamespace := os.Getenv("KUBERNETES_NAMESPACE") + if len(currentNamespace) == 0 { + currentNamespace = v1.NamespaceAll + logrus.Infof("Warning: KUBERNETES_NAMESPACE is unset, will detect changes in all namespaces.") + } // create the clientset clientset, err := kube.GetClient() @@ -25,8 +33,8 @@ func startReloader(cmd *cobra.Command, args []string) { logrus.Fatal(err) } - for k, v := range kube.ResourceMap { - c, err := controller.NewController(clientset, k, v) + for k := range kube.ResourceMap { + c, err := controller.NewController(clientset, k, currentNamespace) if err != nil { logrus.Fatalf("%s", err) } diff --git a/internal/pkg/controller/controller.go b/internal/pkg/controller/controller.go index 1071f4bc..c5384e79 100644 --- a/internal/pkg/controller/controller.go +++ b/internal/pkg/controller/controller.go @@ -3,36 +3,24 @@ package controller import ( "time" "fmt" - "strings" - "bytes" - "sort" + "k8s.io/client-go/kubernetes" "k8s.io/apimachinery/pkg/util/runtime" - informerruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/util/wait" errorHandler "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" "github.com/sirupsen/logrus" - "github.com/pkg/errors" - "k8s.io/api/core/v1" - meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - updateOnChangeAnnotation = "reloader.stakater.com/update-on-change" - // AllNamespaces as our controller will be looking for events in all namespaces - AllNamespaces = "temp-reloader" + "github.com/stakater/Reloader/pkg/kube" + "github.com/stakater/Reloader/internal/pkg/upgrader" ) // Event indicate the informerEvent type Event struct { key string eventType string - namespace string - resourceType string } // Controller for checking events @@ -42,28 +30,25 @@ type Controller struct { queue workqueue.RateLimitingInterface informer cache.Controller resource string - - stopCh chan struct{} + namespace string } // NewController for initializing a Controller func NewController( - client kubernetes.Interface, - resource string, objType informerruntime.Object) (*Controller, error) { + client kubernetes.Interface, resource string, namespace string) (*Controller, error) { c := Controller{ client: client, resource: resource, - stopCh: make(chan struct{}), + namespace: namespace, } queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) - listWatcher := cache.NewListWatchFromClient(client.CoreV1().RESTClient(), resource, AllNamespaces, fields.Everything()) - - indexer, informer := cache.NewIndexerInformer(listWatcher, objType, 0, cache.ResourceEventHandlerFuncs { + listWatcher := cache.NewListWatchFromClient(client.CoreV1().RESTClient(), resource, namespace, fields.Everything()) + + indexer, informer := cache.NewIndexerInformer(listWatcher, kube.ResourceMap[resource], 0, cache.ResourceEventHandlerFuncs { AddFunc: c.Add, UpdateFunc: c.Update, - DeleteFunc: c.Delete, }, cache.Indexers{}) c.indexer = indexer c.informer = informer @@ -95,11 +80,6 @@ func (c *Controller) Update(old interface{}, new interface{}) { } } -// Delete function to add a 'delete' event to the queue in case of deleting a pod -func (c *Controller) Delete(obj interface{}) { - //In current scenario, we dont need to do anything when a pod is deleted so it is empty now -} - //Run function for controller which handles the queue func (c *Controller) Run(threadiness int, stopCh chan struct{}) { @@ -161,14 +141,13 @@ func (c *Controller) takeAction(event Event) error { logrus.Infof("Detected changes in object %s", obj) // process events based on its type logrus.Infof("Performing '%s' action for controller of type '%s'", event.eventType, c.resource) + u, _ := upgrader.NewUpgrader(c.client, c.resource) if c.resource == "configMaps" { switch event.eventType { case "create": - ObjectCreated(obj, c.client) + u.ObjectCreated(obj) case "update": - ObjectUpdated(obj, c.client) - case "delete": - ObjectDeleted(obj) + u.ObjectUpdated(obj) } } } @@ -199,136 +178,4 @@ func (c *Controller) handleErr(err error, key interface{}) { // Report to an external entity that, even after several retries, we could not successfully process this key runtime.HandleError(err) logrus.Infof("Dropping the key %q out of the queue: %v", key, err) -} - -// ObjectCreated Do nothing for default handler -func ObjectCreated(obj interface{}, client kubernetes.Interface) { - message := "Configmap: `" + obj.(*v1.ConfigMap).Name + "`has been created in Namespace: `" + obj.(*v1.ConfigMap).Namespace + "`" - logrus.Infof(message) - err := rollingUpgradeDeployments(obj, client) - if err != nil { - logrus.Errorf("failed to update Deployment: %v", err) - } -} - -// ObjectDeleted Do nothing for default handler -func ObjectDeleted(obj interface{}) { - -} - -// ObjectUpdated Do nothing for default handler -func ObjectUpdated(oldObj interface{}, client kubernetes.Interface) { - message := "Configmap: `" + oldObj.(*v1.ConfigMap).Name + "`has been updated in Namespace: `" + oldObj.(*v1.ConfigMap).Namespace + "`" - logrus.Infof(message) - err := rollingUpgradeDeployments(oldObj, client) - if err != nil { - logrus.Errorf("failed to update Deployment: %v", err) - } -} - -// Implementation has been borrowed from fabric8io/configmapcontroller -// Method has been modified a little to use updated liberaries. -func rollingUpgradeDeployments(oldObj interface{}, client kubernetes.Interface) error { - ns := oldObj.(*v1.ConfigMap).Namespace - configMapName := oldObj.(*v1.ConfigMap).Name - configMapVersion := convertConfigMapToToken(oldObj.(*v1.ConfigMap)) - - deployments, err := client.Apps().Deployments(ns).List(meta_v1.ListOptions{}) - if err != nil { - return errors.Wrap(err, "failed to list deployments") - } - for _, d := range deployments.Items { - containers := d.Spec.Template.Spec.Containers - // match deployments with the correct annotation - annotationValue, _ := d.ObjectMeta.Annotations[updateOnChangeAnnotation] - if annotationValue != "" { - values := strings.Split(annotationValue, ",") - matches := false - for _, value := range values { - if value == configMapName { - matches = true - break - } - } - if matches { - updateContainers(containers, annotationValue, configMapVersion) - - // update the deployment - _, err := client.Apps().Deployments(ns).Update(&d) - if err != nil { - return errors.Wrap(err, "update deployment failed") - } - logrus.Infof("Updated Deployment %s", d.Name) - } - } - } - return nil -} - -func updateContainers(containers []v1.Container, annotationValue, configMapVersion string) bool { - // we can have multiple configmaps to update - answer := false - configmaps := strings.Split(annotationValue, ",") - for _, cmNameToUpdate := range configmaps { - configmapEnvar := "STAKATER_" + convertToEnvVarName(cmNameToUpdate) + "_CONFIGMAP" - - for i := range containers { - envs := containers[i].Env - matched := false - for j := range envs { - if envs[j].Name == configmapEnvar { - matched = true - if envs[j].Value != configMapVersion { - logrus.Infof("Updating %s to %s", configmapEnvar, configMapVersion) - envs[j].Value = configMapVersion - answer = true - } - } - } - // if no existing env var exists lets create one - if !matched { - e := v1.EnvVar{ - Name: configmapEnvar, - Value: configMapVersion, - } - containers[i].Env = append(containers[i].Env, e) - answer = true - } - } - } - return answer -} - -// convertToEnvVarName converts the given text into a usable env var -// removing any special chars with '_' -func convertToEnvVarName(text string) string { - var buffer bytes.Buffer - lower := strings.ToUpper(text) - lastCharValid := false - for i := 0; i < len(lower); i++ { - ch := lower[i] - if (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') { - buffer.WriteString(string(ch)) - lastCharValid = true - } else { - if lastCharValid { - buffer.WriteString("_") - } - lastCharValid = false - } - } - return buffer.String() -} - -// lets convert the configmap into a unique token based on the data values -func convertConfigMapToToken(cm *v1.ConfigMap) string { - values := []string{} - for k, v := range cm.Data { - values = append(values, k+"="+v) - } - sort.Strings(values) - text := strings.Join(values, ";") - // we could zip and base64 encode - // but for now we could leave this easy to read so that its easier to diagnose when & why things changed - return text } \ No newline at end of file diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index 4dd54ded..dfec2100 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -10,7 +10,7 @@ import ( ) var ( - client, _ = kube.GetClient() + client, err = kube.GetClient() configmapNamePrefix = "testconfigmap-reloader" letters = []rune("abcdefghijklmnopqrstuvwxyz") ) diff --git a/internal/pkg/upgrader/upgrader.go b/internal/pkg/upgrader/upgrader.go new file mode 100644 index 00000000..9263db31 --- /dev/null +++ b/internal/pkg/upgrader/upgrader.go @@ -0,0 +1,157 @@ +package upgrader + +import ( + "bytes" + "sort" + "strings" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "k8s.io/api/core/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +const ( + updateOnChangeAnnotation = "reloader.stakater.com/update-on-change" +) + +type Upgrader struct{ + client kubernetes.Interface + resourceType string +} + +func NewUpgrader(client kubernetes.Interface, resourceType string) (*Upgrader, error) { + u := Upgrader{ + client: client, + resourceType: resourceType, + } + return &u, nil +} + +// ObjectCreated Detects if the configmap or secret has been created +func (u *Upgrader)ObjectCreated(obj interface{}) { + message := u.resourceType+": `" + obj.(*v1.ConfigMap).Name + "`has been created in Namespace: `" + obj.(*v1.ConfigMap).Namespace + "`" + logrus.Infof(message) + err := rollingUpgradeDeployments(obj, u.client) + if err != nil { + logrus.Errorf("failed to update Deployment: %v", err) + } +} + +// ObjectUpdated Detects if the configmap or secret has been updated +func (u *Upgrader)ObjectUpdated(oldObj interface{}) { + message := u.resourceType+": `" + oldObj.(*v1.ConfigMap).Name + "`has been updated in Namespace: `" + oldObj.(*v1.ConfigMap).Namespace + "`" + logrus.Infof(message) + err := rollingUpgradeDeployments(oldObj, u.client) + if err != nil { + logrus.Errorf("failed to update Deployment: %v", err) + } +} + +// Implementation has been borrowed from fabric8io/configmapcontroller +// Method has been modified a little to use updated liberaries. +func rollingUpgradeDeployments(oldObj interface{}, client kubernetes.Interface) error { + ns := oldObj.(*v1.ConfigMap).Namespace + configMapName := oldObj.(*v1.ConfigMap).Name + configMapVersion := convertConfigMapToToken(oldObj.(*v1.ConfigMap)) + + deployments, err := client.AppsV1().Deployments(ns).List(meta_v1.ListOptions{}) + if err != nil { + return errors.Wrap(err, "failed to list deployments") + } + for _, d := range deployments.Items { + containers := d.Spec.Template.Spec.Containers + // match deployments with the correct annotation + annotationValue := d.ObjectMeta.Annotations[updateOnChangeAnnotation] + if annotationValue != "" { + values := strings.Split(annotationValue, ",") + matches := false + for _, value := range values { + if value == configMapName { + matches = true + break + } + } + if matches { + updateContainers(containers, annotationValue, configMapVersion) + + // update the deployment + _, err := client.AppsV1().Deployments(ns).Update(&d) + if err != nil { + return errors.Wrap(err, "update deployment failed") + } + logrus.Infof("Updated Deployment %s", d.Name) + } + } + } + return nil +} + +func updateContainers(containers []v1.Container, annotationValue, configMapVersion string) bool { + // we can have multiple configmaps to update + answer := false + configmaps := strings.Split(annotationValue, ",") + for _, cmNameToUpdate := range configmaps { + configmapEnvar := "STAKATER_" + convertToEnvVarName(cmNameToUpdate) + "_CONFIGMAP" + + for i := range containers { + envs := containers[i].Env + matched := false + for j := range envs { + if envs[j].Name == configmapEnvar { + matched = true + if envs[j].Value != configMapVersion { + logrus.Infof("Updating %s to %s", configmapEnvar, configMapVersion) + envs[j].Value = configMapVersion + answer = true + } + } + } + // if no existing env var exists lets create one + if !matched { + e := v1.EnvVar{ + Name: configmapEnvar, + Value: configMapVersion, + } + containers[i].Env = append(containers[i].Env, e) + answer = true + } + } + } + return answer +} + +// convertToEnvVarName converts the given text into a usable env var +// removing any special chars with '_' +func convertToEnvVarName(text string) string { + var buffer bytes.Buffer + lower := strings.ToUpper(text) + lastCharValid := false + for i := 0; i < len(lower); i++ { + ch := lower[i] + if (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') { + buffer.WriteString(string(ch)) + lastCharValid = true + } else { + if lastCharValid { + buffer.WriteString("_") + } + lastCharValid = false + } + } + return buffer.String() +} + +// lets convert the configmap into a unique token based on the data values +func convertConfigMapToToken(cm *v1.ConfigMap) string { + values := []string{} + for k, v := range cm.Data { + values = append(values, k+"="+v) + } + sort.Strings(values) + text := strings.Join(values, ";") + // we could zip and base64 encode + // but for now we could leave this easy to read so that its easier to diagnose when & why things changed + return text +} \ No newline at end of file diff --git a/pkg/kube/resourcemapper.go b/pkg/kube/resourcemapper.go index 17e7d121..7ad76286 100644 --- a/pkg/kube/resourcemapper.go +++ b/pkg/kube/resourcemapper.go @@ -5,19 +5,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -const ( - DefaultResource = "default" -) - -// MapToRuntimeObject maps the resource type string to the actual resource -func MapToRuntimeObject(resourceType string) runtime.Object { - rType, ok := ResourceMap[resourceType] - if !ok { - return ResourceMap[DefaultResource] - } - return rType -} - // ResourceMap are resources from where changes are going to be detected var ResourceMap = map[string]runtime.Object{ "configMaps": &v1.ConfigMap{}, From 9376276b15ae704c8663cdea13338bbd3c459def Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Thu, 12 Jul 2018 14:36:08 +0500 Subject: [PATCH 14/27] Implement PR-1 review comments --- .../templates/chart/Chart.yaml.tmpl | 13 +++++ .../templates/chart/values.yaml.tmpl | 14 +++++ internal/pkg/controller/controller_test.go | 54 +++++++++++++------ internal/pkg/upgrader/upgrader.go | 2 + 4 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 deployments/kubernetes/templates/chart/Chart.yaml.tmpl create mode 100644 deployments/kubernetes/templates/chart/values.yaml.tmpl diff --git a/deployments/kubernetes/templates/chart/Chart.yaml.tmpl b/deployments/kubernetes/templates/chart/Chart.yaml.tmpl new file mode 100644 index 00000000..b270c31e --- /dev/null +++ b/deployments/kubernetes/templates/chart/Chart.yaml.tmpl @@ -0,0 +1,13 @@ +# Generated from deployments/kubernetes/templates/chart/Chart.yaml.tmpl + +apiVersion: v1 +name: reloader +description: Reloader chart that runs on kubernetes +version: {{ getenv "VERSION" }} +keywords: + - Reloader + - kubernetes +home: https://github.com/stakater/Reloader +maintainers: +- name: Stakater + email: hello@stakater.com \ No newline at end of file diff --git a/deployments/kubernetes/templates/chart/values.yaml.tmpl b/deployments/kubernetes/templates/chart/values.yaml.tmpl new file mode 100644 index 00000000..8d6a0c57 --- /dev/null +++ b/deployments/kubernetes/templates/chart/values.yaml.tmpl @@ -0,0 +1,14 @@ +# Generated from deployments/kubernetes/templates/chart/values.yaml.tmpl + +kubernetes: + host: https://kubernetes.default + +reloader: + labels: + provider: stakater + group: com.stakater.platform + version: {{ getenv "VERSION" }} + image: + name: {{ getenv "DOCKER_IMAGE" }} + tag: "{{ getenv "VERSION" }}" + pullPolicy: IfNotPresent \ No newline at end of file diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index dfec2100..ea6f8e02 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -3,10 +3,13 @@ package controller import ( "time" "math/rand" + "testing" "github.com/stakater/Reloader/pkg/kube" + "github.com/sirupsen/logrus" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ) var ( @@ -24,9 +27,13 @@ func randSeq(n int) string { return string(b) } -// Creating a Controller for Updating Pod with Default Action without Resources so messages printed -/*func TestControllerForUpdatePodShouldUpdateDefaultAction(t *testing.T) { - controller, err := NewController(client, "configMaps", &v1.ConfigMap{}) +// Creating a Controller to do a rolling upgrade upon updating the configmap or secret +func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { + namespace := "test-reloader" + createNamespace(t, namespace) + defer deleteNamespace(t, namespace) + + controller, err := NewController(client, "configMaps", namespace) if err != nil { logrus.Infof("Unable to create NewController error = %v", err) return @@ -35,7 +42,7 @@ func randSeq(n int) string { defer close(stop) go controller.Run(1, stop) time.Sleep(10 * time.Second) - namespace := "test" + configmapName := configmapNamePrefix + "-withoutresources-update-" + randSeq(5) configmapClient := client.CoreV1().ConfigMaps(namespace) configmap := initConfigmap(namespace, configmapName) @@ -47,28 +54,25 @@ func randSeq(n int) string { time.Sleep(10 * time.Second) logrus.Infof("Updating Configmap %q.\n", configmap.GetObjectMeta().GetName()) - retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - configmap, err = configmapClient.Get(configmapName, metav1.GetOptions{}) - if err != nil { + configmap, err = configmapClient.Get(configmapName, metav1.GetOptions{}) + if err != nil { - } - configmap = updateConfigmap(namespace, configmapName) - _, updateErr := configmapClient.Update(configmap) - return updateErr - }) + } + configmap = updateConfigmap(namespace, configmapName) + _, updateErr := configmapClient.Update(configmap) // TODO: Add functionality to verify reloader functionality here - if retryErr != nil { + if updateErr != nil { controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) - panic(retryErr) + panic(updateErr) } time.Sleep(10 * time.Second) - logrus.Infof("Deleting Pod %q.\n", configmap.GetObjectMeta().GetName()) + logrus.Infof("Deleting Configmap %q.\n", configmap.GetObjectMeta().GetName()) controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) time.Sleep(15 * time.Second) -}*/ +} func initConfigmap(namespace string, configmapName string) *v1.ConfigMap { return &v1.ConfigMap{ @@ -77,6 +81,21 @@ func initConfigmap(namespace string, configmapName string) *v1.ConfigMap { Namespace: namespace, Labels: map[string]string{"firstLabel": "temp"}, }, + Data: map[string]string{"test.url":"www.google.com"}, + } +} + +func createNamespace(t *testing.T, namespace string) { + _, err := client.CoreV1().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}) + if err != nil { + t.Error("Failed to create namespace for testing", err) + } +} + +func deleteNamespace(t *testing.T, namespace string) { + err := client.CoreV1().Namespaces().Delete(namespace, &metav1.DeleteOptions{}) + if err != nil { + t.Error("Failed to delete namespace that was created for testing", err) } } @@ -85,7 +104,8 @@ func updateConfigmap(namespace string, configmapName string) *v1.ConfigMap { ObjectMeta: metav1.ObjectMeta{ Name: configmapName, Namespace: namespace, - Labels: map[string]string{"firstLabel": "updated"}, + Labels: map[string]string{"firstLabel": "temp"}, }, + Data: map[string]string{"test.url":"www.stakater.com"}, } } diff --git a/internal/pkg/upgrader/upgrader.go b/internal/pkg/upgrader/upgrader.go index 9263db31..7181cf40 100644 --- a/internal/pkg/upgrader/upgrader.go +++ b/internal/pkg/upgrader/upgrader.go @@ -16,11 +16,13 @@ const ( updateOnChangeAnnotation = "reloader.stakater.com/update-on-change" ) +// Upgrader will upgrade the relevent deployment, deamonset and deamonset. type Upgrader struct{ client kubernetes.Interface resourceType string } +// NewUpgrader Initializes the Upgrader func NewUpgrader(client kubernetes.Interface, resourceType string) (*Upgrader, error) { u := Upgrader{ client: client, From bf34e3a6919852b2f7e4f472d71c64793129b3c6 Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Thu, 12 Jul 2018 15:04:51 +0500 Subject: [PATCH 15/27] Implement PR-1 code review comments --- internal/pkg/cmd/reloader.go | 3 +- internal/pkg/controller/controller.go | 51 +++++++++++----------- internal/pkg/controller/controller_test.go | 28 ++++++------ internal/pkg/upgrader/upgrader.go | 16 +++---- 4 files changed, 50 insertions(+), 48 deletions(-) diff --git a/internal/pkg/cmd/reloader.go b/internal/pkg/cmd/reloader.go index 8f31d807..2e00421d 100644 --- a/internal/pkg/cmd/reloader.go +++ b/internal/pkg/cmd/reloader.go @@ -3,13 +3,14 @@ package cmd import ( "os" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/stakater/Reloader/internal/pkg/controller" "github.com/stakater/Reloader/pkg/kube" - "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// NewReloaderCommand starts the reloader controller func NewReloaderCommand() *cobra.Command { cmds := &cobra.Command{ Use: "reloader", diff --git a/internal/pkg/controller/controller.go b/internal/pkg/controller/controller.go index c5384e79..1d9b32b4 100644 --- a/internal/pkg/controller/controller.go +++ b/internal/pkg/controller/controller.go @@ -1,36 +1,35 @@ package controller import ( - "time" "fmt" - - - "k8s.io/client-go/kubernetes" - "k8s.io/apimachinery/pkg/util/runtime" + "time" + + "github.com/sirupsen/logrus" + "github.com/stakater/Reloader/internal/pkg/upgrader" + "github.com/stakater/Reloader/pkg/kube" "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/util/runtime" errorHandler "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" - "github.com/sirupsen/logrus" - "github.com/stakater/Reloader/pkg/kube" - "github.com/stakater/Reloader/internal/pkg/upgrader" ) // Event indicate the informerEvent type Event struct { - key string - eventType string + key string + eventType string } // Controller for checking events type Controller struct { - client kubernetes.Interface - indexer cache.Indexer - queue workqueue.RateLimitingInterface - informer cache.Controller - resource string - namespace string + client kubernetes.Interface + indexer cache.Indexer + queue workqueue.RateLimitingInterface + informer cache.Controller + resource string + namespace string } // NewController for initializing a Controller @@ -38,15 +37,15 @@ func NewController( client kubernetes.Interface, resource string, namespace string) (*Controller, error) { c := Controller{ - client: client, - resource: resource, + client: client, + resource: resource, namespace: namespace, } queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) listWatcher := cache.NewListWatchFromClient(client.CoreV1().RESTClient(), resource, namespace, fields.Everything()) - - indexer, informer := cache.NewIndexerInformer(listWatcher, kube.ResourceMap[resource], 0, cache.ResourceEventHandlerFuncs { + + indexer, informer := cache.NewIndexerInformer(listWatcher, kube.ResourceMap[resource], 0, cache.ResourceEventHandlerFuncs{ AddFunc: c.Add, UpdateFunc: c.Update, }, cache.Indexers{}) @@ -144,10 +143,10 @@ func (c *Controller) takeAction(event Event) error { u, _ := upgrader.NewUpgrader(c.client, c.resource) if c.resource == "configMaps" { switch event.eventType { - case "create": - u.ObjectCreated(obj) - case "update": - u.ObjectUpdated(obj) + case "create": + u.ObjectCreated(obj) + case "update": + u.ObjectUpdated(obj) } } } @@ -178,4 +177,4 @@ func (c *Controller) handleErr(err error, key interface{}) { // Report to an external entity that, even after several retries, we could not successfully process this key runtime.HandleError(err) logrus.Infof("Dropping the key %q out of the queue: %v", key, err) -} \ No newline at end of file +} diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index ea6f8e02..808be7cc 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -1,21 +1,20 @@ package controller import ( - "time" "math/rand" "testing" + "time" - "github.com/stakater/Reloader/pkg/kube" "github.com/sirupsen/logrus" + "github.com/stakater/Reloader/pkg/kube" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ) var ( - client, err = kube.GetClient() + client, err = kube.GetClient() configmapNamePrefix = "testconfigmap-reloader" - letters = []rune("abcdefghijklmnopqrstuvwxyz") + letters = []rune("abcdefghijklmnopqrstuvwxyz") ) func randSeq(n int) string { @@ -42,8 +41,8 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { defer close(stop) go controller.Run(1, stop) time.Sleep(10 * time.Second) - - configmapName := configmapNamePrefix + "-withoutresources-update-" + randSeq(5) + + configmapName := configmapNamePrefix + "-update-" + randSeq(5) configmapClient := client.CoreV1().ConfigMaps(namespace) configmap := initConfigmap(namespace, configmapName) configmap, err = configmapClient.Create(configmap) @@ -61,9 +60,8 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { configmap = updateConfigmap(namespace, configmapName) _, updateErr := configmapClient.Update(configmap) - // TODO: Add functionality to verify reloader functionality here - + if updateErr != nil { controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) panic(updateErr) @@ -79,9 +77,9 @@ func initConfigmap(namespace string, configmapName string) *v1.ConfigMap { ObjectMeta: metav1.ObjectMeta{ Name: configmapName, Namespace: namespace, - Labels: map[string]string{"firstLabel": "temp"}, + Labels: map[string]string{"firstLabel": "temp"}, }, - Data: map[string]string{"test.url":"www.google.com"}, + Data: map[string]string{"test.url": "www.google.com"}, } } @@ -89,6 +87,8 @@ func createNamespace(t *testing.T, namespace string) { _, err := client.CoreV1().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}) if err != nil { t.Error("Failed to create namespace for testing", err) + } else { + logrus.Infof("Creating namespace for testing = %s", namespace) } } @@ -96,6 +96,8 @@ func deleteNamespace(t *testing.T, namespace string) { err := client.CoreV1().Namespaces().Delete(namespace, &metav1.DeleteOptions{}) if err != nil { t.Error("Failed to delete namespace that was created for testing", err) + } else { + logrus.Infof("Deleting namespace for testing = %s", namespace) } } @@ -104,8 +106,8 @@ func updateConfigmap(namespace string, configmapName string) *v1.ConfigMap { ObjectMeta: metav1.ObjectMeta{ Name: configmapName, Namespace: namespace, - Labels: map[string]string{"firstLabel": "temp"}, + Labels: map[string]string{"firstLabel": "temp"}, }, - Data: map[string]string{"test.url":"www.stakater.com"}, + Data: map[string]string{"test.url": "www.stakater.com"}, } } diff --git a/internal/pkg/upgrader/upgrader.go b/internal/pkg/upgrader/upgrader.go index 7181cf40..2c272315 100644 --- a/internal/pkg/upgrader/upgrader.go +++ b/internal/pkg/upgrader/upgrader.go @@ -17,7 +17,7 @@ const ( ) // Upgrader will upgrade the relevent deployment, deamonset and deamonset. -type Upgrader struct{ +type Upgrader struct { client kubernetes.Interface resourceType string } @@ -32,8 +32,8 @@ func NewUpgrader(client kubernetes.Interface, resourceType string) (*Upgrader, e } // ObjectCreated Detects if the configmap or secret has been created -func (u *Upgrader)ObjectCreated(obj interface{}) { - message := u.resourceType+": `" + obj.(*v1.ConfigMap).Name + "`has been created in Namespace: `" + obj.(*v1.ConfigMap).Namespace + "`" +func (u *Upgrader) ObjectCreated(obj interface{}) { + message := u.resourceType + ": `" + obj.(*v1.ConfigMap).Name + "`has been created in Namespace: `" + obj.(*v1.ConfigMap).Namespace + "`" logrus.Infof(message) err := rollingUpgradeDeployments(obj, u.client) if err != nil { @@ -42,8 +42,8 @@ func (u *Upgrader)ObjectCreated(obj interface{}) { } // ObjectUpdated Detects if the configmap or secret has been updated -func (u *Upgrader)ObjectUpdated(oldObj interface{}) { - message := u.resourceType+": `" + oldObj.(*v1.ConfigMap).Name + "`has been updated in Namespace: `" + oldObj.(*v1.ConfigMap).Namespace + "`" +func (u *Upgrader) ObjectUpdated(oldObj interface{}) { + message := u.resourceType + ": `" + oldObj.(*v1.ConfigMap).Name + "`has been updated in Namespace: `" + oldObj.(*v1.ConfigMap).Namespace + "`" logrus.Infof(message) err := rollingUpgradeDeployments(oldObj, u.client) if err != nil { @@ -58,7 +58,7 @@ func rollingUpgradeDeployments(oldObj interface{}, client kubernetes.Interface) configMapName := oldObj.(*v1.ConfigMap).Name configMapVersion := convertConfigMapToToken(oldObj.(*v1.ConfigMap)) - deployments, err := client.AppsV1().Deployments(ns).List(meta_v1.ListOptions{}) + deployments, err := client.Apps().Deployments(ns).List(meta_v1.ListOptions{}) if err != nil { return errors.Wrap(err, "failed to list deployments") } @@ -79,7 +79,7 @@ func rollingUpgradeDeployments(oldObj interface{}, client kubernetes.Interface) updateContainers(containers, annotationValue, configMapVersion) // update the deployment - _, err := client.AppsV1().Deployments(ns).Update(&d) + _, err := client.Apps().Deployments(ns).Update(&d) if err != nil { return errors.Wrap(err, "update deployment failed") } @@ -156,4 +156,4 @@ func convertConfigMapToToken(cm *v1.ConfigMap) string { // we could zip and base64 encode // but for now we could leave this easy to read so that its easier to diagnose when & why things changed return text -} \ No newline at end of file +} From f73318f25edeee6b2aded34fdcedded25d801a1a Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Thu, 12 Jul 2018 16:16:46 +0500 Subject: [PATCH 16/27] Implement PR-1 review comments --- .../chart/reloader/templates/rbac.yaml | 8 ++- internal/pkg/app/app.go | 2 +- internal/pkg/controller/controller_test.go | 68 +++++++++++++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/deployments/kubernetes/chart/reloader/templates/rbac.yaml b/deployments/kubernetes/chart/reloader/templates/rbac.yaml index 87b3fde0..678725d0 100644 --- a/deployments/kubernetes/chart/reloader/templates/rbac.yaml +++ b/deployments/kubernetes/chart/reloader/templates/rbac.yaml @@ -7,12 +7,13 @@ metadata: name: {{ template "reloader-name" . }} --- apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: ClusterRole +kind: Role metadata: labels: {{ include "reloader-labels.stakater" . | indent 4 }} {{ include "reloader-labels.chart" . | indent 4 }} name: {{ template "reloader-name" . }}-role + namespace: {{ .Release.Namespace }} rules: - apiGroups: - "" @@ -25,15 +26,16 @@ rules: - watch --- apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: ClusterRoleBinding +kind: RoleBinding metadata: labels: {{ include "reloader-labels.stakater" . | indent 4 }} {{ include "reloader-labels.chart" . | indent 4 }} name: {{ template "reloader-name" . }}-role-binding + namespace: {{ .Release.Namespace }} roleRef: apiGroup: rbac.authorization.k8s.io - kind: ClusterRole + kind: Role name: {{ template "reloader-name" . }}-role subjects: - kind: ServiceAccount diff --git a/internal/pkg/app/app.go b/internal/pkg/app/app.go index 319501a7..8d09188f 100644 --- a/internal/pkg/app/app.go +++ b/internal/pkg/app/app.go @@ -6,4 +6,4 @@ import "github.com/stakater/Reloader/internal/pkg/cmd" func Run() error { cmd := cmd.NewReloaderCommand() return cmd.Execute() -} \ No newline at end of file +} diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index 808be7cc..2ebbcfeb 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -14,6 +14,7 @@ import ( var ( client, err = kube.GetClient() configmapNamePrefix = "testconfigmap-reloader" + secretNamePrefix = "testsecret-reloader" letters = []rune("abcdefghijklmnopqrstuvwxyz") ) @@ -72,6 +73,51 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { time.Sleep(15 * time.Second) } +func TestControllerForUpdatingSecretShouldUpdateDeployment(t *testing.T) { + namespace := "test-reloader-secrets" + createNamespace(t, namespace) + defer deleteNamespace(t, namespace) + + controller, err := NewController(client, "secrets", namespace) + if err != nil { + logrus.Infof("Unable to create NewController error = %v", err) + return + } + stop := make(chan struct{}) + defer close(stop) + go controller.Run(1, stop) + time.Sleep(10 * time.Second) + + secretName := secretNamePrefix + "-update-" + randSeq(5) + secretClient := client.CoreV1().Secrets(namespace) + secret := initSecret(namespace, secretName) + secret, err = secretClient.Create(secret) + if err != nil { + panic(err) + } + logrus.Infof("Created Secret %q.\n", secret.GetObjectMeta().GetName()) + time.Sleep(10 * time.Second) + + logrus.Infof("Updating Secret %q.\n", secret.GetObjectMeta().GetName()) + secret, err = secretClient.Get(secretName, metav1.GetOptions{}) + if err != nil { + + } + secret = updateSecret(namespace, secretName) + _, updateErr := secretClient.Update(secret) + + // TODO: Add functionality to verify reloader functionality here + + if updateErr != nil { + controller.client.CoreV1().Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{}) + panic(updateErr) + } + time.Sleep(10 * time.Second) + logrus.Infof("Deleting Secret %q.\n", secret.GetObjectMeta().GetName()) + controller.client.CoreV1().Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{}) + time.Sleep(15 * time.Second) +} + func initConfigmap(namespace string, configmapName string) *v1.ConfigMap { return &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ @@ -83,6 +129,17 @@ func initConfigmap(namespace string, configmapName string) *v1.ConfigMap { } } +func initSecret(namespace string, secretName string) *v1.Secret { + return &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespace, + Labels: map[string]string{"firstLabel": "temp"}, + }, + Data: map[string][]byte{"test.url": []byte("dGVzdFNlY3JldEVuY29kaW5nRm9yUmVsb2FkZXI=")}, + } +} + func createNamespace(t *testing.T, namespace string) { _, err := client.CoreV1().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}) if err != nil { @@ -111,3 +168,14 @@ func updateConfigmap(namespace string, configmapName string) *v1.ConfigMap { Data: map[string]string{"test.url": "www.stakater.com"}, } } + +func updateSecret(namespace string, secretName string) *v1.Secret { + return &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespace, + Labels: map[string]string{"firstLabel": "temp"}, + }, + Data: map[string][]byte{"test.url": []byte("dGVzdFVwZGF0ZWRTZWNyZXRFbmNvZGluZ0ZvclJlbG9hZGVy")}, + } +} From c35bb74896254d83d46a1ff485acb1ed68494309 Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Thu, 12 Jul 2018 16:59:42 +0500 Subject: [PATCH 17/27] Implement PR-1 golangci review comments --- internal/pkg/controller/controller_test.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index 2ebbcfeb..e7bffbb6 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -56,7 +56,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { logrus.Infof("Updating Configmap %q.\n", configmap.GetObjectMeta().GetName()) configmap, err = configmapClient.Get(configmapName, metav1.GetOptions{}) if err != nil { - + logrus.Infof("Error while getting configmap %v", err) } configmap = updateConfigmap(namespace, configmapName) _, updateErr := configmapClient.Update(configmap) @@ -64,12 +64,16 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { // TODO: Add functionality to verify reloader functionality here if updateErr != nil { - controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) + error := controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) + logrus.Infof("Error while deleting the configmap %v", error) panic(updateErr) } time.Sleep(10 * time.Second) logrus.Infof("Deleting Configmap %q.\n", configmap.GetObjectMeta().GetName()) - controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) + error := controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) + if error != nil { + logrus.Infof("Error while deleting the configmap %v", error) + } time.Sleep(15 * time.Second) } @@ -101,7 +105,7 @@ func TestControllerForUpdatingSecretShouldUpdateDeployment(t *testing.T) { logrus.Infof("Updating Secret %q.\n", secret.GetObjectMeta().GetName()) secret, err = secretClient.Get(secretName, metav1.GetOptions{}) if err != nil { - + logrus.Infof("Error while getting secret %v", err) } secret = updateSecret(namespace, secretName) _, updateErr := secretClient.Update(secret) @@ -109,12 +113,16 @@ func TestControllerForUpdatingSecretShouldUpdateDeployment(t *testing.T) { // TODO: Add functionality to verify reloader functionality here if updateErr != nil { - controller.client.CoreV1().Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{}) + error := controller.client.CoreV1().Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{}) + logrus.Infof("Error while deleting the secret %v", error) panic(updateErr) } time.Sleep(10 * time.Second) logrus.Infof("Deleting Secret %q.\n", secret.GetObjectMeta().GetName()) - controller.client.CoreV1().Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{}) + error := controller.client.CoreV1().Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{}) + if error != nil { + logrus.Infof("Error while deleting the secret %v", error) + } time.Sleep(15 * time.Second) } From 82bd2b55c5af8290dace48a241100ade1e06fbfd Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Fri, 13 Jul 2018 14:55:02 +0500 Subject: [PATCH 18/27] Implement PR-1 review comments --- .version | 2 +- README.md | 2 +- .../kubernetes/chart/reloader/Chart.yaml | 2 +- .../kubernetes/chart/reloader/values.yaml | 4 +- internal/pkg/controller/controller_test.go | 55 ++++++++++++++++++- internal/pkg/upgrader/upgrader.go | 4 +- main.go | 2 +- pkg/kube/resourcemapper.go | 2 +- 8 files changed, 62 insertions(+), 11 deletions(-) diff --git a/.version b/.version index afaf360d..8a9ecc2e 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -1.0.0 \ No newline at end of file +0.0.1 \ No newline at end of file diff --git a/README.md b/README.md index 8491d172..63e2d63a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ We would like to watch if some change happens in `ConfigMap` and `Secret` object ## Solution -Reloader can watch any changes in `ConfigMap` and `Secret` objects and then performs rolling upgrades on their associated `Deployments`, `Deamonsets` and `Statefulsets` and updating these dynamically. +Reloader can watch any changes in `ConfigMap` and `Secret` objects and update or recreate Pods for their associated `Deployments`, `Deamonsets` and `Statefulsets`. In this way Pods can get the latest changes in `ConfigMap` or `Secret` objects. **NOTE:** This controller has been inspired from [configmapController](https://github.com/fabric8io/configmapcontroller) diff --git a/deployments/kubernetes/chart/reloader/Chart.yaml b/deployments/kubernetes/chart/reloader/Chart.yaml index 5677e79c..deb48769 100644 --- a/deployments/kubernetes/chart/reloader/Chart.yaml +++ b/deployments/kubernetes/chart/reloader/Chart.yaml @@ -3,7 +3,7 @@ apiVersion: v1 name: reloader description: Reloader chart that runs on kubernetes -version: 1.0.0 +version: 0.0.1 keywords: - Reloader - kubernetes diff --git a/deployments/kubernetes/chart/reloader/values.yaml b/deployments/kubernetes/chart/reloader/values.yaml index 0396ebe9..a0ab5ab1 100644 --- a/deployments/kubernetes/chart/reloader/values.yaml +++ b/deployments/kubernetes/chart/reloader/values.yaml @@ -7,8 +7,8 @@ reloader: labels: provider: stakater group: com.stakater.platform - version: 1.0.0 + version: 0.0.1 image: name: stakater/reloader - tag: "1.0.0" + tag: "0.0.1" pullPolicy: IfNotPresent \ No newline at end of file diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index e7bffbb6..ba676ddd 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -8,13 +8,14 @@ import ( "github.com/sirupsen/logrus" "github.com/stakater/Reloader/pkg/kube" "k8s.io/api/core/v1" + "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var ( - client, err = kube.GetClient() + client, _ = kube.GetClient() configmapNamePrefix = "testconfigmap-reloader" - secretNamePrefix = "testsecret-reloader" + secretNamePrefix = "testsecret-reloader" letters = []rune("abcdefghijklmnopqrstuvwxyz") ) @@ -48,10 +49,12 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { configmap := initConfigmap(namespace, configmapName) configmap, err = configmapClient.Create(configmap) if err != nil { + logrus.Infof("Error detected %s.\n", err) panic(err) } logrus.Infof("Created Configmap %q.\n", configmap.GetObjectMeta().GetName()) time.Sleep(10 * time.Second) + createDeployement(configmapName, namespace) logrus.Infof("Updating Configmap %q.\n", configmap.GetObjectMeta().GetName()) configmap, err = configmapClient.Get(configmapName, metav1.GetOptions{}) @@ -77,6 +80,17 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { time.Sleep(15 * time.Second) } +func createDeployement(deploymentName string, namespace string) { + deploymentClient := client.Extensions().Deployments(namespace) + deployment := initDeployment(namespace, deploymentName) + deployment, error := deploymentClient.Create(deployment) + if error != nil { + panic(error) + } + //time.Sleep(10 * time.Second) + logrus.Infof("Created Deployment %q.\n", deployment.GetObjectMeta().GetName()) +} + func TestControllerForUpdatingSecretShouldUpdateDeployment(t *testing.T) { namespace := "test-reloader-secrets" createNamespace(t, namespace) @@ -137,6 +151,43 @@ func initConfigmap(namespace string, configmapName string) *v1.ConfigMap { } } +func initDeployment(namespace string, deploymentName string) *v1beta1.Deployment { + replicaset := int32(1) + return &v1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: deploymentName, + Namespace: namespace, + Labels: map[string]string{"firstLabel": "temp"}, + Annotations: map[string]string{"reloader.stakater.com/update-on-change": deploymentName}, + }, + Spec: v1beta1.DeploymentSpec{ + Replicas: &replicaset, + Strategy: v1beta1.DeploymentStrategy{ + Type: v1beta1.RollingUpdateDeploymentStrategyType, + }, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"secondLabel": "temp"}, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + v1.Container{ + Image: "tutum/hello-world", + Name: deploymentName, + Env: []v1.EnvVar{ + v1.EnvVar{ + Name: "BUCKET_NAME", + Value: "test", + }, + }, + }, + }, + }, + }, + }, + } +} + func initSecret(namespace string, secretName string) *v1.Secret { return &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ diff --git a/internal/pkg/upgrader/upgrader.go b/internal/pkg/upgrader/upgrader.go index 2c272315..37d00f8f 100644 --- a/internal/pkg/upgrader/upgrader.go +++ b/internal/pkg/upgrader/upgrader.go @@ -58,7 +58,7 @@ func rollingUpgradeDeployments(oldObj interface{}, client kubernetes.Interface) configMapName := oldObj.(*v1.ConfigMap).Name configMapVersion := convertConfigMapToToken(oldObj.(*v1.ConfigMap)) - deployments, err := client.Apps().Deployments(ns).List(meta_v1.ListOptions{}) + deployments, err := client.ExtensionsV1beta1().Deployments(ns).List(meta_v1.ListOptions{}) if err != nil { return errors.Wrap(err, "failed to list deployments") } @@ -79,7 +79,7 @@ func rollingUpgradeDeployments(oldObj interface{}, client kubernetes.Interface) updateContainers(containers, annotationValue, configMapVersion) // update the deployment - _, err := client.Apps().Deployments(ns).Update(&d) + _, err := client.ExtensionsV1beta1().Deployments(ns).Update(&d) if err != nil { return errors.Wrap(err, "update deployment failed") } diff --git a/main.go b/main.go index 478976ba..1c429710 100644 --- a/main.go +++ b/main.go @@ -11,4 +11,4 @@ func main() { os.Exit(1) } os.Exit(0) -} \ No newline at end of file +} diff --git a/pkg/kube/resourcemapper.go b/pkg/kube/resourcemapper.go index 7ad76286..bf62cc01 100644 --- a/pkg/kube/resourcemapper.go +++ b/pkg/kube/resourcemapper.go @@ -8,5 +8,5 @@ import ( // ResourceMap are resources from where changes are going to be detected var ResourceMap = map[string]runtime.Object{ "configMaps": &v1.ConfigMap{}, - "secrets": &v1.Secret{}, + "secrets": &v1.Secret{}, } From f7ff8d7b359192cc2ebdfccbd8539d47279cb47b Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Fri, 13 Jul 2018 16:16:57 +0500 Subject: [PATCH 19/27] Remove upgrader --- internal/pkg/controller/controller.go | 66 ++++----- internal/pkg/controller/controller_test.go | 7 +- internal/pkg/upgrader/upgrader.go | 159 --------------------- 3 files changed, 29 insertions(+), 203 deletions(-) delete mode 100644 internal/pkg/upgrader/upgrader.go diff --git a/internal/pkg/controller/controller.go b/internal/pkg/controller/controller.go index 1d9b32b4..84cac772 100644 --- a/internal/pkg/controller/controller.go +++ b/internal/pkg/controller/controller.go @@ -5,7 +5,6 @@ import ( "time" "github.com/sirupsen/logrus" - "github.com/stakater/Reloader/internal/pkg/upgrader" "github.com/stakater/Reloader/pkg/kube" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/util/runtime" @@ -15,11 +14,10 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" ) - -// Event indicate the informerEvent -type Event struct { - key string - eventType string +// ResourceUpdated contains new or updated objects +type ResourceUpdated struct { + newObj interface{} + oldObj interface{} } // Controller for checking events @@ -57,26 +55,17 @@ func NewController( // Add function to add a 'create' event to the queue in case of creating a pod func (c *Controller) Add(obj interface{}) { - key, err := cache.MetaNamespaceKeyFunc(obj) - var event Event - - if err == nil { - event.key = key - event.eventType = "create" - c.queue.Add(event) - } + c.queue.Add(ResourceUpdated{ + newObj: obj, + }) } // Update function to add an 'update' event to the queue in case of updating a pod func (c *Controller) Update(old interface{}, new interface{}) { - key, err := cache.MetaNamespaceKeyFunc(new) - var event Event - - if err == nil { - event.key = key - event.eventType = "update" - c.queue.Add(event) - } + c.queue.Add(ResourceUpdated{ + newObj: new, + oldObj: old, + }) } //Run function for controller which handles the queue @@ -111,43 +100,36 @@ func (c *Controller) runWorker() { func (c *Controller) processNextItem() bool { // Wait until there is a new item in the working queue - event, quit := c.queue.Get() + resourceUpdated, quit := c.queue.Get() if quit { return false } // Tell the queue that we are done with processing this key. This unblocks the key for other workers // This allows safe parallel processing because two events with the same key are never processed in // parallel. - defer c.queue.Done(event) + defer c.queue.Done(resourceUpdated) // Invoke the method containing the business logic - err := c.takeAction(event.(Event)) + err := c.takeAction(resourceUpdated.(ResourceUpdated)) // Handle the error if something went wrong during the execution of the business logic - c.handleErr(err, event) + c.handleErr(err, resourceUpdated) return true } // main business logic that acts bassed on the event or key -func (c *Controller) takeAction(event Event) error { +func (c *Controller) takeAction(resourceUpdated ResourceUpdated) error { - obj, _, err := c.indexer.GetByKey(event.key) - if err != nil { - logrus.Infof("Fetching object with key %s from store failed with %v", event.key, err) - } - if obj == nil { + newObj := resourceUpdated.newObj + oldObj := resourceUpdated.oldObj + if newObj == nil { logrus.Infof("Error in Action") } else { - logrus.Infof("Detected changes in object %s", obj) + logrus.Infof("Detected changes in object %s", newObj) // process events based on its type - logrus.Infof("Performing '%s' action for controller of type '%s'", event.eventType, c.resource) - u, _ := upgrader.NewUpgrader(c.client, c.resource) - if c.resource == "configMaps" { - switch event.eventType { - case "create": - u.ObjectCreated(obj) - case "update": - u.ObjectUpdated(obj) - } + if(oldObj == nil){ + logrus.Infof("Performing 'Added' action for controller of type '%s'", c.resource) + } else { + logrus.Infof("Performing 'Updated' action for controller of type '%s'", c.resource) } } return nil diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index ba676ddd..047eb395 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -54,7 +54,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { } logrus.Infof("Created Configmap %q.\n", configmap.GetObjectMeta().GetName()) time.Sleep(10 * time.Second) - createDeployement(configmapName, namespace) + deployment := createDeployement(configmapName, namespace) logrus.Infof("Updating Configmap %q.\n", configmap.GetObjectMeta().GetName()) configmap, err = configmapClient.Get(configmapName, metav1.GetOptions{}) @@ -72,6 +72,8 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { panic(updateErr) } time.Sleep(10 * time.Second) + logrus.Infof("Deleting Deployment %q.\n", deployment.GetObjectMeta().GetName()) + controller.client.Extensions().Deployments(namespace).Delete(configmapName, &metav1.DeleteOptions{}); logrus.Infof("Deleting Configmap %q.\n", configmap.GetObjectMeta().GetName()) error := controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) if error != nil { @@ -80,7 +82,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { time.Sleep(15 * time.Second) } -func createDeployement(deploymentName string, namespace string) { +func createDeployement(deploymentName string, namespace string) *v1beta1.Deployment { deploymentClient := client.Extensions().Deployments(namespace) deployment := initDeployment(namespace, deploymentName) deployment, error := deploymentClient.Create(deployment) @@ -89,6 +91,7 @@ func createDeployement(deploymentName string, namespace string) { } //time.Sleep(10 * time.Second) logrus.Infof("Created Deployment %q.\n", deployment.GetObjectMeta().GetName()) + return deployment } func TestControllerForUpdatingSecretShouldUpdateDeployment(t *testing.T) { diff --git a/internal/pkg/upgrader/upgrader.go b/internal/pkg/upgrader/upgrader.go deleted file mode 100644 index 37d00f8f..00000000 --- a/internal/pkg/upgrader/upgrader.go +++ /dev/null @@ -1,159 +0,0 @@ -package upgrader - -import ( - "bytes" - "sort" - "strings" - - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "k8s.io/api/core/v1" - meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" -) - -const ( - updateOnChangeAnnotation = "reloader.stakater.com/update-on-change" -) - -// Upgrader will upgrade the relevent deployment, deamonset and deamonset. -type Upgrader struct { - client kubernetes.Interface - resourceType string -} - -// NewUpgrader Initializes the Upgrader -func NewUpgrader(client kubernetes.Interface, resourceType string) (*Upgrader, error) { - u := Upgrader{ - client: client, - resourceType: resourceType, - } - return &u, nil -} - -// ObjectCreated Detects if the configmap or secret has been created -func (u *Upgrader) ObjectCreated(obj interface{}) { - message := u.resourceType + ": `" + obj.(*v1.ConfigMap).Name + "`has been created in Namespace: `" + obj.(*v1.ConfigMap).Namespace + "`" - logrus.Infof(message) - err := rollingUpgradeDeployments(obj, u.client) - if err != nil { - logrus.Errorf("failed to update Deployment: %v", err) - } -} - -// ObjectUpdated Detects if the configmap or secret has been updated -func (u *Upgrader) ObjectUpdated(oldObj interface{}) { - message := u.resourceType + ": `" + oldObj.(*v1.ConfigMap).Name + "`has been updated in Namespace: `" + oldObj.(*v1.ConfigMap).Namespace + "`" - logrus.Infof(message) - err := rollingUpgradeDeployments(oldObj, u.client) - if err != nil { - logrus.Errorf("failed to update Deployment: %v", err) - } -} - -// Implementation has been borrowed from fabric8io/configmapcontroller -// Method has been modified a little to use updated liberaries. -func rollingUpgradeDeployments(oldObj interface{}, client kubernetes.Interface) error { - ns := oldObj.(*v1.ConfigMap).Namespace - configMapName := oldObj.(*v1.ConfigMap).Name - configMapVersion := convertConfigMapToToken(oldObj.(*v1.ConfigMap)) - - deployments, err := client.ExtensionsV1beta1().Deployments(ns).List(meta_v1.ListOptions{}) - if err != nil { - return errors.Wrap(err, "failed to list deployments") - } - for _, d := range deployments.Items { - containers := d.Spec.Template.Spec.Containers - // match deployments with the correct annotation - annotationValue := d.ObjectMeta.Annotations[updateOnChangeAnnotation] - if annotationValue != "" { - values := strings.Split(annotationValue, ",") - matches := false - for _, value := range values { - if value == configMapName { - matches = true - break - } - } - if matches { - updateContainers(containers, annotationValue, configMapVersion) - - // update the deployment - _, err := client.ExtensionsV1beta1().Deployments(ns).Update(&d) - if err != nil { - return errors.Wrap(err, "update deployment failed") - } - logrus.Infof("Updated Deployment %s", d.Name) - } - } - } - return nil -} - -func updateContainers(containers []v1.Container, annotationValue, configMapVersion string) bool { - // we can have multiple configmaps to update - answer := false - configmaps := strings.Split(annotationValue, ",") - for _, cmNameToUpdate := range configmaps { - configmapEnvar := "STAKATER_" + convertToEnvVarName(cmNameToUpdate) + "_CONFIGMAP" - - for i := range containers { - envs := containers[i].Env - matched := false - for j := range envs { - if envs[j].Name == configmapEnvar { - matched = true - if envs[j].Value != configMapVersion { - logrus.Infof("Updating %s to %s", configmapEnvar, configMapVersion) - envs[j].Value = configMapVersion - answer = true - } - } - } - // if no existing env var exists lets create one - if !matched { - e := v1.EnvVar{ - Name: configmapEnvar, - Value: configMapVersion, - } - containers[i].Env = append(containers[i].Env, e) - answer = true - } - } - } - return answer -} - -// convertToEnvVarName converts the given text into a usable env var -// removing any special chars with '_' -func convertToEnvVarName(text string) string { - var buffer bytes.Buffer - lower := strings.ToUpper(text) - lastCharValid := false - for i := 0; i < len(lower); i++ { - ch := lower[i] - if (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') { - buffer.WriteString(string(ch)) - lastCharValid = true - } else { - if lastCharValid { - buffer.WriteString("_") - } - lastCharValid = false - } - } - return buffer.String() -} - -// lets convert the configmap into a unique token based on the data values -func convertConfigMapToToken(cm *v1.ConfigMap) string { - values := []string{} - for k, v := range cm.Data { - values = append(values, k+"="+v) - } - sort.Strings(values) - text := strings.Join(values, ";") - // we could zip and base64 encode - // but for now we could leave this easy to read so that its easier to diagnose when & why things changed - return text -} From ccb1fcd1b55c5bd4a3ed4f57ffdccd071d4f8554 Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Fri, 13 Jul 2018 16:46:35 +0500 Subject: [PATCH 20/27] Implement golangci comments --- internal/pkg/controller/controller_test.go | 30 ++++++++++------------ pkg/kube/client.go | 8 +++++- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index 047eb395..59f8fe57 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -46,23 +46,21 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { configmapName := configmapNamePrefix + "-update-" + randSeq(5) configmapClient := client.CoreV1().ConfigMaps(namespace) - configmap := initConfigmap(namespace, configmapName) - configmap, err = configmapClient.Create(configmap) + _, err = configmapClient.Create(initConfigmap(namespace, configmapName)) if err != nil { logrus.Infof("Error detected %s.\n", err) panic(err) } - logrus.Infof("Created Configmap %q.\n", configmap.GetObjectMeta().GetName()) + logrus.Infof("Created Configmap %q.\n", configmapName) time.Sleep(10 * time.Second) deployment := createDeployement(configmapName, namespace) - logrus.Infof("Updating Configmap %q.\n", configmap.GetObjectMeta().GetName()) - configmap, err = configmapClient.Get(configmapName, metav1.GetOptions{}) + logrus.Infof("Updating Configmap %q.\n", configmapName) + _, err = configmapClient.Get(configmapName, metav1.GetOptions{}) if err != nil { logrus.Infof("Error while getting configmap %v", err) } - configmap = updateConfigmap(namespace, configmapName) - _, updateErr := configmapClient.Update(configmap) + _, updateErr := configmapClient.Update(updateConfigmap(namespace, configmapName)) // TODO: Add functionality to verify reloader functionality here @@ -73,8 +71,8 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { } time.Sleep(10 * time.Second) logrus.Infof("Deleting Deployment %q.\n", deployment.GetObjectMeta().GetName()) - controller.client.Extensions().Deployments(namespace).Delete(configmapName, &metav1.DeleteOptions{}); - logrus.Infof("Deleting Configmap %q.\n", configmap.GetObjectMeta().GetName()) + controller.client.Extensions().Deployments(namespace).Delete(configmapName, &metav1.DeleteOptions{}) + logrus.Infof("Deleting Configmap %q.\n", configmapName) error := controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) if error != nil { logrus.Infof("Error while deleting the configmap %v", error) @@ -111,21 +109,19 @@ func TestControllerForUpdatingSecretShouldUpdateDeployment(t *testing.T) { secretName := secretNamePrefix + "-update-" + randSeq(5) secretClient := client.CoreV1().Secrets(namespace) - secret := initSecret(namespace, secretName) - secret, err = secretClient.Create(secret) + _, err = secretClient.Create(initSecret(namespace, secretName)) if err != nil { panic(err) } - logrus.Infof("Created Secret %q.\n", secret.GetObjectMeta().GetName()) + logrus.Infof("Created Secret %q.\n", secretName) time.Sleep(10 * time.Second) - logrus.Infof("Updating Secret %q.\n", secret.GetObjectMeta().GetName()) - secret, err = secretClient.Get(secretName, metav1.GetOptions{}) + logrus.Infof("Updating Secret %q.\n", secretName) + _, err = secretClient.Get(secretName, metav1.GetOptions{}) if err != nil { logrus.Infof("Error while getting secret %v", err) } - secret = updateSecret(namespace, secretName) - _, updateErr := secretClient.Update(secret) + _, updateErr := secretClient.Update(updateSecret(namespace, secretName)) // TODO: Add functionality to verify reloader functionality here @@ -135,7 +131,7 @@ func TestControllerForUpdatingSecretShouldUpdateDeployment(t *testing.T) { panic(updateErr) } time.Sleep(10 * time.Second) - logrus.Infof("Deleting Secret %q.\n", secret.GetObjectMeta().GetName()) + logrus.Infof("Deleting Secret %q.\n", secretName) error := controller.client.CoreV1().Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{}) if error != nil { logrus.Infof("Error while deleting the secret %v", error) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 3596df28..11c61e16 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -8,7 +8,7 @@ import ( "k8s.io/client-go/tools/clientcmd" ) -// gets the client for k8s, if ~/.kube/config exists so get that config else incluster config +// GetClient gets the client for k8s, if ~/.kube/config exists so get that config else incluster config func GetClient() (*kubernetes.Clientset, error) { var config *rest.Config var err error @@ -19,8 +19,14 @@ func GetClient() (*kubernetes.Clientset, error) { //If file exists so use that config settings if _, err := os.Stat(kubeconfigPath); err == nil { config, err = clientcmd.BuildConfigFromFlags("", kubeconfigPath) + if err != nil { + return nil, err + } } else { //Use Incluster Configuration config, err = rest.InClusterConfig() + if err != nil { + return nil, err + } } if err != nil { return nil, err From 6d821cab7c1f4c45329a535a3aa684ad96f1c982 Mon Sep 17 00:00:00 2001 From: Waseem Hassan Date: Fri, 13 Jul 2018 17:05:14 +0500 Subject: [PATCH 21/27] Fix controller_test format --- internal/pkg/controller/controller_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index ba676ddd..ab62b5d1 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -171,11 +171,11 @@ func initDeployment(namespace string, deploymentName string) *v1beta1.Deployment }, Spec: v1.PodSpec{ Containers: []v1.Container{ - v1.Container{ + { Image: "tutum/hello-world", Name: deploymentName, Env: []v1.EnvVar{ - v1.EnvVar{ + { Name: "BUCKET_NAME", Value: "test", }, From c58efd19bfa3f8723c9df616301c682fa9abca95 Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Fri, 13 Jul 2018 17:08:46 +0500 Subject: [PATCH 22/27] Implement golangci comments --- internal/pkg/controller/controller.go | 3 ++- internal/pkg/controller/controller_test.go | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/pkg/controller/controller.go b/internal/pkg/controller/controller.go index 84cac772..33c2d66a 100644 --- a/internal/pkg/controller/controller.go +++ b/internal/pkg/controller/controller.go @@ -14,6 +14,7 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" ) + // ResourceUpdated contains new or updated objects type ResourceUpdated struct { newObj interface{} @@ -126,7 +127,7 @@ func (c *Controller) takeAction(resourceUpdated ResourceUpdated) error { } else { logrus.Infof("Detected changes in object %s", newObj) // process events based on its type - if(oldObj == nil){ + if oldObj == nil { logrus.Infof("Performing 'Added' action for controller of type '%s'", c.resource) } else { logrus.Infof("Performing 'Updated' action for controller of type '%s'", c.resource) diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index 6633c784..fb3c0a9d 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -71,7 +71,10 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { } time.Sleep(10 * time.Second) logrus.Infof("Deleting Deployment %q.\n", deployment.GetObjectMeta().GetName()) - controller.client.Extensions().Deployments(namespace).Delete(configmapName, &metav1.DeleteOptions{}) + deploymentError := controller.client.Extensions().Deployments(namespace).Delete(configmapName, &metav1.DeleteOptions{}) + if deploymentError != nil { + logrus.Infof("Error while deleting the configmap %v", deploymentError) + } logrus.Infof("Deleting Configmap %q.\n", configmapName) error := controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) if error != nil { From 5823152a04f7a3274d3af7f64a7b0ca0c9d0d96e Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Fri, 13 Jul 2018 17:11:41 +0500 Subject: [PATCH 23/27] Implement golangci comments --- internal/pkg/controller/controller_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index fb3c0a9d..c812f800 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -71,7 +71,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { } time.Sleep(10 * time.Second) logrus.Infof("Deleting Deployment %q.\n", deployment.GetObjectMeta().GetName()) - deploymentError := controller.client.Extensions().Deployments(namespace).Delete(configmapName, &metav1.DeleteOptions{}) + deploymentError := controller.client.ExtensionsV1beta1().Deployments(namespace).Delete(configmapName, &metav1.DeleteOptions{}) if deploymentError != nil { logrus.Infof("Error while deleting the configmap %v", deploymentError) } @@ -84,7 +84,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { } func createDeployement(deploymentName string, namespace string) *v1beta1.Deployment { - deploymentClient := client.Extensions().Deployments(namespace) + deploymentClient := client.ExtensionsV1beta1().Deployments(namespace) deployment := initDeployment(namespace, deploymentName) deployment, error := deploymentClient.Create(deployment) if error != nil { From 14f9570a0476619ccc6e17d8f3d5f9c9b6de1c1d Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Fri, 13 Jul 2018 19:01:04 +0500 Subject: [PATCH 24/27] Add resource handler to handle rolling updates --- internal/pkg/cmd/reloader.go | 4 +- internal/pkg/controller/controller.go | 61 ++++++++-------------- internal/pkg/controller/controller_test.go | 42 ++++++++------- internal/pkg/handler/resourcehandler.go | 58 ++++++++++++++++++++ 4 files changed, 103 insertions(+), 62 deletions(-) create mode 100644 internal/pkg/handler/resourcehandler.go diff --git a/internal/pkg/cmd/reloader.go b/internal/pkg/cmd/reloader.go index 2e00421d..5cb0c7d0 100644 --- a/internal/pkg/cmd/reloader.go +++ b/internal/pkg/cmd/reloader.go @@ -12,12 +12,12 @@ import ( // NewReloaderCommand starts the reloader controller func NewReloaderCommand() *cobra.Command { - cmds := &cobra.Command{ + cmd := &cobra.Command{ Use: "reloader", Short: "A watcher for your Kubernetes cluster", Run: startReloader, } - return cmds + return cmd } func startReloader(cmd *cobra.Command, args []string) { diff --git a/internal/pkg/controller/controller.go b/internal/pkg/controller/controller.go index 33c2d66a..a10572f3 100644 --- a/internal/pkg/controller/controller.go +++ b/internal/pkg/controller/controller.go @@ -5,6 +5,7 @@ import ( "time" "github.com/sirupsen/logrus" + "github.com/stakater/Reloader/internal/pkg/handler" "github.com/stakater/Reloader/pkg/kube" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/util/runtime" @@ -15,19 +16,12 @@ import ( "k8s.io/client-go/util/workqueue" ) -// ResourceUpdated contains new or updated objects -type ResourceUpdated struct { - newObj interface{} - oldObj interface{} -} - // Controller for checking events type Controller struct { client kubernetes.Interface indexer cache.Indexer queue workqueue.RateLimitingInterface informer cache.Controller - resource string namespace string } @@ -37,7 +31,6 @@ func NewController( c := Controller{ client: client, - resource: resource, namespace: namespace, } @@ -47,6 +40,7 @@ func NewController( indexer, informer := cache.NewIndexerInformer(listWatcher, kube.ResourceMap[resource], 0, cache.ResourceEventHandlerFuncs{ AddFunc: c.Add, UpdateFunc: c.Update, + DeleteFunc: c.Delete, }, cache.Indexers{}) c.indexer = indexer c.informer = informer @@ -54,25 +48,31 @@ func NewController( return &c, nil } -// Add function to add a 'create' event to the queue in case of creating a pod +// Add function to add a 'create' event to the queue in case of creating a resource func (c *Controller) Add(obj interface{}) { - c.queue.Add(ResourceUpdated{ - newObj: obj, + c.queue.Add(handler.ResourceCreatedHandler{ + NewResource: obj, }) } -// Update function to add an 'update' event to the queue in case of updating a pod +// Update function to add an 'update' event to the queue in case of updating a resource func (c *Controller) Update(old interface{}, new interface{}) { - c.queue.Add(ResourceUpdated{ - newObj: new, - oldObj: old, + c.queue.Add(handler.ResourceUpdatedHandler{ + NewResource: new, + OldResource: old, }) } +// Delete function to add an 'update' event to the queue in case of deleting a resource +func (c *Controller) Delete(old interface{}) { + // TODO Added this function for future usecase + logrus.Infof("Deleted resource has been added to queue") +} + //Run function for controller which handles the queue func (c *Controller) Run(threadiness int, stopCh chan struct{}) { - logrus.Infof("Starting Controller for type ", c.resource) + logrus.Infof("Starting Controller") defer errorHandler.HandleCrash() // Let the workers stop when we are done @@ -91,7 +91,7 @@ func (c *Controller) Run(threadiness int, stopCh chan struct{}) { } <-stopCh - logrus.Infof("Stopping Controller for type ", c.resource) + logrus.Infof("Stopping Controller") } func (c *Controller) runWorker() { @@ -101,41 +101,22 @@ func (c *Controller) runWorker() { func (c *Controller) processNextItem() bool { // Wait until there is a new item in the working queue - resourceUpdated, quit := c.queue.Get() + resourceHandler, quit := c.queue.Get() if quit { return false } // Tell the queue that we are done with processing this key. This unblocks the key for other workers // This allows safe parallel processing because two events with the same key are never processed in // parallel. - defer c.queue.Done(resourceUpdated) + defer c.queue.Done(resourceHandler) // Invoke the method containing the business logic - err := c.takeAction(resourceUpdated.(ResourceUpdated)) + err := resourceHandler.(handler.ResourceHandler).Handle() // Handle the error if something went wrong during the execution of the business logic - c.handleErr(err, resourceUpdated) + c.handleErr(err, resourceHandler) return true } -// main business logic that acts bassed on the event or key -func (c *Controller) takeAction(resourceUpdated ResourceUpdated) error { - - newObj := resourceUpdated.newObj - oldObj := resourceUpdated.oldObj - if newObj == nil { - logrus.Infof("Error in Action") - } else { - logrus.Infof("Detected changes in object %s", newObj) - // process events based on its type - if oldObj == nil { - logrus.Infof("Performing 'Added' action for controller of type '%s'", c.resource) - } else { - logrus.Infof("Performing 'Updated' action for controller of type '%s'", c.resource) - } - } - return nil -} - // handleErr checks if an error happened and makes sure we will retry later. func (c *Controller) handleErr(err error, key interface{}) { if err == nil { diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index c812f800..e11c87d3 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -48,8 +48,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { configmapClient := client.CoreV1().ConfigMaps(namespace) _, err = configmapClient.Create(initConfigmap(namespace, configmapName)) if err != nil { - logrus.Infof("Error detected %s.\n", err) - panic(err) + logrus.Fatalf("Fatal error in configmap creation: %v", err) } logrus.Infof("Created Configmap %q.\n", configmapName) time.Sleep(10 * time.Second) @@ -65,20 +64,22 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { // TODO: Add functionality to verify reloader functionality here if updateErr != nil { - error := controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) - logrus.Infof("Error while deleting the configmap %v", error) - panic(updateErr) + err = controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) + if err != nil { + logrus.Infof("Error while deleting the configmap %v", err) + } + logrus.Fatalf("Fatal error in configmap update: %v", updateErr) } time.Sleep(10 * time.Second) logrus.Infof("Deleting Deployment %q.\n", deployment.GetObjectMeta().GetName()) deploymentError := controller.client.ExtensionsV1beta1().Deployments(namespace).Delete(configmapName, &metav1.DeleteOptions{}) if deploymentError != nil { - logrus.Infof("Error while deleting the configmap %v", deploymentError) + logrus.Fatalf("Error while deleting the configmap %v", deploymentError) } logrus.Infof("Deleting Configmap %q.\n", configmapName) - error := controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) - if error != nil { - logrus.Infof("Error while deleting the configmap %v", error) + err = controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) + if err != nil { + logrus.Infof("Error while deleting the configmap %v", err) } time.Sleep(15 * time.Second) } @@ -86,11 +87,10 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { func createDeployement(deploymentName string, namespace string) *v1beta1.Deployment { deploymentClient := client.ExtensionsV1beta1().Deployments(namespace) deployment := initDeployment(namespace, deploymentName) - deployment, error := deploymentClient.Create(deployment) - if error != nil { - panic(error) + deployment, err := deploymentClient.Create(deployment) + if err != nil { + logrus.Fatalf("Fatal error in deployment creation: %v", err) } - //time.Sleep(10 * time.Second) logrus.Infof("Created Deployment %q.\n", deployment.GetObjectMeta().GetName()) return deployment } @@ -114,7 +114,7 @@ func TestControllerForUpdatingSecretShouldUpdateDeployment(t *testing.T) { secretClient := client.CoreV1().Secrets(namespace) _, err = secretClient.Create(initSecret(namespace, secretName)) if err != nil { - panic(err) + logrus.Fatalf("Fatal error in secret creation: %v", err) } logrus.Infof("Created Secret %q.\n", secretName) time.Sleep(10 * time.Second) @@ -129,15 +129,17 @@ func TestControllerForUpdatingSecretShouldUpdateDeployment(t *testing.T) { // TODO: Add functionality to verify reloader functionality here if updateErr != nil { - error := controller.client.CoreV1().Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{}) - logrus.Infof("Error while deleting the secret %v", error) - panic(updateErr) + err := controller.client.CoreV1().Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{}) + if err != nil { + logrus.Infof("Error while deleting the secret %v", err) + } + logrus.Infof("Error while deleting the secret %v", err) } time.Sleep(10 * time.Second) logrus.Infof("Deleting Secret %q.\n", secretName) - error := controller.client.CoreV1().Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{}) - if error != nil { - logrus.Infof("Error while deleting the secret %v", error) + err = controller.client.CoreV1().Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{}) + if err != nil { + logrus.Infof("Error while deleting the secret %v", err) } time.Sleep(15 * time.Second) } diff --git a/internal/pkg/handler/resourcehandler.go b/internal/pkg/handler/resourcehandler.go new file mode 100644 index 00000000..bf2178e3 --- /dev/null +++ b/internal/pkg/handler/resourcehandler.go @@ -0,0 +1,58 @@ +package handler + +import ( + "github.com/sirupsen/logrus" + "k8s.io/api/core/v1" +) + +// ResourceHandler handles the creation and update of resources +type ResourceHandler interface { + Handle() error +} + +// ResourceCreatedHandler contains new objects +type ResourceCreatedHandler struct { + NewResource interface{} +} + +// ResourceUpdatedHandler contains updated objects +type ResourceUpdatedHandler struct { + NewResource interface{} + OldResource interface{} +} + +// Handle processes the newly created resource +func (r ResourceCreatedHandler) Handle() error { + if r.NewResource == nil { + logrus.Infof("Error in Handler") + } else { + logrus.Infof("Detected changes in object %s", r.NewResource) + // process resource based on its type + if _, ok := r.NewResource.(*v1.ConfigMap); ok { + logrus.Infof("Performing 'Added' action for resource of type 'configmap'") + } else if _, ok := r.NewResource.(*v1.Secret); ok { + logrus.Infof("Performing 'Added' action for resource of type 'secret'") + } else { + logrus.Infof("Invalid resource") + } + } + return nil +} + +// Handle processes the updated resource +func (r ResourceUpdatedHandler) Handle() error { + if r.NewResource == nil || r.OldResource == nil { + logrus.Infof("Error in Handler") + } else { + logrus.Infof("Detected changes in object %s", r.NewResource) + // process resource based on its type + if _, ok := r.NewResource.(*v1.ConfigMap); ok { + logrus.Infof("Performing 'Updated' action for resource of type 'configmap'") + } else if _, ok := r.NewResource.(*v1.Secret); ok { + logrus.Infof("Performing 'Updated' action for resource of type 'secret'") + } else { + logrus.Infof("Invalid resource") + } + } + return nil +} From 0f78a159f63ac98aef580383bb13377378cc9a0e Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Mon, 16 Jul 2018 13:35:40 +0500 Subject: [PATCH 25/27] Implement PR-1 review comments --- internal/pkg/controller/controller.go | 10 +++---- internal/pkg/controller/controller_test.go | 28 +++++++++++++------ internal/pkg/handler/created-handler.go | 6 ++++ .../{resourcehandler.go => handler.go} | 27 ++++++------------ internal/pkg/handler/updated-handler.go | 7 +++++ 5 files changed, 45 insertions(+), 33 deletions(-) create mode 100644 internal/pkg/handler/created-handler.go rename internal/pkg/handler/{resourcehandler.go => handler.go} (60%) create mode 100644 internal/pkg/handler/updated-handler.go diff --git a/internal/pkg/controller/controller.go b/internal/pkg/controller/controller.go index a10572f3..a3914a41 100644 --- a/internal/pkg/controller/controller.go +++ b/internal/pkg/controller/controller.go @@ -48,22 +48,22 @@ func NewController( return &c, nil } -// Add function to add a 'create' event to the queue in case of creating a resource +// Add function to add a new object to the queue in case of creating a resource func (c *Controller) Add(obj interface{}) { c.queue.Add(handler.ResourceCreatedHandler{ - NewResource: obj, + Resource: obj, }) } -// Update function to add an 'update' event to the queue in case of updating a resource +// Update function to add an old object and a new object to the queue in case of updating a resource func (c *Controller) Update(old interface{}, new interface{}) { c.queue.Add(handler.ResourceUpdatedHandler{ - NewResource: new, + Resource: new, OldResource: old, }) } -// Delete function to add an 'update' event to the queue in case of deleting a resource +// Delete function to add an object to the queue in case of deleting a resource func (c *Controller) Delete(old interface{}) { // TODO Added this function for future usecase logrus.Infof("Deleted resource has been added to queue") diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index e11c87d3..6aa90897 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -10,10 +10,10 @@ import ( "k8s.io/api/core/v1" "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" ) var ( - client, _ = kube.GetClient() configmapNamePrefix = "testconfigmap-reloader" secretNamePrefix = "testsecret-reloader" letters = []rune("abcdefghijklmnopqrstuvwxyz") @@ -30,9 +30,14 @@ func randSeq(n int) string { // Creating a Controller to do a rolling upgrade upon updating the configmap or secret func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { + client, err := kube.GetClient() + if err != nil { + logrus.Infof("Unable to create Kubernetes client error = %v", err) + return + } namespace := "test-reloader" - createNamespace(t, namespace) - defer deleteNamespace(t, namespace) + createNamespace(t, namespace, client) + defer deleteNamespace(t, namespace, client) controller, err := NewController(client, "configMaps", namespace) if err != nil { @@ -52,7 +57,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { } logrus.Infof("Created Configmap %q.\n", configmapName) time.Sleep(10 * time.Second) - deployment := createDeployement(configmapName, namespace) + deployment := createDeployement(configmapName, namespace, client) logrus.Infof("Updating Configmap %q.\n", configmapName) _, err = configmapClient.Get(configmapName, metav1.GetOptions{}) @@ -84,7 +89,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { time.Sleep(15 * time.Second) } -func createDeployement(deploymentName string, namespace string) *v1beta1.Deployment { +func createDeployement(deploymentName string, namespace string, client kubernetes.Interface) *v1beta1.Deployment { deploymentClient := client.ExtensionsV1beta1().Deployments(namespace) deployment := initDeployment(namespace, deploymentName) deployment, err := deploymentClient.Create(deployment) @@ -96,9 +101,14 @@ func createDeployement(deploymentName string, namespace string) *v1beta1.Deploym } func TestControllerForUpdatingSecretShouldUpdateDeployment(t *testing.T) { + client, err := kube.GetClient() + if err != nil { + logrus.Infof("Unable to create Kubernetes client error = %v", err) + return + } namespace := "test-reloader-secrets" - createNamespace(t, namespace) - defer deleteNamespace(t, namespace) + createNamespace(t, namespace, client) + defer deleteNamespace(t, namespace, client) controller, err := NewController(client, "secrets", namespace) if err != nil { @@ -203,7 +213,7 @@ func initSecret(namespace string, secretName string) *v1.Secret { } } -func createNamespace(t *testing.T, namespace string) { +func createNamespace(t *testing.T, namespace string, client kubernetes.Interface) { _, err := client.CoreV1().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}) if err != nil { t.Error("Failed to create namespace for testing", err) @@ -212,7 +222,7 @@ func createNamespace(t *testing.T, namespace string) { } } -func deleteNamespace(t *testing.T, namespace string) { +func deleteNamespace(t *testing.T, namespace string, client kubernetes.Interface) { err := client.CoreV1().Namespaces().Delete(namespace, &metav1.DeleteOptions{}) if err != nil { t.Error("Failed to delete namespace that was created for testing", err) diff --git a/internal/pkg/handler/created-handler.go b/internal/pkg/handler/created-handler.go new file mode 100644 index 00000000..1983018d --- /dev/null +++ b/internal/pkg/handler/created-handler.go @@ -0,0 +1,6 @@ +package handler + +// ResourceCreatedHandler contains new objects +type ResourceCreatedHandler struct { + Resource interface{} +} diff --git a/internal/pkg/handler/resourcehandler.go b/internal/pkg/handler/handler.go similarity index 60% rename from internal/pkg/handler/resourcehandler.go rename to internal/pkg/handler/handler.go index bf2178e3..09f3a4ee 100644 --- a/internal/pkg/handler/resourcehandler.go +++ b/internal/pkg/handler/handler.go @@ -10,27 +10,16 @@ type ResourceHandler interface { Handle() error } -// ResourceCreatedHandler contains new objects -type ResourceCreatedHandler struct { - NewResource interface{} -} - -// ResourceUpdatedHandler contains updated objects -type ResourceUpdatedHandler struct { - NewResource interface{} - OldResource interface{} -} - // Handle processes the newly created resource func (r ResourceCreatedHandler) Handle() error { - if r.NewResource == nil { + if r.Resource == nil { logrus.Infof("Error in Handler") } else { - logrus.Infof("Detected changes in object %s", r.NewResource) + logrus.Infof("Detected changes in object %s", r.Resource) // process resource based on its type - if _, ok := r.NewResource.(*v1.ConfigMap); ok { + if _, ok := r.Resource.(*v1.ConfigMap); ok { logrus.Infof("Performing 'Added' action for resource of type 'configmap'") - } else if _, ok := r.NewResource.(*v1.Secret); ok { + } else if _, ok := r.Resource.(*v1.Secret); ok { logrus.Infof("Performing 'Added' action for resource of type 'secret'") } else { logrus.Infof("Invalid resource") @@ -41,14 +30,14 @@ func (r ResourceCreatedHandler) Handle() error { // Handle processes the updated resource func (r ResourceUpdatedHandler) Handle() error { - if r.NewResource == nil || r.OldResource == nil { + if r.Resource == nil || r.OldResource == nil { logrus.Infof("Error in Handler") } else { - logrus.Infof("Detected changes in object %s", r.NewResource) + logrus.Infof("Detected changes in object %s", r.Resource) // process resource based on its type - if _, ok := r.NewResource.(*v1.ConfigMap); ok { + if _, ok := r.Resource.(*v1.ConfigMap); ok { logrus.Infof("Performing 'Updated' action for resource of type 'configmap'") - } else if _, ok := r.NewResource.(*v1.Secret); ok { + } else if _, ok := r.Resource.(*v1.Secret); ok { logrus.Infof("Performing 'Updated' action for resource of type 'secret'") } else { logrus.Infof("Invalid resource") diff --git a/internal/pkg/handler/updated-handler.go b/internal/pkg/handler/updated-handler.go new file mode 100644 index 00000000..85157afa --- /dev/null +++ b/internal/pkg/handler/updated-handler.go @@ -0,0 +1,7 @@ +package handler + +// ResourceUpdatedHandler contains updated objects +type ResourceUpdatedHandler struct { + Resource interface{} + OldResource interface{} +} From 44341ff539d3dcd2a519ee4d53c22b6b3c51234e Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Mon, 16 Jul 2018 14:21:27 +0500 Subject: [PATCH 26/27] Implement PR-1 review comments --- internal/pkg/controller/controller_test.go | 8 ++--- internal/pkg/handler/created-handler.go | 23 ++++++++++++ internal/pkg/handler/handler.go | 41 ---------------------- internal/pkg/handler/updated-handler.go | 23 ++++++++++++ 4 files changed, 50 insertions(+), 45 deletions(-) diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index 6aa90897..c2517c52 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -32,7 +32,7 @@ func randSeq(n int) string { func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { client, err := kube.GetClient() if err != nil { - logrus.Infof("Unable to create Kubernetes client error = %v", err) + logrus.Errorf("Unable to create Kubernetes client error = %v", err) return } namespace := "test-reloader" @@ -41,7 +41,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { controller, err := NewController(client, "configMaps", namespace) if err != nil { - logrus.Infof("Unable to create NewController error = %v", err) + logrus.Errorf("Unable to create NewController error = %v", err) return } stop := make(chan struct{}) @@ -103,7 +103,7 @@ func createDeployement(deploymentName string, namespace string, client kubernete func TestControllerForUpdatingSecretShouldUpdateDeployment(t *testing.T) { client, err := kube.GetClient() if err != nil { - logrus.Infof("Unable to create Kubernetes client error = %v", err) + logrus.Errorf("Unable to create Kubernetes client error = %v", err) return } namespace := "test-reloader-secrets" @@ -112,7 +112,7 @@ func TestControllerForUpdatingSecretShouldUpdateDeployment(t *testing.T) { controller, err := NewController(client, "secrets", namespace) if err != nil { - logrus.Infof("Unable to create NewController error = %v", err) + logrus.Errorf("Unable to create NewController error = %v", err) return } stop := make(chan struct{}) diff --git a/internal/pkg/handler/created-handler.go b/internal/pkg/handler/created-handler.go index 1983018d..53927afd 100644 --- a/internal/pkg/handler/created-handler.go +++ b/internal/pkg/handler/created-handler.go @@ -1,6 +1,29 @@ package handler +import ( + "github.com/sirupsen/logrus" + "k8s.io/api/core/v1" +) + // ResourceCreatedHandler contains new objects type ResourceCreatedHandler struct { Resource interface{} } + +// Handle processes the newly created resource +func (r ResourceCreatedHandler) Handle() error { + if r.Resource == nil { + logrus.Errorf("Error in Handler") + } else { + logrus.Infof("Detected changes in object %s", r.Resource) + // process resource based on its type + if _, ok := r.Resource.(*v1.ConfigMap); ok { + logrus.Infof("Performing 'Added' action for resource of type 'configmap'") + } else if _, ok := r.Resource.(*v1.Secret); ok { + logrus.Infof("Performing 'Added' action for resource of type 'secret'") + } else { + logrus.Infof("Invalid resource") + } + } + return nil +} diff --git a/internal/pkg/handler/handler.go b/internal/pkg/handler/handler.go index 09f3a4ee..20b28ee7 100644 --- a/internal/pkg/handler/handler.go +++ b/internal/pkg/handler/handler.go @@ -1,47 +1,6 @@ package handler -import ( - "github.com/sirupsen/logrus" - "k8s.io/api/core/v1" -) - // ResourceHandler handles the creation and update of resources type ResourceHandler interface { Handle() error } - -// Handle processes the newly created resource -func (r ResourceCreatedHandler) Handle() error { - if r.Resource == nil { - logrus.Infof("Error in Handler") - } else { - logrus.Infof("Detected changes in object %s", r.Resource) - // process resource based on its type - if _, ok := r.Resource.(*v1.ConfigMap); ok { - logrus.Infof("Performing 'Added' action for resource of type 'configmap'") - } else if _, ok := r.Resource.(*v1.Secret); ok { - logrus.Infof("Performing 'Added' action for resource of type 'secret'") - } else { - logrus.Infof("Invalid resource") - } - } - return nil -} - -// Handle processes the updated resource -func (r ResourceUpdatedHandler) Handle() error { - if r.Resource == nil || r.OldResource == nil { - logrus.Infof("Error in Handler") - } else { - logrus.Infof("Detected changes in object %s", r.Resource) - // process resource based on its type - if _, ok := r.Resource.(*v1.ConfigMap); ok { - logrus.Infof("Performing 'Updated' action for resource of type 'configmap'") - } else if _, ok := r.Resource.(*v1.Secret); ok { - logrus.Infof("Performing 'Updated' action for resource of type 'secret'") - } else { - logrus.Infof("Invalid resource") - } - } - return nil -} diff --git a/internal/pkg/handler/updated-handler.go b/internal/pkg/handler/updated-handler.go index 85157afa..478445db 100644 --- a/internal/pkg/handler/updated-handler.go +++ b/internal/pkg/handler/updated-handler.go @@ -1,7 +1,30 @@ package handler +import ( + "github.com/sirupsen/logrus" + "k8s.io/api/core/v1" +) + // ResourceUpdatedHandler contains updated objects type ResourceUpdatedHandler struct { Resource interface{} OldResource interface{} } + +// Handle processes the updated resource +func (r ResourceUpdatedHandler) Handle() error { + if r.Resource == nil || r.OldResource == nil { + logrus.Errorf("Error in Handler") + } else { + logrus.Infof("Detected changes in object %s", r.Resource) + // process resource based on its type + if _, ok := r.Resource.(*v1.ConfigMap); ok { + logrus.Infof("Performing 'Updated' action for resource of type 'configmap'") + } else if _, ok := r.Resource.(*v1.Secret); ok { + logrus.Infof("Performing 'Updated' action for resource of type 'secret'") + } else { + logrus.Infof("Invalid resource") + } + } + return nil +} From 21fec1c4b9f662fce74f485694df5d18f0732de7 Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Tue, 17 Jul 2018 12:55:56 +0500 Subject: [PATCH 27/27] Fix logrus usage --- build/package/Dockerfile | 5 ----- glide.yaml | 2 +- internal/pkg/cmd/reloader.go | 2 +- internal/pkg/controller/controller.go | 4 ++-- internal/pkg/controller/controller_test.go | 14 +++++++------- internal/pkg/handler/created-handler.go | 2 +- internal/pkg/handler/updated-handler.go | 2 +- 7 files changed, 13 insertions(+), 18 deletions(-) delete mode 100644 build/package/Dockerfile diff --git a/build/package/Dockerfile b/build/package/Dockerfile deleted file mode 100644 index aa806816..00000000 --- a/build/package/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM scratch - -ENTRYPOINT ["/reloader"] - -COPY ./reloader / diff --git a/glide.yaml b/glide.yaml index 018d3683..d56557dc 100644 --- a/glide.yaml +++ b/glide.yaml @@ -11,4 +11,4 @@ import: - package: github.com/spf13/pflag version: 1.0.1 - package: github.com/sirupsen/logrus - version: ~1.0.3 + version: 1.0.5 diff --git a/internal/pkg/cmd/reloader.go b/internal/pkg/cmd/reloader.go index 5cb0c7d0..c3fea3b4 100644 --- a/internal/pkg/cmd/reloader.go +++ b/internal/pkg/cmd/reloader.go @@ -25,7 +25,7 @@ func startReloader(cmd *cobra.Command, args []string) { currentNamespace := os.Getenv("KUBERNETES_NAMESPACE") if len(currentNamespace) == 0 { currentNamespace = v1.NamespaceAll - logrus.Infof("Warning: KUBERNETES_NAMESPACE is unset, will detect changes in all namespaces.") + logrus.Warnf("KUBERNETES_NAMESPACE is unset, will detect changes in all namespaces.") } // create the clientset diff --git a/internal/pkg/controller/controller.go b/internal/pkg/controller/controller.go index a3914a41..e14ead7e 100644 --- a/internal/pkg/controller/controller.go +++ b/internal/pkg/controller/controller.go @@ -66,7 +66,7 @@ func (c *Controller) Update(old interface{}, new interface{}) { // Delete function to add an object to the queue in case of deleting a resource func (c *Controller) Delete(old interface{}) { // TODO Added this function for future usecase - logrus.Infof("Deleted resource has been added to queue") + logrus.Infof("Deleted resource has been detected but no further implementation found to take action") } //Run function for controller which handles the queue @@ -129,7 +129,7 @@ func (c *Controller) handleErr(err error, key interface{}) { // This controller retries 5 times if something goes wrong. After that, it stops trying. if c.queue.NumRequeues(key) < 5 { - logrus.Infof("Error syncing events %v: %v", key, err) + logrus.Errorf("Error syncing events %v: %v", key, err) // Re-enqueue the key rate limited. Based on the rate limiter on the // queue and the re-enqueue history, the key will be processed later again. diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index c2517c52..0035d0fb 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -62,7 +62,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { logrus.Infof("Updating Configmap %q.\n", configmapName) _, err = configmapClient.Get(configmapName, metav1.GetOptions{}) if err != nil { - logrus.Infof("Error while getting configmap %v", err) + logrus.Errorf("Error while getting configmap %v", err) } _, updateErr := configmapClient.Update(updateConfigmap(namespace, configmapName)) @@ -71,7 +71,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { if updateErr != nil { err = controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) if err != nil { - logrus.Infof("Error while deleting the configmap %v", err) + logrus.Errorf("Error while deleting the configmap %v", err) } logrus.Fatalf("Fatal error in configmap update: %v", updateErr) } @@ -84,7 +84,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) { logrus.Infof("Deleting Configmap %q.\n", configmapName) err = controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{}) if err != nil { - logrus.Infof("Error while deleting the configmap %v", err) + logrus.Errorf("Error while deleting the configmap %v", err) } time.Sleep(15 * time.Second) } @@ -132,7 +132,7 @@ func TestControllerForUpdatingSecretShouldUpdateDeployment(t *testing.T) { logrus.Infof("Updating Secret %q.\n", secretName) _, err = secretClient.Get(secretName, metav1.GetOptions{}) if err != nil { - logrus.Infof("Error while getting secret %v", err) + logrus.Errorf("Error while getting secret %v", err) } _, updateErr := secretClient.Update(updateSecret(namespace, secretName)) @@ -141,15 +141,15 @@ func TestControllerForUpdatingSecretShouldUpdateDeployment(t *testing.T) { if updateErr != nil { err := controller.client.CoreV1().Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{}) if err != nil { - logrus.Infof("Error while deleting the secret %v", err) + logrus.Errorf("Error while deleting the secret %v", err) } - logrus.Infof("Error while deleting the secret %v", err) + logrus.Errorf("Error while updating the secret %v", err) } time.Sleep(10 * time.Second) logrus.Infof("Deleting Secret %q.\n", secretName) err = controller.client.CoreV1().Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{}) if err != nil { - logrus.Infof("Error while deleting the secret %v", err) + logrus.Errorf("Error while deleting the secret %v", err) } time.Sleep(15 * time.Second) } diff --git a/internal/pkg/handler/created-handler.go b/internal/pkg/handler/created-handler.go index 53927afd..ac612c4b 100644 --- a/internal/pkg/handler/created-handler.go +++ b/internal/pkg/handler/created-handler.go @@ -22,7 +22,7 @@ func (r ResourceCreatedHandler) Handle() error { } else if _, ok := r.Resource.(*v1.Secret); ok { logrus.Infof("Performing 'Added' action for resource of type 'secret'") } else { - logrus.Infof("Invalid resource") + logrus.Warnf("Invalid resource: Resource should be 'Secret' or 'Configmap' but found %v", r.Resource) } } return nil diff --git a/internal/pkg/handler/updated-handler.go b/internal/pkg/handler/updated-handler.go index 478445db..97d4f7ba 100644 --- a/internal/pkg/handler/updated-handler.go +++ b/internal/pkg/handler/updated-handler.go @@ -23,7 +23,7 @@ func (r ResourceUpdatedHandler) Handle() error { } else if _, ok := r.Resource.(*v1.Secret); ok { logrus.Infof("Performing 'Updated' action for resource of type 'secret'") } else { - logrus.Infof("Invalid resource") + logrus.Warnf("Invalid resource: Resource should be 'Secret' or 'Configmap' but found %v", r.Resource) } } return nil