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 }