From c2f73899a88d4198a8e3a3beb5fd27e62714af42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?p=C3=BDrus?= Date: Fri, 11 Jul 2025 14:17:28 +0200 Subject: [PATCH] Move terraform package into the terraform-provider-openstack/utils repo --- terraform/auth/config.go | 462 ---------------------------- terraform/auth/doc.go | 11 - terraform/auth/util.go | 30 -- terraform/hashcode/hashcode.go | 35 --- terraform/hashcode/hashcode_test.go | 37 --- terraform/mutexkv/mutexkv.go | 51 --- terraform/mutexkv/mutexkv_test.go | 79 ----- 7 files changed, 705 deletions(-) delete mode 100644 terraform/auth/config.go delete mode 100644 terraform/auth/doc.go delete mode 100644 terraform/auth/util.go delete mode 100644 terraform/hashcode/hashcode.go delete mode 100644 terraform/hashcode/hashcode_test.go delete mode 100644 terraform/mutexkv/mutexkv.go delete mode 100644 terraform/mutexkv/mutexkv_test.go diff --git a/terraform/auth/config.go b/terraform/auth/config.go deleted file mode 100644 index 622b9c51..00000000 --- a/terraform/auth/config.go +++ /dev/null @@ -1,462 +0,0 @@ -package auth - -import ( - "context" - "fmt" - "log" - "net/http" - "net/url" - "os" - - "github.com/gophercloud/gophercloud/v2" - "github.com/gophercloud/gophercloud/v2/openstack" - "github.com/gophercloud/gophercloud/v2/openstack/objectstorage/v1/swauth" - osClient "github.com/gophercloud/utils/v2/client" - "github.com/gophercloud/utils/v2/internal" - "github.com/gophercloud/utils/v2/openstack/clientconfig" - "github.com/gophercloud/utils/v2/terraform/mutexkv" -) - -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 - DisableNoCacheHeader bool - Context context.Context - - DelayedAuth bool - AllowReauth bool - OsClient *gophercloud.ProviderClient - AuthOpts *gophercloud.AuthOptions - authenticated bool - authFailed error - swClient *gophercloud.ServiceClient - swAuthFailed error - - TerraformVersion string - SDKVersion string - EnableLogger bool - - *mutexkv.MutexKV -} - -// 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(ctx context.Context) 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") - } - - if c.MaxRetries < 0 { - return fmt.Errorf("max_retries should be a positive value") - } - - clientOpts := new(clientconfig.ClientOpts) - - // If a cloud entry was given, base AuthOptions on a clouds.yaml file. - if c.Cloud != "" { - clientOpts.Cloud = c.Cloud - - // Passing region allows GetCloudFromYAML to apply per-region overrides - clientOpts.RegionName = c.Region - - 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, - } - - // Define System Scope if enabled - if c.AuthOpts.Scope.System { - authInfo.SystemScope = "true" - } - - clientOpts.AuthInfo = authInfo - } - - ao, err := clientconfig.AuthOptions(clientOpts) - if err != nil { - return err - } - - log.Printf("[DEBUG] OpenStack allowReauth: %t", c.AllowReauth) - ao.AllowReauth = c.AllowReauth - - client, err := openstack.NewClient(ao.IdentityEndpoint) - if err != nil { - return err - } - - // Set UserAgent - client.UserAgent.Prepend(terraformUserAgent(c.TerraformVersion, c.SDKVersion)) - - config, err := internal.PrepareTLSConfig(c.CACertFile, c.ClientCertFile, c.ClientKeyFile, c.Insecure) - if err != nil { - return err - } - - c.EnableLogger = enableLogging(c.EnableLogger) - var logger osClient.Logger - if c.EnableLogger { - logger = &osClient.DefaultLogger{} - } - - transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config} - client.HTTPClient = http.Client{ - Transport: &osClient.RoundTripper{ - Rt: transport, - MaxRetries: c.MaxRetries, - Logger: logger, - }, - } - - if !c.DisableNoCacheHeader { - extraHeaders := map[string][]string{ - "Cache-Control": {"no-cache"}, - } - client.HTTPClient.Transport.(*osClient.RoundTripper).SetHeaders(extraHeaders) - } - - if c.MaxRetries > 0 { - client.MaxBackoffRetries = uint(c.MaxRetries) - client.RetryBackoffFunc = osClient.RetryBackoffFunc(logger) - } - - if !c.DelayedAuth && !c.Swauth { - err = openstack.Authenticate(ctx, client, *ao) - if err != nil { - return err - } - } - - c.AuthOpts = ao - c.OsClient = client - - return nil -} - -func (c *Config) Authenticate(ctx context.Context) error { - if !c.DelayedAuth { - return nil - } - - c.MutexKV.Lock("auth") - defer c.MutexKV.Unlock("auth") - - if c.authFailed != nil { - return c.authFailed - } - - if !c.authenticated { - if err := openstack.Authenticate(ctx, c.OsClient, *c.AuthOpts); err != nil { - c.authFailed = err - return err - } - c.authenticated = true - } - - 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, eo gophercloud.EndpointOpts, service string) (*gophercloud.ServiceClient, error) { - v, ok := c.EndpointOverrides[service] - if !ok { - return client, nil - } - val, ok := v.(string) - if !ok || val == "" { - return client, nil - } - - // overriden endpoint is a URL - if u, err := url.Parse(val); err == nil && u.Scheme != "" && u.Host != "" { - eo.ApplyDefaults(service) - client.ProviderClient = c.OsClient - client.Endpoint = val - client.ResourceBase = "" - client.Type = service - log.Printf("[DEBUG] OpenStack Endpoint for %s: %s", service, val) - return client, nil - } - - // overriden endpoint is a new service type - eo.ApplyDefaults(val) - url, err := c.OsClient.EndpointLocator(eo) - if err != nil { - log.Printf("[DEBUG] Cannot set a new OpenStack Endpoint %s alias: %v", val, err) - return client, err - } - client.ProviderClient = c.OsClient - client.Endpoint = url - client.Type = val - - log.Printf("[DEBUG] OpenStack Endpoint for %s alias: %s", val, url) - return client, nil -} - -// 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 -} - -// The following methods assist with the creation of individual Service Clients -// which interact with the various OpenStack services. - -type commonCommonServiceClientInitFunc func(*gophercloud.ProviderClient, gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) - -func (c *Config) CommonServiceClientInit(ctx context.Context, newClient commonCommonServiceClientInitFunc, region, service string) (*gophercloud.ServiceClient, error) { - if err := c.Authenticate(ctx); err != nil { - return nil, err - } - - eo := gophercloud.EndpointOpts{ - Region: c.DetermineRegion(region), - Availability: clientconfig.GetEndpointType(c.EndpointType), - } - client, err := newClient(c.OsClient, eo) - if err, ok := err.(*gophercloud.ErrEndpointNotFound); ok && client != nil { - client, e := c.DetermineEndpoint(client, eo, service) - if e != nil { - return client, e - } - // if the endpoint is still not found, return the original error - if client.ProviderClient == nil { - return client, err - } - } - if err != nil { - return client, err - } - - return c.DetermineEndpoint(client, eo, service) -} - -func (c *Config) BlockStorageV1Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - return c.CommonServiceClientInit(ctx, openstack.NewBlockStorageV1, region, "volume") -} - -func (c *Config) BlockStorageV2Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - return c.CommonServiceClientInit(ctx, openstack.NewBlockStorageV2, region, "volumev2") -} - -func (c *Config) BlockStorageV3Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - return c.CommonServiceClientInit(ctx, openstack.NewBlockStorageV3, region, "volumev3") -} - -func (c *Config) ComputeV2Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - return c.CommonServiceClientInit(ctx, openstack.NewComputeV2, region, "compute") -} - -func (c *Config) DNSV2Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - return c.CommonServiceClientInit(ctx, openstack.NewDNSV2, region, "dns") -} - -func (c *Config) IdentityV3Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - return c.CommonServiceClientInit(ctx, openstack.NewIdentityV3, region, "identity") -} - -func (c *Config) ImageV2Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - return c.CommonServiceClientInit(ctx, openstack.NewImageV2, region, "image") -} - -func (c *Config) MessagingV2Client(ctx context.Context, clientID string, region string) (*gophercloud.ServiceClient, error) { - if err := c.Authenticate(ctx); err != nil { - return nil, err - } - - eo := gophercloud.EndpointOpts{ - Region: c.DetermineRegion(region), - Availability: clientconfig.GetEndpointType(c.EndpointType), - } - client, err := openstack.NewMessagingV2(c.OsClient, clientID, eo) - if err, ok := err.(*gophercloud.ErrEndpointNotFound); ok && client != nil { - client, e := c.DetermineEndpoint(client, eo, "messaging") - if e != nil { - return client, e - } - // if the endpoint is still not found, return the original error - if client.ProviderClient == nil { - return client, err - } - } - if err != nil { - return client, err - } - - return c.DetermineEndpoint(client, eo, "messaging") -} - -func (c *Config) NetworkingV2Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - return c.CommonServiceClientInit(ctx, openstack.NewNetworkV2, region, "network") -} - -func (c *Config) ObjectStorageV1Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - if !c.Swauth { - return c.CommonServiceClientInit(ctx, openstack.NewObjectStorageV1, region, "object-store") - } - - // If Swift Authentication is being used, return a swauth client. - if !c.DelayedAuth { - return swauth.NewObjectStorageV1(ctx, c.OsClient, swauth.AuthOpts{ - User: c.Username, - Key: c.Password, - }) - } - - c.MutexKV.Lock("SwAuth") - defer c.MutexKV.Unlock("SwAuth") - - if c.swAuthFailed != nil { - return nil, c.swAuthFailed - } - - if c.swClient == nil { - c.swClient, c.swAuthFailed = swauth.NewObjectStorageV1(ctx, c.OsClient, swauth.AuthOpts{ - User: c.Username, - Key: c.Password, - }) - return c.swClient, c.swAuthFailed - } - - return c.swClient, nil -} - -func (c *Config) OrchestrationV1Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - return c.CommonServiceClientInit(ctx, openstack.NewOrchestrationV1, region, "orchestration") -} - -func (c *Config) LoadBalancerV2Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - return c.CommonServiceClientInit(ctx, openstack.NewLoadBalancerV2, region, "octavia") -} - -func (c *Config) DatabaseV1Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - return c.CommonServiceClientInit(ctx, openstack.NewDBV1, region, "database") -} - -func (c *Config) ContainerInfraV1Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - return c.CommonServiceClientInit(ctx, openstack.NewContainerInfraV1, region, "container-infra") -} - -func (c *Config) SharedfilesystemV2Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - return c.CommonServiceClientInit(ctx, openstack.NewSharedFileSystemV2, region, "sharev2") -} - -func (c *Config) KeyManagerV1Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - return c.CommonServiceClientInit(ctx, openstack.NewKeyManagerV1, region, "key-manager") -} - -func (c *Config) WorkflowV2Client(ctx context.Context, region string) (*gophercloud.ServiceClient, error) { - return c.CommonServiceClientInit(ctx, openstack.NewWorkflowV2, region, "workflowv2") -} - -// A wrapper to determine if logging in gophercloud should be enabled, with a fallback -// to the OS_DEBUG environment variable when no explicit configuration is passed. -func enableLogging(enable bool) bool { - if enable { - return true - } - - // if OS_DEBUG is set, log the requests and responses - if os.Getenv("OS_DEBUG") != "" { - return true - } - - return false -} diff --git a/terraform/auth/doc.go b/terraform/auth/doc.go deleted file mode 100644 index 057d6081..00000000 --- a/terraform/auth/doc.go +++ /dev/null @@ -1,11 +0,0 @@ -/* -Package auth provides common functionality for Terraform and Terraform -plugins to authenticate with OpenStack. - -It includes a wide array of features, such as flexible authentication, -debugging, client creation, endpoint customization and more. - -While this package is specific to Terraform, it can be used for any kind -of Terraform feature: core, backend, and plugins. -*/ -package auth diff --git a/terraform/auth/util.go b/terraform/auth/util.go deleted file mode 100644 index 2269fb4d..00000000 --- a/terraform/auth/util.go +++ /dev/null @@ -1,30 +0,0 @@ -package auth - -import ( - "fmt" - "log" - "os" - "strings" -) - -// This is copied directly from Terraform in order to remove a single legacy -// vendor dependency. - -const uaEnvVar = "TF_APPEND_USER_AGENT" - -func terraformUserAgent(version, sdkVersion string) string { - ua := fmt.Sprintf("HashiCorp Terraform/%s (+https://www.terraform.io)", version) - if sdkVersion != "" { - ua += " " + fmt.Sprintf("Terraform Plugin SDK/%s", sdkVersion) - } - - if add := os.Getenv(uaEnvVar); add != "" { - add = strings.TrimSpace(add) - if len(add) > 0 { - ua += " " + add - log.Printf("[DEBUG] Using modified User-Agent: %s", ua) - } - } - - return ua -} diff --git a/terraform/hashcode/hashcode.go b/terraform/hashcode/hashcode.go deleted file mode 100644 index 6ccc5231..00000000 --- a/terraform/hashcode/hashcode.go +++ /dev/null @@ -1,35 +0,0 @@ -package hashcode - -import ( - "bytes" - "fmt" - "hash/crc32" -) - -// String hashes a string to a unique hashcode. -// -// crc32 returns a uint32, but for our use we need -// and non negative integer. Here we cast to an integer -// and invert it if the result is negative. -func String(s string) int { - v := int(crc32.ChecksumIEEE([]byte(s))) - if v >= 0 { - return v - } - if -v >= 0 { - return -v - } - // v == MinInt - return 0 -} - -// Strings hashes a list of strings to a unique hashcode. -func Strings(strings []string) string { - var buf bytes.Buffer - - for _, s := range strings { - buf.WriteString(fmt.Sprintf("%s-", s)) - } - - return fmt.Sprintf("%d", String(buf.String())) -} diff --git a/terraform/hashcode/hashcode_test.go b/terraform/hashcode/hashcode_test.go deleted file mode 100644 index 4b90431b..00000000 --- a/terraform/hashcode/hashcode_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package hashcode - -import ( - "testing" -) - -func TestString(t *testing.T) { - v := "hello, world" - expected := String(v) - for i := 0; i < 100; i++ { - actual := String(v) - if actual != expected { - t.Fatalf("bad: %#v\n\t%#v", actual, expected) - } - } -} - -func TestStrings(t *testing.T) { - v := []string{"hello", ",", "world"} - expected := Strings(v) - for i := 0; i < 100; i++ { - actual := Strings(v) - if actual != expected { - t.Fatalf("bad: %#v\n\t%#v", actual, expected) - } - } -} - -func TestString_positiveIndex(t *testing.T) { - // "2338615298" hashes to uint32(2147483648) which is math.MinInt32 - ips := []string{"192.168.1.3", "192.168.1.5", "2338615298"} - for _, ip := range ips { - if index := String(ip); index < 0 { - t.Fatalf("Bad Index %#v for ip %s", index, ip) - } - } -} diff --git a/terraform/mutexkv/mutexkv.go b/terraform/mutexkv/mutexkv.go deleted file mode 100644 index a623e12c..00000000 --- a/terraform/mutexkv/mutexkv.go +++ /dev/null @@ -1,51 +0,0 @@ -package mutexkv - -import ( - "log" - "sync" -) - -// MutexKV is a simple key/value store for arbitrary mutexes. It can be used to -// serialize changes across arbitrary collaborators that share knowledge of the -// keys they must serialize on. -// -// The initial use case is to let aws_security_group_rule resources serialize -// their access to individual security groups based on SG ID. -type MutexKV struct { - lock sync.Mutex - store map[string]*sync.Mutex -} - -// Locks the mutex for the given key. Caller is responsible for calling Unlock -// for the same key. -func (m *MutexKV) Lock(key string) { - log.Printf("[DEBUG] Locking %q", key) - m.get(key).Lock() - log.Printf("[DEBUG] Locked %q", key) -} - -// Unlock the mutex for the given key. Caller must have called Lock for the same key first. -func (m *MutexKV) Unlock(key string) { - log.Printf("[DEBUG] Unlocking %q", key) - m.get(key).Unlock() - log.Printf("[DEBUG] Unlocked %q", key) -} - -// Returns a mutex for the given key, no guarantee of its lock status. -func (m *MutexKV) get(key string) *sync.Mutex { - m.lock.Lock() - defer m.lock.Unlock() - mutex, ok := m.store[key] - if !ok { - mutex = &sync.Mutex{} - m.store[key] = mutex - } - return mutex -} - -// Returns a properly initialized MutexKV. -func NewMutexKV() *MutexKV { - return &MutexKV{ - store: make(map[string]*sync.Mutex), - } -} diff --git a/terraform/mutexkv/mutexkv_test.go b/terraform/mutexkv/mutexkv_test.go deleted file mode 100644 index 27498bd5..00000000 --- a/terraform/mutexkv/mutexkv_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package mutexkv - -import ( - "testing" - "time" -) - -func TestMutexKVLock(t *testing.T) { - s := struct { - mkv *MutexKV - }{ - mkv: NewMutexKV(), - } - - s.mkv.Lock("foo") - - doneCh := make(chan struct{}) - - go func() { - s.mkv.Lock("foo") - close(doneCh) - }() - - select { - case <-doneCh: - t.Fatal("Second lock was able to be taken. This shouldn't happen.") - case <-time.After(50 * time.Millisecond): - // pass - } -} - -func TestMutexKVUnlock(t *testing.T) { - s := struct { - mkv *MutexKV - }{ - mkv: NewMutexKV(), - } - - s.mkv.Lock("foo") - s.mkv.Unlock("foo") - - doneCh := make(chan struct{}) - - go func() { - s.mkv.Lock("foo") - close(doneCh) - }() - - select { - case <-doneCh: - // pass - case <-time.After(50 * time.Millisecond): - t.Fatal("Second lock blocked after unlock. This shouldn't happen.") - } -} - -func TestMutexKVDifferentKeys(t *testing.T) { - s := struct { - mkv *MutexKV - }{ - mkv: NewMutexKV(), - } - - s.mkv.Lock("foo") - - doneCh := make(chan struct{}) - - go func() { - s.mkv.Lock("bar") - close(doneCh) - }() - - select { - case <-doneCh: - // pass - case <-time.After(50 * time.Millisecond): - t.Fatal("Second lock on a different key blocked. This shouldn't happen.") - } -}