Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ FROM scratch
COPY --from=builder /go/bin/ci-github-notifier /go/bin/ci-github-notifier
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

EXPOSE 8080
ENTRYPOINT ["/go/bin/ci-github-notifier"]
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,6 @@ docker run \

# Argo Workflows example
A simple Argo Workflows template can be found in the examples directory.

# Run as a daemon
You may wish to run as a permanent service in your Kubernetes cluster and then call the service using curl or similar to send your message. An example of how to do this can be found in the examples directory.
113 changes: 86 additions & 27 deletions ci-github-notifier/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,103 @@ package main

import (
"encoding/json"
"fmt"
"github.com/imroc/req"
"io/ioutil"
"log"
"os"
"strings"
"flag"
"fmt"
"log"
"net/http"
"os"
)

func main() {
fmt.Printf("Notifiying Github: %s:%s\n", getValidatedEnvVar("context"), getValidatedEnvVar("state"))
var webServerModeFlag = flag.Bool("webServerMode", false, "Run in web server (daemon service) mode")
flag.Parse()

header := req.Header{
"Authorization": fmt.Sprintf("token %s", getToken(os.Getenv("tokenFile"), "access_token")),
}
// Flag can be overridden by environment variable
webServerModeEnv := strings.ToLower(os.Getenv("WEBSERVER_MODE")) == "true"

values := map[string]string{"state": getValidatedEnvVar("state"), "target_url": getValidatedEnvVar("target_url"), "description": getValidatedEnvVar("description"), "context": getValidatedEnvVar("context")}
jsonData, err := json.Marshal(values)
// Determine web server mode by combining the flag and the environment variable
webServerMode := *webServerModeFlag || webServerModeEnv

r, err := req.Post(fmt.Sprintf("https://%s/repos/%s/%s/statuses/%s", getUrl("gh_url", "api.github.com"), getValidatedEnvVar("organisation"), getValidatedEnvVar("app_repo"), getValidatedEnvVar("git_sha")), header, req.BodyJSON(jsonData))
if webServerMode {
fmt.Println("Running in web server mode")
startWebServer()
} else {
fmt.Println("Running in standalone mode")
notifyGitHub(nil, nil)
}
}

if err != nil {
log.Fatal(err)
}
resp := r.Response()
fmt.Println("HTTP response from github:", resp.StatusCode)
func startWebServer() {
http.HandleFunc("/notify", func(w http.ResponseWriter, r *http.Request) {
notifyGitHub(w, r)
})
fmt.Println("Server is listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}

func notifyGitHub(w http.ResponseWriter, r *http.Request) {
// Helper function to get parameter value: first tries URL query, then falls back to environment variable.
getParamValue := func(paramName, envVarName string) string {
if r != nil { // Check if http.Request is available
values := r.URL.Query()
if value, found := values[paramName]; found && len(value) > 0 {
return value[0]
}
}
return getValidatedEnvVar(envVarName)
}

// Parameters
state := getParamValue("state", "state")
targetURL := getParamValue("target_url", "target_url")
description := getParamValue("description", "description")
context := getParamValue("context", "context")
organisation := getParamValue("organisation", "organisation")
appRepo := getParamValue("app_repo", "app_repo")
gitSha := getParamValue("git_sha", "git_sha")
ghURL := getParamValue("gh_url", "gh_url")

// Notification logic
fmt.Printf("Notifying Github: %s:%s\n", context, state)

header := req.Header{
"Authorization": fmt.Sprintf("token %s", getToken(os.Getenv("tokenFile"), "access_token")),
}

values := map[string]string{"state": state, "target_url": targetURL, "description": description, "context": context}
jsonData, err := json.Marshal(values)

resp, err := req.Post(fmt.Sprintf("https://%s/repos/%s/%s/statuses/%s", getUrl(ghURL, "api.github.com"), organisation, appRepo, gitSha), header, req.BodyJSON(jsonData))
if err != nil {
if w != nil { // Check if http.ResponseWriter is available
http.Error(w, err.Error(), http.StatusInternalServerError)
} else {
fmt.Println("Error notifying GitHub:", err)
}
return
}

httpResponse := resp.Response()
if w != nil {
fmt.Fprintf(w, "HTTP response from github: %d", httpResponse.StatusCode)
} else {
fmt.Printf("HTTP response from github: %d\n", httpResponse.StatusCode)
}
}

func getValidatedEnvVar(e string) string {
c := os.Getenv(e)
if os.Getenv(e) == "" {
fmt.Printf("Error: No environment variable called %s available. Exiting.\n", e)
os.Exit(1)
}
return c
c := os.Getenv(e)
if os.Getenv(e) == "" {
log.Fatalf("Error: No environment variable called %s available. Exiting.\n", e)
}
return c
}

func getToken(f string, e string) string {
if os.Getenv(e) == "" {
data, err := ioutil.ReadFile(f)
data, err := os.ReadFile(f)
if err != nil {
fmt.Println("No tokenFile found. Falling back to Environment Variable")
}
Expand All @@ -50,8 +109,8 @@ func getToken(f string, e string) string {
}

func getUrl(e, fallback string) string {
if value, ok := os.LookupEnv(e); ok {
return value
}
return fallback
if value, ok := os.LookupEnv(e); ok {
return value
}
return fallback
}
8 changes: 8 additions & 0 deletions examples/daemon-deployment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Web server mode (daemon) deployment

This example demonstrates how to deploy the CI GitHub Notifier in web server mode (daemon) in Kubernetes. You deploy the CI GitHub Notifier as a Kubernetes Deployment and expose it as a Kubernetes Service. Then you can call it from other services in the same Kubernetes cluster (eg a running Argo Workflow).

Call it with:
```bash
curl http://localhost:8080/notify?state=success&target_url=http%3A%2F%2Fexample.com&description=Deployment+successful&context=deployment&tokenFile=/path/to/tokenfile&organisation=myorg&app_repo=myrepo&git_sha=abc123&gh_url=api.github.com
```
40 changes: 40 additions & 0 deletions examples/daemon-deployment/argo-workflow.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: ci-github-notifier-example-
spec:
entrypoint: main
serviceAccountName: valid-sa-name-here #https://argo-workflows.readthedocs.io/en/latest/http-template/#argo-agent
templates:
- name: main
dag:
tasks:
- name: post-pending
template: ci-github-notifier
arguments:
parameters:
- name: base_url
value: "http://ci-github-notifier.ci-github-notifier.svc.cluster.local:8080/notify"
- name: git_sha
value: "12345"
- name: state
value: "pending"
- name: context
value: "UI Checks"
- name: description
value: "In progress — This check has started..."
- name: target_url
value: "https://pipekit.io/"

- name: ci-github-notifier
inputs:
parameters:
- name: base_url
- name: git_sha
- name: state
- name: context
- name: description
- name: target_url
http:
url: "{{inputs.parameters.base_url}}?app_repo=foo&organisation=bar&git_sha={{inputs.parameters.git_sha}}&state={{inputs.parameters.state}}&context={{inputs.parameters.context}}&description={{inputs.parameters.description}}&target_url={{inputs.parameters.target_url}}"
successCondition: "response.body contains \"200\""
76 changes: 76 additions & 0 deletions examples/daemon-deployment/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: ci-github-notifier
name: ci-github-notifier
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
selector:
matchLabels:
app: ci-github-notifier
template:
metadata:
labels:
app: ci-github-notifier
spec:
containers:
- image: ghcr.io/crumbhole/ci-github-notifier
imagePullPolicy: Always
name: ci-github-notifier
ports:
- containerPort: 8080
name: http
protocol: TCP
resources:
requests:
memory: 20Mi
cpu: 10m
env:
- name: WEBSERVER_MODE
value: "true"
- name: access_token
valueFrom:
secretKeyRef:
name: ci-github-notifier-access-token
key: access_token
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- ci-github-notifier
topologyKey: "topology.kubernetes.io/zone"
---
apiVersion: v1
kind: Secret
metadata:
name: ci-github-notifier-access-token
type: Opaque
data:
access_token: <base64 encoded access token>
---
apiVersion: v1
kind: Service
metadata:
labels:
app: ci-github-notifier
name: ci-github-notifier
spec:
ports:
- name: ci-github-notifier
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: ci-github-notifier
type: ClusterIP