├── .docker └── config.json ├── install ├── namespace.yaml ├── serviceaccount.yaml ├── cronjob.yaml └── rbac.yaml ├── helm └── kubesweeper │ ├── templates │ ├── namespace.yaml │ ├── serviceaccount.yaml │ ├── service.yaml │ ├── kubesweepereventsource.yaml │ ├── rbac.yaml │ └── _helpers.tpl │ ├── Chart.yaml │ ├── values.yaml │ └── .helmignore ├── images └── cloud_native_labs.png ├── Makefile ├── .travis.yml ├── configs └── config.yaml ├── Dockerfile ├── deploy.sh ├── LICENSE ├── go.mod ├── config.go ├── .gitignore ├── README.md ├── go.sum └── main.go /.docker/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "credsStore": "pass" 3 | } 4 | -------------------------------------------------------------------------------- /install/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: att-cloudnative-labs -------------------------------------------------------------------------------- /helm/kubesweeper/templates/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: {{ .Values.namespace }} -------------------------------------------------------------------------------- /images/cloud_native_labs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/att-cloudnative-labs/kubesweeper/HEAD/images/cloud_native_labs.png -------------------------------------------------------------------------------- /install/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: kubesweeper 5 | namespace: att-cloudnative-labs -------------------------------------------------------------------------------- /helm/kubesweeper/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "0.0.1" 3 | description: A Helm chart for Kubernetes 4 | name: kubesweeper 5 | version: 0.0.1 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | docker build -t kubesweeper . 3 | helm template kubesweeper --set image=kubesweeper | kubectl create -f - 4 | go-build: 5 | go build 6 | -------------------------------------------------------------------------------- /helm/kubesweeper/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: {{ .Values.name }} 5 | namespace: {{ .Values.namespace }} -------------------------------------------------------------------------------- /helm/kubesweeper/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for kubesweeper. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | name: kubesweeper 6 | image: docker.io/att-cloudnative-labs/kubesweeper:v0.0.1-alpha 7 | cron: 0 17 * * * 8 | namespace: att-cloudnative-labs 9 | -------------------------------------------------------------------------------- /helm/kubesweeper/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: serving.knative.dev/v1alpha1 2 | kind: Service 3 | metadata: 4 | namespace: {{ .Values.namespace }} 5 | name: {{ .Values.name }} 6 | spec: 7 | runLatest: 8 | configuration: 9 | revisionTemplate: 10 | spec: 11 | container: 12 | image: {{ .Values.image }} 13 | -------------------------------------------------------------------------------- /helm/kubesweeper/templates/kubesweepereventsource.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: sources.eventing.knative.dev/v1alpha1 2 | kind: CronJobSource 3 | metadata: 4 | name: {{ .Values.name }}-source 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | namespace: {{ .Values.namespace }} 8 | schedule: {{ .Values.cron }} 9 | sink: 10 | apiVersion: serving.knative.dev/v1alpha1 11 | kind: Service 12 | name: {{ .Values.name }} 13 | -------------------------------------------------------------------------------- /helm/kubesweeper/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | addons: 3 | apt: 4 | packages: 5 | - pass 6 | 7 | go: 8 | - 1.12.x 9 | 10 | script: 11 | - env GO111MODULE=on go build 12 | - echo "$DOCKER_PASSWORD" | docker login docker.pkg.github.com -u "$DOCKER_USERNAME" --password-stdin 13 | - docker build -t docker.pkg.github.com/att-cloudnative-labs/kubesweeper/kubesweeper:latest . 14 | - docker push docker.pkg.github.com/att-cloudnative-labs/kubesweeper/kubesweeper:latest -------------------------------------------------------------------------------- /configs/config.yaml: -------------------------------------------------------------------------------- 1 | reasons: 2 | - reason: "CrashLoopBackOff" 3 | restart_threshold: 100 4 | delete_func_string: "DeleteCrash" 5 | - reason: "ImagePullBackOff" 6 | delete_func_string: "DeleteGeneric" 7 | - reason: "ErrImagePull" 8 | delete_func_string: "DeleteGeneric" 9 | - reason: "Completed" 10 | delete_func_string: "DeleteGeneric" 11 | - reason: "Failed" 12 | delete_func_string: "DeleteGeneric" 13 | day_limit: 90 14 | delete_ingresses: true 15 | delete_services: true 16 | delete_hpas: true 17 | excluded_namespaces: 18 | - exclude-me -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # First stage: Build the Go binary 2 | FROM golang:1.12.1 as builder 3 | 4 | # copy necessary files 5 | COPY /main.go /app/main.go 6 | COPY /config.go /app/config.go 7 | COPY /configs/ /app/configs/ 8 | COPY go.mod /app/go.mod 9 | COPY go.sum /app/go.sum 10 | 11 | WORKDIR /app 12 | 13 | RUN GIT_TERMINAL_PROMPT=1 GOARCH=amd64 GOOS=linux CGO_ENABLED=0 go build --installsuffix cgo --ldflags="-s" -o kubesweeper 14 | 15 | # The second stage: Just what's needed 16 | FROM alpine:3.8 17 | # copy the binary and the settings into the container 18 | COPY --from=builder /app/kubesweeper /app/kubesweeper 19 | COPY --from=builder /app/configs/ /app/configs/ 20 | 21 | WORKDIR /app 22 | 23 | # Run it 24 | ENTRYPOINT ["./kubesweeper"] -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | # init key for pass 6 | gpg --batch --gen-key <<-EOF ; echo $(gpg --no-auto-check-trustdb --list-secret-keys | grep ^sec | cut -d/ -f2 | cut -d" " -f1) ; pass init $(gpg --no-auto-check-trustdb --list-secret-keys | grep ^sec | cut -d/ -f2 | cut -d" " -f1) 7 | %echo Generating a standard key 8 | Key-Type: DSA 9 | Key-Length: 1024 10 | Subkey-Type: ELG-E 11 | Subkey-Length: 1024 12 | Name-Real: Meshuggah Rocks 13 | Name-Email: meshuggah@example.com 14 | Expire-Date: 0 15 | # Do a commit here, so that we can later print "done" :-) 16 | %commit 17 | %echo done 18 | EOF 19 | 20 | echo "$DOCKER_PASSWORD" | docker login docker.pkg.github.com --username "$DOCKER_USERNAME" --password-stdin 21 | 22 | if [ "$(command -v docker-credential-pass)" = "" ]; then 23 | docker run --rm -itv sh -c "cp /go/bin/docker-credential-pass /src" 24 | fi -------------------------------------------------------------------------------- /install/cronjob.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1beta1 2 | kind: CronJob 3 | metadata: 4 | name: kubesweeper 5 | namespace: att-cloudnative-labs 6 | spec: 7 | concurrencyPolicy: Allow 8 | failedJobsHistoryLimit: 1 9 | jobTemplate: 10 | spec: 11 | template: 12 | spec: 13 | serviceAccountName: kubesweeper 14 | containers: 15 | - args: 16 | image: docker.pkg.github.com/att-cloudnative-labs/kubesweeper/kubesweeper:latest 17 | imagePullPolicy: Always 18 | name: kubesweeper 19 | resources: {} 20 | terminationMessagePath: /dev/termination-log 21 | terminationMessagePolicy: File 22 | dnsPolicy: ClusterFirst 23 | restartPolicy: OnFailure 24 | schedulerName: default-scheduler 25 | securityContext: {} 26 | terminationGracePeriodSeconds: 30 27 | schedule: '0 10 * * *' 28 | successfulJobsHistoryLimit: 3 29 | suspend: false 30 | -------------------------------------------------------------------------------- /helm/kubesweeper/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: {{ .Values.name }} 5 | rules: 6 | - apiGroups: 7 | - "apps" 8 | resources: 9 | - deployments 10 | - replicasets 11 | - pods 12 | verbs: 13 | - get 14 | - list 15 | - watch 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - deployments 23 | - replicasets 24 | - pods 25 | verbs: 26 | - get 27 | - list 28 | - watch 29 | - update 30 | - patch 31 | - delete 32 | --- 33 | apiVersion: rbac.authorization.k8s.io/v1 34 | kind: ClusterRoleBinding 35 | metadata: 36 | name: {{ .Values.name }} 37 | roleRef: 38 | apiGroup: rbac.authorization.k8s.io 39 | kind: ClusterRole 40 | name: {{ .Values.name }} 41 | subjects: 42 | - kind: ServiceAccount 43 | name: {{ .Values.name }} 44 | namespace: {{ .Values.namespace }} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 AT&T Intellectual Property 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /helm/kubesweeper/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "kubernetes-deployment-crawler.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "kubernetes-deployment-crawler.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "kubernetes-deployment-crawler.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /install/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: kubesweeper 5 | rules: 6 | - apiGroups: 7 | - apps 8 | resources: 9 | - deployments 10 | - replicasets 11 | - pods 12 | verbs: 13 | - get 14 | - list 15 | - watch 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - deployments 23 | - replicasets 24 | - pods 25 | - ingresses 26 | - services 27 | verbs: 28 | - get 29 | - list 30 | - watch 31 | - update 32 | - patch 33 | - delete 34 | - apiGroups: 35 | - networking.k8s.io 36 | - extensions 37 | resources: 38 | - ingresses 39 | verbs: 40 | - get 41 | - list 42 | - watch 43 | - update 44 | - patch 45 | - delete 46 | - apiGroups: 47 | - autoscaling 48 | resources: 49 | - horizontalpodautoscalers 50 | verbs: 51 | - get 52 | - list 53 | - watch 54 | - update 55 | - patch 56 | - delete 57 | --- 58 | apiVersion: rbac.authorization.k8s.io/v1 59 | kind: ClusterRoleBinding 60 | metadata: 61 | name: kubesweeper 62 | roleRef: 63 | apiGroup: rbac.authorization.k8s.io 64 | kind: ClusterRole 65 | name: kubesweeper 66 | subjects: 67 | - kind: ServiceAccount 68 | name: kubesweeper 69 | namespace: att-cloudnative-labs -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/att-cloudnative-labs/kubesweeper 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/docker/docker-credential-helpers v0.6.3 // indirect 7 | github.com/ghodss/yaml v1.0.0 // indirect 8 | github.com/gogo/protobuf v1.2.1 // indirect 9 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect 10 | github.com/golang/protobuf v1.3.1 // indirect 11 | github.com/google/btree v1.0.0 // indirect 12 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect 13 | github.com/googleapis/gnostic v0.2.0 // indirect 14 | github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect 15 | github.com/json-iterator/go v1.1.6 // indirect 16 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 17 | github.com/modern-go/reflect2 v1.0.1 // indirect 18 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect 19 | github.com/spf13/viper v1.3.2 20 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c // indirect 21 | golang.org/x/net v0.0.0-20190328230028-74de082e2cca // indirect 22 | golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914 // indirect 23 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect 24 | gopkg.in/inf.v0 v0.9.1 // indirect 25 | k8s.io/api v0.0.0-20190222213804-5cb15d344471 26 | k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628 27 | k8s.io/client-go v10.0.0+incompatible 28 | k8s.io/klog v0.2.0 // indirect 29 | sigs.k8s.io/yaml v1.1.0 // indirect 30 | ) 31 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/spf13/viper" 5 | "log" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | // make this config object available outside 11 | var ConfigObj = KleanerConfig{} 12 | 13 | // This map would need to be updated to account for any other behavior we would want to enable 14 | // The keys in this map have to map directly to the DeleteFuncString in the SweeperConfigDetails struct 15 | var funcMap = map[string]DeleteFunc{ 16 | "DeleteCrash": DeleteCrash, 17 | "DeleteGeneric": DeleteGeneric, 18 | } 19 | 20 | /** 21 | * This is the parent config object 22 | */ 23 | type KleanerConfig struct { 24 | Reasons []SweeperConfigDetails `mapstructure:"reasons"` 25 | DayLimit int `mapstructure:"day_limit"` 26 | DeleteIngresses bool `mapstructure:"delete_ingresses"` 27 | DeleteServices bool `mapstructure:"delete_services"` 28 | DeleteHpas bool `mapstructure:"delete_hpas"` 29 | ExcludedNamespaces []string `mapstructure:"excluded_namespaces"` 30 | } 31 | 32 | /** 33 | * This is the object that holds the necessary information 34 | */ 35 | type SweeperConfigDetails struct { 36 | Reason string `mapstructure:"reason"` 37 | RestartThreshold int `mapstructure:"restart_threshold,omitempty"` 38 | DeleteFuncString string `mapstructure:"delete_func_string"` 39 | DeleteFunction DeleteFunc 40 | } 41 | 42 | func (dc *KleanerConfig) SetFunctions(fnMap map[string]DeleteFunc) { 43 | for i, reason := range dc.Reasons { 44 | // get the appropriate function out of the map 45 | val, ok := fnMap[reason.DeleteFuncString] 46 | if ok { 47 | // add the appropriate function call to the referenced object 48 | dc.Reasons[i].DeleteFunction = val 49 | } 50 | } 51 | } 52 | 53 | func init() { 54 | v := viper.New() 55 | 56 | // we'll read config in from YAML 57 | v.SetConfigType("yaml") 58 | 59 | // The config file name is config.yaml 60 | v.SetConfigName("config") 61 | 62 | // Just in case we want to set another directory via an environment variable 63 | configDir := os.Getenv("GO_CONFIG_DIR") 64 | if len(configDir) > 0 { 65 | v.AddConfigPath(configDir) 66 | } 67 | 68 | v.AddConfigPath("./configs") 69 | // read the config file into memory 70 | 71 | v.AutomaticEnv() 72 | v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 73 | 74 | err := v.ReadInConfig() 75 | if err != nil { 76 | log.Fatal(err.Error()) 77 | } 78 | 79 | // unmarshal yaml file into ConfigObj member 80 | err = v.Unmarshal(&ConfigObj) 81 | 82 | if err != nil { 83 | log.Fatal(err.Error()) 84 | } 85 | 86 | // this is necessary to assign the appropriate functions to the right objects 87 | ConfigObj.SetFunctions(funcMap) 88 | } 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Eclipse template 3 | 4 | .metadata 5 | bin/ 6 | tmp/ 7 | *.tmp 8 | *.bak 9 | *.swp 10 | *~.nib 11 | local.properties 12 | .settings/ 13 | .loadpath 14 | .recommenders 15 | 16 | # External tool builders 17 | .externalToolBuilders/ 18 | 19 | # Locally stored "Eclipse launch configurations" 20 | *.launch 21 | 22 | # PyDev specific (Python IDE for Eclipse) 23 | *.pydevproject 24 | 25 | # CDT-specific (C/C++ Development Tooling) 26 | .cproject 27 | 28 | # CDT- autotools 29 | .autotools 30 | 31 | # Java annotation processor (APT) 32 | .factorypath 33 | 34 | # PDT-specific (PHP Development Tools) 35 | .buildpath 36 | 37 | # sbteclipse plugin 38 | .target 39 | 40 | # Tern plugin 41 | .tern-project 42 | 43 | # TeXlipse plugin 44 | .texlipse 45 | 46 | # STS (Spring Tool Suite) 47 | .springBeans 48 | 49 | # Code Recommenders 50 | .recommenders/ 51 | 52 | # Annotation Processing 53 | .apt_generated/ 54 | 55 | # Scala IDE specific (Scala & Java development for Eclipse) 56 | .cache-main 57 | .scala_dependencies 58 | .worksheet 59 | ### Go template 60 | # Binaries for programs and plugins 61 | *.exe 62 | *.exe~ 63 | *.dll 64 | *.so 65 | *.dylib 66 | 67 | # Test binary, build with `go test -c` 68 | *.test 69 | 70 | # Output of the go coverage tool, specifically when used with LiteIDE 71 | *.out 72 | ### JetBrains template 73 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 74 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 75 | 76 | # User-specific stuff 77 | .idea/**/workspace.xml 78 | .idea/**/tasks.xml 79 | .idea/**/usage.statistics.xml 80 | .idea/**/dictionaries 81 | .idea/**/shelf 82 | 83 | # Sensitive or high-churn files 84 | .idea/**/dataSources/ 85 | .idea/**/dataSources.ids 86 | .idea/**/dataSources.local.xml 87 | .idea/**/sqlDataSources.xml 88 | .idea/**/dynamic.xml 89 | .idea/**/uiDesigner.xml 90 | .idea/**/dbnavigator.xml 91 | 92 | # Gradle 93 | .idea/**/gradle.xml 94 | .idea/**/libraries 95 | 96 | # Gradle and Maven with auto-import 97 | # When using Gradle or Maven with auto-import, you should exclude module files, 98 | # since they will be recreated, and may cause churn. Uncomment if using 99 | # auto-import. 100 | # .idea/modules.xml 101 | # .idea/*.iml 102 | # .idea/modules 103 | 104 | # CMake 105 | cmake-build-*/ 106 | 107 | # Mongo Explorer plugin 108 | .idea/**/mongoSettings.xml 109 | 110 | # File-based project format 111 | *.iws 112 | 113 | # IntelliJ 114 | out/ 115 | 116 | # mpeltonen/sbt-idea plugin 117 | .idea_modules/ 118 | 119 | # JIRA plugin 120 | atlassian-ide-plugin.xml 121 | 122 | # Cursive Clojure plugin 123 | .idea/replstate.xml 124 | 125 | # Crashlytics plugin (for Android Studio and IntelliJ) 126 | com_crashlytics_export_strings.xml 127 | crashlytics.properties 128 | crashlytics-build.properties 129 | fabric.properties 130 | 131 | # Editor-based Rest Client 132 | .idea/httpRequests 133 | ### VisualStudioCode template 134 | .vscode/* 135 | !.vscode/settings.json 136 | !.vscode/tasks.json 137 | !.vscode/launch.json 138 | !.vscode/extensions.json 139 | 140 | /.idea/ 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubesweeper 2 | 3 | Automatically iterates through resources in a lab Kubernetes cluster and acts according to [certain conditions outlined here](#configuration-defaults). As of now, Kubesweeper will delete ```Deployments``` and their associated resources if the waiting reason and/or pod restart count and/or deployment age dictates. Additionally, you can use configurable Boolean environment variables to choose to delete associated ```Services```, ```Ingresses```, and ```HorizontalPodAutoscalers```. 4 | 5 | If your lab Kubernetes clusters are filling up with non-Running pods, then Kubesweeper's automatic deletion 6 | can assist. Future iterations of this project can involve other actions based on crawling through Kubernetes cluster resources, such as generating reports per namespace without actually deleting. 7 | 8 | Please note that Kubesweeper is intended for use in lab—not production, customer-facing—clusters. Any automated cleanup in such an environment is unadvisable, at least for Kubesweeper. 9 | 10 |

11 | 12 | 13 | 14 | 15 |

16 |

17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |

33 |

34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |

44 | 45 | ## Deployment as a Kubernetes CronJob 46 | If the desired cluster does not have Knative installed, then Kubesweeper can be installed as a Kubernetes CronJob. 47 | 48 | 1. Build Docker image 49 | ```bash 50 | $ docker build -t kubesweeper . 51 | ``` 52 | 2. Create Kubernetes resources from ```install``` directory 53 | ```bash 54 | $ kubectl apply -f install/ 55 | ``` 56 | 57 | Note that step 2 must be run in the context of the Kubernetes cluster. After that command is run, the appropriate Kubernetes resources will be created from the .yaml files in ```install```. 58 | 59 | ## Deployment as a Knative CronJobSource 60 | If you wish to deploy Kubesweeper on Knative as a CronJobSource, you can use Helm. For information on installing Helm, please refer to the [Helm quickstart guide](https://helm.sh/docs/using_helm/). After installing Helm, the following steps can be manually run: 61 | 62 | 1. Build Docker image 63 | ```bash 64 | $ docker build -t kubesweeper . 65 | ``` 66 | 2. Run Helm template to install Kubesweeper 67 | ```bash 68 | $ helm template kubesweeper --set image= | kubectl create -f - 69 | ``` 70 | 71 | In lieu of step 2, a Makefile can be used to pull values from ```~/install/helm/kubesweeper/values.yaml```: 72 | 73 | ```bash 74 | $ make 75 | ``` 76 | 77 | ## Configuration Defaults 78 | 79 | Under the ```configs``` folder, the ```config.yaml``` has the following default configurations: 80 | 81 | * Pod waiting reasons 82 | * CrashLoopBackOff 83 | * ImagePullBackOff 84 | * ErrImagePull 85 | * Completed 86 | * Failed 87 | * Pod restart threshold *(in other words, if pod restarts exceed this number, then delete)* 88 | * 100 89 | * If the pod restart threshold is at least this number *and* has a pod waiting reason of ```CrashLoopBackOff```, then Kubesweeper will delete the associated resources 90 | * Deployment age threshold *(in other words, if the deployment creation date is before this number of days, then delete)* 91 | * 90 92 | 93 | You are able to configure these values to your choosing. 94 | 95 | Helm function configurations can be found in ```~/install/helm/kubesweeper/values.yaml```. 96 | 97 | * name 98 | * Name to use for deployment 99 | * image 100 | * Image used in deployment 101 | * cron 102 | * Cron expression used to schedule Kubesweeper 103 | * Any valid cron expression can be used 104 | * namespace 105 | * Namespace job will be deployed in 106 | 107 | ## Contributing 108 | 109 | 1. [Fork Kubesweeper](https://github.com/att-cloudnative-labs/kubesweeper/fork) 110 | 2. Create your feature branch (`git checkout -b feature/fooBar`) 111 | 3. Commit your changes (`git commit -am 'Add some fooBar'`) 112 | 4. Push to the branch (`git push origin feature/fooBar`) 113 | 5. Create a new Pull Request 114 | 115 | ## Additional info 116 | 117 |

118 | 119 | 120 | 121 |

122 | 123 | Maintained and in-use by the Platform Team @ AT&T Entertainment Cloud Native Labs. 124 | 125 | Distributed under the AT&T MIT license. See ``LICENSE`` for more information. 126 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 3 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 4 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 5 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= 9 | github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= 10 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 11 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 12 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 13 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 14 | github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= 15 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 16 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 17 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 18 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 19 | github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= 20 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 21 | github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= 22 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 23 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= 24 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 25 | github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= 26 | github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 27 | github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q= 28 | github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 29 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 30 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 31 | github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= 32 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 33 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 34 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 35 | github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= 36 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 37 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 38 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 39 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 40 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 41 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 42 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 43 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 44 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 45 | github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= 46 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 47 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 48 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 49 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 50 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 51 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 52 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 53 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 54 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 55 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 56 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 57 | github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= 58 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 59 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 60 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 61 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 62 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 63 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 64 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 65 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= 66 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 67 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 68 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 69 | golang.org/x/net v0.0.0-20190328230028-74de082e2cca h1:hyA6yiAgbUwuWqtscNvWAI7U1CtlaD1KilQ6iudt1aI= 70 | golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 71 | golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914 h1:jIOcLT9BZzyJ9ce+IwwZ+aF9yeCqzrR+NrD68a/SHKw= 72 | golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 73 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 74 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 75 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 76 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 77 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 78 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 79 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= 80 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 81 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 82 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 83 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 84 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 85 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 86 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 87 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 88 | k8s.io/api v0.0.0-20190111032252-67edc246be36 h1:XrFGq/4TDgOxYOxtNROTyp2ASjHjBIITdk/+aJD+zyY= 89 | k8s.io/api v0.0.0-20190111032252-67edc246be36/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= 90 | k8s.io/api v0.0.0-20190222213804-5cb15d344471 h1:MzQGt8qWQCR+39kbYRd0uQqsvSidpYqJLFeWiJ9l4OE= 91 | k8s.io/api v0.0.0-20190222213804-5cb15d344471/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= 92 | k8s.io/apimachinery v0.0.0-20190116203031-d49e237a2683 h1:BmOSGJ1vwLhZKgDhTQlLF66TFOlrTio3qPUSimziTdw= 93 | k8s.io/apimachinery v0.0.0-20190116203031-d49e237a2683/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= 94 | k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628 h1:UYfHH+KEF88OTg+GojQUwFTNxbxwmoktLwutUzR0GPg= 95 | k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= 96 | k8s.io/client-go v10.0.0+incompatible h1:F1IqCqw7oMBzDkqlcBymRq1450wD0eNqLE9jzUrIi34= 97 | k8s.io/client-go v10.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= 98 | k8s.io/klog v0.2.0 h1:0ElL0OHzF3N+OhoJTL0uca20SxtYt4X4+bzHeqrB83c= 99 | k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 100 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 101 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 102 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "time" 7 | 8 | v1 "k8s.io/api/apps/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/client-go/kubernetes" 11 | "k8s.io/client-go/rest" 12 | ) 13 | 14 | var ( 15 | deploymentsDeleted = 0 16 | ingressesDeleted = 0 17 | servicesDeleted = 0 18 | hpasDeleted = 0 19 | ) 20 | 21 | type bin int 22 | 23 | func (b bin) String() string { 24 | return fmt.Sprintf("%b", b) 25 | } 26 | 27 | func Find(slice []string, val string) (int, bool) { 28 | for i, item := range slice { 29 | if item == val { 30 | return i, true 31 | } 32 | } 33 | return -1, false 34 | } 35 | 36 | // Determines which delete function to use 37 | type DeleteFunc func(clientset *kubernetes.Clientset, deployment *v1.Deployment, restarts int, restartThreshold int) (bool, error) 38 | 39 | // Pod is in a CrashLoopBackOff state 40 | // Check if restart number meets or exceeds the restart threshold 41 | // The restart threshold will be 0 if not specified in the config, so have to handle that case 42 | func DeleteCrash(clientset *kubernetes.Clientset, deployment *v1.Deployment, restarts int, restartThreshold int) (bool, error) { 43 | if restartThreshold > 0 && restarts >= restartThreshold { 44 | return DeleteGeneric(clientset, deployment, restarts, restartThreshold) 45 | } else { 46 | fmt.Printf("%s/%s is in a CrashLoopBackOff state, but doesn't meet the restart threshold. "+ 47 | "Restarts/Threshold = %v/%v\n", deployment.Namespace, deployment.Name, restarts, restartThreshold) 48 | return false, nil 49 | } 50 | } 51 | 52 | // Pod is in a state defined in config.yaml 53 | func DeleteGeneric(clientset *kubernetes.Clientset, deployment *v1.Deployment, restarts int, restartThreshold int) (bool, error) { 54 | policy := metav1.DeletePropagationForeground 55 | gracePeriodSeconds := int64(0) 56 | fmt.Printf("About to delete %s/%s and its associated resources.\n", deployment.Namespace, deployment.Name) 57 | err := clientset.AppsV1().Deployments(deployment.Namespace).Delete(deployment.Name, &metav1.DeleteOptions{PropagationPolicy: &policy, GracePeriodSeconds: &gracePeriodSeconds}) 58 | if err != nil { 59 | fmt.Printf("%s/%s, Error: %s \n", deployment.Namespace, deployment.Name, err.Error()) 60 | return false, err 61 | } 62 | deploymentsDeleted++ 63 | _, err = DeleteLeftoverResources(clientset, deployment) 64 | if err != nil { 65 | fmt.Printf("%s/%s, Error: %s \n", deployment.Namespace, deployment.Name, err.Error()) 66 | return false, err 67 | } 68 | fmt.Printf("Deleted %s/%s and its associated resources.\n", deployment.Namespace, deployment.Name) 69 | 70 | return true, nil 71 | } 72 | 73 | // Delete--if desired--Ingresses, Services, and HorizontalPodAutoscalers 74 | func DeleteLeftoverResources(clientset *kubernetes.Clientset, deployment *v1.Deployment) (bool, error) { 75 | var kleanerConfig = ConfigObj 76 | 77 | if kleanerConfig.DeleteIngresses { 78 | _, err := DeleteIngress(clientset, deployment) 79 | if err != nil { 80 | fmt.Printf("%s/%s, Error calling DeleteIngress: %s \n", deployment.Namespace, deployment.Name, err.Error()) 81 | return false, err 82 | } 83 | ingressesDeleted++ 84 | } 85 | 86 | if kleanerConfig.DeleteServices { 87 | _, err := DeleteService(clientset, deployment) 88 | if err != nil { 89 | fmt.Printf("%s/%s, Error calling DeleteService: %s \n", deployment.Namespace, deployment.Name, err.Error()) 90 | return false, err 91 | } 92 | servicesDeleted++ 93 | } 94 | 95 | if kleanerConfig.DeleteHpas { 96 | _, err := DeleteHpa(clientset, deployment) 97 | if err != nil { 98 | fmt.Printf("%s/%s, Error calling DeleteHpa: %s \n", deployment.Namespace, deployment.Name, err.Error()) 99 | return false, err 100 | } 101 | hpasDeleted++ 102 | } 103 | 104 | return true, nil 105 | } 106 | 107 | // Delete Ingress resource associated with Deployment 108 | func DeleteIngress(clientset *kubernetes.Clientset, deployment *v1.Deployment) (bool, error) { 109 | policy := metav1.DeletePropagationForeground 110 | gracePeriodSeconds := int64(0) 111 | fmt.Printf("About to delete the ingress of %s/%s.\n", deployment.Namespace, deployment.Name) 112 | 113 | err := clientset.ExtensionsV1beta1().Ingresses(deployment.Namespace).Delete(deployment.Name, &metav1.DeleteOptions{PropagationPolicy: &policy, GracePeriodSeconds: &gracePeriodSeconds}) 114 | if err != nil { 115 | fmt.Printf("%s/%s, Error deleting ingress: %s \n", deployment.Namespace, deployment.Name, err.Error()) 116 | return false, err 117 | } 118 | fmt.Printf("Deleted the ingress of %s/%s.\n", deployment.Namespace, deployment.Name) 119 | 120 | return true, nil 121 | } 122 | 123 | // Delete Service resource associated with Deployment 124 | func DeleteService(clientset *kubernetes.Clientset, deployment *v1.Deployment) (bool, error) { 125 | policy := metav1.DeletePropagationForeground 126 | gracePeriodSeconds := int64(0) 127 | fmt.Printf("About to delete the service of %s/%s.\n", deployment.Namespace, deployment.Name) 128 | 129 | err := clientset.CoreV1().Services(deployment.Namespace).Delete(deployment.Name, &metav1.DeleteOptions{PropagationPolicy: &policy, GracePeriodSeconds: &gracePeriodSeconds}) 130 | if err != nil { 131 | fmt.Printf("%s/%s, Error deleting service: %s \n", deployment.Namespace, deployment.Name, err.Error()) 132 | return false, err 133 | } 134 | fmt.Printf("Deleted the service of %s/%s.\n", deployment.Namespace, deployment.Name) 135 | 136 | return true, nil 137 | } 138 | 139 | // Delete HorizontalPodAutoscaler resource associated with Deployment 140 | func DeleteHpa(clientset *kubernetes.Clientset, deployment *v1.Deployment) (bool, error) { 141 | policy := metav1.DeletePropagationForeground 142 | gracePeriodSeconds := int64(0) 143 | fmt.Printf("About to delete the HPA of %s/%s.\n", deployment.Namespace, deployment.Name) 144 | 145 | err := clientset.AutoscalingV1().HorizontalPodAutoscalers(deployment.Namespace).Delete(deployment.Name, &metav1.DeleteOptions{PropagationPolicy: &policy, GracePeriodSeconds: &gracePeriodSeconds}) 146 | if err != nil { 147 | fmt.Printf("%s/%s, Error deleting HPA: %s \n", deployment.Namespace, deployment.Name, err.Error()) 148 | return false, err 149 | } 150 | fmt.Printf("Deleted the HPA of %s/%s.\n", deployment.Namespace, deployment.Name) 151 | 152 | return true, nil 153 | } 154 | 155 | func main() { 156 | var kleanerConfig = ConfigObj // initialize the config, from yaml or environment variables 157 | 158 | var waitingReasons = make(map[string]SweeperConfigDetails) // create the map that will hold the reasons and the config object 159 | 160 | for _, conf := range kleanerConfig.Reasons { // fill the map 161 | waitingReasons[conf.Reason] = conf 162 | } 163 | 164 | config, err := rest.InClusterConfig() // fail fast if not in the cluster 165 | if err != nil { 166 | panic(err.Error()) 167 | } 168 | 169 | clientset, err := kubernetes.NewForConfig(config) 170 | if err != nil { 171 | panic(err.Error()) 172 | } 173 | 174 | fmt.Println("\n+============ Beginning the sweep. ============+") 175 | 176 | pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{}) // get a list of pods for all namespaces 177 | if err != nil { 178 | panic(err.Error()) 179 | } 180 | 181 | for _, pod := range pods.Items { 182 | StatusLoop: 183 | for _, status := range pod.Status.ContainerStatuses { 184 | _, found := Find(kleanerConfig.ExcludedNamespaces, pod.Namespace) // CHECK #1: Deployment age 185 | if found { 186 | fmt.Println("Pod's namespace is excluded from deletion.") 187 | continue 188 | } 189 | 190 | rs, err := clientset.AppsV1().ReplicaSets(pod.Namespace).Get(pod.OwnerReferences[0].Name, metav1.GetOptions{}) 191 | if err != nil { 192 | fmt.Printf("Error retrieving ReplicaSets. Error: %s\n", err.Error()) 193 | continue StatusLoop 194 | } 195 | deploy, err := clientset.AppsV1().Deployments(pod.Namespace).Get(rs.OwnerReferences[0].Name, metav1.GetOptions{}) 196 | if err != nil { 197 | fmt.Printf("Error retrieving Deployments. Error: %s\n", err.Error()) 198 | continue StatusLoop 199 | } 200 | if deploy != nil && deploy.Name != "" { 201 | fmt.Println(deploy.GetNamespace() + "/" + deploy.GetName()) 202 | fmt.Println(deploy.GetCreationTimestamp()) 203 | if pod.GetCreationTimestamp().AddDate(0, 0, kleanerConfig.DayLimit).Before(time.Now()) { 204 | fmt.Println("I found an old deployment past " + strconv.Itoa(kleanerConfig.DayLimit) + " days!") 205 | _, err = DeleteGeneric(clientset, deploy, int(status.RestartCount), 0) 206 | } 207 | } 208 | 209 | waiting := status.State.Waiting // CHECK #2: Pod "Waiting" state 210 | if waiting != nil { // There is a bad pod state 211 | reason := waiting.Reason 212 | if SweeperConfigDetails, ok := waitingReasons[reason]; ok { 213 | fmt.Printf("Waiting reason match. %s/%s has a waiting reason of: %s\n", pod.Namespace, 214 | pod.OwnerReferences[0].Name, reason) 215 | rs, err := clientset.AppsV1().ReplicaSets(pod.Namespace).Get(pod.OwnerReferences[0].Name, metav1.GetOptions{}) 216 | if err != nil { 217 | fmt.Printf("Error retrieving ReplicaSets. Error: %s\n", err.Error()) 218 | continue StatusLoop 219 | } 220 | if rs.OwnerReferences != nil { 221 | deploy, err := clientset.AppsV1().Deployments(pod.Namespace).Get(rs.OwnerReferences[0].Name, metav1.GetOptions{}) 222 | if err != nil { 223 | fmt.Printf("Error retrieving Deployments. Error: %s\n", err.Error()) 224 | continue StatusLoop 225 | } 226 | if deploy != nil && deploy.Name != "" { // indicates something to be deleted 227 | _, err = SweeperConfigDetails.DeleteFunction(clientset, deploy, int(status.RestartCount), SweeperConfigDetails.RestartThreshold) 228 | if err != nil { 229 | fmt.Printf("Error deleting Deployment. Error: %s\n", err.Error()) 230 | continue StatusLoop 231 | } 232 | } else { 233 | fmt.Println("No deployment found.") 234 | } 235 | } else { 236 | fmt.Println("No replica set owner reference.") 237 | } 238 | } 239 | } 240 | } 241 | } 242 | fmt.Println("\n+============ KUBESWEEPER SUMMARY ============+") 243 | fmt.Println("\nNumber of deployments, replica sets, and pods deleted: " + strconv.Itoa(deploymentsDeleted)) 244 | fmt.Println("\nNumber of ingresses deleted: " + strconv.Itoa(ingressesDeleted)) 245 | fmt.Println("\nNumber of services deleted: " + strconv.Itoa(servicesDeleted)) 246 | fmt.Println("\nNumber of HPAs deleted: " + strconv.Itoa(hpasDeleted)) 247 | fmt.Println("\n+==============================================+") 248 | deploymentsDeleted = 0 249 | ingressesDeleted = 0 250 | servicesDeleted = 0 251 | hpasDeleted = 0 252 | } 253 | --------------------------------------------------------------------------------