Skip to content

Commit

Permalink
Merge pull request #464 from restic/rest-backend
Browse files Browse the repository at this point in the history
Add REST backend
  • Loading branch information
fd0 committed Feb 22, 2016
2 parents 9a82228 + 921c2f6 commit 5dd65a5
Show file tree
Hide file tree
Showing 13 changed files with 612 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/bin
/restic
/.vagrant
/vendor/pkg
59 changes: 59 additions & 0 deletions doc/REST_backend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
REST Backend
============

Restic can interact with HTTP Backend that respects the following REST API. The
following values are valid for `{type}`: `data`, `keys`, `locks`, `snapshots`,
`index`, `config`. `{path}` is a path to the repository, so that multiple
different repositories can be accessed. The default path is `/`.

## HEAD {path}/config

Returns "200 OK" if the repository has a configuration,
an HTTP error otherwise.

## GET {path}/config

Returns the content of the configuration file if the repository has a configuration,
an HTTP error otherwise.

Response format: binary/octet-stream

## POST {path}/config

Returns "200 OK" if the configuration of the request body has been saved,
an HTTP error otherwise.

## GET {path}/{type}/

Returns a JSON array containing the names of all the blobs stored for a given type.

Response format: JSON

## HEAD {path}/{type}/{name}

Returns "200 OK" if the blob with the given name and type is stored in the repository,
"404 not found" otherwise. If the blob exists, the HTTP header `Content-Length`
is set to the file size.

## GET {path}/{type}/{name}

Returns the content of the blob with the given name and type if it is stored in the repository,
"404 not found" otherwise.

If the request specifies a partial read with a Range header field,
then the status code of the response is 206 instead of 200
and the response only contains the specified range.

Response format: binary/octet-stream

## POST {path}/{type}/{name}

Saves the content of the request body as a blob with the given name and type,
an HTTP error otherwise.

Request format: binary/octet-stream

## DELETE {path}/{type}/{name}

Returns "200 OK" if the blob with the given name and type has been deleted from the repository,
an HTTP error otherwise.
87 changes: 87 additions & 0 deletions src/restic/backend/rest/backend_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// DO NOT EDIT, AUTOMATICALLY GENERATED
package rest_test

import (
"testing"

"restic/backend/test"
)

var SkipMessage string

func TestRestBackendCreate(t *testing.T) {
if SkipMessage != "" {
t.Skip(SkipMessage)
}
test.TestCreate(t)
}

func TestRestBackendOpen(t *testing.T) {
if SkipMessage != "" {
t.Skip(SkipMessage)
}
test.TestOpen(t)
}

func TestRestBackendCreateWithConfig(t *testing.T) {
if SkipMessage != "" {
t.Skip(SkipMessage)
}
test.TestCreateWithConfig(t)
}

func TestRestBackendLocation(t *testing.T) {
if SkipMessage != "" {
t.Skip(SkipMessage)
}
test.TestLocation(t)
}

func TestRestBackendConfig(t *testing.T) {
if SkipMessage != "" {
t.Skip(SkipMessage)
}
test.TestConfig(t)
}

func TestRestBackendLoad(t *testing.T) {
if SkipMessage != "" {
t.Skip(SkipMessage)
}
test.TestLoad(t)
}

func TestRestBackendSave(t *testing.T) {
if SkipMessage != "" {
t.Skip(SkipMessage)
}
test.TestSave(t)
}

func TestRestBackendSaveFilenames(t *testing.T) {
if SkipMessage != "" {
t.Skip(SkipMessage)
}
test.TestSaveFilenames(t)
}

func TestRestBackendBackend(t *testing.T) {
if SkipMessage != "" {
t.Skip(SkipMessage)
}
test.TestBackend(t)
}

func TestRestBackendDelete(t *testing.T) {
if SkipMessage != "" {
t.Skip(SkipMessage)
}
test.TestDelete(t)
}

func TestRestBackendCleanup(t *testing.T) {
if SkipMessage != "" {
t.Skip(SkipMessage)
}
test.TestCleanup(t)
}
29 changes: 29 additions & 0 deletions src/restic/backend/rest/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package rest

import (
"errors"
"net/url"
"strings"
)

// Config contains all configuration necessary to connect to a REST server.
type Config struct {
URL *url.URL
}

// ParseConfig parses the string s and extracts the REST server URL.
func ParseConfig(s string) (interface{}, error) {
if !strings.HasPrefix(s, "rest:") {
return nil, errors.New("invalid REST backend specification")
}

s = s[5:]
u, err := url.Parse(s)

if err != nil {
return nil, err
}

cfg := Config{URL: u}
return cfg, nil
}
41 changes: 41 additions & 0 deletions src/restic/backend/rest/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package rest

import (
"net/url"
"reflect"
"testing"
)

func parseURL(s string) *url.URL {
u, err := url.Parse(s)
if err != nil {
panic(err)
}

return u
}

var configTests = []struct {
s string
cfg Config
}{
{"rest:http://localhost:1234", Config{
URL: parseURL("http://localhost:1234"),
}},
}

func TestParseConfig(t *testing.T) {
for i, test := range configTests {
cfg, err := ParseConfig(test.s)
if err != nil {
t.Errorf("test %d:%s failed: %v", i, test.s, err)
continue
}

if !reflect.DeepEqual(cfg, test.cfg) {
t.Errorf("test %d:\ninput:\n %s\n wrong config, want:\n %v\ngot:\n %v",
i, test.s, test.cfg, cfg)
continue
}
}
}

0 comments on commit 5dd65a5

Please sign in to comment.