Skip to content

Instantly share code, notes, and snippets.

@vooon
Last active December 10, 2024 11:35
Show Gist options
  • Save vooon/911c13af31c4aff53477536d8d6afe65 to your computer and use it in GitHub Desktop.
Save vooon/911c13af31c4aff53477536d8d6afe65 to your computer and use it in GitHub Desktop.
Some useful helpers to setup gophercloud
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