1
// Copyright 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
14
"launchpad.net/juju-core/environs"
15
"launchpad.net/juju-core/errors"
16
"launchpad.net/juju-core/utils"
19
// storage implements the environs.Storage interface.
24
// Client returns a storage object that will talk to the storage server
25
// at the given network address (see Serve)
26
func Client(addr string) environs.Storage {
28
baseURL: fmt.Sprintf("http://%s/", addr),
32
// Get opens the given storage file and returns a ReadCloser
33
// that can be used to read its contents. It is the caller's
34
// responsibility to close it after use. If the name does not
35
// exist, it should return a *NotFoundError.
36
func (s *storage) Get(name string) (io.ReadCloser, error) {
37
url, err := s.URL(name)
41
resp, err := http.Get(url)
45
if resp.StatusCode != http.StatusOK {
46
return nil, errors.NotFoundf("file %q", name)
51
// List lists all names in the storage with the given prefix, in
52
// alphabetical order. The names in the storage are considered
53
// to be in a flat namespace, so the prefix may include slashes
54
// and the names returned are the full names for the matching
56
func (s *storage) List(prefix string) ([]string, error) {
57
url, err := s.URL(prefix)
61
resp, err := http.Get(url + "*")
65
if resp.StatusCode != http.StatusOK {
66
return nil, fmt.Errorf("%s", resp.Status)
68
defer resp.Body.Close()
69
body, err := ioutil.ReadAll(resp.Body)
76
names := strings.Split(string(body), "\n")
81
// URL returns an URL that can be used to access the given storage file.
82
func (s *storage) URL(name string) (string, error) {
83
return s.baseURL + name, nil
86
// ConsistencyStrategy is specified in the StorageReader interface.
87
func (s *storage) ConsistencyStrategy() utils.AttemptStrategy {
88
return utils.AttemptStrategy{}
91
// Put reads from r and writes to the given storage file.
92
// The length must be set to the total length of the file.
93
func (s *storage) Put(name string, r io.Reader, length int64) error {
94
url, err := s.URL(name)
98
// Here we wrap up the reader. For some freaky unexplainable reason, the
99
// http library will call Close on the reader if it has a Close method
100
// available. Since we sometimes reuse the reader, especially when
101
// putting tools, we don't want Close called. So we wrap the reader in a
102
// struct so the Close method is not exposed.
103
justReader := struct{ io.Reader }{r}
104
req, err := http.NewRequest("PUT", url, justReader)
108
req.Header.Set("Content-Type", "application/octet-stream")
109
resp, err := http.DefaultClient.Do(req)
113
if resp.StatusCode != 201 {
114
return fmt.Errorf("%d %s", resp.StatusCode, resp.Status)
119
// Remove removes the given file from the environment's
120
// storage. It should not return an error if the file does
122
func (s *storage) Remove(name string) error {
123
url, err := s.URL(name)
127
req, err := http.NewRequest("DELETE", url, nil)
131
resp, err := http.DefaultClient.Do(req)
135
if resp.StatusCode != http.StatusOK {
136
return fmt.Errorf("%d %s", resp.StatusCode, resp.Status)
141
func (s *storage) RemoveAll() error {
142
return environs.RemoveAll(s)