mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-19 09:16:29 +00:00
Add Scaleway support for linuxkit command line tool
Signed-off-by: Patrik Cyvoct <patrik@ptrk.io>
This commit is contained in:
parent
f8d399490e
commit
a6783261f3
@ -19,6 +19,7 @@ func pushUsage() {
|
|||||||
fmt.Printf(" gcp\n")
|
fmt.Printf(" gcp\n")
|
||||||
fmt.Printf(" openstack\n")
|
fmt.Printf(" openstack\n")
|
||||||
fmt.Printf(" packet\n")
|
fmt.Printf(" packet\n")
|
||||||
|
fmt.Printf(" scaleway\n")
|
||||||
fmt.Printf(" vcenter\n")
|
fmt.Printf(" vcenter\n")
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
fmt.Printf("'options' are the backend specific options.\n")
|
fmt.Printf("'options' are the backend specific options.\n")
|
||||||
@ -44,6 +45,8 @@ func push(args []string) {
|
|||||||
pushOpenstack(args[1:])
|
pushOpenstack(args[1:])
|
||||||
case "packet":
|
case "packet":
|
||||||
pushPacket(args[1:])
|
pushPacket(args[1:])
|
||||||
|
case "scaleway":
|
||||||
|
pushScaleway(args[1:])
|
||||||
case "vcenter":
|
case "vcenter":
|
||||||
pushVCenter(args[1:])
|
pushVCenter(args[1:])
|
||||||
case "help", "-h", "-help", "--help":
|
case "help", "-h", "-help", "--help":
|
||||||
|
104
src/cmd/linuxkit/push_scaleway.go
Normal file
104
src/cmd/linuxkit/push_scaleway.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func pushScaleway(args []string) {
|
||||||
|
flags := flag.NewFlagSet("scaleway", flag.ExitOnError)
|
||||||
|
invoked := filepath.Base(os.Args[0])
|
||||||
|
flags.Usage = func() {
|
||||||
|
fmt.Printf("USAGE: %s push scaleway [options] path\n\n", invoked)
|
||||||
|
fmt.Printf("'path' is the full path to an EFI ISO image. It will be copied to a new Scaleway instance in order to create a Scaeway image out of it.\n")
|
||||||
|
fmt.Printf("Options:\n\n")
|
||||||
|
flags.PrintDefaults()
|
||||||
|
}
|
||||||
|
nameFlag := flags.String("img-name", "", "Overrides the name used to identify the image name in Scaleway's images. Defaults to the base of 'path' with the '.iso' suffix removed")
|
||||||
|
tokenFlag := flags.String("token", "", "Token to connet to Scaleway API")
|
||||||
|
sshKeyFlag := flags.String("ssh-key", os.Getenv("HOME")+"/.ssh/id_rsa", "SSH key file")
|
||||||
|
instanceIDFlag := flags.String("instance-id", "", "Instance ID of a running Scaleway instance, with a second volume.")
|
||||||
|
deviceNameFlag := flags.String("device-name", "/dev/vdb", "Device name on which the image will be copied")
|
||||||
|
regionFlag := flags.String("region", defaultScalewayRegion, "Select scaleway region")
|
||||||
|
noCleanFlag := flags.Bool("no-clean", false, "Do not remove temporary instance and volumes")
|
||||||
|
|
||||||
|
if err := flags.Parse(args); err != nil {
|
||||||
|
log.Fatal("Unable to parse args")
|
||||||
|
}
|
||||||
|
|
||||||
|
remArgs := flags.Args()
|
||||||
|
if len(remArgs) == 0 {
|
||||||
|
fmt.Printf("Please specify the path to the image to push\n")
|
||||||
|
flags.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
path := remArgs[0]
|
||||||
|
|
||||||
|
name := getStringValue(scalewayNameVar, *nameFlag, "")
|
||||||
|
token := getStringValue(tokenVar, *tokenFlag, "")
|
||||||
|
sshKeyFile := getStringValue(sshKeyVar, *sshKeyFlag, "")
|
||||||
|
instanceID := getStringValue(instanceIDVar, *instanceIDFlag, "")
|
||||||
|
deviceName := getStringValue(deviceNameVar, *deviceNameFlag, "")
|
||||||
|
region := getStringValue(regionVar, *regionFlag, defaultScalewayRegion)
|
||||||
|
|
||||||
|
const suffix = ".iso"
|
||||||
|
if name == "" {
|
||||||
|
name = strings.TrimSuffix(path, suffix)
|
||||||
|
name = filepath.Base(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := NewScalewayClient(token, region)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to connect to Scaleway: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no instanceID is provided, we create the instance
|
||||||
|
if instanceID == "" {
|
||||||
|
instanceID, err = client.CreateInstance()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error creating a Scaleway instance: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.BootInstanceAndWait(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error booting instance: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeID, err := client.GetSecondVolumeID(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error retrieving second volume ID: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.CopyImageToInstance(instanceID, path, sshKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error copying ISO file to Scaleway's instance: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.WriteImageToVolume(instanceID, deviceName)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error writing ISO file to additional volume: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.TerminateInstance(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error terminating Scaleway's instance: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.CreateScalewayImage(instanceID, volumeID, name)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error creating Scaleway image: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !*noCleanFlag {
|
||||||
|
err = client.DeleteInstanceAndVolumes(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error deleting Scaleway instance and volumes: %v")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,7 @@ func runUsage() {
|
|||||||
fmt.Printf(" openstack\n")
|
fmt.Printf(" openstack\n")
|
||||||
fmt.Printf(" packet\n")
|
fmt.Printf(" packet\n")
|
||||||
fmt.Printf(" qemu [linux]\n")
|
fmt.Printf(" qemu [linux]\n")
|
||||||
|
fmt.Printf(" scaleway\n")
|
||||||
fmt.Printf(" vbox\n")
|
fmt.Printf(" vbox\n")
|
||||||
fmt.Printf(" vcenter\n")
|
fmt.Printf(" vcenter\n")
|
||||||
fmt.Printf(" vmware\n")
|
fmt.Printf(" vmware\n")
|
||||||
@ -62,6 +63,8 @@ func run(args []string) {
|
|||||||
runPacket(args[1:])
|
runPacket(args[1:])
|
||||||
case "qemu":
|
case "qemu":
|
||||||
runQemu(args[1:])
|
runQemu(args[1:])
|
||||||
|
case "scaleway":
|
||||||
|
runScaleway(args[1:])
|
||||||
case "vmware":
|
case "vmware":
|
||||||
runVMware(args[1:])
|
runVMware(args[1:])
|
||||||
case "vbox":
|
case "vbox":
|
||||||
|
94
src/cmd/linuxkit/run_scaleway.go
Normal file
94
src/cmd/linuxkit/run_scaleway.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultScalewayInstanceType = "VC1S"
|
||||||
|
defaultScalewayRegion = "par1"
|
||||||
|
|
||||||
|
scalewayNameVar = "SCW_IMAGE_NAME" // non-standard
|
||||||
|
tokenVar = "SCW_TOKEN" // non-standard
|
||||||
|
sshKeyVar = "SCW_SSH_KEY_FILE" // non-standard
|
||||||
|
instanceIDVar = "SCW_INSTANCE_ID" // non-standard
|
||||||
|
deviceNameVar = "SCW_DEVICE_NAME" // non-standard
|
||||||
|
regionVar = "SCW_TARGET_REGION"
|
||||||
|
|
||||||
|
instanceTypeVar = "SCW_RUN_TYPE" // non-standard
|
||||||
|
)
|
||||||
|
|
||||||
|
func runScaleway(args []string) {
|
||||||
|
flags := flag.NewFlagSet("scaleway", flag.ExitOnError)
|
||||||
|
invoked := filepath.Base(os.Args[0])
|
||||||
|
flags.Usage = func() {
|
||||||
|
fmt.Printf("USAGE: %s run scaleway [options] [name]\n\n", invoked)
|
||||||
|
fmt.Printf("'name' is the name of a Scaleway image that has alread \n")
|
||||||
|
fmt.Printf("been uploaded using 'linuxkit push'\n\n")
|
||||||
|
fmt.Printf("Options:\n\n")
|
||||||
|
flags.PrintDefaults()
|
||||||
|
}
|
||||||
|
instanceTypeFlag := flags.String("instance-type", defaultScalewayInstanceType, "Scaleway instance type")
|
||||||
|
instanceNameFlag := flags.String("instance-name", "linuxkit", "Name of the create instance, default to the image name")
|
||||||
|
tokenFlag := flags.String("token", "", "Token to connect to Scaleway API")
|
||||||
|
regionFlag := flags.String("region", defaultScalewayRegion, "Select Scaleway region")
|
||||||
|
cleanFlag := flags.Bool("clean", false, "Remove instance")
|
||||||
|
noAttachFlag := flags.Bool("no-attach", false, "Don't attach to serial port, you will have to connect to instance manually")
|
||||||
|
|
||||||
|
if err := flags.Parse(args); err != nil {
|
||||||
|
log.Fatal("Unable to parse args")
|
||||||
|
}
|
||||||
|
|
||||||
|
remArgs := flags.Args()
|
||||||
|
if len(remArgs) == 0 {
|
||||||
|
fmt.Printf("Please specify the name of the image to boot\n")
|
||||||
|
flags.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
name := remArgs[0]
|
||||||
|
|
||||||
|
instanceType := getStringValue(instanceTypeVar, *instanceTypeFlag, defaultScalewayInstanceType)
|
||||||
|
instanceName := getStringValue("", *instanceNameFlag, name)
|
||||||
|
token := getStringValue(tokenVar, *tokenFlag, "")
|
||||||
|
region := getStringValue(regionVar, *regionFlag, defaultScalewayRegion)
|
||||||
|
|
||||||
|
client, err := NewScalewayClient(token, region)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to connect to Scaleway: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceID, err := client.CreateLinuxkitInstance(instanceName, name, instanceType)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to create Scaleway instance: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.BootInstance(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to boot Scaleway instance: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !*noAttachFlag {
|
||||||
|
err = client.ConnectSerialPort(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to connect to serial port: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if *cleanFlag {
|
||||||
|
err = client.TerminateInstance(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to stop instance: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.DeleteInstanceAndVolumes(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to delete instance: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
523
src/cmd/linuxkit/scaleway.go
Normal file
523
src/cmd/linuxkit/scaleway.go
Normal file
@ -0,0 +1,523 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
gotty "github.com/moul/gotty-client"
|
||||||
|
scw "github.com/scaleway/go-scaleway"
|
||||||
|
"github.com/scaleway/go-scaleway/logger"
|
||||||
|
"github.com/scaleway/go-scaleway/types"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ScalewayClient contains state required for communication with Scaleway as well as the instance
|
||||||
|
type ScalewayClient struct {
|
||||||
|
api *scw.ScalewayAPI
|
||||||
|
fileName string
|
||||||
|
region string
|
||||||
|
sshConfig *ssh.ClientConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScalewayConfig contains required field to read scaleway config file
|
||||||
|
type ScalewayConfig struct {
|
||||||
|
Organization string `json:"organization"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewScalewayClient creates a new scaleway client
|
||||||
|
func NewScalewayClient(token, region string) (*ScalewayClient, error) {
|
||||||
|
log.Debugf("Connecting to Scaleway")
|
||||||
|
organization := ""
|
||||||
|
if token == "" {
|
||||||
|
log.Debugf("Using .scwrc file to get token")
|
||||||
|
homeDir := os.Getenv("HOME")
|
||||||
|
if homeDir == "" {
|
||||||
|
homeDir = os.Getenv("USERPROFILE") // Windows support
|
||||||
|
}
|
||||||
|
if homeDir == "" {
|
||||||
|
return nil, fmt.Errorf("Home directory not found")
|
||||||
|
}
|
||||||
|
swrcPath := filepath.Join(homeDir, ".scwrc")
|
||||||
|
|
||||||
|
file, err := ioutil.ReadFile(swrcPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error reading Scaleway config file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var scalewayConfig ScalewayConfig
|
||||||
|
err = json.Unmarshal(file, &scalewayConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error during unmarshal of Scaleway config file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token = scalewayConfig.Token
|
||||||
|
organization = scalewayConfig.Organization
|
||||||
|
}
|
||||||
|
|
||||||
|
api, err := scw.NewScalewayAPI(organization, token, "", region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l := logger.NewDisableLogger()
|
||||||
|
api.Logger = l
|
||||||
|
|
||||||
|
if organization == "" {
|
||||||
|
organisations, err := api.GetOrganization()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
api.Organization = organisations.Organizations[0].ID
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &ScalewayClient{
|
||||||
|
api: api,
|
||||||
|
fileName: "",
|
||||||
|
region: region,
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateInstance create an instance with one additional volume
|
||||||
|
func (s *ScalewayClient) CreateInstance() (string, error) {
|
||||||
|
// get the Ubuntu Xenial image id
|
||||||
|
image, err := s.api.GetImageID("Ubuntu Xenial", "x86_64") // TODO fix arch and use from args
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
imageID := image.Identifier
|
||||||
|
|
||||||
|
var serverDefinition types.ScalewayServerDefinition
|
||||||
|
|
||||||
|
serverDefinition.Name = "linuxkit-builder"
|
||||||
|
serverDefinition.Image = &imageID
|
||||||
|
serverDefinition.CommercialType = "VC1M" // TODO use args?
|
||||||
|
|
||||||
|
// creation of second volume
|
||||||
|
var volumeDefinition types.ScalewayVolumeDefinition
|
||||||
|
volumeDefinition.Name = "linuxkit-builder-volume"
|
||||||
|
volumeDefinition.Size = 50000000000 // FIX remove hardcoded value
|
||||||
|
volumeDefinition.Type = "l_ssd"
|
||||||
|
|
||||||
|
log.Debugf("Creating volume on Scaleway")
|
||||||
|
volumeID, err := s.api.PostVolume(volumeDefinition)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
serverDefinition.Volumes = make(map[string]string)
|
||||||
|
serverDefinition.Volumes["1"] = volumeID
|
||||||
|
|
||||||
|
serverID, err := s.api.PostServer(serverDefinition)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Created server %s on Scaleway", serverID)
|
||||||
|
return serverID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSecondVolumeID returns the ID of the second volume of the server
|
||||||
|
func (s *ScalewayClient) GetSecondVolumeID(instanceID string) (string, error) {
|
||||||
|
server, err := s.api.GetServer(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
secondVolume, ok := server.Volumes["1"]
|
||||||
|
if !ok {
|
||||||
|
return "", errors.New("No second volume found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return secondVolume.Identifier, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootInstanceAndWait boots and wait for instance to be booted
|
||||||
|
func (s *ScalewayClient) BootInstanceAndWait(instanceID string) error {
|
||||||
|
err := s.api.PostServerAction(instanceID, "poweron")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Waiting for server %s to be started", instanceID)
|
||||||
|
|
||||||
|
// code taken from scaleway-cli, could need some changes
|
||||||
|
promise := make(chan bool)
|
||||||
|
var server *types.ScalewayServer
|
||||||
|
var currentState string
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(promise)
|
||||||
|
|
||||||
|
for {
|
||||||
|
server, err = s.api.GetServer(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
promise <- false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentState != server.State {
|
||||||
|
currentState = server.State
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.State == "running" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if server.State == "stopped" {
|
||||||
|
promise <- false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := server.PublicAddress.IP
|
||||||
|
if ip == "" && server.EnableIPV6 {
|
||||||
|
ip = fmt.Sprintf("[%s]", server.IPV6.Address)
|
||||||
|
}
|
||||||
|
dest := fmt.Sprintf("%s:22", ip)
|
||||||
|
for {
|
||||||
|
conn, err := net.Dial("tcp", dest)
|
||||||
|
if err == nil {
|
||||||
|
defer conn.Close()
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
promise <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
loop := 0
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case done := <-promise:
|
||||||
|
if !done {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debugf("Server %s started", instanceID)
|
||||||
|
return nil
|
||||||
|
case <-time.After(time.Millisecond * 100):
|
||||||
|
loop = loop + 1
|
||||||
|
if loop == 5 {
|
||||||
|
loop = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSSHAuth is uses to get the ssh.Signer needed to connect via SSH
|
||||||
|
func getSSHAuth(sshKeyPath string) (ssh.Signer, error) {
|
||||||
|
f, err := os.Open(sshKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
signer, err := ssh.ParsePrivateKey(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return signer, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyImageToInstance copies the image to the instance via ssh
|
||||||
|
func (s *ScalewayClient) CopyImageToInstance(instanceID, path, sshKeyPath string) error {
|
||||||
|
_, base := filepath.Split(path)
|
||||||
|
s.fileName = base
|
||||||
|
|
||||||
|
server, err := s.api.GetServer(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, err := getSSHAuth(sshKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.sshConfig = &ssh.ClientConfig{
|
||||||
|
User: "root",
|
||||||
|
Auth: []ssh.AuthMethod{
|
||||||
|
ssh.PublicKeys(signer),
|
||||||
|
},
|
||||||
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // TODO validate server before?
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := ssh.Dial("tcp", server.PublicAddress.IP+":22", s.sshConfig) // TODO remove hardocoded port?
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// code taken from bramvdbogaerde/go-scp
|
||||||
|
contentBytes, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bytesReader := bytes.NewReader(contentBytes)
|
||||||
|
|
||||||
|
log.Infof("Starting to upload %s on server", base)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
w, err := session.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
fmt.Fprintln(w, "C0600", int64(len(contentBytes)), base)
|
||||||
|
io.Copy(w, bytesReader)
|
||||||
|
fmt.Fprintln(w, "\x00")
|
||||||
|
}()
|
||||||
|
|
||||||
|
session.Run("/usr/bin/scp -t /root/") // TODO remove hardcoded remote path?
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteImageToVolume does a dd command on the remote instance via ssh
|
||||||
|
func (s *ScalewayClient) WriteImageToVolume(instanceID, deviceName string) error {
|
||||||
|
server, err := s.api.GetServer(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := ssh.Dial("tcp", server.PublicAddress.IP+":22", s.sshConfig) // TODO remove hardcoded port + use the same dial as before?
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
var ddPathBuf bytes.Buffer
|
||||||
|
session.Stdout = &ddPathBuf
|
||||||
|
|
||||||
|
err = session.Run("which dd") // get the right path
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err = client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
ddCommand := strings.Trim(ddPathBuf.String(), " \n")
|
||||||
|
command := fmt.Sprintf("%s if=%s of=%s", ddCommand, s.fileName, deviceName)
|
||||||
|
|
||||||
|
log.Infof("Starting writing iso to disk")
|
||||||
|
|
||||||
|
err = session.Run(command)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("ISO image written to disk")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TerminateInstance terminates the instance and wait for termination
|
||||||
|
func (s *ScalewayClient) TerminateInstance(instanceID string) error {
|
||||||
|
server, err := s.api.GetServer(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Shutting down server %s", instanceID)
|
||||||
|
|
||||||
|
err = s.api.PostServerAction(server.Identifier, "poweroff")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// code taken from scaleway-cli
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
|
||||||
|
var currentState string
|
||||||
|
|
||||||
|
log.Debugf("Waiting for server to shutdown")
|
||||||
|
|
||||||
|
for {
|
||||||
|
server, err = s.api.GetServer(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if currentState != server.State {
|
||||||
|
currentState = server.State
|
||||||
|
}
|
||||||
|
if server.State == "stopped" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateScalewayImage creates the image and delete old image and snapshot if same name
|
||||||
|
func (s *ScalewayClient) CreateScalewayImage(instanceID, volumeID, name string) error {
|
||||||
|
oldImage, err := s.api.GetImageID(name, "x86_64")
|
||||||
|
if err == nil {
|
||||||
|
err = s.api.DeleteImage(oldImage.Identifier)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oldSnapshot, err := s.api.GetSnapshotID(name)
|
||||||
|
if err == nil {
|
||||||
|
err := s.api.DeleteSnapshot(oldSnapshot)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshotID, err := s.api.PostSnapshot(volumeID, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
imageID, err := s.api.PostImage(snapshotID, name, "", "x86_64") // TODO remove hardcoded arch
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Image %s with ID %s created", name, imageID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteInstanceAndVolumes deletes the instance and the volumes attached
|
||||||
|
func (s *ScalewayClient) DeleteInstanceAndVolumes(instanceID string) error {
|
||||||
|
server, err := s.api.GetServer(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.api.DeleteServer(instanceID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, volume := range server.Volumes {
|
||||||
|
err = s.api.DeleteVolume(volume.Identifier)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Server %s deleted", instanceID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLinuxkitInstance creates an instance with the given linuxkit image
|
||||||
|
func (s *ScalewayClient) CreateLinuxkitInstance(instanceName, imageName, instanceType string) (string, error) {
|
||||||
|
// get the image ID
|
||||||
|
image, err := s.api.GetImageID(imageName, "x86_64") // TODO fix arch and use from args
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
imageID := image.Identifier
|
||||||
|
|
||||||
|
var serverDefinition types.ScalewayServerDefinition
|
||||||
|
|
||||||
|
serverDefinition.Name = instanceName
|
||||||
|
serverDefinition.Image = &imageID
|
||||||
|
serverDefinition.CommercialType = instanceType
|
||||||
|
serverDefinition.BootType = "local"
|
||||||
|
|
||||||
|
log.Debugf("Creating volume on Scaleway")
|
||||||
|
|
||||||
|
log.Debugf("Creating server %s on Scaleway", serverDefinition.Name)
|
||||||
|
serverID, err := s.api.PostServer(serverDefinition)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootInstance boots the specified instance, and don't wait
|
||||||
|
func (s *ScalewayClient) BootInstance(instanceID string) error {
|
||||||
|
err := s.api.PostServerAction(instanceID, "poweron")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectSerialPort connects to the serial port of the instance
|
||||||
|
func (s *ScalewayClient) ConnectSerialPort(instanceID string) error {
|
||||||
|
var gottyURL string
|
||||||
|
switch s.region {
|
||||||
|
case "par1":
|
||||||
|
gottyURL = "https://tty-par1.scaleway.com/v2/"
|
||||||
|
case "ams1":
|
||||||
|
gottyURL = "https://tty-ams1.scaleway.com/"
|
||||||
|
default:
|
||||||
|
return errors.New("Instance have no region")
|
||||||
|
}
|
||||||
|
|
||||||
|
fullURL := fmt.Sprintf("%s?arg=%s&arg=%s", gottyURL, s.api.Token, instanceID)
|
||||||
|
|
||||||
|
log.Debugf("Connection to ", fullURL)
|
||||||
|
gottyClient, err := gotty.NewClient(fullURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gottyClient.SkipTLSVerify = true
|
||||||
|
|
||||||
|
gottyClient.UseProxyFromEnv = true
|
||||||
|
|
||||||
|
err = gottyClient.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
|
|
||||||
|
fmt.Println("You are connected, type 'Ctrl+q' to quit.")
|
||||||
|
go func() {
|
||||||
|
err = gottyClient.Loop()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("ERROR: " + err.Error())
|
||||||
|
}
|
||||||
|
//gottyClient.Close()
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
<-done
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user