mirror of https://github.com/hashicorp/packer
Extract ProfitBricks Plugin (#11084)
* Extract ProfitBricks Plugin * Update CHANGELOGpull/11096/head
parent
927093e5c0
commit
e22346f0fa
@ -1,37 +0,0 @@
|
||||
package profitbricks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Artifact struct {
|
||||
snapshotData string
|
||||
|
||||
// StateData should store data such as GeneratedData
|
||||
// to be shared with post-processors
|
||||
StateData map[string]interface{}
|
||||
}
|
||||
|
||||
func (*Artifact) BuilderId() string {
|
||||
return BuilderId
|
||||
}
|
||||
|
||||
func (a *Artifact) Files() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (*Artifact) Id() string {
|
||||
return "Null"
|
||||
}
|
||||
|
||||
func (a *Artifact) String() string {
|
||||
return fmt.Sprintf("A snapshot was created: '%v'", a.snapshotData)
|
||||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
return a.StateData[name]
|
||||
}
|
||||
|
||||
func (a *Artifact) Destroy() error {
|
||||
return nil
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
package profitbricks
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func TestArtifact_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Artifact{}
|
||||
if _, ok := raw.(packersdk.Artifact); !ok {
|
||||
t.Fatalf("Artifact should be artifact")
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactString(t *testing.T) {
|
||||
generatedData := make(map[string]interface{})
|
||||
a := &Artifact{"packer-foobar", generatedData}
|
||||
expected := "A snapshot was created: 'packer-foobar'"
|
||||
|
||||
if a.String() != expected {
|
||||
t.Fatalf("artifact string should match: %v", expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactState_StateData(t *testing.T) {
|
||||
expectedData := "this is the data"
|
||||
artifact := &Artifact{
|
||||
StateData: map[string]interface{}{"state_data": expectedData},
|
||||
}
|
||||
|
||||
// Valid state
|
||||
result := artifact.State("state_data")
|
||||
if result != expectedData {
|
||||
t.Fatalf("Bad: State data was %s instead of %s", result, expectedData)
|
||||
}
|
||||
|
||||
// Invalid state
|
||||
result = artifact.State("invalid_key")
|
||||
if result != nil {
|
||||
t.Fatalf("Bad: State should be nil for invalid state data name")
|
||||
}
|
||||
|
||||
// Nil StateData should not fail and should return nil
|
||||
artifact = &Artifact{}
|
||||
result = artifact.State("key")
|
||||
if result != nil {
|
||||
t.Fatalf("Bad: State should be nil for nil StateData")
|
||||
}
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
package profitbricks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
const BuilderId = "packer.profitbricks"
|
||||
|
||||
type Builder struct {
|
||||
config Config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
warnings, errs := b.config.Prepare(raws...)
|
||||
if errs != nil {
|
||||
return nil, warnings, errs
|
||||
}
|
||||
|
||||
return nil, warnings, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
||||
state := new(multistep.BasicStateBag)
|
||||
|
||||
state.Put("config", &b.config)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
steps := []multistep.Step{
|
||||
&StepCreateSSHKey{
|
||||
Debug: b.config.PackerDebug,
|
||||
DebugKeyPath: fmt.Sprintf("pb_%s", b.config.SnapshotName),
|
||||
},
|
||||
new(stepCreateServer),
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.Comm,
|
||||
Host: communicator.CommHost(b.config.Comm.Host(), "server_ip"),
|
||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
||||
},
|
||||
&commonsteps.StepProvision{},
|
||||
&commonsteps.StepCleanupTempKeys{
|
||||
Comm: &b.config.Comm,
|
||||
},
|
||||
new(stepTakeSnapshot),
|
||||
}
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
|
||||
b.runner = commonsteps.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
b.runner.Run(ctx, state)
|
||||
|
||||
if rawErr, ok := state.GetOk("error"); ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
artifact := &Artifact{
|
||||
snapshotData: config.SnapshotName,
|
||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
||||
}
|
||||
return artifact, nil
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
package profitbricks
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_basic(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccBasic,
|
||||
})
|
||||
}
|
||||
|
||||
func testAccPreCheck(t *testing.T) {
|
||||
if v := os.Getenv("PROFITBRICKS_USERNAME"); v == "" {
|
||||
t.Fatal("PROFITBRICKS_USERNAME must be set for acceptance tests")
|
||||
}
|
||||
|
||||
if v := os.Getenv("PROFITBRICKS_PASSWORD"); v == "" {
|
||||
t.Fatal("PROFITBRICKS_PASSWORD must be set for acceptance tests")
|
||||
}
|
||||
}
|
||||
|
||||
const testBuilderAccBasic = `
|
||||
{
|
||||
"builders": [{
|
||||
"image": "Ubuntu-16.04",
|
||||
"password": "password",
|
||||
"username": "username",
|
||||
"snapshot_name": "packer",
|
||||
"type": "profitbricks"
|
||||
}]
|
||||
}
|
||||
`
|
||||
@ -1,57 +0,0 @@
|
||||
package profitbricks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"image": "Ubuntu-16.04",
|
||||
"password": "password",
|
||||
"username": "username",
|
||||
"snapshot_name": "packer",
|
||||
"type": "profitbricks",
|
||||
}
|
||||
}
|
||||
|
||||
func TestImplementsBuilder(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Builder{}
|
||||
if _, ok := raw.(packersdk.Builder); !ok {
|
||||
t.Fatalf("Builder should be a builder")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_Prepare_BadType(t *testing.T) {
|
||||
b := &Builder{}
|
||||
c := map[string]interface{}{
|
||||
"api_key": []string{},
|
||||
}
|
||||
|
||||
_, warns, err := b.Prepare(c)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println(warns)
|
||||
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")
|
||||
}
|
||||
}
|
||||
@ -1,128 +0,0 @@
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
|
||||
package profitbricks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
PBUsername string `mapstructure:"username"`
|
||||
PBPassword string `mapstructure:"password"`
|
||||
PBUrl string `mapstructure:"url"`
|
||||
|
||||
Region string `mapstructure:"location"`
|
||||
Image string `mapstructure:"image"`
|
||||
SSHKey string
|
||||
SnapshotName string `mapstructure:"snapshot_name"`
|
||||
DiskSize int `mapstructure:"disk_size"`
|
||||
DiskType string `mapstructure:"disk_type"`
|
||||
Cores int `mapstructure:"cores"`
|
||||
Ram int `mapstructure:"ram"`
|
||||
Retries int `mapstructure:"retries"`
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
|
||||
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, err
|
||||
}
|
||||
|
||||
var errs *packersdk.MultiError
|
||||
|
||||
if c.Comm.SSHPassword == "" && c.Comm.SSHPrivateKeyFile == "" {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, errors.New("Either ssh private key path or ssh password must be set."))
|
||||
}
|
||||
|
||||
if c.SnapshotName == "" {
|
||||
def, err := interpolate.Render("packer-{{timestamp}}", nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Default to packer-{{ unix timestamp (utc) }}
|
||||
c.SnapshotName = def
|
||||
}
|
||||
|
||||
if c.PBUsername == "" {
|
||||
c.PBUsername = os.Getenv("PROFITBRICKS_USERNAME")
|
||||
}
|
||||
|
||||
if c.PBPassword == "" {
|
||||
c.PBPassword = os.Getenv("PROFITBRICKS_PASSWORD")
|
||||
}
|
||||
|
||||
if c.PBUrl == "" {
|
||||
c.PBUrl = "https://api.profitbricks.com/cloudapi/v4"
|
||||
}
|
||||
|
||||
if c.Cores == 0 {
|
||||
c.Cores = 4
|
||||
}
|
||||
|
||||
if c.Ram == 0 {
|
||||
c.Ram = 2048
|
||||
}
|
||||
|
||||
if c.DiskSize == 0 {
|
||||
c.DiskSize = 50
|
||||
}
|
||||
|
||||
if c.Region == "" {
|
||||
c.Region = "us/las"
|
||||
}
|
||||
|
||||
if c.DiskType == "" {
|
||||
c.DiskType = "HDD"
|
||||
}
|
||||
|
||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
||||
errs = packersdk.MultiErrorAppend(errs, es...)
|
||||
}
|
||||
|
||||
if c.Image == "" {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, errors.New("ProfitBricks 'image' is required"))
|
||||
}
|
||||
|
||||
if c.PBUsername == "" {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, errors.New("ProfitBricks username is required"))
|
||||
}
|
||||
|
||||
if c.PBPassword == "" {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, errors.New("ProfitBricks password is required"))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
packersdk.LogSecretFilter.Set(c.PBUsername)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
@ -1,167 +0,0 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
|
||||
package profitbricks
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatConfig struct {
|
||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
||||
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
|
||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
||||
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
|
||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
||||
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
|
||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
||||
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
|
||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
|
||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
|
||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
|
||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
|
||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
|
||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
|
||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
||||
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
|
||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
|
||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
|
||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
|
||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
|
||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
|
||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
|
||||
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
|
||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
|
||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
|
||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
|
||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
|
||||
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
|
||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
|
||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
|
||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
|
||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
||||
PBUsername *string `mapstructure:"username" cty:"username" hcl:"username"`
|
||||
PBPassword *string `mapstructure:"password" cty:"password" hcl:"password"`
|
||||
PBUrl *string `mapstructure:"url" cty:"url" hcl:"url"`
|
||||
Region *string `mapstructure:"location" cty:"location" hcl:"location"`
|
||||
Image *string `mapstructure:"image" cty:"image" hcl:"image"`
|
||||
SSHKey *string `cty:"ssh_key" hcl:"ssh_key"`
|
||||
SnapshotName *string `mapstructure:"snapshot_name" cty:"snapshot_name" hcl:"snapshot_name"`
|
||||
DiskSize *int `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
|
||||
DiskType *string `mapstructure:"disk_type" cty:"disk_type" hcl:"disk_type"`
|
||||
Cores *int `mapstructure:"cores" cty:"cores" hcl:"cores"`
|
||||
Ram *int `mapstructure:"ram" cty:"ram" hcl:"ram"`
|
||||
Retries *int `mapstructure:"retries" cty:"retries" hcl:"retries"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatConfig.
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a Config.
|
||||
// This spec is used by HCL to read the fields of Config.
|
||||
// The decoded values from this spec will then be applied to a FlatConfig.
|
||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
||||
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
|
||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
||||
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
|
||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
||||
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
||||
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
||||
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
||||
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
|
||||
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
||||
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
|
||||
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
||||
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
||||
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
||||
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
|
||||
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
|
||||
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
|
||||
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
|
||||
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
|
||||
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
|
||||
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
|
||||
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
||||
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
||||
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
||||
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
|
||||
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
||||
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
||||
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
||||
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
|
||||
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
|
||||
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
|
||||
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
|
||||
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
|
||||
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
|
||||
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
|
||||
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
|
||||
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
|
||||
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
|
||||
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
|
||||
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
|
||||
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
||||
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
||||
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
||||
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
|
||||
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
|
||||
"url": &hcldec.AttrSpec{Name: "url", Type: cty.String, Required: false},
|
||||
"location": &hcldec.AttrSpec{Name: "location", Type: cty.String, Required: false},
|
||||
"image": &hcldec.AttrSpec{Name: "image", Type: cty.String, Required: false},
|
||||
"ssh_key": &hcldec.AttrSpec{Name: "ssh_key", Type: cty.String, Required: false},
|
||||
"snapshot_name": &hcldec.AttrSpec{Name: "snapshot_name", Type: cty.String, Required: false},
|
||||
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
|
||||
"disk_type": &hcldec.AttrSpec{Name: "disk_type", Type: cty.String, Required: false},
|
||||
"cores": &hcldec.AttrSpec{Name: "cores", Type: cty.Number, Required: false},
|
||||
"ram": &hcldec.AttrSpec{Name: "ram", Type: cty.Number, Required: false},
|
||||
"retries": &hcldec.AttrSpec{Name: "retries", Type: cty.Number, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
@ -1,279 +0,0 @@
|
||||
package profitbricks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/profitbricks/profitbricks-sdk-go"
|
||||
)
|
||||
|
||||
type stepCreateServer struct{}
|
||||
|
||||
func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
c := state.Get("config").(*Config)
|
||||
|
||||
profitbricks.SetAuth(c.PBUsername, c.PBPassword)
|
||||
profitbricks.SetDepth("5")
|
||||
if c.Comm.SSHPublicKey != nil {
|
||||
c.SSHKey = string(c.Comm.SSHPublicKey)
|
||||
} else {
|
||||
ui.Error("No ssh private key set; ssh authentication won't be possible. Please specify your private key in the ssh_private_key_file configuration key.")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
ui.Say("Creating Virtual Data Center...")
|
||||
img := s.getImageId(c.Image, c)
|
||||
alias := ""
|
||||
if img == "" {
|
||||
alias = s.getImageAlias(c.Image, c.Region, ui)
|
||||
}
|
||||
|
||||
datacenter := profitbricks.Datacenter{
|
||||
Properties: profitbricks.DatacenterProperties{
|
||||
Name: c.SnapshotName,
|
||||
Location: c.Region,
|
||||
},
|
||||
}
|
||||
server := profitbricks.Server{
|
||||
Properties: profitbricks.ServerProperties{
|
||||
Name: c.SnapshotName,
|
||||
Ram: c.Ram,
|
||||
Cores: c.Cores,
|
||||
},
|
||||
Entities: &profitbricks.ServerEntities{
|
||||
Volumes: &profitbricks.Volumes{
|
||||
Items: []profitbricks.Volume{
|
||||
{
|
||||
Properties: profitbricks.VolumeProperties{
|
||||
Type: c.DiskType,
|
||||
Size: c.DiskSize,
|
||||
Name: c.SnapshotName,
|
||||
ImageAlias: alias,
|
||||
Image: img,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if c.SSHKey != "" {
|
||||
server.Entities.Volumes.Items[0].Properties.SshKeys = []string{c.SSHKey}
|
||||
}
|
||||
|
||||
if c.Comm.SSHPassword != "" {
|
||||
server.Entities.Volumes.Items[0].Properties.ImagePassword = c.Comm.SSHPassword
|
||||
}
|
||||
|
||||
datacenter = profitbricks.CompositeCreateDatacenter(datacenter)
|
||||
if datacenter.StatusCode > 299 {
|
||||
if datacenter.StatusCode > 299 {
|
||||
var restError RestError
|
||||
err := json.Unmarshal([]byte(datacenter.Response), &restError)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error decoding json response: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if len(restError.Messages) > 0 {
|
||||
ui.Error(restError.Messages[0].Message)
|
||||
} else {
|
||||
ui.Error(datacenter.Response)
|
||||
}
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
err := s.waitTillProvisioned(datacenter.Headers.Get("Location"), *c)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error occurred while creating a datacenter %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("datacenter_id", datacenter.Id)
|
||||
|
||||
server = profitbricks.CreateServer(datacenter.Id, server)
|
||||
if server.StatusCode > 299 {
|
||||
ui.Error(fmt.Sprintf("Error occurred %s", parseErrorMessage(server.Response)))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
err = s.waitTillProvisioned(server.Headers.Get("Location"), *c)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error occurred while creating a server %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
lan := profitbricks.CreateLan(datacenter.Id, profitbricks.CreateLanRequest{
|
||||
Properties: profitbricks.CreateLanProperties{
|
||||
Public: true,
|
||||
Name: c.SnapshotName,
|
||||
},
|
||||
})
|
||||
|
||||
if lan.StatusCode > 299 {
|
||||
ui.Error(fmt.Sprintf("Error occurred %s", parseErrorMessage(lan.Response)))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
err = s.waitTillProvisioned(lan.Headers.Get("Location"), *c)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error occurred while creating a LAN %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
lanId, _ := strconv.Atoi(lan.Id)
|
||||
nic := profitbricks.CreateNic(datacenter.Id, server.Id, profitbricks.Nic{
|
||||
Properties: &profitbricks.NicProperties{
|
||||
Name: c.SnapshotName,
|
||||
Lan: lanId,
|
||||
Dhcp: true,
|
||||
},
|
||||
})
|
||||
|
||||
if lan.StatusCode > 299 {
|
||||
ui.Error(fmt.Sprintf("Error occurred %s", parseErrorMessage(nic.Response)))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
err = s.waitTillProvisioned(nic.Headers.Get("Location"), *c)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error occurred while creating a NIC %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("volume_id", server.Entities.Volumes.Items[0].Id)
|
||||
|
||||
server = profitbricks.GetServer(datacenter.Id, server.Id)
|
||||
// instance_id is the generic term used so that users can have access to the
|
||||
// instance id inside of the provisioners, used in step_provision.
|
||||
state.Put("instance_id", server.Id)
|
||||
|
||||
state.Put("server_ip", server.Entities.Nics.Items[0].Properties.Ips[0])
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
|
||||
c := state.Get("config").(*Config)
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
|
||||
ui.Say("Removing Virtual Data Center...")
|
||||
|
||||
profitbricks.SetAuth(c.PBUsername, c.PBPassword)
|
||||
|
||||
if dcId, ok := state.GetOk("datacenter_id"); ok {
|
||||
resp := profitbricks.DeleteDatacenter(dcId.(string))
|
||||
if err := s.checkForErrors(resp); err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error deleting Virtual Data Center. Please destroy it manually: %s", err))
|
||||
}
|
||||
if err := s.waitTillProvisioned(resp.Headers.Get("Location"), *c); err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error deleting Virtual Data Center. Please destroy it manually: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *stepCreateServer) waitTillProvisioned(path string, config Config) error {
|
||||
d.setPB(config.PBUsername, config.PBPassword, config.PBUrl)
|
||||
waitCount := 120
|
||||
if config.Retries > 0 {
|
||||
waitCount = config.Retries
|
||||
}
|
||||
for i := 0; i < waitCount; i++ {
|
||||
request := profitbricks.GetRequestStatus(path)
|
||||
if request.Metadata.Status == "DONE" {
|
||||
return nil
|
||||
}
|
||||
if request.Metadata.Status == "FAILED" {
|
||||
return errors.New(request.Metadata.Message)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
i++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *stepCreateServer) setPB(username string, password string, url string) {
|
||||
profitbricks.SetAuth(username, password)
|
||||
profitbricks.SetEndpoint(url)
|
||||
}
|
||||
|
||||
func (d *stepCreateServer) checkForErrors(instance profitbricks.Resp) error {
|
||||
if instance.StatusCode > 299 {
|
||||
return fmt.Errorf("Error occurred %s", string(instance.Body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RestError struct {
|
||||
HttpStatus int `json:"httpStatus,omitempty"`
|
||||
Messages []Message `json:"messages,omitempty"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
ErrorCode string `json:"errorCode,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (d *stepCreateServer) getImageId(imageName string, c *Config) string {
|
||||
d.setPB(c.PBUsername, c.PBPassword, c.PBUrl)
|
||||
|
||||
images := profitbricks.ListImages()
|
||||
|
||||
for i := 0; i < len(images.Items); i++ {
|
||||
imgName := ""
|
||||
if images.Items[i].Properties.Name != "" {
|
||||
imgName = images.Items[i].Properties.Name
|
||||
}
|
||||
diskType := c.DiskType
|
||||
if c.DiskType == "SSD" {
|
||||
diskType = "HDD"
|
||||
}
|
||||
if imgName != "" && strings.Contains(strings.ToLower(imgName), strings.ToLower(imageName)) && images.Items[i].Properties.ImageType == diskType && images.Items[i].Properties.Location == c.Region && images.Items[i].Properties.Public == true {
|
||||
return images.Items[i].Id
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (d *stepCreateServer) getImageAlias(imageAlias string, location string, ui packersdk.Ui) string {
|
||||
if imageAlias == "" {
|
||||
return ""
|
||||
}
|
||||
locations := profitbricks.GetLocation(location)
|
||||
if len(locations.Properties.ImageAliases) > 0 {
|
||||
for _, i := range locations.Properties.ImageAliases {
|
||||
alias := ""
|
||||
if i != "" {
|
||||
alias = i
|
||||
}
|
||||
if alias != "" && strings.EqualFold(alias, imageAlias) {
|
||||
return alias
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseErrorMessage(raw string) (toreturn string) {
|
||||
var tmp map[string]interface{}
|
||||
if json.Unmarshal([]byte(raw), &tmp) != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, v := range tmp["messages"].([]interface{}) {
|
||||
for index, i := range v.(map[string]interface{}) {
|
||||
if index == "message" {
|
||||
toreturn = toreturn + i.(string) + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
return toreturn
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
package profitbricks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type StepCreateSSHKey struct {
|
||||
Debug bool
|
||||
DebugKeyPath string
|
||||
}
|
||||
|
||||
func (s *StepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
c := state.Get("config").(*Config)
|
||||
|
||||
if c.Comm.SSHPrivateKeyFile != "" {
|
||||
pemBytes, err := c.Comm.ReadSSHPrivateKeyFile()
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
|
||||
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
|
||||
if err != nil {
|
||||
|
||||
state.Put("error", err.Error())
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
priv_blk := pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Headers: nil,
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(priv),
|
||||
}
|
||||
|
||||
pub, err := ssh.NewPublicKey(&priv.PublicKey)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating temporary ssh key: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
c.Comm.SSHPrivateKey = pem.EncodeToMemory(&priv_blk)
|
||||
c.Comm.SSHPublicKey = ssh.MarshalAuthorizedKey(pub)
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepCreateSSHKey) Cleanup(state multistep.StateBag) {}
|
||||
@ -1,190 +0,0 @@
|
||||
package profitbricks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/profitbricks/profitbricks-sdk-go"
|
||||
)
|
||||
|
||||
type stepTakeSnapshot struct{}
|
||||
|
||||
func (s *stepTakeSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
c := state.Get("config").(*Config)
|
||||
|
||||
ui.Say("Creating ProfitBricks snapshot...")
|
||||
|
||||
profitbricks.SetAuth(c.PBUsername, c.PBPassword)
|
||||
|
||||
dcId := state.Get("datacenter_id").(string)
|
||||
volumeId := state.Get("volume_id").(string)
|
||||
serverId := state.Get("instance_id").(string)
|
||||
|
||||
comm, _ := state.Get("communicator").(packersdk.Communicator)
|
||||
if comm == nil {
|
||||
ui.Error("no communicator found")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
/* sync fs changes from the provisioning step */
|
||||
os, err := s.getOs(dcId, serverId)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("an error occurred while getting the server os: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Server OS is %s", os))
|
||||
|
||||
switch strings.ToLower(os) {
|
||||
case "linux":
|
||||
ui.Say("syncing file system changes")
|
||||
if err := s.syncFs(ctx, comm); err != nil {
|
||||
ui.Error(fmt.Sprintf("error syncing fs changes: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
snapshot := profitbricks.CreateSnapshot(dcId, volumeId, c.SnapshotName, "")
|
||||
state.Put("snapshotname", c.SnapshotName)
|
||||
|
||||
if snapshot.StatusCode > 299 {
|
||||
var restError RestError
|
||||
if err := json.Unmarshal([]byte(snapshot.Response), &restError); err != nil {
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if len(restError.Messages) > 0 {
|
||||
ui.Error(restError.Messages[0].Message)
|
||||
} else {
|
||||
ui.Error(snapshot.Response)
|
||||
}
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating a snapshot for %s/volumes/%s", dcId, volumeId))
|
||||
|
||||
err = s.waitForRequest(snapshot.Headers.Get("Location"), *c, ui)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("An error occurred while waiting for the request to be done: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
err = s.waitTillSnapshotAvailable(snapshot.Id, *c, ui)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("An error occurred while waiting for the snapshot to be created: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepTakeSnapshot) Cleanup(_ multistep.StateBag) {
|
||||
}
|
||||
|
||||
func (s *stepTakeSnapshot) waitForRequest(path string, config Config, ui packersdk.Ui) error {
|
||||
|
||||
ui.Say(fmt.Sprintf("Watching request %s", path))
|
||||
s.setPB(config.PBUsername, config.PBPassword, config.PBUrl)
|
||||
waitCount := 50
|
||||
var waitInterval = 10 * time.Second
|
||||
if config.Retries > 0 {
|
||||
waitCount = config.Retries
|
||||
}
|
||||
done := false
|
||||
for i := 0; i < waitCount; i++ {
|
||||
request := profitbricks.GetRequestStatus(path)
|
||||
ui.Say(fmt.Sprintf("request status = %s", request.Metadata.Status))
|
||||
if request.Metadata.Status == "DONE" {
|
||||
done = true
|
||||
break
|
||||
}
|
||||
if request.Metadata.Status == "FAILED" {
|
||||
return fmt.Errorf("Request failed: %s", request.Response)
|
||||
}
|
||||
time.Sleep(waitInterval)
|
||||
i++
|
||||
}
|
||||
|
||||
if done == false {
|
||||
return fmt.Errorf("request not fulfilled after waiting %d seconds",
|
||||
int64(waitCount)*int64(waitInterval)/int64(time.Second))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stepTakeSnapshot) waitTillSnapshotAvailable(id string, config Config, ui packersdk.Ui) error {
|
||||
s.setPB(config.PBUsername, config.PBPassword, config.PBUrl)
|
||||
waitCount := 50
|
||||
var waitInterval = 10 * time.Second
|
||||
if config.Retries > 0 {
|
||||
waitCount = config.Retries
|
||||
}
|
||||
done := false
|
||||
ui.Say(fmt.Sprintf("waiting for snapshot %s to become available", id))
|
||||
for i := 0; i < waitCount; i++ {
|
||||
snap := profitbricks.GetSnapshot(id)
|
||||
ui.Say(fmt.Sprintf("snapshot status = %s", snap.Metadata.State))
|
||||
if snap.StatusCode != 200 {
|
||||
return fmt.Errorf("%s", snap.Response)
|
||||
}
|
||||
if snap.Metadata.State == "AVAILABLE" {
|
||||
done = true
|
||||
break
|
||||
}
|
||||
time.Sleep(waitInterval)
|
||||
i++
|
||||
ui.Say(fmt.Sprintf("... still waiting, %d seconds have passed", int64(waitInterval)*int64(i)))
|
||||
}
|
||||
|
||||
if done == false {
|
||||
return fmt.Errorf("snapshot not created after waiting %d seconds",
|
||||
int64(waitCount)*int64(waitInterval)/int64(time.Second))
|
||||
}
|
||||
|
||||
ui.Say("snapshot created")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stepTakeSnapshot) syncFs(ctx context.Context, comm packersdk.Communicator) error {
|
||||
cmd := &packersdk.RemoteCmd{
|
||||
Command: "sync",
|
||||
}
|
||||
if err := comm.Start(ctx, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if cmd.Wait() != 0 {
|
||||
return fmt.Errorf("sync command exited with code %d", cmd.ExitStatus())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stepTakeSnapshot) getOs(dcId string, serverId string) (string, error) {
|
||||
server := profitbricks.GetServer(dcId, serverId)
|
||||
if server.StatusCode != 200 {
|
||||
return "", errors.New(server.Response)
|
||||
}
|
||||
|
||||
if server.Properties.BootVolume == nil {
|
||||
return "", errors.New("no boot volume found on server")
|
||||
}
|
||||
|
||||
volumeId := server.Properties.BootVolume.Id
|
||||
volume := profitbricks.GetVolume(dcId, volumeId)
|
||||
if volume.StatusCode != 200 {
|
||||
return "", errors.New(volume.Response)
|
||||
}
|
||||
|
||||
return volume.Properties.LicenceType, nil
|
||||
}
|
||||
|
||||
func (s *stepTakeSnapshot) setPB(username string, password string, url string) {
|
||||
profitbricks.SetAuth(username, password)
|
||||
profitbricks.SetEndpoint(url)
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
||||
packerVersion "github.com/hashicorp/packer/version"
|
||||
)
|
||||
|
||||
var ProfitbricksPluginVersion *version.PluginVersion
|
||||
|
||||
func init() {
|
||||
ProfitbricksPluginVersion = version.InitializePluginVersion(
|
||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
||||
}
|
||||
@ -1,86 +0,0 @@
|
||||
---
|
||||
description: The ProfitBricks builder is able to create images for ProfitBricks cloud.
|
||||
page_title: ProfitBricks - Builders
|
||||
---
|
||||
|
||||
# ProfitBricks Builder
|
||||
|
||||
Type: `profitbricks`
|
||||
Artifact BuilderId: `packer.profitbricks`
|
||||
|
||||
The ProfitBricks Builder is able to create virtual machines for
|
||||
[ProfitBricks](https://www.profitbricks.com).
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
There are many configuration options available for the builder. They are
|
||||
segmented below into two categories: required and optional parameters. Within
|
||||
each category, the available configuration keys are alphabetized.
|
||||
|
||||
In addition to the options listed here, a
|
||||
[communicator](/docs/templates/legacy_json_templates/communicator) can be configured for this
|
||||
builder. In addition to the options defined there, a private key file
|
||||
can also be supplied to override the typical auto-generated key:
|
||||
|
||||
@include 'packer-plugin-sdk/communicator/SSH-Private-Key-File-not-required.mdx'
|
||||
|
||||
### Required
|
||||
|
||||
- `image` (string) - ProfitBricks volume image. Only Linux public images are
|
||||
supported. To obtain full list of available images you can use
|
||||
[ProfitBricks CLI](https://github.com/profitbricks/profitbricks-cli#image).
|
||||
|
||||
- `password` (string) - ProfitBricks password. This can be specified via
|
||||
environment variable \`PROFITBRICKS_PASSWORD', if provided. The value
|
||||
defined in the config has precedence over environemnt variable.
|
||||
|
||||
- `username` (string) - ProfitBricks username. This can be specified via
|
||||
environment variable \`PROFITBRICKS_USERNAME', if provided. The value
|
||||
defined in the config has precedence over environemnt variable.
|
||||
|
||||
### Optional
|
||||
|
||||
- `cores` (number) - Amount of CPU cores to use for this build. Defaults to
|
||||
"4".
|
||||
|
||||
- `disk_size` (string) - Amount of disk space for this image in GB. Defaults
|
||||
to "50"
|
||||
|
||||
- `disk_type` (string) - Type of disk to use for this image. Defaults to
|
||||
"HDD".
|
||||
|
||||
- `location` (string) - Defaults to "us/las".
|
||||
|
||||
- `ram` (number) - Amount of RAM to use for this image. Defaults to "2048".
|
||||
|
||||
- `retries` (string) - Number of retries Packer will make status requests
|
||||
while waiting for the build to complete. Default value 120 seconds.
|
||||
|
||||
- `snapshot_name` (string) - If snapshot name is not provided Packer will
|
||||
generate it
|
||||
|
||||
- `snapshot_password` (string) - Password for the snapshot.
|
||||
<!-- markdown-link-check-disable -->
|
||||
- `url` (string) - Endpoint for the ProfitBricks REST API. Default URL
|
||||
"<https://api.profitbricks.com/rest/v2>"
|
||||
<!-- markdown-link-check-enable -->
|
||||
|
||||
## Example
|
||||
|
||||
Here is a basic example:
|
||||
|
||||
```json
|
||||
{
|
||||
"builders": [
|
||||
{
|
||||
"image": "Ubuntu-16.04",
|
||||
"type": "profitbricks",
|
||||
"disk_size": "5",
|
||||
"snapshot_name": "double",
|
||||
"snapshot_password": "test1234",
|
||||
"ssh_username": "root",
|
||||
"timeout": 100
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Loading…
Reference in new issue