MVP
This commit is contained in:
parent
248318571f
commit
11bd001494
15 changed files with 634 additions and 37 deletions
60
.github/workflows/go.yml
vendored
Normal file
60
.github/workflows/go.yml
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '^1.18'
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||
version: latest
|
||||
|
||||
# Optional: working directory, useful for monorepos
|
||||
# working-directory: somedir
|
||||
|
||||
# Optional: golangci-lint command line arguments.
|
||||
# args: --issues-exit-code=0
|
||||
|
||||
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||
# only-new-issues: true
|
||||
|
||||
# Optional: if set to true then the action will use pre-installed Go.
|
||||
# skip-go-installation: true
|
||||
|
||||
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
|
||||
# skip-pkg-cache: true
|
||||
|
||||
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
|
||||
# skip-build-cache: true
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -15,3 +15,4 @@
|
|||
# vendor/
|
||||
|
||||
.idea/
|
||||
dist/
|
||||
|
|
147
.golangci.yml
Normal file
147
.golangci.yml
Normal file
|
@ -0,0 +1,147 @@
|
|||
linters-settings:
|
||||
dupl:
|
||||
threshold: 100
|
||||
funlen:
|
||||
lines: 100
|
||||
statements: 50
|
||||
gci:
|
||||
local-prefixes: github.com/baez90/kreaper
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 2
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- opinionated
|
||||
- performance
|
||||
disabled-checks:
|
||||
- ifElseChain
|
||||
- octalLiteral
|
||||
- wrapperFunc
|
||||
# see https://github.com/golangci/golangci-lint/issues/2649
|
||||
- hugeParam
|
||||
- rangeValCopy
|
||||
# settings:
|
||||
# hugeParam:
|
||||
# sizeThreshold: 200
|
||||
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
goimports:
|
||||
local-prefixes: github.com/baez90/kreaper
|
||||
golint:
|
||||
min-confidence: 0
|
||||
gomnd:
|
||||
settings:
|
||||
mnd:
|
||||
# don't include the "operation" and "assign"
|
||||
checks:
|
||||
- argument
|
||||
- case
|
||||
- condition
|
||||
- return
|
||||
gomoddirectives:
|
||||
replace-allow-list:
|
||||
# pin versions
|
||||
- k8s.io/api
|
||||
- k8s.io/apiextensions-apiserver
|
||||
- k8s.io/apimachinery
|
||||
- k8s.io/client-go
|
||||
- k8s.io/component-base
|
||||
govet:
|
||||
check-shadowing: true
|
||||
enable-all: true
|
||||
disable:
|
||||
- fieldalignment
|
||||
# see https://github.com/golangci/golangci-lint/issues/2649
|
||||
- nilness
|
||||
- unusedwrite
|
||||
importas:
|
||||
no-unaliased: true
|
||||
alias:
|
||||
- pkg: (k8s.io/api|k8s.io/apimachinery/pkg/apis)/([A-z0-9]+)/([A-z0-9]+)
|
||||
alias: $2$3
|
||||
lll:
|
||||
line-length: 140
|
||||
misspell:
|
||||
locale: US
|
||||
nolintlint:
|
||||
allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
|
||||
allow-unused: false # report any unused nolint directives
|
||||
require-explanation: false # don't require an explanation for nolint directives
|
||||
require-specific: true
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- contextcheck
|
||||
- deadcode
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
- errchkjson
|
||||
- errname
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
- funlen
|
||||
- gocognit
|
||||
- goconst
|
||||
# - gocritic
|
||||
- gocyclo
|
||||
- godox
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gomoddirectives
|
||||
- gomnd
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ifshort
|
||||
- importas
|
||||
- ineffassign
|
||||
# - ireturn - enable later
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- nestif
|
||||
- nilnil
|
||||
- noctx
|
||||
- nolintlint
|
||||
- paralleltest
|
||||
- prealloc
|
||||
- predeclared
|
||||
- promlinter
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- stylecheck
|
||||
- tenv
|
||||
- testpackage
|
||||
- thelper
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- varcheck
|
||||
- whitespace
|
||||
# - unused
|
||||
- wastedassign
|
||||
|
||||
issues:
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- dupl
|
||||
- funlen
|
||||
- gocognit
|
||||
- gomnd
|
||||
- govet
|
||||
- path: magefiles/
|
||||
linters:
|
||||
- deadcode
|
||||
|
||||
run:
|
||||
skip-files:
|
||||
- ".*.mock.\\.go$"
|
||||
modules-download-mode: readonly
|
||||
timeout: 5m
|
45
.goreleaser.yaml
Normal file
45
.goreleaser.yaml
Normal file
|
@ -0,0 +1,45 @@
|
|||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
builds:
|
||||
- id: kreaper
|
||||
binary: kreaper
|
||||
flags:
|
||||
- -trimpath
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
archives:
|
||||
- builds:
|
||||
- kreaper
|
||||
replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
386: i386
|
||||
amd64: x86_64
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ incpatch .Version }}-next"
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
|
||||
dockers:
|
||||
- ids:
|
||||
- kreaper
|
||||
image_templates:
|
||||
- ghcr.io/baez90/kreaper:latest
|
||||
- ghcr.io/baez90/kreaper:{{ .Tag }}
|
||||
- ghcr.io/baez90/kreaper:{{ .Major }}
|
||||
- ghcr.io/baez90/kreaper:{{ .ShortCommit}}
|
||||
dockerfile: deployments/Dockerfile
|
28
Tiltfile
Normal file
28
Tiltfile
Normal file
|
@ -0,0 +1,28 @@
|
|||
# -*- mode: Python -*-
|
||||
|
||||
local_resource(
|
||||
'build',
|
||||
'CGO_ENABLED=0 go build -trimpath -ldflags "-w -s" -installsuffix cgo -o dist/kreaper main.go',
|
||||
deps=['main.go', './reaper'],
|
||||
)
|
||||
|
||||
debug_dockerfile = """
|
||||
FROM docker.io/alpine:3.15
|
||||
|
||||
COPY kreaper /usr/local/bin/
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/kreaper"]
|
||||
"""
|
||||
|
||||
custom_build(
|
||||
'kreaper',
|
||||
'docker build -f deployments/Dockerfile -t $EXPECTED_REF --build-arg BASE="docker.io/alpine:3.15" ./dist/',
|
||||
entrypoint='/usr/local/bin/kreaper',
|
||||
deps=['./dist/kreaper'],
|
||||
live_update=[
|
||||
sync('./dist/kreaper', '/usr/local/bin/kreaper'),
|
||||
]
|
||||
)
|
||||
|
||||
k8s_yaml(['testdata/target_pod.yaml', 'testdata/deployment.yaml'])
|
||||
k8s_resource('kreaper', resource_deps=['build'])
|
7
deployments/Dockerfile
Normal file
7
deployments/Dockerfile
Normal file
|
@ -0,0 +1,7 @@
|
|||
ARG BASE="gcr.io/distroless/static:nonroot"
|
||||
|
||||
FROM $BASE
|
||||
|
||||
COPY kreaper /usr/local/bin/
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/kreaper"]
|
18
go.mod
18
go.mod
|
@ -3,7 +3,9 @@ module github.com/baez90/kreaper
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
go.uber.org/zap v1.21.0
|
||||
k8s.io/api v0.23.5
|
||||
k8s.io/apimachinery v0.23.5
|
||||
k8s.io/client-go v0.23.5
|
||||
sigs.k8s.io/controller-runtime v0.11.2
|
||||
)
|
||||
|
@ -17,10 +19,14 @@ require (
|
|||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
|
||||
golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8 // indirect
|
||||
|
@ -32,8 +38,6 @@ require (
|
|||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
k8s.io/api v0.23.5 // indirect
|
||||
k8s.io/apimachinery v0.23.5 // indirect
|
||||
k8s.io/klog/v2 v2.30.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
|
||||
k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect
|
||||
|
@ -43,9 +47,9 @@ require (
|
|||
)
|
||||
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.23.1
|
||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.1
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.23.1
|
||||
k8s.io/client-go => k8s.io/client-go v0.23.1
|
||||
k8s.io/component-base => k8s.io/component-base v0.23.1
|
||||
k8s.io/api => k8s.io/api v0.23.5
|
||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.5
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.23.5
|
||||
k8s.io/client-go => k8s.io/client-go v0.23.5
|
||||
k8s.io/component-base => k8s.io/component-base v0.23.5
|
||||
)
|
||||
|
|
33
go.sum
33
go.sum
|
@ -49,6 +49,8 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0
|
|||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
|
@ -175,6 +177,7 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
|||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
|
@ -215,6 +218,7 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
|
|||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
@ -248,9 +252,16 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
|
||||
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
@ -606,13 +617,13 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.23.1 h1:ncu/qfBfUoClqwkTGbeRqqOqBCRoUAflMuOaOD7J0c8=
|
||||
k8s.io/api v0.23.1/go.mod h1:WfXnOnwSqNtG62Y1CdjoMxh7r7u9QXGCkA1u0na2jgo=
|
||||
k8s.io/apiextensions-apiserver v0.23.1 h1:xxE0q1vLOVZiWORu1KwNRQFsGWtImueOrqSl13sS5EU=
|
||||
k8s.io/apimachinery v0.23.1 h1:sfBjlDFwj2onG0Ijx5C+SrAoeUscPrmghm7wHP+uXlo=
|
||||
k8s.io/apimachinery v0.23.1/go.mod h1:SADt2Kl8/sttJ62RRsi9MIV4o8f5S3coArm0Iu3fBno=
|
||||
k8s.io/client-go v0.23.1 h1:Ma4Fhf/p07Nmj9yAB1H7UwbFHEBrSPg8lviR24U2GiQ=
|
||||
k8s.io/client-go v0.23.1/go.mod h1:6QSI8fEuqD4zgFK0xbdwfB/PthBsIxCJMa3s17WlcO0=
|
||||
k8s.io/api v0.23.5 h1:zno3LUiMubxD/V1Zw3ijyKO3wxrhbUF1Ck+VjBvfaoA=
|
||||
k8s.io/api v0.23.5/go.mod h1:Na4XuKng8PXJ2JsploYYrivXrINeTaycCGcYgF91Xm8=
|
||||
k8s.io/apiextensions-apiserver v0.23.5 h1:5SKzdXyvIJKu+zbfPc3kCbWpbxi+O+zdmAJBm26UJqI=
|
||||
k8s.io/apimachinery v0.23.5 h1:Va7dwhp8wgkUPWsEXk6XglXWU4IKYLKNlv8VkX7SDM0=
|
||||
k8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM=
|
||||
k8s.io/client-go v0.23.5 h1:zUXHmEuqx0RY4+CsnkOn5l0GU+skkRXKGJrhmE2SLd8=
|
||||
k8s.io/client-go v0.23.5/go.mod h1:flkeinTO1CirYgzMPRWxUCnV0G4Fbu2vLhYCObnt/r4=
|
||||
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
||||
|
@ -621,7 +632,6 @@ k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
|||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4=
|
||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk=
|
||||
k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5fZ/shhbrgPUxDAE=
|
||||
k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
@ -632,7 +642,6 @@ sigs.k8s.io/controller-runtime v0.11.2/go.mod h1:P6QCzrEjLaZGqHsfd+os7JQ+WFZhvB8
|
|||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s=
|
||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
|
|
96
main.go
96
main.go
|
@ -3,9 +3,15 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
|
@ -14,36 +20,89 @@ import (
|
|||
"github.com/baez90/kreaper/reaper"
|
||||
)
|
||||
|
||||
type ReaperTarget string
|
||||
const defaultKreaperLifetime = 5 * time.Minute
|
||||
|
||||
var (
|
||||
kubeconfig string
|
||||
target reaper.Target
|
||||
lifetime time.Duration
|
||||
dryRun bool
|
||||
logLevel *zapcore.Level
|
||||
kreaper = reaper.Kreaper{
|
||||
Target: lookupEnvOr[reaper.Target]("KREAPER_TARGET", "", reaper.ParseTarget),
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
prepareFlags()
|
||||
setupLogging()
|
||||
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
|
||||
|
||||
if err := run(ctx); err != nil {
|
||||
cancel()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run(ctx context.Context) error {
|
||||
logger := zap.L()
|
||||
|
||||
restCfg, err := loadRestConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
logger.Error("Failed to get cluster config", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
k8sClient, err := client.NewWithWatch(restCfg, client.Options{})
|
||||
|
||||
labels := client.MatchingLabels{
|
||||
"from": "value",
|
||||
if kreaper.Client, err = client.NewWithWatch(restCfg, client.Options{}); err != nil {
|
||||
logger.Error("failed to prepare Kubernetes API client", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
k8sClient.Watch(context.Background(), nil, labels)
|
||||
|
||||
return kreaper.Kill(ctx)
|
||||
}
|
||||
|
||||
func setupLogging() {
|
||||
cfg := zap.NewProductionConfig()
|
||||
cfg.Level = zap.NewAtomicLevelAt(*logLevel)
|
||||
logger, err := cfg.Build()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to setup logging: %v", err)
|
||||
}
|
||||
|
||||
zap.ReplaceGlobals(logger)
|
||||
}
|
||||
|
||||
func prepareFlags() {
|
||||
flag.Var(&target, "target", "Target that should be monitored")
|
||||
flag.DurationVar(&lifetime, "lifetime", 5*time.Minute, "Lifetime after which all matching targets will be deleted")
|
||||
logLevel = zap.LevelFlag("log-level", zapcore.InfoLevel, "Log level to use")
|
||||
flag.Var(&kreaper.Target, "target", "Target that should be monitored")
|
||||
|
||||
flag.BoolVar(
|
||||
&dryRun,
|
||||
"dry-run",
|
||||
lookupEnvOr("KREAPER_DRY_RUN", false, strconv.ParseBool),
|
||||
"Don't actually delete anything but only list all found pods matching the target - env variable: KREAPER_DRY_RUN",
|
||||
)
|
||||
|
||||
flag.DurationVar(
|
||||
&kreaper.Lifetime,
|
||||
"lifetime",
|
||||
lookupEnvOr("KREAPER_LIFETIME", defaultKreaperLifetime, time.ParseDuration),
|
||||
"Lifetime after which all matching targets will be deleted - env variable: KREAPER_LIFETIME",
|
||||
)
|
||||
|
||||
flag.StringVar(
|
||||
&kreaper.TargetNamespace,
|
||||
"target-namespace",
|
||||
lookupEnvOr("KREAPER_TARGET_NAMESPACE", "default", identity[string]),
|
||||
"Set target namespace in which kreaper will look for pods - env variable: KREAPER_TARGET_NAMESPACE",
|
||||
)
|
||||
|
||||
if home := homedir.HomeDir(); home != "" {
|
||||
flag.StringVar(&kubeconfig, "kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
|
||||
} else {
|
||||
flag.StringVar(&kubeconfig, "kubeconfig", "", "absolute path to the kubeconfig file")
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func loadRestConfig() (cfg *rest.Config, err error) {
|
||||
|
@ -53,3 +112,20 @@ func loadRestConfig() (cfg *rest.Config, err error) {
|
|||
|
||||
return clientcmd.BuildConfigFromFlags("", kubeconfig)
|
||||
}
|
||||
|
||||
func lookupEnvOr[T any](envKey string, fallback T, parse func(envVal string) (T, error)) T {
|
||||
envVal := os.Getenv(envKey)
|
||||
if envVal == "" {
|
||||
return fallback
|
||||
}
|
||||
|
||||
if parsed, err := parse(envVal); err != nil {
|
||||
return fallback
|
||||
} else {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
|
||||
func identity[T any](in T) (T, error) {
|
||||
return in, nil
|
||||
}
|
||||
|
|
113
reaper/reaper.go
113
reaper/reaper.go
|
@ -1,8 +1,117 @@
|
|||
package reaper
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
type Reaper struct {
|
||||
"go.uber.org/zap"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
type Kreaper struct {
|
||||
labelSelector labels.Selector
|
||||
Client client.WithWatch
|
||||
Lifetime time.Duration
|
||||
Target Target
|
||||
TargetNamespace string
|
||||
}
|
||||
|
||||
func (k Kreaper) Kill(ctx context.Context) (err error) {
|
||||
var (
|
||||
logger = zap.L()
|
||||
podList corev1.PodList
|
||||
)
|
||||
|
||||
if k.labelSelector, err = k.Target.Selector(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts := []client.ListOption{
|
||||
client.MatchingLabelsSelector{Selector: k.labelSelector},
|
||||
client.InNamespace(k.TargetNamespace),
|
||||
}
|
||||
|
||||
if err = k.Client.List(ctx, &podList, opts...); err != nil {
|
||||
logger.Error("failed to list", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if len(podList.Items) < 1 {
|
||||
logger.Warn("No pod targets found")
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := range podList.Items {
|
||||
logger.Info("Found pod", zap.String("pod_name", podList.Items[i].Name))
|
||||
}
|
||||
|
||||
watcher, err := k.Client.Watch(ctx, &podList, opts...)
|
||||
if err != nil {
|
||||
logger.Error("failed to setup watch", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
defer watcher.Stop()
|
||||
done, err := k.startPodWatcher(ctx, podList, opts)
|
||||
|
||||
select {
|
||||
case <-time.After(k.Lifetime):
|
||||
logger.Info("Reached end of lifetime force delete all matching pods")
|
||||
if err := k.forceDeleteAll(ctx); err != nil {
|
||||
logger.Error("Failed to delete all pods", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case <-done:
|
||||
logger.Info("All pods deleted")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kreaper) startPodWatcher(ctx context.Context, podList corev1.PodList, opts []client.ListOption) (<-chan struct{}, error) {
|
||||
logger := zap.L()
|
||||
watcher, err := k.Client.Watch(ctx, &podList, opts...)
|
||||
if err != nil {
|
||||
logger.Error("failed to setup watch", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer watcher.Stop()
|
||||
|
||||
for ev := range watcher.ResultChan() {
|
||||
if ev.Type != watch.Deleted {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := k.Client.List(ctx, &podList, opts...); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(podList.Items) == 0 {
|
||||
close(done)
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return done, nil
|
||||
}
|
||||
|
||||
func (k *Kreaper) forceDeleteAll(ctx context.Context) error {
|
||||
return k.Client.DeleteAllOf(
|
||||
ctx,
|
||||
new(corev1.Pod),
|
||||
client.InNamespace(k.TargetNamespace),
|
||||
client.PropagationPolicy(metav1.DeletePropagationForeground),
|
||||
client.MatchingLabelsSelector{
|
||||
Selector: k.labelSelector,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,15 +6,24 @@ import (
|
|||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
var ErrNotAVAlidTarget = errors.New("not a valid target")
|
||||
|
||||
var _ flag.Value = (*Target)(nil)
|
||||
|
||||
func ParseTarget(val string) (Target, error) {
|
||||
t := Target(val)
|
||||
if _, err := t.Selector(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
type Target string
|
||||
|
||||
func (t Target) Selector() (*metav1.LabelSelector, error) {
|
||||
func (t Target) Selector() (labels.Selector, error) {
|
||||
s := string(t)
|
||||
if s == "" {
|
||||
return nil, ErrNotAVAlidTarget
|
||||
|
@ -25,11 +34,13 @@ func (t Target) Selector() (*metav1.LabelSelector, error) {
|
|||
return nil, ErrNotAVAlidTarget
|
||||
}
|
||||
|
||||
return &metav1.LabelSelector{
|
||||
sel := &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
key: val,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return metav1.LabelSelectorAsSelector(sel)
|
||||
}
|
||||
|
||||
func (t Target) String() string {
|
||||
|
|
3
scripts/build.sh
Executable file
3
scripts/build.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
CGO_ENABLED=0 go build -trimpath -ldflags "-w -s" -installsuffix cgo -o dist/kreaper main.go
|
61
testdata/deployment.yaml
vendored
Normal file
61
testdata/deployment.yaml
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: kreaper-debug
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: kreaper-debug
|
||||
rules:
|
||||
- verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- deletecollection
|
||||
apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: kreaper-debug
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: kreaper-debug
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: kreaper-debug
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: kreaper
|
||||
spec:
|
||||
backoffLimit: 3
|
||||
completions: 1
|
||||
ttlSecondsAfterFinished: 30
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: kreaper
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
serviceAccountName: kreaper-debug
|
||||
containers:
|
||||
- name: kreaper
|
||||
image: kreaper
|
||||
args:
|
||||
- -log-level=debug
|
||||
env:
|
||||
- name: KREAPER_TARGET
|
||||
value: org.testcontainers.golang/sessionID=ee8dcc4d-72b5-4e77-8244-37abe525f948
|
||||
- name: KREAPER_LIFETIME
|
||||
value: "30s"
|
||||
- name: KREAPER_TARGET_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
12
testdata/kind.yaml
vendored
Normal file
12
testdata/kind.yaml
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Creates a kind cluster with Kind's custom cluster config
|
||||
# https://pkg.go.dev/sigs.k8s.io/kind/pkg/apis/config/v1alpha4#Cluster
|
||||
# Creates a cluster with 2 nodes.
|
||||
apiVersion: ctlptl.dev/v1alpha1
|
||||
kind: Cluster
|
||||
product: kind
|
||||
registry: ctlptl-registry
|
||||
kindV1Alpha4Cluster:
|
||||
name: kind
|
||||
nodes:
|
||||
- role: control-plane
|
||||
- role: worker
|
24
testdata/target_pod.yaml
vendored
Normal file
24
testdata/target_pod.yaml
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: kreaper-target
|
||||
labels:
|
||||
org.testcontainers.golang/sessionID: ee8dcc4d-72b5-4e77-8244-37abe525f948
|
||||
app.kubernetes.io/created-by: testcontainers-go
|
||||
app.kubernetes.io/managed-by: testcontainers-go
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 1
|
||||
containers:
|
||||
- name: busybox
|
||||
image: docker.io/busybox:latest
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- "sleep 15 && touch /tmp/healthy && sleep 7200"
|
||||
startupProbe:
|
||||
exec:
|
||||
command:
|
||||
- cat
|
||||
- /tmp/healthy
|
||||
periodSeconds: 3
|
||||
failureThreshold: 10
|
Reference in a new issue