nuage/main.go

257 lines
5.0 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"io"
"os"
"fmt"
"errors"
"time"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
)
const PARALLELIZE = 16
const instanceNotFound = Error("instance not found")
func main() {
if len(os.Args) < 2 {
usage()
}
var err error
switch os.Args[1] {
case "spawn":
err = spawn()
case "run":
err = run()
case "destroy":
err = destroy()
}
if err != nil {
fmt.Fprintf(os.Stderr, "cmd failed: %v\n", err)
os.Exit(1)
}
}
func usage() {
var programName = "swtool"
if len(os.Args) > 0 {
programName = os.Args[0]
}
fmt.Fprintf(os.Stderr, "Usage: %v (spawn|run|destroy)\n", programName)
os.Exit(1)
}
/**
* Errors
*/
type Error string
func (e Error) Error() string {
return string(e)
}
/**
* Parser logic
*/
type instanceReceiver interface {
onInstance(zone, machine, image, name string) error
}
func passInstanceTo(r io.Reader, d instanceReceiver) error {
com := make(chan error, PARALLELIZE)
count := 0
failed := 0
for {
var zone, machine, image, name string
n, err := fmt.Fscanf(r, "%s %s %s %s", &zone, &machine, &image, &name)
if err == io.EOF || n == 0 {
break
} else if err != nil {
return err
} else if n != 4 {
return errors.New(fmt.Sprintf("Wrong number of values (got %d, expected 4)\n", n))
}
go func(zone, machine, image, name string) {
err := d.onInstance(zone, machine, image, name)
if err != nil {
fmt.Fprintf(os.Stderr, "❌ Operation failed for %v (%v, %v, %v): %v\n", name, zone, machine, image, err)
}
com <- err
}(zone, machine, image, name)
count += 1
}
for count > 0 {
err := <- com
count -= 1
if err != nil {
failed += 1
}
fmt.Fprintf(os.Stdout, " Waiting for %v more servers\n", count)
}
if failed > 0 {
return errors.New(fmt.Sprintf("%d operations failed", failed))
}
return nil
}
/**
* Spawner utility
*/
type spawner struct {
api *instance.API
}
func newSpawner() (spawner, error) {
client, err := getClient()
if err != nil {
return spawner{}, err
}
return spawner{
api: instance.NewAPI(client),
}, nil
}
func (sp *spawner) getInstanceByName(zone scw.Zone, name string) (*instance.Server, error) {
lr, err := sp.api.ListServers(&instance.ListServersRequest{
Zone: zone,
Name: &name,
})
if err != nil {
return nil, err
}
for _, s := range lr.Servers {
if s.Name == name {
return s, nil
}
}
return nil, instanceNotFound
}
func (sp *spawner) onInstance(zone, machine, image, name string) error {
z, err := scw.ParseZone(zone)
if err != nil {
return err
}
targetServer, err := sp.getInstanceByName(z, name)
if err == nil {
ip := parseIP(targetServer)
if targetServer.State == instance.ServerStateRunning {
fmt.Fprintf(os.Stdout, "🟣 Found %v on zone %v with ip %v\n", targetServer.Name, targetServer.Zone, ip)
return nil
}
} else if err == instanceNotFound {
// Create a new server
createRes, err := sp.api.CreateServer(&instance.CreateServerRequest{
Zone: z,
Name: name,
CommercialType: machine,
Image: image,
DynamicIPRequired: scw.BoolPtr(true),
})
if err != nil {
return err
}
targetServer = createRes.Server
} else {
return err
}
timeout := 5 * time.Minute
err = sp.api.ServerActionAndWait(&instance.ServerActionAndWaitRequest{
ServerID: targetServer.ID,
Action: instance.ServerActionPoweron,
Zone: z,
Timeout: &timeout,
})
if err != nil {
return err
}
targetServer, err = sp.getInstanceByName(z, name)
if err != nil {
return err
}
ip := parseIP(targetServer)
fmt.Fprintf(os.Stdout, "✅ Started %v on zone %v with ip %v\n", targetServer.Name, targetServer.Zone, ip)
return nil
}
func parseIP(s *instance.Server) string {
ip := "(no address)"
if s.PublicIP != nil {
ip = s.PublicIP.Address.String()
if s.PublicIP.Dynamic {
ip += " (dynamic)"
}
} else if s.PrivateIP != nil {
ip = fmt.Sprintf("%v %v", s.PrivateIP, "(private)")
}
return ip
}
/**
* Commands
*/
func spawn() error {
sp, err := newSpawner()
if err != nil {
return err
}
err = passInstanceTo(os.Stdin, &sp)
if err != nil {
return err
}
return nil
}
func run() error {
return nil
}
func destroy() error {
return nil
}
func getClient() (*scw.Client, error) {
// Get config
// Install scw: https://github.com/scaleway/scaleway-cli
// And run `scw init` to create the config file
config, err := scw.LoadConfig()
if err != nil {
return nil, err
}
// Use active profile
profile, err := config.GetActiveProfile()
if err != nil {
return nil, err
}
// We use the default profile
client, err := scw.NewClient(
scw.WithProfile(profile),
scw.WithEnv(), // env variable may overwrite profile values
)
return client, err
}