delete openstack files
This commit is contained in:
parent
6fa213235f
commit
88192f1fdd
|
@ -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))
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 ""
|
||||
}
|
|
@ -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...
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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.")
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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...
|
||||
}
|
|
@ -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...
|
||||
}
|
|
@ -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...
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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`
|
Loading…
Reference in New Issue