Source: google/metadata_server.js

/**
 * @module google/metadata_server
 */

var http = require('http');

/**
 * Google Cloud Metadata Server API convenience object. Metadata server stores
 * metadata configured for each instance, this object allows you to easily communicate
 * with Cliques instance metadata servers to easily retrieve metadata for an instance.
 *
 * For more information on Metadata servers, see
 * [the Google Cloud Metadata Server Docs](https://cloud.google.com/compute/docs/storing-retrieving-metadata).
 *
 * @class
 * @param {Object} [options] options object
 * @param {String} [options.apiVersion='v1'] Google Metadata Server API version
 * @param {String} [options.hostname='metadata.google.internal'] API hostname
 * @param {String} [options.basepath='computeMetadata'] Basepath from API hostname
 * @param {Object} [options.defaultHeaders={"Metadata-Flavor":"Google"}]
 * @type {exports.MetadataServerAPI}
 */
var MetadataServerAPI = exports.MetadataServerAPI = function(options){
    options = options || {};
    this.apiVersion    = options.apiVersion || 'v1';
    this.hostname       = 'metadata.google.internal';
    this.basePath       = 'computeMetadata';
    this.defaultHeaders = {
        "Metadata-Flavor": "Google"
    };
};

/**
 * Helper function to take boilerplate code out of request options generation.
 *
 * Takes a simplified options object and transforms to full options object
 * to pass to http.request
 *
 * @param {Array} path_array base API path
 * @param {Object} options simplified options object specific to this instance
 * @param {String} [options.method='GET'] http method, defaults to 'GET'
 * @param {Object} [options.query] query object, e.g. {k: 'v', k2: 'v2'}
 * @param {Object} [options.headers] any additional headers to pass into request
 * @returns {Object} Augmented options object to pass to http.request
 * @private
 */
MetadataServerAPI.prototype._getOptions = function(path_array, options){
    options     = options || {};
    var method  = options.method || 'GET';
    var query   = options.query;
    var headers = options.headers;

    var root_path_arr = ['',this.basePath, this.apiVersion];
    var full_path_arr = root_path_arr.concat(path_array);
    var path = full_path_arr.join('/');
    if (query){
        path = [path, querystring.stringify(query)].join('?');
    }
    var new_options = {
        path:       path,
        hostname:   this.hostname,
        method:     method,
        headers:    this.defaultHeaders
    };
    if (headers){
        Object.keys(headers).forEach(function(key){
            if (headers.hasOwnProperty(key)){
                new_options.headers[key] = headers[key]
            }
        })
    }
    return new_options;
};

/**
 * Wrapper function to be used to send any API requests
 *
 * @param {Array} path_array path of API resource, provided as array
 * @param {Object} [options] optional additional options object.
 *       Passed to _getOptions, see docstring there for details
 * @param {Object} [data] optional POST or PUT JSON data.  If provided,
 *       options.method arg must be set
 * @param callback
 * @private
 */
MetadataServerAPI.prototype._sendAPIRequest = function(path_array, options, data, callback){
    // tedious handling of optional arguments
    if (arguments.length == 2) {
        callback = options;
        data = false;
        options = {}
    } else if (arguments.length == 3){
        callback = data;
        data = false;
    } else {
        // Set default JSON data headers, if data is provided
        data = JSON.stringify(data);
        if (!options.hasOwnProperty("headers")) {
            options.headers = {};
        }
        options.headers["Content-Type"] = "application/json";
        options.headers["Content-Length"] = data.length;
    }
    var new_options = this._getOptions(path_array, options);

    //options string just for returning to callback error handler
    var options_str = 'path: '+new_options.path+', hostname: '+new_options.hostname+
        ', port: '+new_options.port+', method: '+new_options.method;

    // now send request
    var req = http.request(new_options, function(res){
        if (res.statusCode == "400"){
            return callback('HTTP ERROR: 400 REST API Request Error at ' + options_str)
        }
        // handle response body data, pass to callback as JSON
        var body = '';
        res.on('data', function(chunk){
            body += chunk;
        });
        res.on('end', function(){
            return callback(null, body);
        });
    });

    // add error handler
    req.on("error", function(e){
        callback(e + ", Request options: " + options_str);
    });

    // write stringified JSON data, if any
    if (data){
        req.write(data);
    }
    req.end();
};

MetadataServerAPI.prototype.getInstanceMetadataVal = function(key, callback){
    var path = ['instance', 'attributes', key];
    this._sendAPIRequest(path, callback);
};