feat: add Timeweb Cloud provider for Woodpecker CI autoscaler
- Implement timewebcloud provider with DeployAgent, RemoveAgent, ListDeployedAgentNames - Add minimal HTTP API client for Timeweb Cloud (create/list/delete servers) - Register provider in main.go with CLI flags - Add timeweb-list and timeweb-tester utilities - Include Dockerfile and docker-compose.yml for deployment - Update DEPLOY.md with verified OS/preset IDs
This commit is contained in:
122
providers/timewebcloud/provider.go
Normal file
122
providers/timewebcloud/provider.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package timewebcloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"go.woodpecker-ci.org/autoscaler/config"
|
||||
"go.woodpecker-ci.org/autoscaler/engine/inits/cloudinit"
|
||||
"go.woodpecker-ci.org/autoscaler/engine/types"
|
||||
"go.woodpecker-ci.org/autoscaler/providers/timewebcloud/api"
|
||||
woodpecker "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
name string
|
||||
config *config.Config
|
||||
client *api.Client
|
||||
osID int32
|
||||
presetID int32
|
||||
availabilityZone string
|
||||
sshKeyIDs []int32
|
||||
userDataTemplate *template.Template
|
||||
}
|
||||
|
||||
func New(_ context.Context, c *cli.Command, config *config.Config) (types.Provider, error) {
|
||||
p := &Provider{
|
||||
name: "timewebcloud",
|
||||
config: config,
|
||||
client: api.NewClient(c.String("timewebcloud-api-token")),
|
||||
osID: int32(c.Int("timewebcloud-os-id")),
|
||||
presetID: int32(c.Int("timewebcloud-preset-id")),
|
||||
availabilityZone: c.String("timewebcloud-availability-zone"),
|
||||
}
|
||||
|
||||
for _, raw := range c.StringSlice("timewebcloud-ssh-key-ids") {
|
||||
id, err := strconv.ParseInt(raw, 10, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: invalid ssh key id: %s", p.name, raw)
|
||||
}
|
||||
p.sshKeyIDs = append(p.sshKeyIDs, int32(id))
|
||||
}
|
||||
|
||||
if u := c.String("timewebcloud-user-data"); u != "" {
|
||||
log.Warn().Msg("timewebcloud-user-data is deprecated, please use provider-user-data instead")
|
||||
tmpl, err := template.New("user-data").Parse(u)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: template.New.Parse %w", p.name, err)
|
||||
}
|
||||
p.userDataTemplate = tmpl
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *Provider) DeployAgent(ctx context.Context, agent *woodpecker.Agent) error {
|
||||
userData, err := cloudinit.RenderUserDataTemplate(p.config, agent, p.userDataTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: cloudinit.RenderUserDataTemplate: %w", p.name, err)
|
||||
}
|
||||
|
||||
req := api.CreateServerRequest{
|
||||
Name: agent.Name,
|
||||
OsID: p.osID,
|
||||
PresetID: p.presetID,
|
||||
CloudInit: userData,
|
||||
AvailabilityZone: p.availabilityZone,
|
||||
SSHKeysIds: p.sshKeyIDs,
|
||||
Comment: fmt.Sprintf("woodpecker autoscaler agent for pool %s", p.config.PoolID),
|
||||
}
|
||||
|
||||
log.Info().Str("agent", agent.Name).Msg("create agent")
|
||||
|
||||
_, err = p.client.CreateServer(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: CreateServer: %w", p.name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) RemoveAgent(ctx context.Context, agent *woodpecker.Agent) error {
|
||||
servers, err := p.client.GetServers(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: GetServers: %w", p.name, err)
|
||||
}
|
||||
|
||||
for _, server := range servers.Servers {
|
||||
if server.Name == agent.Name {
|
||||
log.Info().Str("agent", agent.Name).Int32("server_id", server.ID).Msg("delete agent")
|
||||
if err := p.client.DeleteServer(ctx, server.ID); err != nil {
|
||||
return fmt.Errorf("%s: DeleteServer: %w", p.name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
log.Warn().Str("agent", agent.Name).Msg("agent not found, skipping deletion")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) ListDeployedAgentNames(ctx context.Context) ([]string, error) {
|
||||
servers, err := p.client.GetServers(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: GetServers: %w", p.name, err)
|
||||
}
|
||||
|
||||
prefix := fmt.Sprintf("pool-%s-agent-", p.config.PoolID)
|
||||
var names []string
|
||||
for _, server := range servers.Servers {
|
||||
if strings.HasPrefix(server.Name, prefix) {
|
||||
names = append(names, server.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return names, nil
|
||||
}
|
||||
Reference in New Issue
Block a user