721 lines
21 KiB
Go
721 lines
21 KiB
Go
/*
|
|
Copyright 2020, 2021 The Flux authors
|
|
|
|
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.
|
|
*/
|
|
|
|
package controllers
|
|
|
|
import (
|
|
"archive/tar"
|
|
"compress/gzip"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/fluxcd/go-git/v5/plumbing/format/gitignore"
|
|
. "github.com/onsi/gomega"
|
|
|
|
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
|
)
|
|
|
|
func TestStorageConstructor(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
if _, err := NewStorage("/nonexistent", "hostname", time.Minute, 2); err == nil {
|
|
t.Fatal("nonexistent path was allowable in storage constructor")
|
|
}
|
|
|
|
f, err := os.CreateTemp(dir, "")
|
|
if err != nil {
|
|
t.Fatalf("while creating temporary file: %v", err)
|
|
}
|
|
f.Close()
|
|
|
|
if _, err := NewStorage(f.Name(), "hostname", time.Minute, 2); err == nil {
|
|
os.Remove(f.Name())
|
|
t.Fatal("file path was accepted as basedir")
|
|
}
|
|
os.Remove(f.Name())
|
|
|
|
if _, err := NewStorage(dir, "hostname", time.Minute, 2); err != nil {
|
|
t.Fatalf("Valid path did not successfully return: %v", err)
|
|
}
|
|
}
|
|
|
|
// walks a tar.gz and looks for paths with the basename. It does not match
|
|
// symlinks properly at this time because that's painful.
|
|
func walkTar(tarFile string, match string, dir bool) (int64, int64, bool, error) {
|
|
f, err := os.Open(tarFile)
|
|
if err != nil {
|
|
return 0, 0, false, fmt.Errorf("could not open file: %w", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
gzr, err := gzip.NewReader(f)
|
|
if err != nil {
|
|
return 0, 0, false, fmt.Errorf("could not unzip file: %w", err)
|
|
}
|
|
defer gzr.Close()
|
|
|
|
tr := tar.NewReader(gzr)
|
|
for {
|
|
header, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return 0, 0, false, fmt.Errorf("corrupt tarball reading header: %w", err)
|
|
}
|
|
|
|
switch header.Typeflag {
|
|
case tar.TypeDir:
|
|
if header.Name == match && dir {
|
|
return 0, header.Mode, true, nil
|
|
}
|
|
case tar.TypeReg:
|
|
if header.Name == match {
|
|
return header.Size, header.Mode, true, nil
|
|
}
|
|
default:
|
|
// skip
|
|
}
|
|
}
|
|
|
|
return 0, 0, false, nil
|
|
}
|
|
|
|
func TestStorage_Archive(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
storage, err := NewStorage(dir, "hostname", time.Minute, 2)
|
|
if err != nil {
|
|
t.Fatalf("error while bootstrapping storage: %v", err)
|
|
}
|
|
|
|
createFiles := func(files map[string][]byte) (dir string, err error) {
|
|
dir = t.TempDir()
|
|
for name, b := range files {
|
|
absPath := filepath.Join(dir, name)
|
|
if err = os.MkdirAll(filepath.Dir(absPath), 0o750); err != nil {
|
|
return
|
|
}
|
|
f, err := os.Create(absPath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("could not create file %q: %w", absPath, err)
|
|
}
|
|
if n, err := f.Write(b); err != nil {
|
|
f.Close()
|
|
return "", fmt.Errorf("could not write %d bytes to file %q: %w", n, f.Name(), err)
|
|
}
|
|
f.Close()
|
|
}
|
|
return
|
|
}
|
|
|
|
matchFiles := func(t *testing.T, storage *Storage, artifact sourcev1.Artifact, files map[string][]byte, dirs []string) {
|
|
t.Helper()
|
|
for name, b := range files {
|
|
mustExist := !(name[0:1] == "!")
|
|
if !mustExist {
|
|
name = name[1:]
|
|
}
|
|
s, m, exist, err := walkTar(storage.LocalPath(artifact), name, false)
|
|
if err != nil {
|
|
t.Fatalf("failed reading tarball: %v", err)
|
|
}
|
|
if bs := int64(len(b)); s != bs {
|
|
t.Fatalf("%q size %v != %v", name, s, bs)
|
|
}
|
|
if exist != mustExist {
|
|
if mustExist {
|
|
t.Errorf("could not find file %q in tarball", name)
|
|
} else {
|
|
t.Errorf("tarball contained excluded file %q", name)
|
|
}
|
|
}
|
|
if exist && m != defaultFileMode {
|
|
t.Fatalf("%q mode %v != %v", name, m, defaultFileMode)
|
|
}
|
|
}
|
|
for _, name := range dirs {
|
|
mustExist := !(name[0:1] == "!")
|
|
if !mustExist {
|
|
name = name[1:]
|
|
}
|
|
_, m, exist, err := walkTar(storage.LocalPath(artifact), name, true)
|
|
if err != nil {
|
|
t.Fatalf("failed reading tarball: %v", err)
|
|
}
|
|
if exist != mustExist {
|
|
if mustExist {
|
|
t.Errorf("could not find dir %q in tarball", name)
|
|
} else {
|
|
t.Errorf("tarball contained excluded file %q", name)
|
|
}
|
|
}
|
|
if exist && m != defaultDirMode {
|
|
t.Fatalf("%q mode %v != %v", name, m, defaultDirMode)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
files map[string][]byte
|
|
filter ArchiveFileFilter
|
|
want map[string][]byte
|
|
wantDirs []string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "no filter",
|
|
files: map[string][]byte{
|
|
".git/config": nil,
|
|
"file.jpg": []byte(`contents`),
|
|
"manifest.yaml": nil,
|
|
},
|
|
filter: nil,
|
|
want: map[string][]byte{
|
|
".git/config": nil,
|
|
"file.jpg": []byte(`contents`),
|
|
"manifest.yaml": nil,
|
|
},
|
|
},
|
|
{
|
|
name: "exclude VCS",
|
|
files: map[string][]byte{
|
|
".git/config": nil,
|
|
"manifest.yaml": nil,
|
|
},
|
|
wantDirs: []string{
|
|
"!.git",
|
|
},
|
|
filter: SourceIgnoreFilter(nil, nil),
|
|
want: map[string][]byte{
|
|
"!.git/config": nil,
|
|
"manifest.yaml": nil,
|
|
},
|
|
},
|
|
{
|
|
name: "custom",
|
|
files: map[string][]byte{
|
|
".git/config": nil,
|
|
"custom": nil,
|
|
"horse.jpg": nil,
|
|
},
|
|
filter: SourceIgnoreFilter([]gitignore.Pattern{
|
|
gitignore.ParsePattern("custom", nil),
|
|
}, nil),
|
|
want: map[string][]byte{
|
|
"!git/config": nil,
|
|
"!custom": nil,
|
|
"horse.jpg": nil,
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "including directories",
|
|
files: map[string][]byte{
|
|
"test/.gitkeep": nil,
|
|
},
|
|
filter: SourceIgnoreFilter([]gitignore.Pattern{
|
|
gitignore.ParsePattern("custom", nil),
|
|
}, nil),
|
|
wantDirs: []string{
|
|
"test",
|
|
},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
dir, err := createFiles(tt.files)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
artifact := sourcev1.Artifact{
|
|
Path: filepath.Join(randStringRunes(10), randStringRunes(10), randStringRunes(10)+".tar.gz"),
|
|
}
|
|
if err := storage.MkdirAll(artifact); err != nil {
|
|
t.Fatalf("artifact directory creation failed: %v", err)
|
|
}
|
|
if err := storage.Archive(&artifact, dir, tt.filter); (err != nil) != tt.wantErr {
|
|
t.Errorf("Archive() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
matchFiles(t, storage, artifact, tt.want, tt.wantDirs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStorageRemoveAllButCurrent(t *testing.T) {
|
|
t.Run("bad directory in archive", func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
s, err := NewStorage(dir, "hostname", time.Minute, 2)
|
|
if err != nil {
|
|
t.Fatalf("Valid path did not successfully return: %v", err)
|
|
}
|
|
|
|
if _, err := s.RemoveAllButCurrent(sourcev1.Artifact{Path: filepath.Join(dir, "really", "nonexistent")}); err == nil {
|
|
t.Fatal("Did not error while pruning non-existent path")
|
|
}
|
|
})
|
|
|
|
t.Run("collect names of deleted items", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
dir := t.TempDir()
|
|
|
|
s, err := NewStorage(dir, "hostname", time.Minute, 2)
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to create new storage")
|
|
|
|
artifact := sourcev1.Artifact{
|
|
Path: filepath.Join("foo", "bar", "artifact1.tar.gz"),
|
|
}
|
|
|
|
// Create artifact dir and artifacts.
|
|
artifactDir := filepath.Join(dir, "foo", "bar")
|
|
g.Expect(os.MkdirAll(artifactDir, 0o750)).NotTo(HaveOccurred())
|
|
current := []string{
|
|
filepath.Join(artifactDir, "artifact1.tar.gz"),
|
|
}
|
|
wantDeleted := []string{
|
|
filepath.Join(artifactDir, "file1.txt"),
|
|
filepath.Join(artifactDir, "file2.txt"),
|
|
}
|
|
createFile := func(files []string) {
|
|
for _, c := range files {
|
|
f, err := os.Create(c)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
g.Expect(f.Close()).ToNot(HaveOccurred())
|
|
}
|
|
}
|
|
createFile(current)
|
|
createFile(wantDeleted)
|
|
_, err = s.Symlink(artifact, "latest.tar.gz")
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to create symlink")
|
|
|
|
deleted, err := s.RemoveAllButCurrent(artifact)
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to remove all but current")
|
|
g.Expect(deleted).To(Equal(wantDeleted))
|
|
})
|
|
}
|
|
|
|
func TestStorageRemoveAll(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
artifactPath string
|
|
createArtifactPath bool
|
|
wantDeleted string
|
|
}{
|
|
{
|
|
name: "delete non-existent path",
|
|
artifactPath: filepath.Join("foo", "bar", "artifact1.tar.gz"),
|
|
createArtifactPath: false,
|
|
wantDeleted: "",
|
|
},
|
|
{
|
|
name: "delete existing path",
|
|
artifactPath: filepath.Join("foo", "bar", "artifact1.tar.gz"),
|
|
createArtifactPath: true,
|
|
wantDeleted: filepath.Join("foo", "bar"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
dir := t.TempDir()
|
|
|
|
s, err := NewStorage(dir, "hostname", time.Minute, 2)
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to create new storage")
|
|
|
|
artifact := sourcev1.Artifact{
|
|
Path: tt.artifactPath,
|
|
}
|
|
|
|
if tt.createArtifactPath {
|
|
g.Expect(os.MkdirAll(filepath.Join(dir, tt.artifactPath), 0o750)).ToNot(HaveOccurred())
|
|
}
|
|
|
|
deleted, err := s.RemoveAll(artifact)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
g.Expect(deleted).To(ContainSubstring(tt.wantDeleted), "unexpected deleted path")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStorageCopyFromPath(t *testing.T) {
|
|
type File struct {
|
|
Name string
|
|
Content []byte
|
|
}
|
|
|
|
dir := t.TempDir()
|
|
|
|
storage, err := NewStorage(dir, "hostname", time.Minute, 2)
|
|
if err != nil {
|
|
t.Fatalf("error while bootstrapping storage: %v", err)
|
|
}
|
|
|
|
createFile := func(file *File) (absPath string, err error) {
|
|
dir = t.TempDir()
|
|
absPath = filepath.Join(dir, file.Name)
|
|
if err = os.MkdirAll(filepath.Dir(absPath), 0o750); err != nil {
|
|
return
|
|
}
|
|
f, err := os.Create(absPath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("could not create file %q: %w", absPath, err)
|
|
}
|
|
if n, err := f.Write(file.Content); err != nil {
|
|
f.Close()
|
|
return "", fmt.Errorf("could not write %d bytes to file %q: %w", n, f.Name(), err)
|
|
}
|
|
f.Close()
|
|
return
|
|
}
|
|
|
|
matchFile := func(t *testing.T, storage *Storage, artifact sourcev1.Artifact, file *File, expectMismatch bool) {
|
|
c, err := os.ReadFile(storage.LocalPath(artifact))
|
|
if err != nil {
|
|
t.Fatalf("failed reading file: %v", err)
|
|
}
|
|
if (string(c) != string(file.Content)) != expectMismatch {
|
|
t.Errorf("artifact content does not match and not expecting mismatch, got: %q, want: %q", string(c), string(file.Content))
|
|
}
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
file *File
|
|
want *File
|
|
expectMismatch bool
|
|
}{
|
|
{
|
|
name: "content match",
|
|
file: &File{
|
|
Name: "manifest.yaml",
|
|
Content: []byte(`contents`),
|
|
},
|
|
want: &File{
|
|
Name: "manifest.yaml",
|
|
Content: []byte(`contents`),
|
|
},
|
|
},
|
|
{
|
|
name: "content not match",
|
|
file: &File{
|
|
Name: "manifest.yaml",
|
|
Content: []byte(`contents`),
|
|
},
|
|
want: &File{
|
|
Name: "manifest.yaml",
|
|
Content: []byte(`mismatch contents`),
|
|
},
|
|
expectMismatch: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
absPath, err := createFile(tt.file)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
artifact := sourcev1.Artifact{
|
|
Path: filepath.Join(randStringRunes(10), randStringRunes(10), randStringRunes(10)),
|
|
}
|
|
if err := storage.MkdirAll(artifact); err != nil {
|
|
t.Fatalf("artifact directory creation failed: %v", err)
|
|
}
|
|
if err := storage.CopyFromPath(&artifact, absPath); err != nil {
|
|
t.Errorf("CopyFromPath() error = %v", err)
|
|
}
|
|
matchFile(t, storage, artifact, tt.want, tt.expectMismatch)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStorage_getGarbageFiles(t *testing.T) {
|
|
artifactFolder := filepath.Join("foo", "bar")
|
|
tests := []struct {
|
|
name string
|
|
artifactPaths []string
|
|
createPause time.Duration
|
|
ttl time.Duration
|
|
maxItemsToBeRetained int
|
|
totalCountLimit int
|
|
wantDeleted []string
|
|
}{
|
|
{
|
|
name: "delete files based on maxItemsToBeRetained",
|
|
artifactPaths: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact3.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact4.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact5.tar.gz"),
|
|
},
|
|
createPause: time.Millisecond * 10,
|
|
ttl: time.Minute * 2,
|
|
totalCountLimit: 10,
|
|
maxItemsToBeRetained: 2,
|
|
wantDeleted: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact3.tar.gz"),
|
|
},
|
|
},
|
|
{
|
|
name: "delete files based on maxItemsToBeRetained, ignore lock files",
|
|
artifactPaths: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz.lock"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz.lock"),
|
|
filepath.Join(artifactFolder, "artifact3.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact3.tar.gz.lock"),
|
|
filepath.Join(artifactFolder, "artifact4.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact5.tar.gz"),
|
|
},
|
|
createPause: time.Millisecond * 10,
|
|
ttl: time.Minute * 2,
|
|
totalCountLimit: 10,
|
|
maxItemsToBeRetained: 2,
|
|
wantDeleted: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact3.tar.gz"),
|
|
},
|
|
},
|
|
{
|
|
name: "delete files based on ttl",
|
|
artifactPaths: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact3.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact4.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact5.tar.gz"),
|
|
},
|
|
createPause: time.Second * 1,
|
|
ttl: time.Second*3 + time.Millisecond*500,
|
|
totalCountLimit: 10,
|
|
maxItemsToBeRetained: 4,
|
|
wantDeleted: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
},
|
|
},
|
|
{
|
|
name: "delete files based on ttl, ignore lock files",
|
|
artifactPaths: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz.lock"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz.lock"),
|
|
filepath.Join(artifactFolder, "artifact3.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact4.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact5.tar.gz"),
|
|
},
|
|
createPause: time.Second * 1,
|
|
ttl: time.Second*3 + time.Millisecond*500,
|
|
totalCountLimit: 10,
|
|
maxItemsToBeRetained: 4,
|
|
wantDeleted: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
},
|
|
},
|
|
{
|
|
name: "delete files based on ttl and maxItemsToBeRetained",
|
|
artifactPaths: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact3.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact4.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact5.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact6.tar.gz"),
|
|
},
|
|
createPause: time.Second * 1,
|
|
ttl: time.Second*5 + time.Millisecond*500,
|
|
totalCountLimit: 10,
|
|
maxItemsToBeRetained: 4,
|
|
wantDeleted: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
},
|
|
},
|
|
{
|
|
name: "delete files based on ttl and maxItemsToBeRetained and totalCountLimit",
|
|
artifactPaths: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact3.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact4.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact5.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact6.tar.gz"),
|
|
},
|
|
createPause: time.Millisecond * 500,
|
|
ttl: time.Millisecond * 500,
|
|
totalCountLimit: 3,
|
|
maxItemsToBeRetained: 2,
|
|
wantDeleted: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact3.tar.gz"),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
dir := t.TempDir()
|
|
|
|
s, err := NewStorage(dir, "hostname", tt.ttl, tt.maxItemsToBeRetained)
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to create new storage")
|
|
|
|
artifact := sourcev1.Artifact{
|
|
Path: tt.artifactPaths[len(tt.artifactPaths)-1],
|
|
}
|
|
g.Expect(os.MkdirAll(filepath.Join(dir, artifactFolder), 0o750)).ToNot(HaveOccurred())
|
|
for _, artifactPath := range tt.artifactPaths {
|
|
f, err := os.Create(filepath.Join(dir, artifactPath))
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
g.Expect(f.Close()).ToNot(HaveOccurred())
|
|
time.Sleep(tt.createPause)
|
|
}
|
|
|
|
deletedPaths, err := s.getGarbageFiles(artifact, tt.totalCountLimit, tt.maxItemsToBeRetained, tt.ttl)
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to collect garbage files")
|
|
g.Expect(len(tt.wantDeleted)).To(Equal(len(deletedPaths)))
|
|
for _, wantDeletedPath := range tt.wantDeleted {
|
|
present := false
|
|
for _, deletedPath := range deletedPaths {
|
|
if strings.Contains(deletedPath, wantDeletedPath) {
|
|
present = true
|
|
break
|
|
}
|
|
}
|
|
if !present {
|
|
g.Fail(fmt.Sprintf("expected file to be deleted, still exists: %s", wantDeletedPath))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStorage_GarbageCollect(t *testing.T) {
|
|
artifactFolder := filepath.Join("foo", "bar")
|
|
tests := []struct {
|
|
name string
|
|
artifactPaths []string
|
|
wantCollected []string
|
|
wantDeleted []string
|
|
wantErr string
|
|
ctxTimeout time.Duration
|
|
}{
|
|
{
|
|
name: "garbage collects",
|
|
artifactPaths: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz.lock"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz.lock"),
|
|
filepath.Join(artifactFolder, "artifact3.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact4.tar.gz"),
|
|
},
|
|
wantCollected: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
},
|
|
wantDeleted: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz.lock"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz.lock"),
|
|
},
|
|
ctxTimeout: time.Second * 1,
|
|
},
|
|
{
|
|
name: "garbage collection fails with context timeout",
|
|
artifactPaths: []string{
|
|
filepath.Join(artifactFolder, "artifact1.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact2.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact3.tar.gz"),
|
|
filepath.Join(artifactFolder, "artifact4.tar.gz"),
|
|
},
|
|
wantErr: "context deadline exceeded",
|
|
ctxTimeout: time.Nanosecond * 1,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
dir := t.TempDir()
|
|
|
|
s, err := NewStorage(dir, "hostname", time.Second*2, 2)
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to create new storage")
|
|
|
|
artifact := sourcev1.Artifact{
|
|
Path: tt.artifactPaths[len(tt.artifactPaths)-1],
|
|
}
|
|
g.Expect(os.MkdirAll(filepath.Join(dir, artifactFolder), 0o750)).ToNot(HaveOccurred())
|
|
for i, artifactPath := range tt.artifactPaths {
|
|
f, err := os.Create(filepath.Join(dir, artifactPath))
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
g.Expect(f.Close()).ToNot(HaveOccurred())
|
|
if i != len(tt.artifactPaths)-1 {
|
|
time.Sleep(time.Second * 1)
|
|
}
|
|
}
|
|
|
|
collectedPaths, err := s.GarbageCollect(context.TODO(), artifact, tt.ctxTimeout)
|
|
if tt.wantErr == "" {
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to collect garbage files")
|
|
} else {
|
|
g.Expect(err).To(HaveOccurred())
|
|
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
|
|
}
|
|
if len(tt.wantCollected) > 0 {
|
|
g.Expect(len(tt.wantCollected)).To(Equal(len(collectedPaths)))
|
|
for _, wantCollectedPath := range tt.wantCollected {
|
|
present := false
|
|
for _, collectedPath := range collectedPaths {
|
|
if strings.Contains(collectedPath, wantCollectedPath) {
|
|
g.Expect(collectedPath).ToNot(BeAnExistingFile())
|
|
present = true
|
|
break
|
|
}
|
|
}
|
|
if present == false {
|
|
g.Fail(fmt.Sprintf("expected file to be garbage collected, still exists: %s", wantCollectedPath))
|
|
}
|
|
}
|
|
}
|
|
for _, delFile := range tt.wantDeleted {
|
|
g.Expect(filepath.Join(dir, delFile)).ToNot(BeAnExistingFile())
|
|
}
|
|
})
|
|
}
|
|
}
|