2023-05-29 21:21:50 +00:00
|
|
|
package hetzner
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-07-30 19:55:05 +00:00
|
|
|
"encoding/json"
|
2023-05-29 21:21:50 +00:00
|
|
|
"fmt"
|
2023-07-30 19:55:05 +00:00
|
|
|
"io"
|
|
|
|
"net/http"
|
2023-05-29 21:21:50 +00:00
|
|
|
"os"
|
2023-07-15 21:31:23 +00:00
|
|
|
"regexp"
|
2023-07-15 19:51:31 +00:00
|
|
|
"strconv"
|
2023-05-29 21:21:50 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"git.stinnesbeck.com/go/log"
|
|
|
|
"github.com/hetznercloud/hcloud-go/hcloud"
|
|
|
|
)
|
|
|
|
|
2023-07-30 19:55:05 +00:00
|
|
|
type VersionInfo struct {
|
|
|
|
Source string `json:"source"`
|
|
|
|
Version string `json:"version"`
|
|
|
|
}
|
|
|
|
|
2023-05-29 21:21:50 +00:00
|
|
|
func connect() *hcloud.Client {
|
|
|
|
token := os.Getenv("HCLOUD_API_TOKEN")
|
|
|
|
if token == "" {
|
|
|
|
log.PrintError("please specify HCLOUD_API_TOKEN")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
return hcloud.NewClient(hcloud.WithToken(token))
|
|
|
|
}
|
|
|
|
|
2023-07-15 19:51:31 +00:00
|
|
|
func GetNewSuffix(servers []*hcloud.Server) (int, error) {
|
|
|
|
// cop out if there are no servers yet
|
|
|
|
if len(servers) == 0 {
|
|
|
|
return 1, nil
|
2023-05-29 21:21:50 +00:00
|
|
|
}
|
|
|
|
|
2023-07-15 19:51:31 +00:00
|
|
|
// variable to hold highest number
|
|
|
|
var highestNumber int
|
2023-05-29 21:21:50 +00:00
|
|
|
|
2023-07-15 19:51:31 +00:00
|
|
|
// loop through all servers
|
|
|
|
for i := range servers {
|
|
|
|
serverNameSlice := strings.Split(servers[i].Name, "-")
|
|
|
|
suffix, err := strconv.Atoi(serverNameSlice[len(serverNameSlice)-1])
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// compare our current number agains the highest yet
|
|
|
|
if suffix > highestNumber {
|
|
|
|
highestNumber = suffix
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// return one number higher than highest number
|
|
|
|
return highestNumber + 1, nil
|
2023-05-29 21:21:50 +00:00
|
|
|
}
|
|
|
|
|
2023-07-15 19:51:31 +00:00
|
|
|
// ListJynxServers lists all servers managed by Jynx hosted on Hetzner Cloud
|
|
|
|
func ListJynxServers(prefix string) ([]*hcloud.Server, error) {
|
|
|
|
// connect to the hcloud API
|
2023-05-29 21:21:50 +00:00
|
|
|
client := connect()
|
2023-07-15 19:51:31 +00:00
|
|
|
|
|
|
|
// get all servers
|
2023-05-29 21:21:50 +00:00
|
|
|
servers, err := client.Server.All(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-15 19:51:31 +00:00
|
|
|
// create object that holds all servers managed by Jynx
|
|
|
|
var jynxServers []*hcloud.Server
|
2023-05-29 21:21:50 +00:00
|
|
|
|
2023-07-15 19:51:31 +00:00
|
|
|
// loop through servers and filter out managed servers
|
|
|
|
for _, server := range servers {
|
|
|
|
if strings.HasPrefix(server.Name, prefix) {
|
|
|
|
jynxServers = append(jynxServers, server)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// return managed servers
|
|
|
|
return jynxServers, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func CheckServerDeletions(servers []*hcloud.Server) error {
|
|
|
|
interval := os.Getenv("SCALER_POLL_INTERVAL")
|
|
|
|
if interval == "" {
|
|
|
|
interval = "5"
|
|
|
|
}
|
|
|
|
|
|
|
|
pollIntervalSeconds, err := strconv.Atoi(interval)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if pollIntervalSeconds < 60 {
|
|
|
|
pollIntervalSeconds = 60
|
|
|
|
}
|
|
|
|
|
|
|
|
if pollIntervalSeconds > 3600 {
|
|
|
|
pollIntervalSeconds %= 3600
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, server := range servers {
|
|
|
|
safeInterval := time.Duration(3600-pollIntervalSeconds) * time.Second
|
|
|
|
optimalDeleteTime := server.Created.Add(safeInterval).Local()
|
|
|
|
log.PrintDebug(server.Name, "optimal delete time:", optimalDeleteTime)
|
|
|
|
if time.Now().Local().Before(optimalDeleteTime) {
|
2023-05-29 21:21:50 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-07-15 19:51:31 +00:00
|
|
|
// optimal delete time has passed, delete the server
|
|
|
|
if err := DeleteServer(server); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-05-29 21:21:50 +00:00
|
|
|
}
|
|
|
|
|
2023-07-15 19:51:31 +00:00
|
|
|
return nil
|
2023-05-29 21:21:50 +00:00
|
|
|
}
|
|
|
|
|
2023-07-15 19:51:31 +00:00
|
|
|
func DeleteServer(server *hcloud.Server) error {
|
|
|
|
client := connect()
|
|
|
|
log.PrintInfo("Deleting", server.Name)
|
2023-07-15 21:31:23 +00:00
|
|
|
_, response, err := client.Server.DeleteWithResult(context.Background(), server)
|
2023-07-15 19:51:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-07-15 21:31:23 +00:00
|
|
|
if response.StatusCode != 200 {
|
|
|
|
return fmt.Errorf("api returned errorcode %d", response.StatusCode)
|
|
|
|
}
|
|
|
|
|
2023-07-15 19:51:31 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func CreateServers(servers []string) error {
|
|
|
|
// connect to hcloud API
|
2023-05-29 21:21:50 +00:00
|
|
|
client := connect()
|
2023-07-15 19:51:31 +00:00
|
|
|
|
|
|
|
// read in some environment variables
|
2023-05-29 21:21:50 +00:00
|
|
|
agentSecret := os.Getenv("WOODPECKER_AGENT_SECRET")
|
2023-07-15 21:31:23 +00:00
|
|
|
r := regexp.MustCompile(`http[s]*:\/\/(.*)`)
|
2023-07-30 19:55:05 +00:00
|
|
|
woodpeckerHostname := os.Getenv("WOODPECKER_HOSTNAME")
|
2023-07-15 21:31:23 +00:00
|
|
|
hasPort := regexp.MustCompile(`^.*(:\d+)`)
|
|
|
|
withoutPort := regexp.MustCompile(`^https?:\/\/(.*):`)
|
2023-07-30 19:55:05 +00:00
|
|
|
woodpeckerHost := r.FindStringSubmatch(woodpeckerHostname)[1]
|
2023-07-15 21:31:23 +00:00
|
|
|
if hasPort.MatchString(woodpeckerHost) {
|
|
|
|
woodpeckerHost = withoutPort.FindStringSubmatch(woodpeckerHost)[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
// default port for stats
|
|
|
|
statsPort := "9000"
|
|
|
|
|
|
|
|
// check if port was set
|
|
|
|
if port := os.Getenv("WOODPECKER_STATS_PORT"); port != "" {
|
|
|
|
statsPort = port
|
|
|
|
}
|
|
|
|
woodpeckerHost += ":" + statsPort
|
2023-05-29 21:21:50 +00:00
|
|
|
|
2023-07-30 19:55:05 +00:00
|
|
|
// get woodpecker server version
|
|
|
|
// prepare request
|
|
|
|
req, err := http.NewRequest(http.MethodGet, woodpeckerHostname+"/version", nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
c := http.DefaultClient
|
|
|
|
response, err := c.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := io.ReadAll(response.Body)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var version VersionInfo
|
|
|
|
|
|
|
|
err = json.Unmarshal(b, &version)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-07-15 19:51:31 +00:00
|
|
|
// loop through each server
|
|
|
|
for _, server := range servers {
|
|
|
|
// create unique AgentCMD for cloud init
|
2023-07-30 19:55:05 +00:00
|
|
|
AgentCMD := fmt.Sprintf("docker run -d -v /var/run/docker.sock:/var/run/docker.sock -e WOODPECKER_GRPC_SECURE=false -e WOODPECKER_SERVER=%s -e WOODPECKER_AGENT_SECRET=%s -e WOODPECKER_MAX_PROCS=%d -e WOODPECKER_HOSTNAME=%s woodpeckerci/woodpecker-agent:v"+version.Version, woodpeckerHost, agentSecret, 1, server)
|
2023-05-29 21:21:50 +00:00
|
|
|
|
2023-07-15 19:51:31 +00:00
|
|
|
userdata := fmt.Sprintf("#cloud-config\nruncmd:\n - %s", AgentCMD)
|
|
|
|
labels := make(map[string]string)
|
|
|
|
labels["created_by"] = "jynx"
|
|
|
|
labels["created"] = fmt.Sprintf("%d", time.Now().Unix())
|
2023-05-29 21:21:50 +00:00
|
|
|
|
2023-07-15 19:51:31 +00:00
|
|
|
var sshkeys []*hcloud.SSHKey
|
2023-07-15 21:31:23 +00:00
|
|
|
sshKeyNames := strings.Split(os.Getenv("HETZNER_SSH_KEY_NAMES"), ",")
|
2023-05-29 21:21:50 +00:00
|
|
|
|
2023-07-15 21:31:23 +00:00
|
|
|
sshkeys, err := getSSHKeys(sshKeyNames...)
|
2023-07-15 19:51:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2023-05-29 21:21:50 +00:00
|
|
|
|
2023-07-15 19:51:31 +00:00
|
|
|
serverOpts := hcloud.ServerCreateOpts{
|
|
|
|
Name: server,
|
|
|
|
StartAfterCreate: hcloud.Ptr(true),
|
|
|
|
SSHKeys: sshkeys,
|
|
|
|
Image: &hcloud.Image{Name: "docker-ce"},
|
|
|
|
ServerType: &hcloud.ServerType{Name: "cax11"},
|
|
|
|
Location: &hcloud.Location{Name: "fsn1"},
|
|
|
|
UserData: userdata,
|
|
|
|
PublicNet: &hcloud.ServerCreatePublicNet{EnableIPv4: true},
|
|
|
|
Labels: labels,
|
|
|
|
}
|
|
|
|
_, _, err = client.Server.Create(context.Background(), serverOpts)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-05-29 21:21:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|