diff --git a/cloudstack/cloudstack.go b/cloudstack/cloudstack.go index e3dcb4e..7a347a9 100644 --- a/cloudstack/cloudstack.go +++ b/cloudstack/cloudstack.go @@ -46,6 +46,9 @@ func IsID(id string) bool { return idRegex.MatchString(id) } +// ClientOption can be passed to new client functions to set custom options +type ClientOption func(*CloudStackClient) + // OptionFunc can be passed to the courtesy helper functions to set additional parameters type OptionFunc func(*CloudStackClient, interface{}) error @@ -142,7 +145,7 @@ type CloudStackClient struct { } // Creates a new client for communicating with CloudStack -func newClient(apiurl string, apikey string, secret string, async bool, verifyssl bool) *CloudStackClient { +func newClient(apiurl string, apikey string, secret string, async bool, verifyssl bool, options ...ClientOption) *CloudStackClient { jar, _ := cookiejar.New(nil) cs := &CloudStackClient{ client: &http.Client{ @@ -169,6 +172,11 @@ func newClient(apiurl string, apikey string, secret string, async bool, verifyss options: []OptionFunc{}, timeout: 300, } + + for _, fn := range options { + fn(cs) + } + cs.APIDiscovery = NewAPIDiscoveryService(cs) cs.Account = NewAccountService(cs) cs.Address = NewAddressService(cs) @@ -238,14 +246,15 @@ func newClient(apiurl string, apikey string, secret string, async bool, verifyss cs.VirtualMachine = NewVirtualMachineService(cs) cs.Volume = NewVolumeService(cs) cs.Zone = NewZoneService(cs) + return cs } // Default non-async client. So for async calls you need to implement and check the async job result yourself. When using // HTTPS with a self-signed certificate to connect to your CloudStack API, you would probably want to set 'verifyssl' to // false so the call ignores the SSL errors/warnings. -func NewClient(apiurl string, apikey string, secret string, verifyssl bool) *CloudStackClient { - cs := newClient(apiurl, apikey, secret, false, verifyssl) +func NewClient(apiurl string, apikey string, secret string, verifyssl bool, options ...ClientOption) *CloudStackClient { + cs := newClient(apiurl, apikey, secret, false, verifyssl, options...) return cs } @@ -253,8 +262,8 @@ func NewClient(apiurl string, apikey string, secret string, verifyssl bool) *Clo // this client will wait until the async job is finished or until the configured AsyncTimeout is reached. When the async // job finishes successfully it will return actual object received from the API and nil, but when the timout is // reached it will return the initial object containing the async job ID for the running job and a warning. -func NewAsyncClient(apiurl string, apikey string, secret string, verifyssl bool) *CloudStackClient { - cs := newClient(apiurl, apikey, secret, true, verifyssl) +func NewAsyncClient(apiurl string, apikey string, secret string, verifyssl bool, options ...ClientOption) *CloudStackClient { + cs := newClient(apiurl, apikey, secret, true, verifyssl, options...) return cs } @@ -419,6 +428,16 @@ func getRawValue(b json.RawMessage) (json.RawMessage, error) { return nil, fmt.Errorf("Unable to extract the raw value from:\n\n%s\n\n", string(b)) } +// WithAsyncTimeout takes a custom timeout to be used by the CloudStackClient +func WithHTTPClient(timeout int64) ClientOption { + return func(cs *CloudStackClient) { + if timeout == 0 { + return + } + cs.timeout = timeout + } +} + // DomainIDSetter is an interface that every type that can set a domain ID must implement type DomainIDSetter interface { SetDomainid(string) @@ -447,6 +466,19 @@ func WithDomain(domain string) OptionFunc { } } +// WithHTTPClient takes a custom HTTP client to be used by the CloudStackClient +func WithHTTPClient(client *http.Client) ClientOption { + return func(cs *CloudStackClient) { + if client == nil { + return + } + if client.Jar == nil { + client.Jar = cs.client.Jar + } + cs.client = client + } +} + // ProjectIDSetter is an interface that every type that can set a project ID must implement type ProjectIDSetter interface { SetProjectid(string) diff --git a/generate/generate.go b/generate/generate.go index 36894d3..b44eff6 100644 --- a/generate/generate.go +++ b/generate/generate.go @@ -241,6 +241,9 @@ func (as *allServices) GeneralCode() ([]byte, error) { pn(" return idRegex.MatchString(id)") pn("}") pn("") + pn("// ClientOption can be passed to new client functions to set custom options") + pn("type ClientOption func(*CloudStackClient)") + pn("") pn("// OptionFunc can be passed to the courtesy helper functions to set additional parameters") pn("type OptionFunc func(*CloudStackClient, interface{}) error") pn("") @@ -271,7 +274,7 @@ func (as *allServices) GeneralCode() ([]byte, error) { pn("}") pn("") pn("// Creates a new client for communicating with CloudStack") - pn("func newClient(apiurl string, apikey string, secret string, async bool, verifyssl bool) *CloudStackClient {") + pn("func newClient(apiurl string, apikey string, secret string, async bool, verifyssl bool, options ...ClientOption) *CloudStackClient {") pn(" jar, _ := cookiejar.New(nil)") pn(" cs := &CloudStackClient{") pn(" client: &http.Client{") @@ -298,17 +301,23 @@ func (as *allServices) GeneralCode() ([]byte, error) { pn(" options: []OptionFunc{},") pn(" timeout: 300,") pn(" }") + pn("") + pn(" for _, fn := range options {") + pn(" fn(cs)") + pn(" }") + pn("") for _, s := range as.services { pn(" cs.%s = New%s(cs)", strings.TrimSuffix(s.name, "Service"), s.name) } + pn("") pn(" return cs") pn("}") pn("") pn("// Default non-async client. So for async calls you need to implement and check the async job result yourself. When using") pn("// HTTPS with a self-signed certificate to connect to your CloudStack API, you would probably want to set 'verifyssl' to") pn("// false so the call ignores the SSL errors/warnings.") - pn("func NewClient(apiurl string, apikey string, secret string, verifyssl bool) *CloudStackClient {") - pn(" cs := newClient(apiurl, apikey, secret, false, verifyssl)") + pn("func NewClient(apiurl string, apikey string, secret string, verifyssl bool, options ...ClientOption) *CloudStackClient {") + pn(" cs := newClient(apiurl, apikey, secret, false, verifyssl, options...)") pn(" return cs") pn("}") pn("") @@ -316,8 +325,8 @@ func (as *allServices) GeneralCode() ([]byte, error) { pn("// this client will wait until the async job is finished or until the configured AsyncTimeout is reached. When the async") pn("// job finishes successfully it will return actual object received from the API and nil, but when the timout is") pn("// reached it will return the initial object containing the async job ID for the running job and a warning.") - pn("func NewAsyncClient(apiurl string, apikey string, secret string, verifyssl bool) *CloudStackClient {") - pn(" cs := newClient(apiurl, apikey, secret, true, verifyssl)") + pn("func NewAsyncClient(apiurl string, apikey string, secret string, verifyssl bool, options ...ClientOption) *CloudStackClient {") + pn(" cs := newClient(apiurl, apikey, secret, true, verifyssl, options...)") pn(" return cs") pn("}") pn("") @@ -482,6 +491,16 @@ func (as *allServices) GeneralCode() ([]byte, error) { pn(" return nil, fmt.Errorf(\"Unable to extract the raw value from:\\n\\n%%s\\n\\n\", string(b))") pn("}") pn("") + pn("// WithAsyncTimeout takes a custom timeout to be used by the CloudStackClient") + pn("func WithHTTPClient(timeout int64) ClientOption {") + pn(" return func(cs *CloudStackClient) {") + pn(" if timeout == 0 {") + pn(" return") + pn(" }") + pn(" cs.timeout = timeout") + pn(" }") + pn("}") + pn("") pn("// DomainIDSetter is an interface that every type that can set a domain ID must implement") pn("type DomainIDSetter interface {") pn(" SetDomainid(string)") @@ -510,6 +529,19 @@ func (as *allServices) GeneralCode() ([]byte, error) { pn(" }") pn("}") pn("") + pn("// WithHTTPClient takes a custom HTTP client to be used by the CloudStackClient") + pn("func WithHTTPClient(client *http.Client) ClientOption {") + pn(" return func(cs *CloudStackClient) {") + pn(" if client == nil {") + pn(" return") + pn(" }") + pn(" if client.Jar == nil {") + pn(" client.Jar = cs.client.Jar") + pn(" }") + pn(" cs.client = client") + pn(" }") + pn("}") + pn("") pn("// ProjectIDSetter is an interface that every type that can set a project ID must implement") pn("type ProjectIDSetter interface {") pn(" SetProjectid(string)")