Read the snapshot name using the stored hostname #261
@@ -22,6 +22,17 @@ 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`.
|
||||||
|
`snapshot purge --keep-latest --snapshot home` then printed "No
|
||||||
|
snapshots to delete", and `snapshot create --prune` 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 `remote info` stop reporting a snapshot's blobs as
|
- 2026-10-07: Made `remote info` stop reporting a snapshot's blobs as
|
||||||
orphaned when its manifest cannot be read, and stop printing raw
|
orphaned when its manifest cannot be read, and stop printing raw
|
||||||
names from under `metadata/`
|
names from under `metadata/`
|
||||||
|
|||||||
@@ -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_docs_2026-03-01T00:00:00Z",
|
||||||
|
hostname: "my_host",
|
||||||
|
want: "docs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stored hostname with domain",
|
||||||
|
snapshotID: "my_host_mail_2026-03-01T00:00:00Z",
|
||||||
|
hostname: "my_host.example.com",
|
||||||
|
want: "mail",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 "docs".
|
||||||
|
func TestPurgeKeepLatest_HostnameWithUnderscore(t *testing.T) {
|
||||||
|
log.Initialize(log.Config{})
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const (
|
||||||
|
system = "my_host_system_2026-01-01T00:00:00Z"
|
||||||
|
docsT1 = "my_host_docs_2026-01-01T01:00:00Z"
|
||||||
|
docsT2 = "my_host_docs_2026-01-01T02:00:00Z"
|
||||||
|
)
|
||||||
|
|
||||||
|
v := setupPurgeTest(t, "my_host", []string{system, docsT1, docsT2})
|
||||||
|
|
||||||
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
||||||
|
KeepLatest: true,
|
||||||
|
Force: true,
|
||||||
|
Names: []string{"docs"},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.ElementsMatch(t, []string{system, docsT2},
|
||||||
|
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