initial implementation #1
37
.woodpecker.yml
Normal file
37
.woodpecker.yml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
steps:
|
||||||
|
test:
|
||||||
|
image: golang:1.21
|
||||||
|
commands:
|
||||||
|
- go test ./...
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
|
build:
|
||||||
|
image: woodpeckerci/plugin-docker-buildx
|
||||||
|
settings:
|
||||||
|
platforms: linux/arm64/v8
|
||||||
|
repo: lerentis/metallb-ip-floater
|
||||||
|
tags:
|
||||||
|
- latest
|
||||||
|
- ${CI_COMMIT_SHA}
|
||||||
|
dry-run: true
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
|
notify:
|
||||||
|
image: appleboy/drone-telegram
|
||||||
|
settings:
|
||||||
|
message: "Commit {{ commit.message }} ({{ commit.link }}) ran with build {{ build.number }} and finished with status {{ build.status }}."
|
||||||
|
to:
|
||||||
|
from_secret: telegram_userid
|
||||||
|
token:
|
||||||
|
from_secret: telegram_secret
|
||||||
|
when:
|
||||||
|
status:
|
||||||
|
- failure
|
||||||
|
- success
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
- pull_request
|
18
Dockerfile
Normal file
18
Dockerfile
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
FROM golang:1.21 as build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN go mod tidy && CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags '-w -extldflags "-static"' -o woodpecker-autoscaler ./cmd/
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
|
||||||
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
|
COPY --from=build /etc/passwd /etc/passwd
|
||||||
|
COPY --from=build /etc/group /etc/group
|
||||||
|
COPY --from=build --chown=65534:65534 /app/woodpecker-autoscaler /usr/local/bin/woodpecker-autoscaler
|
||||||
|
|
||||||
|
USER nobody
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/woodpecker-autoscaler"]
|
38
cmd/woodpecker-autoscaler.go
Normal file
38
cmd/woodpecker-autoscaler.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.uploadfilter24.eu/covidnetes/woodpecker-autoscaler/internal/config"
|
||||||
|
"git.uploadfilter24.eu/covidnetes/woodpecker-autoscaler/internal/health"
|
||||||
|
"git.uploadfilter24.eu/covidnetes/woodpecker-autoscaler/internal/logging"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
cfg, err := config.GenConfig()
|
||||||
|
logging.ConfigureLogger(cfg)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"Caller": "Main",
|
||||||
|
}).Fatal(fmt.Sprintf("Error generating Config: %s", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"Caller": "Main",
|
||||||
|
}).Info("Starting Health Endpoint")
|
||||||
|
health.StartHealthEndpoint()
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"Caller": "Main",
|
||||||
|
}).Info("Entering main event loop")
|
||||||
|
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Duration(cfg.CheckInterval) * time.Minute)
|
||||||
|
}
|
||||||
|
}
|
15
go.mod
Normal file
15
go.mod
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
module git.uploadfilter24.eu/covidnetes/woodpecker-autoscaler
|
||||||
|
|
||||||
|
go 1.21.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gorilla/mux v1.8.0
|
||||||
|
github.com/jinzhu/configor v1.2.1
|
||||||
|
github.com/sirupsen/logrus v1.9.3
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||||
|
)
|
24
go.sum
Normal file
24
go.sum
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
|
github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
|
||||||
|
github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
38
internal/config/config.go
Normal file
38
internal/config/config.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jinzhu/configor"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config = struct {
|
||||||
|
LogLevel string `default:"Info" env:"WOODPECKER_AUTOSCALER_LOGLEVEL"`
|
||||||
|
CheckInterval int `default:"15" env:"WOODPECKER_AUTOSCALER_CHECK_INTERVAL"`
|
||||||
|
LabelSelector string `default:"uploadfilter24.eu/instance-role=Woodpecker" env:"WOODPECKER_AUTOSCALER_LABELSELECTOR"`
|
||||||
|
WoodpeckerInstance string `default:"" env:"WOODPECKER_AUTOSCALER_WOODPECKER_INSTANCE"`
|
||||||
|
WoodpeckerAgentSecret string `default:"" env:"WOODPECKER_AUTOSCALER_WOODPECKER_AGENT_SECRET"`
|
||||||
|
Protocol string `default:"http" env:"WOODPECKER_AUTOSCALER_PROTOCOL"`
|
||||||
|
HcloudToken string `default:"" env:"WOODPECKER_AUTOSCALER_HCLOUD_TOKEN"`
|
||||||
|
InstanceType string `default:"" env:"WOODPECKER_AUTOSCALER_INSTANCE_TYPE"`
|
||||||
|
Zone string `default:"" env:"WOODPECKER_AUTOSCALER_ZONE"`
|
||||||
|
DryRun bool `default:"false" env:"WOODPECKER_AUTOSCALER_DRY_RUN"`
|
||||||
|
SSHKey string `default:"" env:"WOODPECKER_AUTOSCALER_SSH_KEY"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenConfig() (cfg *Config, err error) {
|
||||||
|
|
||||||
|
cfg = &Config{}
|
||||||
|
|
||||||
|
err = configor.New(&configor.Config{
|
||||||
|
ENVPrefix: "WOODPECKER_AUTOSCALER",
|
||||||
|
AutoReload: true,
|
||||||
|
Silent: true,
|
||||||
|
AutoReloadInterval: time.Minute}).Load(cfg, "config.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(fmt.Sprintf("Error generating Config: %s", err.Error()))
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
31
internal/health/healthcheck.go
Normal file
31
internal/health/healthcheck.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StartHealthEndpoint() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.Use(mux.CORSMethodMiddleware(r))
|
||||||
|
r.HandleFunc("/health", send200).Methods(http.MethodGet)
|
||||||
|
err := http.ListenAndServe("0.0.0.0:8080", r)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"Caller": "StartHealthEndpoint",
|
||||||
|
}).Error(fmt.Sprintf("Error creating health endpoint: %s", err.Error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func send200(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, err := w.Write([]byte{})
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"Caller": "send200",
|
||||||
|
}).Error(fmt.Sprintf("Error answering health endpoint: %s", err.Error()))
|
||||||
|
}
|
||||||
|
}
|
55
internal/hetzner/hetzneragent.go
Normal file
55
internal/hetzner/hetzneragent.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package hetzner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"git.uploadfilter24.eu/covidnetes/woodpecker-autoscaler/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
var USER_DATA_TEMPLATE = `
|
||||||
|
write_files:
|
||||||
|
- content: |
|
||||||
|
# docker-compose.yml
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
woodpecker-agent:
|
||||||
|
image: {{ .Image }}
|
||||||
|
command: agent
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
environment:
|
||||||
|
{{- range $key, $val := .EnvConfig }}
|
||||||
|
- {{ $key }}: {{ $val }}
|
||||||
|
{{- end }}
|
||||||
|
path: /root/docker-compose.yml
|
||||||
|
runcmd:
|
||||||
|
- [ sh, -xc, "cd /root; docker run --rm --privileged multiarch/qemu-user-static --reset -p yes; docker compose up -d" ]
|
||||||
|
`
|
||||||
|
|
||||||
|
type UserDataConfig struct {
|
||||||
|
Image string
|
||||||
|
EnvConfig map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateConfig(cfg *config.Config) {
|
||||||
|
envConfig := map[string]string{}
|
||||||
|
envConfig["WOODPECKER_SERVER"] = cfg.WoodpeckerInstance
|
||||||
|
envConfig["WOODPECKER_AGENT_SECRET"] = cfg.WoodpeckerAgentSecret
|
||||||
|
envConfig["WOODPECKER_FILTER_LABELS"] = cfg.LabelSelector
|
||||||
|
config := UserDataConfig{
|
||||||
|
Image: "woodpeckerci/woodpecker-agent:latest",
|
||||||
|
EnvConfig: envConfig,
|
||||||
|
}
|
||||||
|
tmpl, err := template.New("test").Parse(USER_DATA_TEMPLATE)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = tmpl.Execute(os.Stdout, config)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
28
internal/logging/logging.go
Normal file
28
internal/logging/logging.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.uploadfilter24.eu/covidnetes/woodpecker-autoscaler/internal/config"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConfigureLogger(cfg *config.Config) {
|
||||||
|
|
||||||
|
switch cfg.LogLevel {
|
||||||
|
case "Debug":
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
case "Info":
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
case "Warn":
|
||||||
|
log.SetLevel(log.WarnLevel)
|
||||||
|
case "Error":
|
||||||
|
log.SetLevel(log.ErrorLevel)
|
||||||
|
default:
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
log.Warnf("Home: invalid log level supplied: '%s'", cfg.LogLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetFormatter(&log.JSONFormatter{})
|
||||||
|
log.SetOutput(os.Stdout)
|
||||||
|
}
|
7
internal/woodpecker/metrics.go
Normal file
7
internal/woodpecker/metrics.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package woodpecker
|
||||||
|
|
||||||
|
import "git.uploadfilter24.eu/covidnetes/woodpecker-autoscaler/internal/config"
|
||||||
|
|
||||||
|
func GetPendingJobs(cfg *config.Config) {
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user