diff --git a/.env-sample b/.env-sample new file mode 100644 index 0000000..b4586bb --- /dev/null +++ b/.env-sample @@ -0,0 +1,2 @@ +AZURE_ACCOUNTNAME= +AZURE_KEY= diff --git a/.gitignore b/.gitignore index fac8cfd..a18bea7 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,11 @@ $RECYCLE.BIN/ # Node.js dependencies node_modules node_modules/* +# Lock files should not be committed +package-lock.json +# Add nodered folder for docker container +nodered/* +!nodered/.gitkeep # Tar G-Zipped files *.tgz @@ -57,3 +62,8 @@ Temporary Items # VS Code .vscode +!.vscode/lauch.json + +==== +# env file +.env diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..8498dd6 --- /dev/null +++ b/.npmignore @@ -0,0 +1,7 @@ +Dockerfile +docker-compose.yml +.env +.gitignore +.env-sample +.git +.vscode \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8694c6c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "attach", + "name": "Attach to Node-RED", + "address": "localhost", + "port": 9229, + "localRoot": "${workspaceFolder}", + "remoteRoot": "/package_src" + } + ] + } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a777a04 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM nodered/node-red:4.0.8 + +FROM nodered/node-red:4.0.8-22 + +# package +USER root + +COPY ./ /package_src/ +RUN cd /package_src/ && npm install + + +RUN npm install /package_src/ + +# defaults +USER node-red + +ENTRYPOINT ["./entrypoint.sh"] + diff --git a/azuretablestorage.html b/azuretablestorage.html index 6210e0b..bf9ed3c 100644 --- a/azuretablestorage.html +++ b/azuretablestorage.html @@ -4,7 +4,11 @@ color: '#00abec', defaults: { name: { value: "Azure Table Storage" }, - debug: { value: false } + debug: { value: false }, + accountname: { value: "", required: true }, + accountname_type: { value: "str" }, + key: { value: "", required: true }, + key_type: { value: "str" }, }, inputs: 1, outputs: 1, @@ -12,10 +16,32 @@ label: function () { return this.name || "Azure Table Storage"; }, - credentials: { - accountname: { type: "text" }, - key: { type: "text" } - } + oneditprepare: function () { + $('#node-input-accountname').typedInput({ + default: 'str', + types: ['str', 'cred', 'env'], + }); + + $('#node-input-accountname').typedInput('value', this.accountname); + $('#node-input-accountname').typedInput('type', this.accountname_type); + + + $('#node-input-key').typedInput({ + default: 'str', + types: ['str', 'cred', 'env'], + }); + + $('#node-input-key').typedInput('value', this.key); + $('#node-input-key').typedInput('type', this.key_type); + + }, + oneditsave: function () { + this.accountname = $('#node-input-accountname').typedInput('value'); + this.accountname_type = $('#node-input-accountname').typedInput('type'); + + this.key = $('#node-input-key').typedInput('value'); + this.key_type = $('#node-input-key').typedInput('type'); + }, }); diff --git a/azuretablestorage.js b/azuretablestorage.js index 15a2328..1413a0f 100644 --- a/azuretablestorage.js +++ b/azuretablestorage.js @@ -16,8 +16,9 @@ module.exports = function (RED) { // Create the Node-RED node RED.nodes.createNode(this, config); - let clientAccountName = node.credentials.accountname - let clientAccountKey = node.credentials.key; + let clientAccountName = RED.util.evaluateNodeProperty(config.accountname, config.accountname_type, node); + let clientAccountKey = RED.util.evaluateNodeProperty(config.key, config.key_type, node); + let clientTableService = Client.createTableService(clientAccountName, clientAccountKey); if (config.debug == true) @@ -51,7 +52,7 @@ module.exports = function (RED) { return entity; }; - const sendData = (entityClass) => { + const sendData = (entityClass, msg) => { clientTableService.createTableIfNotExists(entityClass.tableName, function (error, result, response) { if (error) { node.error(error); @@ -63,29 +64,25 @@ module.exports = function (RED) { clientTableService.insertEntity(entityClass.tableName, entity, function (err, result, response) { node.log('trying to insert'); if (err) { - let newMsg = { - payload: { - partitionKey: entityClass.partitionKey, - rowKey: entityClass.rowKey, - }, - status: "Error", - message: err.toString() + msg.payload = { + partitionKey: entityClass.partitionKey, + rowKey: entityClass.rowKey }; + msg.status = "Error"; + msg.message = err.toString(); node.error('Error while trying to save data:' + err.toString()); - node.send(newMsg); + node.send(msg); setStatus(statusEnum.error); } else { - let newMsg = { - payload: { - partitionKey: entityClass.partitionKey, - rowKey: entityClass.rowKey, - }, - status: "OK" + msg.payload = { + partitionKey: entityClass.partitionKey, + rowKey: entityClass.rowKey }; + msg.status = "OK"; node.log(entityClass.partitionKey + ' saved.'); - node.send(newMsg); + node.send(msg); setStatus(statusEnum.sent); } }); @@ -93,91 +90,81 @@ module.exports = function (RED) { }); }; - const readData = (entityClass) => { + const readData = (entityClass, msg) => { clientTableService.retrieveEntity(entityClass.tableName, entityClass.partitionKey, entityClass.rowKey, function (err, result, response) { if (err) { - let newMsg = { - payload: { - partitionKey: entityClass.partitionKey, - rowKey: entityClass.rowKey, - }, - status: "Error", - message: err.toString() + msg.payload = { + partitionKey: entityClass.partitionKey, + rowKey: entityClass.rowKey }; + msg.status = "Error"; + msg.message = err.toString(); node.error('Error while trying to read data:' + err.toString()); - node.send(newMsg); + node.send(msg); setStatus(statusEnum.error); } else { - let newMsg = { - payload: result, - status: "OK", - partitionKey: entityClass.partitionKey, - rowKey: entityClass.rowKey - }; + msg.payload = result; + msg.status = "OK"; + msg.partitionKey = entityClass.partitionKey; + msg.rowKey = entityClass.rowKey; node.log("Read Data...OK"); - node.send(newMsg); + node.send(msg); setStatus(statusEnum.sent); } }); } - const deleteTable = (table) => { + const deleteTable = (table, msg) => { node.log("Deleting table"); clientTableService.deleteTable(table, function (err) { if (err) { node.error('Error while trying to delete table:' + err.toString()); setStatus(statusEnum.error); } else { - let newMsg = { - payload: table, - status: "OK" - }; + msg.payload = table; + msg.status = "OK"; node.log('table ' + table + ' deleted'); - node.send(newMsg); + node.send(msg); setStatus(statusEnum.sent); } }); } - const updateEntity = (entityClass) => { + const updateEntity = (entityClass, msg) => { node.log('updating entity'); let entity = formatEntityWithGenerator(entityClass); clientTableService.insertOrReplaceEntity(entityClass.tableName, entity, function (err, result, response) { if (err) { - let newMsg = { - payload: { - partitionKey: entityClass.partitionKey, - rowKey: entityClass.rowKey, - }, - status: "Error", - message: err.name + " - " + err.toString(), - stack: err.stack + msg.payload = { + partitionKey: entityClass.partitionKey, + rowKey: entityClass.rowKey }; + msg.status = "Error"; + msg.message = err.name + " - " + err.toString(); + msg.stack = err.stack; node.error('Error while trying to update' + err.toString()); - node.send(newMsg); + node.send(msg); setStatus(statusEnum.error); } else { - let newMsg = { - payload: { - partitionKey: entityClass.partitionKey, - rowKey: entityClass.rowKey, - }, - status: "OK" + msg.payload = { + partitionKey: entityClass.partitionKey, + rowKey: entityClass.rowKey }; + msg.status = "OK"; node.log(entityClass.partitionKey + ' updated.'); - node.send(newMsg); + node.send(msg); setStatus(statusEnum.sent); } }); } - const deleteEntity = (entityClass) => { + const deleteEntity = (entityClass, msg) => { node.log('deleting entity'); var entGen = Client.TableUtilities.entityGenerator; @@ -188,28 +175,24 @@ module.exports = function (RED) { clientTableService.deleteEntity(entityClass.tableName, entity, function (err, result, response) { if (err) { - let newMsg = { - payload: { - partitionKey: entityClass.partitionKey, - rowKey: entityClass.rowKey, - }, - status: "Error", - message: err.toString() + msg.payload = { + partitionKey: entityClass.partitionKey, + rowKey: entityClass.rowKey }; + msg.status = "Error"; + msg.message = err.toString(); - node.send(newMsg); + node.send(msg); node.error('Error while trying to delete entity:' + err.toString()); setStatus(statusEnum.error); } else { - let newMsg = { - payload: { - partitionKey: entityClass.partitionKey, - rowKey: entityClass.rowKey, - }, - status: "OK" + msg.payload = { + partitionKey: entityClass.partitionKey, + rowKey: entityClass.rowKey }; + msg.status = "OK"; - node.send(newMsg); + node.send(msg); node.log('entity deleted'); setStatus(statusEnum.sent); } @@ -217,7 +200,7 @@ module.exports = function (RED) { } - const queryEntity = (entityClass) => { + const queryEntity = (entityClass, msg) => { node.log('query entity'); let top = entityClass.top ? entityClass.top : 0; @@ -228,23 +211,19 @@ module.exports = function (RED) { clientTableService.queryEntities(entityClass.tableName, query, null, function (err, result, response) { if (err) { - let newMsg = { - payload: entityClass, - status: "Error", - message: err.toString() - }; + msg.payload = entityClass; + msg.status = "Error"; + msg.message = err.toString(); node.error('Error while trying to save data:' + err.toString()); - node.send(newMsg); + node.send(msg); setStatus(statusEnum.error); } else { - let newMsg = { - payload: result.entries, - status: "OK" - }; + msg.payload = result.entries; + msg.status = "OK"; node.log("queried " + entityClass.tableName + "...OK"); - node.send(newMsg); + node.send(msg); setStatus(statusEnum.sent); } }); @@ -282,32 +261,32 @@ module.exports = function (RED) { switch (action) { case "I": node.log('Trying to insert entity'); - sendData(entityClass); + sendData(entityClass, msg); break; case "R": node.log('Trying to read entity'); - readData(entityClass); + readData(entityClass, msg); break; case "DT": node.log('Trying to delete table'); - deleteTable(entityClass.tableName); + deleteTable(entityClass.tableName, msg); break; case "Q": node.log('Trying to query data'); - queryEntity(entityClass); + queryEntity(entityClass, msg); break; case "U": node.log('trying to update entity'); - updateEntity(entityClass); + updateEntity(entityClass, msg); break; case "D": node.log('trying to delete entity'); - deleteEntity(entityClass) + deleteEntity(entityClass, msg) break; default: node.log('action was not detected'); node.error('action was not detected'); - setStatus(statusEnum.error); + setStatus(statusEnum.error, msg); break; } }); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7e6fbd9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +services: + node-red: + image: 5minds/node-red:azuretablestorage + build: + context: . + ports: + # node-red port + - 1880:1880 + # debugger port + - 9229:9229 + environment: + # start node-red with debugger port open + - NODE_OPTIONS=--inspect=0.0.0.0:9229 + env_file: + - .env + volumes: + # required for mapping the current source into the directory + - ./.:/package_src/ + # save the flows + - ././nodered:/data + # data + - ./:/local_data + + \ No newline at end of file diff --git a/examples/examples.json b/examples/examples.json new file mode 100644 index 0000000..92a3411 --- /dev/null +++ b/examples/examples.json @@ -0,0 +1,145 @@ +[ + { + "id": "088c9d6fa558f4da", + "type": "tab", + "label": "Flow 1", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "f0c8fc28bdd6d726", + "type": "group", + "z": "088c9d6fa558f4da", + "name": "", + "style": { + "label": true + }, + "nodes": [ + "ffbb09bc396a57e2", + "edcf5d1a57d2029b", + "312af8c7422dd941", + "381b93bddf50f57e", + "1c28feb1b37b42c0" + ], + "x": 34, + "y": 259, + "w": 652, + "h": 202 + }, + { + "id": "ffbb09bc396a57e2", + "type": "Aleph Table Storage", + "z": "088c9d6fa558f4da", + "g": "f0c8fc28bdd6d726", + "name": "Azure Table Storage", + "debug": false, + "accountname": "AZURE_ACCOUNTNAME", + "accountname_type": "env", + "key": "AZURE_KEY", + "key_type": "env", + "x": 380, + "y": 300, + "wires": [ + [ + "312af8c7422dd941" + ] + ] + }, + { + "id": "edcf5d1a57d2029b", + "type": "inject", + "z": "088c9d6fa558f4da", + "g": "f0c8fc28bdd6d726", + "name": "update (hello)", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"tableName\":\"test5minds\",\"action\":\"U\",\"partitionKey\":\"p_key_01\",\"rowKey\":\"r_key_01\",\"data\":{\"hello\":\"world\"}}", + "payloadType": "json", + "x": 150, + "y": 300, + "wires": [ + [ + "ffbb09bc396a57e2" + ] + ] + }, + { + "id": "312af8c7422dd941", + "type": "debug", + "z": "088c9d6fa558f4da", + "g": "f0c8fc28bdd6d726", + "name": "debug 1", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 580, + "y": 300, + "wires": [] + }, + { + "id": "381b93bddf50f57e", + "type": "inject", + "z": "088c9d6fa558f4da", + "g": "f0c8fc28bdd6d726", + "name": "read", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"tableName\":\"test5minds\",\"action\":\"R\",\"partitionKey\":\"p_key_01\",\"rowKey\":\"r_key_01\"}", + "payloadType": "json", + "x": 170, + "y": 420, + "wires": [ + [ + "ffbb09bc396a57e2" + ] + ] + }, + { + "id": "1c28feb1b37b42c0", + "type": "inject", + "z": "088c9d6fa558f4da", + "g": "f0c8fc28bdd6d726", + "name": "update (foo)", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"tableName\":\"test5minds\",\"action\":\"U\",\"partitionKey\":\"p_key_01\",\"rowKey\":\"r_key_01\",\"data\":{\"foo\":\"bar\"}}", + "payloadType": "json", + "x": 150, + "y": 360, + "wires": [ + [ + "ffbb09bc396a57e2" + ] + ] + } +] \ No newline at end of file diff --git a/nodered/.gitkeep b/nodered/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json index 848cf92..c2c77c6 100644 --- a/package.json +++ b/package.json @@ -1,26 +1,40 @@ { "name": "node-red-contrib-azure-table-storage-aleph", "description": "Connector to Azure Table Storage on Node-Red", - "version": "0.1.8", + "version": "0.2.1", "engines": { "node": ">=0.10.0" }, - "dependencies" : { - "azure-storage": "2.1.0" - }, - "keywords":["node-red", "Azure", "Azure Storage", "Storage", "Table Storage"], - "node-red" :{ - "nodes" : { - "Azure": "azuretablestorage.js" - } + "dependencies": { + "azure-storage": "2.1.0" + }, + "keywords": [ + "node-red", + "Azure", + "Azure Storage", + "Storage", + "Table Storage" + ], + "node-red": { + "nodes": { + "Azure": "azuretablestorage.js" + }, + "examples": "examples" }, "repository": { "type": "git", "url": "https://github.com/javis86/node-red-contrib-azure-table-storage-aleph" }, - "author": { - "name": "Javier Colombera", - "email": "jcolombera@gmail.com", - "url": "https://github.com/javis86" - } + "authors": [ + { + "name": "Javier Colombera", + "email": "jcolombera@gmail.com", + "url": "https://github.com/javis86" + }, + { + "name": "Martin Moellenbeck", + "email": "Martin.Moellenbeck@5Minds.de", + "url": "https://github.com/5minds" + } + ] } \ No newline at end of file