feat(go-traefik-certmanager): initial commit
Some checks failed
ci/woodpecker/push/build unknown status
ci/woodpecker/push/lint Pipeline failed
ci/woodpecker/push/deploy unknown status

This commit is contained in:
Tom Neuber 2025-01-21 21:29:51 +01:00
parent 913eaceaa4
commit e2101672f8
Signed by: tom
GPG key ID: F17EFE4272D89FF6
13 changed files with 809 additions and 0 deletions

41
.woodpecker/.build.yaml Normal file
View file

@ -0,0 +1,41 @@
steps:
- name: docker
image: woodpeckerci/plugin-docker-buildx
settings:
registry: git.ar21.de
username:
from_secret: REGISTRY_USER
password:
from_secret: REGISTRY_PASS
repo: git.ar21.de/yolokube/go-traefik-certmanager
platforms:
- linux/amd64
- linux/arm64
tags:
- latest
- ${CI_PIPELINE_NUMBER}
when:
- branch: main
event: [push, manual]
- name: docker-staging
image: woodpeckerci/plugin-docker-buildx
settings:
registry: git.ar21.de
username:
from_secret: REGISTRY_USER
password:
from_secret: REGISTRY_PASS
repo: git.ar21.de/yolokube/go-traefik-certmanager
platforms:
- linux/amd64
- linux/arm64
tags:
- staging
- staging-${CI_PIPELINE_NUMBER}
dry_run: true
when:
- branch:
exclude: main
event: [push, manual]
depends_on:
- lint

57
.woodpecker/.deploy.yaml Normal file
View file

@ -0,0 +1,57 @@
skip_clone: true
steps:
- name: bump tag in deployment-repo (prod)
image: git.ar21.de/aaron/kustomize-ci
commands:
- git clone https://git.ar21.de/yolokube/core-deployments.git deployment-repo
- cd deployment-repo/traefik-certmanager/overlay
- kustomize edit set image git.ar21.de/yolokube/go-traefik-certmanager=git.ar21.de/yolokube/go-traefik-certmanager:${CI_PIPELINE_NUMBER}
when:
- branch: main
event: [push, manual]
- name: push new tag to deployment-repo (prod)
image: appleboy/drone-git-push
settings:
branch: "${CI_PIPELINE_NUMBER}_traefik-certmanager_prod"
remote: ssh://git@git.ar21.de:2222/yolokube/core-deployments.git
path: deployment-repo
force: false
commit: true
commit_message: "traefik-certmanager: update image tag to ${CI_PIPELINE_NUMBER} (done automagically via Woodpecker pipeline)"
ssh_key:
from_secret: FORGEJO_SSH_KEY
when:
- branch: main
event: [push, manual]
- name: create pull request (prod)
image: git.ar21.de/tom/push-message-randomizer:latest
pull: true
settings:
gitea_address: https://git.ar21.de
gitea_token:
from_secret: FORGEJO_API
owner: ${CI_REPO_OWNER}
repo: core-deployments
branch: "${CI_PIPELINE_NUMBER}_traefik-certmanager_prod"
base_branch: main
pr_title: "traefik-certmanager: update image tag to ${CI_PIPELINE_NUMBER}"
pr_body: |
### traefik-certmanager image update
{- random-pr-message -}
### Configuration
📅 **Schedule**: Branch creation - At any time, Automerge - At any time.
🚦 **Automerge**: Enabled.
skip_on_missing_branch: true
close_pr_if_empty: true
delete_branch_if_pr_empty: true
merge_when_checks_succeed: true
delete_branch_after_merge: true
when:
- branch: main
event: [push, manual]
depends_on:
- build

20
.woodpecker/.lint.yaml Normal file
View file

@ -0,0 +1,20 @@
steps:
- name: gofmt
image: golang:1.23.5
commands:
- gofmt -l -s .
when:
- event: [push, manual]
- name: vuln-check
image: golang:1.23.5
commands:
- go install golang.org/x/vuln/cmd/govulncheck@latest
- govulncheck ./...
when:
- event: [push, manual]
- name: golangci-linter
image: golangci/golangci-lint:v1.63.4
commands:
- golangci-lint run ./...
when:
- event: [push, manual]

39
go.mod Normal file
View file

@ -0,0 +1,39 @@
module git.ar21.de/yolokube/go-traefik-certmanager
go 1.23.4
require (
github.com/alecthomas/kong v1.6.0
github.com/cert-manager/cert-manager v1.16.2
k8s.io/api v0.32.0
k8s.io/apimachinery v0.32.0
k8s.io/client-go v0.32.0
)
require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.7.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/apiextensions-apiserver v0.31.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/gateway-api v1.1.0 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

146
go.sum Normal file
View file

@ -0,0 +1,146 @@
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v1.6.0 h1:mwOzbdMR7uv2vul9J0FU3GYxE7ls/iX1ieMg5WIM6gE=
github.com/alecthomas/kong v1.6.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/cert-manager/cert-manager v1.16.2 h1:c9UU2E+8XWGruyvC/mdpc1wuLddtgmNr8foKdP7a8Jg=
github.com/cert-manager/cert-manager v1.16.2/go.mod h1:MfLVTL45hFZsqmaT1O0+b2ugaNNQQZttSFV9hASHUb0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE=
k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0=
k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/N6E40=
k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ=
k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg=
k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8=
k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM=
sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

36
internal/cmd/cli.go Normal file
View file

@ -0,0 +1,36 @@
package cmd
import (
"git.ar21.de/yolokube/go-traefik-certmanager/internal/config"
"github.com/alecthomas/kong"
)
//nolint:lll // ignore line length
type CLI struct {
CertIssuerName string `name:"cert-issuer-name" env:"CERT_ISSUER_NAME" help:"Name for the certificate issuer" default:"${default_cert_issuer_name}"`
CertIssuerKind string `name:"cert-issuer-kind" env:"CERT_ISSUER_KIND" help:"Kind for the certificate issuer" default:"${default_cert_issuer_kind}"`
CertCleanup bool `name:"cert-cleanup" env:"CERT_CLEANUP" help:"Delete outdated / removed certificates" default:"${default_cert_cleanup}"`
PatchSecretName bool `name:"patch-secret-name" env:"PATCH_SECRET_NAME" help:"Adjust secret names" default:"${default_patch_secret_name}"`
}
func (c *CLI) Parse() *config.AppSettings {
_ = kong.Parse(
c,
kong.Vars{
"default_cert_issuer_name": "letsencrypt",
"default_cert_issuer_kind": "ClusterIssuer",
"default_cert_cleanup": "false",
"default_patch_secret_name": "false",
},
kong.Name("go-traefik-certmanager"),
kong.Description("🚀 Start a simple service to sync certificates for traefik CRs"),
kong.UsageOnError(),
)
return &config.AppSettings{
CertIssuerName: c.CertIssuerName,
CertIssuerKind: c.CertIssuerKind,
CertCleanup: c.CertCleanup,
PatchSecretName: c.PatchSecretName,
}
}

View file

@ -0,0 +1,8 @@
package config
type AppSettings struct {
CertIssuerName string
CertIssuerKind string
CertCleanup bool
PatchSecretName bool
}

51
main.go Normal file
View file

@ -0,0 +1,51 @@
package main
import (
"log"
"os"
"os/signal"
"syscall"
"git.ar21.de/yolokube/go-traefik-certmanager/internal/cmd"
"git.ar21.de/yolokube/go-traefik-certmanager/pkg/certmanager"
"git.ar21.de/yolokube/go-traefik-certmanager/pkg/ingressroute"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
)
func main() {
cli := cmd.CLI{}
appSettings := cli.Parse()
config, err := rest.InClusterConfig()
if err != nil {
log.Fatal(err)
}
client, err := dynamic.NewForConfig(config)
if err != nil {
log.Fatal(err)
}
cmClient := certmanager.NewClient(
*client,
certmanager.WithCertIssuerKind(appSettings.CertIssuerKind),
certmanager.WithCertIssuerName(appSettings.CertIssuerName),
)
irClient := ingressroute.NewClient(
*client,
cmClient,
ingressroute.WithCertCleanup(),
)
stopCh := make(chan struct{})
defer close(stopCh)
go irClient.IngressRoutes.Watch(stopCh)
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
<-signalCh
log.Print("Shutting down gracefully")
}

View file

@ -0,0 +1,153 @@
package certmanager
import (
"context"
"encoding/json"
"errors"
"fmt"
"regexp"
"strings"
cmv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
cmmetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
)
const (
certGroup = "cert-manager.io"
certVersion = "v1"
certKind = "Certificate"
certResource = "certificates"
)
var (
ErrCertificateAlreadyExist = errors.New("certificate already exists")
ErrCertificateToUnstructured = errors.New("certificate cannot get converted to unstructured")
ErrCertificateCreation = errors.New("certificate creation error")
ErrCertificateToJSON = errors.New("certificate cannot get converted to JSON")
)
type certificateClient struct {
client *Client
gvr schema.GroupVersionResource
}
func newCertificateClient(client *Client) certificateClient {
return certificateClient{
client: client,
gvr: schema.GroupVersionResource{
Group: certGroup,
Version: certVersion,
Resource: certResource,
},
}
}
func (c *certificateClient) Create(
ctx context.Context,
namespace, secretName string,
routes []map[string]interface{},
) error {
_, err := c.client.crdClient.Resource(c.gvr).Namespace(namespace).Get(ctx, secretName, metav1.GetOptions{})
if err == nil {
return ErrCertificateAlreadyExist
}
hosts := extractHosts(routes)
cert := cmv1.Certificate{
TypeMeta: metav1.TypeMeta{
Kind: certKind,
APIVersion: fmt.Sprintf("%s/%s", certGroup, certVersion),
},
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
},
Spec: cmv1.CertificateSpec{
DNSNames: hosts,
SecretName: secretName,
IssuerRef: cmmetav1.ObjectReference{
Name: c.client.certIssuerName,
Kind: c.client.certIssuerKind,
},
},
}
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cert)
if err != nil {
return errors.Join(ErrCertificateToUnstructured, err)
}
_, err = c.client.crdClient.Resource(c.gvr).Namespace(namespace).Create(
ctx,
&unstructured.Unstructured{Object: obj},
metav1.CreateOptions{},
)
if err != nil {
return errors.Join(ErrCertificateCreation, err)
}
return nil
}
func (c *certificateClient) Delete(ctx context.Context, namespace, name string) error {
return c.client.crdClient.Resource(c.gvr).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{})
}
func (c *certificateClient) Patch(ctx context.Context, namespace, name string, cert cmv1.Certificate) error {
data, err := json.Marshal(cert)
if err != nil {
return errors.Join(ErrCertificateToJSON, err)
}
_, err = c.client.crdClient.Resource(c.gvr).Namespace(namespace).Patch(
ctx,
name,
types.JSONPatchType,
data,
metav1.PatchOptions{},
)
return err
}
func (c *certificateClient) PatchSecretName(ctx context.Context, namespace, name, secretName string) error {
cert := cmv1.Certificate{
Spec: cmv1.CertificateSpec{
SecretName: secretName,
},
}
return c.Patch(ctx, namespace, name, cert)
}
func extractHosts(routes []map[string]interface{}) []string {
var hosts []string
re := regexp.MustCompile(`Host\(([^)]*)\)`)
for _, route := range routes {
var (
kind string
match string
ok bool
)
kind, ok = route["kind"].(string)
if !ok || kind != "Rule" {
continue
}
if match, ok = route["match"].(string); ok {
hostMatches := re.FindAllStringSubmatch(match, -1)
for _, match := range hostMatches {
if len(match) > 1 {
hosts = append(hosts, strings.Split(match[1], ",")...)
}
}
}
}
return hosts
}

41
pkg/certmanager/client.go Normal file
View file

@ -0,0 +1,41 @@
package certmanager
import (
"k8s.io/client-go/dynamic"
)
type Client struct {
crdClient dynamic.DynamicClient
certIssuerName string
certIssuerKind string
Certificates certificateClient
}
type ClientOption func(*Client)
func WithCertIssuerName(name string) ClientOption {
return func(c *Client) {
c.certIssuerName = name
}
}
func WithCertIssuerKind(kind string) ClientOption {
return func(c *Client) {
c.certIssuerKind = kind
}
}
func NewClient(crdClient dynamic.DynamicClient, options ...ClientOption) *Client {
client := &Client{
crdClient: crdClient,
}
for _, option := range options {
option(client)
}
client.Certificates = newCertificateClient(client)
return client
}

View file

@ -0,0 +1,37 @@
package ingressroute
import (
"git.ar21.de/yolokube/go-traefik-certmanager/pkg/certmanager"
"k8s.io/client-go/dynamic"
)
type Client struct {
crdClient dynamic.DynamicClient
certmanager *certmanager.Client
certCleanup bool
IngressRoutes ingressRouteClient
}
type ClientOption func(*Client)
func WithCertCleanup() ClientOption {
return func(c *Client) {
c.certCleanup = true
}
}
func NewClient(crdClient dynamic.DynamicClient, cmClient *certmanager.Client, options ...ClientOption) *Client {
client := &Client{
crdClient: crdClient,
certmanager: cmClient,
}
for _, option := range options {
option(client)
}
client.IngressRoutes = ingressRouteClient{client: client}
return client
}

View file

@ -0,0 +1,161 @@
package ingressroute
import (
"context"
"errors"
"fmt"
"log"
"time"
"git.ar21.de/yolokube/go-traefik-certmanager/pkg/certmanager"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
)
const (
group = "traefik.io"
version = "v1alpha1"
resource = "ingressroutes"
)
type ingressRouteClient struct {
client *Client
}
func (i *ingressRouteClient) Watch(stopCh chan struct{}) {
gvr := schema.GroupVersionResource{
Group: group,
Version: version,
Resource: resource,
}
listWatch := &cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
return i.client.crdClient.Resource(gvr).Namespace(corev1.NamespaceAll).List(context.Background(), options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
return i.client.crdClient.Resource(gvr).Namespace(corev1.NamespaceAll).Watch(context.Background(), options)
},
}
queue := workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any]())
informer := cache.NewSharedInformer(listWatch, &unstructured.Unstructured{}, 0)
_, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err != nil {
return
}
convObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return
}
rawRoutes, found, err := unstructured.NestedSlice(convObj, "spec", "routes")
if err != nil || !found {
return
}
routes, err := routeInterfaceToMapSlice(rawRoutes)
if err != nil {
return
}
queue.Add(event{key: key, eventType: watch.Added, routes: routes})
},
UpdateFunc: func(_, newObj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(newObj)
if err == nil {
queue.Add(event{key: key, eventType: watch.Modified})
}
},
DeleteFunc: func(obj interface{}) {
if !i.client.certCleanup {
return
}
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
if err == nil {
queue.Add(event{key: key, eventType: watch.Deleted})
}
},
})
if err != nil {
log.Printf("Cannot add event handler: %v", err)
}
go informer.Run(stopCh)
wait.Until(func() {
for i.processNextItem(queue) {
}
}, time.Second, stopCh)
}
func (i *ingressRouteClient) processNextItem(queue workqueue.TypedRateLimitingInterface[any]) bool {
item, quit := queue.Get()
if quit {
return false
}
defer queue.Done(item)
log.Printf("Processing key %v", item)
event, ok := item.(event)
if !ok {
log.Printf("Invalid data struct: %v", item)
return true
}
namespace, name, err := cache.SplitMetaNamespaceKey(event.key)
if err != nil {
log.Printf("Failed to split namespace and name: %v", err)
return true
}
//nolint:exhaustive // ignore missing switch cases
switch event.eventType {
case watch.Added, watch.Modified:
createErr := i.client.certmanager.Certificates.Create(context.Background(), namespace, name, event.routes)
if createErr != nil {
if errors.Is(createErr, certmanager.ErrCertificateAlreadyExist) {
log.Printf("Certificate %s already exists", event.key)
} else {
log.Printf("Failed to create certificate %s: %v", event.key, createErr)
}
}
case watch.Deleted:
if deleteErr := i.client.certmanager.Certificates.Delete(context.Background(), namespace, name); deleteErr != nil {
log.Printf("Failed to delete certificate %s: %v", event.key, deleteErr)
}
}
return true
}
type event struct {
key string
eventType watch.EventType
routes []map[string]interface{}
}
func routeInterfaceToMapSlice(input []interface{}) ([]map[string]interface{}, error) {
var result []map[string]interface{}
for _, item := range input {
match, ok := item.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("item is not of type map[string]interface{}: %v", item)
}
result = append(result, match)
}
return result, nil
}

19
renovate.json Normal file
View file

@ -0,0 +1,19 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
"regexManagers:dockerfileVersions"
],
"packageRules": [
{
"matchPackagePatterns": ["*"],
"automerge": true,
"automergeType": "pr",
"platformAutomerge": true,
"dependencyDashboard": true
}
],
"postUpdateOptions": [
"gomodTidy"
]
}