selvpc.go raw

   1  package selvpcclient
   2  
   3  import (
   4  	"bytes"
   5  	"context"
   6  	"encoding/json"
   7  	"errors"
   8  	"fmt"
   9  	"runtime/debug"
  10  	"strings"
  11  	"time"
  12  
  13  	"github.com/gophercloud/gophercloud"
  14  
  15  	"github.com/selectel/go-selvpcclient/v4/selvpcclient/clients"
  16  	clientservices "github.com/selectel/go-selvpcclient/v4/selvpcclient/clients/services"
  17  )
  18  
  19  var errRequiredClientOptions = errors.New("some of the required options are not set")
  20  
  21  const (
  22  	AppName = "go-selvpcclient"
  23  )
  24  
  25  type Client struct {
  26  	// Resell - client for Cloud Management API.
  27  	Resell *clients.ResellClient
  28  
  29  	// QuotaManager - client for Cloud Quota Management API.
  30  	QuotaManager *clients.QuotaManagerClient
  31  
  32  	// Catalog - service for simplified resolve regional endpoints from Keystone catalog.
  33  	Catalog *clientservices.CatalogService
  34  
  35  	serviceClient *gophercloud.ServiceClient
  36  }
  37  
  38  type ClientOptions struct {
  39  	Context context.Context
  40  
  41  	// Your Account ID, for example: 234567.
  42  	DomainName string
  43  
  44  	// Specify Identity endpoint.
  45  	AuthURL string
  46  
  47  	// Setting a location for auth endpoint like ResellAPI or Keystone.
  48  	AuthRegion string
  49  
  50  	// Credentials of your service user.
  51  	// Documentation: https://docs.selectel.ru/control-panel-actions/users-and-roles/
  52  	Username string
  53  	Password string
  54  
  55  	// Optional field, that is used for authentication with project scope.
  56  	// If you created service user with admin role of project, then this is field for you.
  57  	ProjectID string
  58  
  59  	// Optional field to specify the domain name where the user is located.
  60  	// Used in private clouds to issue a token not from owned domain.
  61  	// If this field is not set, then it will be equal to the value of DomainName.
  62  	UserDomainName string
  63  }
  64  
  65  func NewClient(options *ClientOptions) (*Client, error) {
  66  	requiredAbsent := make([]string, 0)
  67  	if options.DomainName == "" {
  68  		requiredAbsent = append(requiredAbsent, "DomainName")
  69  	}
  70  
  71  	if options.Username == "" {
  72  		requiredAbsent = append(requiredAbsent, "Username")
  73  	}
  74  
  75  	if options.Password == "" {
  76  		requiredAbsent = append(requiredAbsent, "Password")
  77  	}
  78  
  79  	if options.AuthURL == "" {
  80  		requiredAbsent = append(requiredAbsent, "AuthURL")
  81  	}
  82  
  83  	if options.AuthRegion == "" {
  84  		requiredAbsent = append(requiredAbsent, "AuthRegion")
  85  	}
  86  
  87  	if len(requiredAbsent) > 0 {
  88  		return nil, fmt.Errorf("validation error: %w: %s", errRequiredClientOptions, strings.Join(requiredAbsent, ", "))
  89  	}
  90  
  91  	serviceClientOptions := clientservices.ServiceClientOptions{
  92  		DomainName:     options.DomainName,
  93  		Username:       options.Username,
  94  		Password:       options.Password,
  95  		AuthURL:        options.AuthURL,
  96  		AuthRegion:     options.AuthRegion,
  97  		ProjectID:      options.ProjectID,
  98  		UserDomainName: options.UserDomainName,
  99  		UserAgent:      fmt.Sprintf("%s/%s", AppName, findModuleVersion()),
 100  	}
 101  
 102  	serviceClient, err := clientservices.NewServiceClient(&serviceClientOptions)
 103  	if err != nil {
 104  		return nil, fmt.Errorf("failed to create service client, err: %w", err)
 105  	}
 106  
 107  	serviceClient.ProviderClient.Context = options.Context
 108  
 109  	catalogService, err := clientservices.NewCatalogService(serviceClient)
 110  	if err != nil {
 111  		return nil, fmt.Errorf("failed to initialize endpoints catalog service, err: %w", err)
 112  	}
 113  
 114  	requestService := clientservices.NewRequestService(serviceClient)
 115  
 116  	client := Client{
 117  		Resell:        clients.NewResellClient(requestService, catalogService, options.AuthRegion),
 118  		QuotaManager:  clients.NewQuotaManagerClient(requestService, catalogService),
 119  		Catalog:       catalogService,
 120  		serviceClient: serviceClient,
 121  	}
 122  
 123  	return &client, nil
 124  }
 125  
 126  func findModuleVersion() string {
 127  	moduleName := "github.com/selectel/" + AppName
 128  
 129  	info, ok := debug.ReadBuildInfo()
 130  	if ok {
 131  		for _, dep := range info.Deps {
 132  			// Use prefix, because module has name with major version - github.com/selectel/go-selvpcclient/v4
 133  			if strings.HasPrefix(dep.Path, moduleName) {
 134  				return dep.Version
 135  			}
 136  		}
 137  	}
 138  	return "unknown_version"
 139  }
 140  
 141  // GetXAuthToken - returns X-Auth-Token from Service Provider. This method doesn't guarantee that the token is valid.
 142  // It returns the last used token from the service provider. Usually the lifetime of the token is 24h. If you use
 143  // this token, then you should handle 401 error.
 144  func (selvpc *Client) GetXAuthToken() string {
 145  	return selvpc.serviceClient.Token()
 146  }
 147  
 148  // ---------------------------------------------------------------------------------------------------------------------
 149  
 150  // RFC3339NoZ describes a timestamp format used by some SelVPC responses.
 151  const RFC3339NoZ = "2006-01-02T15:04:05"
 152  
 153  // JSONRFC3339NoZTimezone is a type for timestamps SelVPC responses with the RFC3339NoZ format.
 154  type JSONRFC3339NoZTimezone time.Time
 155  
 156  // UnmarshalJSON helps to unmarshal timestamps from SelVPC responses to the
 157  // JSONRFC3339NoZTimezone type.
 158  func (jt *JSONRFC3339NoZTimezone) UnmarshalJSON(data []byte) error {
 159  	b := bytes.NewBuffer(data)
 160  	dec := json.NewDecoder(b)
 161  	var s string
 162  	if err := dec.Decode(&s); err != nil {
 163  		return err
 164  	}
 165  	if s == "" {
 166  		return nil
 167  	}
 168  	t, err := time.Parse(RFC3339NoZ, s)
 169  	if err != nil {
 170  		return err
 171  	}
 172  	*jt = JSONRFC3339NoZTimezone(t)
 173  	return nil
 174  }
 175  
 176  const (
 177  	// IPv4 represents IP version 4.
 178  	IPv4 IPVersion = "ipv4"
 179  
 180  	// IPv6 represents IP version 6.
 181  	IPv6 IPVersion = "ipv6"
 182  )
 183  
 184  // IPVersion represents a type for the IP versions of the different Selectel VPC APIs.
 185  type IPVersion string
 186