Compare commits

..

No commits in common. "main" and "feature/destroy" have entirely different histories.

4 changed files with 5 additions and 205 deletions

View file

@ -1,37 +0,0 @@
## nuage
`nuage` is a tool to deploy instances on scaleway.
You can see it as a basic re-implementation of a tool like Terraform, specially tailored for my needs/workflow.
you will need Scaleway's command line tool to setup your tokens.
- [install it](https://github.com/scaleway/scaleway-cli#installation)
- run `scw init`
nuage is idempotent, so if you run twice the same command, it will not fail but just say that the operation has already be done.
nuage, and not Scaleway, requires that instance's name are uniques to provide this idempotency feature.
## install
1. Install go on your machine (cf golang.org)
2. Clone this repository
3. Run `go build`
## usage
Now, you can create your own inventory, the file format is basic: `<zone> <instance model> <image> <name>`.
See the committed `inventory.txt` for an example.
Then it could be used as follow:
```
./nuage spawn < inventory.txt
./nuage run <(echo "touch /etc/nuage.txt") < inventory.txt
./nuage destroy < inventory.txt
```
1. It will spawn the 2 instances defined in the inventory
2. It will run `touch ...` on all instances of the inventory
3. It will power off then delete the instances

6
go.mod
View file

@ -2,8 +2,4 @@ module git.deuxfleurs.fr/quentin/nuage
go 1.16
require (
github.com/pkg/sftp v1.13.3
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
)
require github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7

26
go.sum
View file

@ -1,33 +1,7 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/pkg/sftp v1.13.3 h1:XFSVAvRDGUhzAJ8Ll0APzHx3NTCAnMGaAsd3yi+Oc9k=
github.com/pkg/sftp v1.13.3/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

141
main.go
View file

@ -1,19 +1,12 @@
package main
import (
"bytes"
"io"
"net"
"os"
"fmt"
"errors"
"time"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"github.com/pkg/sftp"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
)
@ -141,6 +134,8 @@ func (i *action) getInstanceByName(zone scw.Zone, name string) (*instance.Server
return nil, instanceNotFound
}
func parseIP(s *instance.Server) string {
ip := "(no address)"
if s.PublicIP != nil {
@ -180,7 +175,7 @@ func (sp *spawner) onInstance(zone, machine, image, name string) error {
Name: name,
CommercialType: machine,
Image: image,
DynamicIPRequired: scw.BoolPtr(true),
DynamicIPRequired: scw.BoolPtr(false),
})
if err != nil {
return err
@ -252,127 +247,6 @@ func (dt *destroyer) onInstance(zone, machine, image, name string) error {
return nil
}
/**
* Runner
*/
type runner struct { action }
func (r *runner) connect(zone, name string) (*ssh.Client, error) {
// Connect to the remote
z, err := scw.ParseZone(zone)
if err != nil {
return nil, err
}
targetServer, err := r.getInstanceByName(z, name)
if err != nil {
return nil, err
}
if targetServer.PublicIP == nil {
return nil, errors.New("run failed: this instance has no public ip.")
}
ip := targetServer.PublicIP.Address
socket := os.Getenv("SSH_AUTH_SOCK")
conn, err := net.Dial("unix", socket)
if err != nil {
return nil, err
}
agentClient := agent.NewClient(conn)
config := &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{
ssh.PublicKeysCallback(agentClient.Signers),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
return ssh.Dial("tcp", ip.String()+":22", config)
}
func (r *runner) send(sshClient *ssh.Client) error {
// Source script
if len(os.Args) < 3 {
return errors.New("Missing script to run on the command line")
}
src, err := os.Open(os.Args[2])
if err != nil {
return err
}
defer src.Close()
// Send our script to the destination
sftpClient, err := sftp.NewClient(sshClient)
if err != nil {
return err
}
defer sftpClient.Close()
dst, err := sftpClient.Create("/tmp/nuage")
if err != nil {
return err
}
defer dst.Close()
err = dst.Chmod(0755)
if err != nil {
return err
}
_, err = io.Copy(dst, src)
return err
}
func (r *runner) exec(sshClient *ssh.Client) error {
// Run the script
session, err := sshClient.NewSession()
if err != nil {
return err
}
defer session.Close()
var sshStdout, sshStderr bytes.Buffer
session.Stdout = &sshStdout
session.Stderr = &sshStderr
err = session.Run("/tmp/nuage")
if os.Getenv("VERBOSE") != "" {
fmt.Fprintf(os.Stdout, "logs\n")
io.Copy(os.Stdout, &sshStdout)
io.Copy(os.Stderr, &sshStderr)
}
if err != nil {
return err
}
return nil
}
func (r *runner) onInstance(zone, machine, image, name string) error {
sshClient, err := r.connect(zone, name)
if err != nil {
return err
}
defer sshClient.Close()
err = r.send(sshClient)
if err != nil {
return err
}
err = r.exec(sshClient)
if err != nil {
return err
}
fmt.Fprintf(os.Stdout, "✅ Successfully ran the script on %v (zone %v)\n", name, zone)
return nil
}
/**
* Commands
@ -390,14 +264,7 @@ func spawn() error {
}
func run() error {
r := runner{}
err := r.init()
if err != nil {
return err
}
err = passInstanceTo(os.Stdin, &r)
return err
return nil
}
func destroy() error {