mirror of https://github.com/hashicorp/packer
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
|
||||
}
|
||||
@ -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
|
||||
`
|
||||
Loading…
Reference in new issue