Skip to content
This repository has been archived by the owner on Mar 8, 2022. It is now read-only.

Commit

Permalink
Major breaking change: Always apply resources on events (#5)
Browse files Browse the repository at this point in the history
* Refactor, always run workflow

* Add -ignore flag, remove broken reconciler

Allows ignoring refs by regex

* Support more events, add dry-run, update README
  • Loading branch information
discordianfish authored Oct 15, 2019
1 parent 8b57a9d commit efabec1
Show file tree
Hide file tree
Showing 13 changed files with 379 additions and 618 deletions.
42 changes: 19 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,28 @@
# k8s-webhook-handler
The k8s-webhook-handler listens for (GitHub) webhooks and acts on various events

## Event Handlers
### DeleteEvent
On branch deletion and deletes all resources in a kubernetes cluster that have a
label matching the repo name and are in a namespace matching the branch name.
If there are no other objects with the given label key in the namespace, it also
deletes the namespace and all remaining objects.

### PushEvent
On push events, k8s-webhook-handler will checkout `.ci/workflow.yaml` from the
repo the push was and submit it to the k8s api with the following annotations
added:

- `k8s-webhook-handler.io/ref`: event.Ref
- `k8s-webhook-handler.io/before`: event.Before
- `k8s-webhook-handler.io/revision`: event.HeadCommit.ID
- `k8s-webhook-handler.io/repo_name`: event.Repo.FullName
- `k8s-webhook-handler.io/repo_url`: event.Repo.GitURL
- `k8s-webhook-handler.io/repo_ssh`: event.Repo.SSHURL
Create Kubernetes resources in response to (GitHub) webhooks!

## How does it work?
When the k8s-webhook-handler receives a webhook, it downloads a manifest
(`.ci/workflow.yaml` by default) from the repository.

For push events, it downloads the manifest from the given revision. Otherwise
it's checked out from the repository's default branch.

After that, it applies the manifest and adds the following annotations:

- `k8s-webhook-handler.io/ref`: Git reference (e.g. `refs/heads/master`)
- `k8s-webhook-handler.io/revision`: Revision of HEAD
- `k8s-webhook-handler.io/repo_name`: Repo name including user
(e.g. `itskoko/k8s-webhook-handler`)
- `k8s-webhook-handler.io/repo_url`: git URL (e.g.
`git://github.com/itskoko/k8s-webhook-handler.git`)
- `k8s-webhook-handler.io/repo_ssh`: ssh URL (e.g.
`[email protected]:itskoko/k8s-webhook-handler.git`)

## Binaries
- cmd/webhook is the actual webhook handling server
- cmd/reconciler iterates over all k8s namespaces and deletes all objects that
are labeled for which there is no remote branch anymore.

## Usage
Currently only github delete webhooks in json format are supported.
Beside the manifests and templates in `deploy/`, a secret 'webhook-handler' with
the following fields is expected:

Expand Down
38 changes: 0 additions & 38 deletions cmd/reconciler/main.go

This file was deleted.

69 changes: 36 additions & 33 deletions cmd/webhook/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"flag"
"net/http"
"os"
"regexp"
"time"

"github.com/go-kit/kit/log"
Expand All @@ -15,75 +16,77 @@ import (
)

var (
listenAddr = flag.String("l", ":8080", "Address to listen on for webhook requests")
sourceSelectorKey = flag.String("sk", "ci-source-repo", "Label key that identifies source repo")
namespace = flag.String("ns", "ci", "Namespace to deploy workflows to")
kubeconfig = flag.String("kubeconfig", "", "If set, use this kubeconfig to connect to kubernetes")
dryRun = flag.Bool("dry", false, "Enable dry-run, print resources instead of deleting them")
baseURL = flag.String("gh-base-url", "", "GitHub Enterprise: Base URL")
uploadURL = flag.String("gh-upload-url", "", "GitHub Enterprise: Upload URL")
gitAddress = flag.String("git", "[email protected]", "Git address")
debug = flag.Bool("debug", false, "Enable debug logging")
insecure = flag.Bool("insecure", false, "Allow omitting WEBHOOK_SECRET for testing")
listenAddr = flag.String("l", ":8080", "Address to listen on for webhook requests")
namespace = flag.String("ns", "ci", "Namespace to deploy workflows to")
resoucePath = flag.String("p", ".ci/workflow.yaml", "Path to resource manifest in repository")
kubeconfig = flag.String("kubeconfig", "", "If set, use this kubeconfig to connect to kubernetes")
baseURL = flag.String("gh-base-url", "", "GitHub Enterprise: Base URL")
uploadURL = flag.String("gh-upload-url", "", "GitHub Enterprise: Upload URL")
gitAddress = flag.String("git", "[email protected]", "Git address")
debug = flag.Bool("debug", false, "Enable debug logging")
dryRun = flag.Bool("dry", false, "Dry run; Do not apply resouce manifest")
insecure = flag.Bool("insecure", false, "Allow omitting WEBHOOK_SECRET for testing")
ignoreRef = flag.String("ignore", "", "Ignore refs matching this regex")

statsdAddress = flag.String("statsd.address", "localhost:8125", "Address to send statsd metrics to")
statsdProto = flag.String("statsd.proto", "udp", "Protocol to use for statsd")
statsdInterval = flag.Duration("statsd.interval", 30*time.Second, "statsd flush interval")

logger = log.With(log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)), "caller", log.Caller(5))
)

func fatal(err error) {
func fatal(logger log.Logger, err error) {
// FIXME: override caller, not add it
logger := log.With(logger, "caller", log.Caller(4))
level.Error(logger).Log("msg", err.Error())
level.Error(logger).Log("msg", err.Error(), "caller", log.Caller(4))
os.Exit(1)
}

func main() {
logger := log.With(log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)), "caller", log.Caller(5))
flag.Parse()
githubSecret := os.Getenv("WEBHOOK_SECRET")
if githubSecret == "" && !*insecure {
fatal(errors.New("WEBHOOK_SECRET not set. Use -insecure to disable webhook verification"))
fatal(logger, errors.New("WEBHOOK_SECRET not set. Use -insecure to disable webhook verification"))
}
if *debug {
logger = level.NewFilter(logger, level.AllowAll())
} else {
logger = level.NewFilter(logger, level.AllowInfo())
}

kconfig, err := handler.NewKubernetesConfig(*kubeconfig)
if err != nil {
fatal(err)
config := &handler.Config{
Namespace: *namespace,
ResourcePath: *resoucePath,
Secret: []byte(githubSecret),
DryRun: *dryRun,
}

dh, err := handler.NewDeleteHandler(logger, kconfig, *sourceSelectorKey, *dryRun)
dh.GitAddress = *gitAddress
if err != nil {
fatal(err)
if *ignoreRef != "" {
level.Debug(logger).Log("msg", "Parsing regex", "regex", *ignoreRef)
regex, err := regexp.Compile(*ignoreRef)
if err != nil {
fatal(logger, err)
}
config.IgnoreRefRegex = regex
}

ghClient, err := handler.NewGitHubClient(os.Getenv("GITHUB_TOKEN"), *baseURL, *uploadURL)
level.Info(logger).Log("msg", "Connecting to kubernetes", "kubeconfig", *kubeconfig)
kClient, err := handler.NewKubernetesClient(*kubeconfig)
if err != nil {
fatal(err)
fatal(logger, err)
}

ph, err := handler.NewPushHandler(logger, kconfig, ghClient)
loader, err := handler.NewGithubLoader(os.Getenv("GITHUB_TOKEN"), *baseURL, *uploadURL)
if err != nil {
fatal(err)
fatal(logger, err)
}
ph.Namespace = *namespace

ticker := time.NewTicker(*statsdInterval)
defer ticker.Stop()
statsdClient := statsd.New("k8s-ci-purger.", logger)
go statsdClient.SendLoop(ticker.C, *statsdProto, *statsdAddress)

h := handler.NewGithubHookHandler([]byte(githubSecret), statsdClient)
h.DeleteHandler = dh
h.PushHandler = ph
server := handler.NewGithubHookHandler(logger, config, kClient, loader, statsdClient)

http.Handle("/", h)
http.Handle("/", server)
level.Info(logger).Log("msg", "Start listening", "addr", *listenAddr)
fatal(http.ListenAndServe(*listenAddr, nil))
fatal(logger, http.ListenAndServe(*listenAddr, nil))
}
30 changes: 0 additions & 30 deletions cmd/webhook/main_test.go

This file was deleted.

Loading

0 comments on commit efabec1

Please sign in to comment.