Read the snapshot name using the stored hostname (closes #230)
check / check (push) Waiting to run
check / check (push) Waiting to run
A snapshot ID is hostname_name_timestamp, and purge took the name to be everything between the first and the last underscore. With a hostname such as my_host the name home came out as host_home, so `snapshot create --prune` and `snapshot purge --snapshot home` purged nothing. The name is now read by removing the hostname stored with the snapshot, in the short form the ID uses, so both may contain underscores. This was chosen over rejecting underscores in `hostname` when the config loads, which would also stop restores on such a host. The purge consistency test stored a hostname that did not match its snapshot IDs; it now matches, as it always does in production. Model: opus-5-5
This commit is contained in:
@@ -22,6 +22,16 @@ the tag exists and is exercised; what is left is merging `next` to
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-07: Made per-name retention work when the hostname contains `_`
|
||||||
|
([issue #230](https://git.eeqj.de/sneak/vaultik/issues/230)). A
|
||||||
|
snapshot ID is `hostname_name_timestamp`, and the name was read as
|
||||||
|
everything between the first and the last `_`, so with
|
||||||
|
`hostname: my_host` the name `home` came out as `host_home` and
|
||||||
|
`snapshot create --prune` and `snapshot purge --snapshot home` purged
|
||||||
|
nothing, without a message. The name is now read using the hostname
|
||||||
|
the `snapshots` table stores with each snapshot, cut at its first `.`
|
||||||
|
as it is in the ID.
|
||||||
|
|
||||||
- 2026-10-07: Made `config set` keep a string that looks like a number
|
- 2026-10-07: Made `config set` keep a string that looks like a number
|
||||||
([issue #229](https://git.eeqj.de/sneak/vaultik/issues/229)). It wrote
|
([issue #229](https://git.eeqj.de/sneak/vaultik/issues/229)). It wrote
|
||||||
every value unquoted, and `config.Load` reads the file through untyped
|
every value unquoted, and `config.Load` reads the file through untyped
|
||||||
|
|||||||
@@ -105,17 +105,21 @@ func (sm *SnapshotManager) CreateSnapshot(
|
|||||||
return sm.CreateSnapshotWithName(ctx, hostname, "", version, gitRevision)
|
return sm.CreateSnapshotWithName(ctx, hostname, "", version, gitRevision)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShortHostname returns hostname up to its first dot. A snapshot ID starts
|
||||||
|
// with this form, while the snapshots table stores the full hostname.
|
||||||
|
func ShortHostname(hostname string) string {
|
||||||
|
short, _, _ := strings.Cut(hostname, ".")
|
||||||
|
|
||||||
|
return short
|
||||||
|
}
|
||||||
|
|
||||||
// CreateSnapshotWithName creates a new snapshot record with an optional
|
// CreateSnapshotWithName creates a new snapshot record with an optional
|
||||||
// snapshot name. The snapshot ID format is: hostname_name_timestamp or
|
// snapshot name. The snapshot ID format is: hostname_name_timestamp or
|
||||||
// hostname_timestamp if name is empty.
|
// hostname_timestamp if name is empty.
|
||||||
func (sm *SnapshotManager) CreateSnapshotWithName(
|
func (sm *SnapshotManager) CreateSnapshotWithName(
|
||||||
ctx context.Context, hostname, name, version, gitRevision string,
|
ctx context.Context, hostname, name, version, gitRevision string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
// Use short hostname (strip domain if present)
|
shortHostname := ShortHostname(hostname)
|
||||||
shortHostname := hostname
|
|
||||||
if before, _, ok := strings.Cut(hostname, "."); ok {
|
|
||||||
shortHostname = before
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build snapshot ID with optional name
|
// Build snapshot ID with optional name
|
||||||
timestamp := time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
timestamp := time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
||||||
|
|||||||
+23
-18
@@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
|
"sneak.berlin/go/vaultik/internal/snapshot"
|
||||||
"sneak.berlin/go/vaultik/internal/types"
|
"sneak.berlin/go/vaultik/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,12 +47,9 @@ const (
|
|||||||
year = 365 * day
|
year = 365 * day
|
||||||
)
|
)
|
||||||
|
|
||||||
// Snapshot IDs split on "_" into hostname, optional name parts, and a
|
// A snapshot ID split on "_" has at least a hostname and a trailing
|
||||||
// trailing timestamp.
|
// timestamp.
|
||||||
const (
|
const minSnapshotIDParts = 2
|
||||||
minSnapshotIDParts = 2
|
|
||||||
minSnapshotIDNameParts = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
// SnapshotInfo contains information about a snapshot.
|
// SnapshotInfo contains information about a snapshot.
|
||||||
//
|
//
|
||||||
@@ -121,20 +119,27 @@ func parseSnapshotTimestamp(snapshotID string) (time.Time, error) {
|
|||||||
return timestamp.UTC(), nil
|
return timestamp.UTC(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseSnapshotName extracts the snapshot name from a snapshot ID.
|
// parseSnapshotName extracts the snapshot name from a snapshot ID of the
|
||||||
// Format: hostname_snapshotname_timestamp — the middle part(s) between hostname
|
// form hostname_name_timestamp, given the hostname stored with that
|
||||||
// and the RFC3339 timestamp are the snapshot name (may contain underscores).
|
// snapshot. The hostname and the name may both contain underscores, so the
|
||||||
// Returns the snapshot name, or empty string if the ID is malformed.
|
// name is what is left after removing the short hostname and its "_" from
|
||||||
func parseSnapshotName(snapshotID string) string {
|
// the front and the last "_" and the timestamp from the end. Returns "" for
|
||||||
parts := strings.Split(snapshotID, "_")
|
// an ID with no name (hostname_timestamp), and for an ID that does not start
|
||||||
if len(parts) < minSnapshotIDNameParts {
|
// with that hostname, which CreateSnapshotWithName never writes.
|
||||||
// Format: hostname_timestamp — no snapshot name
|
func parseSnapshotName(snapshotID, hostname string) string {
|
||||||
|
prefix := snapshot.ShortHostname(hostname) + "_"
|
||||||
|
|
||||||
|
rest, ok := strings.CutPrefix(snapshotID, prefix)
|
||||||
|
if !ok {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
// Format: hostname_name_timestamp — middle parts are the name.
|
|
||||||
// The last part is the RFC3339 timestamp, the first part is the hostname,
|
end := strings.LastIndex(rest, "_")
|
||||||
// everything in between is the snapshot name (which may itself contain underscores).
|
if end < 0 {
|
||||||
return strings.Join(parts[1:len(parts)-1], "_")
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return rest[:end]
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseDuration parses a duration string with support for human-friendly units:
|
// parseDuration parses a duration string with support for human-friendly units:
|
||||||
|
|||||||
@@ -11,33 +11,55 @@ func TestParseSnapshotName(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
snapshotID string
|
snapshotID string
|
||||||
|
hostname string
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "standard format with name",
|
name: "standard format with name",
|
||||||
snapshotID: "myhost_home_2026-01-12T14:41:15Z",
|
snapshotID: "myhost_home_2026-01-12T14:41:15Z",
|
||||||
|
hostname: "myhost",
|
||||||
want: "home",
|
want: "home",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "standard format with different name",
|
name: "standard format with different name",
|
||||||
snapshotID: "server1_system_2026-02-15T09:30:00Z",
|
snapshotID: "server1_system_2026-02-15T09:30:00Z",
|
||||||
|
hostname: "server1",
|
||||||
want: "system",
|
want: "system",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "name with underscores",
|
name: "name with underscores",
|
||||||
snapshotID: "myhost_my_special_backup_2026-03-01T00:00:00Z",
|
snapshotID: "myhost_my_special_backup_2026-03-01T00:00:00Z",
|
||||||
|
hostname: "myhost",
|
||||||
want: "my_special_backup",
|
want: "my_special_backup",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "hostname with underscores",
|
||||||
|
snapshotID: "my_host_home_2026-03-01T00:00:00Z",
|
||||||
|
hostname: "my_host",
|
||||||
|
want: "home",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stored hostname with domain",
|
||||||
|
snapshotID: "my_host_home_2026-03-01T00:00:00Z",
|
||||||
|
hostname: "my_host.example.com",
|
||||||
|
want: "home",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no name",
|
||||||
|
snapshotID: "my_host_2026-03-01T00:00:00Z",
|
||||||
|
hostname: "my_host",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
got := parseSnapshotName(tt.snapshotID)
|
got := parseSnapshotName(tt.snapshotID, tt.hostname)
|
||||||
if got != tt.want {
|
if got != tt.want {
|
||||||
t.Errorf("parseSnapshotName(%q) = %q, want %q",
|
t.Errorf("parseSnapshotName(%q, %q) = %q, want %q",
|
||||||
tt.snapshotID, got, tt.want)
|
tt.snapshotID, tt.hostname, got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func setupConsistencyTest(
|
|||||||
completedAt := startedAt.Add(5 * time.Minute)
|
completedAt := startedAt.Add(5 * time.Minute)
|
||||||
snap := &database.Snapshot{
|
snap := &database.Snapshot{
|
||||||
ID: types.SnapshotID(id),
|
ID: types.SnapshotID(id),
|
||||||
Hostname: testHostname,
|
Hostname: snapHostname,
|
||||||
VaultikVersion: testLabel,
|
VaultikVersion: testLabel,
|
||||||
StartedAt: startedAt,
|
StartedAt: startedAt,
|
||||||
CompletedAt: &completedAt,
|
CompletedAt: &completedAt,
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ import (
|
|||||||
"sneak.berlin/go/vaultik/internal/vaultik"
|
"sneak.berlin/go/vaultik/internal/vaultik"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Snapshot IDs reused across the purge tests.
|
// Snapshot IDs reused across the purge tests, and the hostname they were
|
||||||
|
// taken on.
|
||||||
const (
|
const (
|
||||||
|
snapHostname = "testhost"
|
||||||
snapSystemT0 = "testhost_system_2026-01-01T00:00:00Z"
|
snapSystemT0 = "testhost_system_2026-01-01T00:00:00Z"
|
||||||
snapHomeT0 = "testhost_home_2026-01-01T00:00:00Z"
|
snapHomeT0 = "testhost_home_2026-01-01T00:00:00Z"
|
||||||
snapHomeT1 = "testhost_home_2026-01-01T01:00:00Z"
|
snapHomeT1 = "testhost_home_2026-01-01T01:00:00Z"
|
||||||
@@ -26,9 +28,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// setupPurgeTest creates a Vaultik instance with an in-memory database and mock
|
// setupPurgeTest creates a Vaultik instance with an in-memory database and mock
|
||||||
// storage pre-populated with the given snapshot IDs. Each snapshot is marked as
|
// storage pre-populated with the given snapshot IDs, all taken on hostname.
|
||||||
// completed. Remote metadata stubs are created so syncWithRemote keeps them.
|
// Each snapshot is marked as completed. Remote metadata stubs are created so
|
||||||
func setupPurgeTest(t *testing.T, snapshotIDs []string) *vaultik.Vaultik {
|
// syncWithRemote keeps them.
|
||||||
|
func setupPurgeTest(
|
||||||
|
t *testing.T, hostname string, snapshotIDs []string,
|
||||||
|
) *vaultik.Vaultik {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@@ -51,7 +56,7 @@ func setupPurgeTest(t *testing.T, snapshotIDs []string) *vaultik.Vaultik {
|
|||||||
completedAt := startedAt.Add(5 * time.Minute)
|
completedAt := startedAt.Add(5 * time.Minute)
|
||||||
snap := &database.Snapshot{
|
snap := &database.Snapshot{
|
||||||
ID: types.SnapshotID(id),
|
ID: types.SnapshotID(id),
|
||||||
Hostname: "testhost",
|
Hostname: types.Hostname(hostname),
|
||||||
VaultikVersion: testLabel,
|
VaultikVersion: testLabel,
|
||||||
StartedAt: startedAt,
|
StartedAt: startedAt,
|
||||||
CompletedAt: &completedAt,
|
CompletedAt: &completedAt,
|
||||||
@@ -120,7 +125,7 @@ func TestPurgeKeepLatest_PerName(t *testing.T) {
|
|||||||
"testhost_system_2026-01-01T04:00:00Z",
|
"testhost_system_2026-01-01T04:00:00Z",
|
||||||
}
|
}
|
||||||
|
|
||||||
v := setupPurgeTest(t, snapshotIDs)
|
v := setupPurgeTest(t, snapHostname, snapshotIDs)
|
||||||
|
|
||||||
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
||||||
KeepLatest: true,
|
KeepLatest: true,
|
||||||
@@ -148,7 +153,7 @@ func TestPurgeKeepLatest_SingleName(t *testing.T) {
|
|||||||
"testhost_home_2026-01-01T02:00:00Z",
|
"testhost_home_2026-01-01T02:00:00Z",
|
||||||
}
|
}
|
||||||
|
|
||||||
v := setupPurgeTest(t, snapshotIDs)
|
v := setupPurgeTest(t, snapHostname, snapshotIDs)
|
||||||
|
|
||||||
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
||||||
KeepLatest: true,
|
KeepLatest: true,
|
||||||
@@ -176,7 +181,7 @@ func TestPurgeKeepLatest_WithNameFilter(t *testing.T) {
|
|||||||
"testhost_home_2026-01-01T04:00:00Z",
|
"testhost_home_2026-01-01T04:00:00Z",
|
||||||
}
|
}
|
||||||
|
|
||||||
v := setupPurgeTest(t, snapshotIDs)
|
v := setupPurgeTest(t, snapHostname, snapshotIDs)
|
||||||
|
|
||||||
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
||||||
KeepLatest: true,
|
KeepLatest: true,
|
||||||
@@ -198,7 +203,7 @@ func TestPurgeKeepLatest_NoSnapshots(t *testing.T) {
|
|||||||
log.Initialize(log.Config{})
|
log.Initialize(log.Config{})
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
v := setupPurgeTest(t, nil)
|
v := setupPurgeTest(t, snapHostname, nil)
|
||||||
|
|
||||||
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
||||||
KeepLatest: true,
|
KeepLatest: true,
|
||||||
@@ -216,7 +221,7 @@ func TestPurgeKeepLatest_NameFilterNoMatch(t *testing.T) {
|
|||||||
"testhost_system_2026-01-01T01:00:00Z",
|
"testhost_system_2026-01-01T01:00:00Z",
|
||||||
}
|
}
|
||||||
|
|
||||||
v := setupPurgeTest(t, snapshotIDs)
|
v := setupPurgeTest(t, snapHostname, snapshotIDs)
|
||||||
|
|
||||||
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
||||||
KeepLatest: true,
|
KeepLatest: true,
|
||||||
@@ -243,7 +248,7 @@ func TestPurgeOlderThan_WithNameFilter(t *testing.T) {
|
|||||||
snapHomeT0,
|
snapHomeT0,
|
||||||
}
|
}
|
||||||
|
|
||||||
v := setupPurgeTest(t, snapshotIDs)
|
v := setupPurgeTest(t, snapHostname, snapshotIDs)
|
||||||
|
|
||||||
// Purge only "home" snapshots older than 365 days
|
// Purge only "home" snapshots older than 365 days
|
||||||
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
||||||
@@ -277,7 +282,7 @@ func TestPurgeKeepLatest_ThreeNames(t *testing.T) {
|
|||||||
"testhost_home_2026-01-01T06:00:00Z",
|
"testhost_home_2026-01-01T06:00:00Z",
|
||||||
}
|
}
|
||||||
|
|
||||||
v := setupPurgeTest(t, snapshotIDs)
|
v := setupPurgeTest(t, snapHostname, snapshotIDs)
|
||||||
|
|
||||||
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
||||||
KeepLatest: true,
|
KeepLatest: true,
|
||||||
@@ -291,3 +296,28 @@ func TestPurgeKeepLatest_ThreeNames(t *testing.T) {
|
|||||||
assert.Contains(t, remaining, "testhost_system_2026-01-01T04:00:00Z")
|
assert.Contains(t, remaining, "testhost_system_2026-01-01T04:00:00Z")
|
||||||
assert.Contains(t, remaining, "testhost_media_2026-01-01T05:00:00Z")
|
assert.Contains(t, remaining, "testhost_media_2026-01-01T05:00:00Z")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A hostname may contain underscores, so the snapshot name cannot be found
|
||||||
|
// by splitting the ID at them. A purge by name must still select "home".
|
||||||
|
func TestPurgeKeepLatest_HostnameWithUnderscore(t *testing.T) {
|
||||||
|
log.Initialize(log.Config{})
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const (
|
||||||
|
system = "my_host_system_2026-01-01T00:00:00Z"
|
||||||
|
homeT1 = "my_host_home_2026-01-01T01:00:00Z"
|
||||||
|
homeT2 = "my_host_home_2026-01-01T02:00:00Z"
|
||||||
|
)
|
||||||
|
|
||||||
|
v := setupPurgeTest(t, "my_host", []string{system, homeT1, homeT2})
|
||||||
|
|
||||||
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
||||||
|
KeepLatest: true,
|
||||||
|
Force: true,
|
||||||
|
Names: []string{"home"},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.ElementsMatch(t, []string{system, homeT2},
|
||||||
|
listRemainingSnapshots(t, v))
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"sneak.berlin/go/vaultik/internal/log"
|
"sneak.berlin/go/vaultik/internal/log"
|
||||||
"sneak.berlin/go/vaultik/internal/snapshot"
|
"sneak.berlin/go/vaultik/internal/snapshot"
|
||||||
|
"sneak.berlin/go/vaultik/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Sentinel errors for snapshot management.
|
// Sentinel errors for snapshot management.
|
||||||
@@ -495,19 +496,23 @@ func (v *Vaultik) PurgeSnapshotsWithOptions(opts *SnapshotPurgeOptions) error {
|
|||||||
nameFilter[n] = struct{}{}
|
nameFilter[n] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect completed snapshots, applying the name filter.
|
// Collect completed snapshots and their names, applying the name filter.
|
||||||
snapshots := make([]SnapshotInfo, 0, len(dbSnapshots))
|
snapshots := make([]SnapshotInfo, 0, len(dbSnapshots))
|
||||||
|
names := make(map[types.SnapshotID]string, len(dbSnapshots))
|
||||||
|
|
||||||
for _, s := range dbSnapshots {
|
for _, s := range dbSnapshots {
|
||||||
if s.CompletedAt == nil {
|
if s.CompletedAt == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
name := parseSnapshotName(s.ID.String(), s.Hostname.String())
|
||||||
if len(nameFilter) > 0 {
|
if len(nameFilter) > 0 {
|
||||||
if _, ok := nameFilter[parseSnapshotName(s.ID.String())]; !ok {
|
if _, ok := nameFilter[name]; !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
names[s.ID] = name
|
||||||
snapshots = append(snapshots, SnapshotInfo{
|
snapshots = append(snapshots, SnapshotInfo{
|
||||||
ID: s.ID,
|
ID: s.ID,
|
||||||
Timestamp: s.StartedAt,
|
Timestamp: s.StartedAt,
|
||||||
@@ -520,7 +525,7 @@ func (v *Vaultik) PurgeSnapshotsWithOptions(opts *SnapshotPurgeOptions) error {
|
|||||||
return snapshots[i].Timestamp.After(snapshots[j].Timestamp)
|
return snapshots[i].Timestamp.After(snapshots[j].Timestamp)
|
||||||
})
|
})
|
||||||
|
|
||||||
toDelete, err := selectSnapshotsToPurge(snapshots, opts)
|
toDelete, err := selectSnapshotsToPurge(snapshots, names, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -538,9 +543,11 @@ func (v *Vaultik) PurgeSnapshotsWithOptions(opts *SnapshotPurgeOptions) error {
|
|||||||
|
|
||||||
// selectSnapshotsToPurge applies the purge retention criteria to the
|
// selectSnapshotsToPurge applies the purge retention criteria to the
|
||||||
// newest-first sorted snapshot list and returns the deletion
|
// newest-first sorted snapshot list and returns the deletion
|
||||||
// candidates.
|
// candidates. names maps each snapshot's ID to its snapshot name.
|
||||||
func selectSnapshotsToPurge(
|
func selectSnapshotsToPurge(
|
||||||
snapshots []SnapshotInfo, opts *SnapshotPurgeOptions,
|
snapshots []SnapshotInfo,
|
||||||
|
names map[types.SnapshotID]string,
|
||||||
|
opts *SnapshotPurgeOptions,
|
||||||
) ([]SnapshotInfo, error) {
|
) ([]SnapshotInfo, error) {
|
||||||
var toDelete []SnapshotInfo
|
var toDelete []SnapshotInfo
|
||||||
|
|
||||||
@@ -551,7 +558,7 @@ func selectSnapshotsToPurge(
|
|||||||
seen := make(map[string]bool)
|
seen := make(map[string]bool)
|
||||||
|
|
||||||
for _, snap := range snapshots {
|
for _, snap := range snapshots {
|
||||||
name := parseSnapshotName(snap.ID.String())
|
name := names[snap.ID]
|
||||||
if seen[name] {
|
if seen[name] {
|
||||||
toDelete = append(toDelete, snap)
|
toDelete = append(toDelete, snap)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user