Initial
This commit is contained in:
commit
932ff67f17
2
.env.example
Normal file
2
.env.example
Normal file
@ -0,0 +1,2 @@
|
||||
CONFIG_SERVICE_HOST=:8000
|
||||
CONFIG_SERVICE_SECRET=
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.vscode
|
||||
.env
|
22
Readme.md
Normal file
22
Readme.md
Normal 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
|
||||
|
||||
```
|
||||
|
5
central-pipeline-config.yml
Normal file
5
central-pipeline-config.yml
Normal file
@ -0,0 +1,5 @@
|
||||
pipeline:
|
||||
centralized:
|
||||
image: alpine
|
||||
commands:
|
||||
- echo "Hello there from a central place"
|
8
go.mod
Normal file
8
go.mod
Normal 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
|
||||
)
|
97
main.go
Normal file
97
main.go
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user