2023-10-29 21:44:39 +00:00
|
|
|
package hetzner
|
|
|
|
|
|
|
|
import (
|
2023-10-30 21:46:17 +00:00
|
|
|
"bytes"
|
2023-10-31 22:18:08 +00:00
|
|
|
"context"
|
2023-10-30 21:46:17 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2023-11-06 20:52:37 +00:00
|
|
|
"strings"
|
2023-10-29 21:44:39 +00:00
|
|
|
"text/template"
|
|
|
|
|
|
|
|
"git.uploadfilter24.eu/covidnetes/woodpecker-autoscaler/internal/config"
|
2023-10-31 22:18:08 +00:00
|
|
|
"git.uploadfilter24.eu/covidnetes/woodpecker-autoscaler/internal/utils"
|
|
|
|
"github.com/hetznercloud/hcloud-go/hcloud"
|
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
2023-10-29 21:44:39 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var USER_DATA_TEMPLATE = `
|
2023-11-05 14:06:22 +00:00
|
|
|
#cloud-config
|
2023-10-29 21:44:39 +00:00
|
|
|
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 }}
|
2023-11-06 19:55:30 +00:00
|
|
|
- {{ $key }}={{ $val }}
|
2023-10-29 21:44:39 +00:00
|
|
|
{{- 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
|
2023-11-05 19:39:13 +00:00
|
|
|
EnvConfig map[string]interface{}
|
2023-10-29 21:44:39 +00:00
|
|
|
}
|
|
|
|
|
2023-10-31 22:18:08 +00:00
|
|
|
func generateConfig(cfg *config.Config, name string) (string, error) {
|
2023-11-05 19:39:13 +00:00
|
|
|
envConfig := map[string]interface{}{
|
2023-11-06 19:55:30 +00:00
|
|
|
"WOODPECKER_SERVER": fmt.Sprintf(`"%s"`, cfg.WoodpeckerGrpc),
|
2023-11-05 19:39:13 +00:00
|
|
|
"WOODPECKER_GRPC_SECURE": true,
|
2023-11-06 19:55:30 +00:00
|
|
|
"WOODPECKER_AGENT_SECRET": fmt.Sprintf(`"%s"`, cfg.WoodpeckerAgentSecret),
|
|
|
|
"WOODPECKER_FILTER_LABELS": fmt.Sprintf(`"%s"`, cfg.WoodpeckerLabelSelector),
|
|
|
|
"WOODPECKER_HOSTNAME": fmt.Sprintf(`"%s"`, name),
|
2023-11-05 19:39:13 +00:00
|
|
|
}
|
2023-10-29 21:44:39 +00:00
|
|
|
config := UserDataConfig{
|
|
|
|
Image: "woodpeckerci/woodpecker-agent:latest",
|
|
|
|
EnvConfig: envConfig,
|
|
|
|
}
|
2023-10-30 21:46:17 +00:00
|
|
|
tmpl, err := template.New("userdata").Parse(USER_DATA_TEMPLATE)
|
2023-10-29 21:44:39 +00:00
|
|
|
if err != nil {
|
2023-10-30 21:46:17 +00:00
|
|
|
return "", errors.New(fmt.Sprintf("Errors in userdata template: %s", err.Error()))
|
2023-10-29 21:44:39 +00:00
|
|
|
}
|
2023-10-30 21:46:17 +00:00
|
|
|
var buf bytes.Buffer
|
|
|
|
err = tmpl.Execute(&buf, &config)
|
2023-10-29 21:44:39 +00:00
|
|
|
if err != nil {
|
2023-10-30 21:46:17 +00:00
|
|
|
return "", errors.New(fmt.Sprintf("Could not render userdata template: %s", err.Error()))
|
2023-10-29 21:44:39 +00:00
|
|
|
}
|
2023-10-30 21:46:17 +00:00
|
|
|
return buf.String(), nil
|
2023-10-29 21:44:39 +00:00
|
|
|
}
|
2023-10-31 22:18:08 +00:00
|
|
|
|
|
|
|
func CreateNewAgent(cfg *config.Config) (*hcloud.Server, error) {
|
|
|
|
client := hcloud.NewClient(hcloud.WithToken(cfg.HcloudToken))
|
|
|
|
name := fmt.Sprintf("woodpecker-autoscaler-agent-%s", utils.RandStringBytes(5))
|
|
|
|
userdata, err := generateConfig(cfg, name)
|
2023-11-06 20:52:37 +00:00
|
|
|
keys := []*hcloud.SSHKey{}
|
|
|
|
for _, keyName := range strings.Split(cfg.HcloudSSHKeys, ",") {
|
|
|
|
key, _, err := client.SSHKey.GetByName(context.Background(), keyName)
|
|
|
|
if err != nil {
|
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"Caller": "CreateNewAgent",
|
|
|
|
}).Warnf("Failed to look up ssh key %s: %s", keyName, err.Error())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
keys = append(keys, key)
|
|
|
|
}
|
2023-11-04 20:32:38 +00:00
|
|
|
img, _, err := client.Image.GetByNameAndArchitecture(context.Background(), "docker-ce", "x86")
|
2023-11-01 19:48:45 +00:00
|
|
|
loc, _, err := client.Location.GetByName(context.Background(), cfg.HcloudRegion)
|
|
|
|
pln, _, err := client.ServerType.GetByName(context.Background(), cfg.HcloudInstanceType)
|
|
|
|
dc, _, err := client.Datacenter.GetByName(context.Background(), cfg.HcloudDatacenter)
|
2023-10-31 22:18:08 +00:00
|
|
|
labels := map[string]string{}
|
|
|
|
labels["Role"] = "WoodpeckerAgent"
|
|
|
|
labels["ControledBy"] = "WoodpeckerAutoscaler"
|
|
|
|
|
|
|
|
if err != nil {
|
2023-11-04 20:13:01 +00:00
|
|
|
return nil, errors.New(fmt.Sprintf("Could not parse agent spec: %s", err.Error()))
|
2023-10-31 22:18:08 +00:00
|
|
|
}
|
|
|
|
|
2023-11-05 19:54:41 +00:00
|
|
|
networkConf := hcloud.ServerCreatePublicNet{
|
2023-11-07 20:05:28 +00:00
|
|
|
EnableIPv4: true,
|
2023-11-05 19:54:41 +00:00
|
|
|
EnableIPv6: true,
|
|
|
|
}
|
|
|
|
|
2023-10-31 22:18:08 +00:00
|
|
|
res, _, err := client.Server.Create(context.Background(), hcloud.ServerCreateOpts{
|
|
|
|
Name: name,
|
|
|
|
ServerType: pln,
|
|
|
|
Image: img,
|
2023-11-06 20:52:37 +00:00
|
|
|
SSHKeys: keys,
|
2023-10-31 22:18:08 +00:00
|
|
|
Location: loc,
|
|
|
|
Datacenter: dc,
|
|
|
|
UserData: userdata,
|
|
|
|
StartAfterCreate: utils.BoolPointer(true),
|
|
|
|
Labels: labels,
|
2023-11-05 19:54:41 +00:00
|
|
|
PublicNet: &networkConf,
|
2023-10-31 22:18:08 +00:00
|
|
|
})
|
|
|
|
|
2023-11-04 20:13:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New(fmt.Sprintf("Could not create new Agent: %s", err.Error()))
|
|
|
|
}
|
|
|
|
|
2023-10-31 22:18:08 +00:00
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"Caller": "CreateNewAgent",
|
|
|
|
}).Infof("Created new Build Agent %s", res.Server.Name)
|
|
|
|
|
|
|
|
return res.Server, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ListAgents(cfg *config.Config) ([]hcloud.Server, error) {
|
|
|
|
client := hcloud.NewClient(hcloud.WithToken(cfg.HcloudToken))
|
|
|
|
allServers, err := client.Server.All(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New(fmt.Sprintf("Could not query Server list: %s", err.Error()))
|
|
|
|
}
|
|
|
|
myServers := []hcloud.Server{}
|
|
|
|
for _, server := range allServers {
|
|
|
|
val, exists := server.Labels["ControledBy"]
|
|
|
|
if exists && val == "WoodpeckerAutoscaler" {
|
|
|
|
myServers = append(myServers, *server)
|
2023-11-01 19:48:45 +00:00
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"Caller": "ListAgents",
|
|
|
|
}).Debugf("Owning %s Hetzner node", server.Name)
|
2023-10-31 22:18:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return myServers, nil
|
|
|
|
}
|
|
|
|
|
2023-11-01 19:48:45 +00:00
|
|
|
func DecomNode(cfg *config.Config, server *hcloud.Server) error {
|
2023-10-31 22:18:08 +00:00
|
|
|
client := hcloud.NewClient(hcloud.WithToken(cfg.HcloudToken))
|
2023-11-01 19:48:45 +00:00
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"Caller": "DecomNode",
|
|
|
|
}).Debugf("Deleting %s node", server.Name)
|
2023-10-31 22:18:08 +00:00
|
|
|
_, _, err := client.Server.DeleteWithResult(context.Background(), server)
|
|
|
|
if err != nil {
|
|
|
|
return errors.New(fmt.Sprintf("Could not delete Agent: %s", err.Error()))
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2023-11-04 21:37:09 +00:00
|
|
|
|
|
|
|
func RefreshNodeInfo(cfg *config.Config, serverID int) (*hcloud.Server, error) {
|
|
|
|
client := hcloud.NewClient(hcloud.WithToken(cfg.HcloudToken))
|
|
|
|
server, _, err := client.Server.GetByID(context.Background(), serverID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New(fmt.Sprintf("Could not refresh server info: %s", err.Error()))
|
|
|
|
}
|
|
|
|
return server, nil
|
|
|
|
}
|