Skip to content
This repository has been archived by the owner on Oct 11, 2019. It is now read-only.

Commit

Permalink
Merge a9e04ad into 614f760
Browse files Browse the repository at this point in the history
  • Loading branch information
petemoore committed Sep 25, 2018
2 parents 614f760 + a9e04ad commit 1858a3c
Show file tree
Hide file tree
Showing 16 changed files with 198 additions and 310 deletions.
32 changes: 16 additions & 16 deletions codegenerator/model/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,6 @@ import (
tcclient "github.com/taskcluster/taskcluster-client-go"
)
const (
DefaultBaseURL = "` + api.BaseURL + `"
)
type ` + api.apiDef.Name + ` tcclient.Client
// New returns ` + text.IndefiniteArticle(api.apiDef.Name) + ` ` + api.apiDef.Name + ` client, configured to run against production. Pass in
Expand All @@ -136,10 +132,7 @@ type ` + api.apiDef.Name + ` tcclient.Client
`
// Here we want to add spaces between commands and comments, such that the comments line up, e.g.:
//
// myQueue, credsError := queue.New(nil) // credentials loaded from TASKCLUSTER_* environment variables
// if credsError != nil {
// // handle malformed credentials...
// }
// myQueue := queue.New(nil) // credentials loaded from TASKCLUSTER_* environment variables
// myQueue.Authenticate = false // disable authentication (creds above are now ignored)
// myQueue.BaseURL = "http://localhost:1234/api/Queue/v1" // alternative API endpoint (production by default)
// data, err := myQueue.Task(.....) // for example, call the Task(.....) API endpoint (described further down)...
Expand Down Expand Up @@ -177,14 +170,18 @@ type ` + api.apiDef.Name + ` tcclient.Client
}

content += "// if err != nil {\n"
content += "// // handle errors...\n"
content += "// // handle errors...\n"
content += "// }"
apiVersion := api.APIVersion
if apiVersion == "" {
apiVersion = "v1"
}
content += `
func New(credentials *tcclient.Credentials) *` + api.apiDef.Name + ` {
return &` + api.apiDef.Name + `{
Credentials: credentials,
BaseURL: DefaultBaseURL,
Authenticate: credentials != nil,
ServiceName: "` + api.ServiceName + `",
APIVersion: "` + apiVersion + `",
}
}
Expand All @@ -193,15 +190,18 @@ func New(credentials *tcclient.Credentials) *` + api.apiDef.Name + ` {
// TASKCLUSTER_CLIENT_ID
// TASKCLUSTER_ACCESS_TOKEN
// TASKCLUSTER_CERTIFICATE
// TASKCLUSTER_ROOT_URL
//
// No validation is performed on the loaded values, and unset environment
// variables will result in empty string values.
//
// If environment variables TASKCLUSTER_CLIENT_ID is empty string or undefined
// If environment variable TASKCLUSTER_CLIENT_ID is empty string or not set,
// authentication will be disabled.
func NewFromEnv() *` + api.apiDef.Name + ` {
c := tcclient.CredentialsFromEnvVars()
return &` + api.apiDef.Name + `{
Credentials: c,
BaseURL: DefaultBaseURL,
Authenticate: c.ClientID != "",
Credentials: tcclient.CredentialsFromEnvVars(),
ServiceName: "` + api.ServiceName + `",
APIVersion: "` + apiVersion + `",
}
}
Expand Down
30 changes: 17 additions & 13 deletions creds.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package tcclient

import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
Expand Down Expand Up @@ -34,6 +33,10 @@ type Credentials struct {
// no scopes at all.
// See https://docs.taskcluster.net/manual/apis/authorized-scopes
AuthorizedScopes []string `json:"authorizedScopes"`
// RootURL is the root url of the taskcluster cluster to which the
// credentials pertain, for example "https://taskcluster.net" or
// "https://my.taskcluster.org:23430".
RootURL string
}

func (creds *Credentials) String() string {
Expand All @@ -46,21 +49,19 @@ func (creds *Credentials) String() string {
)
}

// Client is the entry point into all the functionality in this package. It
// contains authentication credentials, and a service endpoint, which are
// required for all HTTP operations.
// Client provides access to all taskcluster HTTP APIs for a given service, on
// a given API version, of a given deployed cluster.
type Client struct {
// Version of client API, e.g. "v1"
APIVersion string
// Service name, e.g. "aws-provisioner"
ServiceName string
// Credentials (including root url of cluster credentials pertain to) for
// making API calls
Credentials *Credentials
// The URL of the API endpoint to hit.
// For example, "https://auth.taskcluster.net/v1" for production auth service.
BaseURL string
// Whether authentication is enabled (e.g. set to 'false' when using taskcluster-proxy)
Authenticate bool
// HTTPClient is a ReducedHTTPClient to be used for the http call instead of
// the DefaultHTTPClient.
// HTTPClient is a ReducedHTTPClient to be used for http calls. If nil,
// DefaultHTTPClient will be used.
HTTPClient ReducedHTTPClient
// Context that aborts all requests with this client
Context context.Context
}

// Certificate represents the certificate used in Temporary Credentials. See
Expand Down Expand Up @@ -203,15 +204,18 @@ func (creds *Credentials) Cert() (cert *Certificate, err error) {

// CredentialsFromEnvVars creates and returns Taskcluster credentials
// initialised from the values of environment variables:
//
// TASKCLUSTER_CLIENT_ID
// TASKCLUSTER_ACCESS_TOKEN
// TASKCLUSTER_CERTIFICATE
//
// No validation is performed on the loaded values, and unset environment
// variables will result in empty string values.
func CredentialsFromEnvVars() *Credentials {
return &Credentials{
ClientID: os.Getenv("TASKCLUSTER_CLIENT_ID"),
AccessToken: os.Getenv("TASKCLUSTER_ACCESS_TOKEN"),
Certificate: os.Getenv("TASKCLUSTER_CERTIFICATE"),
RootURL: os.Getenv("TASKCLUSTER_ROOT_URL"),
}
}
50 changes: 18 additions & 32 deletions http.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@ import (
"io"
"io/ioutil"
"net/http"
"strings"

// "net/http/httputil"
"net/url"
"reflect"
"time"

"github.com/taskcluster/httpbackoff"
tcurls "github.com/taskcluster/taskcluster-lib-urls"
hawk "github.com/tent/hawk-go"
)

const (
DefaultRootURL = "https://taskcluster.net"
)

// CallSummary provides information about the underlying http request and
// response issued for a given API call.
type CallSummary struct {
Expand Down Expand Up @@ -81,19 +83,15 @@ type ReducedHTTPClient interface {
// making multiple requests in various goroutines.
var defaultHTTPClient ReducedHTTPClient = &http.Client{}

// utility function to create a URL object based on given data
func setURL(client *Client, route string, query url.Values) (u *url.URL, err error) {
URL := client.BaseURL
// See https://bugzil.la/1484702
// Avoid double separator; routes must start with `/`, so baseURL shouldn't
// end with `/`.
if strings.HasSuffix(URL, "/") {
URL = URL[:len(URL)-1]
func (client *Client) UnsignedURL(route string, query url.Values) (u *url.URL, err error) {
rootURL := DefaultRootURL
if client.Credentials != nil && client.Credentials.RootURL != "" {
rootURL = client.Credentials.RootURL
}
URL += route
URL := tcurls.API(rootURL, client.ServiceName, client.APIVersion, route)
u, err = url.Parse(URL)
if err != nil {
return nil, fmt.Errorf("Cannot parse url: '%v', is BaseURL (%v) set correctly?\n%v\n", URL, client.BaseURL, err)
return nil, fmt.Errorf("Cannot parse url: '%v', is RootURL (%v) correct?\n%v\n", URL, client.Credentials.RootURL, err)
}
if query != nil {
u.RawQuery = query.Encode()
Expand All @@ -115,39 +113,31 @@ func (client *Client) Request(rawPayload []byte, method, route string, query url
httpCall := func() (*http.Response, error, error) {
var ioReader io.Reader
ioReader = bytes.NewReader(rawPayload)
u, err := setURL(client, route, query)
u, err := client.UnsignedURL(route, query)
if err != nil {
return nil, nil, fmt.Errorf("apiCall url cannot be parsed:\n%v\n", err)
}
callSummary.HTTPRequest, err = http.NewRequest(method, u.String(), ioReader)
if err != nil {
return nil, nil, fmt.Errorf("Internal error: apiCall url cannot be parsed although thought to be valid: '%v', is the BaseURL (%v) set correctly?\n%v\n", u.String(), client.BaseURL, err)
return nil, nil, fmt.Errorf("Internal error: apiCall url cannot be parsed although thought to be valid: '%v', is the RootURL (%v) correct?\n%v\n", u.String(), client.Credentials.RootURL, err)
}
if len(rawPayload) > 0 {
callSummary.HTTPRequest.Header.Set("Content-Type", "application/json")
}
// Refresh Authorization header with each call...
// Only authenticate if client library user wishes to.
if client.Authenticate {
if client.Credentials != nil && client.Credentials.ClientID != "" {
err = client.Credentials.SignRequest(callSummary.HTTPRequest)
if err != nil {
return nil, nil, err
}
}
// Set context if one is given
if client.Context != nil {
callSummary.HTTPRequest = callSummary.HTTPRequest.WithContext(client.Context)
}
var resp *http.Response
if client.HTTPClient != nil {
resp, err = client.HTTPClient.Do(callSummary.HTTPRequest)
} else {
resp, err = defaultHTTPClient.Do(callSummary.HTTPRequest)
}
// return cancelled error, if context was cancelled
if client.Context != nil && client.Context.Err() != nil {
return nil, nil, client.Context.Err()
}
// b, e := httputil.DumpResponse(resp, true)
// if e == nil {
// fmt.Println(string(b))
Expand Down Expand Up @@ -183,7 +173,7 @@ func (c *Credentials) SignRequest(req *http.Request) (err error) {
Hash: sha256.New,
}
reqAuth := hawk.NewRequestAuth(req, credentials, 0)
reqAuth.Ext, err = getExtHeader(c)
reqAuth.Ext, err = c.ExtHeader()
if err != nil {
return fmt.Errorf("Internal error: was not able to generate hawk ext header from provided credentials:\n%s\n%s", c, err)
}
Expand Down Expand Up @@ -223,10 +213,6 @@ func (client *Client) APICall(payload interface{}, method, route string, result
callSummary, err := client.Request(rawPayload, method, route, query)
callSummary.HTTPRequestObject = payload
if err != nil {
// If context failed during this request, then we should just return that error
if client.Context != nil && client.Context.Err() != nil {
return result, callSummary, client.Context.Err()
}
return result,
callSummary,
&APICallException{
Expand Down Expand Up @@ -256,7 +242,7 @@ func (client *Client) APICall(payload interface{}, method, route string, result
// query string parameters, if any, and duration is the amount of time that the
// signed URL should remain valid for.
func (client *Client) SignedURL(route string, query url.Values, duration time.Duration) (u *url.URL, err error) {
u, err = setURL(client, route, query)
u, err = client.UnsignedURL(route, query)
if err != nil {
return
}
Expand All @@ -269,7 +255,7 @@ func (client *Client) SignedURL(route string, query url.Values, duration time.Du
if err != nil {
return
}
reqAuth.Ext, err = getExtHeader(client.Credentials)
reqAuth.Ext, err = client.Credentials.ExtHeader()
if err != nil {
return
}
Expand All @@ -295,7 +281,7 @@ func (client *Client) SignedURL(route string, query url.Values, duration time.Du
// See:
// * https://docs.taskcluster.net/manual/apis/authorized-scopes
// * https://docs.taskcluster.net/manual/apis/temporary-credentials
func getExtHeader(credentials *Credentials) (header string, err error) {
func (credentials *Credentials) ExtHeader() (header string, err error) {
ext := &ExtHeader{}
if credentials.Certificate != "" {
certObj := new(Certificate)
Expand Down

0 comments on commit 1858a3c

Please sign in to comment.