104 lines
3.4 KiB
Go
104 lines
3.4 KiB
Go
package openstack
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
|
||
|
|
"github.com/gophercloud/gophercloud/v2"
|
||
|
|
osauth "github.com/gophercloud/gophercloud/v2/openstack"
|
||
|
|
"github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/floatingips"
|
||
|
|
)
|
||
|
|
|
||
|
|
// ClientConfig carries pre-resolved credential values (already read from
|
||
|
|
// environment variables by the caller — see config.OpenStackAuth). Token
|
||
|
|
// auth is required per the deployment constraint that the admin credential
|
||
|
|
// is supplied via the process environment, not a config file.
|
||
|
|
type ClientConfig struct {
|
||
|
|
AuthURL string
|
||
|
|
Token string
|
||
|
|
ProjectID string
|
||
|
|
ProjectName string
|
||
|
|
DomainName string
|
||
|
|
Region string
|
||
|
|
}
|
||
|
|
|
||
|
|
type Client struct {
|
||
|
|
networking *gophercloud.ServiceClient
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewClient authenticates against Keystone using a pre-issued admin token
|
||
|
|
// and returns a Client scoped to the given project/region, backed by the
|
||
|
|
// Neutron (networking v2) service catalog entry.
|
||
|
|
func NewClient(ctx context.Context, cfg ClientConfig) (*Client, error) {
|
||
|
|
if cfg.AuthURL == "" || cfg.Token == "" {
|
||
|
|
return nil, fmt.Errorf("openstack: auth URL and token are required")
|
||
|
|
}
|
||
|
|
|
||
|
|
authOpts := gophercloud.AuthOptions{
|
||
|
|
IdentityEndpoint: cfg.AuthURL,
|
||
|
|
TokenID: cfg.Token,
|
||
|
|
TenantID: cfg.ProjectID,
|
||
|
|
TenantName: cfg.ProjectName,
|
||
|
|
DomainName: cfg.DomainName,
|
||
|
|
}
|
||
|
|
if cfg.ProjectID != "" || cfg.ProjectName != "" {
|
||
|
|
authOpts.Scope = &gophercloud.AuthScope{
|
||
|
|
ProjectID: cfg.ProjectID,
|
||
|
|
ProjectName: cfg.ProjectName,
|
||
|
|
DomainName: cfg.DomainName,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
provider, err := osauth.AuthenticatedClient(ctx, authOpts)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("openstack: authenticate: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
networking, err := osauth.NewNetworkV2(provider, gophercloud.EndpointOpts{Region: cfg.Region})
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("openstack: networking client: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return &Client{networking: networking}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) GetFloatingIPByAddress(ctx context.Context, address string) (*FloatingIP, error) {
|
||
|
|
pages, err := floatingips.List(c.networking, floatingips.ListOpts{FloatingIP: address}).AllPages(ctx)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("openstack: list floating ips: %w", err)
|
||
|
|
}
|
||
|
|
list, err := floatingips.ExtractFloatingIPs(pages)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("openstack: extract floating ips: %w", err)
|
||
|
|
}
|
||
|
|
if len(list) == 0 {
|
||
|
|
return nil, ErrNotFound(address)
|
||
|
|
}
|
||
|
|
f := list[0]
|
||
|
|
return &FloatingIP{ID: f.ID, Address: f.FloatingIP, PortID: f.PortID, ProjectID: f.TenantID}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) AssociateFloatingIP(ctx context.Context, fipID, portID string) error {
|
||
|
|
_, err := floatingips.Update(ctx, c.networking, fipID, floatingips.UpdateOpts{
|
||
|
|
PortID: &portID,
|
||
|
|
}).Extract()
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("openstack: associate floating ip %s -> port %s: %w", fipID, portID, err)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) DisassociateFloatingIP(ctx context.Context, fipID string) error {
|
||
|
|
// gophercloud's UpdateOpts.PortID is *string with `omitempty`: a nil
|
||
|
|
// pointer is dropped from the request body entirely (no-op), while a
|
||
|
|
// pointer to "" is what actually serializes as port_id:null and
|
||
|
|
// disassociates the floating IP. See floatingips.UpdateOpts godoc.
|
||
|
|
empty := ""
|
||
|
|
_, err := floatingips.Update(ctx, c.networking, fipID, floatingips.UpdateOpts{
|
||
|
|
PortID: &empty,
|
||
|
|
}).Extract()
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("openstack: disassociate floating ip %s: %w", fipID, err)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|