Browse Source

More work on ZT1 NodeJS API client library.

Adam Ierymenko 10 years ago
parent
commit
c075e68c6c
3 changed files with 186 additions and 57 deletions
  1. 58 0
      js/zt1-api-client/constrain-types.js
  2. 125 54
      js/zt1-api-client/index.js
  3. 3 3
      js/zt1-api-client/test.js

+ 58 - 0
js/zt1-api-client/constrain-types.js

@@ -0,0 +1,58 @@
+'use strict'
+
+function convertType(v,t)
+{
+	if (Array.isArray(t)) {
+		var r = v;
+		if (t.length !== 0) {
+			if (Array.isArray(v)) {
+				r = [];
+				for(var i=0;i<v.length;++i)
+					r.push(convertType(v[i],t[0]));
+			} else r = [ convertType(v,t[0]) ];
+		} else r = [ v ];
+		return r;
+	} else if (t === 'string') {
+		if (typeof v === 'string')
+			return v;
+		else if ((typeof v === 'boolean')||(typeof v === 'number'))
+			return v.toString();
+		else if (Array.isArray(v)||(typeof v === 'object'))
+			return JSON.stringify(v);
+		else return '';
+	} else if (t === 'integer') {
+		if (typeof v === 'number')
+			return Math.round(v);
+		else if (typeof v === 'string')
+			return parseInt(v);
+		else if (typeof v === 'boolean')
+			return ((v) ? 1 : 0);
+		else return 0;
+	} else if (t === 'number') {
+		if (typeof v === 'number')
+			return v;
+		else if (typeof v === 'string')
+			return parseFloat(v);
+		else if (typeof v === 'boolean')
+			return ((v) ? 1 : 0);
+		else return 0;
+	} else if (t === 'boolean') {
+		return ((v) ? true : false);
+	} else if (typeof t === 'object') {
+		if ((v !== null)&&(typeof v === 'object'))
+			return constrainTypes(v,t);
+		else return {};
+	} else return v;
+}
+
+function constrainTypes(obj,typeMap)
+{
+	var r = {};
+	for(var k in obj) {
+		var t = typeMap[k];
+		r[k] = convertType(v,t);
+	}
+	return r;
+}
+
+exports = constrainTypes;

+ 125 - 54
js/zt1-api-client/index.js

@@ -1,49 +1,103 @@
 'use strict'
 
 var request = require('request');
+var constrainTypes = require('./constrain-types.js');
 
+// Types that fields must be in submissions -- used with constrainTypes to
+// ensure that submitted JSON objects are correctly typed since the JSON
+// API is very sensitive to this. This only includes writable fields since
+// non-writable and unknown fields are ignored.
+var REQUEST_TYPE_MAPS = {
+	'controller/network/*/relay': {
+		'address': 'string',
+		'phyAddress': 'string'
+	},
+	'controller/network/*/rule': {
+		'ruleId': 'integer',
+		'nodeId': 'string',
+		'vlanId': 'integer',
+		'vlanPcp': 'integer',
+		'etherType': 'integer',
+		'macSource': 'string',
+		'macDest': 'string',
+		'ipSource': 'string',
+		'ipDest': 'string',
+		'ipTos': 'integer',
+		'ipProtocol': 'integer',
+		'ipSourcePort': 'integer',
+		'ipDestPort': 'integer',
+		'flags': 'integer',
+		'invFlags': 'integer',
+		'action': 'string'
+	},
+	'controller/network/*/ipAssignmentPool': {
+		'network': 'string',
+		'netmaskBits': 'integer'
+	},
+	'controller/network/*/member': {
+		'authorized': 'boolean',
+		'activeBridge': 'boolean',
+		'ipAssignments': [ 'string' ]
+	},
+	'controller/network/*': {
+		'name': 'string',
+		'private': 'boolean',
+		'enableBroadcast': 'boolean',
+		'allowPassiveBridging': 'boolean',
+		'v4AssignMode': 'string',
+		'v6AssignMode': 'string',
+		'multicastLimit': 'integer',
+		'relays': [ this['controller/network/*/relay'] ],
+		'ipAssignmentPools': [ this['controller/network/*/ipAssignmentPool'] ],
+		'rules': [ this['controller/network/*/rule'] ]
+	}
+};
+
+// URL must end with trailing slash e.g. http://127.0.0.1:9993/
 function ZT1ApiClient(url,authToken)
 {
 	this.url = url;
 	this.authToken = authToken;
 }
 
-// Generate new ZeroTier identity -- mostly for testing
-ZT1ApiClient.prototype.newIdentity = function(callback)
+// Simple JSON URI getter, for internal use.
+ZT1ApiClient.prototype._jsonGet = function(getPath,callback)
 {
 	request({
-		url: this.url + 'newIdentity',
+		url: this.url + getPath,
 		method: 'GET',
-		json: false,
 		headers: {
 			'X-ZT1-Auth': this.authToken
 		}
 	},function(error,response,body) {
 		if (error)
 			return callback(error,null);
-		if (response.statusCode === 200)
-			return callback(null,body);
-		return callback(new Error('server responded with error: '+response.statusCode),'');
+		if (response.statusCode !== 200)
+			return callback(new Error('server responded with error: '+response.statusCode),null);
+		return callback(null,(typeof body === 'string') ? JSON.parse(body) : null);
 	});
-}
+};
 
-ZT1ApiClient.prototype._jsonGet = function(getPath,callback)
+// Generate new ZeroTier identity -- mostly for testing
+ZT1ApiClient.prototype.newIdentity = function(callback)
 {
 	request({
-		url: this.url + getPath,
+		url: this.url + 'newIdentity',
 		method: 'GET',
+		json: false,
 		headers: {
 			'X-ZT1-Auth': this.authToken
 		}
 	},function(error,response,body) {
 		if (error)
 			return callback(error,null);
-		if (response.statusCode !== 200)
-			return callback(new Error('server responded with error: '+response.statusCode),null);
-		return callback(null,(typeof body === 'string') ? JSON.parse(body) : null);
+		if (response.statusCode === 200)
+			return callback(null,body);
+		return callback(new Error('server responded with error: '+response.statusCode),'');
 	});
-};
+}
 
+// Get node status -- returns a combination of regular status and (if present) controller info
 ZT1ApiClient.prototype.status = function(callback)
 {
 	request({
@@ -54,9 +108,9 @@ ZT1ApiClient.prototype.status = function(callback)
 		}
 	},function(error,response,body) {
 		if (error)
-			return callback(error,{});
+			return callback(error,null);
 		var controllerStatus = {};
-		if (typeof body === 'string')
+		if ((typeof body === 'string')&&(response.statusCode === 200))
 			controllerStatus = JSON.parse(body);
 		request({
 			url: this.url + 'status',
@@ -97,50 +151,15 @@ ZT1ApiClient.prototype.getControllerNetwork = function(nwid,callback)
 	this._jsonGet('controller/network/' + nwid,callback);
 };
 
+// If NWID is the special ##########______ format, a new NWID will
+// be generated server side and filled in in returned object.
 ZT1ApiClient.prototype.saveControllerNetwork = function(network,callback)
 {
-	if ((typeof network.nwid !== 'string')||(network.nwid.length !== 16))
-		return callback(new Error('Missing required field: nwid'),null);
-
-	// The ZT1 service is type variation intolerant, so recreate our submission with the correct types
-	var n = {
-		nwid: network.nwid
-	};
-	if (network.name)
-		n.name = network.name.toString();
-	if ('private' in network)
-		n.private = (network.private) ? true : false;
-	if ('enableBroadcast' in network)
-		n.enableBroadcast = (network.enableBroadcast) ? true : false;
-	if ('allowPassiveBridging' in network)
-		n.allowPassiveBridging = (network.allowPassiveBridging) ? true : false;
-	if ('v4AssignMode' in network) {
-		if (network.v4AssignMode)
-			n.v4AssignMode = network.v4AssignMode.toString();
-		else n.v4AssignMode = 'none';
-	}
-	if ('v6AssignMode' in network) {
-		if (network.v6AssignMode)
-			n.v6AssignMode = network.v6AssignMode.toString();
-		else n.v4AssignMode = 'none';
-	}
-	if ('multicastLimit' in network) {
-		if (typeof network.multicastLimit === 'number')
-			n.multicastLimit = network.multicastLimit;
-		else n.multicastLimit = parseInt(network.multicastLimit.toString());
-	}
-	if (Array.isArray(network.relays))
-		n.relays = network.relays;
-	if (Array.isArray(network.ipAssignmentPools))
-		n.ipAssignmentPools = network.ipAssignmentPools;
-	if (Array.isArray(network.rules))
-		n.rules = network.rules;
-
 	request({
 		url: this.url + 'controller/network/' + n.nwid,
 		method: 'POST',
 		json: true,
-		body: n,
+		body: constrainTypes(network,REQUEST_TYPE_MAPS['controller/network/*']),
 		headers: {
 			'X-ZT1-Auth': this.authToken
 		}
@@ -153,8 +172,60 @@ ZT1ApiClient.prototype.saveControllerNetwork = function(network,callback)
 	});
 };
 
+ZT1ApiClient.prototype.deleteControllerNetwork = function(nwid,callback) {
+	request({
+		url: this.url + 'controller/network/'+ nwid,
+		method: 'DELETE',
+		headers: {
+			'X-ZT1-Auth': this.authToken
+		}
+	},function(err,response,body) {
+		if (err)
+			return callback(err);
+		else if (response.statusCode === 200)
+			return callback(null);
+		else return callback(new Error('server responded with error: '+response.statusCode));
+	});
+};
+
 ZT1ApiClient.prototype.getControllerNetworkMember = function(nwid,address,callback) {
 	this._jsonGet('controller/network/' + nwid + '/member/' + address,callback);
 };
 
+ZT1ApiClient.prototype.saveControllerNetworkMember = function(nwid,member,callback) {
+	var m = constrainTypes(member,REQUEST_TYPE_MAPS['controller/network/*/member']);
+	m.nwid = nwid;
+	request({
+		url: this.url + 'controller/network' + nwid + '/member/' + member.address,
+		method: 'POST',
+		json: true,
+		body: m,
+		headers: {
+			'X-ZT1-Auth': this.authToken
+		}
+	},function(err,response,body) {
+		if (err)
+			return callback(err,null);
+		if (response.statusCode !== 200)
+			return callback(new Error('server responded with error: '+response.statusCode),null);
+		return callback(null,(typeof body === 'string') ? JSON.parse(body) : body);
+	});
+};
+
+ZT1ApiClient.prototype.deleteControllerNetworkMember = function(nwid,address,callback) {
+	request({
+		url: this.url + 'controller/network/' + nwid + '/member/' + address,
+		method: 'DELETE',
+		headers: {
+			'X-ZT1-Auth': this.authToken
+		}
+	},function(err,response,body) {
+		if (err)
+			return callback(err);
+		else if (response.statusCode === 200)
+			return callback(null);
+		else return callback(new Error('server responded with error: '+response.statusCode));
+	});
+};
+
 exports.ZT1ApiClient = ZT1ApiClient;

+ 3 - 3
js/zt1-api-client/test.js

@@ -19,9 +19,9 @@ zt1.status(function(err,status) {
 
 			if (status.controller) {
 				zt1.saveControllerNetwork({
-					nwid: status.address + 'dead01',
-					name: 'test network',
-					private: true
+					"nwid": status.address + 'dead01',
+					"name": 'test network',
+					"private": true
 				},function(err,network) {
 					if (err)
 						console.log(err);