This commit is contained in:
Lukas Bachschwell 2022-02-27 20:18:27 +01:00
commit 932ff67f17
No known key found for this signature in database
GPG Key ID: CCC6AA87CC8DF425
7 changed files with 2096 additions and 0 deletions

2
.env.example Normal file
View File

@ -0,0 +1,2 @@
CONFIG_SERVICE_HOST=:8000
CONFIG_SERVICE_SECRET=

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.vscode
.env

22
Readme.md Normal file
View File

@ -0,0 +1,22 @@
# Woodpecker CI Configuration Service example
This repository provides a very simplistic example of how to set up an external configuration service for Woodpecker CI
The external service gets a HTTP POST request with information about the repo, current build, and the configs that would normally be used.
It can then decide to acnowledge the current configs (By returning HTTP 204), or overriding the configurations and returning new ones in the response
Usecases for this system are:
- Centralized configuration for multiple repositories at once
- Preprocessing steps in the pipeline like templating, macros or conversion from different pipeline formats to woodpeckers format
This service is written in go, run using `go run .`. Then configure your woodpecker instance to point to it and configure the same secret. See [Woodpeckers documentation here](https://woodpecker-ci.org/docs/administration/external-configuration-api)
eg:
```shell
# Server
# ...
WOODPECKER_CONFIG_SERVICE_ENDPOINT=http://<service>:8000/ciconfig
WOODPECKER_CONFIG_SERVICE_SECRET=mysecretsigningkey
```

View File

@ -0,0 +1,5 @@
pipeline:
centralized:
image: alpine
commands:
- echo "Hello there from a central place"

8
go.mod Normal file
View File

@ -0,0 +1,8 @@
module woodpecker-config-service
go 1.17
require (
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e
github.com/woodpecker-ci/woodpecker v0.15.0
)

1960
go.sum Normal file

File diff suppressed because it is too large Load Diff

97
main.go Normal file
View File

@ -0,0 +1,97 @@
package main
import (
_ "embed"
"encoding/json"
"io/ioutil"
"log"
"net/http"
"os"
"regexp"
"github.com/99designs/httpsignatures-go"
"github.com/woodpecker-ci/woodpecker/server/model"
)
type config struct {
Name string `json:"name"`
Data string `json:"data"`
}
type incoming struct {
Repo *model.Repo `json:"repo"`
Build *model.Build `json:"build"`
Configuration []*config `json:"configs"`
}
//go:embed central-pipeline-config.yml
var overrideConfiguration string
func main() {
log.Println("Woodpecker central config server")
secretToken := os.Getenv("CONFIG_SERVICE_SECRET")
host := os.Getenv("CONFIG_SERVICE_HOST")
filterRegex := os.Getenv("CONFIG_SERVICE_OVERRIDE_FILTER")
if secretToken == "" && host == "" {
log.Println("Please make sure CONFIG_SERVICE_HOST and CONFIG_SERVICE_SECRET are set properly")
os.Exit(1)
}
filter := regexp.MustCompile(filterRegex)
http.HandleFunc("/ciconfig", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
signature, err := httpsignatures.FromRequest(r)
if err != nil {
log.Printf("config: invalid or missing signature in http.Request")
http.Error(w, "Invalid or Missing Signature", http.StatusBadRequest)
return
}
if !signature.IsValid(secretToken, r) {
log.Printf("config: invalid signature in http.Request")
http.Error(w, "Invalid Signature", http.StatusBadRequest)
return
}
var req incoming
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error reading body: %v", err)
http.Error(w, "can't read body", http.StatusBadRequest)
return
}
err = json.Unmarshal(body, &req)
if err != nil {
http.Error(w, "Failed to parse JSON"+err.Error(), http.StatusBadRequest)
return
}
if filter.MatchString(req.Repo.Name) {
w.WriteHeader(http.StatusOK)
err = json.NewEncoder(w).Encode(map[string]interface{}{"configs": []config{
{
Name: "central pipe",
Data: overrideConfiguration,
},
}})
if err != nil {
log.Printf("Error on encoding json %v\n", err)
}
} else {
w.WriteHeader(http.StatusNoContent) // use default config
// No need to write a response body
}
})
err := http.ListenAndServe(os.Getenv("CONFIG_SERVICE_HOST"), nil)
if err != nil {
log.Printf("Error on listen: %v", err)
}
}