1 | /**
|
2 | * Copyright 2018 F5 Networks, Inc.
|
3 | *
|
4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | * you may not use this file except in compliance with the License.
|
6 | * You may obtain a copy of the License at
|
7 | *
|
8 | * http://www.apache.org/licenses/LICENSE-2.0
|
9 | *
|
10 | * Unless required by applicable law or agreed to in writing, software
|
11 | * distributed under the License is distributed on an "AS IS" BASIS,
|
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13 | * See the License for the specific language governing permissions and
|
14 | * limitations under the License.
|
15 | */
|
16 |
|
17 | ;
|
18 |
|
19 | const util = require('util');
|
20 | const q = require('q');
|
21 | const jmespath = require('jmespath');
|
22 |
|
23 | const CloudProvider = require('./cloudProvider');
|
24 | const Logger = require('./logger');
|
25 | const cloudUtil = require('./util');
|
26 | const cryptoUtil = require('./cryptoUtil');
|
27 |
|
28 | let logger;
|
29 |
|
30 | util.inherits(GenericNodeProvider, CloudProvider);
|
31 |
|
32 | /**
|
33 | * Constructor
|
34 | * @class
|
35 | * @classdesc
|
36 | * Generic node provider implementation.
|
37 | *
|
38 | * @param {Object} [options] - Options for the instance.
|
39 | * @param {Object} [options.clOptions] - Command line options if called from a script.
|
40 | * @param {Object} [options.logger] - Logger to use. Or, pass loggerOptions to get your own logger.
|
41 | * @param {Object} [options.loggerOptions] - Options for the logger.
|
42 | * See {@link module:logger.getLogger} for details.
|
43 | */
|
44 | function GenericNodeProvider(options) {
|
45 | GenericNodeProvider.super_.call(this, options);
|
46 |
|
47 | this.loggerOptions = options ? options.loggerOptions : undefined;
|
48 |
|
49 | logger = options ? options.logger : undefined;
|
50 |
|
51 | if (logger) {
|
52 | this.logger = logger;
|
53 | cloudUtil.setLogger(logger);
|
54 | cryptoUtil.setLogger(logger);
|
55 | } else if (this.loggerOptions) {
|
56 | this.loggerOptions.module = module;
|
57 | logger = Logger.getLogger(this.loggerOptions);
|
58 | cloudUtil.setLoggerOptions(this.loggerOptions);
|
59 | cryptoUtil.setLoggerOptions(this.loggerOptions);
|
60 | this.logger = logger;
|
61 | } else {
|
62 | // use super's logger
|
63 | logger = this.logger;
|
64 | cloudUtil.setLogger(logger);
|
65 | cryptoUtil.setLogger(logger);
|
66 | }
|
67 | }
|
68 |
|
69 |
|
70 | /**
|
71 | * Initialize class
|
72 | *
|
73 | * Override for implementation specific initialization needs (read info
|
74 | * from cloud provider, read database, etc.). Called at the start of
|
75 | * processing.
|
76 | *
|
77 | * @param {Object} providerOptions - Provider specific options.
|
78 | * @param {String} providerOptions.propertyPathId - Object property path ('.' separated)
|
79 | * that describes how to fetch the ID
|
80 | * from the provided resource. An empty
|
81 | * property name in the path fetches all
|
82 | * keys in the current object.
|
83 | *
|
84 | * "example.uuid",
|
85 | *
|
86 | * Alternately you can use jmesPathQuery
|
87 | *
|
88 | *
|
89 | * @param {String} providerOptions.propertyPathIpPrivate - Object property path ('.' separated)
|
90 | * that describes how to fetch the private
|
91 | * IP from the provided resource. An empty
|
92 | * property name in the path fetches all
|
93 | * keys in the current object.
|
94 | *
|
95 | * "example.address.private"
|
96 | *
|
97 | * Alternately you can use jmesPathQuery
|
98 | *
|
99 | *
|
100 | * @param {String} [providerOptions.propertyPathIpPublic] - Object property path ('.' separated)
|
101 | * that describes how to fetch the public
|
102 | * IP from the provided resource. An empty
|
103 | * property name in the path fetches all
|
104 | * keys in the current object.
|
105 | *
|
106 | * "example.address.public"
|
107 | *
|
108 | * Alternately you can use jmesPathQuery
|
109 | *
|
110 | *
|
111 | * @param {String} [providerOptions.jmesPathQuery] - Use a JMESPath query to construct the
|
112 | * data structure that is returned from
|
113 | * the URI. The query should return both
|
114 | * a string value for "ip.private" and "id"
|
115 | *
|
116 | * "[*].{id:ID||Node,ip:{private:Node,public:Node}}"
|
117 | *
|
118 | * @param {Object} [options] - Options for this instance.
|
119 | *
|
120 | * @returns {Promise} A promise which will be resolved when init is complete.
|
121 | */
|
122 | GenericNodeProvider.prototype.init = function init(providerOptions, options) {
|
123 | this.initOptions = options || {};
|
124 | this.providerOptions = providerOptions || {};
|
125 |
|
126 | if (typeof this.providerOptions.propertyPathId !== 'string' &&
|
127 | (!this.providerOptions.jmesPathQuery)) {
|
128 | return q.reject(new Error('ProviderOptions.propertyPathId required to fetch node data'));
|
129 | }
|
130 | if (typeof this.providerOptions.propertyPathIpPrivate !== 'string' &&
|
131 | (!this.providerOptions.jmesPathQuery)) {
|
132 | return q.reject(new Error('ProviderOptions.propertyPathIpPrivate required to fetch node data'));
|
133 | }
|
134 |
|
135 | if (typeof this.providerOptions.propertyPathId === 'string') {
|
136 | this.propertyPaths = {};
|
137 | Object.keys(this.providerOptions).forEach((key) => {
|
138 | if (key.startsWith('propertyPath')) {
|
139 | this.propertyPaths[key] = this.providerOptions[key].split('.');
|
140 | }
|
141 | });
|
142 | this.propertyPaths = Object.assign({
|
143 | propertyPathIpPublic: []
|
144 | }, this.propertyPaths);
|
145 | }
|
146 |
|
147 | return q();
|
148 | };
|
149 |
|
150 |
|
151 | /**
|
152 | * Gets nodes from the provided URI. The resource should be in JSON
|
153 | * format as an array of objects. JSON strings that parse to an array
|
154 | * of objects are also supported.
|
155 | *
|
156 | * @param {String} uri - The URI of the resource.
|
157 | * @param {Object} [options] - http/https request options
|
158 | *
|
159 | * @returns {Promise} A promise which will be resolved with an array of instances.
|
160 | * Each instance value should be:
|
161 | *
|
162 | * {
|
163 | * id: Node ID,
|
164 | * ip: {
|
165 | * public: public IP,
|
166 | * private: private IP
|
167 | * }
|
168 | * }
|
169 | */
|
170 | GenericNodeProvider.prototype.getNodesFromUri = function getNodesFromUri(uri, options) {
|
171 | return cloudUtil.getDataFromUrl(uri, options)
|
172 | .then((data) => {
|
173 | const nodes = [];
|
174 | let resData = data;
|
175 |
|
176 | if (typeof resData === 'string') {
|
177 | try {
|
178 | resData = JSON.parse(data);
|
179 | } catch (e) {
|
180 | return q.reject(new Error(`${e.message}. Data must parse to a JSON array of objects.`));
|
181 | }
|
182 | }
|
183 |
|
184 | if (!Array.isArray(resData)) {
|
185 | return q.reject(new Error('Data must be a JSON array of objects.'));
|
186 | }
|
187 | if (this.providerOptions.jmesPathQuery) {
|
188 | resData = jmespath.search(resData, this.providerOptions.jmesPathQuery);
|
189 | resData = resData.filter((n) => {
|
190 | return (typeof (n.id) === 'string' &&
|
191 | (typeof (n.ip.private) === 'string' || typeof (n.ip.public) === 'string'));
|
192 | });
|
193 | return q(resData);
|
194 | }
|
195 | for (let i = 0; i < resData.length; i++) {
|
196 | const node = { ip: {} };
|
197 | node.id = getDataFromPropPath(this.propertyPaths.propertyPathId, resData[i]);
|
198 | node.ip.private = getDataFromPropPath(this.propertyPaths.propertyPathIpPrivate, resData[i]);
|
199 | node.ip.public = getDataFromPropPath(this.propertyPaths.propertyPathIpPublic, resData[i]);
|
200 | if (typeof node.id !== 'undefined' && typeof node.ip.private !== 'undefined') {
|
201 | nodes.push(node);
|
202 | }
|
203 | }
|
204 |
|
205 | return q(nodes);
|
206 | });
|
207 | };
|
208 |
|
209 | /**
|
210 | * Gets the value from an Object's property using a provided Object property path. An empty
|
211 | * property name in the path fetches all keys in the current Object.
|
212 | *
|
213 | * @param {String[]} pathArray - Array of properties to traverse through.
|
214 | * @param {Object} obj - Object to traverse.
|
215 | *
|
216 | * @returns {*} Data that was fetched from the property.
|
217 | */
|
218 | function getDataFromPropPath(pathArray, obj) {
|
219 | if (pathArray.length === 0) {
|
220 | return undefined;
|
221 | }
|
222 | return pathArray.reduce((result, prop) => {
|
223 | if (typeof result !== 'object' || result === null) {
|
224 | return {};
|
225 | }
|
226 | if (prop === '') {
|
227 | return result;
|
228 | }
|
229 | return result[prop];
|
230 | }, obj);
|
231 | }
|
232 |
|
233 | module.exports = GenericNodeProvider;
|