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