From 5920890fa3d5c1249030ab30916c2cda003c4fa5 Mon Sep 17 00:00:00 2001 From: faizanahmad055 Date: Fri, 6 Jul 2018 20:26:29 +0500 Subject: [PATCH] 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 0000000..f83e763 --- /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 0000000..afaf360 --- /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 0000000..8d76442 --- /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 0000000..28e821b --- /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 0000000..5314683 --- /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 0000000..a702332 --- /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 0000000..319501a --- /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 0000000..5f00be6 --- /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 0000000..64ce140 --- /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 0000000..78bac1e --- /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 0000000..4b48de4 --- /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 0000000..478976b --- /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 0000000..3596df2 --- /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 0000000..7857171 --- /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 0000000..e30e7c8 --- /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