482 lines
13 KiB
Go
482 lines
13 KiB
Go
package openstack
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
|
|
"github.com/gophercloud/gophercloud"
|
|
"github.com/gophercloud/gophercloud/openstack"
|
|
"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth"
|
|
"github.com/gophercloud/utils/openstack/clientconfig"
|
|
"github.com/hashicorp/terraform/helper/pathorcontents"
|
|
"github.com/hashicorp/terraform/terraform"
|
|
)
|
|
|
|
type Config struct {
|
|
CACertFile string
|
|
ClientCertFile string
|
|
ClientKeyFile string
|
|
Cloud string
|
|
DefaultDomain string
|
|
DomainID string
|
|
DomainName string
|
|
EndpointOverrides map[string]interface{}
|
|
EndpointType string
|
|
IdentityEndpoint string
|
|
Insecure *bool
|
|
Password string
|
|
ProjectDomainName string
|
|
ProjectDomainID string
|
|
Region string
|
|
Swauth bool
|
|
TenantID string
|
|
TenantName string
|
|
Token string
|
|
UserDomainName string
|
|
UserDomainID string
|
|
Username string
|
|
UserID string
|
|
ApplicationCredentialID string
|
|
ApplicationCredentialName string
|
|
ApplicationCredentialSecret string
|
|
useOctavia bool
|
|
MaxRetries int
|
|
|
|
OsClient *gophercloud.ProviderClient
|
|
}
|
|
|
|
// LoadAndValidate performs the authentication and initial configuration
|
|
// of an OpenStack Provider Client. This sets up the HTTP client and
|
|
// authenticates to an OpenStack cloud.
|
|
//
|
|
// Individual Service Clients are created later in this file.
|
|
func (c *Config) LoadAndValidate() error {
|
|
// Make sure at least one of auth_url or cloud was specified.
|
|
if c.IdentityEndpoint == "" && c.Cloud == "" {
|
|
return fmt.Errorf("One of 'auth_url' or 'cloud' must be specified")
|
|
}
|
|
|
|
validEndpoint := false
|
|
validEndpoints := []string{
|
|
"internal", "internalURL",
|
|
"admin", "adminURL",
|
|
"public", "publicURL",
|
|
"",
|
|
}
|
|
|
|
for _, endpoint := range validEndpoints {
|
|
if c.EndpointType == endpoint {
|
|
validEndpoint = true
|
|
}
|
|
}
|
|
|
|
if !validEndpoint {
|
|
return fmt.Errorf("Invalid endpoint type provided")
|
|
}
|
|
|
|
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 err
|
|
}
|
|
|
|
if c.Region == "" && cloud.RegionName != "" {
|
|
c.Region = cloud.RegionName
|
|
}
|
|
|
|
if c.CACertFile == "" && cloud.CACertFile != "" {
|
|
c.CACertFile = cloud.CACertFile
|
|
}
|
|
|
|
if c.ClientCertFile == "" && cloud.ClientCertFile != "" {
|
|
c.ClientCertFile = cloud.ClientCertFile
|
|
}
|
|
|
|
if c.ClientKeyFile == "" && cloud.ClientKeyFile != "" {
|
|
c.ClientKeyFile = cloud.ClientKeyFile
|
|
}
|
|
|
|
if c.Insecure == nil && cloud.Verify != nil {
|
|
v := (!*cloud.Verify)
|
|
c.Insecure = &v
|
|
}
|
|
} else {
|
|
authInfo := &clientconfig.AuthInfo{
|
|
AuthURL: c.IdentityEndpoint,
|
|
DefaultDomain: c.DefaultDomain,
|
|
DomainID: c.DomainID,
|
|
DomainName: c.DomainName,
|
|
Password: c.Password,
|
|
ProjectDomainID: c.ProjectDomainID,
|
|
ProjectDomainName: c.ProjectDomainName,
|
|
ProjectID: c.TenantID,
|
|
ProjectName: c.TenantName,
|
|
Token: c.Token,
|
|
UserDomainID: c.UserDomainID,
|
|
UserDomainName: c.UserDomainName,
|
|
Username: c.Username,
|
|
UserID: c.UserID,
|
|
ApplicationCredentialID: c.ApplicationCredentialID,
|
|
ApplicationCredentialName: c.ApplicationCredentialName,
|
|
ApplicationCredentialSecret: c.ApplicationCredentialSecret,
|
|
}
|
|
clientOpts.AuthInfo = authInfo
|
|
}
|
|
|
|
ao, err := clientconfig.AuthOptions(clientOpts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
client, err := openstack.NewClient(ao.IdentityEndpoint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set UserAgent
|
|
client.UserAgent.Prepend(terraform.UserAgentString())
|
|
|
|
config := &tls.Config{}
|
|
if c.CACertFile != "" {
|
|
caCert, _, err := pathorcontents.Read(c.CACertFile)
|
|
if err != nil {
|
|
return fmt.Errorf("Error reading CA Cert: %s", err)
|
|
}
|
|
|
|
caCertPool := x509.NewCertPool()
|
|
caCertPool.AppendCertsFromPEM([]byte(caCert))
|
|
config.RootCAs = caCertPool
|
|
}
|
|
|
|
if c.Insecure == nil {
|
|
config.InsecureSkipVerify = false
|
|
} else {
|
|
config.InsecureSkipVerify = *c.Insecure
|
|
}
|
|
|
|
if c.ClientCertFile != "" && c.ClientKeyFile != "" {
|
|
clientCert, _, err := pathorcontents.Read(c.ClientCertFile)
|
|
if err != nil {
|
|
return fmt.Errorf("Error reading Client Cert: %s", err)
|
|
}
|
|
clientKey, _, err := pathorcontents.Read(c.ClientKeyFile)
|
|
if err != nil {
|
|
return fmt.Errorf("Error reading Client Key: %s", err)
|
|
}
|
|
|
|
cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.Certificates = []tls.Certificate{cert}
|
|
config.BuildNameToCertificate()
|
|
}
|
|
|
|
// if OS_DEBUG is set, log the requests and responses
|
|
var osDebug bool
|
|
if os.Getenv("OS_DEBUG") != "" {
|
|
osDebug = true
|
|
}
|
|
|
|
transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config}
|
|
client.HTTPClient = http.Client{
|
|
Transport: &LogRoundTripper{
|
|
Rt: transport,
|
|
OsDebug: osDebug,
|
|
MaxRetries: c.MaxRetries,
|
|
},
|
|
}
|
|
|
|
// If using Swift Authentication, there's no need to validate authentication normally.
|
|
if !c.Swauth {
|
|
err = openstack.Authenticate(client, *ao)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if c.MaxRetries < 0 {
|
|
return fmt.Errorf("max_retries should be a positive value")
|
|
}
|
|
|
|
c.OsClient = client
|
|
|
|
return nil
|
|
}
|
|
|
|
// determineEndpoint is a helper method to determine if the user wants to
|
|
// override an endpoint returned from the catalog.
|
|
func (c *Config) determineEndpoint(client *gophercloud.ServiceClient, service string) *gophercloud.ServiceClient {
|
|
finalEndpoint := client.ResourceBaseURL()
|
|
|
|
if v, ok := c.EndpointOverrides[service]; ok {
|
|
if endpoint, ok := v.(string); ok && endpoint != "" {
|
|
finalEndpoint = endpoint
|
|
client.Endpoint = endpoint
|
|
client.ResourceBase = ""
|
|
}
|
|
}
|
|
|
|
log.Printf("[DEBUG] OpenStack Endpoint for %s: %s", service, finalEndpoint)
|
|
|
|
return client
|
|
}
|
|
|
|
// determineRegion is a helper method to determine the region based on
|
|
// the user's settings.
|
|
func (c *Config) determineRegion(region string) string {
|
|
// If a resource-level region was not specified, and a provider-level region was set,
|
|
// use the provider-level region.
|
|
if region == "" && c.Region != "" {
|
|
region = c.Region
|
|
}
|
|
|
|
log.Printf("[DEBUG] OpenStack Region is: %s", region)
|
|
return region
|
|
}
|
|
|
|
// getEndpointType is a helper method to determine the endpoint type
|
|
// requested by the user.
|
|
func (c *Config) 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
|
|
}
|
|
|
|
// The following methods assist with the creation of individual Service Clients
|
|
// which interact with the various OpenStack services.
|
|
|
|
func (c *Config) blockStorageV1Client(region string) (*gophercloud.ServiceClient, error) {
|
|
client, err := openstack.NewBlockStorageV1(c.OsClient, gophercloud.EndpointOpts{
|
|
Region: c.determineRegion(region),
|
|
Availability: c.getEndpointType(),
|
|
})
|
|
|
|
if err != nil {
|
|
return client, err
|
|
}
|
|
|
|
// Check if an endpoint override was specified for the volume service.
|
|
client = c.determineEndpoint(client, "volume")
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (c *Config) blockStorageV2Client(region string) (*gophercloud.ServiceClient, error) {
|
|
client, err := openstack.NewBlockStorageV2(c.OsClient, gophercloud.EndpointOpts{
|
|
Region: c.determineRegion(region),
|
|
Availability: c.getEndpointType(),
|
|
})
|
|
|
|
if err != nil {
|
|
return client, err
|
|
}
|
|
|
|
// Check if an endpoint override was specified for the volumev2 service.
|
|
client = c.determineEndpoint(client, "volumev2")
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (c *Config) blockStorageV3Client(region string) (*gophercloud.ServiceClient, error) {
|
|
client, err := openstack.NewBlockStorageV3(c.OsClient, gophercloud.EndpointOpts{
|
|
Region: c.determineRegion(region),
|
|
Availability: c.getEndpointType(),
|
|
})
|
|
|
|
if err != nil {
|
|
return client, err
|
|
}
|
|
|
|
// Check if an endpoint override was specified for the volumev3 service.
|
|
client = c.determineEndpoint(client, "volumev3")
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (c *Config) computeV2Client(region string) (*gophercloud.ServiceClient, error) {
|
|
client, err := openstack.NewComputeV2(c.OsClient, gophercloud.EndpointOpts{
|
|
Region: c.determineRegion(region),
|
|
Availability: c.getEndpointType(),
|
|
})
|
|
|
|
if err != nil {
|
|
return client, err
|
|
}
|
|
|
|
// Check if an endpoint override was specified for the compute service.
|
|
client = c.determineEndpoint(client, "compute")
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (c *Config) dnsV2Client(region string) (*gophercloud.ServiceClient, error) {
|
|
client, err := openstack.NewDNSV2(c.OsClient, gophercloud.EndpointOpts{
|
|
Region: c.determineRegion(region),
|
|
Availability: c.getEndpointType(),
|
|
})
|
|
|
|
if err != nil {
|
|
return client, err
|
|
}
|
|
|
|
// Check if an endpoint override was specified for the dns service.
|
|
client = c.determineEndpoint(client, "dns")
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (c *Config) identityV3Client(region string) (*gophercloud.ServiceClient, error) {
|
|
client, err := openstack.NewIdentityV3(c.OsClient, gophercloud.EndpointOpts{
|
|
Region: c.determineRegion(region),
|
|
Availability: c.getEndpointType(),
|
|
})
|
|
|
|
if err != nil {
|
|
return client, err
|
|
}
|
|
|
|
// Check if an endpoint override was specified for the identity service.
|
|
client = c.determineEndpoint(client, "identity")
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (c *Config) imageV2Client(region string) (*gophercloud.ServiceClient, error) {
|
|
client, err := openstack.NewImageServiceV2(c.OsClient, gophercloud.EndpointOpts{
|
|
Region: c.determineRegion(region),
|
|
Availability: c.getEndpointType(),
|
|
})
|
|
|
|
if err != nil {
|
|
return client, err
|
|
}
|
|
|
|
// Check if an endpoint override was specified for the image service.
|
|
client = c.determineEndpoint(client, "image")
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (c *Config) networkingV2Client(region string) (*gophercloud.ServiceClient, error) {
|
|
client, err := openstack.NewNetworkV2(c.OsClient, gophercloud.EndpointOpts{
|
|
Region: c.determineRegion(region),
|
|
Availability: c.getEndpointType(),
|
|
})
|
|
|
|
if err != nil {
|
|
return client, err
|
|
}
|
|
|
|
// Check if an endpoint override was specified for the network service.
|
|
client = c.determineEndpoint(client, "network")
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (c *Config) objectStorageV1Client(region string) (*gophercloud.ServiceClient, error) {
|
|
var client *gophercloud.ServiceClient
|
|
var err error
|
|
|
|
// If Swift Authentication is being used, return a swauth client.
|
|
// Otherwise, use a Keystone-based client.
|
|
if c.Swauth {
|
|
client, err = swauth.NewObjectStorageV1(c.OsClient, swauth.AuthOpts{
|
|
User: c.Username,
|
|
Key: c.Password,
|
|
})
|
|
} else {
|
|
client, err = openstack.NewObjectStorageV1(c.OsClient, gophercloud.EndpointOpts{
|
|
Region: c.determineRegion(region),
|
|
Availability: c.getEndpointType(),
|
|
})
|
|
}
|
|
|
|
if err != nil {
|
|
return client, err
|
|
}
|
|
|
|
// Check if an endpoint override was specified for the object-store service.
|
|
client = c.determineEndpoint(client, "object-store")
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (c *Config) loadBalancerV2Client(region string) (*gophercloud.ServiceClient, error) {
|
|
client, err := openstack.NewLoadBalancerV2(c.OsClient, gophercloud.EndpointOpts{
|
|
Region: c.determineRegion(region),
|
|
Availability: c.getEndpointType(),
|
|
})
|
|
|
|
if err != nil {
|
|
return client, err
|
|
}
|
|
|
|
// Check if an endpoint override was specified for the octavia service.
|
|
client = c.determineEndpoint(client, "octavia")
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (c *Config) databaseV1Client(region string) (*gophercloud.ServiceClient, error) {
|
|
client, err := openstack.NewDBV1(c.OsClient, gophercloud.EndpointOpts{
|
|
Region: c.determineRegion(region),
|
|
Availability: c.getEndpointType(),
|
|
})
|
|
|
|
if err != nil {
|
|
return client, err
|
|
}
|
|
|
|
// Check if an endpoint override was specified for the database service.
|
|
client = c.determineEndpoint(client, "database")
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (c *Config) containerInfraV1Client(region string) (*gophercloud.ServiceClient, error) {
|
|
client, err := openstack.NewContainerInfraV1(c.OsClient, gophercloud.EndpointOpts{
|
|
Region: c.determineRegion(region),
|
|
Availability: c.getEndpointType(),
|
|
})
|
|
|
|
if err != nil {
|
|
return client, err
|
|
}
|
|
|
|
// Check if an endpoint override was specified for the container-infra service.
|
|
client = c.determineEndpoint(client, "container-infra")
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (c *Config) sharedfilesystemV2Client(region string) (*gophercloud.ServiceClient, error) {
|
|
client, err := openstack.NewSharedFileSystemV2(c.OsClient, gophercloud.EndpointOpts{
|
|
Region: c.determineRegion(region),
|
|
Availability: c.getEndpointType(),
|
|
})
|
|
|
|
if err != nil {
|
|
return client, err
|
|
}
|
|
|
|
// Check if an endpoint override was specified for the sharev2 service.
|
|
client = c.determineEndpoint(client, "sharev2")
|
|
|
|
return client, nil
|
|
}
|