aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md3
-rw-r--r--account.go3
-rw-r--r--bulk.go4
-rw-r--r--capabilities.go3
-rw-r--r--container.go5
-rw-r--r--errors.go3
-rw-r--r--largeobject.go8
-rw-r--r--object.go37
-rw-r--r--tests/object_test.go14
9 files changed, 48 insertions, 32 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index aa145a8..96ae415 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -37,6 +37,3 @@ container](https://github.com/bouncestorage/docker-swift).
2. Run the tests with `./testing/with-saio.sh go test`. The script will find how to access the Swift API inside the
container, and configure the auth environment variables accordingly. You can use this with any command that requires
Swift credentials, e.g. `./testing/with-saio.sh swift stat`.
-
-**WARNING:** At the time of this writing, https://github.com/bouncestorage/docker-swift/pull/30 has not been merged, so
-you need to patch the proxy-server.conf manually to get the symlink-related tests to pass.
diff --git a/account.go b/account.go
index c101b54..ca3c9e8 100644
--- a/account.go
+++ b/account.go
@@ -38,7 +38,8 @@ type Account struct {
caps *Capabilities
}
-func (a *Account) isEqualTo(other *Account) bool {
+//IsEqualTo returns true if both Account instances refer to the same account.
+func (a *Account) IsEqualTo(other *Account) bool {
return other.baseURL == a.baseURL && other.name == a.name
}
diff --git a/bulk.go b/bulk.go
index 5d6304b..a160652 100644
--- a/bulk.go
+++ b/bulk.go
@@ -145,12 +145,12 @@ func makeBulkObjectError(fullName string, statusCode int) BulkObjectError {
func (a *Account) BulkDelete(objects []*Object, containers []*Container, opts *RequestOptions) (numDeleted int, numNotFound int, deleteError error) {
//validate that all given objects are in this account
for _, obj := range objects {
- if !a.isEqualTo(obj.Container().Account()) {
+ if !a.IsEqualTo(obj.Container().Account()) {
return 0, 0, ErrAccountMismatch
}
}
for _, container := range containers {
- if !a.isEqualTo(container.Account()) {
+ if !a.IsEqualTo(container.Account()) {
return 0, 0, ErrAccountMismatch
}
}
diff --git a/capabilities.go b/capabilities.go
index fab7784..69d6183 100644
--- a/capabilities.go
+++ b/capabilities.go
@@ -67,6 +67,9 @@ type Capabilities struct {
MaximumUploadPartNumber uint `json:"max_upload_part_num"`
Version string `json:"version"`
} `json:"swift3"`
+ Symlink *struct {
+ MaximumLoopCount uint `json:"symloop_max"`
+ } `json:"symlink"`
TempAuth *struct {
AccountACLs bool `json:"account_acls"`
} `json:"tempauth"`
diff --git a/container.go b/container.go
index 6982efe..147400d 100644
--- a/container.go
+++ b/container.go
@@ -32,8 +32,9 @@ type Container struct {
headers *ContainerHeaders
}
-func (c *Container) isEqualTo(other *Container) bool {
- return other.name == c.name && other.a.isEqualTo(c.a)
+//IsEqualTo returns true if both Container instances refer to the same container.
+func (c *Container) IsEqualTo(other *Container) bool {
+ return other.name == c.name && other.a.IsEqualTo(c.a)
}
//Container returns a handle to the container with the given name within this
diff --git a/errors.go b/errors.go
index 598419a..825e96a 100644
--- a/errors.go
+++ b/errors.go
@@ -54,9 +54,6 @@ var (
//provided is malformed or uses features not supported by the LargeObject's
//strategy. See documentation for LargeObject.AddSegment() for details.
ErrSegmentInvalid = errors.New("segment invalid or incompatible with large object strategy")
- //ErrNotASymlink is returned by Object.SymlinkTarget() if the object in
- //question exists, but is not a symlink.
- ErrNotASymlink = errors.New("not a symlink")
)
//UnexpectedStatusCodeError is generated when a request to Swift does not yield
diff --git a/largeobject.go b/largeobject.go
index 6628279..0d22734 100644
--- a/largeobject.go
+++ b/largeobject.go
@@ -435,7 +435,7 @@ func (o *Object) AsNewLargeObject(sopts SegmentingOptions, topts *TruncateOption
if sopts.SegmentContainer == nil {
panic("missing value for sopts.SegmentingContainer")
}
- if !sopts.SegmentContainer.a.isEqualTo(o.c.a) {
+ if !sopts.SegmentContainer.a.IsEqualTo(o.c.a) {
return nil, ErrAccountMismatch
}
@@ -520,7 +520,7 @@ func (lo *LargeObject) NextSegmentObject() *Object {
if o == nil { //can happen for data segments
continue
}
- if lo.segmentContainer.isEqualTo(o.c) && strings.HasPrefix(o.Name(), lo.segmentPrefix) {
+ if lo.segmentContainer.IsEqualTo(o.c) && strings.HasPrefix(o.Name(), lo.segmentPrefix) {
prevSegmentName = s.Object.Name()
//keep going, we want to find the last such segment
}
@@ -596,7 +596,7 @@ func (lo *LargeObject) AddSegment(segment SegmentInfo) error {
//required attributes
return ErrSegmentInvalid
}
- if !o.c.a.isEqualTo(lo.segmentContainer.a) {
+ if !o.c.a.IsEqualTo(lo.segmentContainer.a) {
return ErrAccountMismatch
}
@@ -607,7 +607,7 @@ func (lo *LargeObject) AddSegment(segment SegmentInfo) error {
return ErrSegmentInvalid
}
- if !o.c.isEqualTo(lo.segmentContainer) {
+ if !o.c.IsEqualTo(lo.segmentContainer) {
return ErrContainerMismatch
}
if !strings.HasPrefix(o.name, lo.segmentPrefix) {
diff --git a/object.go b/object.go
index 55114d6..ee022e3 100644
--- a/object.go
+++ b/object.go
@@ -41,6 +41,11 @@ type Object struct {
symlinkHeaders *ObjectHeaders //from HEAD/GET with ?symlink=get
}
+//IsEqualTo returns true if both Object instances refer to the same object.
+func (o *Object) IsEqualTo(other *Object) bool {
+ return other.name == o.name && other.c.IsEqualTo(o.c)
+}
+
//Object returns a handle to the object with the given name within this
//container. This function does not issue any HTTP requests, and therefore cannot
//ensure that the object exists. Use the Exists() function to check for the
@@ -93,7 +98,7 @@ func (o *Object) Exists() (bool, error) {
//has not been cached yet, a HEAD request is issued on the object.
//
//For symlinks, this operation returns the metadata for the target object. Use
-//Object.InspectSymlink() to obtain the metadata for the symlink instead.
+//Object.SymlinkHeaders() to obtain the metadata for the symlink instead.
//
//This operation fails with http.StatusNotFound if the object does not exist.
func (o *Object) Headers() (ObjectHeaders, error) {
@@ -507,7 +512,7 @@ type SymlinkOptions struct {
func (o *Object) SymlinkTo(target *Object, opts *SymlinkOptions, ropts *RequestOptions) error {
ropts = cloneRequestOptions(ropts, nil)
ropts.Headers.Set("X-Symlink-Target", target.FullName())
- if !target.c.a.isEqualTo(o.c.a) {
+ if !target.c.a.IsEqualTo(o.c.a) {
ropts.Headers.Set("X-Symlink-Target-Account", target.c.a.Name())
}
if ropts.Headers.Get("Content-Type") == "" {
@@ -526,29 +531,41 @@ func (o *Object) SymlinkTo(target *Object, opts *SymlinkOptions, ropts *RequestO
return o.Upload(nil, uopts, ropts)
}
-//InspectSymlink returns the object that this symlink points to, and the
-//metadata of the symlink. ErrNotASymlink is returned if the object is not a
-//symlink.
+//SymlinkHeaders is similar to Headers, but if the object is a symlink, it
+//returns the metadata of the symlink rather than the metadata of the target.
+//It also returns a reference to the target object.
+//
+//If this object is not a symlink, Object.SymlinkHeaders() returns the same
+//ObjectHeaders as Object.Headers(), and a nil target object.
+//
+//In a nutshell, if Object.Headers() is like os.Stat(), then
+//Object.SymlinkHeaders() is like os.Lstat().
+//
+//If you do not know whether a given object is a symlink or not, it's a good
+//idea to call Object.SymlinkHeaders() first: If the object turns out not to be
+//a symlink, the cache for Object.Headers() has already been populated.
//
//This operation fails with http.StatusNotFound if the object does not exist.
-func (o *Object) InspectSymlink() (target *Object, headers ObjectHeaders, err error) {
+func (o *Object) SymlinkHeaders() (headers ObjectHeaders, target *Object, err error) {
if o.symlinkHeaders == nil {
o.symlinkHeaders, err = o.fetchHeaders(&RequestOptions{
Values: url.Values{"symlink": []string{"get"}},
})
if err != nil {
- return nil, ObjectHeaders{}, err
+ return ObjectHeaders{}, nil, err
}
}
//is this a symlink?
targetFullName := o.symlinkHeaders.Get("X-Symlink-Target")
if targetFullName == "" {
- return nil, ObjectHeaders{}, ErrNotASymlink
+ //not a symlink - the o.symlinkHeaders are just the regular headers
+ o.headers = o.symlinkHeaders
+ return *o.headers, nil, nil
}
fields := strings.SplitN(targetFullName, "/", 2)
if len(fields) < 2 {
- return nil, ObjectHeaders{}, MalformedHeaderError{
+ return ObjectHeaders{}, nil, MalformedHeaderError{
Key: "X-Symlink-Target",
ParseError: fmt.Errorf("expected \"container/object\", got \"%s\"", targetFullName),
}
@@ -561,7 +578,7 @@ func (o *Object) InspectSymlink() (target *Object, headers ObjectHeaders, err er
targetAccount = targetAccount.SwitchAccount(accountName)
}
target = targetAccount.Container(fields[0]).Object(fields[1])
- return target, *o.symlinkHeaders, nil
+ return *o.symlinkHeaders, target, nil
}
//URL returns the canonical url for the object in the objectstore
diff --git a/tests/object_test.go b/tests/object_test.go
index 618c835..15aa8f2 100644
--- a/tests/object_test.go
+++ b/tests/object_test.go
@@ -277,21 +277,21 @@ func expectObjectContent(t *testing.T, obj *schwift.Object, expected []byte) {
func expectObjectSymlink(t *testing.T, source, expectedTarget *schwift.Object) {
t.Helper()
- target, _, err := source.InspectSymlink()
+ _, target, err := source.SymlinkHeaders()
if expectedTarget == nil {
switch err {
- case schwift.ErrNotASymlink:
- return //success
case nil:
- t.Errorf("expected %s to not be a symlink, but found symlink to %s\n",
- source.FullName(), target.FullName())
+ if target != nil {
+ t.Errorf("expected %s to not be a symlink, but found symlink to %s\n",
+ source.FullName(), target.FullName())
+ }
default:
- t.Errorf("got unexpected error from Object.SymlinkTarget() for %s: %s\n",
+ t.Errorf("got unexpected error from Object.SymlinkHeaders() for %s: %s\n",
source.FullName(), err.Error())
}
} else {
if err != nil {
- t.Errorf("expected %s to be a symlink to %s, but Object.SymlinkTarget() returned error: %s\n",
+ t.Errorf("expected %s to be a symlink to %s, but Object.SymlinkHeaders() returned error: %s\n",
source.FullName(), expectedTarget.FullName(), err.Error())
} else if target.FullName() != expectedTarget.FullName() {
t.Errorf("expected %s to be a symlink to %s, but got target %s\n",