Merge branch 'main' into fix/backup-name-validation

This commit is contained in:
R4mbo
2026-08-25 22:26:42 +05:30
committed by GitHub
168 changed files with 4336 additions and 741 deletions
+13 -2
View File
@@ -416,8 +416,19 @@ func ParseOrderedResources(orderMapStr string) (map[string]string, error) {
return nil, fmt.Errorf("invalid OrderedResources '%s'", entry)
}
kind := strings.TrimSpace(kv[0])
order := strings.TrimSpace(kv[1])
orderedResources[kind] = order
orderParts := strings.Split(kv[1], ",")
cleaned := make([]string, 0, len(orderParts))
for _, part := range orderParts {
name := strings.TrimSpace(part)
if name == "" {
continue
}
cleaned = append(cleaned, name)
}
if kind == "" || len(cleaned) == 0 {
return nil, fmt.Errorf("invalid OrderedResources '%s'", entry)
}
orderedResources[kind] = strings.Join(cleaned, ",")
}
return orderedResources, nil
}
+8
View File
@@ -234,6 +234,14 @@ func TestCreateOptions_OrderedResources(t *testing.T) {
"persistentvolumes": "pv1,pv2",
}
assert.Equal(t, expectedMixedResources, orderedResources)
// Spaces after commas in the resource list must be trimmed.
orderedResources, err = ParseOrderedResources("pods=ns1/p1, ns1/p2 ; persistentvolumeclaims= ns2/pvc1, ns2/pvc2")
require.NoError(t, err)
assert.Equal(t, map[string]string{
"pods": "ns1/p1,ns1/p2",
"persistentvolumeclaims": "ns2/pvc1,ns2/pvc2",
}, orderedResources)
}
func TestCreateCommand(t *testing.T) {
+1 -1
View File
@@ -111,7 +111,7 @@ func NewAddCommand(f client.Factory) *cobra.Command {
}
// add the plugin as an init container
plugin := *builder.ForPluginContainer(args[0], corev1api.PullPolicy(imagePullPolicyFlag.String())).Result()
plugin := *builder.ForPluginContainer(args[0], corev1api.PullPolicy(imagePullPolicyFlag.String()), veleroDeploy.Spec.Template.Spec.InitContainers).Result()
veleroDeploy.Spec.Template.Spec.InitContainers = append(veleroDeploy.Spec.Template.Spec.InitContainers, plugin)
-6
View File
@@ -57,13 +57,11 @@ var resToDelete = []kbclient.ObjectList{}
// uninstallOptions collects all the options for uninstalling Velero from a Kubernetes cluster.
type uninstallOptions struct {
wait bool // deprecated
force bool
}
// BindFlags adds command line values to the options struct.
func (o *uninstallOptions) BindFlags(flags *pflag.FlagSet) {
flags.BoolVar(&o.wait, "wait", o.wait, "Wait for Velero uninstall to be ready. Optional. Deprecated.")
flags.BoolVar(&o.force, "force", o.force, "Forces the Velero uninstall. Optional.")
}
@@ -81,10 +79,6 @@ Use '--force' to skip the prompt confirming if you want to uninstall Velero.
`,
Example: ` # velero uninstall --namespace staging`,
Run: func(c *cobra.Command, args []string) {
if o.wait {
fmt.Println("Warning: the \"--wait\" option is deprecated and will be removed in a future release. The uninstall command always waits for the uninstall to complete.")
}
// Confirm if not asked to force-skip confirmation
if !o.force {
fmt.Println("You are about to uninstall Velero.")
+13 -1
View File
@@ -28,6 +28,11 @@ const (
defaultPodVolumeOperationTimeout = 240 * time.Minute
defaultResourceTerminatingTimeout = 10 * time.Minute
// DefaultResourceTimeout is the default for --resource-timeout. It matches
// defaultResourceTerminatingTimeout so controller fallbacks stay aligned with
// server defaults (see pkg/cmd/server/config/config.go).
DefaultResourceTimeout = defaultResourceTerminatingTimeout
// server's client default qps and burst
defaultClientQPS float32 = 100.0
defaultClientBurst int = 100
@@ -41,7 +46,7 @@ const (
defaultCSISnapshotTimeout = 10 * time.Minute
defaultItemOperationTimeout = 4 * time.Hour
resourceTimeout = 10 * time.Minute
resourceTimeout = defaultResourceTerminatingTimeout
defaultMaxConcurrentK8SConnections = 30
defaultDisableInformerCache = false
@@ -183,6 +188,7 @@ type Config struct {
ConcurrentBackups int
GlobalBackupVolumePoliciesConfigMap string
DefaultResourceModifierConfigMap string
MaxBackupExtractionSize int
}
func GetDefaultConfig() *Config {
@@ -289,4 +295,10 @@ func (c *Config) BindFlags(flags *pflag.FlagSet) {
c.DefaultResourceModifierConfigMap,
"The name of a ConfigMap in the Velero namespace containing default resource modifier rules applied to all restores. Ignored when a per-restore resource modifier is specified.",
)
flags.IntVar(
&c.MaxBackupExtractionSize,
"max-backup-extraction-size",
c.MaxBackupExtractionSize,
"Maximum size of a backup extraction in megabytes. If not set, default value (16GB) will be used.",
)
}
+6
View File
@@ -61,6 +61,7 @@ import (
"github.com/vmware-tanzu/velero/internal/storage"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov2alpha1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
"github.com/vmware-tanzu/velero/pkg/archive"
"github.com/vmware-tanzu/velero/pkg/backup"
"github.com/vmware-tanzu/velero/pkg/buildinfo"
"github.com/vmware-tanzu/velero/pkg/client"
@@ -935,6 +936,11 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
}
}
if s.config.MaxBackupExtractionSize > 0 {
s.logger.Infof("Setting backup data extraction cap as %v MB", s.config.MaxBackupExtractionSize)
archive.SetMaxExtractionSize(int64(s.config.MaxBackupExtractionSize) * 1024 * 1024)
}
s.logger.Info("Server starting...")
if err := s.mgr.Start(s.ctx); err != nil {
@@ -40,6 +40,12 @@ import (
// not found
var ErrNotFound = errors.New("file not found")
var ErrDownloadRequestDownloadURLTimeout = errors.New("download request download url timeout, check velero server logs for errors. backup storage location may not be available")
var unzipLimit int64 = 1024 * 1024 * 1024 // 1GB limit
// ErrDownloadRequestFailed is returned when the server refused the request and gave no
// reason. The controller sets a message in every path that fails today, so this is a
// fallback rather than the usual case.
var ErrDownloadRequestFailed = errors.New("download request failed, check velero server logs for errors")
func Stream(
ctx context.Context,
@@ -114,6 +120,16 @@ func getDownloadURL(
if updated.Status.DownloadURL != "" {
return updated.Status.DownloadURL, nil
}
// Failed is terminal. Waiting for a URL that will never be signed would end in
// ErrDownloadRequestDownloadURLTimeout, which blames the storage location for
// something the status already explains.
if updated.Status.Phase == veleroV1api.DownloadRequestPhaseFailed {
if updated.Status.Message != "" {
return "", errors.New(updated.Status.Message)
}
return "", ErrDownloadRequestFailed
}
}
}
}
@@ -202,17 +218,35 @@ func download(
return errors.Errorf("request failed: %v", string(body))
}
reader := resp.Body
var r io.Reader = resp.Body
var gzipReader *gzip.Reader
if kind != veleroV1api.DownloadTargetKindBackupContents {
// need to decompress logs
gzipReader, err := gzip.NewReader(resp.Body)
var err error
gzipReader, err = gzip.NewReader(resp.Body)
if err != nil {
return err
}
defer gzipReader.Close()
reader = gzipReader
r = io.LimitReader(gzipReader, unzipLimit)
}
_, err = io.Copy(w, reader)
return err
_, err = io.Copy(w, r)
if err != nil {
return err
}
if gzipReader != nil {
var buf [1]byte
n, err := gzipReader.Read(buf[:])
if n > 0 || err == nil {
return errors.Errorf("decompressed data exceeds the limit")
}
if err != io.EOF {
return err
}
}
return nil
}
@@ -463,6 +463,7 @@ func TestDownload(t *testing.T) {
expectedContent string
expectedError bool
errorType error
expectedErrMsg string
}{
{
name: "successful download with gzip for logs",
@@ -474,6 +475,16 @@ func TestDownload(t *testing.T) {
expectedContent: testContent,
expectedError: false,
},
{
name: "error decompressed data exceeds the limit",
serverHandler: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write(compressedContent.Bytes())
},
target: velerov1api.DownloadTargetKindBackupLog,
expectedError: true,
expectedErrMsg: "decompressed data exceeds the limit",
},
{
name: "successful download without gzip for backup contents",
serverHandler: func(w http.ResponseWriter, r *http.Request) {
@@ -506,6 +517,12 @@ func TestDownload(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
originalLimit := unzipLimit
if tc.expectedErrMsg == "decompressed data exceeds the limit" {
unzipLimit = 10
}
defer func() { unzipLimit = originalLimit }()
server := httptest.NewServer(tc.serverHandler)
defer server.Close()
@@ -525,6 +542,9 @@ func TestDownload(t *testing.T) {
if tc.errorType != nil {
assert.Equal(t, tc.errorType, err)
}
if tc.expectedErrMsg != "" {
assert.Contains(t, err.Error(), tc.expectedErrMsg)
}
} else {
require.NoError(t, err)
assert.Equal(t, tc.expectedContent, buf.String())
+6 -2
View File
@@ -90,8 +90,12 @@ func printBackup(backup *velerov1api.Backup) []metav1.TableRow {
if backup.Status.Expiration != nil {
expiration = backup.Status.Expiration.Time
}
if expiration.IsZero() && backup.Spec.TTL.Duration > 0 {
expiration = backup.CreationTimestamp.Add(backup.Spec.TTL.Duration)
// Only estimate expiration from TTL after the backup has started. Backups
// stalled in New have no Status.Expiration yet; using CreationTimestamp
// would incorrectly show them as already expired (issue #3555).
if expiration.IsZero() && backup.Spec.TTL.Duration > 0 &&
backup.Status.StartTimestamp != nil && !backup.Status.StartTimestamp.Time.IsZero() {
expiration = backup.Status.StartTimestamp.Time.Add(backup.Spec.TTL.Duration)
}
status := string(backup.Status.Phase)
@@ -76,6 +76,26 @@ func TestPrintBackupWithoutStartTimestamp(t *testing.T) {
assert.Equal(t, string(velerov1api.BackupPhaseFailedValidation), rows[0].Cells[1])
}
func TestPrintBackupExpiresForStalledNewBackup(t *testing.T) {
created := metav1.NewTime(time.Now().Add(-20 * 24 * time.Hour))
backup := &velerov1api.Backup{
ObjectMeta: metav1.ObjectMeta{
Name: "clusterstate-20210128123759",
CreationTimestamp: created,
},
Spec: velerov1api.BackupSpec{
TTL: metav1.Duration{Duration: 10 * 24 * time.Hour},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseNew,
},
}
rows := printBackup(backup)
require.Len(t, rows, 1)
assert.Equal(t, "n/a", rows[0].Cells[5], "stalled New backup should not show expiration in the past")
}
func TestPrintBackupWithStartTimestamp(t *testing.T) {
started := metav1.NewTime(time.Date(2026, 8, 8, 21, 6, 28, 0, time.UTC))
backup := &velerov1api.Backup{