Add Scaleway builder
This commit is contained in:
parent
57faecbdd4
commit
7e36cfcff1
|
@ -0,0 +1,49 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
)
|
||||
|
||||
type Artifact struct {
|
||||
// The name of the snapshot
|
||||
snapshotName string
|
||||
|
||||
// The ID of the snapshot
|
||||
snapshotId string
|
||||
|
||||
// The name of the region
|
||||
regionName string
|
||||
|
||||
// The client for making API calls
|
||||
client *api.ScalewayAPI
|
||||
}
|
||||
|
||||
func (*Artifact) BuilderId() string {
|
||||
return BuilderId
|
||||
}
|
||||
|
||||
func (*Artifact) Files() []string {
|
||||
// No files with Scaleway
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) Id() string {
|
||||
return fmt.Sprintf("%s:%s", a.regionName, a.snapshotId)
|
||||
}
|
||||
|
||||
func (a *Artifact) String() string {
|
||||
return fmt.Sprintf("A snapshot was created: '%v' (ID: %v) in region '%v'", a.snapshotName, a.snapshotId, a.regionName)
|
||||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) Destroy() error {
|
||||
log.Printf("Destroying image: %s (%s)", a.snapshotId, a.snapshotName)
|
||||
err := a.client.DeleteSnapshot(a.snapshotId)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestArtifact_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Artifact{}
|
||||
if _, ok := raw.(packer.Artifact); !ok {
|
||||
t.Fatalf("Artifact should be artifact")
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactId(t *testing.T) {
|
||||
a := &Artifact{"packer-foobar", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil}
|
||||
expected := "ams1:cc586e45-5156-4f71-b223-cf406b10dd1c"
|
||||
|
||||
if a.Id() != expected {
|
||||
t.Fatalf("artifact ID should match: %v", expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactString(t *testing.T) {
|
||||
a := &Artifact{"packer-foobar", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil}
|
||||
expected := "A snapshot was created: 'packer-foobar' (ID: cc586e45-5156-4f71-b223-cf406b10dd1c) in region 'ams1'"
|
||||
|
||||
if a.String() != expected {
|
||||
t.Fatalf("artifact string should match: %v", expected)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
// The scaleway package contains a packer.Builder implementation
|
||||
// that builds Scaleway images (snapshots).
|
||||
|
||||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
)
|
||||
|
||||
// The unique id for the builder
|
||||
const BuilderId = "pearkes.scaleway"
|
||||
|
||||
type Builder struct {
|
||||
config Config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
c, warnings, errs := NewConfig(raws...)
|
||||
if errs != nil {
|
||||
return warnings, errs
|
||||
}
|
||||
b.config = *c
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
client, _ := api.NewScalewayAPI(b.config.Organization, b.config.Token, b.config.UserAgent, b.config.Region)
|
||||
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", b.config)
|
||||
state.Put("client", client)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
steps := []multistep.Step{
|
||||
&stepCreateSSHKey{
|
||||
Debug: b.config.PackerDebug,
|
||||
DebugKeyPath: fmt.Sprintf("scw_%s.pem", b.config.PackerBuildName),
|
||||
},
|
||||
new(stepCreateServer),
|
||||
new(stepServerInfo),
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.Comm,
|
||||
Host: commHost,
|
||||
SSHConfig: sshConfig,
|
||||
},
|
||||
new(common.StepProvision),
|
||||
new(stepShutdown),
|
||||
new(stepSnapshot),
|
||||
new(stepTerminate),
|
||||
}
|
||||
|
||||
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
b.runner.Run(state)
|
||||
|
||||
if rawErr, ok := state.GetOk("error"); ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("snapshot_name"); !ok {
|
||||
log.Println("Failed to find snapshot_name in state. Bug?")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
artifact := &Artifact{
|
||||
snapshotName: state.Get("snapshot_name").(string),
|
||||
snapshotId: state.Get("snapshot_id").(string),
|
||||
regionName: state.Get("region").(string),
|
||||
client: client,
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"api_organization": "foo",
|
||||
"api_token": "bar",
|
||||
"region": "ams1",
|
||||
"commercial_type": "VC1S",
|
||||
"ssh_username": "root",
|
||||
"image": "image-uuid",
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Builder{}
|
||||
if _, ok := raw.(packer.Builder); !ok {
|
||||
t.Fatalf("Builder should be a builder")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_Prepare_BadType(t *testing.T) {
|
||||
b := &Builder{}
|
||||
c := map[string]interface{}{
|
||||
"api_token": []string{},
|
||||
}
|
||||
|
||||
warnings, err := b.Prepare(c)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("prepare should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
config["i_should_not_be_valid"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_Region(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
delete(config, "region")
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("should error")
|
||||
}
|
||||
|
||||
expected := "ams1"
|
||||
|
||||
config["region"] = expected
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.Region != expected {
|
||||
t.Errorf("found %s, expected %s", b.config.Region, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_CommercialType(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
delete(config, "commercial_type")
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("should error")
|
||||
}
|
||||
|
||||
expected := "VC1S"
|
||||
|
||||
config["commercial_type"] = expected
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.CommercialType != expected {
|
||||
t.Errorf("found %s, expected %s", b.config.CommercialType, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_Image(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
delete(config, "image")
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
|
||||
expected := "cc586e45-5156-4f71-b223-cf406b10dd1c"
|
||||
|
||||
config["image"] = expected
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.Image != expected {
|
||||
t.Errorf("found %s, expected %s", b.config.Image, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_SnapshotName(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.SnapshotName == "" {
|
||||
t.Errorf("invalid: %s", b.config.SnapshotName)
|
||||
}
|
||||
|
||||
config["snapshot_name"] = "foobarbaz"
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
config["snapshot_name"] = "{{timestamp}}"
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
_, err = strconv.ParseInt(b.config.SnapshotName, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse int in template: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ServerName(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.ServerName == "" {
|
||||
t.Errorf("invalid: %s", b.config.ServerName)
|
||||
}
|
||||
|
||||
config["server_name"] = "foobar"
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
config["server_name"] = "foobar-{{timestamp}}"
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
config["server_name"] = "foobar-{{"
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/uuid"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
Token string `mapstructure:"api_token"`
|
||||
Organization string `mapstructure:"api_organization"`
|
||||
|
||||
Region string `mapstructure:"region"`
|
||||
Image string `mapstructure:"image"`
|
||||
CommercialType string `mapstructure:"commercial_type"`
|
||||
|
||||
SnapshotName string `mapstructure:"snapshot_name"`
|
||||
ServerName string `mapstructure:"server_name"`
|
||||
|
||||
UserAgent string
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
c := new(Config)
|
||||
|
||||
var md mapstructure.Metadata
|
||||
err := config.Decode(c, &config.DecodeOpts{
|
||||
Metadata: &md,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &c.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"run_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c.UserAgent = "Packer - Scaleway builder"
|
||||
|
||||
if c.SnapshotName == "" {
|
||||
def, err := interpolate.Render("packer-{{timestamp}}", nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.SnapshotName = def
|
||||
}
|
||||
|
||||
if c.ServerName == "" {
|
||||
// Default to packer-[time-ordered-uuid]
|
||||
c.ServerName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
||||
errs = packer.MultiErrorAppend(errs, es...)
|
||||
}
|
||||
if c.Organization == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("Scaleway Organization ID must be specified"))
|
||||
}
|
||||
|
||||
if c.Token == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("Scaleway Token must be specified"))
|
||||
}
|
||||
|
||||
if c.Region == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("region is required"))
|
||||
}
|
||||
|
||||
if c.CommercialType == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("commercial type is required"))
|
||||
}
|
||||
|
||||
if c.Image == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("image is required"))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, nil, errs
|
||||
}
|
||||
|
||||
common.ScrubConfig(c, c.Token)
|
||||
return c, nil, nil
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func commHost(state multistep.StateBag) (string, error) {
|
||||
ipAddress := state.Get("server_ip").(string)
|
||||
return ipAddress, nil
|
||||
}
|
||||
|
||||
func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||
config := state.Get("config").(Config)
|
||||
privateKey := state.Get("privateKey").(string)
|
||||
|
||||
signer, err := ssh.ParsePrivateKey([]byte(privateKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
}
|
||||
|
||||
return &ssh.ClientConfig{
|
||||
User: config.Comm.SSHUsername,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type stepCreateServer struct {
|
||||
serverId string
|
||||
}
|
||||
|
||||
func (s *stepCreateServer) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*api.ScalewayAPI)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("config").(Config)
|
||||
sshPubKey := state.Get("ssh_pubkey").(string)
|
||||
|
||||
ui.Say("Creating server...")
|
||||
|
||||
server, err := client.PostServer(api.ScalewayServerDefinition{
|
||||
Name: c.ServerName,
|
||||
Image: &c.Image,
|
||||
Organization: c.Organization,
|
||||
CommercialType: c.CommercialType,
|
||||
Tags: []string{fmt.Sprintf("AUTHORIZED_KEY=%s", strings.TrimSpace(sshPubKey))},
|
||||
})
|
||||
|
||||
err = client.PostServerAction(server, "poweron")
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating server: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.serverId = server
|
||||
|
||||
state.Put("server_id", server)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
|
||||
if s.serverId != "" {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*api.ScalewayAPI)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Destroying server...")
|
||||
err := client.PostServerAction(s.serverId, "terminate")
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error destroying server. Please destroy it manually: %s", err))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type stepCreateSSHKey struct {
|
||||
Debug bool
|
||||
DebugKeyPath string
|
||||
}
|
||||
|
||||
func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Creating temporary ssh key for server...")
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2014)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// ASN.1 DER encoded form
|
||||
priv_der := x509.MarshalPKCS1PrivateKey(priv)
|
||||
priv_blk := pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Headers: nil,
|
||||
Bytes: priv_der,
|
||||
}
|
||||
|
||||
// Set the private key in the statebag for later
|
||||
state.Put("privateKey", string(pem.EncodeToMemory(&priv_blk)))
|
||||
|
||||
pub, _ := ssh.NewPublicKey(&priv.PublicKey)
|
||||
pub_sshformat := string(ssh.MarshalAuthorizedKey(pub))
|
||||
pub_sshformat = strings.Replace(pub_sshformat, " ", "_", -1)
|
||||
|
||||
log.Printf("temporary ssh key created")
|
||||
|
||||
// Remember some state for the future
|
||||
state.Put("ssh_pubkey", string(pub_sshformat))
|
||||
|
||||
// If we're in debug mode, output the private key to the working directory.
|
||||
if s.Debug {
|
||||
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
|
||||
f, err := os.Create(s.DebugKeyPath)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Write the key out
|
||||
if _, err := f.Write(pem.EncodeToMemory(&priv_blk)); err != nil {
|
||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Chmod it so that it is SSH ready
|
||||
if runtime.GOOS != "windows" {
|
||||
if err := f.Chmod(0600); err != nil {
|
||||
state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {
|
||||
// SSH key is passed via tag. Nothing to do here.
|
||||
return
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
)
|
||||
|
||||
type stepServerInfo struct{}
|
||||
|
||||
func (s *stepServerInfo) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*api.ScalewayAPI)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
serverID := state.Get("server_id").(string)
|
||||
|
||||
ui.Say("Waiting for server to become active...")
|
||||
|
||||
_, err := api.WaitForServerState(client, serverID, "running")
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for server to become booted: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
server, err := client.GetServer(serverID)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error retrieving server: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("server_ip", server.PublicAddress.IP)
|
||||
state.Put("root_volume_id", server.Volumes["0"].Identifier)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepServerInfo) Cleanup(state multistep.StateBag) {
|
||||
// no cleanup
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
)
|
||||
|
||||
type stepShutdown struct{}
|
||||
|
||||
func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*api.ScalewayAPI)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
serverId := state.Get("server_id").(string)
|
||||
|
||||
ui.Say("Shutting down server...")
|
||||
|
||||
err := client.PostServerAction(serverId, "poweroff")
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error stopping server: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
_, err = api.WaitForServerState(client, serverId, "stopped")
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error shutting down server: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepShutdown) Cleanup(state multistep.StateBag) {
|
||||
// no cleanup
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
)
|
||||
|
||||
type stepSnapshot struct{}
|
||||
|
||||
func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*api.ScalewayAPI)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("config").(Config)
|
||||
volumeId := state.Get("root_volume_id").(string)
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating snapshot: %v", c.SnapshotName))
|
||||
snapshot, err := client.PostSnapshot(volumeId, c.SnapshotName)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating snapshot: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Looking up snapshot ID for snapshot: %s", c.SnapshotName)
|
||||
_, err = client.GetSnapshot(snapshot)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error looking up snapshot ID: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Snapshot ID: %s", snapshot)
|
||||
state.Put("snapshot_id", snapshot)
|
||||
state.Put("snapshot_name", c.SnapshotName)
|
||||
state.Put("region", c.Region)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepSnapshot) Cleanup(state multistep.StateBag) {
|
||||
// no cleanup
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
)
|
||||
|
||||
type stepTerminate struct{}
|
||||
|
||||
func (s *stepTerminate) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*api.ScalewayAPI)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
serverId := state.Get("server_id").(string)
|
||||
|
||||
ui.Say("Terminating server...")
|
||||
|
||||
err := client.DeleteServerForce(serverId)
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error terminating server: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepTerminate) Cleanup(state multistep.StateBag) {
|
||||
// no cleanup
|
||||
}
|
|
@ -39,6 +39,7 @@ import (
|
|||
parallelspvmbuilder "github.com/hashicorp/packer/builder/parallels/pvm"
|
||||
profitbricksbuilder "github.com/hashicorp/packer/builder/profitbricks"
|
||||
qemubuilder "github.com/hashicorp/packer/builder/qemu"
|
||||
scalewaybuilder "github.com/hashicorp/packer/builder/scaleway"
|
||||
tritonbuilder "github.com/hashicorp/packer/builder/triton"
|
||||
virtualboxisobuilder "github.com/hashicorp/packer/builder/virtualbox/iso"
|
||||
virtualboxovfbuilder "github.com/hashicorp/packer/builder/virtualbox/ovf"
|
||||
|
@ -108,6 +109,7 @@ var Builders = map[string]packer.Builder{
|
|||
"parallels-pvm": new(parallelspvmbuilder.Builder),
|
||||
"profitbricks": new(profitbricksbuilder.Builder),
|
||||
"qemu": new(qemubuilder.Builder),
|
||||
"scaleway": new(scalewaybuilder.Builder),
|
||||
"triton": new(tritonbuilder.Builder),
|
||||
"virtualbox-iso": new(virtualboxisobuilder.Builder),
|
||||
"virtualbox-ovf": new(virtualboxovfbuilder.Builder),
|
||||
|
|
|
@ -189,6 +189,8 @@ func providerFromBuilderName(name string) string {
|
|||
switch name {
|
||||
case "aws":
|
||||
return "aws"
|
||||
case "scaleway":
|
||||
return "scaleway"
|
||||
case "digitalocean":
|
||||
return "digitalocean"
|
||||
case "virtualbox":
|
||||
|
|
|
@ -26,6 +26,7 @@ var builtins = map[string]string{
|
|||
"mitchellh.vmware-esx": "vmware",
|
||||
"pearkes.digitalocean": "digitalocean",
|
||||
"packer.googlecompute": "google",
|
||||
"pearkes.scaleway": "scaleway",
|
||||
"packer.parallels": "parallels",
|
||||
"MSOpenTech.hyperv": "hyperv",
|
||||
"transcend.qemu": "libvirt",
|
||||
|
@ -221,6 +222,8 @@ func providerForName(name string) Provider {
|
|||
switch name {
|
||||
case "aws":
|
||||
return new(AWSProvider)
|
||||
case "scaleway":
|
||||
return new(ScalewayProvider)
|
||||
case "digitalocean":
|
||||
return new(DigitalOceanProvider)
|
||||
case "virtualbox":
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type scalewayVagrantfileTemplate struct {
|
||||
Image string ""
|
||||
Region string ""
|
||||
}
|
||||
|
||||
type ScalewayProvider struct{}
|
||||
|
||||
func (p *ScalewayProvider) KeepInputArtifact() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *ScalewayProvider) Process(ui packer.Ui, artifact packer.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
|
||||
// Create the metadata
|
||||
metadata = map[string]interface{}{"provider": "scaleway"}
|
||||
|
||||
// Determine the image and region...
|
||||
tplData := &scalewayVagrantfileTemplate{}
|
||||
|
||||
parts := strings.Split(artifact.Id(), ":")
|
||||
if len(parts) != 2 {
|
||||
err = fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id())
|
||||
return
|
||||
}
|
||||
tplData.Region = parts[0]
|
||||
tplData.Image = parts[1]
|
||||
|
||||
// Build up the Vagrantfile
|
||||
var contents bytes.Buffer
|
||||
t := template.Must(template.New("vf").Parse(defaultScalewayVagrantfile))
|
||||
err = t.Execute(&contents, tplData)
|
||||
vagrantfile = contents.String()
|
||||
return
|
||||
}
|
||||
|
||||
var defaultScalewayVagrantfile = `
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.provider :scaleway do |scaleway|
|
||||
scaleway.image = "{{ .Image }}"
|
||||
scaleway.region = "{{ .Region }}"
|
||||
end
|
||||
end
|
||||
`
|
|
@ -233,6 +233,12 @@
|
|||
"revision": "c2e73f942591b0f033a3c6df00f44badb2347c38",
|
||||
"revisionTime": "2018-01-10T05:50:12Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "8dVO3L8yAdQ17X3lAhIziyF3OFk=",
|
||||
"path": "github.com/Sirupsen/logrus",
|
||||
"revision": "10f801ebc38b33738c9d17d50860f484a0988ff5",
|
||||
"revisionTime": "2017-03-17T14:32:14Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "HttiPj314X1a0i2Jen1p6lRH/vE=",
|
||||
"path": "github.com/aliyun/aliyun-oss-go-sdk/oss",
|
||||
|
@ -589,6 +595,12 @@
|
|||
"path": "github.com/biogo/hts/bgzf",
|
||||
"revision": "50da7d4131a3b5c9d063932461cab4d1fafb20b0"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "bFj0ceSRvaFFCfmS4el1PjWhcgw=",
|
||||
"path": "github.com/creack/goselect",
|
||||
"revision": "1bd5ca702c6154bccc56ecd598932ee8b295cab2",
|
||||
"revisionTime": "2016-07-14T17:28:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Lf3uUXTkKK5DJ37BxQvxO1Fq+K8=",
|
||||
"comment": "v1.0.0-3-g6d21280",
|
||||
|
@ -638,6 +650,24 @@
|
|||
"revision": "4c04abe183f449bd9ede285f0e5c7ee575d0dbe4",
|
||||
"revisionTime": "2017-04-07T15:15:42Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "1n5MBJthemxmfqU2gN3qLCd8s04=",
|
||||
"path": "github.com/docker/docker/pkg/namesgenerator",
|
||||
"revision": "fa3e2d5ab9b577cecd24201902bbe72b3f1b851c",
|
||||
"revisionTime": "2017-04-06T12:40:27Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "lThih54jzz9A4zHKEFb9SIV3Ed0=",
|
||||
"path": "github.com/docker/docker/pkg/random",
|
||||
"revision": "fa3e2d5ab9b577cecd24201902bbe72b3f1b851c",
|
||||
"revisionTime": "2017-04-06T12:40:27Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "rhLUtXvcmouYuBwOq9X/nYKzvNg=",
|
||||
"path": "github.com/dustin/go-humanize",
|
||||
"revision": "259d2a102b871d17f30e3cd9881a642961a1e486",
|
||||
"revisionTime": "2017-02-28T07:34:54Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "GCskdwYAPW2S34918Z5CgNMJ2Wc=",
|
||||
"path": "github.com/dylanmei/iso8601",
|
||||
|
@ -779,6 +809,12 @@
|
|||
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
|
||||
"revisionTime": "2017-06-23T01:44:30Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "xSmii71kfQASGNG2C8ttmHx9KTE=",
|
||||
"path": "github.com/gorilla/websocket",
|
||||
"revision": "a91eba7f97777409bc2c443f5534d41dd20c5720",
|
||||
"revisionTime": "2017-03-19T17:27:27Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "izBSRxLAHN+a/XpAku0in05UzlY=",
|
||||
"comment": "20141209094003-92-g95fa852",
|
||||
|
@ -1032,6 +1068,18 @@
|
|||
"path": "github.com/mitchellh/reflectwalk",
|
||||
"revision": "eecf4c70c626c7cfbb95c90195bc34d386c74ac6"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "KhOVzKefFYORHdIVe+/gNAHB23A=",
|
||||
"path": "github.com/moul/anonuuid",
|
||||
"revision": "609b752a95effbbef26d134ac18ed6f57e01b98e",
|
||||
"revisionTime": "2016-02-22T16:21:17Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "WcXDSYIAP73RAvy22iD57nE/peI=",
|
||||
"path": "github.com/moul/gotty-client",
|
||||
"revision": "99224eea3278d662fce9124bb2bf6c2bb39f5160",
|
||||
"revisionTime": "2017-02-05T09:54:39Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "gcLub3oB+u4QrOJZcYmk/y2AP4k=",
|
||||
"path": "github.com/nu7hatch/gouuid",
|
||||
|
@ -1117,6 +1165,12 @@
|
|||
"revision": "7bdb11aecb0e457ea23c86898c6b49bfc0eb4bb1",
|
||||
"revisionTime": "2017-08-01T13:52:49Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "DF3jZEw4lCq/SEaC7DIl/R+7S70=",
|
||||
"path": "github.com/renstrom/fuzzysearch/fuzzy",
|
||||
"revision": "2d205ac6ec17a839a94bdbfd16d2fa6c6dada2e0",
|
||||
"revisionTime": "2016-03-31T20:48:55Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "zmC8/3V4ls53DJlNTKDZwPSC/dA=",
|
||||
"path": "github.com/satori/go.uuid",
|
||||
|
@ -1129,6 +1183,24 @@
|
|||
"revision": "5bf94b69c6b68ee1b541973bb8e1144db23a194b",
|
||||
"revisionTime": "2017-03-21T23:07:31Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "FFhSGe3Y3J1laR/6rwSS7U2esrk=",
|
||||
"path": "github.com/scaleway/scaleway-cli/pkg/api",
|
||||
"revision": "e50cb485747a4f25a361c90ef3ba05be79944c56",
|
||||
"revisionTime": "2017-04-03T16:01:47Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "kveaAmNlnvmIIuEkFcMlB+N7TqY=",
|
||||
"path": "github.com/scaleway/scaleway-cli/pkg/sshcommand",
|
||||
"revision": "e50cb485747a4f25a361c90ef3ba05be79944c56",
|
||||
"revisionTime": "2017-04-03T16:01:47Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "xM3G5ct9YYYnVIL3XMRrcf41xVw=",
|
||||
"path": "github.com/scaleway/scaleway-cli/pkg/utils",
|
||||
"revision": "e50cb485747a4f25a361c90ef3ba05be79944c56",
|
||||
"revisionTime": "2017-04-03T16:01:47Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "iydUphwYqZRq3WhstEdGsbvBAKs=",
|
||||
"comment": "v1.1.4-4-g976c720",
|
||||
|
@ -1281,6 +1353,12 @@
|
|||
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
|
||||
"revisionTime": "2017-02-08T20:51:15Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "xiderUuvye8Kpn7yX3niiJg32bE=",
|
||||
"path": "golang.org/x/crypto/ssh/terminal",
|
||||
"revision": "c2303dcbe84172e0c0da4c9f083eeca54c06f298",
|
||||
"revisionTime": "2017-01-17T19:20:27Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=",
|
||||
"path": "golang.org/x/net/context",
|
||||
|
@ -1342,6 +1420,12 @@
|
|||
"path": "golang.org/x/oauth2/jwt",
|
||||
"revision": "8a57ed94ffd43444c0879fe75701732a38afc985"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "S0DP7Pn7sZUmXc55IzZnNvERu6s=",
|
||||
"path": "golang.org/x/sync/errgroup",
|
||||
"revision": "5a06fca2c336a4b2b2fcb45702e8c47621b2aa2c",
|
||||
"revisionTime": "2017-03-17T17:13:11Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "NzQ3QYllWwK+3GZliu11jMU6xwo=",
|
||||
"path": "golang.org/x/sys/unix",
|
||||
|
|
Loading…
Reference in New Issue