objectstorage: add posix standard storage

rename the previous posix storage to posixflat and make it currently not user
selectable (since I'm not sure it's really worth using it).

The new posix storage uses the filesystem without any escaping so it's not a
real flat namespace.

This isn't a real issue since also minio is not a flat namespace and we are so
forced to use it like a hierarchycal filesystem.
This commit is contained in:
Simone Gotti 2019-05-21 15:17:53 +02:00
parent 0e10a406f9
commit b3867fb7ca
19 changed files with 525 additions and 165 deletions

View File

@ -24,6 +24,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sorintlab/agola/internal/etcd" "github.com/sorintlab/agola/internal/etcd"
"github.com/sorintlab/agola/internal/objectstorage" "github.com/sorintlab/agola/internal/objectstorage"
"github.com/sorintlab/agola/internal/objectstorage/posix"
"github.com/sorintlab/agola/internal/objectstorage/s3"
"github.com/sorintlab/agola/internal/services/config" "github.com/sorintlab/agola/internal/services/config"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -81,7 +83,7 @@ func NewObjectStorage(c *config.ObjectStorage) (*objectstorage.ObjStorage, error
switch c.Type { switch c.Type {
case config.ObjectStorageTypePosix: case config.ObjectStorageTypePosix:
ost, err = objectstorage.NewPosixStorage(c.Path) ost, err = posix.New(c.Path)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to create posix object storage") return nil, errors.Wrapf(err, "failed to create posix object storage")
} }
@ -100,7 +102,7 @@ func NewObjectStorage(c *config.ObjectStorage) (*objectstorage.ObjStorage, error
return nil, errors.Errorf("wrong s3 endpoint scheme %q (must be http or https)", u.Scheme) return nil, errors.Errorf("wrong s3 endpoint scheme %q (must be http or https)", u.Scheme)
} }
} }
ost, err = objectstorage.NewS3Storage(c.Bucket, c.Location, endpoint, c.AccessKey, c.SecretAccessKey, secure) ost, err = s3.New(c.Bucket, c.Location, endpoint, c.AccessKey, c.SecretAccessKey, secure)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to create s3 object storage") return nil, errors.Wrapf(err, "failed to create s3 object storage")
} }

View File

@ -23,7 +23,7 @@ import (
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sorintlab/agola/internal/objectstorage" ostypes "github.com/sorintlab/agola/internal/objectstorage/types"
"github.com/sorintlab/agola/internal/sequence" "github.com/sorintlab/agola/internal/sequence"
) )
@ -98,19 +98,19 @@ func (d *DataManager) writeData(ctx context.Context, wals []*WalData) error {
func (d *DataManager) writeDataType(ctx context.Context, wals []*WalData, datatype, dataSequence string) error { func (d *DataManager) writeDataType(ctx context.Context, wals []*WalData, datatype, dataSequence string) error {
curDataStatus, err := d.GetLastDataStatus() curDataStatus, err := d.GetLastDataStatus()
if err != nil && err != objectstorage.ErrNotExist { if err != nil && err != ostypes.ErrNotExist {
return err return err
} }
dataEntriesMap := map[string]*DataEntry{} dataEntriesMap := map[string]*DataEntry{}
if err != objectstorage.ErrNotExist { if err != ostypes.ErrNotExist {
curDataSequence := curDataStatus.DataSequence curDataSequence := curDataStatus.DataSequence
oldDataf, err := d.ost.ReadObject(dataFilePath(datatype, curDataSequence)) oldDataf, err := d.ost.ReadObject(dataFilePath(datatype, curDataSequence))
if err != nil && err != objectstorage.ErrNotExist { if err != nil && err != ostypes.ErrNotExist {
return err return err
} }
if err != objectstorage.ErrNotExist { if err != ostypes.ErrNotExist {
dec := json.NewDecoder(oldDataf) dec := json.NewDecoder(oldDataf)
for { for {
var de *DataEntry var de *DataEntry
@ -240,7 +240,7 @@ func (d *DataManager) Read(dataType, id string) (io.Reader, error) {
pos, ok := dataFileIndex.Index[id] pos, ok := dataFileIndex.Index[id]
if !ok { if !ok {
return nil, objectstorage.ErrNotExist return nil, ostypes.ErrNotExist
} }
dataf, err := d.ost.ReadObject(dataFilePath(dataType, dataSequence)) dataf, err := d.ost.ReadObject(dataFilePath(dataType, dataSequence))
@ -276,7 +276,7 @@ func (d *DataManager) GetLastDataStatusPath() (string, error) {
} }
} }
if dataStatusPath == "" { if dataStatusPath == "" {
return "", objectstorage.ErrNotExist return "", ostypes.ErrNotExist
} }
return dataStatusPath, nil return dataStatusPath, nil

View File

@ -24,6 +24,8 @@ import (
slog "github.com/sorintlab/agola/internal/log" slog "github.com/sorintlab/agola/internal/log"
"github.com/sorintlab/agola/internal/objectstorage" "github.com/sorintlab/agola/internal/objectstorage"
"github.com/sorintlab/agola/internal/objectstorage/posix"
ostypes "github.com/sorintlab/agola/internal/objectstorage/types"
"github.com/sorintlab/agola/internal/testutil" "github.com/sorintlab/agola/internal/testutil"
"go.uber.org/zap" "go.uber.org/zap"
@ -76,7 +78,7 @@ func TestEtcdReset(t *testing.T) {
ostDir, err := ioutil.TempDir(dir, "ost") ostDir, err := ioutil.TempDir(dir, "ost")
ost, err := objectstorage.NewPosixStorage(ostDir) ost, err := posix.New(ostDir)
if err != nil { if err != nil {
t.Fatalf("unexpected err: %v", err) t.Fatalf("unexpected err: %v", err)
} }
@ -175,7 +177,7 @@ func TestConcurrentUpdate(t *testing.T) {
ostDir, err := ioutil.TempDir(dir, "ost") ostDir, err := ioutil.TempDir(dir, "ost")
ost, err := objectstorage.NewPosixStorage(ostDir) ost, err := posix.New(ostDir)
if err != nil { if err != nil {
t.Fatalf("unexpected err: %v", err) t.Fatalf("unexpected err: %v", err)
} }
@ -257,7 +259,7 @@ func TestWalCleaner(t *testing.T) {
ostDir, err := ioutil.TempDir(dir, "ost") ostDir, err := ioutil.TempDir(dir, "ost")
ost, err := objectstorage.NewPosixStorage(ostDir) ost, err := posix.New(ostDir)
if err != nil { if err != nil {
t.Fatalf("unexpected err: %v", err) t.Fatalf("unexpected err: %v", err)
} }
@ -317,7 +319,7 @@ func TestReadObject(t *testing.T) {
ctx := context.Background() ctx := context.Background()
ostDir, err := ioutil.TempDir(dir, "ost") ostDir, err := ioutil.TempDir(dir, "ost")
ost, err := objectstorage.NewPosixStorage(ostDir) ost, err := posix.New(ostDir)
if err != nil { if err != nil {
t.Fatalf("unexpected err: %v", err) t.Fatalf("unexpected err: %v", err)
} }
@ -386,8 +388,8 @@ func TestReadObject(t *testing.T) {
// should not exists // should not exists
_, _, err = dm.ReadObject("datatype01", "object1", nil) _, _, err = dm.ReadObject("datatype01", "object1", nil)
if err != objectstorage.ErrNotExist { if err != ostypes.ErrNotExist {
t.Fatalf("expected err %v, got: %v", objectstorage.ErrNotExist, err) t.Fatalf("expected err %v, got: %v", ostypes.ErrNotExist, err)
} }
// should exist // should exist
_, _, err = dm.ReadObject("datatype01", "object19", nil) _, _, err = dm.ReadObject("datatype01", "object19", nil)
@ -406,8 +408,8 @@ func TestReadObject(t *testing.T) {
// should not exists // should not exists
_, _, err = dm.ReadObject("datatype01", "object1", nil) _, _, err = dm.ReadObject("datatype01", "object1", nil)
if err != objectstorage.ErrNotExist { if err != ostypes.ErrNotExist {
t.Fatalf("expected err %v, got: %v", objectstorage.ErrNotExist, err) t.Fatalf("expected err %v, got: %v", ostypes.ErrNotExist, err)
} }
// should exist // should exist
_, _, err = dm.ReadObject("datatype01", "object19", nil) _, _, err = dm.ReadObject("datatype01", "object19", nil)

View File

@ -26,12 +26,12 @@ import (
"strings" "strings"
"time" "time"
uuid "github.com/satori/go.uuid"
"github.com/sorintlab/agola/internal/etcd" "github.com/sorintlab/agola/internal/etcd"
"github.com/sorintlab/agola/internal/objectstorage" ostypes "github.com/sorintlab/agola/internal/objectstorage/types"
"github.com/sorintlab/agola/internal/sequence" "github.com/sorintlab/agola/internal/sequence"
"github.com/pkg/errors" "github.com/pkg/errors"
uuid "github.com/satori/go.uuid"
etcdclientv3 "go.etcd.io/etcd/clientv3" etcdclientv3 "go.etcd.io/etcd/clientv3"
"go.etcd.io/etcd/clientv3/concurrency" "go.etcd.io/etcd/clientv3/concurrency"
etcdclientv3rpc "go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes" etcdclientv3rpc "go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
@ -163,7 +163,7 @@ func (d *DataManager) changesList(paths []string, prefix, startWith string, recu
func (d *DataManager) HasOSTWal(walseq string) (bool, error) { func (d *DataManager) HasOSTWal(walseq string) (bool, error) {
_, err := d.ost.Stat(d.storageWalStatusFile(walseq) + ".committed") _, err := d.ost.Stat(d.storageWalStatusFile(walseq) + ".committed")
if err == objectstorage.ErrNotExist { if err == ostypes.ErrNotExist {
return false, nil return false, nil
} }
if err != nil { if err != nil {

View File

@ -0,0 +1,84 @@
// Copyright 2019 Sorint.lab
//
// 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 common
import (
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
// WriteFileAtomicFunc atomically writes a file, it achieves this by creating a
// temporary file and then moving it. writeFunc is the func that will write
// data to the file.
// TODO(sgotti) remove left over tmp files if process crashes before calling
// os.Remove
func WriteFileAtomicFunc(p, baseDir, tmpDir string, perm os.FileMode, persist bool, writeFunc func(f io.Writer) error) error {
f, err := ioutil.TempFile(tmpDir, "tmpfile")
if err != nil {
return err
}
err = writeFunc(f)
if persist && err == nil {
err = f.Sync()
}
if closeErr := f.Close(); err == nil {
err = closeErr
}
if permErr := os.Chmod(f.Name(), perm); err == nil {
err = permErr
}
if err == nil {
err = os.Rename(f.Name(), p)
}
if err != nil {
os.Remove(f.Name())
return err
}
if !persist {
return nil
}
// sync parent dirs
pdir := filepath.Dir(p)
for {
if !strings.HasPrefix(pdir, baseDir) {
break
}
f, err := os.Open(pdir)
if err != nil {
f.Close()
return nil
}
if err := f.Sync(); err != nil {
f.Close()
return nil
}
f.Close()
pdir = filepath.Dir(pdir)
}
return nil
}
func WriteFileAtomic(filename, baseDir, tmpDir string, perm os.FileMode, persist bool, data []byte) error {
return WriteFileAtomicFunc(filename, baseDir, tmpDir, perm, persist,
func(f io.Writer) error {
_, err := f.Write(data)
return err
})
}

View File

@ -15,37 +15,17 @@
package objectstorage package objectstorage
import ( import (
"errors"
"io" "io"
"time"
"github.com/sorintlab/agola/internal/objectstorage/types"
) )
// TODO(sgotti)
// define common errors (like notFound) so the implementations will return them
// instead of their own errors
var ErrNotExist = errors.New("does not exist")
type ReadSeekCloser interface {
io.Reader
io.Seeker
io.Closer
}
type Storage interface { type Storage interface {
Stat(filepath string) (*ObjectInfo, error) Stat(filepath string) (*types.ObjectInfo, error)
ReadObject(filepath string) (ReadSeekCloser, error) ReadObject(filepath string) (types.ReadSeekCloser, error)
WriteObject(filepath string, data io.Reader, size int64, persist bool) error WriteObject(filepath string, data io.Reader, size int64, persist bool) error
DeleteObject(filepath string) error DeleteObject(filepath string) error
List(prefix, startWith, delimiter string, doneCh <-chan struct{}) <-chan ObjectInfo List(prefix, startWith, delimiter string, doneCh <-chan struct{}) <-chan types.ObjectInfo
}
type ObjectInfo struct {
Path string
LastModified time.Time
Err error
} }
// ObjStorage wraps a Storage providing additional helper functions // ObjStorage wraps a Storage providing additional helper functions
@ -62,7 +42,7 @@ func (s *ObjStorage) Delimiter() string {
return s.delimiter return s.delimiter
} }
func (s *ObjStorage) List(prefix, startWith string, recursive bool, doneCh <-chan struct{}) <-chan ObjectInfo { func (s *ObjStorage) List(prefix, startWith string, recursive bool, doneCh <-chan struct{}) <-chan types.ObjectInfo {
delimiter := s.delimiter delimiter := s.delimiter
if recursive { if recursive {
delimiter = "" delimiter = ""

View File

@ -18,10 +18,15 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
"github.com/sorintlab/agola/internal/objectstorage/posix"
"github.com/sorintlab/agola/internal/objectstorage/posixflat"
"github.com/sorintlab/agola/internal/objectstorage/s3"
) )
func TestList(t *testing.T) { func TestList(t *testing.T) {
@ -31,12 +36,16 @@ func TestList(t *testing.T) {
} }
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
ls, err := NewPosixStorage(dir) ps, err := posix.New(path.Join(dir, "posix"))
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
pfs, err := posixflat.New(path.Join(dir, "posixflat"))
if err != nil { if err != nil {
t.Fatalf("unexpected err: %v", err) t.Fatalf("unexpected err: %v", err)
} }
var s3 *S3Storage var s3s *s3.S3Storage
minioEndpoint := os.Getenv("MINIO_ENDPOINT") minioEndpoint := os.Getenv("MINIO_ENDPOINT")
minioAccessKey := os.Getenv("MINIO_ACCESSKEY") minioAccessKey := os.Getenv("MINIO_ACCESSKEY")
minioSecretKey := os.Getenv("MINIO_SECRETKEY") minioSecretKey := os.Getenv("MINIO_SECRETKEY")
@ -44,7 +53,7 @@ func TestList(t *testing.T) {
t.Logf("missing MINIO_ENDPOINT env, skipping tests with minio storage") t.Logf("missing MINIO_ENDPOINT env, skipping tests with minio storage")
} else { } else {
var err error var err error
s3, err = NewS3Storage(filepath.Base(dir), "", minioEndpoint, minioAccessKey, minioSecretKey, false) s3s, err = s3.New(filepath.Base(dir), "", minioEndpoint, minioAccessKey, minioSecretKey, false)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -62,7 +71,7 @@ func TestList(t *testing.T) {
ops []listop ops []listop
}{ }{
{ {
map[string]Storage{"local": ls}, map[string]Storage{"posixflat": pfs},
[]string{ []string{
// Minio (as of 20190201) IMHO is not real S3 since it tries to map to a // Minio (as of 20190201) IMHO is not real S3 since it tries to map to a
// file system and not a flat namespace like S3. For this reason this test // file system and not a flat namespace like S3. For this reason this test
@ -172,7 +181,7 @@ func TestList(t *testing.T) {
}, },
}, },
{ {
map[string]Storage{"local": ls, "minio": s3}, map[string]Storage{"posix": ps, "posixflat": pfs, "minio": s3s},
[]string{ []string{
// These are multiple of 8 chars on purpose to test the filemarker behavior to // These are multiple of 8 chars on purpose to test the filemarker behavior to
// distinguish between a file or a directory when the files ends at the path // distinguish between a file or a directory when the files ends at the path
@ -252,7 +261,7 @@ func TestList(t *testing.T) {
for sname, s := range tt.s { for sname, s := range tt.s {
t.Run(fmt.Sprintf("test with storage type %s", sname), func(t *testing.T) { t.Run(fmt.Sprintf("test with storage type %s", sname), func(t *testing.T) {
switch s := s.(type) { switch s := s.(type) {
case *S3Storage: case *s3.S3Storage:
if s == nil { if s == nil {
t.SkipNow() t.SkipNow()
} }

View File

@ -0,0 +1,234 @@
// Copyright 2019 Sorint.lab
//
// 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 posix
import (
"io"
"os"
"path"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/sorintlab/agola/internal/objectstorage/common"
"github.com/sorintlab/agola/internal/objectstorage/types"
)
const (
dataDirName = "data"
tmpDirName = "tmp"
)
type PosixStorage struct {
dataDir string
tmpDir string
}
func New(baseDir string) (*PosixStorage, error) {
if err := os.MkdirAll(baseDir, 0770); err != nil {
return nil, err
}
dataDir := filepath.Join(baseDir, dataDirName)
tmpDir := filepath.Join(baseDir, tmpDirName)
if err := os.MkdirAll(dataDir, 0770); err != nil {
return nil, errors.Wrapf(err, "failed to create data dir")
}
if err := os.MkdirAll(tmpDir, 0770); err != nil {
return nil, errors.Wrapf(err, "failed to create tmp dir")
}
return &PosixStorage{
dataDir: dataDir,
tmpDir: tmpDir,
}, nil
}
func (s *PosixStorage) fsPath(p string) (string, error) {
return filepath.Join(s.dataDir, p), nil
}
func (s *PosixStorage) Stat(p string) (*types.ObjectInfo, error) {
fspath, err := s.fsPath(p)
if err != nil {
return nil, err
}
fi, err := os.Stat(fspath)
if err != nil {
if os.IsNotExist(err) {
return nil, types.ErrNotExist
}
return nil, err
}
return &types.ObjectInfo{Path: p, LastModified: fi.ModTime()}, nil
}
func (s *PosixStorage) ReadObject(p string) (types.ReadSeekCloser, error) {
fspath, err := s.fsPath(p)
if err != nil {
return nil, err
}
f, err := os.Open(fspath)
if err != nil && os.IsNotExist(err) {
return nil, types.ErrNotExist
}
return f, err
}
func (s *PosixStorage) WriteObject(p string, data io.Reader, size int64, persist bool) error {
fspath, err := s.fsPath(p)
if err != nil {
return err
}
if err := os.MkdirAll(path.Dir(fspath), 0770); err != nil {
return err
}
return common.WriteFileAtomicFunc(fspath, s.dataDir, s.tmpDir, 0660, persist, func(f io.Writer) error {
_, err := io.Copy(f, data)
return err
})
}
func (s *PosixStorage) DeleteObject(p string) error {
fspath, err := s.fsPath(p)
if err != nil {
return err
}
if err := os.Remove(fspath); err != nil {
if os.IsNotExist(err) {
return types.ErrNotExist
}
return err
}
// try to remove parent empty dirs
// TODO(sgotti) if this fails we ignore errors and the dirs will be left as
// empty, clean them asynchronously
pdir := filepath.Dir(fspath)
for {
if pdir == s.dataDir || !strings.HasPrefix(pdir, s.dataDir) {
break
}
f, err := os.Open(pdir)
if err != nil {
return nil
}
_, err = f.Readdirnames(1)
if err == io.EOF {
f.Close()
if err := os.Remove(pdir); err != nil {
return nil
}
} else {
f.Close()
break
}
pdir = filepath.Dir(pdir)
}
return nil
}
func (s *PosixStorage) List(prefix, startWith, delimiter string, doneCh <-chan struct{}) <-chan types.ObjectInfo {
objectCh := make(chan types.ObjectInfo, 1)
if len(delimiter) > 1 {
objectCh <- types.ObjectInfo{Err: errors.Errorf("wrong delimiter %q", delimiter)}
return objectCh
}
if startWith != "" && !strings.Contains(startWith, prefix) {
objectCh <- types.ObjectInfo{Err: errors.Errorf("wrong startwith value %q for prefix %q", startWith, prefix)}
return objectCh
}
recursive := delimiter == ""
// remove leading slash from prefix
if strings.HasPrefix(prefix, "/") {
prefix = strings.TrimPrefix(prefix, "/")
}
fprefix := filepath.Join(s.dataDir, prefix)
root := filepath.Dir(fprefix)
if len(root) < len(s.dataDir) {
root = s.dataDir
}
// remove leading slash
if strings.HasPrefix(startWith, "/") {
startWith = strings.TrimPrefix(startWith, "/")
}
go func(objectCh chan<- types.ObjectInfo) {
defer close(objectCh)
err := filepath.Walk(root, func(ep string, info os.FileInfo, err error) error {
if err != nil && !os.IsNotExist(err) {
return err
}
if os.IsNotExist(err) {
return nil
}
p := ep
// get the path with / separator
p = filepath.ToSlash(p)
p, err = filepath.Rel(s.dataDir, p)
if err != nil {
return err
}
if !recursive && len(p) > len(prefix) {
rel := strings.TrimPrefix(p, prefix)
skip := strings.Contains(rel, delimiter)
if info.IsDir() && skip {
return filepath.SkipDir
}
if skip {
return nil
}
}
if info.IsDir() {
return nil
}
if strings.HasPrefix(p, prefix) && p > startWith {
select {
// Send object content.
case objectCh <- types.ObjectInfo{Path: p, LastModified: info.ModTime()}:
// If receives done from the caller, return here.
case <-doneCh:
return io.EOF
}
}
return nil
})
if err != nil && err != io.EOF {
objectCh <- types.ObjectInfo{
Err: err,
}
return
}
}(objectCh)
return objectCh
}

View File

@ -0,0 +1,60 @@
// Copyright 2019 Sorint.lab
//
// 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 posix
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestDeleteObject(t *testing.T) {
objects := []string{"☺☺☺☺a☺☺☺☺☺☺b☺☺☺☺", "s3/is/nota/fil.fa", "s3/is/not/a/file///system/fi%l%%e01"}
dir, err := ioutil.TempDir("", "objectstorage")
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
//defer os.RemoveAll(dir)
ls, err := New(dir)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
for _, obj := range objects {
if err := ls.WriteObject(obj, bytes.NewReader([]byte{}), 0, true); err != nil {
t.Fatalf("unexpected err: %v", err)
}
if err := ls.DeleteObject(obj); err != nil {
t.Fatalf("unexpected err: %v", err)
}
}
// no files and directories should be left
bd, err := os.Open(filepath.Join(dir, dataDirName))
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
files, err := bd.Readdirnames(0)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if len(files) > 0 {
t.Fatalf("expected 0 files got %d files", len(files))
}
}

View File

@ -12,11 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package objectstorage package posixflat
import ( import (
"io" "io"
"io/ioutil"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -24,6 +23,9 @@ import (
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"github.com/sorintlab/agola/internal/objectstorage/common"
"github.com/sorintlab/agola/internal/objectstorage/types"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -204,12 +206,12 @@ func unescape(s string) (string, bool, error) {
return string(t), hasFileMarker, nil return string(t), hasFileMarker, nil
} }
type PosixStorage struct { type PosixFlatStorage struct {
dataDir string dataDir string
tmpDir string tmpDir string
} }
func NewPosixStorage(baseDir string) (*PosixStorage, error) { func New(baseDir string) (*PosixFlatStorage, error) {
if err := os.MkdirAll(baseDir, 0770); err != nil { if err := os.MkdirAll(baseDir, 0770); err != nil {
return nil, err return nil, err
} }
@ -221,20 +223,20 @@ func NewPosixStorage(baseDir string) (*PosixStorage, error) {
if err := os.MkdirAll(tmpDir, 0770); err != nil { if err := os.MkdirAll(tmpDir, 0770); err != nil {
return nil, errors.Wrapf(err, "failed to create tmp dir") return nil, errors.Wrapf(err, "failed to create tmp dir")
} }
return &PosixStorage{ return &PosixFlatStorage{
dataDir: dataDir, dataDir: dataDir,
tmpDir: tmpDir, tmpDir: tmpDir,
}, nil }, nil
} }
func (s *PosixStorage) fsPath(p string) (string, error) { func (s *PosixFlatStorage) fsPath(p string) (string, error) {
if p == "" { if p == "" {
return "", errors.Errorf("empty key name") return "", errors.Errorf("empty key name")
} }
return filepath.Join(s.dataDir, escape(p)), nil return filepath.Join(s.dataDir, escape(p)), nil
} }
func (s *PosixStorage) Stat(p string) (*ObjectInfo, error) { func (s *PosixFlatStorage) Stat(p string) (*types.ObjectInfo, error) {
fspath, err := s.fsPath(p) fspath, err := s.fsPath(p)
if err != nil { if err != nil {
return nil, err return nil, err
@ -243,15 +245,15 @@ func (s *PosixStorage) Stat(p string) (*ObjectInfo, error) {
fi, err := os.Stat(fspath) fi, err := os.Stat(fspath)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil, ErrNotExist return nil, types.ErrNotExist
} }
return nil, err return nil, err
} }
return &ObjectInfo{Path: p, LastModified: fi.ModTime()}, nil return &types.ObjectInfo{Path: p, LastModified: fi.ModTime()}, nil
} }
func (s *PosixStorage) ReadObject(p string) (ReadSeekCloser, error) { func (s *PosixFlatStorage) ReadObject(p string) (types.ReadSeekCloser, error) {
fspath, err := s.fsPath(p) fspath, err := s.fsPath(p)
if err != nil { if err != nil {
return nil, err return nil, err
@ -259,12 +261,12 @@ func (s *PosixStorage) ReadObject(p string) (ReadSeekCloser, error) {
f, err := os.Open(fspath) f, err := os.Open(fspath)
if err != nil && os.IsNotExist(err) { if err != nil && os.IsNotExist(err) {
return nil, ErrNotExist return nil, types.ErrNotExist
} }
return f, err return f, err
} }
func (s *PosixStorage) WriteObject(p string, data io.Reader, size int64, persist bool) error { func (s *PosixFlatStorage) WriteObject(p string, data io.Reader, size int64, persist bool) error {
fspath, err := s.fsPath(p) fspath, err := s.fsPath(p)
if err != nil { if err != nil {
return err return err
@ -273,13 +275,13 @@ func (s *PosixStorage) WriteObject(p string, data io.Reader, size int64, persist
if err := os.MkdirAll(path.Dir(fspath), 0770); err != nil { if err := os.MkdirAll(path.Dir(fspath), 0770); err != nil {
return err return err
} }
return s.WriteFileAtomicFunc(fspath, 0660, persist, func(f io.Writer) error { return common.WriteFileAtomicFunc(fspath, s.dataDir, s.tmpDir, 0660, persist, func(f io.Writer) error {
_, err := io.Copy(f, data) _, err := io.Copy(f, data)
return err return err
}) })
} }
func (s *PosixStorage) DeleteObject(p string) error { func (s *PosixFlatStorage) DeleteObject(p string) error {
fspath, err := s.fsPath(p) fspath, err := s.fsPath(p)
if err != nil { if err != nil {
return err return err
@ -287,7 +289,7 @@ func (s *PosixStorage) DeleteObject(p string) error {
if err := os.Remove(fspath); err != nil { if err := os.Remove(fspath); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return ErrNotExist return types.ErrNotExist
} }
return err return err
} }
@ -321,16 +323,16 @@ func (s *PosixStorage) DeleteObject(p string) error {
return nil return nil
} }
func (s *PosixStorage) List(prefix, startWith, delimiter string, doneCh <-chan struct{}) <-chan ObjectInfo { func (s *PosixFlatStorage) List(prefix, startWith, delimiter string, doneCh <-chan struct{}) <-chan types.ObjectInfo {
objectCh := make(chan ObjectInfo, 1) objectCh := make(chan types.ObjectInfo, 1)
if len(delimiter) > 1 { if len(delimiter) > 1 {
objectCh <- ObjectInfo{Err: errors.Errorf("wrong delimiter %q", delimiter)} objectCh <- types.ObjectInfo{Err: errors.Errorf("wrong delimiter %q", delimiter)}
return objectCh return objectCh
} }
if startWith != "" && !strings.Contains(startWith, prefix) { if startWith != "" && !strings.Contains(startWith, prefix) {
objectCh <- ObjectInfo{Err: errors.Errorf("wrong startwith value %q for prefix %q", startWith, prefix)} objectCh <- types.ObjectInfo{Err: errors.Errorf("wrong startwith value %q for prefix %q", startWith, prefix)}
return objectCh return objectCh
} }
@ -352,7 +354,7 @@ func (s *PosixStorage) List(prefix, startWith, delimiter string, doneCh <-chan s
startWith = strings.TrimPrefix(startWith, "/") startWith = strings.TrimPrefix(startWith, "/")
} }
go func(objectCh chan<- ObjectInfo) { go func(objectCh chan<- types.ObjectInfo) {
var prevp string var prevp string
defer close(objectCh) defer close(objectCh)
err := filepath.Walk(root, func(ep string, info os.FileInfo, err error) error { err := filepath.Walk(root, func(ep string, info os.FileInfo, err error) error {
@ -410,7 +412,7 @@ func (s *PosixStorage) List(prefix, startWith, delimiter string, doneCh <-chan s
if p > prevp { if p > prevp {
select { select {
// Send object content. // Send object content.
case objectCh <- ObjectInfo{Path: p, LastModified: info.ModTime()}: case objectCh <- types.ObjectInfo{Path: p, LastModified: info.ModTime()}:
// If receives done from the caller, return here. // If receives done from the caller, return here.
case <-doneCh: case <-doneCh:
return io.EOF return io.EOF
@ -422,7 +424,7 @@ func (s *PosixStorage) List(prefix, startWith, delimiter string, doneCh <-chan s
return nil return nil
}) })
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
objectCh <- ObjectInfo{ objectCh <- types.ObjectInfo{
Err: err, Err: err,
} }
return return
@ -431,64 +433,3 @@ func (s *PosixStorage) List(prefix, startWith, delimiter string, doneCh <-chan s
return objectCh return objectCh
} }
// WriteFileAtomicFunc atomically writes a file, it achieves this by creating a
// temporary file and then moving it. writeFunc is the func that will write
// data to the file.
// TODO(sgotti) remove left over tmp files if process crashes before calling
// os.Remove
func (s *PosixStorage) WriteFileAtomicFunc(p string, perm os.FileMode, persist bool, writeFunc func(f io.Writer) error) error {
f, err := ioutil.TempFile(s.tmpDir, "tmpfile")
if err != nil {
return err
}
err = writeFunc(f)
if persist && err == nil {
err = f.Sync()
}
if closeErr := f.Close(); err == nil {
err = closeErr
}
if permErr := os.Chmod(f.Name(), perm); err == nil {
err = permErr
}
if err == nil {
err = os.Rename(f.Name(), p)
}
if err != nil {
os.Remove(f.Name())
return err
}
if !persist {
return nil
}
// sync parent dirs
pdir := filepath.Dir(p)
for {
if !strings.HasPrefix(pdir, s.dataDir) {
break
}
f, err := os.Open(pdir)
if err != nil {
f.Close()
return nil
}
if err := f.Sync(); err != nil {
f.Close()
return nil
}
f.Close()
pdir = filepath.Dir(pdir)
}
return nil
}
func (s *PosixStorage) WriteFileAtomic(filename string, perm os.FileMode, persist bool, data []byte) error {
return s.WriteFileAtomicFunc(filename, perm, persist,
func(f io.Writer) error {
_, err := f.Write(data)
return err
})
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package objectstorage package posixflat
import ( import (
"bytes" "bytes"
@ -86,7 +86,7 @@ func TestDeleteObject(t *testing.T) {
} }
//defer os.RemoveAll(dir) //defer os.RemoveAll(dir)
ls, err := NewPosixStorage(dir) ls, err := New(dir)
if err != nil { if err != nil {
t.Fatalf("unexpected err: %v", err) t.Fatalf("unexpected err: %v", err)
} }

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package objectstorage package s3
import ( import (
"io" "io"
@ -21,6 +21,8 @@ import (
"os" "os"
"strings" "strings"
"github.com/sorintlab/agola/internal/objectstorage/types"
minio "github.com/minio/minio-go" minio "github.com/minio/minio-go"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -32,7 +34,7 @@ type S3Storage struct {
minioCore *minio.Core minioCore *minio.Core
} }
func NewS3Storage(bucket, location, endpoint, accessKeyID, secretAccessKey string, secure bool) (*S3Storage, error) { func New(bucket, location, endpoint, accessKeyID, secretAccessKey string, secure bool) (*S3Storage, error) {
minioClient, err := minio.New(endpoint, accessKeyID, secretAccessKey, secure) minioClient, err := minio.New(endpoint, accessKeyID, secretAccessKey, secure)
if err != nil { if err != nil {
return nil, err return nil, err
@ -60,24 +62,24 @@ func NewS3Storage(bucket, location, endpoint, accessKeyID, secretAccessKey strin
}, nil }, nil
} }
func (s *S3Storage) Stat(p string) (*ObjectInfo, error) { func (s *S3Storage) Stat(p string) (*types.ObjectInfo, error) {
oi, err := s.minioClient.StatObject(s.bucket, p, minio.StatObjectOptions{}) oi, err := s.minioClient.StatObject(s.bucket, p, minio.StatObjectOptions{})
if err != nil { if err != nil {
merr := minio.ToErrorResponse(err) merr := minio.ToErrorResponse(err)
if merr.StatusCode == http.StatusNotFound { if merr.StatusCode == http.StatusNotFound {
return nil, ErrNotExist return nil, types.ErrNotExist
} }
return nil, merr return nil, merr
} }
return &ObjectInfo{Path: p, LastModified: oi.LastModified}, nil return &types.ObjectInfo{Path: p, LastModified: oi.LastModified}, nil
} }
func (s *S3Storage) ReadObject(filepath string) (ReadSeekCloser, error) { func (s *S3Storage) ReadObject(filepath string) (types.ReadSeekCloser, error) {
if _, err := s.minioClient.StatObject(s.bucket, filepath, minio.StatObjectOptions{}); err != nil { if _, err := s.minioClient.StatObject(s.bucket, filepath, minio.StatObjectOptions{}); err != nil {
merr := minio.ToErrorResponse(err) merr := minio.ToErrorResponse(err)
if merr.StatusCode == http.StatusNotFound { if merr.StatusCode == http.StatusNotFound {
return nil, ErrNotExist return nil, types.ErrNotExist
} }
return nil, merr return nil, merr
} }
@ -117,11 +119,11 @@ func (s *S3Storage) DeleteObject(filepath string) error {
return s.minioClient.RemoveObject(s.bucket, filepath) return s.minioClient.RemoveObject(s.bucket, filepath)
} }
func (s *S3Storage) List(prefix, startWith, delimiter string, doneCh <-chan struct{}) <-chan ObjectInfo { func (s *S3Storage) List(prefix, startWith, delimiter string, doneCh <-chan struct{}) <-chan types.ObjectInfo {
objectCh := make(chan ObjectInfo, 1) objectCh := make(chan types.ObjectInfo, 1)
if len(delimiter) > 1 { if len(delimiter) > 1 {
objectCh <- ObjectInfo{ objectCh <- types.ObjectInfo{
Err: errors.Errorf("wrong delimiter %q", delimiter), Err: errors.Errorf("wrong delimiter %q", delimiter),
} }
return objectCh return objectCh
@ -136,7 +138,7 @@ func (s *S3Storage) List(prefix, startWith, delimiter string, doneCh <-chan stru
} }
// Initiate list objects goroutine here. // Initiate list objects goroutine here.
go func(objectCh chan<- ObjectInfo) { go func(objectCh chan<- types.ObjectInfo) {
defer close(objectCh) defer close(objectCh)
// Save continuationToken for next request. // Save continuationToken for next request.
var continuationToken string var continuationToken string
@ -144,7 +146,7 @@ func (s *S3Storage) List(prefix, startWith, delimiter string, doneCh <-chan stru
// Get list of objects a maximum of 1000 per request. // Get list of objects a maximum of 1000 per request.
result, err := s.minioCore.ListObjectsV2(s.bucket, prefix, continuationToken, false, delimiter, 1000, startWith) result, err := s.minioCore.ListObjectsV2(s.bucket, prefix, continuationToken, false, delimiter, 1000, startWith)
if err != nil { if err != nil {
objectCh <- ObjectInfo{ objectCh <- types.ObjectInfo{
Err: err, Err: err,
} }
return return
@ -154,7 +156,7 @@ func (s *S3Storage) List(prefix, startWith, delimiter string, doneCh <-chan stru
for _, object := range result.Contents { for _, object := range result.Contents {
select { select {
// Send object content. // Send object content.
case objectCh <- ObjectInfo{Path: object.Key, LastModified: object.LastModified}: case objectCh <- types.ObjectInfo{Path: object.Key, LastModified: object.LastModified}:
// If receives done from the caller, return here. // If receives done from the caller, return here.
case <-doneCh: case <-doneCh:
return return

View File

@ -0,0 +1,41 @@
// Copyright 2019 Sorint.lab
//
// 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 types
import (
"errors"
"io"
"time"
)
// TODO(sgotti)
// define common errors (like notFound) so the implementations will return them
// instead of their own errors
var ErrNotExist = errors.New("does not exist")
type ReadSeekCloser interface {
io.Reader
io.Seeker
io.Closer
}
type ObjectInfo struct {
Path string
LastModified time.Time
Err error
}

View File

@ -28,6 +28,7 @@ import (
"github.com/sorintlab/agola/internal/db" "github.com/sorintlab/agola/internal/db"
"github.com/sorintlab/agola/internal/etcd" "github.com/sorintlab/agola/internal/etcd"
"github.com/sorintlab/agola/internal/objectstorage" "github.com/sorintlab/agola/internal/objectstorage"
ostypes "github.com/sorintlab/agola/internal/objectstorage/types"
"github.com/sorintlab/agola/internal/sequence" "github.com/sorintlab/agola/internal/sequence"
"github.com/sorintlab/agola/internal/services/types" "github.com/sorintlab/agola/internal/services/types"
"github.com/sorintlab/agola/internal/util" "github.com/sorintlab/agola/internal/util"
@ -125,10 +126,10 @@ func (r *ReadDB) ResetDB() error {
func (r *ReadDB) SyncFromDump() (string, error) { func (r *ReadDB) SyncFromDump() (string, error) {
dumpIndex, err := r.dm.GetLastDataStatus() dumpIndex, err := r.dm.GetLastDataStatus()
if err != nil && err != objectstorage.ErrNotExist { if err != nil && err != ostypes.ErrNotExist {
return "", errors.WithStack(err) return "", errors.WithStack(err)
} }
if err == objectstorage.ErrNotExist { if err == ostypes.ErrNotExist {
return "", nil return "", nil
} }
for dataType, files := range dumpIndex.Files { for dataType, files := range dumpIndex.Files {

View File

@ -26,6 +26,7 @@ import (
"github.com/sorintlab/agola/internal/db" "github.com/sorintlab/agola/internal/db"
"github.com/sorintlab/agola/internal/etcd" "github.com/sorintlab/agola/internal/etcd"
"github.com/sorintlab/agola/internal/objectstorage" "github.com/sorintlab/agola/internal/objectstorage"
ostypes "github.com/sorintlab/agola/internal/objectstorage/types"
"github.com/sorintlab/agola/internal/services/runservice/action" "github.com/sorintlab/agola/internal/services/runservice/action"
"github.com/sorintlab/agola/internal/services/runservice/common" "github.com/sorintlab/agola/internal/services/runservice/common"
"github.com/sorintlab/agola/internal/services/runservice/readdb" "github.com/sorintlab/agola/internal/services/runservice/readdb"
@ -198,7 +199,7 @@ func (h *LogsHandler) readTaskLogs(ctx context.Context, runID, taskID string, se
} }
f, err := h.ost.ReadObject(logPath) f, err := h.ost.ReadObject(logPath)
if err != nil { if err != nil {
if err == objectstorage.ErrNotExist { if err == ostypes.ErrNotExist {
return common.NewErrNotExist(err), true return common.NewErrNotExist(err), true
} }
return err, true return err, true

View File

@ -22,13 +22,15 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/gorilla/mux"
"github.com/sorintlab/agola/internal/etcd" "github.com/sorintlab/agola/internal/etcd"
"github.com/sorintlab/agola/internal/objectstorage" "github.com/sorintlab/agola/internal/objectstorage"
ostypes "github.com/sorintlab/agola/internal/objectstorage/types"
"github.com/sorintlab/agola/internal/services/runservice/action" "github.com/sorintlab/agola/internal/services/runservice/action"
"github.com/sorintlab/agola/internal/services/runservice/common" "github.com/sorintlab/agola/internal/services/runservice/common"
"github.com/sorintlab/agola/internal/services/runservice/store" "github.com/sorintlab/agola/internal/services/runservice/store"
"github.com/sorintlab/agola/internal/services/runservice/types" "github.com/sorintlab/agola/internal/services/runservice/types"
"github.com/gorilla/mux"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -244,7 +246,7 @@ func (h *ArchivesHandler) readArchive(rtID string, step int, w io.Writer) error
archivePath := store.OSTRunTaskArchivePath(rtID, step) archivePath := store.OSTRunTaskArchivePath(rtID, step)
f, err := h.ost.ReadObject(archivePath) f, err := h.ost.ReadObject(archivePath)
if err != nil { if err != nil {
if err == objectstorage.ErrNotExist { if err == ostypes.ErrNotExist {
return common.NewErrNotExist(err) return common.NewErrNotExist(err)
} }
return err return err
@ -321,7 +323,7 @@ func matchCache(ost *objectstorage.ObjStorage, key string, prefix bool) (string,
defer close(doneCh) defer close(doneCh)
// get the latest modified object // get the latest modified object
var lastObject *objectstorage.ObjectInfo var lastObject *ostypes.ObjectInfo
for object := range ost.List(store.OSTCacheDir()+"/"+key, "", false, doneCh) { for object := range ost.List(store.OSTCacheDir()+"/"+key, "", false, doneCh) {
if object.Err != nil { if object.Err != nil {
return "", object.Err return "", object.Err
@ -340,7 +342,7 @@ func matchCache(ost *objectstorage.ObjStorage, key string, prefix bool) (string,
} }
_, err := ost.Stat(cachePath) _, err := ost.Stat(cachePath)
if err == objectstorage.ErrNotExist { if err == ostypes.ErrNotExist {
return "", nil return "", nil
} }
if err != nil { if err != nil {
@ -353,7 +355,7 @@ func (h *CacheHandler) readCache(key string, w io.Writer) error {
cachePath := store.OSTCachePath(key) cachePath := store.OSTCachePath(key)
f, err := h.ost.ReadObject(cachePath) f, err := h.ost.ReadObject(cachePath)
if err != nil { if err != nil {
if err == objectstorage.ErrNotExist { if err == ostypes.ErrNotExist {
return common.NewErrNotExist(err) return common.NewErrNotExist(err)
} }
return err return err

View File

@ -32,6 +32,7 @@ import (
"github.com/sorintlab/agola/internal/db" "github.com/sorintlab/agola/internal/db"
"github.com/sorintlab/agola/internal/etcd" "github.com/sorintlab/agola/internal/etcd"
"github.com/sorintlab/agola/internal/objectstorage" "github.com/sorintlab/agola/internal/objectstorage"
ostypes "github.com/sorintlab/agola/internal/objectstorage/types"
"github.com/sorintlab/agola/internal/sequence" "github.com/sorintlab/agola/internal/sequence"
"github.com/sorintlab/agola/internal/services/runservice/common" "github.com/sorintlab/agola/internal/services/runservice/common"
"github.com/sorintlab/agola/internal/services/runservice/store" "github.com/sorintlab/agola/internal/services/runservice/store"
@ -647,10 +648,10 @@ func (r *ReadDB) SyncObjectStorage(ctx context.Context) error {
func (r *ReadDB) SyncFromDump() (string, error) { func (r *ReadDB) SyncFromDump() (string, error) {
dumpIndex, err := r.dm.GetLastDataStatus() dumpIndex, err := r.dm.GetLastDataStatus()
if err != nil && err != objectstorage.ErrNotExist { if err != nil && err != ostypes.ErrNotExist {
return "", errors.WithStack(err) return "", errors.WithStack(err)
} }
if err == objectstorage.ErrNotExist { if err == ostypes.ErrNotExist {
return "", nil return "", nil
} }
for dataType, files := range dumpIndex.Files { for dataType, files := range dumpIndex.Files {

View File

@ -27,7 +27,7 @@ import (
"github.com/sorintlab/agola/internal/datamanager" "github.com/sorintlab/agola/internal/datamanager"
"github.com/sorintlab/agola/internal/etcd" "github.com/sorintlab/agola/internal/etcd"
slog "github.com/sorintlab/agola/internal/log" slog "github.com/sorintlab/agola/internal/log"
"github.com/sorintlab/agola/internal/objectstorage" ostypes "github.com/sorintlab/agola/internal/objectstorage/types"
"github.com/sorintlab/agola/internal/runconfig" "github.com/sorintlab/agola/internal/runconfig"
"github.com/sorintlab/agola/internal/services/runservice/common" "github.com/sorintlab/agola/internal/services/runservice/common"
"github.com/sorintlab/agola/internal/services/runservice/store" "github.com/sorintlab/agola/internal/services/runservice/store"
@ -862,7 +862,7 @@ func (s *Runservice) runTasksUpdater(ctx context.Context) error {
func (s *Runservice) OSTFileExists(path string) (bool, error) { func (s *Runservice) OSTFileExists(path string) (bool, error) {
_, err := s.ost.Stat(path) _, err := s.ost.Stat(path)
if err != nil && err != objectstorage.ErrNotExist { if err != nil && err != ostypes.ErrNotExist {
return false, err return false, err
} }
return err == nil, nil return err == nil, nil
@ -1363,7 +1363,7 @@ func (s *Runservice) cacheCleaner(ctx context.Context, cacheExpireInterval time.
} }
if object.LastModified.Add(cacheExpireInterval).Before(time.Now()) { if object.LastModified.Add(cacheExpireInterval).Before(time.Now()) {
if err := s.ost.DeleteObject(object.Path); err != nil { if err := s.ost.DeleteObject(object.Path); err != nil {
if err != objectstorage.ErrNotExist { if err != ostypes.ErrNotExist {
log.Warnf("failed to delete cache object %q: %v", object.Path, err) log.Warnf("failed to delete cache object %q: %v", object.Path, err)
} }
} }

View File

@ -24,7 +24,7 @@ import (
"github.com/sorintlab/agola/internal/datamanager" "github.com/sorintlab/agola/internal/datamanager"
"github.com/sorintlab/agola/internal/etcd" "github.com/sorintlab/agola/internal/etcd"
"github.com/sorintlab/agola/internal/objectstorage" ostypes "github.com/sorintlab/agola/internal/objectstorage/types"
"github.com/sorintlab/agola/internal/services/runservice/common" "github.com/sorintlab/agola/internal/services/runservice/common"
"github.com/sorintlab/agola/internal/services/runservice/types" "github.com/sorintlab/agola/internal/services/runservice/types"
"github.com/sorintlab/agola/internal/util" "github.com/sorintlab/agola/internal/util"
@ -540,7 +540,7 @@ func GetRunEtcdOrOST(ctx context.Context, e *etcd.Store, dm *datamanager.DataMan
} }
if r == nil { if r == nil {
r, err = OSTGetRun(dm, runID) r, err = OSTGetRun(dm, runID)
if err != nil && err != objectstorage.ErrNotExist { if err != nil && err != ostypes.ErrNotExist {
return nil, err return nil, err
} }
} }