Merge pull request #116 from xanzy/svh/f-http-client

Introduce optional client configuration options
diff --git a/cloudstack/cloudstack.go b/cloudstack/cloudstack.go
index e3dcb4e..98f2014 100644
--- a/cloudstack/cloudstack.go
+++ b/cloudstack/cloudstack.go
@@ -46,6 +46,9 @@
 	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 @@
 }
 
 // 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 @@
 		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 @@
 	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 @@
 // 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,15 @@
 	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 WithAsyncTimeout(timeout int64) ClientOption {
+	return func(cs *CloudStackClient) {
+		if timeout != 0 {
+			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 +465,18 @@
 	}
 }
 
+// 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 {
+			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..edbe6af 100644
--- a/generate/generate.go
+++ b/generate/generate.go
@@ -241,6 +241,9 @@
 	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 @@
 	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 @@
 	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 @@
 	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,15 @@
 	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 WithAsyncTimeout(timeout int64) ClientOption {")
+	pn("	return func(cs *CloudStackClient) {")
+	pn("		if timeout != 0 {")
+	pn("			cs.timeout = timeout")
+	pn("		}")
+	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 +528,18 @@
 	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("			if client.Jar == nil {")
+	pn("				client.Jar = cs.client.Jar")
+	pn("			}")
+	pn("			cs.client = client")
+	pn("		}")
+	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)")