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 }