scaleway: Fix bugs and add option for changing image size

* Fix using ams1 as zone
* Allow specifying image size (+ calculate default from ISO size)
* Fix mangling logs when asking for ssh passphrase
* Some minor code and docs cleanups

Signed-off-by: Karol Woźniak <wozniakk@gmail.com>
This commit is contained in:
Karol Woźniak 2020-05-01 01:15:24 +02:00
parent c750f54cb0
commit d861987b79
4 changed files with 88 additions and 58 deletions

View File

@ -4,8 +4,11 @@ This is a quick guide to run LinuxKit on Scaleway (only VPS x86_64 for now)
## Setup
You must create a Scaleway Secret Key (available ine the [Scaleway Console](https://console.scaleway.com/account/credentials)) first.
Then you can use it either with the `SCW_SECRET_KEY` environment variable or with the `-secret-key` flag of the `linuxkit push scaleway` and `linuxkit run scaleway` commands.
You must create a Scaleway API Token (combination of Access and Secret Key), available at [Scaleway Console](https://console.scaleway.com/account/credentials), first.
Then you can use it either with the `SCW_ACCESS_KEY` and `SCW_SECRET_KEY` environment variables or the `-access-key` and `-secret-key` flags
of the `linuxkit push scaleway` and `linuxkit run scaleway` commands.
In addition, Organization ID value has to be set, either with the `SCW_DEFAULT_ORGANIZATION_ID` environment variable or the `-organization-id` command line flag.
The environment variable `SCW_DEFAULT_ZONE` is used to set the zone (there is also the `-zone` flag)

View File

@ -3,6 +3,7 @@ package main
import (
"flag"
"fmt"
"math"
"os"
"path/filepath"
"strings"
@ -10,6 +11,8 @@ import (
log "github.com/sirupsen/logrus"
)
const defaultScalewayVolumeSize = 10 // GB
func pushScaleway(args []string) {
flags := flag.NewFlagSet("scaleway", flag.ExitOnError)
invoked := filepath.Base(os.Args[0])
@ -20,12 +23,14 @@ func pushScaleway(args []string) {
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")
secretKeyFlag := flags.String("secret-key", "", "Secret Key to connet to Scaleway API")
accessKeyFlag := flags.String("access-key", "", "Access Key to connect to Scaleway API")
secretKeyFlag := flags.String("secret-key", "", "Secret Key to connect 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")
volumeSizeFlag := flags.Int("volume-size", 0, "Size of the volume to use (in GB). Defaults to size of the ISO file rounded up to GB")
zoneFlag := flags.String("zone", defaultScalewayZone, "Select Scaleway zone")
projectIDFlag := flags.String("project-id", "", "Select Scaleway's project ID")
organizationIDFlag := flags.String("organization-id", "", "Select Scaleway's organization ID")
noCleanFlag := flags.Bool("no-clean", false, "Do not remove temporary instance and volumes")
if err := flags.Parse(args); err != nil {
@ -41,12 +46,14 @@ func pushScaleway(args []string) {
path := remArgs[0]
name := getStringValue(scalewayNameVar, *nameFlag, "")
accessKey := getStringValue(accessKeyVar, *accessKeyFlag, "")
secretKey := getStringValue(secretKeyVar, *secretKeyFlag, "")
sshKeyFile := getStringValue(sshKeyVar, *sshKeyFlag, "")
instanceID := getStringValue(instanceIDVar, *instanceIDFlag, "")
deviceName := getStringValue(deviceNameVar, *deviceNameFlag, "")
volumeSize := getIntValue(volumeSizeVar, *volumeSizeFlag, 0)
zone := getStringValue(zoneVar, *zoneFlag, defaultScalewayZone)
projectID := getStringValue(projectIDVar, *projectIDFlag, "")
organizationID := getStringValue(organizationIDVar, *organizationIDFlag, "")
const suffix = ".iso"
if name == "" {
@ -54,14 +61,25 @@ func pushScaleway(args []string) {
name = filepath.Base(name)
}
client, err := NewScalewayClient(secretKey, zone, projectID)
client, err := NewScalewayClient(accessKey, secretKey, zone, organizationID)
if err != nil {
log.Fatalf("Unable to connect to Scaleway: %v", err)
}
// if volume size not set, try to calculate it from file size
if volumeSize == 0 {
if fi, err := os.Stat(path); err == nil {
volumeSize = int(math.Ceil(float64(fi.Size()) / 1000000000)) // / 1 GB
} else {
// fallback to default
log.Warnf("Unable to calculate volume size, using default of %d GB: %v", defaultScalewayVolumeSize, err)
volumeSize = defaultScalewayVolumeSize
}
}
// if no instanceID is provided, we create the instance
if instanceID == "" {
instanceID, err = client.CreateInstance()
instanceID, err = client.CreateInstance(volumeSize)
if err != nil {
log.Fatalf("Error creating a Scaleway instance: %v", err)
}

View File

@ -13,13 +13,15 @@ const (
defaultScalewayInstanceType = "DEV1-S"
defaultScalewayZone = "par1"
scalewayNameVar = "SCW_IMAGE_NAME" // non-standard
secretKeyVar = "SCW_SECRET_KEY" // non-standard
sshKeyVar = "SCW_SSH_KEY_FILE" // non-standard
instanceIDVar = "SCW_INSTANCE_ID" // non-standard
deviceNameVar = "SCW_DEVICE_NAME" // non-standard
scwZoneVar = "SCW_DEFAULT_ZONE"
projectIDVar = "SCW_DEFAULT_PROJECT_ID"
scalewayNameVar = "SCW_IMAGE_NAME" // non-standard
accessKeyVar = "SCW_ACCESS_KEY"
secretKeyVar = "SCW_SECRET_KEY"
sshKeyVar = "SCW_SSH_KEY_FILE" // non-standard
instanceIDVar = "SCW_INSTANCE_ID" // non-standard
deviceNameVar = "SCW_DEVICE_NAME" // non-standard
volumeSizeVar = "SCW_VOLUME_SIZE" // non-standard
scwZoneVar = "SCW_DEFAULT_ZONE"
organizationIDVar = "SCW_DEFAULT_ORGANIZATION_ID"
instanceTypeVar = "SCW_RUN_TYPE" // non-standard
)
@ -29,16 +31,17 @@ func runScaleway(args []string) {
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("'name' is the name of a Scaleway image that has already \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")
accessKeyFlag := flags.String("access-key", "", "Access Key to connect to Scaleway API")
secretKeyFlag := flags.String("secret-key", "", "Secret Key to connect to Scaleway API")
zoneFlag := flags.String("zone", defaultScalewayZone, "Select Scaleway zone")
projectIDFlag := flags.String("project-id", "", "Select Scaleway's project ID")
organizationIDFlag := flags.String("organization-id", "", "Select Scaleway's organization ID")
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")
@ -56,11 +59,12 @@ func runScaleway(args []string) {
instanceType := getStringValue(instanceTypeVar, *instanceTypeFlag, defaultScalewayInstanceType)
instanceName := getStringValue("", *instanceNameFlag, name)
accessKey := getStringValue(accessKeyVar, *accessKeyFlag, "")
secretKey := getStringValue(secretKeyVar, *secretKeyFlag, "")
zone := getStringValue(scwZoneVar, *zoneFlag, defaultScalewayZone)
projectID := getStringValue(projectIDVar, *projectIDFlag, "")
organizationID := getStringValue(organizationIDVar, *organizationIDFlag, "")
client, err := NewScalewayClient(secretKey, zone, projectID)
client, err := NewScalewayClient(accessKey, secretKey, zone, organizationID)
if err != nil {
log.Fatalf("Unable to connect to Scaleway: %v", err)
}

View File

@ -24,12 +24,12 @@ import (
)
var (
defaultScalewayCommercialType = "DEV1-S"
defaultScalewayImageName = "Ubuntu Bionic"
defaultScalewayImageArch = "x86_64"
defaultVolumeSize = scw.GB * 10
scalewayDynamicIPRequired = true
scalewayBootType = instance.BootTypeLocal
defaultScalewayCommercialType = "DEV1-S"
defaultScalewayImageName = "Ubuntu Bionic"
defaultScalewayImageArch = "x86_64"
scalewayDynamicIPRequired = true
scalewayBootType = instance.BootTypeLocal
scalewayInstanceVolumeSize scw.Size = 20
)
// ScalewayClient contains state required for communication with Scaleway as well as the instance
@ -37,16 +37,18 @@ type ScalewayClient struct {
instanceAPI *instance.API
marketplaceAPI *marketplace.API
fileName string
zone string
zone scw.Zone
sshConfig *ssh.ClientConfig
secretKey string
}
// NewScalewayClient creates a new scaleway client
func NewScalewayClient(secretKey, zone, organizationID string) (*ScalewayClient, error) {
var scwClient *scw.Client
func NewScalewayClient(accessKey, secretKey, zone, organizationID string) (*ScalewayClient, error) {
log.Debugf("Connecting to Scaleway")
if secretKey == "" {
scwOptions := []scw.ClientOption{}
if accessKey == "" || secretKey == "" {
config, err := scw.LoadConfig()
if err != nil {
return nil, err
@ -56,36 +58,35 @@ func NewScalewayClient(secretKey, zone, organizationID string) (*ScalewayClient,
return nil, err
}
scwClient, err = scw.NewClient(
scw.WithProfile(profile),
scw.WithEnv(),
)
if err != nil {
return nil, err
scwOptions = append(scwOptions, scw.WithProfile(profile), scw.WithEnv())
if *profile.DefaultZone != "" {
zone = *profile.DefaultZone
}
} else {
scwZone, err := scw.ParseZone(zone)
if err != nil {
return nil, err
}
scwClient, err = scw.NewClient(
scw.WithAuth("", secretKey),
scw.WithDefaultZone(scwZone),
scwOptions = append(
scwOptions,
scw.WithAuth(accessKey, secretKey),
scw.WithDefaultOrganizationID(organizationID),
)
if err != nil {
return nil, err
}
}
scwZone, err := scw.ParseZone(zone)
if err != nil {
return nil, err
}
scwOptions = append(scwOptions, scw.WithDefaultZone(scwZone))
scwClient, err := scw.NewClient(scwOptions...)
if err != nil {
return nil, err
}
instanceAPI := instance.NewAPI(scwClient)
marketplaceAPI := marketplace.NewAPI(scwClient)
client := &ScalewayClient{
instanceAPI: instanceAPI,
marketplaceAPI: marketplaceAPI,
zone: zone,
zone: scwZone,
fileName: "",
secretKey: secretKey,
}
@ -102,7 +103,7 @@ func (s *ScalewayClient) getImageID(imageName, commercialType, arch string) (str
if image.Name == imageName {
for _, version := range image.Versions {
for _, localImage := range version.LocalImages {
if localImage.Arch == arch {
if localImage.Arch == arch && localImage.Zone == s.zone {
for _, compatibleCommercialType := range localImage.CompatibleCommercialTypes {
if compatibleCommercialType == commercialType {
return localImage.ID, nil
@ -117,17 +118,20 @@ func (s *ScalewayClient) getImageID(imageName, commercialType, arch string) (str
}
// CreateInstance create an instance with one additional volume
func (s *ScalewayClient) CreateInstance() (string, error) {
// get the Ubuntu Xenial image id
func (s *ScalewayClient) CreateInstance(volumeSize int) (string, error) {
// get the Ubuntu Bionic image id
imageID, err := s.getImageID(defaultScalewayImageName, defaultScalewayCommercialType, defaultScalewayImageArch)
if err != nil {
return "", err
}
scwVolumeSize := scw.Size(volumeSize)
builderVolumeSize := scwVolumeSize * scw.GB
createVolumeRequest := &instance.CreateVolumeRequest{
Name: "linuxkit-builder-volume",
VolumeType: "l_ssd",
Size: &defaultVolumeSize,
Size: &builderVolumeSize,
}
log.Debugf("Creating volume on Scaleway")
@ -136,11 +140,9 @@ func (s *ScalewayClient) CreateInstance() (string, error) {
return "", err
}
volumeMap := make(map[string]*instance.VolumeTemplate)
volumeTemplate := &instance.VolumeTemplate{
Size: defaultVolumeSize,
volumeMap := map[string]*instance.VolumeTemplate{
"0": {Size: (scalewayInstanceVolumeSize - scwVolumeSize) * scw.GB},
}
volumeMap["0"] = volumeTemplate
createServerRequest := &instance.CreateServerRequest{
Name: "linuxkit-builder",
@ -152,6 +154,7 @@ func (s *ScalewayClient) CreateInstance() (string, error) {
Volumes: volumeMap,
}
log.Debug("Creating server on Scaleway")
serverResp, err := s.instanceAPI.CreateServer(createServerRequest)
if err != nil {
return "", err
@ -268,7 +271,7 @@ func (s *ScalewayClient) BootInstanceAndWait(instanceID string) error {
}
}
// getSSHAuth is uses to get the ssh.Signer needed to connect via SSH
// getSSHAuth gets the ssh.Signer needed to connect via SSH
func getSSHAuth(sshKeyPath string) (ssh.Signer, error) {
f, err := os.Open(sshKeyPath)
if err != nil {
@ -284,6 +287,8 @@ func getSSHAuth(sshKeyPath string) (ssh.Signer, error) {
if err != nil {
fmt.Print("Enter ssh key passphrase: ")
bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
// ReadPassword eats newline, put it back to avoid mangling logs
fmt.Println()
if err != nil {
return nil, err
}
@ -581,9 +586,9 @@ func (s *ScalewayClient) BootInstance(instanceID string) error {
func (s *ScalewayClient) ConnectSerialPort(instanceID string) error {
var gottyURL string
switch s.zone {
case "par1":
case scw.ZoneFrPar1:
gottyURL = "https://tty-par1.scaleway.com/v2/"
case "ams1":
case scw.ZoneNlAms1:
gottyURL = "https://tty-ams1.scaleway.com/"
default:
return errors.New("Instance have no region")