mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-03 11:45:20 +00:00
Merge branch 'main' into 9097
This commit is contained in:
@@ -18,6 +18,7 @@ package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -46,6 +47,104 @@ const (
|
||||
bslValidationEnqueuePeriod = 10 * time.Second
|
||||
)
|
||||
|
||||
// sanitizeStorageError cleans up verbose HTTP responses from cloud provider errors,
|
||||
// particularly Azure which includes full HTTP response details and XML in error messages.
|
||||
// It extracts the error code and message while removing HTTP headers and response bodies.
|
||||
// It also scrubs sensitive information like SAS tokens from URLs.
|
||||
func sanitizeStorageError(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
errMsg := err.Error()
|
||||
|
||||
// Scrub sensitive information from URLs (SAS tokens, credentials, etc.)
|
||||
// Azure SAS token parameters: sig, se, st, sp, spr, sv, sr, sip, srt, ss
|
||||
// These appear as query parameters in URLs like: ?sig=value&se=value
|
||||
sasParamsRegex := regexp.MustCompile(`([?&])(sig|se|st|sp|spr|sv|sr|sip|srt|ss)=([^&\s<>\n]+)`)
|
||||
errMsg = sasParamsRegex.ReplaceAllString(errMsg, `${1}${2}=***REDACTED***`)
|
||||
|
||||
// Check if this looks like an Azure HTTP response error
|
||||
// Azure errors contain patterns like "RESPONSE 404:" and "ERROR CODE:"
|
||||
if !strings.Contains(errMsg, "RESPONSE") || !strings.Contains(errMsg, "ERROR CODE:") {
|
||||
// Not an Azure-style error, return as-is
|
||||
return errMsg
|
||||
}
|
||||
|
||||
// Extract the error code (e.g., "ContainerNotFound", "BlobNotFound")
|
||||
errorCodeRegex := regexp.MustCompile(`ERROR CODE:\s*(\w+)`)
|
||||
errorCodeMatch := errorCodeRegex.FindStringSubmatch(errMsg)
|
||||
var errorCode string
|
||||
if len(errorCodeMatch) > 1 {
|
||||
errorCode = errorCodeMatch[1]
|
||||
}
|
||||
|
||||
// Extract the error message from the XML or plain text
|
||||
// Look for message between <Message> tags or after "RESPONSE XXX:"
|
||||
var errorMessage string
|
||||
|
||||
// Try to extract from XML first
|
||||
messageRegex := regexp.MustCompile(`<Message>(.*?)</Message>`)
|
||||
messageMatch := messageRegex.FindStringSubmatch(errMsg)
|
||||
if len(messageMatch) > 1 {
|
||||
errorMessage = messageMatch[1]
|
||||
// Remove RequestId and Time from the message
|
||||
if idx := strings.Index(errorMessage, "\nRequestId:"); idx != -1 {
|
||||
errorMessage = errorMessage[:idx]
|
||||
}
|
||||
} else {
|
||||
// Try to extract from plain text response (e.g., "RESPONSE 404: 404 The specified container does not exist.")
|
||||
responseRegex := regexp.MustCompile(`RESPONSE\s+\d+:\s+\d+\s+([^\n]+)`)
|
||||
responseMatch := responseRegex.FindStringSubmatch(errMsg)
|
||||
if len(responseMatch) > 1 {
|
||||
errorMessage = strings.TrimSpace(responseMatch[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Build a clean error message
|
||||
var cleanMsg string
|
||||
if errorCode != "" && errorMessage != "" {
|
||||
cleanMsg = errorCode + ": " + errorMessage
|
||||
} else if errorCode != "" {
|
||||
cleanMsg = errorCode
|
||||
} else if errorMessage != "" {
|
||||
cleanMsg = errorMessage
|
||||
} else {
|
||||
// Fallback: try to extract the desc part from gRPC error
|
||||
descRegex := regexp.MustCompile(`desc\s*=\s*(.+)`)
|
||||
descMatch := descRegex.FindStringSubmatch(errMsg)
|
||||
if len(descMatch) > 1 {
|
||||
// Take everything up to the first newline or "RESPONSE" marker
|
||||
desc := descMatch[1]
|
||||
if idx := strings.Index(desc, "\n"); idx != -1 {
|
||||
desc = desc[:idx]
|
||||
}
|
||||
if idx := strings.Index(desc, "RESPONSE"); idx != -1 {
|
||||
desc = strings.TrimSpace(desc[:idx])
|
||||
}
|
||||
cleanMsg = desc
|
||||
} else {
|
||||
// Last resort: return first line
|
||||
if idx := strings.Index(errMsg, "\n"); idx != -1 {
|
||||
cleanMsg = errMsg[:idx]
|
||||
} else {
|
||||
cleanMsg = errMsg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve the prefix part of the error (e.g., "rpc error: code = Unknown desc = ")
|
||||
// but replace the verbose description with our clean message
|
||||
if strings.Contains(errMsg, "desc = ") {
|
||||
parts := strings.SplitN(errMsg, "desc = ", 2)
|
||||
if len(parts) == 2 {
|
||||
return parts[0] + "desc = " + cleanMsg
|
||||
}
|
||||
}
|
||||
|
||||
return cleanMsg
|
||||
}
|
||||
|
||||
// BackupStorageLocationReconciler reconciles a BackupStorageLocation object
|
||||
type backupStorageLocationReconciler struct {
|
||||
ctx context.Context
|
||||
@@ -125,9 +224,9 @@ func (r *backupStorageLocationReconciler) Reconcile(ctx context.Context, req ctr
|
||||
if err != nil {
|
||||
log.Info("BackupStorageLocation is invalid, marking as unavailable")
|
||||
err = errors.Wrapf(err, "BackupStorageLocation %q is unavailable", location.Name)
|
||||
unavailableErrors = append(unavailableErrors, err.Error())
|
||||
unavailableErrors = append(unavailableErrors, sanitizeStorageError(err))
|
||||
location.Status.Phase = velerov1api.BackupStorageLocationPhaseUnavailable
|
||||
location.Status.Message = err.Error()
|
||||
location.Status.Message = sanitizeStorageError(err)
|
||||
} else {
|
||||
log.Info("BackupStorageLocations is valid, marking as available")
|
||||
location.Status.Phase = velerov1api.BackupStorageLocationPhaseAvailable
|
||||
|
||||
Reference in New Issue
Block a user