Initial commit
This commit is contained in:
commit
248346d980
5 changed files with 271 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
nuage
|
5
go.mod
Normal file
5
go.mod
Normal file
|
@ -0,0 +1,5 @@
|
|||
module git.deuxfleurs.fr/quentin/nuage
|
||||
|
||||
go 1.16
|
||||
|
||||
require github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7
|
7
go.sum
Normal file
7
go.sum
Normal file
|
@ -0,0 +1,7 @@
|
|||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7 h1:Do8ksLD4Nr3pA0x0hnLOLftZgkiTDvwPDShRTUxtXpE=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
2
inventory.txt
Normal file
2
inventory.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
fr-par-1 dev1-s debian_bullseye géronimo
|
||||
pl-waw-1 dev1-s debian_bullseye gorinémo
|
256
main.go
Normal file
256
main.go
Normal file
|
@ -0,0 +1,256 @@
|
|||
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
|
||||
}
|
Loading…
Reference in a new issue