blob: 63fb854b3117877d0f99624bab8322f077cd97d9 [file] [log] [blame]
David K. Bainbridge528b3182017-01-23 08:51:59 -08001// Copyright 2016 Canonical Ltd.
2// Licensed under the LGPLv3, see LICENCE file for details.
3
4package gomaasapi
5
6import (
7 "encoding/base64"
8 "net/http"
9 "net/url"
10
11 "github.com/juju/errors"
12 "github.com/juju/schema"
13 "github.com/juju/version"
14)
15
16type file struct {
17 controller *controller
18
19 resourceURI string
20 filename string
21 anonymousURI *url.URL
22 content string
23}
24
25// Filename implements File.
26func (f *file) Filename() string {
27 return f.filename
28}
29
30// AnonymousURL implements File.
31func (f *file) AnonymousURL() string {
32 url := f.controller.client.GetURL(f.anonymousURI)
33 return url.String()
34}
35
36// Delete implements File.
37func (f *file) Delete() error {
38 err := f.controller.delete(f.resourceURI)
39 if err != nil {
40 if svrErr, ok := errors.Cause(err).(ServerError); ok {
41 switch svrErr.StatusCode {
42 case http.StatusNotFound:
43 return errors.Wrap(err, NewNoMatchError(svrErr.BodyMessage))
44 case http.StatusForbidden:
45 return errors.Wrap(err, NewPermissionError(svrErr.BodyMessage))
46 }
47 }
48 return NewUnexpectedError(err)
49 }
50 return nil
51}
52
53// ReadAll implements File.
54func (f *file) ReadAll() ([]byte, error) {
55 if f.content == "" {
56 return f.readFromServer()
57 }
58 bytes, err := base64.StdEncoding.DecodeString(f.content)
59 if err != nil {
60 return nil, NewUnexpectedError(err)
61 }
62 return bytes, nil
63}
64
65func (f *file) readFromServer() ([]byte, error) {
66 // If the content is available, it is base64 encoded, so
67 args := make(url.Values)
68 args.Add("filename", f.filename)
69 bytes, err := f.controller._getRaw("files", "get", args)
70 if err != nil {
71 if svrErr, ok := errors.Cause(err).(ServerError); ok {
72 switch svrErr.StatusCode {
73 case http.StatusNotFound:
74 return nil, errors.Wrap(err, NewNoMatchError(svrErr.BodyMessage))
75 case http.StatusForbidden:
76 return nil, errors.Wrap(err, NewPermissionError(svrErr.BodyMessage))
77 }
78 }
79 return nil, NewUnexpectedError(err)
80 }
81 return bytes, nil
82}
83
84func readFiles(controllerVersion version.Number, source interface{}) ([]*file, error) {
85 readFunc, err := getFileDeserializationFunc(controllerVersion)
86 if err != nil {
87 return nil, errors.Trace(err)
88 }
89
90 checker := schema.List(schema.StringMap(schema.Any()))
91 coerced, err := checker.Coerce(source, nil)
92 if err != nil {
93 return nil, WrapWithDeserializationError(err, "file base schema check failed")
94 }
95 valid := coerced.([]interface{})
96 return readFileList(valid, readFunc)
97}
98
99func readFile(controllerVersion version.Number, source interface{}) (*file, error) {
100 readFunc, err := getFileDeserializationFunc(controllerVersion)
101 if err != nil {
102 return nil, errors.Trace(err)
103 }
104
105 checker := schema.StringMap(schema.Any())
106 coerced, err := checker.Coerce(source, nil)
107 if err != nil {
108 return nil, WrapWithDeserializationError(err, "file base schema check failed")
109 }
110 valid := coerced.(map[string]interface{})
111 return readFunc(valid)
112}
113
114func getFileDeserializationFunc(controllerVersion version.Number) (fileDeserializationFunc, error) {
115 var deserialisationVersion version.Number
116 for v := range fileDeserializationFuncs {
117 if v.Compare(deserialisationVersion) > 0 && v.Compare(controllerVersion) <= 0 {
118 deserialisationVersion = v
119 }
120 }
121 if deserialisationVersion == version.Zero {
122 return nil, NewUnsupportedVersionError("no file read func for version %s", controllerVersion)
123 }
124 return fileDeserializationFuncs[deserialisationVersion], nil
125}
126
127// readFileList expects the values of the sourceList to be string maps.
128func readFileList(sourceList []interface{}, readFunc fileDeserializationFunc) ([]*file, error) {
129 result := make([]*file, 0, len(sourceList))
130 for i, value := range sourceList {
131 source, ok := value.(map[string]interface{})
132 if !ok {
133 return nil, NewDeserializationError("unexpected value for file %d, %T", i, value)
134 }
135 file, err := readFunc(source)
136 if err != nil {
137 return nil, errors.Annotatef(err, "file %d", i)
138 }
139 result = append(result, file)
140 }
141 return result, nil
142}
143
144type fileDeserializationFunc func(map[string]interface{}) (*file, error)
145
146var fileDeserializationFuncs = map[version.Number]fileDeserializationFunc{
147 twoDotOh: file_2_0,
148}
149
150func file_2_0(source map[string]interface{}) (*file, error) {
151 fields := schema.Fields{
152 "resource_uri": schema.String(),
153 "filename": schema.String(),
154 "anon_resource_uri": schema.String(),
155 "content": schema.String(),
156 }
157 defaults := schema.Defaults{
158 "content": "",
159 }
160 checker := schema.FieldMap(fields, defaults)
161 coerced, err := checker.Coerce(source, nil)
162 if err != nil {
163 return nil, WrapWithDeserializationError(err, "file 2.0 schema check failed")
164 }
165 valid := coerced.(map[string]interface{})
166 // From here we know that the map returned from the schema coercion
167 // contains fields of the right type.
168
169 anonURI, err := url.ParseRequestURI(valid["anon_resource_uri"].(string))
170 if err != nil {
171 return nil, NewUnexpectedError(err)
172 }
173
174 result := &file{
175 resourceURI: valid["resource_uri"].(string),
176 filename: valid["filename"].(string),
177 anonymousURI: anonURI,
178 content: valid["content"].(string),
179 }
180 return result, nil
181}