diff options
| -rw-r--r-- | CONTRIBUTING.md | 3 | ||||
| -rw-r--r-- | account.go | 3 | ||||
| -rw-r--r-- | bulk.go | 4 | ||||
| -rw-r--r-- | capabilities.go | 3 | ||||
| -rw-r--r-- | container.go | 5 | ||||
| -rw-r--r-- | errors.go | 3 | ||||
| -rw-r--r-- | largeobject.go | 8 | ||||
| -rw-r--r-- | object.go | 37 | ||||
| -rw-r--r-- | tests/object_test.go | 14 |
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. @@ -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 } @@ -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 @@ -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) { @@ -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", |
