lego/vendor/github.com/exoscale/egoscale/client.go
Ludovic Fernandez 42941ccea6
Refactor the core of the lib (#700)
- Packages
- Isolate code used by the CLI into the package `cmd`
- (experimental) Add e2e tests for HTTP01, TLS-ALPN-01 and DNS-01, use [Pebble](https://github.com/letsencrypt/pebble) and [challtestsrv](https://github.com/letsencrypt/boulder/tree/master/test/challtestsrv) 
- Support non-ascii domain name (punnycode)
- Check all challenges in a predictable order
- No more global exported variables
- Archive revoked certificates
- Fixes revocation for subdomains and non-ascii domains
- Disable pending authorizations
- use pointer for RemoteError/ProblemDetails
- Poll authz URL instead of challenge URL
- The ability for a DNS provider to solve the challenge sequentially
- Check all nameservers in a predictable order
- Option to disable the complete propagation Requirement
- CLI, support for renew with CSR
- CLI, add SAN on renew
- Add command to list certificates.
- Logs every iteration of waiting for the propagation
- update DNSimple client
- update github.com/miekg/dns
2018-12-06 22:50:17 +01:00

385 lines
9.4 KiB
Go

package egoscale
import (
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"os"
"reflect"
"strings"
"time"
)
// Taggable represents a resource which can have tags attached
//
// This is a helper to fill the resourcetype of a CreateTags call
type Taggable interface {
// CloudStack resource type of the Taggable type
ResourceType() string
}
// Deletable represents an Interface that can be "Delete" by the client
type Deletable interface {
// Delete removes the given resource(s) or throws
Delete(context context.Context, client *Client) error
}
// Listable represents an Interface that can be "List" by the client
type Listable interface {
// ListRequest builds the list command
ListRequest() (ListCommand, error)
}
// Gettable represents an Interface that can be "Get" by the client
type Gettable interface {
Listable
}
// Client represents the CloudStack API client
type Client struct {
// HTTPClient holds the HTTP client
HTTPClient *http.Client
// Endpoints is CloudStack API
Endpoint string
// APIKey is the API identifier
APIKey string
// apisecret is the API secret, hence non exposed
apiSecret string
// PageSize represents the default size for a paginated result
PageSize int
// Timeout represents the default timeout for the async requests
Timeout time.Duration
// RetryStrategy represents the waiting strategy for polling the async requests
RetryStrategy RetryStrategyFunc
// Logger contains any log, plug your own
Logger *log.Logger
}
// RetryStrategyFunc represents a how much time to wait between two calls to CloudStack
type RetryStrategyFunc func(int64) time.Duration
// IterateItemFunc represents the callback to iterate a list of results, if false stops
type IterateItemFunc func(interface{}, error) bool
// WaitAsyncJobResultFunc represents the callback to wait a results of an async request, if false stops
type WaitAsyncJobResultFunc func(*AsyncJobResult, error) bool
// NewClient creates a CloudStack API client with default timeout (60)
//
// Timeout is set to both the HTTP client and the client itself.
func NewClient(endpoint, apiKey, apiSecret string) *Client {
timeout := 60 * time.Second
httpClient := &http.Client{
Transport: http.DefaultTransport,
}
client := &Client{
HTTPClient: httpClient,
Endpoint: endpoint,
APIKey: apiKey,
apiSecret: apiSecret,
PageSize: 50,
Timeout: timeout,
RetryStrategy: MonotonicRetryStrategyFunc(2),
Logger: log.New(ioutil.Discard, "", 0),
}
if prefix, ok := os.LookupEnv("EXOSCALE_TRACE"); ok {
client.Logger = log.New(os.Stderr, prefix, log.LstdFlags)
client.TraceOn()
}
return client
}
// Get populates the given resource or fails
func (client *Client) Get(g Gettable) error {
ctx, cancel := context.WithTimeout(context.Background(), client.Timeout)
defer cancel()
return client.GetWithContext(ctx, g)
}
// GetWithContext populates the given resource or fails
func (client *Client) GetWithContext(ctx context.Context, g Gettable) error {
gs, err := client.ListWithContext(ctx, g)
if err != nil {
return err
}
count := len(gs)
if count != 1 {
req, err := g.ListRequest()
if err != nil {
return err
}
params, err := client.Payload(req)
if err != nil {
return err
}
// formatting the query string nicely
payload := params.Encode()
payload = strings.Replace(payload, "&", ", ", -1)
if count == 0 {
return &ErrorResponse{
ErrorCode: ParamError,
ErrorText: fmt.Sprintf("not found, query: %s", payload),
}
}
return fmt.Errorf("more than one element found: %s", payload)
}
return Copy(g, gs[0])
}
// Delete removes the given resource of fails
func (client *Client) Delete(g Deletable) error {
ctx, cancel := context.WithTimeout(context.Background(), client.Timeout)
defer cancel()
return client.DeleteWithContext(ctx, g)
}
// DeleteWithContext removes the given resource of fails
func (client *Client) DeleteWithContext(ctx context.Context, g Deletable) error {
return g.Delete(ctx, client)
}
// List lists the given resource (and paginate till the end)
func (client *Client) List(g Listable) ([]interface{}, error) {
ctx, cancel := context.WithTimeout(context.Background(), client.Timeout)
defer cancel()
return client.ListWithContext(ctx, g)
}
// ListWithContext lists the given resources (and paginate till the end)
func (client *Client) ListWithContext(ctx context.Context, g Listable) ([]interface{}, error) {
s := make([]interface{}, 0)
if g == nil || reflect.ValueOf(g).IsNil() {
return s, fmt.Errorf("g Listable shouldn't be nil, got %#v", g)
}
req, err := g.ListRequest()
if err != nil {
return s, err
}
client.PaginateWithContext(ctx, req, func(item interface{}, e error) bool {
if item != nil {
s = append(s, item)
return true
}
err = e
return false
})
return s, err
}
// AsyncListWithContext lists the given resources (and paginate till the end)
//
//
// // NB: goroutine may leak if not read until the end. Create a proper context!
// ctx, cancel := context.WithCancel(context.Background())
// defer cancel()
//
// outChan, errChan := client.AsyncListWithContext(ctx, new(egoscale.VirtualMachine))
//
// for {
// select {
// case i, ok := <- outChan:
// if ok {
// vm := i.(egoscale.VirtualMachine)
// // ...
// } else {
// outChan = nil
// }
// case err, ok := <- errChan:
// if ok {
// // do something
// }
// // Once an error has been received, you can expect the channels to be closed.
// errChan = nil
// }
// if errChan == nil && outChan == nil {
// break
// }
// }
//
func (client *Client) AsyncListWithContext(ctx context.Context, g Listable) (<-chan interface{}, <-chan error) {
outChan := make(chan interface{}, client.PageSize)
errChan := make(chan error)
go func() {
defer close(outChan)
defer close(errChan)
req, err := g.ListRequest()
if err != nil {
errChan <- err
return
}
client.PaginateWithContext(ctx, req, func(item interface{}, e error) bool {
if item != nil {
outChan <- item
return true
}
errChan <- e
return false
})
}()
return outChan, errChan
}
// Paginate runs the ListCommand and paginates
func (client *Client) Paginate(req ListCommand, callback IterateItemFunc) {
ctx, cancel := context.WithTimeout(context.Background(), client.Timeout)
defer cancel()
client.PaginateWithContext(ctx, req, callback)
}
// PaginateWithContext runs the ListCommand as long as the ctx is valid
func (client *Client) PaginateWithContext(ctx context.Context, req ListCommand, callback IterateItemFunc) {
pageSize := client.PageSize
page := 1
for {
req.SetPage(page)
req.SetPageSize(pageSize)
resp, err := client.RequestWithContext(ctx, req)
if err != nil {
callback(nil, err)
break
}
size := 0
didErr := false
req.each(resp, func(element interface{}, err error) bool {
// If the context was cancelled, kill it in flight
if e := ctx.Err(); e != nil {
element = nil
err = e
}
if callback(element, err) {
size++
return true
}
didErr = true
return false
})
if size < pageSize || didErr {
break
}
page++
}
}
// APIName returns the CloudStack name of the given command
func (client *Client) APIName(command Command) string {
// This is due to a limitation of Go<=1.7
if _, ok := command.(*AuthorizeSecurityGroupEgress); ok {
return "authorizeSecurityGroupEgress"
}
info, err := info(command)
if err != nil {
panic(err)
}
return info.Name
}
// APIDescription returns the description of the given CloudStack command
func (client *Client) APIDescription(command Command) string {
info, err := info(command)
if err != nil {
return "*missing description*"
}
return info.Description
}
// Response returns the response structure of the given command
func (client *Client) Response(command Command) interface{} {
switch command.(type) {
case AsyncCommand:
return (command.(AsyncCommand)).asyncResponse()
default:
return command.response()
}
}
// TraceOn activates the HTTP tracer
func (client *Client) TraceOn() {
if _, ok := client.HTTPClient.Transport.(*traceTransport); !ok {
client.HTTPClient.Transport = &traceTransport{
transport: client.HTTPClient.Transport,
logger: client.Logger,
}
}
}
// TraceOff deactivates the HTTP tracer
func (client *Client) TraceOff() {
if rt, ok := client.HTTPClient.Transport.(*traceTransport); ok {
client.HTTPClient.Transport = rt.transport
}
}
// traceTransport contains the original HTTP transport to enable it to be reverted
type traceTransport struct {
transport http.RoundTripper
logger *log.Logger
}
// RoundTrip executes a single HTTP transaction
func (t *traceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if dump, err := httputil.DumpRequest(req, true); err == nil {
t.logger.Printf("%s", dump)
}
resp, err := t.transport.RoundTrip(req)
if err != nil {
return nil, err
}
if dump, err := httputil.DumpResponse(resp, true); err == nil {
t.logger.Printf("%s", dump)
}
return resp, nil
}
// MonotonicRetryStrategyFunc returns a function that waits for n seconds for each iteration
func MonotonicRetryStrategyFunc(seconds int) RetryStrategyFunc {
return func(iteration int64) time.Duration {
return time.Duration(seconds) * time.Second
}
}
// FibonacciRetryStrategy waits for an increasing amount of time following the Fibonacci sequence
func FibonacciRetryStrategy(iteration int64) time.Duration {
var a, b, i, tmp int64
a = 0
b = 1
for i = 0; i < iteration; i++ {
tmp = a + b
a = b
b = tmp
}
return time.Duration(a) * time.Second
}