delete openstack files

This commit is contained in:
Megan Marsh 2021-04-20 09:57:14 -07:00
parent 6fa213235f
commit 88192f1fdd
34 changed files with 0 additions and 4336 deletions

View File

@ -1,334 +0,0 @@
//go:generate packer-sdc struct-markdown
package openstack
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/utils/openstack/clientconfig"
"github.com/hashicorp/go-cleanhttp"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
)
// AccessConfig is for common configuration related to openstack access
type AccessConfig struct {
// The username or id used to connect to the OpenStack service. If not
// specified, Packer will use the environment variable OS_USERNAME or
// OS_USERID, if set. This is not required if using access token or
// application credential instead of password, or if using cloud.yaml.
Username string `mapstructure:"username" required:"true"`
// Sets username
UserID string `mapstructure:"user_id"`
// The password used to connect to the OpenStack service. If not specified,
// Packer will use the environment variables OS_PASSWORD, if set. This is
// not required if using access token or application credential instead of
// password, or if using cloud.yaml.
Password string `mapstructure:"password" required:"true"`
// The URL to the OpenStack Identity service. If not specified, Packer will
// use the environment variables OS_AUTH_URL, if set. This is not required
// if using cloud.yaml.
IdentityEndpoint string `mapstructure:"identity_endpoint" required:"true"`
// The tenant ID or name to boot the instance into. Some OpenStack
// installations require this. If not specified, Packer will use the
// environment variable OS_TENANT_NAME or OS_TENANT_ID, if set. Tenant is
// also called Project in later versions of OpenStack.
TenantID string `mapstructure:"tenant_id" required:"false"`
TenantName string `mapstructure:"tenant_name"`
DomainID string `mapstructure:"domain_id"`
// The Domain name or ID you are authenticating with. OpenStack
// installations require this if identity v3 is used. Packer will use the
// environment variable OS_DOMAIN_NAME or OS_DOMAIN_ID, if set.
DomainName string `mapstructure:"domain_name" required:"false"`
// Whether or not the connection to OpenStack can be done over an insecure
// connection. By default this is false.
Insecure bool `mapstructure:"insecure" required:"false"`
// The name of the region, such as "DFW", in which to launch the server to
// create the image. If not specified, Packer will use the environment
// variable OS_REGION_NAME, if set.
Region string `mapstructure:"region" required:"false"`
// The endpoint type to use. Can be any of "internal", "internalURL",
// "admin", "adminURL", "public", and "publicURL". By default this is
// "public".
EndpointType string `mapstructure:"endpoint_type" required:"false"`
// Custom CA certificate file path. If omitted the OS_CACERT environment
// variable can be used.
CACertFile string `mapstructure:"cacert" required:"false"`
// Client certificate file path for SSL client authentication. If omitted
// the OS_CERT environment variable can be used.
ClientCertFile string `mapstructure:"cert" required:"false"`
// Client private key file path for SSL client authentication. If omitted
// the OS_KEY environment variable can be used.
ClientKeyFile string `mapstructure:"key" required:"false"`
// the token (id) to use with token based authorization. Packer will use
// the environment variable OS_TOKEN, if set.
Token string `mapstructure:"token" required:"false"`
// The application credential name to use with application credential based
// authorization. Packer will use the environment variable
// OS_APPLICATION_CREDENTIAL_NAME, if set.
ApplicationCredentialName string `mapstructure:"application_credential_name" required:"false"`
// The application credential id to use with application credential based
// authorization. Packer will use the environment variable
// OS_APPLICATION_CREDENTIAL_ID, if set.
ApplicationCredentialID string `mapstructure:"application_credential_id" required:"false"`
// The application credential secret to use with application credential
// based authorization. Packer will use the environment variable
// OS_APPLICATION_CREDENTIAL_SECRET, if set.
ApplicationCredentialSecret string `mapstructure:"application_credential_secret" required:"false"`
// An entry in a `clouds.yaml` file. See the OpenStack os-client-config
// [documentation](https://docs.openstack.org/os-client-config/latest/user/configuration.html)
// for more information about `clouds.yaml` files. If omitted, the
// `OS_CLOUD` environment variable is used.
Cloud string `mapstructure:"cloud" required:"false"`
osClient *gophercloud.ProviderClient
}
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
if c.EndpointType != "internal" && c.EndpointType != "internalURL" &&
c.EndpointType != "admin" && c.EndpointType != "adminURL" &&
c.EndpointType != "public" && c.EndpointType != "publicURL" &&
c.EndpointType != "" {
return []error{fmt.Errorf("Invalid endpoint type provided")}
}
// Legacy RackSpace stuff. We're keeping this around to keep things BC.
if c.Password == "" {
c.Password = os.Getenv("SDK_PASSWORD")
}
if c.Region == "" {
c.Region = os.Getenv("SDK_REGION")
}
if c.TenantName == "" {
c.TenantName = os.Getenv("SDK_PROJECT")
}
if c.Username == "" {
c.Username = os.Getenv("SDK_USERNAME")
}
// End RackSpace
if c.Cloud == "" {
c.Cloud = os.Getenv("OS_CLOUD")
}
if c.Region == "" {
c.Region = os.Getenv("OS_REGION_NAME")
}
if c.CACertFile == "" {
c.CACertFile = os.Getenv("OS_CACERT")
}
if c.ClientCertFile == "" {
c.ClientCertFile = os.Getenv("OS_CERT")
}
if c.ClientKeyFile == "" {
c.ClientKeyFile = os.Getenv("OS_KEY")
}
clientOpts := new(clientconfig.ClientOpts)
// If a cloud entry was given, base AuthOptions on a clouds.yaml file.
if c.Cloud != "" {
clientOpts.Cloud = c.Cloud
cloud, err := clientconfig.GetCloudFromYAML(clientOpts)
if err != nil {
return []error{err}
}
if c.Region == "" && cloud.RegionName != "" {
c.Region = cloud.RegionName
}
} else {
authInfo := &clientconfig.AuthInfo{
AuthURL: c.IdentityEndpoint,
DomainID: c.DomainID,
DomainName: c.DomainName,
Password: c.Password,
ProjectID: c.TenantID,
ProjectName: c.TenantName,
Token: c.Token,
Username: c.Username,
UserID: c.UserID,
}
clientOpts.AuthInfo = authInfo
}
ao, err := clientconfig.AuthOptions(clientOpts)
if err != nil {
return []error{err}
}
// Make sure we reauth as needed
ao.AllowReauth = true
// Override values if we have them in our config
overrides := []struct {
From, To *string
}{
{&c.Username, &ao.Username},
{&c.UserID, &ao.UserID},
{&c.Password, &ao.Password},
{&c.IdentityEndpoint, &ao.IdentityEndpoint},
{&c.TenantID, &ao.TenantID},
{&c.TenantName, &ao.TenantName},
{&c.DomainID, &ao.DomainID},
{&c.DomainName, &ao.DomainName},
{&c.Token, &ao.TokenID},
{&c.ApplicationCredentialName, &ao.ApplicationCredentialName},
{&c.ApplicationCredentialID, &ao.ApplicationCredentialID},
{&c.ApplicationCredentialSecret, &ao.ApplicationCredentialSecret},
}
for _, s := range overrides {
if *s.From != "" {
*s.To = *s.From
}
}
// Build the client itself
client, err := openstack.NewClient(ao.IdentityEndpoint)
if err != nil {
return []error{err}
}
tls_config := &tls.Config{}
if c.CACertFile != "" {
caCert, err := ioutil.ReadFile(c.CACertFile)
if err != nil {
return []error{err}
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
tls_config.RootCAs = caCertPool
}
// If we have insecure set, then create a custom HTTP client that ignores
// SSL errors.
if c.Insecure {
tls_config.InsecureSkipVerify = true
}
if c.ClientCertFile != "" && c.ClientKeyFile != "" {
cert, err := tls.LoadX509KeyPair(c.ClientCertFile, c.ClientKeyFile)
if err != nil {
return []error{err}
}
tls_config.Certificates = []tls.Certificate{cert}
}
transport := cleanhttp.DefaultTransport()
transport.TLSClientConfig = tls_config
client.HTTPClient.Transport = transport
// Auth
err = openstack.Authenticate(client, *ao)
if err != nil {
return []error{err}
}
c.osClient = client
return nil
}
func (c *AccessConfig) enableDebug(ui packersdk.Ui) {
c.osClient.HTTPClient = http.Client{
Transport: &DebugRoundTripper{
ui: ui,
rt: c.osClient.HTTPClient.Transport,
},
}
}
func (c *AccessConfig) computeV2Client() (*gophercloud.ServiceClient, error) {
return openstack.NewComputeV2(c.osClient, gophercloud.EndpointOpts{
Region: c.Region,
Availability: c.getEndpointType(),
})
}
func (c *AccessConfig) imageV2Client() (*gophercloud.ServiceClient, error) {
return openstack.NewImageServiceV2(c.osClient, gophercloud.EndpointOpts{
Region: c.Region,
Availability: c.getEndpointType(),
})
}
func (c *AccessConfig) blockStorageV3Client() (*gophercloud.ServiceClient, error) {
return openstack.NewBlockStorageV3(c.osClient, gophercloud.EndpointOpts{
Region: c.Region,
Availability: c.getEndpointType(),
})
}
func (c *AccessConfig) networkV2Client() (*gophercloud.ServiceClient, error) {
return openstack.NewNetworkV2(c.osClient, gophercloud.EndpointOpts{
Region: c.Region,
Availability: c.getEndpointType(),
})
}
func (c *AccessConfig) getEndpointType() gophercloud.Availability {
if c.EndpointType == "internal" || c.EndpointType == "internalURL" {
return gophercloud.AvailabilityInternal
}
if c.EndpointType == "admin" || c.EndpointType == "adminURL" {
return gophercloud.AvailabilityAdmin
}
return gophercloud.AvailabilityPublic
}
type DebugRoundTripper struct {
ui packersdk.Ui
rt http.RoundTripper
numReauthAttempts int
}
// RoundTrip performs a round-trip HTTP request and logs relevant information about it.
func (drt *DebugRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
defer func() {
if request.Body != nil {
request.Body.Close()
}
}()
var response *http.Response
var err error
response, err = drt.rt.RoundTrip(request)
if response == nil {
return nil, err
}
if response.StatusCode == http.StatusUnauthorized {
if drt.numReauthAttempts == 3 {
return response, fmt.Errorf("Tried to re-authenticate 3 times with no success.")
}
drt.numReauthAttempts++
}
drt.DebugMessage(fmt.Sprintf("Request %s %s %d", request.Method, request.URL, response.StatusCode))
if response.StatusCode >= 400 {
buf := bytes.NewBuffer([]byte{})
body, _ := ioutil.ReadAll(io.TeeReader(response.Body, buf))
drt.DebugMessage(fmt.Sprintf("Response Error: %+v\n", string(body)))
bufWithClose := ioutil.NopCloser(buf)
response.Body = bufWithClose
}
return response, err
}
func (drt *DebugRoundTripper) DebugMessage(message string) {
drt.ui.Message(fmt.Sprintf("[DEBUG] %s", message))
}

View File

@ -1,51 +0,0 @@
package openstack
import (
"fmt"
"log"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
)
// Artifact is an artifact implementation that contains built images.
type Artifact struct {
// ImageId of built image
ImageId string
// BuilderId is the unique ID for the builder that created this image
BuilderIdValue string
// OpenStack connection for performing API stuff.
Client *gophercloud.ServiceClient
// StateData should store data such as GeneratedData
// to be shared with post-processors
StateData map[string]interface{}
}
func (a *Artifact) BuilderId() string {
return a.BuilderIdValue
}
func (*Artifact) Files() []string {
// We have no files
return nil
}
func (a *Artifact) Id() string {
return a.ImageId
}
func (a *Artifact) String() string {
return fmt.Sprintf("An image was created: %v", a.ImageId)
}
func (a *Artifact) State(name string) interface{} {
return a.StateData[name]
}
func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %s", a.ImageId)
return images.Delete(a.Client, a.ImageId).ExtractErr()
}

View File

@ -1,62 +0,0 @@
package openstack
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestArtifact_Impl(t *testing.T) {
var _ packersdk.Artifact = new(Artifact)
}
func TestArtifactId(t *testing.T) {
expected := `b8cdf55b-c916-40bd-b190-389ec144c4ed`
a := &Artifact{
ImageId: "b8cdf55b-c916-40bd-b190-389ec144c4ed",
}
result := a.Id()
if result != expected {
t.Fatalf("bad: %s", result)
}
}
func TestArtifactString(t *testing.T) {
expected := "An image was created: b8cdf55b-c916-40bd-b190-389ec144c4ed"
a := &Artifact{
ImageId: "b8cdf55b-c916-40bd-b190-389ec144c4ed",
}
result := a.String()
if result != expected {
t.Fatalf("bad: %s", result)
}
}
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")
}
}

View File

@ -1,200 +0,0 @@
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,ImageFilter,ImageFilterOptions
// The openstack package contains a packersdk.Builder implementation that
// builds Images for openstack.
package openstack
import (
"context"
"fmt"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/common"
"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"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
)
// The unique ID for this builder
const BuilderId = "mitchellh.openstack"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
AccessConfig `mapstructure:",squash"`
ImageConfig `mapstructure:",squash"`
RunConfig `mapstructure:",squash"`
ctx interpolate.Context
}
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) {
err := config.Decode(&b.config, &config.DecodeOpts{
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &b.config.ctx,
}, raws...)
if err != nil {
return nil, nil, err
}
// Accumulate any errors
var errs *packersdk.MultiError
errs = packersdk.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
errs = packersdk.MultiErrorAppend(errs, b.config.ImageConfig.Prepare(&b.config.ctx)...)
errs = packersdk.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs
}
if b.config.ImageConfig.ImageDiskFormat != "" && !b.config.RunConfig.UseBlockStorageVolume {
return nil, nil, fmt.Errorf("use_blockstorage_volume must be true if image_disk_format is specified.")
}
// By default, instance name is same as image name
if b.config.InstanceName == "" {
b.config.InstanceName = b.config.ImageName
}
packersdk.LogSecretFilter.Set(b.config.Password)
return nil, nil, nil
}
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
if b.config.PackerDebug {
b.config.enableDebug(ui)
}
computeClient, err := b.config.computeV2Client()
if err != nil {
return nil, fmt.Errorf("Error initializing compute client: %s", err)
}
imageClient, err := b.config.imageV2Client()
if err != nil {
return nil, fmt.Errorf("Error initializing image client: %s", err)
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("hook", hook)
state.Put("ui", ui)
// Build the steps
steps := []multistep.Step{
&StepLoadFlavor{
Flavor: b.config.Flavor,
},
&StepKeyPair{
Debug: b.config.PackerDebug,
Comm: &b.config.Comm,
DebugKeyPath: fmt.Sprintf("os_%s.pem", b.config.PackerBuildName),
},
&StepSourceImageInfo{
SourceImage: b.config.RunConfig.SourceImage,
SourceImageName: b.config.RunConfig.SourceImageName,
ExternalSourceImageURL: b.config.RunConfig.ExternalSourceImageURL,
ExternalSourceImageFormat: b.config.RunConfig.ExternalSourceImageFormat,
ExternalSourceImageProperties: b.config.RunConfig.ExternalSourceImageProperties,
SourceImageOpts: b.config.RunConfig.sourceImageOpts,
SourceMostRecent: b.config.SourceImageFilters.MostRecent,
SourceProperties: b.config.SourceImageFilters.Filters.Properties,
},
&StepDiscoverNetwork{
Networks: b.config.Networks,
NetworkDiscoveryCIDRs: b.config.NetworkDiscoveryCIDRs,
Ports: b.config.Ports,
},
&StepCreateVolume{
UseBlockStorageVolume: b.config.UseBlockStorageVolume,
VolumeName: b.config.VolumeName,
VolumeType: b.config.VolumeType,
VolumeAvailabilityZone: b.config.VolumeAvailabilityZone,
},
&StepRunSourceServer{
Name: b.config.InstanceName,
SecurityGroups: b.config.SecurityGroups,
AvailabilityZone: b.config.AvailabilityZone,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
ConfigDrive: b.config.ConfigDrive,
InstanceMetadata: b.config.InstanceMetadata,
UseBlockStorageVolume: b.config.UseBlockStorageVolume,
ForceDelete: b.config.ForceDelete,
},
&StepGetPassword{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,
},
&StepWaitForRackConnect{
Wait: b.config.RackconnectWait,
},
&StepAllocateIp{
FloatingIPNetwork: b.config.FloatingIPNetwork,
FloatingIP: b.config.FloatingIP,
ReuseIPs: b.config.ReuseIPs,
InstanceFloatingIPNet: b.config.InstanceFloatingIPNet,
},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: CommHost(
b.config.RunConfig.Comm.Host(),
computeClient,
b.config.SSHInterface,
b.config.SSHIPVersion),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
&commonsteps.StepProvision{},
&commonsteps.StepCleanupTempKeys{
Comm: &b.config.RunConfig.Comm,
},
&StepStopServer{},
&StepDetachVolume{
UseBlockStorageVolume: b.config.UseBlockStorageVolume,
},
&stepCreateImage{
UseBlockStorageVolume: b.config.UseBlockStorageVolume,
},
&stepUpdateImageTags{},
&stepUpdateImageVisibility{},
&stepAddImageMembers{},
&stepUpdateImageMinDisk{},
}
// Run!
b.runner = commonsteps.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If there are no images, then just return
if _, ok := state.GetOk("image"); !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &Artifact{
ImageId: state.Get("image").(string),
BuilderIdValue: BuilderId,
Client: imageClient,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
return artifact, nil
}

View File

@ -1,322 +0,0 @@
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
package openstack
import (
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"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"`
Username *string `mapstructure:"username" required:"true" cty:"username" hcl:"username"`
UserID *string `mapstructure:"user_id" cty:"user_id" hcl:"user_id"`
Password *string `mapstructure:"password" required:"true" cty:"password" hcl:"password"`
IdentityEndpoint *string `mapstructure:"identity_endpoint" required:"true" cty:"identity_endpoint" hcl:"identity_endpoint"`
TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"`
TenantName *string `mapstructure:"tenant_name" cty:"tenant_name" hcl:"tenant_name"`
DomainID *string `mapstructure:"domain_id" cty:"domain_id" hcl:"domain_id"`
DomainName *string `mapstructure:"domain_name" required:"false" cty:"domain_name" hcl:"domain_name"`
Insecure *bool `mapstructure:"insecure" required:"false" cty:"insecure" hcl:"insecure"`
Region *string `mapstructure:"region" required:"false" cty:"region" hcl:"region"`
EndpointType *string `mapstructure:"endpoint_type" required:"false" cty:"endpoint_type" hcl:"endpoint_type"`
CACertFile *string `mapstructure:"cacert" required:"false" cty:"cacert" hcl:"cacert"`
ClientCertFile *string `mapstructure:"cert" required:"false" cty:"cert" hcl:"cert"`
ClientKeyFile *string `mapstructure:"key" required:"false" cty:"key" hcl:"key"`
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
ApplicationCredentialName *string `mapstructure:"application_credential_name" required:"false" cty:"application_credential_name" hcl:"application_credential_name"`
ApplicationCredentialID *string `mapstructure:"application_credential_id" required:"false" cty:"application_credential_id" hcl:"application_credential_id"`
ApplicationCredentialSecret *string `mapstructure:"application_credential_secret" required:"false" cty:"application_credential_secret" hcl:"application_credential_secret"`
Cloud *string `mapstructure:"cloud" required:"false" cty:"cloud" hcl:"cloud"`
ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name" hcl:"image_name"`
ImageMetadata map[string]string `mapstructure:"metadata" required:"false" cty:"metadata" hcl:"metadata"`
ImageVisibility *images.ImageVisibility `mapstructure:"image_visibility" required:"false" cty:"image_visibility" hcl:"image_visibility"`
ImageMembers []string `mapstructure:"image_members" required:"false" cty:"image_members" hcl:"image_members"`
ImageAutoAcceptMembers *bool `mapstructure:"image_auto_accept_members" required:"false" cty:"image_auto_accept_members" hcl:"image_auto_accept_members"`
ImageDiskFormat *string `mapstructure:"image_disk_format" required:"false" cty:"image_disk_format" hcl:"image_disk_format"`
ImageTags []string `mapstructure:"image_tags" required:"false" cty:"image_tags" hcl:"image_tags"`
ImageMinDisk *int `mapstructure:"image_min_disk" required:"false" cty:"image_min_disk" hcl:"image_min_disk"`
SkipCreateImage *bool `mapstructure:"skip_create_image" required:"false" cty:"skip_create_image" hcl:"skip_create_image"`
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"`
SSHInterface *string `mapstructure:"ssh_interface" required:"false" cty:"ssh_interface" hcl:"ssh_interface"`
SSHIPVersion *string `mapstructure:"ssh_ip_version" required:"false" cty:"ssh_ip_version" hcl:"ssh_ip_version"`
SourceImage *string `mapstructure:"source_image" required:"true" cty:"source_image" hcl:"source_image"`
SourceImageName *string `mapstructure:"source_image_name" required:"true" cty:"source_image_name" hcl:"source_image_name"`
ExternalSourceImageURL *string `mapstructure:"external_source_image_url" required:"true" cty:"external_source_image_url" hcl:"external_source_image_url"`
ExternalSourceImageFormat *string `mapstructure:"external_source_image_format" required:"false" cty:"external_source_image_format" hcl:"external_source_image_format"`
ExternalSourceImageProperties map[string]string `mapstructure:"external_source_image_properties" required:"false" cty:"external_source_image_properties" hcl:"external_source_image_properties"`
SourceImageFilters *FlatImageFilter `mapstructure:"source_image_filter" required:"true" cty:"source_image_filter" hcl:"source_image_filter"`
Flavor *string `mapstructure:"flavor" required:"true" cty:"flavor" hcl:"flavor"`
AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone" hcl:"availability_zone"`
RackconnectWait *bool `mapstructure:"rackconnect_wait" required:"false" cty:"rackconnect_wait" hcl:"rackconnect_wait"`
FloatingIPNetwork *string `mapstructure:"floating_ip_network" required:"false" cty:"floating_ip_network" hcl:"floating_ip_network"`
InstanceFloatingIPNet *string `mapstructure:"instance_floating_ip_net" required:"false" cty:"instance_floating_ip_net" hcl:"instance_floating_ip_net"`
FloatingIP *string `mapstructure:"floating_ip" required:"false" cty:"floating_ip" hcl:"floating_ip"`
ReuseIPs *bool `mapstructure:"reuse_ips" required:"false" cty:"reuse_ips" hcl:"reuse_ips"`
SecurityGroups []string `mapstructure:"security_groups" required:"false" cty:"security_groups" hcl:"security_groups"`
Networks []string `mapstructure:"networks" required:"false" cty:"networks" hcl:"networks"`
Ports []string `mapstructure:"ports" required:"false" cty:"ports" hcl:"ports"`
NetworkDiscoveryCIDRs []string `mapstructure:"network_discovery_cidrs" required:"false" cty:"network_discovery_cidrs" hcl:"network_discovery_cidrs"`
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file" hcl:"user_data_file"`
InstanceName *string `mapstructure:"instance_name" required:"false" cty:"instance_name" hcl:"instance_name"`
InstanceMetadata map[string]string `mapstructure:"instance_metadata" required:"false" cty:"instance_metadata" hcl:"instance_metadata"`
ForceDelete *bool `mapstructure:"force_delete" required:"false" cty:"force_delete" hcl:"force_delete"`
ConfigDrive *bool `mapstructure:"config_drive" required:"false" cty:"config_drive" hcl:"config_drive"`
FloatingIPPool *string `mapstructure:"floating_ip_pool" required:"false" cty:"floating_ip_pool" hcl:"floating_ip_pool"`
UseBlockStorageVolume *bool `mapstructure:"use_blockstorage_volume" required:"false" cty:"use_blockstorage_volume" hcl:"use_blockstorage_volume"`
VolumeName *string `mapstructure:"volume_name" required:"false" cty:"volume_name" hcl:"volume_name"`
VolumeType *string `mapstructure:"volume_type" required:"false" cty:"volume_type" hcl:"volume_type"`
VolumeSize *int `mapstructure:"volume_size" required:"false" cty:"volume_size" hcl:"volume_size"`
VolumeAvailabilityZone *string `mapstructure:"volume_availability_zone" required:"false" cty:"volume_availability_zone" hcl:"volume_availability_zone"`
OpenstackProvider *string `mapstructure:"openstack_provider" cty:"openstack_provider" hcl:"openstack_provider"`
UseFloatingIp *bool `mapstructure:"use_floating_ip" required:"false" cty:"use_floating_ip" hcl:"use_floating_ip"`
}
// 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},
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
"user_id": &hcldec.AttrSpec{Name: "user_id", Type: cty.String, Required: false},
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
"identity_endpoint": &hcldec.AttrSpec{Name: "identity_endpoint", Type: cty.String, Required: false},
"tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false},
"tenant_name": &hcldec.AttrSpec{Name: "tenant_name", Type: cty.String, Required: false},
"domain_id": &hcldec.AttrSpec{Name: "domain_id", Type: cty.String, Required: false},
"domain_name": &hcldec.AttrSpec{Name: "domain_name", Type: cty.String, Required: false},
"insecure": &hcldec.AttrSpec{Name: "insecure", Type: cty.Bool, Required: false},
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
"endpoint_type": &hcldec.AttrSpec{Name: "endpoint_type", Type: cty.String, Required: false},
"cacert": &hcldec.AttrSpec{Name: "cacert", Type: cty.String, Required: false},
"cert": &hcldec.AttrSpec{Name: "cert", Type: cty.String, Required: false},
"key": &hcldec.AttrSpec{Name: "key", Type: cty.String, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"application_credential_name": &hcldec.AttrSpec{Name: "application_credential_name", Type: cty.String, Required: false},
"application_credential_id": &hcldec.AttrSpec{Name: "application_credential_id", Type: cty.String, Required: false},
"application_credential_secret": &hcldec.AttrSpec{Name: "application_credential_secret", Type: cty.String, Required: false},
"cloud": &hcldec.AttrSpec{Name: "cloud", Type: cty.String, Required: false},
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
"metadata": &hcldec.AttrSpec{Name: "metadata", Type: cty.Map(cty.String), Required: false},
"image_visibility": &hcldec.AttrSpec{Name: "image_visibility", Type: cty.String, Required: false},
"image_members": &hcldec.AttrSpec{Name: "image_members", Type: cty.List(cty.String), Required: false},
"image_auto_accept_members": &hcldec.AttrSpec{Name: "image_auto_accept_members", Type: cty.Bool, Required: false},
"image_disk_format": &hcldec.AttrSpec{Name: "image_disk_format", Type: cty.String, Required: false},
"image_tags": &hcldec.AttrSpec{Name: "image_tags", Type: cty.List(cty.String), Required: false},
"image_min_disk": &hcldec.AttrSpec{Name: "image_min_disk", Type: cty.Number, Required: false},
"skip_create_image": &hcldec.AttrSpec{Name: "skip_create_image", Type: cty.Bool, 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},
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
"ssh_ip_version": &hcldec.AttrSpec{Name: "ssh_ip_version", Type: cty.String, Required: false},
"source_image": &hcldec.AttrSpec{Name: "source_image", Type: cty.String, Required: false},
"source_image_name": &hcldec.AttrSpec{Name: "source_image_name", Type: cty.String, Required: false},
"external_source_image_url": &hcldec.AttrSpec{Name: "external_source_image_url", Type: cty.String, Required: false},
"external_source_image_format": &hcldec.AttrSpec{Name: "external_source_image_format", Type: cty.String, Required: false},
"external_source_image_properties": &hcldec.AttrSpec{Name: "external_source_image_properties", Type: cty.Map(cty.String), Required: false},
"source_image_filter": &hcldec.BlockSpec{TypeName: "source_image_filter", Nested: hcldec.ObjectSpec((*FlatImageFilter)(nil).HCL2Spec())},
"flavor": &hcldec.AttrSpec{Name: "flavor", Type: cty.String, Required: false},
"availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false},
"rackconnect_wait": &hcldec.AttrSpec{Name: "rackconnect_wait", Type: cty.Bool, Required: false},
"floating_ip_network": &hcldec.AttrSpec{Name: "floating_ip_network", Type: cty.String, Required: false},
"instance_floating_ip_net": &hcldec.AttrSpec{Name: "instance_floating_ip_net", Type: cty.String, Required: false},
"floating_ip": &hcldec.AttrSpec{Name: "floating_ip", Type: cty.String, Required: false},
"reuse_ips": &hcldec.AttrSpec{Name: "reuse_ips", Type: cty.Bool, Required: false},
"security_groups": &hcldec.AttrSpec{Name: "security_groups", Type: cty.List(cty.String), Required: false},
"networks": &hcldec.AttrSpec{Name: "networks", Type: cty.List(cty.String), Required: false},
"ports": &hcldec.AttrSpec{Name: "ports", Type: cty.List(cty.String), Required: false},
"network_discovery_cidrs": &hcldec.AttrSpec{Name: "network_discovery_cidrs", Type: cty.List(cty.String), Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
"instance_metadata": &hcldec.AttrSpec{Name: "instance_metadata", Type: cty.Map(cty.String), Required: false},
"force_delete": &hcldec.AttrSpec{Name: "force_delete", Type: cty.Bool, Required: false},
"config_drive": &hcldec.AttrSpec{Name: "config_drive", Type: cty.Bool, Required: false},
"floating_ip_pool": &hcldec.AttrSpec{Name: "floating_ip_pool", Type: cty.String, Required: false},
"use_blockstorage_volume": &hcldec.AttrSpec{Name: "use_blockstorage_volume", Type: cty.Bool, Required: false},
"volume_name": &hcldec.AttrSpec{Name: "volume_name", Type: cty.String, Required: false},
"volume_type": &hcldec.AttrSpec{Name: "volume_type", Type: cty.String, Required: false},
"volume_size": &hcldec.AttrSpec{Name: "volume_size", Type: cty.Number, Required: false},
"volume_availability_zone": &hcldec.AttrSpec{Name: "volume_availability_zone", Type: cty.String, Required: false},
"openstack_provider": &hcldec.AttrSpec{Name: "openstack_provider", Type: cty.String, Required: false},
"use_floating_ip": &hcldec.AttrSpec{Name: "use_floating_ip", Type: cty.Bool, Required: false},
}
return s
}
// FlatImageFilter is an auto-generated flat version of ImageFilter.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatImageFilter struct {
Filters *FlatImageFilterOptions `mapstructure:"filters" required:"false" cty:"filters" hcl:"filters"`
MostRecent *bool `mapstructure:"most_recent" required:"false" cty:"most_recent" hcl:"most_recent"`
}
// FlatMapstructure returns a new FlatImageFilter.
// FlatImageFilter is an auto-generated flat version of ImageFilter.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*ImageFilter) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatImageFilter)
}
// HCL2Spec returns the hcl spec of a ImageFilter.
// This spec is used by HCL to read the fields of ImageFilter.
// The decoded values from this spec will then be applied to a FlatImageFilter.
func (*FlatImageFilter) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"filters": &hcldec.BlockSpec{TypeName: "filters", Nested: hcldec.ObjectSpec((*FlatImageFilterOptions)(nil).HCL2Spec())},
"most_recent": &hcldec.AttrSpec{Name: "most_recent", Type: cty.Bool, Required: false},
}
return s
}
// FlatImageFilterOptions is an auto-generated flat version of ImageFilterOptions.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatImageFilterOptions struct {
Name *string `mapstructure:"name" cty:"name" hcl:"name"`
Owner *string `mapstructure:"owner" cty:"owner" hcl:"owner"`
Tags []string `mapstructure:"tags" cty:"tags" hcl:"tags"`
Visibility *string `mapstructure:"visibility" cty:"visibility" hcl:"visibility"`
Properties map[string]string `mapstructure:"properties" cty:"properties" hcl:"properties"`
}
// FlatMapstructure returns a new FlatImageFilterOptions.
// FlatImageFilterOptions is an auto-generated flat version of ImageFilterOptions.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*ImageFilterOptions) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatImageFilterOptions)
}
// HCL2Spec returns the hcl spec of a ImageFilterOptions.
// This spec is used by HCL to read the fields of ImageFilterOptions.
// The decoded values from this spec will then be applied to a FlatImageFilterOptions.
func (*FlatImageFilterOptions) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"name": &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false},
"owner": &hcldec.AttrSpec{Name: "owner", Type: cty.String, Required: false},
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.List(cty.String), Required: false},
"visibility": &hcldec.AttrSpec{Name: "visibility", Type: cty.String, Required: false},
"properties": &hcldec.AttrSpec{Name: "properties", Type: cty.Map(cty.String), Required: false},
}
return s
}

View File

@ -1,30 +0,0 @@
package openstack
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestBuilder_ImplementsBuilder(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{}{
"password": []string{},
}
_, warns, err := b.Prepare(c)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatalf("prepare should fail")
}
}

View File

@ -1,83 +0,0 @@
//go:generate packer-sdc struct-markdown
package openstack
import (
"fmt"
"strings"
imageservice "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
)
// ImageConfig is for common configuration related to creating Images.
type ImageConfig struct {
// The name of the resulting image.
ImageName string `mapstructure:"image_name" required:"true"`
// Glance metadata that will be applied to the image.
ImageMetadata map[string]string `mapstructure:"metadata" required:"false"`
// One of "public", "private", "shared", or "community".
ImageVisibility imageservice.ImageVisibility `mapstructure:"image_visibility" required:"false"`
// List of members to add to the image after creation. An image member is
// usually a project (also called the "tenant") with whom the image is
// shared.
ImageMembers []string `mapstructure:"image_members" required:"false"`
// When true, perform the image accept so the members can see the image in their
// project. This requires a user with priveleges both in the build project and
// in the members provided. Defaults to false.
ImageAutoAcceptMembers bool `mapstructure:"image_auto_accept_members" required:"false"`
// Disk format of the resulting image. This option works if
// use_blockstorage_volume is true.
ImageDiskFormat string `mapstructure:"image_disk_format" required:"false"`
// List of tags to add to the image after creation.
ImageTags []string `mapstructure:"image_tags" required:"false"`
// Minimum disk size needed to boot image, in gigabytes.
ImageMinDisk int `mapstructure:"image_min_disk" required:"false"`
// Skip creating the image. Useful for setting to `true` during a build test stage. Defaults to `false`.
SkipCreateImage bool `mapstructure:"skip_create_image" required:"false"`
}
func (c *ImageConfig) Prepare(ctx *interpolate.Context) []error {
errs := make([]error, 0)
if c.ImageName == "" {
errs = append(errs, fmt.Errorf("An image_name must be specified"))
}
// By default, OpenStack seems to create the image with an image_type of
// "snapshot", since it came from snapshotting a VM. A "snapshot" looks
// slightly different in the OpenStack UI and OpenStack won't show
// "snapshot" images as a choice in the list of images to boot from for a
// new instance. See https://github.com/hashicorp/packer/issues/3038
if c.ImageMetadata == nil {
c.ImageMetadata = map[string]string{"image_type": "image"}
} else if c.ImageMetadata["image_type"] == "" {
c.ImageMetadata["image_type"] = "image"
}
// ImageVisibility values
// https://wiki.openstack.org/wiki/Glance-v2-community-image-visibility-design
if c.ImageVisibility != "" {
validVals := []imageservice.ImageVisibility{"public", "private", "shared", "community"}
valid := false
for _, val := range validVals {
if strings.EqualFold(string(c.ImageVisibility), string(val)) {
valid = true
c.ImageVisibility = val
break
}
}
if !valid {
errs = append(errs, fmt.Errorf("Unknown visibility value %s", c.ImageVisibility))
}
}
if c.ImageMinDisk < 0 {
errs = append(errs, fmt.Errorf("An image min disk size must be greater than or equal to 0"))
}
if len(errs) > 0 {
return errs
}
return nil
}

View File

@ -1,23 +0,0 @@
package openstack
import (
"testing"
)
func testImageConfig() *ImageConfig {
return &ImageConfig{
ImageName: "foo",
}
}
func TestImageConfigPrepare_Region(t *testing.T) {
c := testImageConfig()
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.ImageName = ""
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
}

View File

@ -1,180 +0,0 @@
package openstack
import (
"fmt"
"log"
"net"
"github.com/google/uuid"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
"github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
"github.com/gophercloud/gophercloud/pagination"
)
// CheckFloatingIP gets a floating IP by its ID and checks if it is already
// associated with any internal interface.
// It returns floating IP if it can be used.
func CheckFloatingIP(client *gophercloud.ServiceClient, id string) (*floatingips.FloatingIP, error) {
floatingIP, err := floatingips.Get(client, id).Extract()
if err != nil {
return nil, err
}
if floatingIP.PortID != "" {
return nil, fmt.Errorf("provided floating IP '%s' is already associated with port '%s'",
id, floatingIP.PortID)
}
return floatingIP, nil
}
// FindFreeFloatingIP returns free unassociated floating IP.
// It will return first floating IP if there are many.
func FindFreeFloatingIP(client *gophercloud.ServiceClient) (*floatingips.FloatingIP, error) {
var freeFloatingIP *floatingips.FloatingIP
pager := floatingips.List(client, floatingips.ListOpts{
Status: "DOWN",
})
err := pager.EachPage(func(page pagination.Page) (bool, error) {
candidates, err := floatingips.ExtractFloatingIPs(page)
if err != nil {
return false, err // stop and throw error out
}
for _, candidate := range candidates {
if candidate.PortID != "" {
continue // this floating IP is associated with port, move to next in list
}
// Floating IP is able to be allocated.
freeFloatingIP = &candidate
return false, nil // stop iterating over pages
}
return true, nil // try the next page
})
if err != nil {
return nil, err
}
if freeFloatingIP == nil {
return nil, fmt.Errorf("no free floating IPs found")
}
return freeFloatingIP, nil
}
// GetInstancePortID returns internal port of the instance that can be used for
// the association of a floating IP.
// It will return an ID of a first port if there are many.
func GetInstancePortID(client *gophercloud.ServiceClient, id string, instance_float_net string) (string, error) {
selected_interface := 0
interfacesPage, err := attachinterfaces.List(client, id).AllPages()
if err != nil {
return "", err
}
interfaces, err := attachinterfaces.ExtractInterfaces(interfacesPage)
if err != nil {
return "", err
}
if len(interfaces) == 0 {
return "", fmt.Errorf("instance '%s' has no interfaces", id)
}
for i := 0; i < len(interfaces); i++ {
log.Printf("Instance interface: %v: %+v\n", i, interfaces[i])
if interfaces[i].NetID == instance_float_net {
log.Printf("Found preferred interface: %v\n", i)
selected_interface = i
log.Printf("Using interface value: %v", selected_interface)
}
}
return interfaces[selected_interface].PortID, nil
}
// CheckFloatingIPNetwork checks provided network reference and returns a valid
// Networking service ID.
func CheckFloatingIPNetwork(client *gophercloud.ServiceClient, networkRef string) (string, error) {
if _, err := uuid.Parse(networkRef); err != nil {
return GetFloatingIPNetworkIDByName(client, networkRef)
}
return networkRef, nil
}
// ExternalNetwork is a network with external router.
type ExternalNetwork struct {
networks.Network
external.NetworkExternalExt
}
// GetFloatingIPNetworkIDByName searches for the external network ID by the provided name.
func GetFloatingIPNetworkIDByName(client *gophercloud.ServiceClient, networkName string) (string, error) {
var externalNetworks []ExternalNetwork
allPages, err := networks.List(client, networks.ListOpts{
Name: networkName,
}).AllPages()
if err != nil {
return "", err
}
if err := networks.ExtractNetworksInto(allPages, &externalNetworks); err != nil {
return "", err
}
if len(externalNetworks) == 0 {
return "", fmt.Errorf("can't find external network %s", networkName)
}
// Check and return the first external network.
if !externalNetworks[0].External {
return "", fmt.Errorf("network %s is not external", networkName)
}
return externalNetworks[0].ID, nil
}
// DiscoverProvisioningNetwork finds the first network whose subnet matches the given network ranges.
func DiscoverProvisioningNetwork(client *gophercloud.ServiceClient, cidrs []string) (string, error) {
allPages, err := subnets.List(client, subnets.ListOpts{}).AllPages()
if err != nil {
return "", err
}
allSubnets, err := subnets.ExtractSubnets(allPages)
if err != nil {
return "", err
}
for _, subnet := range allSubnets {
_, tenantIPNet, err := net.ParseCIDR(subnet.CIDR)
if err != nil {
return "", err
}
for _, cidr := range cidrs {
_, candidateIPNet, err := net.ParseCIDR(cidr)
if err != nil {
return "", err
}
if containsNet(candidateIPNet, tenantIPNet) {
return subnet.NetworkID, nil
}
}
}
return "", fmt.Errorf("failed to discover a provisioning network")
}
// containsNet returns true whenever IPNet `a` contains IPNet `b`
func containsNet(a *net.IPNet, b *net.IPNet) bool {
aMask, _ := a.Mask.Size()
bMask, _ := b.Mask.Size()
return a.Contains(b.IP) && aMask <= bMask
}

View File

@ -1,54 +0,0 @@
package openstack
import (
"net"
"testing"
)
func testYes(t *testing.T, a, b string) {
var m, n *net.IPNet
_, m, _ = net.ParseCIDR(a)
_, n, _ = net.ParseCIDR(b)
if !containsNet(m, n) {
t.Errorf("%s expected to contain %s", m, n)
}
}
func testNot(t *testing.T, a, b string) {
var m, n *net.IPNet
_, m, _ = net.ParseCIDR(a)
_, n, _ = net.ParseCIDR(b)
if containsNet(m, n) {
t.Errorf("%s expected to not contain %s", m, n)
}
}
func TestNetworkDiscovery_SubnetContainsGood_IPv4(t *testing.T) {
testYes(t, "192.168.0.0/23", "192.168.0.0/24")
testYes(t, "192.168.0.0/24", "192.168.0.0/24")
testNot(t, "192.168.0.0/25", "192.168.0.0/24")
testYes(t, "192.168.101.202/16", "192.168.202.101/16")
testNot(t, "192.168.101.202/24", "192.168.202.101/24")
testNot(t, "192.168.202.101/24", "192.168.101.202/24")
testYes(t, "0.0.0.0/0", "192.168.0.0/24")
testYes(t, "0.0.0.0/0", "0.0.0.0/1")
testNot(t, "192.168.0.0/24", "0.0.0.0/0")
testNot(t, "0.0.0.0/1", "0.0.0.0/0")
}
func TestNetworkDiscovery_SubnetContainsGood_IPv6(t *testing.T) {
testYes(t, "2001:db8::/63", "2001:db8::/64")
testYes(t, "2001:db8::/64", "2001:db8::/64")
testNot(t, "2001:db8::/65", "2001:db8::/64")
testYes(t, "2001:db8:fefe:b00b::/32", "2001:db8:b00b:fefe::/32")
testNot(t, "2001:db8:fefe:b00b::/64", "2001:db8:b00b:fefe::/64")
testNot(t, "2001:db8:b00b:fefe::/64", "2001:db8:fefe:b00b::/64")
testYes(t, "::/0", "2001:db8::/64")
testYes(t, "::/0", "::/1")
testNot(t, "2001:db8::/64", "::/0")
testNot(t, "::/1", "::/0")
}

View File

@ -1,345 +0,0 @@
//go:generate packer-sdc struct-markdown
package openstack
import (
"errors"
"fmt"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer-plugin-sdk/uuid"
)
// RunConfig contains configuration for running an instance from a source image
// and details on how to access that launched image.
type RunConfig struct {
Comm communicator.Config `mapstructure:",squash"`
// The type of interface to connect via SSH. Values useful for Rackspace
// are "public" or "private", and the default behavior is to connect via
// whichever is returned first from the OpenStack API.
SSHInterface string `mapstructure:"ssh_interface" required:"false"`
// The IP version to use for SSH connections, valid values are `4` and `6`.
// Useful on dual stacked instances where the default behavior is to
// connect via whichever IP address is returned first from the OpenStack
// API.
SSHIPVersion string `mapstructure:"ssh_ip_version" required:"false"`
// The ID or full URL to the base image to use. This is the image that will
// be used to launch a new server and provision it. Unless you specify
// completely custom SSH settings, the source image must have cloud-init
// installed so that the keypair gets assigned properly.
SourceImage string `mapstructure:"source_image" required:"true"`
// The name of the base image to use. This is an alternative way of
// providing source_image and only either of them can be specified.
SourceImageName string `mapstructure:"source_image_name" required:"true"`
// The URL of an external base image to use. This is an alternative way of
// providing source_image and only either of them can be specified.
ExternalSourceImageURL string `mapstructure:"external_source_image_url" required:"true"`
// The format of the external source image to use, e.g. qcow2, raw.
ExternalSourceImageFormat string `mapstructure:"external_source_image_format" required:"false"`
// Properties to set for the external source image
ExternalSourceImageProperties map[string]string `mapstructure:"external_source_image_properties" required:"false"`
// Filters used to populate filter options. Example:
//
// ```json
//{
// "source_image_filter": {
// "filters": {
// "name": "ubuntu-16.04",
// "visibility": "protected",
// "owner": "d1a588cf4b0743344508dc145649372d1",
// "tags": ["prod", "ready"],
// "properties": {
// "os_distro": "ubuntu"
// }
// },
// "most_recent": true
// }
// }
// ```
//
// This selects the most recent production Ubuntu 16.04 shared to you by
// the given owner. NOTE: This will fail unless *exactly* one image is
// returned, or `most_recent` is set to true. In the example of multiple
// returned images, `most_recent` will cause this to succeed by selecting
// the newest image of the returned images.
//
// - `filters` (map of strings) - filters used to select a
// `source_image`.
// NOTE: This will fail unless *exactly* one image is returned, or
// `most_recent` is set to true. Of the filters described in
// [ImageService](https://developer.openstack.org/api-ref/image/v2/), the
// following are valid:
//
// - name (string)
// - owner (string)
// - tags (array of strings)
// - visibility (string)
// - properties (map of strings to strings) (fields that can be set
// with `openstack image set --property key=value`)
//
// - `most_recent` (boolean) - Selects the newest created image when
// true.
// This is most useful for selecting a daily distro build.
//
// You may set use this in place of `source_image` If `source_image_filter`
// is provided alongside `source_image`, the `source_image` will override
// the filter. The filter will not be used in this case.
SourceImageFilters ImageFilter `mapstructure:"source_image_filter" required:"true"`
// The ID, name, or full URL for the desired flavor for the server to be
// created.
Flavor string `mapstructure:"flavor" required:"true"`
// The availability zone to launch the server in. If this isn't specified,
// the default enforced by your OpenStack cluster will be used. This may be
// required for some OpenStack clusters.
AvailabilityZone string `mapstructure:"availability_zone" required:"false"`
// For rackspace, whether or not to wait for Rackconnect to assign the
// machine an IP address before connecting via SSH. Defaults to false.
RackconnectWait bool `mapstructure:"rackconnect_wait" required:"false"`
// The ID or name of an external network that can be used for creation of a
// new floating IP.
FloatingIPNetwork string `mapstructure:"floating_ip_network" required:"false"`
// The ID of the network to which the instance is attached and which should
// be used to associate with the floating IP. This provides control over
// the floating ip association on multi-homed instances. The association
// otherwise depends on a first-returned-interface policy which could fail
// if the network to which it is connected is unreachable from the floating
// IP network.
InstanceFloatingIPNet string `mapstructure:"instance_floating_ip_net" required:"false"`
// A specific floating IP to assign to this instance.
FloatingIP string `mapstructure:"floating_ip" required:"false"`
// Whether or not to attempt to reuse existing unassigned floating ips in
// the project before allocating a new one. Note that it is not possible to
// safely do this concurrently, so if you are running multiple openstack
// builds concurrently, or if other processes are assigning and using
// floating IPs in the same openstack project while packer is running, you
// should not set this to true. Defaults to false.
ReuseIPs bool `mapstructure:"reuse_ips" required:"false"`
// A list of security groups by name to add to this instance.
SecurityGroups []string `mapstructure:"security_groups" required:"false"`
// A list of networks by UUID to attach to this instance.
Networks []string `mapstructure:"networks" required:"false"`
// A list of ports by UUID to attach to this instance.
Ports []string `mapstructure:"ports" required:"false"`
// A list of network CIDRs to discover the network to attach to this instance.
// The first network whose subnet is contained within any of the given CIDRs
// is used. Ignored if either of the above two options are provided.
NetworkDiscoveryCIDRs []string `mapstructure:"network_discovery_cidrs" required:"false"`
// User data to apply when launching the instance. Note that you need to be
// careful about escaping characters due to the templates being JSON. It is
// often more convenient to use user_data_file, instead. Packer will not
// automatically wait for a user script to finish before shutting down the
// instance this must be handled in a provisioner.
UserData string `mapstructure:"user_data" required:"false"`
// Path to a file that will be used for the user data when launching the
// instance.
UserDataFile string `mapstructure:"user_data_file" required:"false"`
// Name that is applied to the server instance created by Packer. If this
// isn't specified, the default is same as image_name.
InstanceName string `mapstructure:"instance_name" required:"false"`
// Metadata that is applied to the server instance created by Packer. Also
// called server properties in some documentation. The strings have a max
// size of 255 bytes each.
InstanceMetadata map[string]string `mapstructure:"instance_metadata" required:"false"`
// Whether to force the OpenStack instance to be forcefully deleted. This
// is useful for environments that have reclaim / soft deletion enabled. By
// default this is false.
ForceDelete bool `mapstructure:"force_delete" required:"false"`
// Whether or not nova should use ConfigDrive for cloud-init metadata.
ConfigDrive bool `mapstructure:"config_drive" required:"false"`
// Deprecated use floating_ip_network instead.
FloatingIPPool string `mapstructure:"floating_ip_pool" required:"false"`
// Use Block Storage service volume for the instance root volume instead of
// Compute service local volume (default).
UseBlockStorageVolume bool `mapstructure:"use_blockstorage_volume" required:"false"`
// Name of the Block Storage service volume. If this isn't specified,
// random string will be used.
VolumeName string `mapstructure:"volume_name" required:"false"`
// Type of the Block Storage service volume. If this isn't specified, the
// default enforced by your OpenStack cluster will be used.
VolumeType string `mapstructure:"volume_type" required:"false"`
// Size of the Block Storage service volume in GB. If this isn't specified,
// it is set to source image min disk value (if set) or calculated from the
// source image bytes size. Note that in some cases this needs to be
// specified, if use_blockstorage_volume is true.
VolumeSize int `mapstructure:"volume_size" required:"false"`
// Availability zone of the Block Storage service volume. If omitted,
// Compute instance availability zone will be used. If both of Compute
// instance and Block Storage volume availability zones aren't specified,
// the default enforced by your OpenStack cluster will be used.
VolumeAvailabilityZone string `mapstructure:"volume_availability_zone" required:"false"`
// Not really used, but here for BC
OpenstackProvider string `mapstructure:"openstack_provider"`
// *Deprecated* use `floating_ip` or `floating_ip_pool` instead.
UseFloatingIp bool `mapstructure:"use_floating_ip" required:"false"`
sourceImageOpts images.ListOpts
}
type ImageFilter struct {
// filters used to select a source_image. NOTE: This will fail unless
// exactly one image is returned, or most_recent is set to true. Of the
// filters described in ImageService, the following are valid:
Filters ImageFilterOptions `mapstructure:"filters" required:"false"`
// Selects the newest created image when true. This is most useful for
// selecting a daily distro build.
MostRecent bool `mapstructure:"most_recent" required:"false"`
}
type ImageFilterOptions struct {
Name string `mapstructure:"name"`
Owner string `mapstructure:"owner"`
Tags []string `mapstructure:"tags"`
Visibility string `mapstructure:"visibility"`
Properties map[string]string `mapstructure:"properties"`
}
func (f *ImageFilterOptions) Empty() bool {
return f.Name == "" && f.Owner == "" && len(f.Tags) == 0 && f.Visibility == "" && len(f.Properties) == 0
}
func (f *ImageFilterOptions) Build() (*images.ListOpts, error) {
opts := images.ListOpts{}
// Set defaults for status, member_status, and sort
opts.Status = images.ImageStatusActive
opts.MemberStatus = images.ImageMemberStatusAccepted
opts.Sort = "created_at:desc"
var err error
if f.Name != "" {
opts.Name = f.Name
}
if f.Owner != "" {
opts.Owner = f.Owner
}
if len(f.Tags) > 0 {
opts.Tags = f.Tags
}
if f.Visibility != "" {
v, err := getImageVisibility(f.Visibility)
if err == nil {
opts.Visibility = *v
}
}
return &opts, err
}
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
// If we are not given an explicit ssh_keypair_name or
// ssh_private_key_file, then create a temporary one, but only if the
// temporary_key_pair_name has not been provided and we are not using
// ssh_password.
if c.Comm.SSHKeyPairName == "" && c.Comm.SSHTemporaryKeyPairName == "" &&
c.Comm.SSHPrivateKeyFile == "" && c.Comm.SSHPassword == "" {
c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
}
if c.FloatingIPPool != "" && c.FloatingIPNetwork == "" {
c.FloatingIPNetwork = c.FloatingIPPool
}
// Validation
errs := c.Comm.Prepare(ctx)
if c.Comm.SSHKeyPairName != "" {
if c.Comm.Type == "winrm" && c.Comm.WinRMPassword == "" && c.Comm.SSHPrivateKeyFile == "" {
errs = append(errs, errors.New("A ssh_private_key_file must be provided to retrieve the winrm password when using ssh_keypair_name."))
} else if c.Comm.SSHPrivateKeyFile == "" && !c.Comm.SSHAgentAuth {
errs = append(errs, errors.New("A ssh_private_key_file must be provided or ssh_agent_auth enabled when ssh_keypair_name is specified."))
}
}
if c.SourceImage == "" && c.SourceImageName == "" && c.ExternalSourceImageURL == "" && c.SourceImageFilters.Filters.Empty() {
errs = append(errs, errors.New("Either a source_image, a source_image_name, an external_source_image_url or source_image_filter must be specified"))
} else {
// Make sure we've only set one image source option
thereCanBeOnlyOne := []bool{len(c.SourceImageName) > 0, len(c.SourceImage) > 0, len(c.ExternalSourceImageURL) > 0, !c.SourceImageFilters.Filters.Empty()}
numSet := 0
for _, val := range thereCanBeOnlyOne {
if val {
numSet += 1
}
}
if numSet > 1 {
errs = append(errs, errors.New("Only one of the options source_image, source_image_name, external_source_image_url, or source_image_filter can be specified, not multiple."))
}
}
// if external_source_image_format is not set use qcow2 as default
if c.ExternalSourceImageFormat == "" {
c.ExternalSourceImageFormat = "qcow2"
}
if c.Flavor == "" {
errs = append(errs, errors.New("A flavor must be specified"))
}
if c.SSHIPVersion != "" && c.SSHIPVersion != "4" && c.SSHIPVersion != "6" {
errs = append(errs, errors.New("SSH IP version must be either 4 or 6"))
}
for key, value := range c.InstanceMetadata {
if len(key) > 255 {
errs = append(errs, fmt.Errorf("Instance metadata key too long (max 255 bytes): %s", key))
}
if len(value) > 255 {
errs = append(errs, fmt.Errorf("Instance metadata value too long (max 255 bytes): %s", value))
}
}
if c.UseBlockStorageVolume {
// Use Compute instance availability zone for the Block Storage volume
// if it's not provided.
if c.VolumeAvailabilityZone == "" {
c.VolumeAvailabilityZone = c.AvailabilityZone
}
// Use random name for the Block Storage volume if it's not provided.
if c.VolumeName == "" {
c.VolumeName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
}
}
// if neither ID, image name or external image URL is provided outside the filter,
// build the filter
if len(c.SourceImage) == 0 && len(c.SourceImageName) == 0 && len(c.ExternalSourceImageURL) == 0 {
listOpts, filterErr := c.SourceImageFilters.Filters.Build()
if filterErr != nil {
errs = append(errs, filterErr)
}
c.sourceImageOpts = *listOpts
}
// if c.ExternalSourceImageURL is set use a generated source image name
if c.ExternalSourceImageURL != "" {
c.SourceImageName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
}
return errs
}
// Retrieve the specific ImageVisibility using the exported const from images
func getImageVisibility(visibility string) (*images.ImageVisibility, error) {
visibilities := [...]images.ImageVisibility{
images.ImageVisibilityPublic,
images.ImageVisibilityPrivate,
images.ImageVisibilityCommunity,
images.ImageVisibilityShared,
}
for _, v := range visibilities {
if string(v) == visibility {
return &v, nil
}
}
return nil, fmt.Errorf("Not a valid visibility: %s", visibility)
}

View File

@ -1,284 +0,0 @@
package openstack
import (
"os"
"regexp"
"testing"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/mitchellh/mapstructure"
)
func init() {
// Clear out the openstack env vars so they don't
// affect our tests.
os.Setenv("SDK_USERNAME", "")
os.Setenv("SDK_PASSWORD", "")
os.Setenv("SDK_PROVIDER", "")
}
func testRunConfig() *RunConfig {
return &RunConfig{
SourceImage: "abcd",
Flavor: "m1.small",
Comm: communicator.Config{
SSH: communicator.SSH{
SSHUsername: "foo",
},
},
}
}
func TestRunConfigPrepare(t *testing.T) {
c := testRunConfig()
err := c.Prepare(nil)
if len(err) > 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_InstanceType(t *testing.T) {
c := testRunConfig()
c.Flavor = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SourceImage(t *testing.T) {
c := testRunConfig()
c.SourceImage = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SSHPort(t *testing.T) {
c := testRunConfig()
c.Comm.SSHPort = 0
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHPort != 22 {
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
}
c.Comm.SSHPort = 44
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHPort != 44 {
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
}
}
func TestRunConfigPrepare_BlockStorage(t *testing.T) {
c := testRunConfig()
c.UseBlockStorageVolume = true
c.VolumeType = "fast"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.VolumeType != "fast" {
t.Fatalf("invalid value: %s", c.VolumeType)
}
c.AvailabilityZone = "RegionTwo"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.VolumeAvailabilityZone != "RegionTwo" {
t.Fatalf("invalid value: %s", c.VolumeAvailabilityZone)
}
c.VolumeAvailabilityZone = "RegionOne"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.VolumeAvailabilityZone != "RegionOne" {
t.Fatalf("invalid value: %s", c.VolumeAvailabilityZone)
}
c.VolumeName = "PackerVolume"
if c.VolumeName != "PackerVolume" {
t.Fatalf("invalid value: %s", c.VolumeName)
}
}
func TestRunConfigPrepare_FloatingIPPoolCompat(t *testing.T) {
c := testRunConfig()
c.FloatingIPPool = "uuid1"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.FloatingIPNetwork != "uuid1" {
t.Fatalf("invalid value: %s", c.FloatingIPNetwork)
}
c.FloatingIPNetwork = "uuid2"
c.FloatingIPPool = "uuid3"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.FloatingIPNetwork != "uuid2" {
t.Fatalf("invalid value: %s", c.FloatingIPNetwork)
}
}
func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
c := testRunConfig()
// test setting both ExternalSourceImageURL and SourceImage causes an error
c.ExternalSourceImageURL = "http://example.com/image.qcow2"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
c = testRunConfig()
// test setting both ExternalSourceImageURL and SourceImageName causes an error
c.SourceImage = ""
c.SourceImageName = "abcd"
c.ExternalSourceImageURL = "http://example.com/image.qcow2"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
c = testRunConfig()
// test neither setting SourceImage, SourceImageName or ExternalSourceImageURL causes an error
c.SourceImage = ""
c.SourceImageName = ""
c.ExternalSourceImageURL = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
c = testRunConfig()
// test setting only ExternalSourceImageURL passes
c.SourceImage = ""
c.SourceImageName = ""
c.ExternalSourceImageURL = "http://example.com/image.qcow2"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
// test default values
if c.ExternalSourceImageFormat != "qcow2" {
t.Fatalf("ExternalSourceImageFormat should have been set to default: qcow2")
}
p := `packer_[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}`
if matches, _ := regexp.MatchString(p, c.SourceImageName); !matches {
t.Fatalf("invalid format for SourceImageName: %s", c.SourceImageName)
}
c = testRunConfig()
// test setting a filter passes
c.SourceImage = ""
c.SourceImageName = ""
c.ExternalSourceImageURL = ""
c.SourceImageFilters = ImageFilter{
Filters: ImageFilterOptions{
Name: "Ubuntu 16.04",
Visibility: "public",
Owner: "1234567890",
Tags: []string{"prod", "ready"},
Properties: map[string]string{"os_distro": "ubuntu", "os_version": "16.04"},
},
MostRecent: true,
}
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("Should not error if everything but filter is empty: %s", err)
}
}
// This test case confirms that only allowed fields will be set to values
// The checked values are non-nil for their target type
func TestBuildImageFilter(t *testing.T) {
filters := ImageFilterOptions{
Name: "Ubuntu 16.04",
Visibility: "public",
Owner: "1234567890",
Tags: []string{"prod", "ready"},
Properties: map[string]string{"os_distro": "ubuntu", "os_version": "16.04"},
}
listOpts, err := filters.Build()
if err != nil {
t.Errorf("Building filter failed with: %s", err)
}
if listOpts.Name != "Ubuntu 16.04" {
t.Errorf("Name did not build correctly: %s", listOpts.Name)
}
if listOpts.Visibility != images.ImageVisibilityPublic {
t.Errorf("Visibility did not build correctly: %s", listOpts.Visibility)
}
if listOpts.Owner != "1234567890" {
t.Errorf("Owner did not build correctly: %s", listOpts.Owner)
}
}
func TestBuildBadImageFilter(t *testing.T) {
filterMap := map[string]interface{}{
"limit": "3",
"size_min": "25",
}
filters := ImageFilterOptions{}
mapstructure.Decode(filterMap, &filters)
listOpts, err := filters.Build()
if err != nil {
t.Errorf("Error returned processing image filter: %s", err.Error())
return // we cannot trust listOpts to not cause unexpected behaviour
}
if listOpts.Limit == filterMap["limit"] {
t.Errorf("Limit was parsed into ListOpts: %d", listOpts.Limit)
}
if listOpts.SizeMin != 0 {
t.Errorf("SizeMin was parsed into ListOpts: %d", listOpts.SizeMin)
}
if listOpts.Sort != "created_at:desc" {
t.Errorf("Sort was not applied: %s", listOpts.Sort)
}
if !filters.Empty() {
t.Errorf("The filters should be empty due to lack of input")
}
}
// Tests that the Empty method on ImageFilterOptions works as expected
func TestImageFiltersEmpty(t *testing.T) {
filledFilters := ImageFilterOptions{
Name: "Ubuntu 16.04",
Visibility: "public",
Owner: "1234567890",
Tags: []string{"prod", "ready"},
Properties: map[string]string{"os_distro": "ubuntu", "os_version": "16.04"},
}
if filledFilters.Empty() {
t.Errorf("Expected filled filters to be non-empty: %v", filledFilters)
}
emptyFilters := ImageFilterOptions{}
if !emptyFilters.Empty() {
t.Errorf("Expected default filter to be empty: %v", emptyFilters)
}
}

View File

@ -1,93 +0,0 @@
package openstack
import (
"errors"
"fmt"
"log"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
// StateRefreshFunc is a function type used for StateChangeConf that is
// responsible for refreshing the item being watched for a state change.
//
// It returns three results. `result` is any object that will be returned
// as the final object after waiting for state change. This allows you to
// return the final updated object, for example an openstack instance after
// refreshing it.
//
// `state` is the latest state of that object. And `err` is any error that
// may have happened while refreshing the state.
type StateRefreshFunc func() (result interface{}, state string, progress int, err error)
// StateChangeConf is the configuration struct used for `WaitForState`.
type StateChangeConf struct {
Pending []string
Refresh StateRefreshFunc
StepState multistep.StateBag
Target []string
}
// ServerStateRefreshFunc returns a StateRefreshFunc that is used to watch
// an openstack server.
func ServerStateRefreshFunc(
client *gophercloud.ServiceClient, s *servers.Server) StateRefreshFunc {
return func() (interface{}, string, int, error) {
serverNew, err := servers.Get(client, s.ID).Extract()
if err != nil {
if _, ok := err.(gophercloud.ErrDefault404); ok {
log.Printf("[INFO] 404 on ServerStateRefresh, returning DELETED")
return nil, "DELETED", 0, nil
}
log.Printf("[ERROR] Error on ServerStateRefresh: %s", err)
return nil, "", 0, err
}
return serverNew, serverNew.Status, serverNew.Progress, nil
}
}
// WaitForState watches an object and waits for it to achieve a certain
// state.
func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
log.Printf("Waiting for state to become: %s", conf.Target)
for {
var currentProgress int
var currentState string
i, currentState, currentProgress, err = conf.Refresh()
if err != nil {
return
}
for _, t := range conf.Target {
if currentState == t {
return
}
}
if conf.StepState != nil {
if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("interrupted")
}
}
found := false
for _, allowed := range conf.Pending {
if currentState == allowed {
found = true
break
}
}
if !found {
return nil, fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
}
log.Printf("Waiting for state to become: %s currently %s (%d%%)", conf.Target, currentState, currentProgress)
time.Sleep(2 * time.Second)
}
}

View File

@ -1,116 +0,0 @@
package openstack
import (
"errors"
"fmt"
"log"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
// CommHost looks up the host for the communicator.
func CommHost(
host string,
client *gophercloud.ServiceClient,
sshinterface string,
sshipversion string) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
if host != "" {
log.Printf("Using host value: %s", host)
return host, nil
}
s := state.Get("server").(*servers.Server)
// If we have a specific interface, try that
if sshinterface != "" {
if addr := sshAddrFromPool(s, sshinterface, sshipversion); addr != "" {
log.Printf("[DEBUG] Using IP address %s from specified interface %s to connect", addr, sshinterface)
return addr, nil
}
}
// If we have a floating IP, use that
ip := state.Get("access_ip").(*floatingips.FloatingIP)
if ip != nil && ip.FloatingIP != "" {
log.Printf("[DEBUG] Using floating IP %s to connect", ip.FloatingIP)
return ip.FloatingIP, nil
}
if s.AccessIPv4 != "" {
log.Printf("[DEBUG] Using AccessIPv4 %s to connect", s.AccessIPv4)
return s.AccessIPv4, nil
}
// Try to get it from the requested interface
if addr := sshAddrFromPool(s, sshinterface, sshipversion); addr != "" {
log.Printf("[DEBUG] Using IP address %s to connect", addr)
return addr, nil
}
s, err := servers.Get(client, s.ID).Extract()
if err != nil {
return "", err
}
state.Put("server", s)
time.Sleep(1 * time.Second)
return "", errors.New("couldn't determine IP address for server")
}
}
func sshAddrFromPool(s *servers.Server, desired string, sshIPVersion string) string {
// Get all the addresses associated with this server. This
// was taken directly from Terraform.
for pool, networkAddresses := range s.Addresses {
// If we have an SSH interface specified, skip it if no match
if desired != "" && pool != desired {
log.Printf(
"[INFO] Skipping pool %s, doesn't match requested %s",
pool, desired)
continue
}
elements, ok := networkAddresses.([]interface{})
if !ok {
log.Printf(
"[ERROR] Unknown return type for address field: %#v",
networkAddresses)
continue
}
for _, element := range elements {
var addr string
address := element.(map[string]interface{})
if address["OS-EXT-IPS:type"] == "floating" {
addr = address["addr"].(string)
} else if sshIPVersion == "4" {
if address["version"].(float64) == 4 {
addr = address["addr"].(string)
}
} else if sshIPVersion == "6" {
if address["version"].(float64) == 6 {
addr = fmt.Sprintf("[%s]", address["addr"].(string))
}
} else {
if address["version"].(float64) == 6 {
addr = fmt.Sprintf("[%s]", address["addr"].(string))
} else {
addr = address["addr"].(string)
}
}
if addr != "" {
log.Printf("[DEBUG] Detected address: %s", addr)
return addr
}
}
}
return ""
}

View File

@ -1,63 +0,0 @@
package openstack
import (
"context"
"fmt"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/members"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type stepAddImageMembers struct{}
func (s *stepAddImageMembers) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
config := state.Get("config").(*Config)
if config.SkipCreateImage {
ui.Say("Skipping image add members...")
return multistep.ActionContinue
}
imageId := state.Get("image").(string)
if len(config.ImageMembers) == 0 {
return multistep.ActionContinue
}
imageClient, err := config.imageV2Client()
if err != nil {
err = fmt.Errorf("Error initializing image service client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
for _, member := range config.ImageMembers {
ui.Say(fmt.Sprintf("Adding member '%s' to image %s", member, imageId))
r := members.Create(imageClient, imageId, member)
if _, err = r.Extract(); err != nil {
err = fmt.Errorf("Error adding member to image: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
}
if config.ImageAutoAcceptMembers {
for _, member := range config.ImageMembers {
ui.Say(fmt.Sprintf("Accepting image %s for member '%s'", imageId, member))
r := members.Update(imageClient, imageId, member, members.UpdateOpts{Status: "accepted"})
if _, err = r.Extract(); err != nil {
err = fmt.Errorf("Error accepting image for member: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}
func (s *stepAddImageMembers) Cleanup(multistep.StateBag) {
// No cleanup...
}

View File

@ -1,178 +0,0 @@
package openstack
import (
"context"
"fmt"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type StepAllocateIp struct {
FloatingIPNetwork string
FloatingIP string
ReuseIPs bool
InstanceFloatingIPNet string
}
func (s *StepAllocateIp) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
config := state.Get("config").(*Config)
server := state.Get("server").(*servers.Server)
var instanceIP floatingips.FloatingIP
// This is here in case we error out before putting instanceIp into the
// statebag below, because it is requested by Cleanup()
state.Put("access_ip", &instanceIP)
if s.FloatingIP == "" && !s.ReuseIPs && s.FloatingIPNetwork == "" {
ui.Message("Floating IP not required")
return multistep.ActionContinue
}
// We need the v2 compute client
computeClient, err := config.computeV2Client()
if err != nil {
err = fmt.Errorf("Error initializing compute client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
// We need the v2 network client
networkClient, err := config.networkV2Client()
if err != nil {
err = fmt.Errorf("Error initializing network client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
// Try to Use the OpenStack floating IP by checking provided parameters in
// the following order:
// - try to use "FloatingIP" ID directly if it's provided
// - try to find free floating IP in the project if "ReuseIPs" is set
// - create a new floating IP if "FloatingIPNetwork" is provided (it can be
// ID or name of the network).
if s.FloatingIP != "" {
// Try to use FloatingIP if it was provided by the user.
freeFloatingIP, err := CheckFloatingIP(networkClient, s.FloatingIP)
if err != nil {
err := fmt.Errorf("Error using provided floating IP '%s': %s", s.FloatingIP, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
instanceIP = *freeFloatingIP
ui.Message(fmt.Sprintf("Selected floating IP: '%s' (%s)", instanceIP.ID, instanceIP.FloatingIP))
state.Put("floatingip_istemp", false)
} else if s.ReuseIPs {
// If ReuseIPs is set to true and we have a free floating IP, use it rather
// than creating one.
ui.Say(fmt.Sprint("Searching for unassociated floating IP"))
freeFloatingIP, err := FindFreeFloatingIP(networkClient)
if err != nil {
err := fmt.Errorf("Error searching for floating IP: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
instanceIP = *freeFloatingIP
ui.Message(fmt.Sprintf("Selected floating IP: '%s' (%s)", instanceIP.ID, instanceIP.FloatingIP))
state.Put("floatingip_istemp", false)
} else if s.FloatingIPNetwork != "" {
// Lastly, if FloatingIPNetwork was provided by the user, we need to use it
// to allocate a new floating IP and associate it to the instance.
floatingNetwork, err := CheckFloatingIPNetwork(networkClient, s.FloatingIPNetwork)
if err != nil {
err := fmt.Errorf("Error using the provided floating_ip_network: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Creating floating IP using network %s ...", floatingNetwork))
newIP, err := floatingips.Create(networkClient, floatingips.CreateOpts{
FloatingNetworkID: floatingNetwork,
}).Extract()
if err != nil {
err := fmt.Errorf("Error creating floating IP from floating network '%s': %s", floatingNetwork, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
instanceIP = *newIP
ui.Message(fmt.Sprintf("Created floating IP: '%s' (%s)", instanceIP.ID, instanceIP.FloatingIP))
state.Put("floatingip_istemp", true)
}
// Assoctate a floating IP if it was obtained in the previous steps.
if instanceIP.ID != "" {
ui.Say(fmt.Sprintf("Associating floating IP '%s' (%s) with instance port...",
instanceIP.ID, instanceIP.FloatingIP))
portID, err := GetInstancePortID(computeClient, server.ID, s.InstanceFloatingIPNet)
if err != nil {
err := fmt.Errorf("Error getting interfaces of the instance '%s': %s", server.ID, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
_, err = floatingips.Update(networkClient, instanceIP.ID, floatingips.UpdateOpts{
PortID: &portID,
}).Extract()
if err != nil {
err := fmt.Errorf(
"Error associating floating IP '%s' (%s) with instance port '%s': %s",
instanceIP.ID, instanceIP.FloatingIP, portID, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Message(fmt.Sprintf(
"Added floating IP '%s' (%s) to instance!", instanceIP.ID, instanceIP.FloatingIP))
}
state.Put("access_ip", &instanceIP)
return multistep.ActionContinue
}
func (s *StepAllocateIp) Cleanup(state multistep.StateBag) {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
instanceIP := state.Get("access_ip").(*floatingips.FloatingIP)
// Don't clean up if unless required
if instanceIP.ID == "" && instanceIP.FloatingIP == "" {
return
}
// Don't delete pool addresses we didn't allocate
if state.Get("floatingip_istemp") == false {
return
}
// We need the v2 network client
client, err := config.networkV2Client()
if err != nil {
ui.Error(fmt.Sprintf(
"Error deleting temporary floating IP '%s' (%s)", instanceIP.ID, instanceIP.FloatingIP))
return
}
if instanceIP.ID != "" {
if err := floatingips.Delete(client, instanceIP.ID).ExtractErr(); err != nil {
ui.Error(fmt.Sprintf(
"Error deleting temporary floating IP '%s' (%s)", instanceIP.ID, instanceIP.FloatingIP))
return
}
ui.Say(fmt.Sprintf("Deleted temporary floating IP '%s' (%s)", instanceIP.ID, instanceIP.FloatingIP))
}
}

View File

@ -1,152 +0,0 @@
package openstack
import (
"context"
"fmt"
"log"
"time"
"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type stepCreateImage struct {
UseBlockStorageVolume bool
}
func (s *stepCreateImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
server := state.Get("server").(*servers.Server)
ui := state.Get("ui").(packersdk.Ui)
if config.SkipCreateImage {
ui.Say("Skipping image creation...")
return multistep.ActionContinue
}
// We need the v2 compute client
computeClient, err := config.computeV2Client()
if err != nil {
err = fmt.Errorf("Error initializing compute client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
// We need the v2 image client
imageClient, err := config.imageV2Client()
if err != nil {
err = fmt.Errorf("Error initializing image service client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
// Create the image.
// Image source depends on the type of the Compute instance. It can be
// Block Storage service volume or regular Compute service local volume.
ui.Say(fmt.Sprintf("Creating the image: %s", config.ImageName))
var imageId string
var blockStorageClient *gophercloud.ServiceClient
if s.UseBlockStorageVolume {
// We need the v3 block storage client.
blockStorageClient, err = config.blockStorageV3Client()
if err != nil {
err = fmt.Errorf("Error initializing block storage client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
volume := state.Get("volume_id").(string)
// set ImageMetadata before uploading to glance so the new image captured the desired values
if len(config.ImageMetadata) > 0 {
err = volumeactions.SetImageMetadata(blockStorageClient, volume, volumeactions.ImageMetadataOpts{
Metadata: config.ImageMetadata,
}).ExtractErr()
if err != nil {
err := fmt.Errorf("Error setting image metadata: %s", err)
ui.Error(err.Error())
}
}
image, err := volumeactions.UploadImage(blockStorageClient, volume, volumeactions.UploadImageOpts{
DiskFormat: config.ImageDiskFormat,
ImageName: config.ImageName,
}).Extract()
if err != nil {
err := fmt.Errorf("Error creating image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
imageId = image.ImageID
} else {
imageId, err = servers.CreateImage(computeClient, server.ID, servers.CreateImageOpts{
Name: config.ImageName,
Metadata: config.ImageMetadata,
}).ExtractImageID()
if err != nil {
err := fmt.Errorf("Error creating image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
// Set the Image ID in the state
ui.Message(fmt.Sprintf("Image: %s", imageId))
state.Put("image", imageId)
// Wait for the image to become ready
ui.Say(fmt.Sprintf("Waiting for image %s (image id: %s) to become ready...", config.ImageName, imageId))
if err := WaitForImage(ctx, imageClient, imageId); err != nil {
err := fmt.Errorf("Error waiting for image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepCreateImage) Cleanup(multistep.StateBag) {
// No cleanup...
}
// WaitForImage waits for the given Image ID to become ready.
func WaitForImage(ctx context.Context, client *gophercloud.ServiceClient, imageId string) error {
maxNumErrors := 10
numErrors := 0
for {
if err := ctx.Err(); err != nil {
return err
}
image, err := images.Get(client, imageId).Extract()
if err != nil {
errCode, ok := err.(*gophercloud.ErrUnexpectedResponseCode)
if ok && (errCode.Actual == 500 || errCode.Actual == 404) {
numErrors++
if numErrors >= maxNumErrors {
log.Printf("[ERROR] Maximum number of errors (%d) reached; failing with: %s", numErrors, err)
return err
}
log.Printf("[ERROR] %d error received, will ignore and retry: %s", errCode.Actual, err)
time.Sleep(2 * time.Second)
continue
}
return err
}
if image.Status == "active" {
return nil
}
log.Printf("Waiting for image creation status: %s", image.Status)
time.Sleep(2 * time.Second)
}
}

View File

@ -1,134 +0,0 @@
package openstack
import (
"context"
"fmt"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type StepCreateVolume struct {
UseBlockStorageVolume bool
VolumeName string
VolumeType string
VolumeAvailabilityZone string
volumeID string
doCleanup bool
}
func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
// Proceed only if block storage volume is required.
if !s.UseBlockStorageVolume {
return multistep.ActionContinue
}
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
sourceImage := state.Get("source_image").(string)
// We will need Block Storage and Image services clients.
blockStorageClient, err := config.blockStorageV3Client()
if err != nil {
err = fmt.Errorf("Error initializing block storage client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
volumeSize := config.VolumeSize
// Get needed volume size from the source image.
if volumeSize == 0 {
imageClient, err := config.imageV2Client()
if err != nil {
err = fmt.Errorf("Error initializing image client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
volumeSize, err = GetVolumeSize(imageClient, sourceImage)
if err != nil {
err := fmt.Errorf("Error creating volume: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
ui.Say("Creating volume...")
volumeOpts := volumes.CreateOpts{
Size: volumeSize,
VolumeType: s.VolumeType,
AvailabilityZone: s.VolumeAvailabilityZone,
Name: s.VolumeName,
ImageID: sourceImage,
Metadata: config.ImageMetadata,
}
volume, err := volumes.Create(blockStorageClient, volumeOpts).Extract()
if err != nil {
err := fmt.Errorf("Error creating volume: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Wait for volume to become available.
ui.Say(fmt.Sprintf("Waiting for volume %s (volume id: %s) to become available...", config.VolumeName, volume.ID))
if err := WaitForVolume(blockStorageClient, volume.ID); err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Volume was created, so remember to clean it up.
s.doCleanup = true
// Set the Volume ID in the state.
ui.Message(fmt.Sprintf("Volume ID: %s", volume.ID))
state.Put("volume_id", volume.ID)
s.volumeID = volume.ID
return multistep.ActionContinue
}
func (s *StepCreateVolume) Cleanup(state multistep.StateBag) {
if !s.doCleanup {
return
}
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
blockStorageClient, err := config.blockStorageV3Client()
if err != nil {
ui.Error(fmt.Sprintf(
"Error cleaning up volume. Please delete the volume manually: %s", s.volumeID))
return
}
// Wait for volume to become available.
status, err := GetVolumeStatus(blockStorageClient, s.volumeID)
if err != nil {
ui.Error(fmt.Sprintf(
"Error getting the volume information. Please delete the volume manually: %s", s.volumeID))
return
}
if status != "available" {
ui.Say(fmt.Sprintf(
"Waiting for volume %s (volume id: %s) to become available...", s.VolumeName, s.volumeID))
if err := WaitForVolume(blockStorageClient, s.volumeID); err != nil {
ui.Error(fmt.Sprintf(
"Error getting the volume information. Please delete the volume manually: %s", s.volumeID))
return
}
}
ui.Say(fmt.Sprintf("Deleting volume: %s ...", s.volumeID))
err = volumes.Delete(blockStorageClient, s.volumeID, volumes.DeleteOpts{}).ExtractErr()
if err != nil {
ui.Error(fmt.Sprintf(
"Error cleaning up volume. Please delete the volume manually: %s", s.volumeID))
}
}

View File

@ -1,54 +0,0 @@
package openstack
import (
"context"
"fmt"
"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type StepDetachVolume struct {
UseBlockStorageVolume bool
}
func (s *StepDetachVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
// Proceed only if block storage volume is used.
if !s.UseBlockStorageVolume {
return multistep.ActionContinue
}
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
blockStorageClient, err := config.blockStorageV3Client()
if err != nil {
err = fmt.Errorf("Error initializing block storage client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
volume := state.Get("volume_id").(string)
ui.Say(fmt.Sprintf("Detaching volume %s (volume id: %s)", config.VolumeName, volume))
if err := volumeactions.Detach(blockStorageClient, volume, volumeactions.DetachOpts{}).ExtractErr(); err != nil {
err = fmt.Errorf("Error detaching block storage volume: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
// Wait for volume to become available.
ui.Say(fmt.Sprintf("Waiting for volume %s (volume id: %s) to become available...", config.VolumeName, volume))
if err := WaitForVolume(blockStorageClient, volume); err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepDetachVolume) Cleanup(multistep.StateBag) {
// No cleanup.
}

View File

@ -1,55 +0,0 @@
package openstack
import (
"context"
"fmt"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type StepDiscoverNetwork struct {
Networks []string
NetworkDiscoveryCIDRs []string
Ports []string
}
func (s *StepDiscoverNetwork) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
networkClient, err := config.networkV2Client()
if err != nil {
err = fmt.Errorf("Error initializing network client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
networks := []servers.Network{}
for _, port := range s.Ports {
networks = append(networks, servers.Network{Port: port})
}
for _, uuid := range s.Networks {
networks = append(networks, servers.Network{UUID: uuid})
}
cidrs := s.NetworkDiscoveryCIDRs
if len(networks) == 0 && len(cidrs) > 0 {
ui.Say(fmt.Sprintf("Discovering provisioning network..."))
networkID, err := DiscoverProvisioningNetwork(networkClient, cidrs)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
ui.Message(fmt.Sprintf("Found network ID: %s", networkID))
networks = append(networks, servers.Network{UUID: networkID})
}
state.Put("networks", networks)
return multistep.ActionContinue
}
func (s *StepDiscoverNetwork) Cleanup(state multistep.StateBag) {}

View File

@ -1,85 +0,0 @@
package openstack
import (
"context"
"crypto/rsa"
"fmt"
"log"
"time"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"golang.org/x/crypto/ssh"
)
// StepGetPassword reads the password from a booted OpenStack server and sets
// it on the WinRM config.
type StepGetPassword struct {
Debug bool
Comm *communicator.Config
BuildName string
}
func (s *StepGetPassword) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
// Skip if we're not using winrm
if s.Comm.Type != "winrm" {
log.Printf("[INFO] Not using winrm communicator, skipping get password...")
return multistep.ActionContinue
}
// If we already have a password, skip it
if s.Comm.WinRMPassword != "" {
ui.Say("Skipping waiting for password since WinRM password set...")
return multistep.ActionContinue
}
// We need the v2 compute client
computeClient, err := config.computeV2Client()
if err != nil {
err = fmt.Errorf("Error initializing compute client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
ui.Say("Waiting for password since WinRM password is not set...")
server := state.Get("server").(*servers.Server)
var password string
privateKey, err := ssh.ParseRawPrivateKey(s.Comm.SSHPrivateKey)
if err != nil {
err = fmt.Errorf("Error parsing private key: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
for ; password == "" && err == nil; password, err = servers.GetPassword(computeClient, server.ID).ExtractPassword(privateKey.(*rsa.PrivateKey)) {
// Check for an interrupt in between attempts.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return multistep.ActionHalt
}
log.Printf("Retrying to get a administrator password evry 5 seconds.")
time.Sleep(5 * time.Second)
}
ui.Message(fmt.Sprintf("Password retrieved!"))
s.Comm.WinRMPassword = password
// In debug-mode, we output the password
if s.Debug {
ui.Message(fmt.Sprintf(
"Password (since debug is enabled) \"%s\"", s.Comm.WinRMPassword))
}
packersdk.LogSecretFilter.Set(s.Comm.WinRMPassword)
return multistep.ActionContinue
}
func (s *StepGetPassword) Cleanup(multistep.StateBag) {}

View File

@ -1,190 +0,0 @@
package openstack
import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"runtime"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/tmp"
"golang.org/x/crypto/ssh"
)
type StepKeyPair struct {
Debug bool
Comm *communicator.Config
DebugKeyPath string
doCleanup bool
}
func (s *StepKeyPair) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
if s.Comm.SSHPrivateKeyFile != "" {
ui.Say("Using existing SSH private key")
privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
s.Comm.SSHPrivateKey = privateKeyBytes
return multistep.ActionContinue
}
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName == "" {
ui.Say("Using SSH Agent with key pair in Source image")
return multistep.ActionContinue
}
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName != "" {
ui.Say(fmt.Sprintf("Using SSH Agent for existing key pair %s", s.Comm.SSHKeyPairName))
s.Comm.SSHKeyPairName = ""
return multistep.ActionContinue
}
if s.Comm.SSHTemporaryKeyPairName == "" {
ui.Say("Not using temporary keypair")
s.Comm.SSHKeyPairName = ""
return multistep.ActionContinue
}
config := state.Get("config").(*Config)
// We need the v2 compute client
computeClient, err := config.computeV2Client()
if err != nil {
err = fmt.Errorf("Error initializing compute client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Creating temporary keypair: %s ...", s.Comm.SSHTemporaryKeyPairName))
keypair, err := keypairs.Create(computeClient, keypairs.CreateOpts{
Name: s.Comm.SSHTemporaryKeyPairName,
}).Extract()
if err != nil {
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
return multistep.ActionHalt
}
if len(keypair.PrivateKey) == 0 {
state.Put("error", fmt.Errorf("The temporary keypair returned was blank"))
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Created temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName))
keypair.PrivateKey = string(berToDer([]byte(keypair.PrivateKey), ui))
// 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([]byte(keypair.PrivateKey)); 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
}
}
}
// we created a temporary key, so remember to clean it up
s.doCleanup = true
// Set some state data for use in future steps
s.Comm.SSHKeyPairName = s.Comm.SSHTemporaryKeyPairName
s.Comm.SSHPrivateKey = []byte(keypair.PrivateKey)
return multistep.ActionContinue
}
// Work around for https://github.com/hashicorp/packer/issues/2526
func berToDer(ber []byte, ui packersdk.Ui) []byte {
// Check if x/crypto/ssh can parse the key
_, err := ssh.ParsePrivateKey(ber)
if err == nil {
return ber
}
// Can't parse the key, maybe it's BER encoded. Try to convert it with OpenSSL.
log.Println("Couldn't parse SSH key, trying work around for [GH-2526].")
openSslPath, err := exec.LookPath("openssl")
if err != nil {
log.Println("Couldn't find OpenSSL, aborting work around.")
return ber
}
berKey, err := tmp.File("packer-ber-privatekey-")
defer os.Remove(berKey.Name())
if err != nil {
return ber
}
ioutil.WriteFile(berKey.Name(), ber, os.ModeAppend)
derKey, err := tmp.File("packer-der-privatekey-")
defer os.Remove(derKey.Name())
if err != nil {
return ber
}
args := []string{"rsa", "-in", berKey.Name(), "-out", derKey.Name()}
log.Printf("Executing: %s %v", openSslPath, args)
if err := exec.Command(openSslPath, args...).Run(); err != nil {
log.Printf("OpenSSL failed with error: %s", err)
return ber
}
der, err := ioutil.ReadFile(derKey.Name())
if err != nil {
return ber
}
ui.Say("Successfully converted BER encoded SSH key to DER encoding.")
return der
}
func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
if !s.doCleanup {
return
}
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
// We need the v2 compute client
computeClient, err := config.computeV2Client()
if err != nil {
ui.Error(fmt.Sprintf(
"Error cleaning up keypair. Please delete the key manually: %s", s.Comm.SSHTemporaryKeyPairName))
return
}
ui.Say(fmt.Sprintf("Deleting temporary keypair: %s ...", s.Comm.SSHTemporaryKeyPairName))
err = keypairs.Delete(computeClient, s.Comm.SSHTemporaryKeyPairName).ExtractErr()
if err != nil {
ui.Error(fmt.Sprintf(
"Error cleaning up keypair. Please delete the key manually: %s", s.Comm.SSHTemporaryKeyPairName))
}
}

View File

@ -1,102 +0,0 @@
package openstack
import (
"bytes"
"os/exec"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"golang.org/x/crypto/ssh"
)
var ber_encoded_key = `
-----BEGIN RSA PRIVATE KEY-----
MIIEqQIBAAKCAQEAo1C/iQtu7iSUJROyQgKm5VtJbaPg5GEP7IC+7TTZ2kVnWa9V
MU37sXKWk0J7oRHcSZ/dhRyNDcsvscFAU6V3FRpZxiTEvCqIBgLyoV3g+wkan+gD
AqNmqm77ldUIwMXT7n3AeWipw0uOBzZNjANhAf8qHIT2PXoT6LfaCof4PlPucCTx
eC+oT5zA/MbQoGneNkHnR26ijMac2vRC90+1WpZ5KwCVE6qd2kERlb9gbAGsNLE/
WrqR7bx4d8esLtE3l5zwkBTB63KdojMCrX/ZBiHT15TBQVsFPQqhdBT3BXfssnut
MkCf6+X0PIQkQcW6RqKR5nOHMFdB1kEChaKYMwIDAQABAoIBAQCRBPv/opJvjzWp
stLAgQBYe/Y5EKN7yKDOPwjLM/obMzPx1JqOvJO6X2lL/GYxgd2d1wJq2A586CdC
7brETBLxP0PmifHUsOO2itmO5wEHiW8F/Yzmw9g/kWuAAfrSyxhFF49Zf9H3ZFkL
GHJF2R5EGqP3TS4nKwcQyGkqntCV7p+QmPvlB05jT3vsc2jLn2yXXVEbu1X5Zj82
cdFwH4ZSc2BDv8ixBHy+zOGLx0TMF0hxEHdNIeAjGTyYfiyDr5mgMP4w6igGvP8q
pB5fE60ZKCEPLGyxMw69nDbXJK6YAKyNCVAD79FEl1yLYJlWfYAtOI6UeE0RUPeD
i42vINyBAoIAgQC9zZzl4kJUTdzYLYrxQ7f51B+eOF621kleTYLprr8I4VZUExht
KtsHdHWzMkxVtd7Q7UcG9L+Pqa3DVnD3S2C1IuUkO3eCpa93jhiwCIRYQfR7EzuR
ntVrQHfYaCgr8ahmWdoeZUr8lSDDp0/h+MamEksNEjZA+XwFWg6ycLgGwQKCAIEA
3EYz5iHqaOdekBYXcFpkq8uk+Sn27Cmnd7pLfK/YWEAb3Pie9X0pftA1O8z0tW9p
vEBS+pA27S55a38avmZSkZAiXMJOZ3HV99VZi5WU0Pg9A9oIiQvW6td9b1uuVl5q
o9fCK/r17E7NCFimtqSNtrNR/X08g+l4Dp1Ourgm7/MCggCAIGMohbWhGd+bcqv6
zIaAqzm+F3KI/uv74wKY9yUhZfOFlp0XivFIJLKDrwtDKVD6b249s3sqAOq0QuPK
LPiIzP/iV9dp4jpBgcYWgltBsgm3HRVAEe4nfsCmcp/7UtxOnwBwDsW8EPOlfp1b
LTUVOJtggR99cILh3cvrPBmt3UECggCBALsRH7g8a1+lxmglashu6/n+G1/DZMER
avjCDKOajuf7oe4acpzXK6tX1S2xFM0VDj3iftXuLcdl5ZYGPsceDNc0CgqutXki
cu1jkgV6BgUmHGMuAnuow19znEI7ISaWTohQjsVc/wctsPB6oTKRMwzK40Gc3wzD
9MKsk5T9GYxDAoIAgAFW9agGS4th1RrVwngjKHCPenQR1QSvHDHv3E3EQta1d0K3
Cx7RCdBpDNeNIpHVq8uTaWjSZ+Em6YDja7CqIfPj0HcykizxS4L7Dh0gt7I5+kuy
yBgJhEgIw3mK7U47nm9MmR/67RI4IS978ekVXP2oGdYqVUhHvMuOix8a72xk
-----END RSA PRIVATE KEY-----
`
var der_encoded_key = `
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAmfhARpOHdm5i551GEtOO2NEH36Dm2NjJp6eHQpnrwDW0bqd6
qHJSmhocSb/3r2WwPOsfBKa135aCEOqXKcrc2gY+IsgGMF8bFG//UVcHA0KGG9A8
mWwYBIU4lpb7DY54IllDDO11BERrxOEsPqmNgGzE0yOYApC2t8OcXsUE15kznhLn
d4V53kN//jRGR44KrGMcL8nrKDIBudz90ae7tRGBHYiMWPB89yYdUt06dXPLqqbf
JIUhuOBwle3IOuwJPmPEwsXFXkxKCYzKDAyi+weCDKtqeUl3HNbCB0f7g/uxUDhQ
wRzZ1v4g35LHh5zQR26dizl4Uj2dNLL6K5xkPwIDAQABAoIBABdpTPSuSAG1BSrs
mhQQwP6swgK553//bqIkcgepedRPFjFhG+BzCaZO5BA+tT2hO6v3oE7Hvo3Rx9Mk
qHl9VBl+q4IEYhSG0YpJAUxv7CwNuHCQODan3fsJ+rHDIUdNa2zln7FehdVxReW4
y05334EwiLkGB34UXQQSJTuvv228rF5F7wCs0luZWJ9IpGNWpwI2K6pg3ME/bIb6
xFMf6jKWUUZht1m+40UG2bhsMPPknde2zAq4DjJTl7bJuuhceqp3ylm5h+oUDCl2
twyGhn8gpHrMXVCcEvxwno8aJfNJ18iZu+lIQTT1DVuRrFprGDc9dFW6zVFauv6W
ZG5AUXkCgYEAyoBUPVFaf3E6nDqq26J5x6+DBe6afS7tqNn5E/z2cI54KLiUDwjs
P9axjdVMqqUiNw0jJRi8nUe1psDLguSZo4tFSgsWbsdnZQutQz2fy87jIgZOzZpj
fqud64O/fMm8fpGBz5LQjo27FLRfQcr23KVjWeQgBSMfMk2oe8Mw8+sCgYEAwqWc
abQBpG5TqtgvR3M9W0tAK51SSZF5okKif8KMoDJ67pXX2iTgh4bnUugaYObcG3qV
5VH9xlOuN4td4RfBybzRcKJDN/oinwZhMjQKKjyhpjLInDqnF4ogLPevT6cCq8I+
rHfK6DhtqLUG4BYDD7QWOBZpCTwd3n+7Xk39v/0CgYEAoaM9mpRNgFyJRBswNpDC
VDosg5epiTLkUVtsDiBlNgMCtr5esIGW0n40y9nukGevn/HEk9/i7khHHwvVZm3C
lWCdtjSTe2l/hpCDhKCz5KMHeik+za7mrD2gmFVZi+obo4vR6jZuctt+8U/omUPB
OO5rF12YkYEvbZ+/VMrBUHECgYEArWGpvvpJ0Dc6Ld9d1e5PxCd2pKMBLmj4CNIE
P3uDmhr9J9KvsC/TFMXU/iOjg5eAjrWWGev7+pKFiBKLcDqiMtoPUZ4n9A/KkQ60
u2xhdZgGga2QxqD0P+KYoJWMQo5IschX3XbjdhD1lSaTVj4lQfKvLAzCSSiUjqIG
u40LL90CgYB9t3CISlVJ+l1zy7QPIvAul5VuhwjxBHaotdLuXzvXFJXZH2dQ9j/a
/p2GMUq+VZbKq45x4I5Z3ax462qMRDmpx9yjtrwNavDV47uf34pQrNpuWO7kQfsM
mKMH6Gf6COfSIbLuejdzSOUAmjkFpm+nwBkka1eHdAy4ALn9wNQz3w==
-----END RSA PRIVATE KEY-----
`
func TestBerToDer(t *testing.T) {
_, err := exec.LookPath("openssl")
if err != nil {
t.Skipf("OpenSSL not available skipping test.")
}
msg := new(bytes.Buffer)
ui := &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: msg,
}
// Test - a DER encoded key comes back unchanged.
newKey := string(berToDer([]byte(der_encoded_key), ui))
if newKey != der_encoded_key {
t.Errorf("Trying to convert a DER encoded key should return the same key.")
}
if string(msg.Bytes()) != "" {
t.Errorf("Doing nothing with a DER encoded key result in no messages to the UI .")
}
// Test - a BER encoded key should be converted to DER.
newKey = string(berToDer([]byte(ber_encoded_key), ui))
_, err = ssh.ParsePrivateKey([]byte(newKey))
if err != nil {
t.Errorf("Trying to convert a BER encoded key should return a DER encoded key parsable by Go.")
}
if string(msg.Bytes()) != "Successfully converted BER encoded SSH key to DER encoding.\n" {
t.Errorf("Trying to convert a BER encoded key should tell the UI about the success.")
}
}

View File

@ -1,63 +0,0 @@
package openstack
import (
"context"
"fmt"
"log"
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
flavors_utils "github.com/gophercloud/utils/openstack/compute/v2/flavors"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// StepLoadFlavor gets the FlavorRef from a Flavor. It first assumes
// that the Flavor is a ref and verifies it. Otherwise, it tries to find
// the flavor by name.
type StepLoadFlavor struct {
Flavor string
}
func (s *StepLoadFlavor) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
// We need the v2 compute client
client, err := config.computeV2Client()
if err != nil {
err = fmt.Errorf("Error initializing compute client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Loading flavor: %s", s.Flavor))
log.Printf("[INFO] Loading flavor by ID: %s", s.Flavor)
flavor, err := flavors.Get(client, s.Flavor).Extract()
if err != nil {
log.Printf("[ERROR] Failed to find flavor by ID: %s", err)
geterr := err
log.Printf("[INFO] Loading flavor by name: %s", s.Flavor)
id, err := flavors_utils.IDFromName(client, s.Flavor)
if err != nil {
log.Printf("[ERROR] Failed to find flavor by name: %s", err)
err = fmt.Errorf(
"Unable to find specified flavor by ID or name!\n\n"+
"Error from ID lookup: %s\n\n"+
"Error from name lookup: %s",
geterr,
err)
state.Put("error", err)
return multistep.ActionHalt
}
flavor = &flavors.Flavor{ID: id}
}
ui.Message(fmt.Sprintf("Verified flavor. ID: %s", flavor.ID))
state.Put("flavor_id", flavor.ID)
return multistep.ActionContinue
}
func (s *StepLoadFlavor) Cleanup(state multistep.StateBag) {
}

View File

@ -1,173 +0,0 @@
package openstack
import (
"context"
"fmt"
"io/ioutil"
"log"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type StepRunSourceServer struct {
Name string
SecurityGroups []string
AvailabilityZone string
UserData string
UserDataFile string
ConfigDrive bool
InstanceMetadata map[string]string
UseBlockStorageVolume bool
ForceDelete bool
server *servers.Server
}
func (s *StepRunSourceServer) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
flavor := state.Get("flavor_id").(string)
sourceImage := state.Get("source_image").(string)
networks := state.Get("networks").([]servers.Network)
ui := state.Get("ui").(packersdk.Ui)
// We need the v2 compute client
computeClient, err := config.computeV2Client()
if err != nil {
err = fmt.Errorf("Error initializing compute client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
userData := []byte(s.UserData)
if s.UserDataFile != "" {
userData, err = ioutil.ReadFile(s.UserDataFile)
if err != nil {
err = fmt.Errorf("Error reading user data file: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
}
ui.Say("Launching server...")
serverOpts := servers.CreateOpts{
Name: s.Name,
ImageRef: sourceImage,
FlavorRef: flavor,
SecurityGroups: s.SecurityGroups,
Networks: networks,
AvailabilityZone: s.AvailabilityZone,
UserData: userData,
ConfigDrive: &s.ConfigDrive,
ServiceClient: computeClient,
Metadata: s.InstanceMetadata,
}
var serverOptsExt servers.CreateOptsBuilder
// Create root volume in the Block Storage service if required.
// Add block device mapping v2 to the server create options if required.
if s.UseBlockStorageVolume {
volume := state.Get("volume_id").(string)
blockDeviceMappingV2 := []bootfromvolume.BlockDevice{
{
BootIndex: 0,
DestinationType: bootfromvolume.DestinationVolume,
SourceType: bootfromvolume.SourceVolume,
UUID: volume,
},
}
// ImageRef and block device mapping is an invalid options combination.
serverOpts.ImageRef = ""
serverOptsExt = bootfromvolume.CreateOptsExt{
CreateOptsBuilder: serverOpts,
BlockDevice: blockDeviceMappingV2,
}
} else {
serverOptsExt = serverOpts
}
// Add keypair to the server create options.
keyName := config.Comm.SSHKeyPairName
if keyName != "" {
serverOptsExt = keypairs.CreateOptsExt{
CreateOptsBuilder: serverOptsExt,
KeyName: keyName,
}
}
ui.Say("Launching server...")
s.server, err = servers.Create(computeClient, serverOptsExt).Extract()
if err != nil {
err := fmt.Errorf("Error launching source server: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Message(fmt.Sprintf("Server ID: %s", s.server.ID))
log.Printf("server id: %s", s.server.ID)
ui.Say("Waiting for server to become ready...")
stateChange := StateChangeConf{
Pending: []string{"BUILD"},
Target: []string{"ACTIVE"},
Refresh: ServerStateRefreshFunc(computeClient, s.server),
StepState: state,
}
latestServer, err := WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for server (%s) to become ready: %s", s.server.ID, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.server = latestServer.(*servers.Server)
state.Put("server", s.server)
// 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", s.server.ID)
return multistep.ActionContinue
}
func (s *StepRunSourceServer) Cleanup(state multistep.StateBag) {
if s.server == nil {
return
}
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
// We need the v2 compute client
computeClient, err := config.computeV2Client()
if err != nil {
ui.Error(fmt.Sprintf("Error terminating server, may still be around: %s", err))
return
}
ui.Say(fmt.Sprintf("Terminating the source server: %s ...", s.server.ID))
if config.ForceDelete {
if err := servers.ForceDelete(computeClient, s.server.ID).ExtractErr(); err != nil {
ui.Error(fmt.Sprintf("Error terminating server, may still be around: %s", err))
return
}
} else {
if err := servers.Delete(computeClient, s.server.ID).ExtractErr(); err != nil {
ui.Error(fmt.Sprintf("Error terminating server, may still be around: %s", err))
return
}
}
stateChange := StateChangeConf{
Pending: []string{"ACTIVE", "BUILD", "REBUILD", "SUSPENDED", "SHUTOFF", "STOPPED"},
Refresh: ServerStateRefreshFunc(computeClient, s.server),
Target: []string{"DELETED"},
}
WaitForState(&stateChange)
}

View File

@ -1,201 +0,0 @@
package openstack
import (
"context"
"fmt"
"log"
"time"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/gophercloud/gophercloud/pagination"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type StepSourceImageInfo struct {
SourceImage string
SourceImageName string
ExternalSourceImageURL string
ExternalSourceImageFormat string
ExternalSourceImageProperties map[string]string
SourceImageOpts images.ListOpts
SourceMostRecent bool
SourceProperties map[string]string
}
func PropertiesSatisfied(image *images.Image, props *map[string]string) bool {
for key, value := range *props {
if image.Properties[key] != value {
return false
}
}
return true
}
func (s *StepSourceImageInfo) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
client, err := config.imageV2Client()
if err != nil {
err := fmt.Errorf("error creating image client: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if s.ExternalSourceImageURL != "" {
createOpts := images.CreateOpts{
Name: s.SourceImageName,
ContainerFormat: "bare",
DiskFormat: s.ExternalSourceImageFormat,
Properties: s.ExternalSourceImageProperties,
}
ui.Say("Creating image using external source image with name " + s.SourceImageName)
ui.Say("Using disk format " + s.ExternalSourceImageFormat)
image, err := images.Create(client, createOpts).Extract()
if err != nil {
err := fmt.Errorf("Error creating source image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Created image with ID " + image.ID)
importOpts := imageimport.CreateOpts{
Name: imageimport.WebDownloadMethod,
URI: s.ExternalSourceImageURL,
}
ui.Say("Importing External Source Image from URL " + s.ExternalSourceImageURL)
err = imageimport.Create(client, image.ID, importOpts).ExtractErr()
if err != nil {
err := fmt.Errorf("Error importing source image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for image.Status != images.ImageStatusActive {
ui.Message("Image not Active, retrying in 10 seconds")
time.Sleep(10 * time.Second)
img, err := images.Get(client, image.ID).Extract()
if err != nil {
err := fmt.Errorf("Error querying image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
image = img
}
s.SourceImage = image.ID
}
if s.SourceImage != "" {
state.Put("source_image", s.SourceImage)
return multistep.ActionContinue
}
if s.SourceImageName != "" {
s.SourceImageOpts = images.ListOpts{
Name: s.SourceImageName,
}
}
log.Printf("Using Image Filters %+v", s.SourceImageOpts)
image := &images.Image{}
count := 0
err = images.List(client, s.SourceImageOpts).EachPage(func(page pagination.Page) (bool, error) {
imgs, err := images.ExtractImages(page)
if err != nil {
return false, err
}
for _, img := range imgs {
// Check if all Properties are satisfied
if PropertiesSatisfied(&img, &s.SourceProperties) {
count++
if count == 1 {
// Tentatively return this result.
*image = img
}
// Don't iterate over entries we will never use.
if count > 1 {
break
}
}
}
switch count {
case 0: // Continue looking at next page.
return true, nil
case 1: // Maybe we're done, maybe there is another result in a later page and it is an error.
if s.SourceMostRecent {
return false, nil
}
return true, nil
default: // By now we should know if getting 2+ results is an error or not.
if s.SourceMostRecent {
return false, nil
}
return false, fmt.Errorf(
"Your query returned more than one result. Please try a more specific search, or set most_recent to true. Search filters: %+v properties %+v",
s.SourceImageOpts, s.SourceProperties)
}
})
if err != nil {
err := fmt.Errorf("Error querying image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if image.ID == "" {
err := fmt.Errorf("No image was found matching filters: %+v properties %+v",
s.SourceImageOpts, s.SourceProperties)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Message(fmt.Sprintf("Found Image ID: %s", image.ID))
state.Put("source_image", image.ID)
return multistep.ActionContinue
}
func (s *StepSourceImageInfo) Cleanup(state multistep.StateBag) {
if s.ExternalSourceImageURL != "" {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
client, err := config.imageV2Client()
if err != nil {
err := fmt.Errorf("error creating image client: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return
}
ui.Say(fmt.Sprintf("Deleting temporary external source image: %s ...", s.SourceImageName))
err = images.Delete(client, s.SourceImage).ExtractErr()
if err != nil {
err := fmt.Errorf("error cleaning up external source image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return
}
}
}

View File

@ -1,59 +0,0 @@
package openstack
import (
"context"
"fmt"
"log"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type StepStopServer struct{}
func (s *StepStopServer) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
config := state.Get("config").(*Config)
server := state.Get("server").(*servers.Server)
// We need the v2 compute client
client, err := config.computeV2Client()
if err != nil {
err = fmt.Errorf("Error initializing compute client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Stopping server: %s ...", server.ID))
if err := startstop.Stop(client, server.ID).ExtractErr(); err != nil {
if _, ok := err.(gophercloud.ErrDefault409); ok {
// The server might have already been shut down by Windows Sysprep
log.Printf("[WARN] 409 on stopping an already stopped server, continuing")
return multistep.ActionContinue
} else {
err = fmt.Errorf("Error stopping server: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
}
ui.Message(fmt.Sprintf("Waiting for server to stop: %s ...", server.ID))
stateChange := StateChangeConf{
Pending: []string{"ACTIVE"},
Target: []string{"SHUTOFF", "STOPPED"},
Refresh: ServerStateRefreshFunc(client, server),
StepState: state,
}
if _, err := WaitForState(&stateChange); err != nil {
err := fmt.Errorf("Error waiting for server (%s) to stop: %s", server.ID, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepStopServer) Cleanup(state multistep.StateBag) {}

View File

@ -1,58 +0,0 @@
package openstack
import (
"context"
"fmt"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type stepUpdateImageMinDisk struct{}
func (s *stepUpdateImageMinDisk) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
config := state.Get("config").(*Config)
if config.SkipCreateImage {
ui.Say("Skipping image update mindisk...")
return multistep.ActionContinue
}
imageId := state.Get("image").(string)
if config.ImageMinDisk == 0 {
return multistep.ActionContinue
}
imageClient, err := config.imageV2Client()
if err != nil {
err := fmt.Errorf("Error initializing image service client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Updating image min disk to %d", config.ImageMinDisk))
r := images.Update(
imageClient,
imageId,
images.UpdateOpts{
images.ReplaceImageMinDisk{
NewMinDisk: config.ImageMinDisk,
},
},
)
if _, err := r.Extract(); err != nil {
err = fmt.Errorf("Error updating image min disk: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepUpdateImageMinDisk) Cleanup(multistep.StateBag) {
// No cleanup...
}

View File

@ -1,58 +0,0 @@
package openstack
import (
"context"
"fmt"
"strings"
imageservice "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type stepUpdateImageTags struct{}
func (s *stepUpdateImageTags) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
config := state.Get("config").(*Config)
if config.SkipCreateImage {
ui.Say("Skipping image update tags...")
return multistep.ActionContinue
}
imageId := state.Get("image").(string)
if len(config.ImageTags) == 0 {
return multistep.ActionContinue
}
imageClient, err := config.imageV2Client()
if err != nil {
err = fmt.Errorf("Error initializing image service client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Updating image tags to %s", strings.Join(config.ImageTags, ", ")))
r := imageservice.Update(
imageClient,
imageId,
imageservice.UpdateOpts{
imageservice.ReplaceImageTags{
NewTags: config.ImageTags,
},
},
)
if _, err = r.Extract(); err != nil {
err = fmt.Errorf("Error updating image tags: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepUpdateImageTags) Cleanup(multistep.StateBag) {
// No cleanup...
}

View File

@ -1,57 +0,0 @@
package openstack
import (
"context"
"fmt"
imageservice "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type stepUpdateImageVisibility struct{}
func (s *stepUpdateImageVisibility) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
config := state.Get("config").(*Config)
if config.SkipCreateImage {
ui.Say("Skipping image update visibility...")
return multistep.ActionContinue
}
imageId := state.Get("image").(string)
if config.ImageVisibility == "" {
return multistep.ActionContinue
}
imageClient, err := config.imageV2Client()
if err != nil {
err = fmt.Errorf("Error initializing image service client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Updating image visibility to %s", config.ImageVisibility))
r := imageservice.Update(
imageClient,
imageId,
imageservice.UpdateOpts{
imageservice.UpdateVisibility{
Visibility: config.ImageVisibility,
},
},
)
if _, err = r.Extract(); err != nil {
err = fmt.Errorf("Error updating image visibility: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepUpdateImageVisibility) Cleanup(multistep.StateBag) {
// No cleanup...
}

View File

@ -1,54 +0,0 @@
package openstack
import (
"context"
"fmt"
"time"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type StepWaitForRackConnect struct {
Wait bool
}
func (s *StepWaitForRackConnect) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
if !s.Wait {
return multistep.ActionContinue
}
config := state.Get("config").(*Config)
server := state.Get("server").(*servers.Server)
ui := state.Get("ui").(packersdk.Ui)
// We need the v2 compute client
computeClient, err := config.computeV2Client()
if err != nil {
err = fmt.Errorf("Error initializing compute client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf(
"Waiting for server (%s) to become RackConnect ready...", server.ID))
for {
server, err = servers.Get(computeClient, server.ID).Extract()
if err != nil {
return multistep.ActionHalt
}
if server.Metadata["rackconnect_automation_status"] == "DEPLOYED" {
state.Put("server", server)
break
}
time.Sleep(2 * time.Second)
}
return multistep.ActionContinue
}
func (s *StepWaitForRackConnect) Cleanup(state multistep.StateBag) {
}

View File

@ -1,13 +0,0 @@
package version
import (
"github.com/hashicorp/packer-plugin-sdk/version"
packerVersion "github.com/hashicorp/packer/version"
)
var OpenstackPluginVersion *version.PluginVersion
func init() {
OpenstackPluginVersion = version.InitializePluginVersion(
packerVersion.Version, packerVersion.VersionPrerelease)
}

View File

@ -1,76 +0,0 @@
package openstack
import (
"log"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
)
// WaitForVolume waits for the given volume to become available.
func WaitForVolume(blockStorageClient *gophercloud.ServiceClient, volumeID string) error {
maxNumErrors := 10
numErrors := 0
for {
status, err := GetVolumeStatus(blockStorageClient, volumeID)
if err != nil {
errCode, ok := err.(*gophercloud.ErrUnexpectedResponseCode)
if ok && (errCode.Actual == 500 || errCode.Actual == 404) {
numErrors++
if numErrors >= maxNumErrors {
log.Printf("[ERROR] Maximum number of errors (%d) reached; failing with: %s", numErrors, err)
return err
}
log.Printf("[ERROR] %d error received, will ignore and retry: %s", errCode.Actual, err)
time.Sleep(2 * time.Second)
continue
}
return err
}
if status == "available" {
return nil
}
log.Printf("Waiting for volume creation status: %s", status)
time.Sleep(2 * time.Second)
}
}
// GetVolumeSize returns volume size in gigabytes based on the image min disk
// value if it's not empty.
// Or it calculates needed gigabytes size from the image bytes size.
func GetVolumeSize(imageClient *gophercloud.ServiceClient, imageID string) (int, error) {
sourceImage, err := images.Get(imageClient, imageID).Extract()
if err != nil {
return 0, err
}
if sourceImage.MinDiskGigabytes != 0 {
return sourceImage.MinDiskGigabytes, nil
}
volumeSizeMB := sourceImage.SizeBytes / 1024 / 1024
volumeSizeGB := int(sourceImage.SizeBytes / 1024 / 1024 / 1024)
// Increment gigabytes size if the initial size can't be divided without
// remainder.
if volumeSizeMB%1024 > 0 {
volumeSizeGB++
}
return volumeSizeGB, nil
}
func GetVolumeStatus(blockStorageClient *gophercloud.ServiceClient, volumeID string) (string, error) {
volume, err := volumes.Get(blockStorageClient, volumeID).Extract()
if err != nil {
return "", err
}
return volume.Status, nil
}

View File

@ -1,334 +0,0 @@
---
description: >
The openstack Packer builder is able to create new images for use with
OpenStack. The builder takes a source image, runs any provisioning necessary
on
the image after launching it, then creates a new reusable image. This reusable
image can then be used as the foundation of new servers that are launched
within OpenStack.
page_title: OpenStack - Builders
---
# OpenStack Builder
Type: `openstack`
Artifact BuilderId: `mitchellh.openstack`
The `openstack` Packer builder is able to create new images for use with
[OpenStack](http://www.openstack.org). The builder takes a source image, runs
any provisioning necessary on the image after launching it, then creates a new
reusable image. This reusable image can then be used as the foundation of new
servers that are launched within OpenStack. The builder will create temporary
keypairs that provide temporary access to the server while the image is being
created. This simplifies configuration quite a bit.
The builder does _not_ manage images. Once it creates an image, it is up to you
to use it or delete it.
~> **Note:** To use OpenStack builder with the OpenStack Newton (Oct 2016)
or earlier, we recommend you use Packer v1.1.2 or earlier version.
~> **OpenStack Liberty or later requires OpenSSL!** To use the OpenStack
builder with OpenStack Liberty (Oct 2015) or later you need to have OpenSSL
installed _if you are using temporary key pairs_, i.e. don't use
[`ssh_keypair_name`](#ssh_keypair_name) nor
[`ssh_password`](#ssh_password). All major
OS'es have OpenSSL installed by default except Windows. This have been resolved
in OpenStack Ocata(Feb 2017).
~> **Note:** OpenStack Block Storage volume support is available only for
V3 Block Storage API. It's available in OpenStack since Mitaka release (Apr
2016).
## 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.
### Required:
@include 'builder/openstack/AccessConfig-required.mdx'
@include 'builder/openstack/ImageConfig-required.mdx'
@include 'builder/openstack/RunConfig-required.mdx'
### Optional:
@include 'builder/openstack/AccessConfig-not-required.mdx'
@include 'builder/openstack/ImageConfig-not-required.mdx'
@include 'builder/openstack/RunConfig-not-required.mdx'
### Communicator Configuration
#### Optional:
@include 'packer-plugin-sdk/communicator/Config-not-required.mdx'
@include 'packer-plugin-sdk/communicator/SSH-not-required.mdx'
@include 'packer-plugin-sdk/communicator/SSHTemporaryKeyPair-not-required.mdx'
@include 'packer-plugin-sdk/communicator/SSH-Key-Pair-Name-not-required.mdx'
@include 'packer-plugin-sdk/communicator/SSH-Private-Key-File-not-required.mdx'
@include 'packer-plugin-sdk/communicator/SSH-Agent-Auth-not-required.mdx'
@include 'packer-plugin-sdk/communicator/SSHInterface-not-required.mdx'
## Basic Example: DevStack
Here is a basic example. This is a example to build on DevStack running in a
VM.
<Tabs>
<Tab heading="JSON">
```json
{
"type": "openstack",
"identity_endpoint": "http://<devstack-ip>:5000/v3",
"tenant_name": "admin",
"domain_name": "Default",
"username": "admin",
"password": "<your admin password>",
"region": "RegionOne",
"ssh_username": "root",
"image_name": "Test image",
"source_image": "<image id>",
"flavor": "m1.tiny",
"insecure": "true"
}
```
</Tab>
<Tab heading="HCL2">
```hcl
builders "openstack" {
identity_endpoint = "http://<devstack-ip>:5000/v3"
tenant_name = "admin"
domain_name = "Default"
username = "admin"
password = "<your admin password>"
region = "RegionOne"
ssh_username = "root"
image_name = "Test image"
source_image = "<image id>"
flavor = "m1.tiny"
insecure = "true"
}
```
</Tab>
</Tabs>
## Basic Example: Rackspace public cloud
Here is a basic example. This is a working example to build a Ubuntu 12.04 LTS
(Precise Pangolin) on Rackspace OpenStack cloud offering.
<Tabs>
<Tab heading="JSON">
```json
{
"type": "openstack",
"username": "foo",
"password": "foo",
"region": "DFW",
"ssh_username": "root",
"image_name": "Test image",
"source_image": "23b564c9-c3e6-49f9-bc68-86c7a9ab5018",
"flavor": "2"
}
```
</Tab>
<Tab heading="HCL2">
```hcl
builders "openstack" {
username = "foo"
password = "foo"
region = "DFW"
ssh_username = "root"
image_name = "Test image"
source_image = "23b564c9-c3e6-49f9-bc68-86c7a9ab5018"
flavor = "2"
}
```
</Tab>
</Tabs>
## Basic Example: Private OpenStack cloud
This example builds an Ubuntu 14.04 image on a private OpenStack cloud, powered
by Metacloud.
<Tabs>
<Tab heading="JSON">
```json
{
"type": "openstack",
"ssh_username": "root",
"image_name": "ubuntu1404_packer_test_1",
"source_image": "91d9c168-d1e5-49ca-a775-3bfdbb6c97f1",
"flavor": "2"
}
```
</Tab>
<Tab heading="HCL2">
```hcl
builders "openstack" {
ssh_username = "root"
image_name = "ubuntu1404_packer_test_1"
source_image = "91d9c168-d1e5-49ca-a775-3bfdbb6c97f1"
flavor = "2"
}
```
</Tab>
</Tabs>
In this case, the connection information for connecting to OpenStack doesn't
appear in the template. That is because I source a standard OpenStack script
with environment variables set before I run this. This script is setting
environment variables like:
- `OS_AUTH_URL`
- `OS_TENANT_ID`
- `OS_USERNAME`
- `OS_PASSWORD`
This is slightly different when identity v3 is used:
- `OS_AUTH_URL`
- `OS_USERNAME`
- `OS_PASSWORD`
- `OS_DOMAIN_NAME`
- `OS_TENANT_NAME`
This will authenticate the user on the domain and scope you to the project. A
tenant is the same as a project. It's optional to use names or IDs in v3. This
means you can use `OS_USERNAME` or `OS_USERID`, `OS_TENANT_ID` or
`OS_TENANT_NAME` and `OS_DOMAIN_ID` or `OS_DOMAIN_NAME`.
The above example would be equivalent to an RC file looking like this :
```shell
export OS_AUTH_URL="https://identity.myprovider/v3"
export OS_USERNAME="myuser"
export OS_PASSWORD="password"
export OS_USER_DOMAIN_NAME="mydomain"
export OS_PROJECT_DOMAIN_NAME="mydomain"
```
## Basic Example: Instance with Block Storage root volume
A basic example of Instance with a remote root Block Storage service volume.
This is a working example to build an image on private OpenStack cloud powered
by Selectel VPC.
<Tabs>
<Tab heading="JSON">
```json
{
"type": "openstack",
"identity_endpoint": "https://api.selvpc.com/identity/v3",
"tenant_id": "2e90c5c04c7b4c509be78723e2b55b77",
"username": "foo",
"password": "foo",
"region": "ru-3",
"ssh_username": "root",
"image_name": "Test image",
"source_image": "5f58ea7e-6264-4939-9d0f-0c23072b1132",
"networks": "9aab504e-bedf-48af-9256-682a7fa3dabb",
"flavor": "1001",
"availability_zone": "ru-3a",
"use_blockstorage_volume": true,
"volume_type": "fast.ru-3a"
}
```
</Tab>
<Tab heading="HCL2">
```hcl
builders "openstack" {
identity_endpoint = "https://api.selvpc.com/identity/v3"
tenant_id = "2e90c5c04c7b4c509be78723e2b55b77"
username = "foo"
password = "foo"
region = "ru-3"
ssh_username = "root"
image_name = "Test image"
source_image = "5f58ea7e-6264-4939-9d0f-0c23072b1132"
networks = "9aab504e-bedf-48af-9256-682a7fa3dabb"
flavor = "1001"
availability_zone = "ru-3a"
use_blockstorage_volume = true
volume_type = "fast.ru-3a"
}
```
</Tab>
</Tabs>
## Notes on OpenStack Authorization
The simplest way to get all settings for authorization against OpenStack is to
go into the OpenStack Dashboard (Horizon) select your _Project_ and navigate
_Project, Access & Security_, select _API Access_ and _Download OpenStack RC
File v3_. Source the file, and select your wanted region by setting environment
variable `OS_REGION_NAME` or `OS_REGION_ID` and
`export OS_TENANT_NAME=$OS_PROJECT_NAME` or
`export OS_TENANT_ID=$OS_PROJECT_ID`.
~> `OS_TENANT_NAME` or `OS_TENANT_ID` must be used even with Identity v3,
`OS_PROJECT_NAME` and `OS_PROJECT_ID` has no effect in Packer.
To troubleshoot authorization issues test you environment variables with the
OpenStack cli. It can be installed with
$ pip install --user python-openstackclient
### Authorize Using Tokens
To authorize with a access token only `identity_endpoint` and `token` is
needed, and possibly `tenant_name` or `tenant_id` depending on your token type.
Or use the following environment variables:
- `OS_AUTH_URL`
- `OS_TOKEN`
- One of `OS_TENANT_NAME` or `OS_TENANT_ID`
### Authorize Using Application Credential
To authorize with an application credential, only `identity_endpoint`,
`application_credential_id`, and `application_credential_secret` are needed.
Or use the following environment variables:
- `OS_AUTH_URL`
- `OS_APPLICATION_CREDENTIAL_ID`
- `OS_APPLICATION_CREDENTIAL_SECRET`