blob: 39782521ef250fe4366f21ef3da7c6068ed628b3 [file] [log] [blame]
// Copyright 2012-2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package gomaasapi
import (
"encoding/json"
"errors"
"fmt"
"net/url"
)
// MAASObject represents a MAAS object as returned by the MAAS API, such as a
// Node or a Tag.
// You can extract a MAASObject out of a JSONObject using
// JSONObject.GetMAASObject. A MAAS API call will usually return either a
// MAASObject or a list of MAASObjects. The list itself would be wrapped in
// a JSONObject, so if an API call returns a list of objects "l," you first
// obtain the array using l.GetArray(). Then, for each item "i" in the array,
// obtain the matching MAASObject using i.GetMAASObject().
type MAASObject struct {
values map[string]JSONObject
client Client
uri *url.URL
}
// newJSONMAASObject creates a new MAAS object. It will panic if the given map
// does not contain a valid URL for the 'resource_uri' key.
func newJSONMAASObject(jmap map[string]interface{}, client Client) MAASObject {
obj, err := maasify(client, jmap).GetMAASObject()
if err != nil {
panic(err)
}
return obj
}
// MarshalJSON tells the standard json package how to serialize a MAASObject.
func (obj MAASObject) MarshalJSON() ([]byte, error) {
return json.MarshalIndent(obj.GetMap(), "", " ")
}
// With MarshalJSON, MAASObject implements json.Marshaler.
var _ json.Marshaler = (*MAASObject)(nil)
func marshalNode(node MAASObject) string {
res, _ := json.MarshalIndent(node, "", " ")
return string(res)
}
var noResourceURI = errors.New("not a MAAS object: no 'resource_uri' key")
// extractURI obtains the "resource_uri" string from a JSONObject map.
func extractURI(attrs map[string]JSONObject) (*url.URL, error) {
uriEntry, ok := attrs[resourceURI]
if !ok {
return nil, noResourceURI
}
uri, err := uriEntry.GetString()
if err != nil {
return nil, fmt.Errorf("invalid resource_uri: %v", uri)
}
resourceURL, err := url.Parse(uri)
if err != nil {
return nil, fmt.Errorf("resource_uri does not contain a valid URL: %v", uri)
}
return resourceURL, nil
}
// JSONObject getter for a MAAS object. From a decoding perspective, a
// MAASObject is just like a map except it contains a key "resource_uri", and
// it keeps track of the Client you got it from so that you can invoke API
// methods directly on their MAAS objects.
func (obj JSONObject) GetMAASObject() (MAASObject, error) {
attrs, err := obj.GetMap()
if err != nil {
return MAASObject{}, err
}
uri, err := extractURI(attrs)
if err != nil {
return MAASObject{}, err
}
return MAASObject{values: attrs, client: obj.client, uri: uri}, nil
}
// GetField extracts a string field from this MAAS object.
func (obj MAASObject) GetField(name string) (string, error) {
return obj.values[name].GetString()
}
// URI is the resource URI for this MAAS object. It is an absolute path, but
// without a network part.
func (obj MAASObject) URI() *url.URL {
// Duplicate the URL.
uri, err := url.Parse(obj.uri.String())
if err != nil {
panic(err)
}
return uri
}
// URL returns a full absolute URL (including network part) for this MAAS
// object on the API.
func (obj MAASObject) URL() *url.URL {
return obj.client.GetURL(obj.URI())
}
// GetMap returns all of the object's attributes in the form of a map.
func (obj MAASObject) GetMap() map[string]JSONObject {
return obj.values
}
// GetSubObject returns a new MAASObject representing the API resource found
// at a given sub-path of the current object's resource URI.
func (obj MAASObject) GetSubObject(name string) MAASObject {
uri := obj.URI()
newURL := url.URL{Path: name}
resUrl := uri.ResolveReference(&newURL)
resUrl.Path = EnsureTrailingSlash(resUrl.Path)
input := map[string]interface{}{resourceURI: resUrl.String()}
return newJSONMAASObject(input, obj.client)
}
var NotImplemented = errors.New("Not implemented")
// Get retrieves a fresh copy of this MAAS object from the API.
func (obj MAASObject) Get() (MAASObject, error) {
uri := obj.URI()
result, err := obj.client.Get(uri, "", url.Values{})
if err != nil {
return MAASObject{}, err
}
jsonObj, err := Parse(obj.client, result)
if err != nil {
return MAASObject{}, err
}
return jsonObj.GetMAASObject()
}
// Post overwrites this object's existing value on the API with those given
// in "params." It returns the object's new value as received from the API.
func (obj MAASObject) Post(params url.Values) (JSONObject, error) {
uri := obj.URI()
result, err := obj.client.Post(uri, "", params, nil)
if err != nil {
return JSONObject{}, err
}
return Parse(obj.client, result)
}
// Update modifies this object on the API, based on the values given in
// "params." It returns the object's new value as received from the API.
func (obj MAASObject) Update(params url.Values) (MAASObject, error) {
uri := obj.URI()
result, err := obj.client.Put(uri, params)
if err != nil {
return MAASObject{}, err
}
jsonObj, err := Parse(obj.client, result)
if err != nil {
return MAASObject{}, err
}
return jsonObj.GetMAASObject()
}
// Delete removes this object on the API.
func (obj MAASObject) Delete() error {
uri := obj.URI()
return obj.client.Delete(uri)
}
// CallGet invokes an idempotent API method on this object.
func (obj MAASObject) CallGet(operation string, params url.Values) (JSONObject, error) {
uri := obj.URI()
result, err := obj.client.Get(uri, operation, params)
if err != nil {
return JSONObject{}, err
}
return Parse(obj.client, result)
}
// CallPost invokes a non-idempotent API method on this object.
func (obj MAASObject) CallPost(operation string, params url.Values) (JSONObject, error) {
return obj.CallPostFiles(operation, params, nil)
}
// CallPostFiles invokes a non-idempotent API method on this object. It is
// similar to CallPost but has an extra parameter, 'files', which should
// contain the files that will be uploaded to the API.
func (obj MAASObject) CallPostFiles(operation string, params url.Values, files map[string][]byte) (JSONObject, error) {
uri := obj.URI()
result, err := obj.client.Post(uri, operation, params, files)
if err != nil {
return JSONObject{}, err
}
return Parse(obj.client, result)
}