blob: 39782521ef250fe4366f21ef3da7c6068ed628b3 [file] [log] [blame]
David K. Bainbridge528b3182017-01-23 08:51:59 -08001// Copyright 2012-2016 Canonical Ltd.
2// Licensed under the LGPLv3, see LICENCE file for details.
3
4package gomaasapi
5
6import (
7 "encoding/json"
8 "errors"
9 "fmt"
10 "net/url"
11)
12
13// MAASObject represents a MAAS object as returned by the MAAS API, such as a
14// Node or a Tag.
15// You can extract a MAASObject out of a JSONObject using
16// JSONObject.GetMAASObject. A MAAS API call will usually return either a
17// MAASObject or a list of MAASObjects. The list itself would be wrapped in
18// a JSONObject, so if an API call returns a list of objects "l," you first
19// obtain the array using l.GetArray(). Then, for each item "i" in the array,
20// obtain the matching MAASObject using i.GetMAASObject().
21type MAASObject struct {
22 values map[string]JSONObject
23 client Client
24 uri *url.URL
25}
26
27// newJSONMAASObject creates a new MAAS object. It will panic if the given map
28// does not contain a valid URL for the 'resource_uri' key.
29func newJSONMAASObject(jmap map[string]interface{}, client Client) MAASObject {
30 obj, err := maasify(client, jmap).GetMAASObject()
31 if err != nil {
32 panic(err)
33 }
34 return obj
35}
36
37// MarshalJSON tells the standard json package how to serialize a MAASObject.
38func (obj MAASObject) MarshalJSON() ([]byte, error) {
39 return json.MarshalIndent(obj.GetMap(), "", " ")
40}
41
42// With MarshalJSON, MAASObject implements json.Marshaler.
43var _ json.Marshaler = (*MAASObject)(nil)
44
45func marshalNode(node MAASObject) string {
46 res, _ := json.MarshalIndent(node, "", " ")
47 return string(res)
48
49}
50
51var noResourceURI = errors.New("not a MAAS object: no 'resource_uri' key")
52
53// extractURI obtains the "resource_uri" string from a JSONObject map.
54func extractURI(attrs map[string]JSONObject) (*url.URL, error) {
55 uriEntry, ok := attrs[resourceURI]
56 if !ok {
57 return nil, noResourceURI
58 }
59 uri, err := uriEntry.GetString()
60 if err != nil {
61 return nil, fmt.Errorf("invalid resource_uri: %v", uri)
62 }
63 resourceURL, err := url.Parse(uri)
64 if err != nil {
65 return nil, fmt.Errorf("resource_uri does not contain a valid URL: %v", uri)
66 }
67 return resourceURL, nil
68}
69
70// JSONObject getter for a MAAS object. From a decoding perspective, a
71// MAASObject is just like a map except it contains a key "resource_uri", and
72// it keeps track of the Client you got it from so that you can invoke API
73// methods directly on their MAAS objects.
74func (obj JSONObject) GetMAASObject() (MAASObject, error) {
75 attrs, err := obj.GetMap()
76 if err != nil {
77 return MAASObject{}, err
78 }
79 uri, err := extractURI(attrs)
80 if err != nil {
81 return MAASObject{}, err
82 }
83 return MAASObject{values: attrs, client: obj.client, uri: uri}, nil
84}
85
86// GetField extracts a string field from this MAAS object.
87func (obj MAASObject) GetField(name string) (string, error) {
88 return obj.values[name].GetString()
89}
90
91// URI is the resource URI for this MAAS object. It is an absolute path, but
92// without a network part.
93func (obj MAASObject) URI() *url.URL {
94 // Duplicate the URL.
95 uri, err := url.Parse(obj.uri.String())
96 if err != nil {
97 panic(err)
98 }
99 return uri
100}
101
102// URL returns a full absolute URL (including network part) for this MAAS
103// object on the API.
104func (obj MAASObject) URL() *url.URL {
105 return obj.client.GetURL(obj.URI())
106}
107
108// GetMap returns all of the object's attributes in the form of a map.
109func (obj MAASObject) GetMap() map[string]JSONObject {
110 return obj.values
111}
112
113// GetSubObject returns a new MAASObject representing the API resource found
114// at a given sub-path of the current object's resource URI.
115func (obj MAASObject) GetSubObject(name string) MAASObject {
116 uri := obj.URI()
117 newURL := url.URL{Path: name}
118 resUrl := uri.ResolveReference(&newURL)
119 resUrl.Path = EnsureTrailingSlash(resUrl.Path)
120 input := map[string]interface{}{resourceURI: resUrl.String()}
121 return newJSONMAASObject(input, obj.client)
122}
123
124var NotImplemented = errors.New("Not implemented")
125
126// Get retrieves a fresh copy of this MAAS object from the API.
127func (obj MAASObject) Get() (MAASObject, error) {
128 uri := obj.URI()
129 result, err := obj.client.Get(uri, "", url.Values{})
130 if err != nil {
131 return MAASObject{}, err
132 }
133 jsonObj, err := Parse(obj.client, result)
134 if err != nil {
135 return MAASObject{}, err
136 }
137 return jsonObj.GetMAASObject()
138}
139
140// Post overwrites this object's existing value on the API with those given
141// in "params." It returns the object's new value as received from the API.
142func (obj MAASObject) Post(params url.Values) (JSONObject, error) {
143 uri := obj.URI()
144 result, err := obj.client.Post(uri, "", params, nil)
145 if err != nil {
146 return JSONObject{}, err
147 }
148 return Parse(obj.client, result)
149}
150
151// Update modifies this object on the API, based on the values given in
152// "params." It returns the object's new value as received from the API.
153func (obj MAASObject) Update(params url.Values) (MAASObject, error) {
154 uri := obj.URI()
155 result, err := obj.client.Put(uri, params)
156 if err != nil {
157 return MAASObject{}, err
158 }
159 jsonObj, err := Parse(obj.client, result)
160 if err != nil {
161 return MAASObject{}, err
162 }
163 return jsonObj.GetMAASObject()
164}
165
166// Delete removes this object on the API.
167func (obj MAASObject) Delete() error {
168 uri := obj.URI()
169 return obj.client.Delete(uri)
170}
171
172// CallGet invokes an idempotent API method on this object.
173func (obj MAASObject) CallGet(operation string, params url.Values) (JSONObject, error) {
174 uri := obj.URI()
175 result, err := obj.client.Get(uri, operation, params)
176 if err != nil {
177 return JSONObject{}, err
178 }
179 return Parse(obj.client, result)
180}
181
182// CallPost invokes a non-idempotent API method on this object.
183func (obj MAASObject) CallPost(operation string, params url.Values) (JSONObject, error) {
184 return obj.CallPostFiles(operation, params, nil)
185}
186
187// CallPostFiles invokes a non-idempotent API method on this object. It is
188// similar to CallPost but has an extra parameter, 'files', which should
189// contain the files that will be uploaded to the API.
190func (obj MAASObject) CallPostFiles(operation string, params url.Values, files map[string][]byte) (JSONObject, error) {
191 uri := obj.URI()
192 result, err := obj.client.Post(uri, operation, params, files)
193 if err != nil {
194 return JSONObject{}, err
195 }
196 return Parse(obj.client, result)
197}