Workflows, ExecutionPlanGenerator, Reporting, UserData, conductor improvements
This commit is contained in:
parent
9d0b7e39fe
commit
28bc7ff1d5
@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||||||
# Visual Studio 2012
|
# Visual Studio 2012
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsAgent", "WindowsAgent\WindowsAgent.csproj", "{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsAgent", "WindowsAgent\WindowsAgent.csproj", "{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExecutionPlanGenerator", "ExecutionPlanGenerator\ExecutionPlanGenerator.csproj", "{501BE151-4B8C-4355-88DC-3AEF1921B2D7}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -13,6 +15,10 @@ Global
|
|||||||
{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Release|Any CPU.Build.0 = Release|Any CPU
|
{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{501BE151-4B8C-4355-88DC-3AEF1921B2D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{501BE151-4B8C-4355-88DC-3AEF1921B2D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{501BE151-4B8C-4355-88DC-3AEF1921B2D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{501BE151-4B8C-4355-88DC-3AEF1921B2D7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<configSections>
|
<configSections>
|
||||||
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
|
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
|
||||||
@ -11,20 +11,23 @@
|
|||||||
<targets>
|
<targets>
|
||||||
<target name="file" xsi:type="File" fileName="${basedir}/log.txt"
|
<target name="file" xsi:type="File" fileName="${basedir}/log.txt"
|
||||||
layout="${date} ${level}: <${logger:shortName=true}> ${message} ${exception:format=tostring}"/>
|
layout="${date} ${level}: <${logger:shortName=true}> ${message} ${exception:format=tostring}"/>
|
||||||
|
<target name="console" xsi:type="Console"
|
||||||
|
layout="${date} ${level}: <${logger:shortName=true}> ${message} ${exception:format=tostring}"/>
|
||||||
</targets>
|
</targets>
|
||||||
|
|
||||||
<rules>
|
<rules>
|
||||||
<logger name="*" minlevel="Debug" writeTo="file" />
|
<logger name="*" minlevel="Debug" writeTo="file" />
|
||||||
|
<logger name="*" minlevel="Debug" writeTo="console" />
|
||||||
</rules>
|
</rules>
|
||||||
</nlog>
|
</nlog>
|
||||||
<appSettings>
|
<appSettings>
|
||||||
<add key="rabbitmq.host" value="localhost"/>
|
<add key="rabbitmq.host" value="localhost"/>
|
||||||
<add key="rabbitmq.user" value="guest"/>
|
<add key="rabbitmq.user" value="keero"/>
|
||||||
<add key="rabbitmq.password" value="guest"/>
|
<add key="rabbitmq.password" value="keero"/>
|
||||||
<add key="rabbitmq.vhost" value="/"/>
|
<add key="rabbitmq.vhost" value="keero"/>
|
||||||
<add key="rabbitmq.resultExchange" value=""/>
|
<add key="rabbitmq.resultExchange" value=""/>
|
||||||
<add key="rabbitmq.resultQueue" value="-execution-results"/>
|
<add key="rabbitmq.resultRoutingKey" value="-execution-results"/>
|
||||||
|
<add key="rabbitmq.durableMessages" value="trueтест"/>
|
||||||
|
|
||||||
</appSettings>
|
</appSettings>
|
||||||
</configuration>
|
</configuration>
|
@ -14,8 +14,13 @@ namespace Mirantis.Keero.WindowsAgent
|
|||||||
{
|
{
|
||||||
this.ackFunc = ackFunc;
|
this.ackFunc = ackFunc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqMessage()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public string Body { get; set; }
|
public string Body { get; set; }
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
public void Ack()
|
public void Ack()
|
||||||
{
|
{
|
||||||
|
@ -5,12 +5,15 @@ using System.Linq;
|
|||||||
using System.Management.Automation;
|
using System.Management.Automation;
|
||||||
using System.Management.Automation.Runspaces;
|
using System.Management.Automation.Runspaces;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using NLog;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Mirantis.Keero.WindowsAgent
|
namespace Mirantis.Keero.WindowsAgent
|
||||||
{
|
{
|
||||||
class PlanExecutor
|
class PlanExecutor
|
||||||
{
|
{
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
class ExecutionResult
|
class ExecutionResult
|
||||||
{
|
{
|
||||||
public bool IsException { get; set; }
|
public bool IsException { get; set; }
|
||||||
@ -26,13 +29,14 @@ namespace Mirantis.Keero.WindowsAgent
|
|||||||
|
|
||||||
public bool RebootNeeded { get; set; }
|
public bool RebootNeeded { get; set; }
|
||||||
|
|
||||||
public string Execute()
|
public void Execute()
|
||||||
{
|
{
|
||||||
RebootNeeded = false;
|
RebootNeeded = false;
|
||||||
|
var resultPath = this.path + ".result";
|
||||||
|
Runspace runSpace = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var plan = JsonConvert.DeserializeObject<ExecutionPlan>(File.ReadAllText(this.path));
|
var plan = JsonConvert.DeserializeObject<ExecutionPlan>(File.ReadAllText(this.path));
|
||||||
var resultPath = this.path + ".result";
|
|
||||||
List<ExecutionResult> currentResults = null;
|
List<ExecutionResult> currentResults = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -44,7 +48,7 @@ namespace Mirantis.Keero.WindowsAgent
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var runSpace = RunspaceFactory.CreateRunspace();
|
runSpace = RunspaceFactory.CreateRunspace();
|
||||||
runSpace.Open();
|
runSpace.Open();
|
||||||
|
|
||||||
var runSpaceInvoker = new RunspaceInvoke(runSpace);
|
var runSpaceInvoker = new RunspaceInvoke(runSpace);
|
||||||
@ -70,6 +74,11 @@ namespace Mirantis.Keero.WindowsAgent
|
|||||||
psCommand.Parameters.Add(kvp.Key, kvp.Value);
|
psCommand.Parameters.Add(kvp.Key, kvp.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.Info("Executing {0} {1}", command.Name, string.Join(" ",
|
||||||
|
(command.Arguments ?? new Dictionary<string, object>()).Select(
|
||||||
|
t => string.Format("{0}={1}", t.Key, t.Value == null ? "null" : t.Value.ToString()))));
|
||||||
|
|
||||||
pipeline.Commands.Add(psCommand);
|
pipeline.Commands.Add(psCommand);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -90,6 +99,7 @@ namespace Mirantis.Keero.WindowsAgent
|
|||||||
exception.GetType().FullName, exception.Message
|
exception.GetType().FullName, exception.Message
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -115,16 +125,27 @@ namespace Mirantis.Keero.WindowsAgent
|
|||||||
RebootNeeded = true;
|
RebootNeeded = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File.WriteAllText(resultPath, executionResult);
|
||||||
|
|
||||||
File.Delete(resultPath);
|
|
||||||
return executionResult;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return JsonConvert.SerializeObject(new ExecutionResult {
|
File.WriteAllText(resultPath, JsonConvert.SerializeObject(new ExecutionResult {
|
||||||
IsException = true,
|
IsException = true,
|
||||||
Result = ex.Message
|
Result = ex.Message
|
||||||
}, Formatting.Indented);
|
}, Formatting.Indented));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (runSpace != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
runSpace.Close();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Management.Automation;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
||||||
@ -14,6 +18,8 @@ namespace Mirantis.Keero.WindowsAgent
|
|||||||
private volatile bool stop;
|
private volatile bool stop;
|
||||||
private Thread thread;
|
private Thread thread;
|
||||||
private RabbitMqClient rabbitMqClient;
|
private RabbitMqClient rabbitMqClient;
|
||||||
|
private int delayFactor = 1;
|
||||||
|
private string plansDir;
|
||||||
|
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
@ -23,34 +29,75 @@ namespace Mirantis.Keero.WindowsAgent
|
|||||||
protected override void OnStart(string[] args)
|
protected override void OnStart(string[] args)
|
||||||
{
|
{
|
||||||
base.OnStart(args);
|
base.OnStart(args);
|
||||||
|
|
||||||
|
Log.Info("Version 0.3");
|
||||||
|
|
||||||
this.rabbitMqClient = new RabbitMqClient();
|
this.rabbitMqClient = new RabbitMqClient();
|
||||||
|
|
||||||
|
var basePath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
|
||||||
|
this.plansDir = Path.Combine(basePath, "plans");
|
||||||
|
|
||||||
|
|
||||||
|
if (!Directory.Exists(plansDir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(plansDir);
|
||||||
|
}
|
||||||
|
|
||||||
this.thread = new Thread(Loop);
|
this.thread = new Thread(Loop);
|
||||||
this.thread.Start();
|
this.thread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Loop()
|
void Loop()
|
||||||
{
|
{
|
||||||
var doReboot = false;
|
const string unknownName = "unknown";
|
||||||
const string filePath = "data.json";
|
|
||||||
while (!stop)
|
while (!stop)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!File.Exists(filePath))
|
foreach (var file in Directory.GetFiles(this.plansDir, "*.json.result")
|
||||||
|
.Where(file => !File.Exists(Path.Combine(this.plansDir, Path.GetFileNameWithoutExtension(file)))))
|
||||||
|
{
|
||||||
|
var id = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(file)) ?? unknownName;
|
||||||
|
if (id.Equals(unknownName, StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
id = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = File.ReadAllText(file);
|
||||||
|
Log.Info("Sending results for {0}", id ?? unknownName);
|
||||||
|
rabbitMqClient.SendResult(new MqMessage { Body = result, Id = id });
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = Directory.EnumerateFiles(this.plansDir, "*.json").FirstOrDefault();
|
||||||
|
if (path == null)
|
||||||
{
|
{
|
||||||
var message = rabbitMqClient.GetMessage();
|
var message = rabbitMqClient.GetMessage();
|
||||||
File.WriteAllText(filePath, message.Body);
|
var id = message.Id;
|
||||||
|
if(string.IsNullOrEmpty(id))
|
||||||
|
{
|
||||||
|
id = unknownName;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = Path.Combine(this.plansDir, string.Format("{0}.json", id));
|
||||||
|
File.WriteAllText(path, message.Body);
|
||||||
|
Log.Info("Received new execution plan {0}", id);
|
||||||
message.Ack();
|
message.Ack();
|
||||||
}
|
}
|
||||||
var executor = new PlanExecutor(filePath);
|
else
|
||||||
var result = executor.Execute();
|
{
|
||||||
if(stop) break;
|
var id = Path.GetFileNameWithoutExtension(path);
|
||||||
rabbitMqClient.SendResult(result);
|
Log.Info("Executing exising plan {0}", id);
|
||||||
File.Delete(filePath);
|
}
|
||||||
|
var executor = new PlanExecutor(path);
|
||||||
|
executor.Execute();
|
||||||
|
File.Delete(path);
|
||||||
|
delayFactor = 1;
|
||||||
|
|
||||||
|
if (stop) break;
|
||||||
if (executor.RebootNeeded)
|
if (executor.RebootNeeded)
|
||||||
{
|
{
|
||||||
doReboot = true;
|
Reboot();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
@ -59,16 +106,40 @@ namespace Mirantis.Keero.WindowsAgent
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if (doReboot)
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Reboot()
|
||||||
|
{
|
||||||
|
Log.Info("Going for reboot!!");
|
||||||
|
LogManager.Flush();
|
||||||
|
/*try
|
||||||
{
|
{
|
||||||
try
|
System.Diagnostics.Process.Start("shutdown.exe", "-r -t 0");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.ErrorException("Cannot execute shutdown.exe", ex);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PowerShell.Create().AddCommand("Restart-Computer").AddParameter("Force").Invoke();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
|
||||||
|
Log.FatalException("Reboot exception", exception);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Log.Info("Waiting for reboot");
|
||||||
|
for (var i = 0; i < 10 * 60 * 5 && !stop; i++)
|
||||||
{
|
{
|
||||||
System.Diagnostics.Process.Start("shutdown.exe", "-r -t 0");
|
Thread.Sleep(100);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.ErrorException("Cannot execute shutdown.exe", ex);
|
|
||||||
}
|
}
|
||||||
|
Log.Info("Done waiting for reboot");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -78,18 +149,18 @@ namespace Mirantis.Keero.WindowsAgent
|
|||||||
if (stop) return;
|
if (stop) return;
|
||||||
Log.WarnException("Exception in main loop", exception);
|
Log.WarnException("Exception in main loop", exception);
|
||||||
var i = 0;
|
var i = 0;
|
||||||
while (!stop && i < 10)
|
while (!stop && i < 10 * (delayFactor * delayFactor))
|
||||||
{
|
{
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(100);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
delayFactor = Math.Min(delayFactor + 1, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnStop()
|
protected override void OnStop()
|
||||||
{
|
{
|
||||||
stop = true;
|
stop = true;
|
||||||
this.rabbitMqClient.Dispose();
|
this.rabbitMqClient.Dispose();
|
||||||
Console.WriteLine("Stop");
|
|
||||||
base.OnStop();
|
base.OnStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,12 +5,14 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
using RabbitMQ.Client;
|
using RabbitMQ.Client;
|
||||||
|
|
||||||
namespace Mirantis.Keero.WindowsAgent
|
namespace Mirantis.Keero.WindowsAgent
|
||||||
{
|
{
|
||||||
class RabbitMqClient : IDisposable
|
class RabbitMqClient : IDisposable
|
||||||
{
|
{
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
private static readonly ConnectionFactory connectionFactory;
|
private static readonly ConnectionFactory connectionFactory;
|
||||||
private IConnection currentConnecton;
|
private IConnection currentConnecton;
|
||||||
|
|
||||||
@ -43,21 +45,22 @@ namespace Mirantis.Keero.WindowsAgent
|
|||||||
}
|
}
|
||||||
var session = connection.CreateModel();
|
var session = connection.CreateModel();
|
||||||
session.BasicQos(0, 1, false);
|
session.BasicQos(0, 1, false);
|
||||||
session.QueueDeclare(queueName, true, false, false, null);
|
//session.QueueDeclare(queueName, true, false, false, null);
|
||||||
var consumer = new QueueingBasicConsumer(session);
|
var consumer = new QueueingBasicConsumer(session);
|
||||||
var consumeTag = session.BasicConsume(queueName, false, consumer);
|
var consumeTag = session.BasicConsume(queueName, false, consumer);
|
||||||
var e = (RabbitMQ.Client.Events.BasicDeliverEventArgs)consumer.Queue.Dequeue();
|
var e = (RabbitMQ.Client.Events.BasicDeliverEventArgs) consumer.Queue.Dequeue();
|
||||||
Action ackFunc = delegate {
|
Action ackFunc = delegate {
|
||||||
session.BasicAck(e.DeliveryTag, false);
|
session.BasicAck(e.DeliveryTag, false);
|
||||||
session.BasicCancel(consumeTag);
|
session.BasicCancel(consumeTag);
|
||||||
session.Close();
|
session.Close();
|
||||||
};
|
};
|
||||||
|
|
||||||
return new MqMessage(ackFunc) {
|
return new MqMessage(ackFunc) {
|
||||||
Body = Encoding.UTF8.GetString(e.Body)
|
Body = Encoding.UTF8.GetString(e.Body),
|
||||||
|
Id = e.BasicProperties.MessageId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
|
|
||||||
Dispose();
|
Dispose();
|
||||||
@ -65,10 +68,11 @@ namespace Mirantis.Keero.WindowsAgent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendResult(string text)
|
public void SendResult(MqMessage message)
|
||||||
{
|
{
|
||||||
var exchangeName = ConfigurationManager.AppSettings["rabbitmq.resultExchange"] ?? "";
|
var exchangeName = ConfigurationManager.AppSettings["rabbitmq.resultExchange"] ?? "";
|
||||||
var resultQueue = ConfigurationManager.AppSettings["rabbitmq.resultQueue"] ?? "-execution-results";
|
var resultRoutingKey = ConfigurationManager.AppSettings["rabbitmq.resultRoutingKey"] ?? "-execution-results";
|
||||||
|
bool durable = bool.Parse(ConfigurationManager.AppSettings["rabbitmq.durableMessages"] ?? "true");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -78,18 +82,19 @@ namespace Mirantis.Keero.WindowsAgent
|
|||||||
connection = this.currentConnecton = this.currentConnecton ?? connectionFactory.CreateConnection();
|
connection = this.currentConnecton = this.currentConnecton ?? connectionFactory.CreateConnection();
|
||||||
}
|
}
|
||||||
var session = connection.CreateModel();
|
var session = connection.CreateModel();
|
||||||
if (!string.IsNullOrEmpty(resultQueue))
|
/*if (!string.IsNullOrEmpty(resultQueue))
|
||||||
{
|
{
|
||||||
session.QueueDeclare(resultQueue, true, false, false, null);
|
//session.QueueDeclare(resultQueue, true, false, false, null);
|
||||||
if (!string.IsNullOrEmpty(exchangeName))
|
if (!string.IsNullOrEmpty(exchangeName))
|
||||||
{
|
{
|
||||||
session.ExchangeBind(exchangeName, resultQueue, resultQueue);
|
session.ExchangeBind(exchangeName, resultQueue, resultQueue);
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
var basicProperties = session.CreateBasicProperties();
|
var basicProperties = session.CreateBasicProperties();
|
||||||
basicProperties.SetPersistent(true);
|
basicProperties.SetPersistent(durable);
|
||||||
|
basicProperties.MessageId = message.Id;
|
||||||
basicProperties.ContentType = "application/json";
|
basicProperties.ContentType = "application/json";
|
||||||
session.BasicPublish(exchangeName, resultQueue, basicProperties, Encoding.UTF8.GetBytes(text));
|
session.BasicPublish(exchangeName, resultRoutingKey, basicProperties, Encoding.UTF8.GetBytes(message.Body));
|
||||||
session.Close();
|
session.Close();
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
|
import datetime
|
||||||
import glob
|
import glob
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import tornado.ioloop
|
import tornado.ioloop
|
||||||
|
|
||||||
|
|
||||||
import rabbitmq
|
import rabbitmq
|
||||||
from workflow import Workflow
|
from workflow import Workflow
|
||||||
import cloud_formation
|
import cloud_formation
|
||||||
import windows_agent
|
import windows_agent
|
||||||
from commands.dispatcher import CommandDispatcher
|
from commands.dispatcher import CommandDispatcher
|
||||||
from config import Config
|
from config import Config
|
||||||
|
import reporting
|
||||||
|
|
||||||
config = Config(sys.argv[1] if len(sys.argv) > 1 else None)
|
config = Config(sys.argv[1] if len(sys.argv) > 1 else None)
|
||||||
|
|
||||||
@ -21,16 +21,21 @@ rmqclient = rabbitmq.RabbitMqClient(
|
|||||||
password=config.get_setting('rabbitmq', 'password', 'guest'),
|
password=config.get_setting('rabbitmq', 'password', 'guest'),
|
||||||
host=config.get_setting('rabbitmq', 'host', 'localhost'))
|
host=config.get_setting('rabbitmq', 'host', 'localhost'))
|
||||||
|
|
||||||
|
|
||||||
def schedule(callback, *args, **kwargs):
|
def schedule(callback, *args, **kwargs):
|
||||||
tornado.ioloop.IOLoop.instance().add_timeout(time.time() + 0.1,
|
tornado.ioloop.IOLoop.instance().add_timeout(time.time() + 0.1,
|
||||||
lambda args=args, kwargs = kwargs: callback(*args, **kwargs))
|
lambda args=args, kwargs=kwargs: callback(*args, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
def task_received(task, message_id):
|
||||||
|
print 'Starting at', datetime.datetime.now()
|
||||||
|
reporter = reporting.Reporter(rmqclient, message_id, task['id'])
|
||||||
|
|
||||||
def task_received(task):
|
|
||||||
command_dispatcher = CommandDispatcher(task['name'], rmqclient)
|
command_dispatcher = CommandDispatcher(task['name'], rmqclient)
|
||||||
workflows = []
|
workflows = []
|
||||||
for path in glob.glob("data/workflows/*.xml"):
|
for path in glob.glob("data/workflows/*.xml"):
|
||||||
print "loading", path
|
print "loading", path
|
||||||
workflow = Workflow(path, task, command_dispatcher, config)
|
workflow = Workflow(path, task, command_dispatcher, config, reporter)
|
||||||
workflows.append(workflow)
|
workflows.append(workflow)
|
||||||
|
|
||||||
def loop(callback):
|
def loop(callback):
|
||||||
@ -41,14 +46,14 @@ def task_received(task):
|
|||||||
|
|
||||||
def shutdown():
|
def shutdown():
|
||||||
command_dispatcher.close()
|
command_dispatcher.close()
|
||||||
rmqclient.send('task-results', json.dumps(task))
|
rmqclient.send('task-results', json.dumps(task), message_id=message_id)
|
||||||
print "Done!!!!!!!!!!"
|
print 'Finished at', datetime.datetime.now()
|
||||||
|
|
||||||
loop(shutdown)
|
loop(shutdown)
|
||||||
|
|
||||||
|
|
||||||
def message_received(body):
|
def message_received(body, message_id, **kwargs):
|
||||||
task_received(json.loads(body))
|
task_received(json.loads(body), message_id)
|
||||||
|
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
|
@ -1,10 +1,39 @@
|
|||||||
|
import base64
|
||||||
|
|
||||||
import xml_code_engine
|
import xml_code_engine
|
||||||
|
|
||||||
def update_cf_stack(engine, context, body, template, mappings, arguments, **kwargs):
|
|
||||||
|
def update_cf_stack(engine, context, body, template,
|
||||||
|
mappings, arguments, **kwargs):
|
||||||
command_dispatcher = context['/commandDispatcher']
|
command_dispatcher = context['/commandDispatcher']
|
||||||
|
print "update-cf", template
|
||||||
|
|
||||||
callback = lambda result: engine.evaluate_content(body.find('success'), context)
|
callback = lambda result: engine.evaluate_content(
|
||||||
command_dispatcher.execute(name='cf', template=template, mappings=mappings, arguments=arguments, callback=callback)
|
body.find('success'), context)
|
||||||
|
|
||||||
|
command_dispatcher.execute(
|
||||||
|
name='cf', template=template, mappings=mappings,
|
||||||
|
arguments=arguments, callback=callback)
|
||||||
|
|
||||||
|
|
||||||
xml_code_engine.XmlCodeEngine.register_function(update_cf_stack, "update-cf-stack")
|
def prepare_user_data(context, template='Default', **kwargs):
|
||||||
|
config = context['/config']
|
||||||
|
with open('data/init.ps1') as init_script_file:
|
||||||
|
with open('data/templates/agent-config/%s.template'
|
||||||
|
% template) as template_file:
|
||||||
|
init_script = init_script_file.read()
|
||||||
|
template_data = template_file.read().replace(
|
||||||
|
'%RABBITMQ_HOST%',
|
||||||
|
config.get_setting('rabbitmq', 'host') or 'localhost')
|
||||||
|
|
||||||
|
return init_script.replace(
|
||||||
|
'%WINDOWS_AGENT_CONFIG_BASE64%',
|
||||||
|
base64.b64encode(template_data))
|
||||||
|
|
||||||
|
|
||||||
|
xml_code_engine.XmlCodeEngine.register_function(
|
||||||
|
update_cf_stack, "update-cf-stack")
|
||||||
|
|
||||||
|
xml_code_engine.XmlCodeEngine.register_function(
|
||||||
|
prepare_user_data, "prepare_user_data")
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import conductor.helpers
|
|||||||
from command import CommandBase
|
from command import CommandBase
|
||||||
from subprocess import call
|
from subprocess import call
|
||||||
|
|
||||||
|
|
||||||
class HeatExecutor(CommandBase):
|
class HeatExecutor(CommandBase):
|
||||||
def __init__(self, stack):
|
def __init__(self, stack):
|
||||||
self._pending_list = []
|
self._pending_list = []
|
||||||
@ -15,7 +16,8 @@ class HeatExecutor(CommandBase):
|
|||||||
with open('data/templates/cf/%s.template' % template) as template_file:
|
with open('data/templates/cf/%s.template' % template) as template_file:
|
||||||
template_data = template_file.read()
|
template_data = template_file.read()
|
||||||
|
|
||||||
template_data = conductor.helpers.transform_json(json.loads(template_data), mappings)
|
template_data = conductor.helpers.transform_json(
|
||||||
|
json.loads(template_data), mappings)
|
||||||
|
|
||||||
self._pending_list.append({
|
self._pending_list.append({
|
||||||
'template': template_data,
|
'template': template_data,
|
||||||
@ -27,32 +29,37 @@ class HeatExecutor(CommandBase):
|
|||||||
return len(self._pending_list) > 0
|
return len(self._pending_list) > 0
|
||||||
|
|
||||||
def execute_pending(self, callback):
|
def execute_pending(self, callback):
|
||||||
if not self._pending_list:
|
if not self.has_pending_commands():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
template = {}
|
template = {}
|
||||||
arguments = {}
|
arguments = {}
|
||||||
for t in self._pending_list:
|
for t in self._pending_list:
|
||||||
template = conductor.helpers.merge_dicts(template, t['template'], max_levels=2)
|
template = conductor.helpers.merge_dicts(
|
||||||
arguments = conductor.helpers.merge_dicts(arguments, t['arguments'], max_levels=1)
|
template, t['template'], max_levels=2)
|
||||||
|
arguments = conductor.helpers.merge_dicts(
|
||||||
|
arguments, t['arguments'], max_levels=1)
|
||||||
|
|
||||||
print 'Executing heat template', json.dumps(template), 'with arguments', arguments, 'on stack', self._stack
|
print 'Executing heat template', json.dumps(template), \
|
||||||
|
'with arguments', arguments, 'on stack', self._stack
|
||||||
|
|
||||||
if not os.path.exists("tmp"):
|
if not os.path.exists("tmp"):
|
||||||
os.mkdir("tmp")
|
os.mkdir("tmp")
|
||||||
file_name = "tmp/"+str(uuid.uuid4())
|
file_name = "tmp/" + str(uuid.uuid4())
|
||||||
print "Saving template to", file_name
|
print "Saving template to", file_name
|
||||||
with open(file_name, "w") as f:
|
with open(file_name, "w") as f:
|
||||||
f.write(json.dumps(template))
|
f.write(json.dumps(template))
|
||||||
|
|
||||||
arguments_str = ";".join(['%s=%s' % (key, value) for (key, value) in arguments.items()])
|
arguments_str = ';'.join(['%s=%s' % (key, value)
|
||||||
|
for (key, value) in arguments.items()])
|
||||||
call([
|
call([
|
||||||
"./heat_run","stack-create",
|
"./heat_run", "stack-create",
|
||||||
"-f" + file_name,
|
"-f" + file_name,
|
||||||
"-P" + arguments_str,
|
"-P" + arguments_str,
|
||||||
self._stack
|
self._stack
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
callbacks = []
|
callbacks = []
|
||||||
for t in self._pending_list:
|
for t in self._pending_list:
|
||||||
if t['callback']:
|
if t['callback']:
|
||||||
@ -66,10 +73,3 @@ class HeatExecutor(CommandBase):
|
|||||||
callback()
|
callback()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
class CommandBase(object):
|
class CommandBase(object):
|
||||||
def execute(self, **kwargs):
|
def execute(self, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
@ -2,11 +2,13 @@ import command
|
|||||||
import cloud_formation
|
import cloud_formation
|
||||||
import windows_agent
|
import windows_agent
|
||||||
|
|
||||||
|
|
||||||
class CommandDispatcher(command.CommandBase):
|
class CommandDispatcher(command.CommandBase):
|
||||||
def __init__(self, environment_name, rmqclient):
|
def __init__(self, environment_name, rmqclient):
|
||||||
self._command_map = {
|
self._command_map = {
|
||||||
'cf': cloud_formation.HeatExecutor(environment_name),
|
'cf': cloud_formation.HeatExecutor(environment_name),
|
||||||
'agent': windows_agent.WindowsAgentExecutor(environment_name, rmqclient)
|
'agent': windows_agent.WindowsAgentExecutor(
|
||||||
|
environment_name, rmqclient)
|
||||||
}
|
}
|
||||||
|
|
||||||
def execute(self, name, **kwargs):
|
def execute(self, name, **kwargs):
|
||||||
@ -28,7 +30,6 @@ class CommandDispatcher(command.CommandBase):
|
|||||||
count[0] -= 1
|
count[0] -= 1
|
||||||
result -= 1
|
result -= 1
|
||||||
|
|
||||||
|
|
||||||
return result > 0
|
return result > 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,34 +1,42 @@
|
|||||||
import json
|
import json
|
||||||
|
import uuid
|
||||||
|
|
||||||
import conductor.helpers
|
import conductor.helpers
|
||||||
from command import CommandBase
|
from command import CommandBase
|
||||||
|
|
||||||
|
|
||||||
class WindowsAgentExecutor(CommandBase):
|
class WindowsAgentExecutor(CommandBase):
|
||||||
def __init__(self, stack, rmqclient):
|
def __init__(self, stack, rmqclient):
|
||||||
self._pending_list = []
|
|
||||||
self._stack = stack
|
self._stack = stack
|
||||||
self._rmqclient = rmqclient
|
self._rmqclient = rmqclient
|
||||||
self._callback = None
|
self._callback = None
|
||||||
|
self._pending_list = []
|
||||||
|
self._current_pending_list = []
|
||||||
rmqclient.subscribe('-execution-results', self._on_message)
|
rmqclient.subscribe('-execution-results', self._on_message)
|
||||||
print "--------------------"
|
|
||||||
|
|
||||||
def execute(self, template, mappings, host, callback):
|
def execute(self, template, mappings, host, callback):
|
||||||
with open('data/templates/agent/%s.template' % template) as template_file:
|
with open('data/templates/agent/%s.template' %
|
||||||
|
template) as template_file:
|
||||||
template_data = template_file.read()
|
template_data = template_file.read()
|
||||||
|
|
||||||
template_data = json.dumps(conductor.helpers.transform_json(json.loads(template_data), mappings))
|
template_data = json.dumps(conductor.helpers.transform_json(
|
||||||
|
json.loads(template_data), mappings))
|
||||||
|
|
||||||
self._pending_list.append({
|
self._pending_list.append({
|
||||||
|
'id': str(uuid.uuid4()).lower(),
|
||||||
'template': template_data,
|
'template': template_data,
|
||||||
'host': ('%s-%s' % (self._stack, host)).lower().replace(' ', '-'),
|
'host': ('%s-%s' % (self._stack, host)).lower().replace(' ', '-'),
|
||||||
'callback': callback
|
'callback': callback
|
||||||
})
|
})
|
||||||
|
|
||||||
def _on_message(self, body):
|
def _on_message(self, body, message_id, **kwargs):
|
||||||
if self._pending_list:
|
msg_id = message_id.lower()
|
||||||
item = self._pending_list.pop()
|
item, index = conductor.helpers.find(lambda t: t['id'] == msg_id,
|
||||||
|
self._current_pending_list)
|
||||||
|
if item:
|
||||||
|
self._current_pending_list.pop(index)
|
||||||
item['callback'](json.loads(body))
|
item['callback'](json.loads(body))
|
||||||
if self._callback and not self._pending_list:
|
if self._callback and not self._current_pending_list:
|
||||||
cb = self._callback
|
cb = self._callback
|
||||||
self._callback = None
|
self._callback = None
|
||||||
cb()
|
cb()
|
||||||
@ -37,19 +45,19 @@ class WindowsAgentExecutor(CommandBase):
|
|||||||
return len(self._pending_list) > 0
|
return len(self._pending_list) > 0
|
||||||
|
|
||||||
def execute_pending(self, callback):
|
def execute_pending(self, callback):
|
||||||
if not self._pending_list:
|
if not self.has_pending_commands():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
self._current_pending_list = self._pending_list
|
||||||
|
self._pending_list = []
|
||||||
|
|
||||||
self._callback = callback
|
self._callback = callback
|
||||||
|
|
||||||
for t in self._pending_list:
|
for rec in self._current_pending_list:
|
||||||
self._rmqclient.send(queue=t['host'], data=t['template'])
|
self._rmqclient.send(
|
||||||
print 'Sending RMQ message %s to %s' % (t['template'], t['host'])
|
queue=rec['host'], data=rec['template'], message_id=rec['id'])
|
||||||
|
print 'Sending RMQ message %s to %s' % (
|
||||||
callbacks = []
|
rec['template'], rec['host'])
|
||||||
for t in self._pending_list:
|
|
||||||
if t['callback']:
|
|
||||||
callbacks.append(t['callback'])
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from ConfigParser import SafeConfigParser
|
from ConfigParser import SafeConfigParser
|
||||||
|
|
||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
CONFIG_PATH = './etc/app.config'
|
CONFIG_PATH = './etc/app.config'
|
||||||
|
|
||||||
@ -14,4 +15,5 @@ class Config(object):
|
|||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
parts = item.rsplit('.', 1)
|
parts = item.rsplit('.', 1)
|
||||||
return self.get_setting(parts[0] if len(parts) == 2 else 'DEFAULT', parts[-1])
|
return self.get_setting(
|
||||||
|
parts[0] if len(parts) == 2 else 'DEFAULT', parts[-1])
|
||||||
|
@ -5,7 +5,8 @@ class Context(object):
|
|||||||
|
|
||||||
def _get_data(self):
|
def _get_data(self):
|
||||||
if self._data is None:
|
if self._data is None:
|
||||||
self._data = {} if self._parent is None else self._parent._get_data().copy()
|
self._data = {} if self._parent is None \
|
||||||
|
else self._parent._get_data().copy()
|
||||||
return self._data
|
return self._data
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
import types
|
import types
|
||||||
|
|
||||||
|
|
||||||
def transform_json(json, mappings):
|
def transform_json(json, mappings):
|
||||||
if isinstance(json, types.ListType):
|
if isinstance(json, types.ListType):
|
||||||
result=[]
|
return [transform_json(t, mappings) for t in json]
|
||||||
for t in json:
|
|
||||||
result.append(transform_json(t, mappings))
|
|
||||||
return result
|
|
||||||
|
|
||||||
if isinstance(json, types.DictionaryType):
|
if isinstance(json, types.DictionaryType):
|
||||||
result = {}
|
result = {}
|
||||||
for key, value in json.items():
|
for key, value in json.items():
|
||||||
result[transform_json(key, mappings)] = transform_json(value, mappings)
|
result[transform_json(key, mappings)] = \
|
||||||
|
transform_json(value, mappings)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
if isinstance(json, types.StringTypes) and json.startswith('$'):
|
if isinstance(json, types.StringTypes) and json.startswith('$'):
|
||||||
@ -20,17 +19,30 @@ def transform_json(json, mappings):
|
|||||||
|
|
||||||
return json
|
return json
|
||||||
|
|
||||||
|
|
||||||
def merge_dicts(dict1, dict2, max_levels=0):
|
def merge_dicts(dict1, dict2, max_levels=0):
|
||||||
result = {}
|
result = {}
|
||||||
for key, value in dict1.items():
|
for key, value in dict1.items():
|
||||||
result[key] = value
|
result[key] = value
|
||||||
if key in dict2:
|
if key in dict2:
|
||||||
other_value = dict2[key]
|
other_value = dict2[key]
|
||||||
if max_levels == 1 or not isinstance(other_value, types.DictionaryType):
|
if max_levels == 1 or not isinstance(
|
||||||
|
other_value, types.DictionaryType):
|
||||||
result[key] = other_value
|
result[key] = other_value
|
||||||
else:
|
else:
|
||||||
result[key] = merge_dicts(value, other_value, 0 if max_levels == 0 else max_levels-1)
|
result[key] = merge_dicts(
|
||||||
|
value, other_value,
|
||||||
|
0 if max_levels == 0 else max_levels - 1)
|
||||||
for key, value in dict2.items():
|
for key, value in dict2.items():
|
||||||
if key not in result:
|
if key not in result:
|
||||||
result[key] = value
|
result[key] = value
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def find(f, seq):
|
||||||
|
"""Return first item in sequence where f(item) == True."""
|
||||||
|
index = 0
|
||||||
|
for item in seq:
|
||||||
|
if f(item):
|
||||||
|
return item, index
|
||||||
|
index += 1
|
||||||
|
return None, -1
|
||||||
|
@ -5,22 +5,24 @@ import time
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import tornado.ioloop
|
import tornado.ioloop
|
||||||
|
|
||||||
IOLoop = tornado.ioloop.IOLoop
|
IOLoop = tornado.ioloop.IOLoop
|
||||||
except ImportError:
|
except ImportError:
|
||||||
IOLoop = None
|
IOLoop = None
|
||||||
|
|
||||||
|
|
||||||
class RabbitMqClient(object):
|
class RabbitMqClient(object):
|
||||||
def __init__(self, host='localhost', login='guest', password='guest', virtual_host='/'):
|
def __init__(self, host='localhost', login='guest',
|
||||||
|
password='guest', virtual_host='/'):
|
||||||
credentials = pika.PlainCredentials(login, password)
|
credentials = pika.PlainCredentials(login, password)
|
||||||
self._connection_parameters = pika.ConnectionParameters(
|
self._connection_parameters = pika.ConnectionParameters(
|
||||||
credentials = credentials, host = host, virtual_host = virtual_host)
|
credentials=credentials, host=host, virtual_host=virtual_host)
|
||||||
self._subscriptions = {}
|
self._subscriptions = {}
|
||||||
|
|
||||||
def _create_connection(self):
|
def _create_connection(self):
|
||||||
self.connection = TornadoConnection(
|
self.connection = TornadoConnection(
|
||||||
parameters = self._connection_parameters,
|
parameters=self._connection_parameters,
|
||||||
on_open_callback = self._on_connected)
|
on_open_callback=self._on_connected)
|
||||||
|
|
||||||
def _on_connected(self, connection):
|
def _on_connected(self, connection):
|
||||||
self._channel = connection.channel(self._on_channel_open)
|
self._channel = connection.channel(self._on_channel_open)
|
||||||
@ -32,31 +34,39 @@ class RabbitMqClient(object):
|
|||||||
|
|
||||||
def _on_queue_declared(self, frame, queue, callback, ctag):
|
def _on_queue_declared(self, frame, queue, callback, ctag):
|
||||||
def invoke_callback(ch, method_frame, header_frame, body):
|
def invoke_callback(ch, method_frame, header_frame, body):
|
||||||
callback(body)
|
callback(body=body,
|
||||||
|
message_id=header_frame.message_id or "")
|
||||||
|
|
||||||
self._channel.basic_consume(invoke_callback, queue=queue, no_ack=True, consumer_tag=ctag)
|
self._channel.basic_consume(invoke_callback, queue=queue,
|
||||||
|
no_ack=True, consumer_tag=ctag)
|
||||||
|
|
||||||
def subscribe(self, queue, callback):
|
def subscribe(self, queue, callback):
|
||||||
ctag = str(uuid.uuid4())
|
ctag = str(uuid.uuid4())
|
||||||
self._subscriptions[queue] = ctag
|
self._subscriptions[queue] = ctag
|
||||||
|
|
||||||
self._channel.queue_declare(queue=queue, durable=True,
|
self._channel.queue_declare(
|
||||||
callback= lambda frame, ctag=ctag : self._on_queue_declared(frame, queue, callback, ctag))
|
queue=queue, durable=True,
|
||||||
|
callback=lambda frame, ctag=ctag: self._on_queue_declared(
|
||||||
|
frame, queue, callback, ctag))
|
||||||
|
|
||||||
def unsubscribe(self, queue):
|
def unsubscribe(self, queue):
|
||||||
self._channel.basic_cancel(consumer_tag=self._subscriptions[queue])
|
self._channel.basic_cancel(consumer_tag=self._subscriptions[queue])
|
||||||
del self._subscriptions[queue]
|
del self._subscriptions[queue]
|
||||||
|
|
||||||
|
|
||||||
def start(self, callback=None):
|
def start(self, callback=None):
|
||||||
if IOLoop is None: raise ImportError("Tornado not installed")
|
if IOLoop is None: raise ImportError("Tornado not installed")
|
||||||
self._started_callback = callback
|
self._started_callback = callback
|
||||||
ioloop = IOLoop.instance()
|
ioloop = IOLoop.instance()
|
||||||
self.timeout_id = ioloop.add_timeout(time.time() + 0.1, self._create_connection)
|
self.timeout_id = ioloop.add_timeout(time.time() + 0.1,
|
||||||
|
self._create_connection)
|
||||||
|
|
||||||
def send(self, queue, data, exchange=""):
|
def send(self, queue, data, exchange="", message_id=""):
|
||||||
self._channel.queue_declare(queue=queue, durable=True,
|
properties = pika.BasicProperties(message_id=message_id)
|
||||||
callback = lambda frame: self._channel.basic_publish(exchange=exchange, routing_key=queue, body=data))
|
self._channel.queue_declare(
|
||||||
|
queue=queue, durable=True,
|
||||||
|
callback=lambda frame: self._channel.basic_publish(
|
||||||
|
exchange=exchange, routing_key=queue,
|
||||||
|
body=data, properties=properties))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
29
conductor/conductor/reporting.py
Normal file
29
conductor/conductor/reporting.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import xml_code_engine
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class Reporter(object):
|
||||||
|
def __init__(self, rmqclient, task_id, environment_id):
|
||||||
|
self._rmqclient = rmqclient
|
||||||
|
self._task_id = task_id
|
||||||
|
self._environment_id = environment_id
|
||||||
|
|
||||||
|
def _report_func(self, id, entity, text, **kwargs):
|
||||||
|
msg = json.dumps({
|
||||||
|
'id': id,
|
||||||
|
'entity': entity,
|
||||||
|
'text': text,
|
||||||
|
'environment_id': self._environment_id
|
||||||
|
})
|
||||||
|
self._rmqclient.send(
|
||||||
|
queue='task-reports', data=msg, message_id=self._task_id)
|
||||||
|
|
||||||
|
def _report_func(context, id, entity, text, **kwargs):
|
||||||
|
reporter = context['/reporter']
|
||||||
|
return reporter._report_func(id, entity, text, **kwargs)
|
||||||
|
|
||||||
|
xml_code_engine.XmlCodeEngine.register_function(_report_func, "report")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,13 +1,25 @@
|
|||||||
import xml_code_engine
|
import xml_code_engine
|
||||||
|
|
||||||
def send_command(engine, context, body, template, mappings, host, **kwargs):
|
|
||||||
|
def send_command(engine, context, body, template, host, mappings=None,
|
||||||
|
result=None, **kwargs):
|
||||||
|
if not mappings: mappings = {}
|
||||||
command_dispatcher = context['/commandDispatcher']
|
command_dispatcher = context['/commandDispatcher']
|
||||||
|
|
||||||
def callback(result):
|
def callback(result_value):
|
||||||
print "Received ", result
|
print "Received result for %s: %s. Body is %s" % (template, result_value, body)
|
||||||
engine.evaluate_content(body.find('success'), context)
|
if result is not None:
|
||||||
|
context[result] = result_value['Result']
|
||||||
|
|
||||||
command_dispatcher.execute(name='agent', template=template, mappings=mappings, host=host, callback=callback)
|
success_handler = body.find('success')
|
||||||
|
if success_handler is not None:
|
||||||
|
engine.evaluate_content(success_handler, context)
|
||||||
|
|
||||||
|
command_dispatcher.execute(name='agent',
|
||||||
|
template=template,
|
||||||
|
mappings=mappings,
|
||||||
|
host=host,
|
||||||
|
callback=callback)
|
||||||
|
|
||||||
|
|
||||||
xml_code_engine.XmlCodeEngine.register_function(send_command, "send-command")
|
xml_code_engine.XmlCodeEngine.register_function(send_command, "send-command")
|
@ -6,13 +6,14 @@ import xml_code_engine
|
|||||||
import function_context
|
import function_context
|
||||||
|
|
||||||
class Workflow(object):
|
class Workflow(object):
|
||||||
def __init__(self, filename, data, command_dispatcher, config):
|
def __init__(self, filename, data, command_dispatcher, config, reporter):
|
||||||
self._data = data
|
self._data = data
|
||||||
self._engine = xml_code_engine.XmlCodeEngine()
|
self._engine = xml_code_engine.XmlCodeEngine()
|
||||||
with open(filename) as xml:
|
with open(filename) as xml:
|
||||||
self._engine.load(xml)
|
self._engine.load(xml)
|
||||||
self._command_dispatcher = command_dispatcher
|
self._command_dispatcher = command_dispatcher
|
||||||
self._config = config
|
self._config = config
|
||||||
|
self._reporter = reporter
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
while True:
|
while True:
|
||||||
@ -20,11 +21,16 @@ class Workflow(object):
|
|||||||
context['/dataSource'] = self._data
|
context['/dataSource'] = self._data
|
||||||
context['/commandDispatcher'] = self._command_dispatcher
|
context['/commandDispatcher'] = self._command_dispatcher
|
||||||
context['/config'] = self._config
|
context['/config'] = self._config
|
||||||
|
context['/reporter'] = self._reporter
|
||||||
if not self._engine.execute(context):
|
if not self._engine.execute(context):
|
||||||
break
|
break
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_path(obj, path, create_non_existing=False):
|
def _get_path(obj, path, create_non_existing=False):
|
||||||
|
# result = jsonpath.jsonpath(obj, '.'.join(path))
|
||||||
|
# if not result or len(result) < 1:
|
||||||
|
# return None
|
||||||
|
# return result[0]
|
||||||
current = obj
|
current = obj
|
||||||
for part in path:
|
for part in path:
|
||||||
if isinstance(current, types.ListType):
|
if isinstance(current, types.ListType):
|
||||||
@ -52,65 +58,87 @@ class Workflow(object):
|
|||||||
raise ValueError()
|
raise ValueError()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _select_func(path, context, **kwargs):
|
def _get_relative_position(path, context):
|
||||||
|
position = context['__dataSource_currentPosition'] or []
|
||||||
|
|
||||||
|
index = 0
|
||||||
|
for c in path:
|
||||||
|
if c == ':':
|
||||||
|
if len(position) > 0:
|
||||||
|
position = position[:-1]
|
||||||
|
elif c == '/':
|
||||||
|
position = []
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
return position, path[index:]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _correct_position(path, context):
|
||||||
|
position, suffix = Workflow._get_relative_position(path, context)
|
||||||
|
|
||||||
|
if not suffix:
|
||||||
|
return position
|
||||||
|
else:
|
||||||
|
return position + suffix.split('.')
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _select_func(context, path='', source=None, **kwargs):
|
||||||
|
|
||||||
if path.startswith('##'):
|
if path.startswith('##'):
|
||||||
config = context['/config']
|
config = context['/config']
|
||||||
return config[path[2:]]
|
return config[path[2:]]
|
||||||
elif path.startswith('#'):
|
elif path.startswith('#'):
|
||||||
return context[path[1:]]
|
return context[path[1:]]
|
||||||
|
|
||||||
position = context['dataSource_currentPosition'] or []
|
if source is not None:
|
||||||
data = context['dataSource']
|
return Workflow._get_path(
|
||||||
|
context[source], path.split('.'))
|
||||||
index = 0
|
else:
|
||||||
for c in path:
|
return Workflow._get_path(
|
||||||
if c == ':':
|
context['/dataSource'],
|
||||||
if len(position) > 0:
|
Workflow._correct_position(path, context))
|
||||||
position = position[:-1]
|
|
||||||
elif c == '/':
|
|
||||||
position = []
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
return Workflow._get_path(data, position + path[index:].split('.'))
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _set_func(path, context, body, engine, **kwargs):
|
def _set_func(path, context, body, engine, target=None, **kwargs):
|
||||||
body_data = engine.evaluate_content(body, context)
|
body_data = engine.evaluate_content(body, context)
|
||||||
|
|
||||||
if path[0] == '#':
|
|
||||||
context[path[1:]] = body_data
|
|
||||||
return
|
|
||||||
|
|
||||||
position = context['dataSource_currentPosition'] or []
|
if path.startswith('##'):
|
||||||
data = context['dataSource']
|
raise RuntimeError('Cannot modify config from XML-code')
|
||||||
|
elif path.startswith('#'):
|
||||||
|
context[':' + path[1:]] = body_data
|
||||||
|
return
|
||||||
|
|
||||||
index = 0
|
if target:
|
||||||
for c in path:
|
data = context[target]
|
||||||
if c == ':':
|
position = path.split('.')
|
||||||
if len(position) > 0:
|
if Workflow._get_path(data, position) != body_data:
|
||||||
position = position[:-1]
|
Workflow._set_path(data, position, body_data)
|
||||||
elif c == '/':
|
context['/hasSideEffects'] = True
|
||||||
position = []
|
else:
|
||||||
else:
|
data = context['/dataSource']
|
||||||
break
|
new_position = Workflow._correct_position(path, context)
|
||||||
|
if Workflow._get_path(data, new_position) != body_data:
|
||||||
index += 1
|
Workflow._set_path(data, new_position, body_data)
|
||||||
|
context['/hasSideEffects'] = True
|
||||||
new_position = position + path[index:].split('.')
|
|
||||||
if Workflow._get_path(data, new_position) != body_data:
|
|
||||||
Workflow._set_path(data, new_position, body_data)
|
|
||||||
context['/hasSideEffects'] = True
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _rule_func(match, context, body, engine, limit = 0, **kwargs):
|
def _rule_func(match, context, body, engine, limit=0, name=None, **kwargs):
|
||||||
position = context['dataSource_currentPosition'] or []
|
position = context['__dataSource_currentPosition'] or []
|
||||||
data = context['dataSource_currentObj']
|
|
||||||
if data is None:
|
if name == 'marker':
|
||||||
data = context['dataSource']
|
print "!"
|
||||||
match = re.sub(r'@\.([\w.]+)', r"Workflow._get_path(@, '\1'.split('.'))", match)
|
# data = context['__dataSource_currentObj']
|
||||||
|
# if data is None:
|
||||||
|
# data = context['/dataSource']
|
||||||
|
position, match = Workflow._get_relative_position(match, context)
|
||||||
|
data = Workflow._get_path(context['/dataSource'], position)
|
||||||
|
match = re.sub(r'@\.([\w.]+)',
|
||||||
|
r"Workflow._get_path(@, '\1'.split('.'))", match)
|
||||||
selected = jsonpath.jsonpath(data, match, 'IPATH') or []
|
selected = jsonpath.jsonpath(data, match, 'IPATH') or []
|
||||||
|
|
||||||
index = 0
|
index = 0
|
||||||
@ -119,8 +147,9 @@ class Workflow(object):
|
|||||||
break
|
break
|
||||||
index += 1
|
index += 1
|
||||||
new_position = position + found_match
|
new_position = position + found_match
|
||||||
context['dataSource_currentPosition'] = new_position
|
context['__dataSource_currentPosition'] = new_position
|
||||||
context['dataSource_currentObj'] = Workflow._get_path(context['dataSource'], new_position)
|
context['__dataSource_currentObj'] = Workflow._get_path(
|
||||||
|
context['/dataSource'], new_position)
|
||||||
for element in body:
|
for element in body:
|
||||||
engine.evaluate(element, context)
|
engine.evaluate(element, context)
|
||||||
if element.tag == 'rule' and context['/hasSideEffects']:
|
if element.tag == 'rule' and context['/hasSideEffects']:
|
||||||
@ -136,7 +165,14 @@ class Workflow(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
xml_code_engine.XmlCodeEngine.register_function(Workflow._rule_func, 'rule')
|
xml_code_engine.XmlCodeEngine.register_function(
|
||||||
xml_code_engine.XmlCodeEngine.register_function(Workflow._workflow_func, 'workflow')
|
Workflow._rule_func, 'rule')
|
||||||
xml_code_engine.XmlCodeEngine.register_function(Workflow._set_func, 'set')
|
|
||||||
xml_code_engine.XmlCodeEngine.register_function(Workflow._select_func, 'select')
|
xml_code_engine.XmlCodeEngine.register_function(
|
||||||
|
Workflow._workflow_func, 'workflow')
|
||||||
|
|
||||||
|
xml_code_engine.XmlCodeEngine.register_function(
|
||||||
|
Workflow._set_func, 'set')
|
||||||
|
|
||||||
|
xml_code_engine.XmlCodeEngine.register_function(
|
||||||
|
Workflow._select_func, 'select')
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
#from lxml import etree
|
#from lxml import etree
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
|
import types
|
||||||
|
|
||||||
import function_context
|
import function_context
|
||||||
|
|
||||||
|
|
||||||
class XmlCodeEngine(object):
|
class XmlCodeEngine(object):
|
||||||
_functionMap = {}
|
_functionMap = {}
|
||||||
|
|
||||||
@ -24,13 +27,14 @@ class XmlCodeEngine(object):
|
|||||||
|
|
||||||
definition = self._functionMap[name]
|
definition = self._functionMap[name]
|
||||||
context = function_context.Context(parent_context)
|
context = function_context.Context(parent_context)
|
||||||
args = { 'engine': self, 'body': element, 'context': context }
|
args = {'engine': self, 'body': element, 'context': context}
|
||||||
|
|
||||||
for key, value in element.items():
|
for key, value in element.items():
|
||||||
args[key] = value
|
args[key] = value
|
||||||
|
|
||||||
for parameter in element.findall('parameter'):
|
for parameter in element.findall('parameter'):
|
||||||
args[parameter.get('name')] = self.evaluate_content(parameter, context)
|
args[parameter.get('name')] = self.evaluate_content(
|
||||||
|
parameter, context)
|
||||||
|
|
||||||
return definition(**args)
|
return definition(**args)
|
||||||
|
|
||||||
@ -44,21 +48,22 @@ class XmlCodeEngine(object):
|
|||||||
if sub_element.tag == 'parameter':
|
if sub_element.tag == 'parameter':
|
||||||
continue
|
continue
|
||||||
do_strip = True
|
do_strip = True
|
||||||
parts.append(self._execute_function(sub_element.tag, sub_element, context))
|
parts.append(self._execute_function(
|
||||||
|
sub_element.tag, sub_element, context))
|
||||||
parts.append(sub_element.tail or '')
|
parts.append(sub_element.tail or '')
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
for t in parts:
|
for t in parts:
|
||||||
if not isinstance(t, (str, unicode)):
|
if not isinstance(t, types.StringTypes):
|
||||||
result.append(t)
|
result.append(t)
|
||||||
|
|
||||||
|
return_value = result
|
||||||
if len(result) == 0:
|
if len(result) == 0:
|
||||||
return_value = ''.join(parts)
|
return_value = ''.join(parts)
|
||||||
if do_strip: return_value = return_value.strip()
|
if do_strip: return_value = return_value.strip()
|
||||||
elif len(result) == 1:
|
elif len(result) == 1:
|
||||||
return_value = result[0]
|
return_value = result[0]
|
||||||
else:
|
|
||||||
return_value = result
|
|
||||||
|
|
||||||
return return_value
|
return return_value
|
||||||
|
|
||||||
@ -75,27 +80,34 @@ def _dict_func(engine, body, context, **kwargs):
|
|||||||
result[key] = value
|
result[key] = value
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _array_func(engine, body, context, **kwargs):
|
def _array_func(engine, body, context, **kwargs):
|
||||||
result = []
|
result = []
|
||||||
for item in body:
|
for item in body:
|
||||||
result.append(engine.evaluate(item, context))
|
result.append(engine.evaluate(item, context))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _text_func(engine, body, context, **kwargs):
|
def _text_func(engine, body, context, **kwargs):
|
||||||
return str(engine.evaluate_content(body, context))
|
return str(engine.evaluate_content(body, context))
|
||||||
|
|
||||||
|
|
||||||
def _int_func(engine, body, context, **kwargs):
|
def _int_func(engine, body, context, **kwargs):
|
||||||
return int(engine.evaluate_content(body, context))
|
return int(engine.evaluate_content(body, context))
|
||||||
|
|
||||||
|
|
||||||
def _function_func(engine, body, context, **kwargs):
|
def _function_func(engine, body, context, **kwargs):
|
||||||
return lambda: engine.evaluate_content(body, context)
|
return lambda: engine.evaluate_content(body, context)
|
||||||
|
|
||||||
|
|
||||||
def _null_func(**kwargs):
|
def _null_func(**kwargs):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _true_func(**kwargs):
|
def _true_func(**kwargs):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _false_func(**kwargs):
|
def _false_func(**kwargs):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -115,27 +127,8 @@ def xprint(context, body, **kwargs):
|
|||||||
for arg in kwargs:
|
for arg in kwargs:
|
||||||
print "%s = %s" % (arg, kwargs[arg])
|
print "%s = %s" % (arg, kwargs[arg])
|
||||||
print 'context = ', context
|
print 'context = ', context
|
||||||
print 'body = %s (%s)' %(body, body.text)
|
print 'body = %s (%s)' % (body, body.text)
|
||||||
print "------------------------- end -------------------------"
|
print "------------------------- end -------------------------"
|
||||||
|
|
||||||
|
|
||||||
XmlCodeEngine.register_function(xprint, "print")
|
XmlCodeEngine.register_function(xprint, "print")
|
||||||
|
|
||||||
|
|
||||||
# parser = XmlCodeEngine()
|
|
||||||
# file = open('test2.xml')
|
|
||||||
# parser.load(file)
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# context = functioncontext.Context()
|
|
||||||
# context['test'] = 'xxx'
|
|
||||||
#
|
|
||||||
# parser.execute(context)
|
|
||||||
#
|
|
||||||
# print etree.xpath('/')
|
|
||||||
# root = parser._document.getroot()
|
|
||||||
# print root.items()
|
|
||||||
# print root.text.lstrip() + "|"
|
|
||||||
# for x in root:
|
|
||||||
# print x, type(x), x.tail
|
|
||||||
|
|
||||||
|
14
conductor/data/init.ps1
Normal file
14
conductor/data/init.ps1
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ps1
|
||||||
|
|
||||||
|
$WindowsAgentConfigBase64 = '%WINDOWS_AGENT_CONFIG_BASE64%'
|
||||||
|
$WindowsAgentConfigFile = "C:\Keero\Agent\WindowsAgent.exe.config"
|
||||||
|
|
||||||
|
Import-Module CoreFunctions
|
||||||
|
|
||||||
|
Stop-Service "Keero Agent"
|
||||||
|
Backup-File $WindowsAgentConfigFile
|
||||||
|
Remove-Item $WindowsAgentConfigFile -Force
|
||||||
|
ConvertFrom-Base64String -Base64String $WindowsAgentConfigBase64 -Path $WindowsAgentConfigFile
|
||||||
|
Exec sc.exe 'config','"Keero Agent"','start=','delayed-auto'
|
||||||
|
Start-Service 'Keero Agent'
|
||||||
|
Write-Log 'All done!'
|
30
conductor/data/templates/agent-config/Default.template
Normal file
30
conductor/data/templates/agent-config/Default.template
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<configuration>
|
||||||
|
<configSections>
|
||||||
|
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
|
||||||
|
</configSections>
|
||||||
|
<startup>
|
||||||
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
|
||||||
|
</startup>
|
||||||
|
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<targets>
|
||||||
|
<target name="file" xsi:type="File" fileName="${basedir}/log.txt"
|
||||||
|
layout="${date} ${level}: <${logger:shortName=true}> ${message} ${exception:format=tostring}"/>
|
||||||
|
</targets>
|
||||||
|
|
||||||
|
<rules>
|
||||||
|
<logger name="*" minlevel="Debug" writeTo="file" />
|
||||||
|
</rules>
|
||||||
|
</nlog>
|
||||||
|
<appSettings>
|
||||||
|
<add key="rabbitmq.host" value="%RABBITMQ_HOST%"/>
|
||||||
|
<add key="rabbitmq.user" value="keero"/>
|
||||||
|
<add key="rabbitmq.password" value="keero"/>
|
||||||
|
<add key="rabbitmq.vhost" value="keero"/>
|
||||||
|
<add key="rabbitmq.resultExchange" value=""/>
|
||||||
|
<add key="rabbitmq.resultRoutingKey" value="-execution-results"/>
|
||||||
|
<add key="rabbitmq.durableMessages" value="true"/>
|
||||||
|
|
||||||
|
</appSettings>
|
||||||
|
</configuration>
|
12
conductor/data/templates/agent/AskDnsIp.template
Normal file
12
conductor/data/templates/agent/AskDnsIp.template
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"Scripts": [
|
||||||
|
"ZnVuY3Rpb24gR2V0LURuc0xpc3RlbmluZ0lwQWRkcmVzc2VzIHsNCiAgICAoR2V0LUROU1NlcnZlciAtQ29tcHV0ZXJOYW1lIGxvY2FsaG9zdCkuU2VydmVyU2V0dGluZy5MaXN0ZW5pbmdJcEFkZHJlc3MNCn0NCg=="
|
||||||
|
],
|
||||||
|
"Commands": [
|
||||||
|
{
|
||||||
|
"Name": "Get-DnsListeningIpAddress",
|
||||||
|
"Arguments": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"RebootOnCompletion": 0
|
||||||
|
}
|
@ -12,8 +12,8 @@
|
|||||||
{
|
{
|
||||||
"Name": "Install-RolePrimaryDomainController",
|
"Name": "Install-RolePrimaryDomainController",
|
||||||
"Arguments": {
|
"Arguments": {
|
||||||
"DomainName": "$dc_name",
|
"DomainName": "$domain",
|
||||||
"SafeModePassword": "$recovery_password"
|
"SafeModePassword": "$recoveryPassword"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
23
conductor/data/templates/agent/CreateSecondaryDC.template
Normal file
23
conductor/data/templates/agent/CreateSecondaryDC.template
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"Scripts": [
|
||||||
|
"RnVuY3Rpb24gSW5zdGFsbC1Sb2xlU2Vjb25kYXJ5RG9tYWluQ29udHJvbGxlcg0Kew0KPCMNCi5TWU5PUFNJUw0KSW5zdGFsbCBhZGRpdGlvbmFsIChzZWNvbmRhcnkpIGRvbWFpbiBjb250cm9sbGVyLg0KDQojPg0KCXBhcmFtDQoJKA0KCQlbU3RyaW5nXQ0KCQkjIERvbWFpbiBuYW1lIHRvIGpvaW4gdG8uDQoJCSREb21haW5OYW1lLA0KCQkNCgkJW1N0cmluZ10NCgkJIyBEb21haW4gdXNlciB3aG8gaXMgYWxsb3dlZCB0byBqb2luIGNvbXB1dGVyIHRvIGRvbWFpbi4NCgkJJFVzZXJOYW1lLA0KCQkNCgkJW1N0cmluZ10NCgkJIyBVc2VyJ3MgcGFzc3dvcmQuDQoJCSRQYXNzd29yZCwNCgkJDQoJCVtTdHJpbmddDQoJCSMgRG9tYWluIGNvbnRyb2xsZXIgcmVjb3ZlcnkgbW9kZSBwYXNzd29yZC4NCgkJJFNhZmVNb2RlUGFzc3dvcmQNCgkpDQoNCgl0cmFwIHsgU3RvcC1FeGVjdXRpb24gJF8gfQ0KCQ0KCSRDcmVkZW50aWFsID0gTmV3LUNyZWRlbnRpYWwgLVVzZXJOYW1lICIkRG9tYWluTmFtZVwkVXNlck5hbWUiIC1QYXNzd29yZCAkUGFzc3dvcmQNCgkJDQoJIyBBZGQgcmVxdWlyZWQgd2luZG93cyBmZWF0dXJlcw0KCUFkZC1XaW5kb3dzRmVhdHVyZVdyYXBwZXIgYA0KCQktTmFtZSAiRE5TIiwiQUQtRG9tYWluLVNlcnZpY2VzIiwiUlNBVC1ERlMtTWdtdC1Db24iIGANCgkJLUluY2x1ZGVNYW5hZ2VtZW50VG9vbHMgYA0KICAgICAgICAgICAgICAgIC1Ob3RpZnlSZXN0YXJ0DQoJCQ0KCQ0KICAgICAgICBXcml0ZS1Mb2cgIkFkZGluZyBzZWNvbmRhcnkgZG9tYWluIGNvbnRyb2xsZXIgLi4uIg0KICAgIA0KCSRTTUFQID0gQ29udmVydFRvLVNlY3VyZVN0cmluZyAtU3RyaW5nICRTYWZlTW9kZVBhc3N3b3JkIC1Bc1BsYWluVGV4dCAtRm9yY2UNCg0KCUluc3RhbGwtQUREU0RvbWFpbkNvbnRyb2xsZXIgYA0KCQktRG9tYWluTmFtZSAkRG9tYWluTmFtZSBgDQoJCS1TYWZlTW9kZUFkbWluaXN0cmF0b3JQYXNzd29yZCAkU01BUCBgDQoJCS1DcmVkZW50aWFsICRDcmVkZW50aWFsIGANCgkJLU5vUmVib290T25Db21wbGV0aW9uIGANCgkJLUZvcmNlIGANCgkJLUVycm9yQWN0aW9uIFN0b3AgfCBPdXQtTnVsbA0KDQoJV3JpdGUtTG9nICJXYWl0aW5nIGZvciByZXN0YXJ0IC4uLiINCiMJU3RvcC1FeGVjdXRpb24gLUV4aXRDb2RlIDMwMTAgLUV4aXRTdHJpbmcgIkNvbXB1dGVyIG11c3QgYmUgcmVzdGFydGVkIHRvIGZpbmlzaCBkb21haW4gY29udHJvbGxlciBwcm9tb3Rpb24uIg0KIwlXcml0ZS1Mb2cgIlJlc3RhcnRpbmcgY29tcHV0ZXIgLi4uIg0KIwlSZXN0YXJ0LUNvbXB1dGVyIC1Gb3JjZQ0KfQ0K"
|
||||||
|
],
|
||||||
|
"Commands": [
|
||||||
|
{
|
||||||
|
"Name": "Import-Module",
|
||||||
|
"Arguments": {
|
||||||
|
"Name": "CoreFunctions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Install-RoleSecondaryDomainController",
|
||||||
|
"Arguments": {
|
||||||
|
"DomainName": "$domain",
|
||||||
|
"UserName": "Administrator",
|
||||||
|
"Password": "$domainPassword",
|
||||||
|
"SafeModePassword": "$recoveryPassword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"RebootOnCompletion": 1
|
||||||
|
}
|
12
conductor/data/templates/agent/InstallIIS.template
Normal file
12
conductor/data/templates/agent/InstallIIS.template
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"Scripts": [
|
||||||
|
"ZnVuY3Rpb24gSW5zdGFsbC1XZWJTZXJ2ZXIgew0KICAgIEltcG9ydC1Nb2R1bGUgU2VydmVyTWFuYWdlcg0KICAgIEluc3RhbGwtV2luZG93c0ZlYXR1cmUgV2ViLVNlcnZlciAtSW5jbHVkZU1hbmFnZW1lbnRUb29scw0KfQ0K"
|
||||||
|
],
|
||||||
|
"Commands": [
|
||||||
|
{
|
||||||
|
"Name": "Install-WebServer",
|
||||||
|
"Arguments": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"RebootOnCompletion": 0
|
||||||
|
}
|
27
conductor/data/templates/agent/JoinDomain.template
Normal file
27
conductor/data/templates/agent/JoinDomain.template
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"Scripts": [],
|
||||||
|
"Commands": [
|
||||||
|
{
|
||||||
|
"Name": "Import-Module",
|
||||||
|
"Arguments": {
|
||||||
|
"Name": "CoreFunctions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Set-NetworkAdapterConfiguration",
|
||||||
|
"Arguments": {
|
||||||
|
"FirstAvailable": true,
|
||||||
|
"DNSServer": "$dnsIp"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Join-Domain",
|
||||||
|
"Arguments": {
|
||||||
|
"DomainName": "$domain",
|
||||||
|
"Username": "Administrator",
|
||||||
|
"Password": "$domainPassword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"RebootOnCompletion": 1
|
||||||
|
}
|
@ -13,7 +13,7 @@
|
|||||||
"Name": "Set-LocalUserPassword",
|
"Name": "Set-LocalUserPassword",
|
||||||
"Arguments": {
|
"Arguments": {
|
||||||
"UserName": "Administrator",
|
"UserName": "Administrator",
|
||||||
"Password": "$adm_password",
|
"Password": "$adminPassword",
|
||||||
"Force": true
|
"Force": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,8 @@
|
|||||||
"Properties": {
|
"Properties": {
|
||||||
"InstanceType" : { "Ref" : "InstanceType" },
|
"InstanceType" : { "Ref" : "InstanceType" },
|
||||||
"ImageId" : { "Ref" : "ImageName" },
|
"ImageId" : { "Ref" : "ImageName" },
|
||||||
"KeyName" : { "Ref" : "KeyName" }
|
"KeyName" : { "Ref" : "KeyName" },
|
||||||
|
"UserData": "$userData"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
199
conductor/data/workflows/AD.xml
Normal file
199
conductor/data/workflows/AD.xml
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
<workflow>
|
||||||
|
<rule match="$.services.activeDirectories[?(@.domain)].units[?(not @.isMaster)]">
|
||||||
|
<set path="domain">
|
||||||
|
<select path="::domain"/>
|
||||||
|
</set>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<rule match="$.services.activeDirectories[*].units[?(@.state.instanceName is None)]">
|
||||||
|
<report entity="unit">
|
||||||
|
<parameter name="id"><select path="id"/></parameter>
|
||||||
|
<parameter name="text">Creating instance <select path="name"/></parameter>
|
||||||
|
</report>
|
||||||
|
<update-cf-stack template="Windows">
|
||||||
|
<parameter name="mappings">
|
||||||
|
<map>
|
||||||
|
<mapping name="instanceName">
|
||||||
|
<select path="name"/>
|
||||||
|
</mapping>
|
||||||
|
<mapping name="userData">
|
||||||
|
<prepare_user_data/>
|
||||||
|
</mapping>
|
||||||
|
</map>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="arguments">
|
||||||
|
<map>
|
||||||
|
<argument name="KeyName">keero-linux-keys</argument>
|
||||||
|
<argument name="InstanceType">m1.medium</argument>
|
||||||
|
<argument name="ImageName">ws-2012-full-agent</argument>
|
||||||
|
</map>
|
||||||
|
</parameter>
|
||||||
|
|
||||||
|
<success>
|
||||||
|
<set path="state.instanceName"><select path="name"/></set>
|
||||||
|
<report entity="unit">
|
||||||
|
<parameter name="id"><select path="id"/></parameter>
|
||||||
|
<parameter name="text">Instance <select path="name"/> created</parameter>
|
||||||
|
</report>
|
||||||
|
</success>
|
||||||
|
</update-cf-stack>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<rule match="$.services.activeDirectories[*].units[?(@.state.instanceName and @.adminPassword and @.adminPassword != @.state.adminPassword)]">
|
||||||
|
<send-command template="SetPassword">
|
||||||
|
<parameter name="host">
|
||||||
|
<select path="name"/>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="mappings">
|
||||||
|
<map>
|
||||||
|
<mapping name="adminPassword">
|
||||||
|
<select path="adminPassword"/>
|
||||||
|
</mapping>
|
||||||
|
</map>
|
||||||
|
</parameter>
|
||||||
|
<success>
|
||||||
|
<set path="state.adminPassword">
|
||||||
|
<select path="adminPassword"/>
|
||||||
|
</set>
|
||||||
|
</success>
|
||||||
|
</send-command>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<rule match="$.services.activeDirectories[?(@.adminPassword and @.adminPassword != @.state.domainAdminPassword)].units[?(@.state.instanceName and @.isMaster)]">
|
||||||
|
<send-command template="SetPassword">
|
||||||
|
<parameter name="host">
|
||||||
|
<select path="name"/>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="mappings">
|
||||||
|
<map>
|
||||||
|
<mapping name="adminPassword">
|
||||||
|
<select path="::adminPassword"/>
|
||||||
|
</mapping>
|
||||||
|
</map>
|
||||||
|
</parameter>
|
||||||
|
<success>
|
||||||
|
<set path="::state.domainAdminPassword">
|
||||||
|
<select path="::adminPassword"/>
|
||||||
|
</set>
|
||||||
|
</success>
|
||||||
|
</send-command>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<rule match="$.services.activeDirectories[?(@.state.primaryDc is None)].units[?(@.state.instanceName and @.isMaster)]">
|
||||||
|
<report entity="unit">
|
||||||
|
<parameter name="id"><select path="id"/></parameter>
|
||||||
|
<parameter name="text">Creating Primary Domain Controller on unit <select path="name"/></parameter>
|
||||||
|
</report>
|
||||||
|
<send-command template="CreatePrimaryDC">
|
||||||
|
<parameter name="host">
|
||||||
|
<select path="name"/>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="mappings">
|
||||||
|
<map>
|
||||||
|
<mapping name="domain">
|
||||||
|
<select path="::domain"/>
|
||||||
|
</mapping>
|
||||||
|
<mapping name="recoveryPassword">
|
||||||
|
<select path="recoveryPassword"/>
|
||||||
|
</mapping>
|
||||||
|
</map>
|
||||||
|
</parameter>
|
||||||
|
<success>
|
||||||
|
<set path="::state.primaryDc"><select path="name"/></set>
|
||||||
|
<report entity="unit">
|
||||||
|
<parameter name="id"><select path="id"/></parameter>
|
||||||
|
<parameter name="text">Primary Domain Controller created</parameter>
|
||||||
|
</report>
|
||||||
|
</success>
|
||||||
|
</send-command>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<rule match="$.services.activeDirectories[?(@.state.primaryDc and not @.state.primaryDcIp)].units[?(@.state.instanceName and @.isMaster)]">
|
||||||
|
<send-command template="AskDnsIp" result="ip">
|
||||||
|
<parameter name="host">
|
||||||
|
<select path="name"/>
|
||||||
|
</parameter>
|
||||||
|
<success>
|
||||||
|
<set path="::state.primaryDcIp">
|
||||||
|
<select source="ip" path="0.Result.0"/>
|
||||||
|
</set>
|
||||||
|
<report entity="unit">
|
||||||
|
<parameter name="id"><select path="id"/></parameter>
|
||||||
|
<parameter name="text">DNS IP = <select source="ip" path="0.Result.0"/></parameter>
|
||||||
|
</report>
|
||||||
|
</success>
|
||||||
|
</send-command>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<rule match="$..units[?(@.state.instanceName and @.domain and @.domain != @.state.domain)]">
|
||||||
|
<set path="#unit">
|
||||||
|
<select/>
|
||||||
|
</set>
|
||||||
|
<rule>
|
||||||
|
<parameter name="match">/$.services.activeDirectories[?(@.domain == '<select path="domain"/>' and @.state.primaryDcIp)]</parameter>
|
||||||
|
|
||||||
|
<send-command template="JoinDomain">
|
||||||
|
<parameter name="host">
|
||||||
|
<select path="name" source="unit"/>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="mappings">
|
||||||
|
<map>
|
||||||
|
<mapping name="domain">
|
||||||
|
<select path="domain"/>
|
||||||
|
</mapping>
|
||||||
|
<mapping name="domainPassword">
|
||||||
|
<select path="adminPassword"/>
|
||||||
|
</mapping>
|
||||||
|
<mapping name="dnsIp">
|
||||||
|
<select path="state.primaryDcIp"/>
|
||||||
|
</mapping>
|
||||||
|
</map>
|
||||||
|
</parameter>
|
||||||
|
|
||||||
|
<success>
|
||||||
|
<set path="state.domain" target="unit">
|
||||||
|
<select path="domain"/>
|
||||||
|
</set>
|
||||||
|
<report entity="unit">
|
||||||
|
<parameter name="id"><select path="id" source="unit"/></parameter>
|
||||||
|
<parameter name="text">Unit <select path="name" source="unit"/> has joined domain <select path="domain"/></parameter>
|
||||||
|
</report>
|
||||||
|
</success>
|
||||||
|
</send-command>
|
||||||
|
</rule>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
|
||||||
|
<rule match="$.services.activeDirectories[*].units[?(@.state.domain and not @.isMaster and not @.state.installed)]">
|
||||||
|
<report entity="unit">
|
||||||
|
<parameter name="id"><select path="id"/></parameter>
|
||||||
|
<parameter name="text">Creating Secondary Domain Controller on unit <select path="name"/></parameter>
|
||||||
|
</report>
|
||||||
|
<send-command template="CreateSecondaryDC">
|
||||||
|
<parameter name="host">
|
||||||
|
<select path="name"/>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="mappings">
|
||||||
|
<map>
|
||||||
|
<mapping name="recoveryPassword">
|
||||||
|
<select path="recoveryPassword"/>
|
||||||
|
</mapping>
|
||||||
|
<mapping name="domainPassword">
|
||||||
|
<select path="::adminPassword"/>
|
||||||
|
</mapping>
|
||||||
|
</map>
|
||||||
|
</parameter>
|
||||||
|
<success marker="1">
|
||||||
|
<set path="state.installed"><true/></set>
|
||||||
|
<report entity="unit">
|
||||||
|
<parameter name="id"><select path="id"/></parameter>
|
||||||
|
<parameter name="text">Primary Domain Controller created</parameter>
|
||||||
|
</report>
|
||||||
|
<report entity="service">
|
||||||
|
<parameter name="id"><select path="::id"/></parameter>
|
||||||
|
<parameter name="text">Primary Domain Controller created</parameter>
|
||||||
|
</report>
|
||||||
|
</success>
|
||||||
|
</send-command>
|
||||||
|
</rule>
|
||||||
|
</workflow>
|
60
conductor/data/workflows/IIS.xml
Normal file
60
conductor/data/workflows/IIS.xml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<workflow>
|
||||||
|
<rule match="$.services.webServers[?(@.domain)].units[*]">
|
||||||
|
<set path="domain">
|
||||||
|
<select path="::domain"/>
|
||||||
|
</set>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<rule match="$.services.webServers[*].units[?(@.state.instanceName is None)]">
|
||||||
|
<report entity="unit">
|
||||||
|
<parameter name="id"><select path="id"/></parameter>
|
||||||
|
<parameter name="text">Creating instance <select path="name"/></parameter>
|
||||||
|
</report>
|
||||||
|
<update-cf-stack template="Windows">
|
||||||
|
<parameter name="mappings">
|
||||||
|
<map>
|
||||||
|
<mapping name="instanceName">
|
||||||
|
<select path="name"/>
|
||||||
|
</mapping>
|
||||||
|
<mapping name="userData">
|
||||||
|
<prepare_user_data/>
|
||||||
|
</mapping>
|
||||||
|
</map>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="arguments">
|
||||||
|
<map>
|
||||||
|
<argument name="KeyName">keero-linux-keys</argument>
|
||||||
|
<argument name="InstanceType">m1.medium</argument>
|
||||||
|
<argument name="ImageName">ws-2012-full-agent</argument>
|
||||||
|
</map>
|
||||||
|
</parameter>
|
||||||
|
|
||||||
|
<success>
|
||||||
|
<set path="state.instanceName"><select path="name"/></set>
|
||||||
|
<report entity="unit">
|
||||||
|
<parameter name="id"><select path="id"/></parameter>
|
||||||
|
<parameter name="text">Instance <select path="name"/> created</parameter>
|
||||||
|
</report>
|
||||||
|
</success>
|
||||||
|
</update-cf-stack>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<rule match="$.services.webServers[*].units[?(@.state.instanceName and not @.state.iisInstalled)]">
|
||||||
|
<report entity="unit">
|
||||||
|
<parameter name="id"><select path="id"/></parameter>
|
||||||
|
<parameter name="text">Creating IIS Web Server on unit <select path="name"/></parameter>
|
||||||
|
</report>
|
||||||
|
<send-command template="InstallIIS">
|
||||||
|
<parameter name="host">
|
||||||
|
<select path="name"/>
|
||||||
|
</parameter>
|
||||||
|
<success>
|
||||||
|
<set path="state.iisInstalled"><true/></set>
|
||||||
|
<report entity="unit">
|
||||||
|
<parameter name="id"><select path="id"/></parameter>
|
||||||
|
<parameter name="text">IIS <select path="name"/> has started</parameter>
|
||||||
|
</report>
|
||||||
|
</success>
|
||||||
|
</send-command>
|
||||||
|
</rule>
|
||||||
|
</workflow>
|
@ -1,69 +0,0 @@
|
|||||||
<workflow>
|
|
||||||
<rule match="$.services.activeDirectory[*].units[?(@.state.instanceName is None)]">
|
|
||||||
<update-cf-stack template="Windows">
|
|
||||||
<parameter name="mappings">
|
|
||||||
<map>
|
|
||||||
<mapping name="instanceName">
|
|
||||||
<select path="name"/>
|
|
||||||
</mapping>
|
|
||||||
</map>
|
|
||||||
</parameter>
|
|
||||||
<parameter name="arguments">
|
|
||||||
<map>
|
|
||||||
<argument name="KeyName">keero-linux-keys</argument>
|
|
||||||
<argument name="InstanceType">m1.medium</argument>
|
|
||||||
<argument name="ImageName">ws-2012-full-agent</argument>
|
|
||||||
</map>
|
|
||||||
</parameter>
|
|
||||||
|
|
||||||
<success>
|
|
||||||
<set path="state.instanceName"><select path="name"/></set>
|
|
||||||
</success>
|
|
||||||
</update-cf-stack>
|
|
||||||
</rule>
|
|
||||||
<rule match="$.services.activeDirectory[*].units[?(@.state.instanceName is not None and @.adminPassword is not None)]">
|
|
||||||
<send-command template="SetPassword">
|
|
||||||
<parameter name="host">
|
|
||||||
<select path="name"/>
|
|
||||||
</parameter>
|
|
||||||
<parameter name="mappings">
|
|
||||||
<map>
|
|
||||||
<mapping name="adm_password">
|
|
||||||
<select path="adminPassword"/>
|
|
||||||
</mapping>
|
|
||||||
</map>
|
|
||||||
</parameter>
|
|
||||||
|
|
||||||
<success>
|
|
||||||
<set path="adminPassword"><null/></set>
|
|
||||||
</success>
|
|
||||||
|
|
||||||
</send-command>
|
|
||||||
</rule>
|
|
||||||
<rule match="$.services.activeDirectory[*].units[?(@.state.instanceName is not None and not @.state.isInRole and @.isMaster)]">
|
|
||||||
<send-command template="CreatePrimaryDC">
|
|
||||||
<parameter name="host">
|
|
||||||
<select path="name"/>
|
|
||||||
</parameter>
|
|
||||||
<parameter name="mappings">
|
|
||||||
<map>
|
|
||||||
<mapping name="dc_name">
|
|
||||||
<select path="::domain"/>
|
|
||||||
</mapping>
|
|
||||||
<mapping name="recovery_password">
|
|
||||||
<select path="recoveryPassword"/>
|
|
||||||
</mapping>
|
|
||||||
</map>
|
|
||||||
</parameter>
|
|
||||||
<success>
|
|
||||||
<set path="recoveryPassword"><null/></set>
|
|
||||||
<set path="state.isInRole"><true/></set>
|
|
||||||
<set path="::state.WeHavePrimaryDC"><true/></set>
|
|
||||||
</success>
|
|
||||||
|
|
||||||
</send-command>
|
|
||||||
</rule>
|
|
||||||
<rule match="$.services.activeDirectory[?(@state.WeHavePrimaryDC == True)].units[?(@.state.instanceName is not None and not @.state.isInRole and @.isMaster = False)]">
|
|
||||||
|
|
||||||
</rule>
|
|
||||||
</workflow>
|
|
@ -1,19 +1,37 @@
|
|||||||
{
|
{
|
||||||
"name": "MyDataCenter",
|
"name": "MyDataCenter",
|
||||||
|
"id": "adc6d143f9584d10808c7ef4d07e4802",
|
||||||
"services": {
|
"services": {
|
||||||
"activeDirectory": [
|
"activeDirectories": [
|
||||||
{
|
{
|
||||||
"id": "1234567890",
|
"id": "9571747991184642B95F430A014616F9",
|
||||||
"domain": "acme.loc",
|
"domain": "acme.loc",
|
||||||
|
"adminPassword": "SuperP@ssw0rd!",
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
|
"id": "273c9183b6e74c9c9db7fdd532c5eb25",
|
||||||
"name": "dc01",
|
"name": "dc01",
|
||||||
"isMaster": true,
|
"isMaster": true,
|
||||||
"recoveryPassword": "2SuperP@ssw0rd2"
|
"recoveryPassword": "2SuperP@ssw0rd2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "377c6f16d17a416791f80724dab360c6",
|
||||||
"name": "dc02",
|
"name": "dc02",
|
||||||
"isMaster": false,
|
"isMaster": false,
|
||||||
|
"adminPassword": "SuperP@ssw0rd",
|
||||||
|
"recoveryPassword": "2SuperP@ssw0rd2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"webServers": [
|
||||||
|
{
|
||||||
|
"id": "e9657ceef84a4e669e31795040080262",
|
||||||
|
"domain": "acme.loc",
|
||||||
|
"units": [
|
||||||
|
{
|
||||||
|
"id": "e6f9cfd07ced48fba64e6bd9e65aba64",
|
||||||
|
"name": "iis01",
|
||||||
"adminPassword": "SuperP@ssw0rd"
|
"adminPassword": "SuperP@ssw0rd"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user