diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 6bd5526e..9d3213bf 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -16,23 +16,27 @@ }, { "ImportPath": "github.com/Azure/go-autorest/autorest", - "Rev": "bca49d5b51a50dc5bb17bbf6204c711c6dbded06" + "Rev": "a88c19ef2016e095f0b6c3b451074b4663f53bed" }, { "ImportPath": "github.com/Azure/go-autorest/autorest/adal", - "Rev": "bca49d5b51a50dc5bb17bbf6204c711c6dbded06" + "Rev": "a88c19ef2016e095f0b6c3b451074b4663f53bed" }, { "ImportPath": "github.com/Azure/go-autorest/autorest/azure", - "Rev": "bca49d5b51a50dc5bb17bbf6204c711c6dbded06" + "Rev": "a88c19ef2016e095f0b6c3b451074b4663f53bed" }, { "ImportPath": "github.com/Azure/go-autorest/autorest/date", - "Rev": "bca49d5b51a50dc5bb17bbf6204c711c6dbded06" + "Rev": "a88c19ef2016e095f0b6c3b451074b4663f53bed" + }, + { + "ImportPath": "github.com/Azure/go-autorest/logger", + "Rev": "a88c19ef2016e095f0b6c3b451074b4663f53bed" }, { "ImportPath": "github.com/Azure/go-autorest/version", - "Rev": "bca49d5b51a50dc5bb17bbf6204c711c6dbded06" + "Rev": "a88c19ef2016e095f0b6c3b451074b4663f53bed" }, { "ImportPath": "github.com/davecgh/go-spew/spew", diff --git a/vendor/github.com/Azure/go-autorest/autorest/azure/async.go b/vendor/github.com/Azure/go-autorest/autorest/azure/async.go index cda1e180..9dd7a1d2 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/azure/async.go +++ b/vendor/github.com/Azure/go-autorest/autorest/azure/async.go @@ -119,7 +119,10 @@ func (f *Future) Done(sender autorest.Sender) (bool, error) { if err := f.pt.updatePollingState(f.pt.provisioningStateApplicable()); err != nil { return false, err } - if err := f.pt.updateHeaders(); err != nil { + if err := f.pt.initPollingMethod(); err != nil { + return false, err + } + if err := f.pt.updatePollingMethod(); err != nil { return false, err } return f.pt.hasTerminated(), f.pt.pollingError() @@ -264,7 +267,7 @@ type pollingTracker interface { // these methods can differ per tracker // checks the response headers and status code to determine the polling mechanism - updateHeaders() error + updatePollingMethod() error // checks the response for tracker-specific error conditions checkForErrors() error @@ -274,6 +277,10 @@ type pollingTracker interface { // methods common to all trackers + // initializes a tracker's polling URL and method, called for each iteration. + // these values can be overridden by each polling tracker as required. + initPollingMethod() error + // initializes the tracker's internal state, call this when the tracker is created initializeState() error @@ -348,6 +355,10 @@ func (pt *pollingTrackerBase) initializeState() error { case http.StatusOK: if ps := pt.getProvisioningState(); ps != nil { pt.State = *ps + if pt.hasFailed() { + pt.updateErrorFromResponse() + return pt.pollingError() + } } else { pt.State = operationSucceeded } @@ -364,8 +375,9 @@ func (pt *pollingTrackerBase) initializeState() error { default: pt.State = operationFailed pt.updateErrorFromResponse() + return pt.pollingError() } - return nil + return pt.initPollingMethod() } func (pt pollingTrackerBase) getProvisioningState() *string { @@ -416,12 +428,14 @@ func (pt *pollingTrackerBase) pollForStatus(sender autorest.Sender) error { } else { // check response body for error content pt.updateErrorFromResponse() + err = pt.pollingError() } return err } // attempts to unmarshal a ServiceError type from the response body. // if that fails then make a best attempt at creating something meaningful. +// NOTE: this assumes that the async operation has failed. func (pt *pollingTrackerBase) updateErrorFromResponse() { var err error if pt.resp.ContentLength != 0 { @@ -431,8 +445,7 @@ func (pt *pollingTrackerBase) updateErrorFromResponse() { re := respErr{} defer pt.resp.Body.Close() var b []byte - b, err = ioutil.ReadAll(pt.resp.Body) - if err != nil { + if b, err = ioutil.ReadAll(pt.resp.Body); err != nil { goto Default } if err = json.Unmarshal(b, &re); err != nil { @@ -445,20 +458,29 @@ func (pt *pollingTrackerBase) updateErrorFromResponse() { goto Default } } - if re.ServiceError != nil { + // the unmarshaller will ensure re.ServiceError is non-nil + // even if there was no content unmarshalled so check the code. + if re.ServiceError.Code != "" { pt.Err = re.ServiceError return } } Default: se := &ServiceError{ - Code: fmt.Sprintf("HTTP status code %v", pt.resp.StatusCode), - Message: pt.resp.Status, + Code: pt.pollingStatus(), + Message: "The async operation failed.", } if err != nil { se.InnerError = make(map[string]interface{}) se.InnerError["unmarshalError"] = err.Error() } + // stick the response body into the error object in hopes + // it contains something useful to help diagnose the failure. + if len(pt.rawBody) > 0 { + se.AdditionalInfo = []map[string]interface{}{ + pt.rawBody, + } + } pt.Err = se } @@ -538,13 +560,33 @@ func (pt pollingTrackerBase) baseCheckForErrors() error { return nil } +// default initialization of polling URL/method. each verb tracker will update this as required. +func (pt *pollingTrackerBase) initPollingMethod() error { + if ao, err := getURLFromAsyncOpHeader(pt.resp); err != nil { + return err + } else if ao != "" { + pt.URI = ao + pt.Pm = PollingAsyncOperation + return nil + } + if lh, err := getURLFromLocationHeader(pt.resp); err != nil { + return err + } else if lh != "" { + pt.URI = lh + pt.Pm = PollingLocation + return nil + } + // it's ok if we didn't find a polling header, this will be handled elsewhere + return nil +} + // DELETE type pollingTrackerDelete struct { pollingTrackerBase } -func (pt *pollingTrackerDelete) updateHeaders() error { +func (pt *pollingTrackerDelete) updatePollingMethod() error { // for 201 the Location header is required if pt.resp.StatusCode == http.StatusCreated { if lh, err := getURLFromLocationHeader(pt.resp); err != nil { @@ -600,7 +642,7 @@ type pollingTrackerPatch struct { pollingTrackerBase } -func (pt *pollingTrackerPatch) updateHeaders() error { +func (pt *pollingTrackerPatch) updatePollingMethod() error { // by default we can use the original URL for polling and final GET if pt.URI == "" { pt.URI = pt.resp.Request.URL.String() @@ -658,7 +700,7 @@ type pollingTrackerPost struct { pollingTrackerBase } -func (pt *pollingTrackerPost) updateHeaders() error { +func (pt *pollingTrackerPost) updatePollingMethod() error { // 201 requires Location header if pt.resp.StatusCode == http.StatusCreated { if lh, err := getURLFromLocationHeader(pt.resp); err != nil { @@ -714,7 +756,7 @@ type pollingTrackerPut struct { pollingTrackerBase } -func (pt *pollingTrackerPut) updateHeaders() error { +func (pt *pollingTrackerPut) updatePollingMethod() error { // by default we can use the original URL for polling and final GET if pt.URI == "" { pt.URI = pt.resp.Request.URL.String() @@ -808,7 +850,7 @@ func createPollingTracker(resp *http.Response) (pollingTracker, error) { // this initializes the polling header values, we do this during creation in case the // initial response send us invalid values; this way the API call will return a non-nil // error (not doing this means the error shows up in Future.Done) - return pt, pt.updateHeaders() + return pt, pt.updatePollingMethod() } // gets the polling URL from the Azure-AsyncOperation header. diff --git a/vendor/github.com/Azure/go-autorest/autorest/client.go b/vendor/github.com/Azure/go-autorest/autorest/client.go index 467c1509..5c558c83 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/client.go +++ b/vendor/github.com/Azure/go-autorest/autorest/client.go @@ -22,8 +22,10 @@ import ( "log" "net/http" "net/http/cookiejar" + "strings" "time" + "github.com/Azure/go-autorest/logger" "github.com/Azure/go-autorest/version" ) @@ -208,8 +210,17 @@ func (c Client) Do(r *http.Request) (*http.Response, error) { } return resp, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed") } - + logger.Instance.WriteRequest(r, logger.Filter{ + Header: func(k string, v []string) (bool, []string) { + // remove the auth token from the log + if strings.EqualFold(k, "Authorization") || strings.EqualFold(k, "Ocp-Apim-Subscription-Key") { + v = []string{"**REDACTED**"} + } + return true, v + }, + }) resp, err := SendWithSender(c.sender(), r) + logger.Instance.WriteResponse(resp, logger.Filter{}) Respond(resp, c.ByInspecting()) return resp, err } diff --git a/vendor/github.com/Azure/go-autorest/logger/logger.go b/vendor/github.com/Azure/go-autorest/logger/logger.go new file mode 100644 index 00000000..756fd80c --- /dev/null +++ b/vendor/github.com/Azure/go-autorest/logger/logger.go @@ -0,0 +1,328 @@ +package logger + +// Copyright 2017 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "strings" + "sync" + "time" +) + +// LevelType tells a logger the minimum level to log. When code reports a log entry, +// the LogLevel indicates the level of the log entry. The logger only records entries +// whose level is at least the level it was told to log. See the Log* constants. +// For example, if a logger is configured with LogError, then LogError, LogPanic, +// and LogFatal entries will be logged; lower level entries are ignored. +type LevelType uint32 + +const ( + // LogNone tells a logger not to log any entries passed to it. + LogNone LevelType = iota + + // LogFatal tells a logger to log all LogFatal entries passed to it. + LogFatal + + // LogPanic tells a logger to log all LogPanic and LogFatal entries passed to it. + LogPanic + + // LogError tells a logger to log all LogError, LogPanic and LogFatal entries passed to it. + LogError + + // LogWarning tells a logger to log all LogWarning, LogError, LogPanic and LogFatal entries passed to it. + LogWarning + + // LogInfo tells a logger to log all LogInfo, LogWarning, LogError, LogPanic and LogFatal entries passed to it. + LogInfo + + // LogDebug tells a logger to log all LogDebug, LogInfo, LogWarning, LogError, LogPanic and LogFatal entries passed to it. + LogDebug +) + +const ( + logNone = "NONE" + logFatal = "FATAL" + logPanic = "PANIC" + logError = "ERROR" + logWarning = "WARNING" + logInfo = "INFO" + logDebug = "DEBUG" + logUnknown = "UNKNOWN" +) + +// ParseLevel converts the specified string into the corresponding LevelType. +func ParseLevel(s string) (lt LevelType, err error) { + switch strings.ToUpper(s) { + case logFatal: + lt = LogFatal + case logPanic: + lt = LogPanic + case logError: + lt = LogError + case logWarning: + lt = LogWarning + case logInfo: + lt = LogInfo + case logDebug: + lt = LogDebug + default: + err = fmt.Errorf("bad log level '%s'", s) + } + return +} + +// String implements the stringer interface for LevelType. +func (lt LevelType) String() string { + switch lt { + case LogNone: + return logNone + case LogFatal: + return logFatal + case LogPanic: + return logPanic + case LogError: + return logError + case LogWarning: + return logWarning + case LogInfo: + return logInfo + case LogDebug: + return logDebug + default: + return logUnknown + } +} + +// Filter defines functions for filtering HTTP request/response content. +type Filter struct { + // URL returns a potentially modified string representation of a request URL. + URL func(u *url.URL) string + + // Header returns a potentially modified set of values for the specified key. + // To completely exclude the header key/values return false. + Header func(key string, val []string) (bool, []string) + + // Body returns a potentially modified request/response body. + Body func(b []byte) []byte +} + +func (f Filter) processURL(u *url.URL) string { + if f.URL == nil { + return u.String() + } + return f.URL(u) +} + +func (f Filter) processHeader(k string, val []string) (bool, []string) { + if f.Header == nil { + return true, val + } + return f.Header(k, val) +} + +func (f Filter) processBody(b []byte) []byte { + if f.Body == nil { + return b + } + return f.Body(b) +} + +// Writer defines methods for writing to a logging facility. +type Writer interface { + // Writeln writes the specified message with the standard log entry header and new-line character. + Writeln(level LevelType, message string) + + // Writef writes the specified format specifier with the standard log entry header and no new-line character. + Writef(level LevelType, format string, a ...interface{}) + + // WriteRequest writes the specified HTTP request to the logger if the log level is greater than + // or equal to LogInfo. The request body, if set, is logged at level LogDebug or higher. + // Custom filters can be specified to exclude URL, header, and/or body content from the log. + // By default no request content is excluded. + WriteRequest(req *http.Request, filter Filter) + + // WriteResponse writes the specified HTTP response to the logger if the log level is greater than + // or equal to LogInfo. The response body, if set, is logged at level LogDebug or higher. + // Custom filters can be specified to exclude URL, header, and/or body content from the log. + // By default no respone content is excluded. + WriteResponse(resp *http.Response, filter Filter) +} + +// Instance is the default log writer initialized during package init. +// This can be replaced with a custom implementation as required. +var Instance Writer + +// default log level +var logLevel = LogNone + +// Level returns the value specified in AZURE_GO_AUTOREST_LOG_LEVEL. +// If no value was specified the default value is LogNone. +// Custom loggers can call this to retrieve the configured log level. +func Level() LevelType { + return logLevel +} + +func init() { + // separated for testing purposes + initDefaultLogger() +} + +func initDefaultLogger() { + // init with nilLogger so callers don't have to do a nil check on Default + Instance = nilLogger{} + llStr := strings.ToLower(os.Getenv("AZURE_GO_SDK_LOG_LEVEL")) + if llStr == "" { + return + } + var err error + logLevel, err = ParseLevel(llStr) + if err != nil { + fmt.Fprintf(os.Stderr, "go-autorest: failed to parse log level: %s\n", err.Error()) + return + } + if logLevel == LogNone { + return + } + // default to stderr + dest := os.Stderr + lfStr := os.Getenv("AZURE_GO_SDK_LOG_FILE") + if strings.EqualFold(lfStr, "stdout") { + dest = os.Stdout + } else if lfStr != "" { + lf, err := os.Create(lfStr) + if err == nil { + dest = lf + } else { + fmt.Fprintf(os.Stderr, "go-autorest: failed to create log file, using stderr: %s\n", err.Error()) + } + } + Instance = fileLogger{ + logLevel: logLevel, + mu: &sync.Mutex{}, + logFile: dest, + } +} + +// the nil logger does nothing +type nilLogger struct{} + +func (nilLogger) Writeln(LevelType, string) {} + +func (nilLogger) Writef(LevelType, string, ...interface{}) {} + +func (nilLogger) WriteRequest(*http.Request, Filter) {} + +func (nilLogger) WriteResponse(*http.Response, Filter) {} + +// A File is used instead of a Logger so the stream can be flushed after every write. +type fileLogger struct { + logLevel LevelType + mu *sync.Mutex // for synchronizing writes to logFile + logFile *os.File +} + +func (fl fileLogger) Writeln(level LevelType, message string) { + fl.Writef(level, "%s\n", message) +} + +func (fl fileLogger) Writef(level LevelType, format string, a ...interface{}) { + if fl.logLevel >= level { + fl.mu.Lock() + defer fl.mu.Unlock() + fmt.Fprintf(fl.logFile, "%s %s", entryHeader(level), fmt.Sprintf(format, a...)) + fl.logFile.Sync() + } +} + +func (fl fileLogger) WriteRequest(req *http.Request, filter Filter) { + if req == nil || fl.logLevel < LogInfo { + return + } + b := &bytes.Buffer{} + fmt.Fprintf(b, "%s REQUEST: %s %s\n", entryHeader(LogInfo), req.Method, filter.processURL(req.URL)) + // dump headers + for k, v := range req.Header { + if ok, mv := filter.processHeader(k, v); ok { + fmt.Fprintf(b, "%s: %s\n", k, strings.Join(mv, ",")) + } + } + if fl.shouldLogBody(req.Header, req.Body) { + // dump body + body, err := ioutil.ReadAll(req.Body) + if err == nil { + fmt.Fprintln(b, string(filter.processBody(body))) + if nc, ok := req.Body.(io.Seeker); ok { + // rewind to the beginning + nc.Seek(0, io.SeekStart) + } else { + // recreate the body + req.Body = ioutil.NopCloser(bytes.NewReader(body)) + } + } else { + fmt.Fprintf(b, "failed to read body: %v\n", err) + } + } + fl.mu.Lock() + defer fl.mu.Unlock() + fmt.Fprint(fl.logFile, b.String()) + fl.logFile.Sync() +} + +func (fl fileLogger) WriteResponse(resp *http.Response, filter Filter) { + if resp == nil || fl.logLevel < LogInfo { + return + } + b := &bytes.Buffer{} + fmt.Fprintf(b, "%s RESPONSE: %d %s\n", entryHeader(LogInfo), resp.StatusCode, filter.processURL(resp.Request.URL)) + // dump headers + for k, v := range resp.Header { + if ok, mv := filter.processHeader(k, v); ok { + fmt.Fprintf(b, "%s: %s\n", k, strings.Join(mv, ",")) + } + } + if fl.shouldLogBody(resp.Header, resp.Body) { + // dump body + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err == nil { + fmt.Fprintln(b, string(filter.processBody(body))) + resp.Body = ioutil.NopCloser(bytes.NewReader(body)) + } else { + fmt.Fprintf(b, "failed to read body: %v\n", err) + } + } + fl.mu.Lock() + defer fl.mu.Unlock() + fmt.Fprint(fl.logFile, b.String()) + fl.logFile.Sync() +} + +// returns true if the provided body should be included in the log +func (fl fileLogger) shouldLogBody(header http.Header, body io.ReadCloser) bool { + ct := header.Get("Content-Type") + return fl.logLevel >= LogDebug && body != nil && strings.Index(ct, "application/octet-stream") == -1 +} + +// creates standard header for log entries, it contains a timestamp and the log level +func entryHeader(level LevelType) string { + // this format provides a fixed number of digits so the size of the timestamp is constant + return fmt.Sprintf("(%s) %s:", time.Now().Format("2006-01-02T15:04:05.0000000Z07:00"), level.String()) +} diff --git a/vendor/github.com/Azure/go-autorest/version/version.go b/vendor/github.com/Azure/go-autorest/version/version.go index 10d1df64..ad2d6099 100644 --- a/vendor/github.com/Azure/go-autorest/version/version.go +++ b/vendor/github.com/Azure/go-autorest/version/version.go @@ -20,7 +20,7 @@ import ( ) // Number contains the semantic version of this SDK. -const Number = "v10.14.0" +const Number = "v10.15.4" var ( userAgent = fmt.Sprintf("Go/%s (%s-%s) go-autorest/%s",