mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Add v2 /flows api and deploy-overwrite protection
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2014, 2015 IBM Corp.
|
||||
* Copyright 2014, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,13 +25,23 @@ module.exports = {
|
||||
log = runtime.log;
|
||||
},
|
||||
get: function(req,res) {
|
||||
log.audit({event: "flows.get"},req);
|
||||
res.json(redNodes.getFlows());
|
||||
var version = req.get("Node-RED-API-Version")||"v1";
|
||||
if (version === "v1") {
|
||||
log.audit({event: "flows.get",version:"v1"},req);
|
||||
res.json(redNodes.getFlows().flows);
|
||||
} else if (version === "v2") {
|
||||
log.audit({event: "flows.get",version:"v2"},req);
|
||||
res.json(redNodes.getFlows());
|
||||
} else {
|
||||
log.audit({event: "flows.get",version:version,error:"bad_api_version"},req);
|
||||
res.status(400).json({error:"bad_api_version"});
|
||||
}
|
||||
},
|
||||
post: function(req,res) {
|
||||
var version = req.get("Node-RED-API-Version")||"v1";
|
||||
var flows = req.body;
|
||||
var deploymentType = req.get("Node-RED-Deployment-Type")||"full";
|
||||
log.audit({event: "flows.set",type:deploymentType},req);
|
||||
log.audit({event: "flows.set",type:deploymentType,version:version},req);
|
||||
if (deploymentType === 'reload') {
|
||||
redNodes.loadFlows().then(function() {
|
||||
res.status(204).end();
|
||||
@@ -41,8 +51,28 @@ module.exports = {
|
||||
res.status(500).json({error:"unexpected_error", message:err.message});
|
||||
});
|
||||
} else {
|
||||
redNodes.setFlows(flows,deploymentType).then(function() {
|
||||
res.status(204).end();
|
||||
var flowConfig = flows;
|
||||
if (version === "v2") {
|
||||
flowConfig = flows.flows;
|
||||
if (flows.hasOwnProperty('rev')) {
|
||||
var currentVersion = redNodes.getFlows().rev;
|
||||
if (currentVersion !== flows.rev) {
|
||||
//TODO: log warning
|
||||
return res.status(409).json({error:"version_mismatch"});
|
||||
}
|
||||
}
|
||||
} else if (version !== 'v1') {
|
||||
log.audit({event: "flows.set",version:version,error:"bad_api_version"},req);
|
||||
return res.status(400).json({error:"bad_api_version"});
|
||||
}
|
||||
redNodes.setFlows(flowConfig,deploymentType).then(function(flowId) {
|
||||
if (version === "v1") {
|
||||
res.status(204).end();
|
||||
} else if (version === "v2") {
|
||||
res.json({rev:flowId});
|
||||
} else {
|
||||
// TODO: invalid version
|
||||
}
|
||||
}).otherwise(function(err) {
|
||||
log.warn(log._("api.flows.error-save",{message:err.message}));
|
||||
log.warn(err.stack);
|
||||
|
@@ -121,12 +121,15 @@
|
||||
"confirm": {
|
||||
"button": {
|
||||
"confirm": "Confirm deploy",
|
||||
"cancel": "Cancel"
|
||||
"review": "Review differences",
|
||||
"cancel": "Cancel",
|
||||
"merge": "Merge changes"
|
||||
},
|
||||
"undeployedChanges": "You have undeployed changes.\n\nLeaving this page will lose these changes.",
|
||||
"improperlyConfigured": "The workspace contains some nodes that are not properly configured:",
|
||||
"unknown": "The workspace contains some unknown node types:",
|
||||
"confirm": "Are you sure you want to deploy?"
|
||||
"confirm": "Are you sure you want to deploy?",
|
||||
"conflict": "The server is running a more recent set of flows."
|
||||
}
|
||||
},
|
||||
"subflow": {
|
||||
|
@@ -66,33 +66,47 @@ function init(runtime) {
|
||||
typeEventRegistered = true;
|
||||
}
|
||||
}
|
||||
function load() {
|
||||
|
||||
function loadFlows() {
|
||||
return storage.getFlows().then(function(config) {
|
||||
return credentials.load(config.credentials).then(function() {
|
||||
return setConfig(config.flows,"load");
|
||||
return config;
|
||||
});
|
||||
}).otherwise(function(err) {
|
||||
log.warn(log._("nodes.flows.error",{message:err.toString()}));
|
||||
console.log(err.stack);
|
||||
});
|
||||
}
|
||||
function load() {
|
||||
return setFlows(null,"load",false);
|
||||
}
|
||||
|
||||
function setConfig(_config,type,muteLog) {
|
||||
var config = clone(_config);
|
||||
/*
|
||||
* _config - new node array configuration
|
||||
* type - full/nodes/flows/load (default full)
|
||||
* muteLog - don't emit the standard log messages (used for individual flow api)
|
||||
*/
|
||||
function setFlows(_config,type,muteLog) {
|
||||
type = type||"full";
|
||||
|
||||
var configSavePromise = null;
|
||||
|
||||
var config = null;
|
||||
var diff;
|
||||
var newFlowConfig = flowUtil.parseConfig(clone(config));
|
||||
if (type !== 'full' && type !== 'load') {
|
||||
diff = flowUtil.diffConfigs(activeFlowConfig,newFlowConfig);
|
||||
}
|
||||
var newFlowConfig;
|
||||
|
||||
if (type === 'load') {
|
||||
type = 'full';
|
||||
configSavePromise = when.resolve();
|
||||
if (type === "load") {
|
||||
configSavePromise = loadFlows().then(function(_config) {
|
||||
config = clone(_config.flows);
|
||||
newFlowConfig = flowUtil.parseConfig(clone(config));
|
||||
type = "full";
|
||||
return _config.rev;
|
||||
});
|
||||
} else {
|
||||
config = clone(_config);
|
||||
newFlowConfig = flowUtil.parseConfig(clone(config));
|
||||
if (type !== 'full') {
|
||||
diff = flowUtil.diffConfigs(activeFlowConfig,newFlowConfig);
|
||||
}
|
||||
credentials.clean(config);
|
||||
var credsDirty = credentials.dirty();
|
||||
configSavePromise = credentials.export().then(function(creds) {
|
||||
@@ -101,18 +115,22 @@ function setConfig(_config,type,muteLog) {
|
||||
credentialsDirty:credsDirty,
|
||||
credentials: creds
|
||||
}
|
||||
storage.saveFlows(saveConfig);
|
||||
return storage.saveFlows(saveConfig);
|
||||
});
|
||||
}
|
||||
|
||||
return configSavePromise
|
||||
.then(function() {
|
||||
activeConfig = config;
|
||||
.then(function(flowRevision) {
|
||||
activeConfig = {
|
||||
flows:config,
|
||||
rev:flowRevision
|
||||
};
|
||||
activeFlowConfig = newFlowConfig;
|
||||
if (started) {
|
||||
return stop(type,diff,muteLog).then(function() {
|
||||
context.clean(activeFlowConfig);
|
||||
start(type,diff,muteLog);
|
||||
return flowRevision;
|
||||
}).otherwise(function(err) {
|
||||
})
|
||||
}
|
||||
@@ -143,7 +161,7 @@ function eachNode(cb) {
|
||||
}
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
function getFlows() {
|
||||
return activeConfig;
|
||||
}
|
||||
|
||||
@@ -341,8 +359,8 @@ function checkTypeInUse(id) {
|
||||
throw new Error(log._("nodes.index.unrecognised-id", {id:id}));
|
||||
} else {
|
||||
var inUse = {};
|
||||
var config = getConfig();
|
||||
config.forEach(function(n) {
|
||||
var config = getFlows();
|
||||
config.flows.forEach(function(n) {
|
||||
inUse[n.type] = (inUse[n.type]||0)+1;
|
||||
});
|
||||
var nodesInUse = [];
|
||||
@@ -418,10 +436,10 @@ function addFlow(flow) {
|
||||
nodes.push(node);
|
||||
}
|
||||
}
|
||||
var newConfig = clone(activeConfig);
|
||||
var newConfig = clone(activeConfig.flows);
|
||||
newConfig = newConfig.concat(nodes);
|
||||
|
||||
return setConfig(newConfig,'flows',true).then(function() {
|
||||
return setFlows(newConfig,'flows',true).then(function() {
|
||||
log.info(log._("nodes.flows.added-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
|
||||
return flow.id;
|
||||
});
|
||||
@@ -501,7 +519,7 @@ function updateFlow(id,newFlow) {
|
||||
}
|
||||
label = activeFlowConfig.flows[id].label;
|
||||
}
|
||||
var newConfig = clone(activeConfig);
|
||||
var newConfig = clone(activeConfig.flows);
|
||||
var nodes;
|
||||
|
||||
if (id === 'global') {
|
||||
@@ -539,7 +557,7 @@ function updateFlow(id,newFlow) {
|
||||
}
|
||||
|
||||
newConfig = newConfig.concat(nodes);
|
||||
return setConfig(newConfig,'flows',true).then(function() {
|
||||
return setFlows(newConfig,'flows',true).then(function() {
|
||||
log.info(log._("nodes.flows.updated-flow",{label:(label?label+" ":"")+"["+id+"]"}));
|
||||
})
|
||||
}
|
||||
@@ -556,12 +574,12 @@ function removeFlow(id) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
var newConfig = clone(activeConfig);
|
||||
var newConfig = clone(activeConfig.flows);
|
||||
newConfig = newConfig.filter(function(node) {
|
||||
return node.z !== id && node.id !== id;
|
||||
});
|
||||
|
||||
return setConfig(newConfig,'flows',true).then(function() {
|
||||
return setFlows(newConfig,'flows',true).then(function() {
|
||||
log.info(log._("nodes.flows.removed-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
|
||||
});
|
||||
}
|
||||
@@ -581,7 +599,7 @@ module.exports = {
|
||||
/**
|
||||
* Gets the current flow configuration
|
||||
*/
|
||||
getFlows: getConfig,
|
||||
getFlows: getFlows,
|
||||
|
||||
/**
|
||||
* Sets the current active config.
|
||||
@@ -589,7 +607,7 @@ module.exports = {
|
||||
* @param type the type of deployment to do: full (default), nodes, flows, load
|
||||
* @return a promise for the saving/starting of the new flow
|
||||
*/
|
||||
setFlows: setConfig,
|
||||
setFlows: setFlows,
|
||||
|
||||
/**
|
||||
* Starts the current flow configuration
|
||||
|
@@ -16,6 +16,8 @@
|
||||
|
||||
var when = require('when');
|
||||
var Path = require('path');
|
||||
var crypto = require('crypto');
|
||||
|
||||
var log = require("../log");
|
||||
|
||||
var runtime;
|
||||
@@ -57,10 +59,12 @@ var storageModuleInterface = {
|
||||
getFlows: function() {
|
||||
return storageModule.getFlows().then(function(flows) {
|
||||
return storageModule.getCredentials().then(function(creds) {
|
||||
return {
|
||||
var result = {
|
||||
flows: flows,
|
||||
credentials: creds
|
||||
}
|
||||
};
|
||||
result.rev = crypto.createHash('md5').update(JSON.stringify(result)).digest("hex");
|
||||
return result;
|
||||
})
|
||||
});
|
||||
},
|
||||
@@ -73,9 +77,12 @@ var storageModuleInterface = {
|
||||
} else {
|
||||
credentialSavePromise = when.resolve();
|
||||
}
|
||||
delete config.credentialsDirty;
|
||||
|
||||
return credentialSavePromise.then(function() {
|
||||
return storageModule.saveFlows(flows);
|
||||
return storageModule.saveFlows(flows).then(function() {
|
||||
return crypto.createHash('md5').update(JSON.stringify(config)).digest("hex");
|
||||
})
|
||||
});
|
||||
},
|
||||
// getCredentials: function() {
|
||||
|
Reference in New Issue
Block a user