Last active
December 10, 2024 11:35
-
-
Save vooon/911c13af31c4aff53477536d8d6afe65 to your computer and use it in GitHub Desktop.
Some useful helpers to setup gophercloud
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ( | |
"context" | |
"crypto/tls" | |
"log/slog" | |
"net/http" | |
"github.com/gophercloud/gophercloud/v2" | |
"github.com/gophercloud/gophercloud/v2/openstack/config" | |
"github.com/gophercloud/gophercloud/v2/openstack/config/clouds" | |
oscli "github.com/gophercloud/utils/v2/client" | |
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" | |
"gopkg.in/mcuadros/go-defaults.v1" | |
) | |
type AuthConfig interface { | |
Parse() (gophercloud.AuthOptions, gophercloud.EndpointOpts, *tls.Config, error) | |
HTTPOpts() (debug bool, otelTracing bool) | |
} | |
// CloudConfig common gcloud config | |
type CloudConfig struct { | |
ClientConfigFile string `json:"client-config-file" env:"OS_CLIENT_CONFIG_FILE" name:"client-config-file" placeholder:"FILE" help:"OpenStack clouds.yaml file"` | |
Cloud string `json:"cloud" env:"OS_CLOUD" name:"cloud" placeholder:"CLOUD" xor:"auth,user,pass,dom" help:"Cloud to use from clouds.yaml"` | |
RegionName string `json:"region-name" env:"OS_REGION_NAME" name:"region-name" optional:"" placeholder:"REGION" help:"Override region defined in clouds.yaml"` | |
EndpointType string `json:"endpoint-type" env:"OS_INTERFACE,OS_ENDPOINT_TYPE" name:"endpoint-type" optional:"" placeholder:"PUBLIC" help:"Override interface defined in clouds.yaml"` | |
Debug bool `json:"debug" env:"OS_DEBUG" negatable:"" name:"debug" help:"Enable debug logging of the requests"` | |
OtelTracing bool `json:"otel-tracing" default:"true" env:"OS_TRACING" negatable:"" name:"otel-tracing" help:"Enable OpenTelemetry tracing"` | |
} | |
// EnvCloudConfig common gcloud config | |
type EnvCloudConfig struct { | |
CloudConfig `embed:"" yaml:",inline"` | |
AuthURL string `json:"auth-url" env:"OS_AUTH_URL" name:"auth-url" placeholder:"URL" xor:"auth" help:"Keystone auth URL"` | |
Username string `json:"username" env:"OS_USERNAME" name:"username" placeholder:"NAME" xor:"user" help:"User name"` | |
UserID string `json:"user-id" env:"OS_USER_ID" name:"user-id" placeholder:"ID" xor:"user" help:"User ID"` | |
Password string `json:"password" env:"OS_PASSWORD" name:"password" placeholder:"PASSWORD" xor:"pass" help:"User password"` | |
Passcode string `json:"passcode" env:"OS_PASSCODE" name:"passcode" placeholder:"PASSCODE" help:"User passcode"` | |
ProjectName string `json:"project-name" env:"OS_PROJECT_NAME" name:"project-name" placeholder:"NAME" help:"Project name (may override clouds.yaml)"` | |
ProjectID string `json:"project-id" env:"OS_PROJECT_ID" name:"project-id" placeholder:"ID" help:"Project ID (may override clouds.yaml)"` | |
UserDomainName string `json:"user-domain-name" env:"OS_USER_DOMAIN_NAME,OS_DOMAIN_NAME" name:"user-domain-name" placeholder:"NAME" xor:"dom" help:"User domain name"` | |
UserDomainID string `json:"user-domain-id" env:"OS_USER_DOMAIN_ID,OS_DOMAIN_ID" name:"user-domain-id" placeholder:"ID" xor:"dom" help:"User domain ID"` | |
ApplicationCredentialID string `json:"application-credential-id" env:"OS_APPLICATION_CREDENTIAL_ID" name:"application-credential-id" placeholder:"ID" xor:"user" help:"Application credential ID"` | |
ApplicationCredentialName string `json:"application-credential-name" env:"OS_APPLICATION_CREDENTIAL_NAME" name:"application-credential-name" placeholder:"NAME" xor:"user" help:"Application credential name"` | |
ApplicationCredentialSecret string `json:"application-credential-secret" env:"OS_APPLICATION_CREDENTIAL_SECRET" name:"application-credential-secret" placeholder:"SECRET" xor:"pass" help:"Application credential secret"` | |
} | |
func (osc *CloudConfig) HTTPOpts() (debug bool, otelTracing bool) { | |
return osc.Debug, osc.OtelTracing | |
} | |
func (osc *CloudConfig) Parse() (gophercloud.AuthOptions, gophercloud.EndpointOpts, *tls.Config, error) { | |
pOpts := []clouds.ParseOption{clouds.WithCloudName(osc.Cloud)} | |
if osc.ClientConfigFile != "" { | |
pOpts = append(pOpts, clouds.WithLocations(osc.ClientConfigFile)) | |
} | |
ao, eo, tlsCfg, err := clouds.Parse(pOpts...) | |
if err != nil { | |
return gophercloud.AuthOptions{}, gophercloud.EndpointOpts{}, nil, err | |
} | |
if osc.RegionName != "" { | |
eo.Region = osc.RegionName | |
} | |
if osc.EndpointType != "" { | |
eo.Availability = gophercloud.Availability(osc.EndpointType) | |
} | |
return ao, eo, tlsCfg, nil | |
} | |
func (osc *EnvCloudConfig) Parse() (gophercloud.AuthOptions, gophercloud.EndpointOpts, *tls.Config, error) { | |
if osc.Cloud != "" { | |
ao, eo, tlsCfg, err := osc.CloudConfig.Parse() | |
if err != nil { | |
return gophercloud.AuthOptions{}, gophercloud.EndpointOpts{}, nil, err | |
} | |
if osc.ProjectName != "" { | |
ao.TenantName = osc.ProjectName | |
ao.TenantID = "" | |
} | |
if osc.ProjectID != "" { | |
ao.TenantID = osc.ProjectID | |
ao.TenantName = "" | |
} | |
return ao, eo, tlsCfg, nil | |
} | |
ao := gophercloud.AuthOptions{ | |
IdentityEndpoint: osc.AuthURL, | |
UserID: osc.UserID, | |
Username: osc.Username, | |
Password: osc.Password, | |
Passcode: osc.Passcode, | |
TenantID: osc.ProjectID, | |
TenantName: osc.ProjectName, | |
DomainID: osc.UserDomainID, | |
DomainName: osc.UserDomainName, | |
ApplicationCredentialID: osc.ApplicationCredentialID, | |
ApplicationCredentialName: osc.ApplicationCredentialName, | |
ApplicationCredentialSecret: osc.ApplicationCredentialSecret, | |
} | |
eo := gophercloud.EndpointOpts{ | |
Region: osc.RegionName, | |
Availability: gophercloud.Availability(osc.EndpointType), | |
} | |
return ao, eo, nil, nil | |
} | |
// CloudOpts some additional opts | |
type CloudOpts struct { | |
AllowReauth bool `default:"true"` | |
Lg *slog.Logger | |
} | |
// Create HTTP Client with addons | |
func NewHTTPClient(osc AuthConfig, opts *CloudOpts, tlsCfg *tls.Config) http.Client { | |
hCli := http.Client{ | |
Transport: http.DefaultTransport.(*http.Transport).Clone(), | |
} | |
debug, otelTracing := osc.HTTPOpts() | |
if tlsCfg != nil { | |
tr := hCli.Transport.(*http.Transport) | |
tr.TLSClientConfig = tlsCfg | |
} | |
if debug { | |
var lg *slog.Logger | |
if opts != nil { | |
lg = opts.Lg | |
} | |
if lg == nil { | |
lg = slog.Default() | |
} | |
hCli.Transport = &oscli.RoundTripper{ | |
Rt: hCli.Transport, | |
// zlog provides few helpers around slog (and zap...) | |
// Logger: zlog.NewPrinterAt(lg, slog.LevelDebug), | |
} | |
} | |
if otelTracing { | |
hCli.Transport = otelhttp.NewTransport(hCli.Transport) | |
} | |
return hCli | |
} | |
// New creates authenticated ProviderClient. | |
// | |
// EndpointOpts useful for making ServiceClient. e.g. `openstack.NewComputeV2(pClient, eo)` | |
// AuthOptions might be useful to construct credentials for another project | |
func New(ctx context.Context, osc AuthConfig, opts *CloudOpts) (*gophercloud.ProviderClient, gophercloud.EndpointOpts, gophercloud.AuthOptions, error) { | |
if opts == nil { | |
opts = &CloudOpts{} | |
defaults.SetDefaults(opts) | |
} | |
ao, eo, tlsCfg, err := osc.Parse() | |
if err != nil { | |
return nil, gophercloud.EndpointOpts{}, gophercloud.AuthOptions{}, err | |
} | |
hCli := NewHTTPClient(osc, opts, tlsCfg) | |
ao.AllowReauth = opts.AllowReauth | |
pc, err := config.NewProviderClient(ctx, ao, config.WithHTTPClient(hCli)) | |
if err != nil { | |
return nil, gophercloud.EndpointOpts{}, gophercloud.AuthOptions{}, err | |
} | |
return pc, eo, ao, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment