packer-cn/builder/openstack/access_config.go

279 lines
9.2 KiB
Go

//go:generate struct-markdown
package openstack
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"os"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/utils/openstack/clientconfig"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/packer/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"`
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
// 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) 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
}