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
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsAgent", "WindowsAgent\WindowsAgent.csproj", "{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExecutionPlanGenerator", "ExecutionPlanGenerator\ExecutionPlanGenerator.csproj", "{501BE151-4B8C-4355-88DC-3AEF1921B2D7}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<configuration>
|
||||
<configSections>
|
||||
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
|
||||
@ -11,20 +11,23 @@
|
||||
<targets>
|
||||
<target name="file" xsi:type="File" fileName="${basedir}/log.txt"
|
||||
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>
|
||||
|
||||
<rules>
|
||||
<logger name="*" minlevel="Debug" writeTo="file" />
|
||||
<logger name="*" minlevel="Debug" writeTo="console" />
|
||||
</rules>
|
||||
</nlog>
|
||||
<appSettings>
|
||||
<add key="rabbitmq.host" value="localhost"/>
|
||||
<add key="rabbitmq.user" value="guest"/>
|
||||
<add key="rabbitmq.password" value="guest"/>
|
||||
<add key="rabbitmq.vhost" value="/"/>
|
||||
<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.resultQueue" value="-execution-results"/>
|
||||
<add key="rabbitmq.resultRoutingKey" value="-execution-results"/>
|
||||
<add key="rabbitmq.durableMessages" value="trueтест"/>
|
||||
|
||||
</appSettings>
|
||||
</configuration>
|
@ -14,8 +14,13 @@ namespace Mirantis.Keero.WindowsAgent
|
||||
{
|
||||
this.ackFunc = ackFunc;
|
||||
}
|
||||
|
||||
public MqMessage()
|
||||
{
|
||||
}
|
||||
|
||||
public string Body { get; set; }
|
||||
public string Id { get; set; }
|
||||
|
||||
public void Ack()
|
||||
{
|
||||
|
@ -5,12 +5,15 @@ using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using System.Management.Automation.Runspaces;
|
||||
using System.Text;
|
||||
using NLog;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Mirantis.Keero.WindowsAgent
|
||||
{
|
||||
class PlanExecutor
|
||||
{
|
||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
class ExecutionResult
|
||||
{
|
||||
public bool IsException { get; set; }
|
||||
@ -26,13 +29,14 @@ namespace Mirantis.Keero.WindowsAgent
|
||||
|
||||
public bool RebootNeeded { get; set; }
|
||||
|
||||
public string Execute()
|
||||
public void Execute()
|
||||
{
|
||||
RebootNeeded = false;
|
||||
var resultPath = this.path + ".result";
|
||||
Runspace runSpace = null;
|
||||
try
|
||||
{
|
||||
var plan = JsonConvert.DeserializeObject<ExecutionPlan>(File.ReadAllText(this.path));
|
||||
var resultPath = this.path + ".result";
|
||||
List<ExecutionResult> currentResults = null;
|
||||
try
|
||||
{
|
||||
@ -44,7 +48,7 @@ namespace Mirantis.Keero.WindowsAgent
|
||||
}
|
||||
|
||||
|
||||
var runSpace = RunspaceFactory.CreateRunspace();
|
||||
runSpace = RunspaceFactory.CreateRunspace();
|
||||
runSpace.Open();
|
||||
|
||||
var runSpaceInvoker = new RunspaceInvoke(runSpace);
|
||||
@ -70,6 +74,11 @@ namespace Mirantis.Keero.WindowsAgent
|
||||
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);
|
||||
try
|
||||
{
|
||||
@ -90,6 +99,7 @@ namespace Mirantis.Keero.WindowsAgent
|
||||
exception.GetType().FullName, exception.Message
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -115,16 +125,27 @@ namespace Mirantis.Keero.WindowsAgent
|
||||
RebootNeeded = true;
|
||||
}
|
||||
}
|
||||
File.WriteAllText(resultPath, executionResult);
|
||||
|
||||
File.Delete(resultPath);
|
||||
return executionResult;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return JsonConvert.SerializeObject(new ExecutionResult {
|
||||
File.WriteAllText(resultPath, JsonConvert.SerializeObject(new ExecutionResult {
|
||||
IsException = true,
|
||||
Result = ex.Message
|
||||
}, Formatting.Indented);
|
||||
}, Formatting.Indented));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (runSpace != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
runSpace.Close();
|
||||
}
|
||||
catch
|
||||
{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,11 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using NLog;
|
||||
|
||||
@ -14,6 +18,8 @@ namespace Mirantis.Keero.WindowsAgent
|
||||
private volatile bool stop;
|
||||
private Thread thread;
|
||||
private RabbitMqClient rabbitMqClient;
|
||||
private int delayFactor = 1;
|
||||
private string plansDir;
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
@ -23,34 +29,75 @@ namespace Mirantis.Keero.WindowsAgent
|
||||
protected override void OnStart(string[] args)
|
||||
{
|
||||
base.OnStart(args);
|
||||
|
||||
Log.Info("Version 0.3");
|
||||
|
||||
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.Start();
|
||||
}
|
||||
|
||||
void Loop()
|
||||
{
|
||||
var doReboot = false;
|
||||
const string filePath = "data.json";
|
||||
const string unknownName = "unknown";
|
||||
while (!stop)
|
||||
{
|
||||
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();
|
||||
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();
|
||||
}
|
||||
var executor = new PlanExecutor(filePath);
|
||||
var result = executor.Execute();
|
||||
if(stop) break;
|
||||
rabbitMqClient.SendResult(result);
|
||||
File.Delete(filePath);
|
||||
else
|
||||
{
|
||||
var id = Path.GetFileNameWithoutExtension(path);
|
||||
Log.Info("Executing exising plan {0}", id);
|
||||
}
|
||||
var executor = new PlanExecutor(path);
|
||||
executor.Execute();
|
||||
File.Delete(path);
|
||||
delayFactor = 1;
|
||||
|
||||
if (stop) break;
|
||||
if (executor.RebootNeeded)
|
||||
{
|
||||
doReboot = true;
|
||||
break;
|
||||
Reboot();
|
||||
}
|
||||
}
|
||||
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");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.ErrorException("Cannot execute shutdown.exe", ex);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
Log.Info("Done waiting for reboot");
|
||||
}
|
||||
|
||||
}
|
||||
@ -78,18 +149,18 @@ namespace Mirantis.Keero.WindowsAgent
|
||||
if (stop) return;
|
||||
Log.WarnException("Exception in main loop", exception);
|
||||
var i = 0;
|
||||
while (!stop && i < 10)
|
||||
while (!stop && i < 10 * (delayFactor * delayFactor))
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
i++;
|
||||
}
|
||||
delayFactor = Math.Min(delayFactor + 1, 6);
|
||||
}
|
||||
|
||||
protected override void OnStop()
|
||||
{
|
||||
stop = true;
|
||||
this.rabbitMqClient.Dispose();
|
||||
Console.WriteLine("Stop");
|
||||
base.OnStop();
|
||||
}
|
||||
|
||||
|
@ -5,12 +5,14 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using RabbitMQ.Client;
|
||||
|
||||
namespace Mirantis.Keero.WindowsAgent
|
||||
{
|
||||
class RabbitMqClient : IDisposable
|
||||
{
|
||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||
private static readonly ConnectionFactory connectionFactory;
|
||||
private IConnection currentConnecton;
|
||||
|
||||
@ -43,21 +45,22 @@ namespace Mirantis.Keero.WindowsAgent
|
||||
}
|
||||
var session = connection.CreateModel();
|
||||
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 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 {
|
||||
session.BasicAck(e.DeliveryTag, false);
|
||||
session.BasicCancel(consumeTag);
|
||||
session.Close();
|
||||
};
|
||||
|
||||
|
||||
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();
|
||||
@ -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 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
|
||||
{
|
||||
@ -78,18 +82,19 @@ namespace Mirantis.Keero.WindowsAgent
|
||||
connection = this.currentConnecton = this.currentConnecton ?? connectionFactory.CreateConnection();
|
||||
}
|
||||
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))
|
||||
{
|
||||
session.ExchangeBind(exchangeName, resultQueue, resultQueue);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
var basicProperties = session.CreateBasicProperties();
|
||||
basicProperties.SetPersistent(true);
|
||||
basicProperties.SetPersistent(durable);
|
||||
basicProperties.MessageId = message.Id;
|
||||
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();
|
||||
}
|
||||
catch (Exception)
|
||||
|
@ -21,6 +21,7 @@
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
|
@ -1,17 +1,17 @@
|
||||
import datetime
|
||||
import glob
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
|
||||
import tornado.ioloop
|
||||
|
||||
|
||||
import rabbitmq
|
||||
from workflow import Workflow
|
||||
import cloud_formation
|
||||
import windows_agent
|
||||
from commands.dispatcher import CommandDispatcher
|
||||
from config import Config
|
||||
import reporting
|
||||
|
||||
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'),
|
||||
host=config.get_setting('rabbitmq', 'host', 'localhost'))
|
||||
|
||||
|
||||
def schedule(callback, *args, **kwargs):
|
||||
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)
|
||||
workflows = []
|
||||
for path in glob.glob("data/workflows/*.xml"):
|
||||
print "loading", path
|
||||
workflow = Workflow(path, task, command_dispatcher, config)
|
||||
workflow = Workflow(path, task, command_dispatcher, config, reporter)
|
||||
workflows.append(workflow)
|
||||
|
||||
def loop(callback):
|
||||
@ -41,14 +46,14 @@ def task_received(task):
|
||||
|
||||
def shutdown():
|
||||
command_dispatcher.close()
|
||||
rmqclient.send('task-results', json.dumps(task))
|
||||
print "Done!!!!!!!!!!"
|
||||
rmqclient.send('task-results', json.dumps(task), message_id=message_id)
|
||||
print 'Finished at', datetime.datetime.now()
|
||||
|
||||
loop(shutdown)
|
||||
|
||||
|
||||
def message_received(body):
|
||||
task_received(json.loads(body))
|
||||
def message_received(body, message_id, **kwargs):
|
||||
task_received(json.loads(body), message_id)
|
||||
|
||||
|
||||
def start():
|
||||
|
@ -1,10 +1,39 @@
|
||||
import base64
|
||||
|
||||
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']
|
||||
print "update-cf", template
|
||||
|
||||
callback = lambda result: engine.evaluate_content(body.find('success'), context)
|
||||
command_dispatcher.execute(name='cf', template=template, mappings=mappings, arguments=arguments, callback=callback)
|
||||
callback = lambda result: engine.evaluate_content(
|
||||
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 subprocess import call
|
||||
|
||||
|
||||
class HeatExecutor(CommandBase):
|
||||
def __init__(self, stack):
|
||||
self._pending_list = []
|
||||
@ -15,7 +16,8 @@ class HeatExecutor(CommandBase):
|
||||
with open('data/templates/cf/%s.template' % template) as template_file:
|
||||
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({
|
||||
'template': template_data,
|
||||
@ -27,32 +29,37 @@ class HeatExecutor(CommandBase):
|
||||
return len(self._pending_list) > 0
|
||||
|
||||
def execute_pending(self, callback):
|
||||
if not self._pending_list:
|
||||
if not self.has_pending_commands():
|
||||
return False
|
||||
|
||||
template = {}
|
||||
arguments = {}
|
||||
for t in self._pending_list:
|
||||
template = conductor.helpers.merge_dicts(template, t['template'], max_levels=2)
|
||||
arguments = conductor.helpers.merge_dicts(arguments, t['arguments'], max_levels=1)
|
||||
template = conductor.helpers.merge_dicts(
|
||||
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"):
|
||||
os.mkdir("tmp")
|
||||
file_name = "tmp/"+str(uuid.uuid4())
|
||||
file_name = "tmp/" + str(uuid.uuid4())
|
||||
print "Saving template to", file_name
|
||||
with open(file_name, "w") as f:
|
||||
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([
|
||||
"./heat_run","stack-create",
|
||||
"./heat_run", "stack-create",
|
||||
"-f" + file_name,
|
||||
"-P" + arguments_str,
|
||||
self._stack
|
||||
])
|
||||
|
||||
|
||||
callbacks = []
|
||||
for t in self._pending_list:
|
||||
if t['callback']:
|
||||
@ -66,10 +73,3 @@ class HeatExecutor(CommandBase):
|
||||
callback()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
class CommandBase(object):
|
||||
def execute(self, **kwargs):
|
||||
pass
|
||||
|
@ -2,11 +2,13 @@ import command
|
||||
import cloud_formation
|
||||
import windows_agent
|
||||
|
||||
|
||||
class CommandDispatcher(command.CommandBase):
|
||||
def __init__(self, environment_name, rmqclient):
|
||||
self._command_map = {
|
||||
'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):
|
||||
@ -28,7 +30,6 @@ class CommandDispatcher(command.CommandBase):
|
||||
count[0] -= 1
|
||||
result -= 1
|
||||
|
||||
|
||||
return result > 0
|
||||
|
||||
|
||||
|
@ -1,34 +1,42 @@
|
||||
import json
|
||||
import uuid
|
||||
|
||||
import conductor.helpers
|
||||
from command import CommandBase
|
||||
|
||||
|
||||
class WindowsAgentExecutor(CommandBase):
|
||||
def __init__(self, stack, rmqclient):
|
||||
self._pending_list = []
|
||||
self._stack = stack
|
||||
self._rmqclient = rmqclient
|
||||
self._callback = None
|
||||
self._pending_list = []
|
||||
self._current_pending_list = []
|
||||
rmqclient.subscribe('-execution-results', self._on_message)
|
||||
print "--------------------"
|
||||
|
||||
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 = 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({
|
||||
'id': str(uuid.uuid4()).lower(),
|
||||
'template': template_data,
|
||||
'host': ('%s-%s' % (self._stack, host)).lower().replace(' ', '-'),
|
||||
'callback': callback
|
||||
})
|
||||
|
||||
def _on_message(self, body):
|
||||
if self._pending_list:
|
||||
item = self._pending_list.pop()
|
||||
def _on_message(self, body, message_id, **kwargs):
|
||||
msg_id = message_id.lower()
|
||||
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))
|
||||
if self._callback and not self._pending_list:
|
||||
if self._callback and not self._current_pending_list:
|
||||
cb = self._callback
|
||||
self._callback = None
|
||||
cb()
|
||||
@ -37,19 +45,19 @@ class WindowsAgentExecutor(CommandBase):
|
||||
return len(self._pending_list) > 0
|
||||
|
||||
def execute_pending(self, callback):
|
||||
if not self._pending_list:
|
||||
if not self.has_pending_commands():
|
||||
return False
|
||||
|
||||
self._current_pending_list = self._pending_list
|
||||
self._pending_list = []
|
||||
|
||||
self._callback = callback
|
||||
|
||||
for t in self._pending_list:
|
||||
self._rmqclient.send(queue=t['host'], data=t['template'])
|
||||
print 'Sending RMQ message %s to %s' % (t['template'], t['host'])
|
||||
|
||||
callbacks = []
|
||||
for t in self._pending_list:
|
||||
if t['callback']:
|
||||
callbacks.append(t['callback'])
|
||||
for rec in self._current_pending_list:
|
||||
self._rmqclient.send(
|
||||
queue=rec['host'], data=rec['template'], message_id=rec['id'])
|
||||
print 'Sending RMQ message %s to %s' % (
|
||||
rec['template'], rec['host'])
|
||||
|
||||
return True
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
from ConfigParser import SafeConfigParser
|
||||
|
||||
|
||||
class Config(object):
|
||||
CONFIG_PATH = './etc/app.config'
|
||||
|
||||
@ -14,4 +15,5 @@ class Config(object):
|
||||
|
||||
def __getitem__(self, item):
|
||||
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):
|
||||
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
|
||||
|
||||
def __getitem__(self, item):
|
||||
|
@ -1,16 +1,15 @@
|
||||
import types
|
||||
|
||||
|
||||
def transform_json(json, mappings):
|
||||
if isinstance(json, types.ListType):
|
||||
result=[]
|
||||
for t in json:
|
||||
result.append(transform_json(t, mappings))
|
||||
return result
|
||||
return [transform_json(t, mappings) for t in json]
|
||||
|
||||
if isinstance(json, types.DictionaryType):
|
||||
result = {}
|
||||
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
|
||||
|
||||
if isinstance(json, types.StringTypes) and json.startswith('$'):
|
||||
@ -20,17 +19,30 @@ def transform_json(json, mappings):
|
||||
|
||||
return json
|
||||
|
||||
|
||||
def merge_dicts(dict1, dict2, max_levels=0):
|
||||
result = {}
|
||||
for key, value in dict1.items():
|
||||
result[key] = value
|
||||
if key in dict2:
|
||||
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
|
||||
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():
|
||||
if key not in result:
|
||||
result[key] = value
|
||||
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:
|
||||
import tornado.ioloop
|
||||
|
||||
IOLoop = tornado.ioloop.IOLoop
|
||||
except ImportError:
|
||||
IOLoop = None
|
||||
|
||||
|
||||
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)
|
||||
self._connection_parameters = pika.ConnectionParameters(
|
||||
credentials = credentials, host = host, virtual_host = virtual_host)
|
||||
credentials=credentials, host=host, virtual_host=virtual_host)
|
||||
self._subscriptions = {}
|
||||
|
||||
def _create_connection(self):
|
||||
self.connection = TornadoConnection(
|
||||
parameters = self._connection_parameters,
|
||||
on_open_callback = self._on_connected)
|
||||
parameters=self._connection_parameters,
|
||||
on_open_callback=self._on_connected)
|
||||
|
||||
def _on_connected(self, connection):
|
||||
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 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):
|
||||
ctag = str(uuid.uuid4())
|
||||
self._subscriptions[queue] = ctag
|
||||
|
||||
self._channel.queue_declare(queue=queue, durable=True,
|
||||
callback= lambda frame, ctag=ctag : self._on_queue_declared(frame, queue, callback, ctag))
|
||||
self._channel.queue_declare(
|
||||
queue=queue, durable=True,
|
||||
callback=lambda frame, ctag=ctag: self._on_queue_declared(
|
||||
frame, queue, callback, ctag))
|
||||
|
||||
def unsubscribe(self, queue):
|
||||
self._channel.basic_cancel(consumer_tag=self._subscriptions[queue])
|
||||
del self._subscriptions[queue]
|
||||
|
||||
|
||||
def start(self, callback=None):
|
||||
if IOLoop is None: raise ImportError("Tornado not installed")
|
||||
self._started_callback = callback
|
||||
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=""):
|
||||
self._channel.queue_declare(queue=queue, durable=True,
|
||||
callback = lambda frame: self._channel.basic_publish(exchange=exchange, routing_key=queue, body=data))
|
||||
def send(self, queue, data, exchange="", message_id=""):
|
||||
properties = pika.BasicProperties(message_id=message_id)
|
||||
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
|
||||
|
||||
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']
|
||||
|
||||
def callback(result):
|
||||
print "Received ", result
|
||||
engine.evaluate_content(body.find('success'), context)
|
||||
def callback(result_value):
|
||||
print "Received result for %s: %s. Body is %s" % (template, result_value, body)
|
||||
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")
|
@ -6,13 +6,14 @@ import xml_code_engine
|
||||
import function_context
|
||||
|
||||
class Workflow(object):
|
||||
def __init__(self, filename, data, command_dispatcher, config):
|
||||
def __init__(self, filename, data, command_dispatcher, config, reporter):
|
||||
self._data = data
|
||||
self._engine = xml_code_engine.XmlCodeEngine()
|
||||
with open(filename) as xml:
|
||||
self._engine.load(xml)
|
||||
self._command_dispatcher = command_dispatcher
|
||||
self._config = config
|
||||
self._reporter = reporter
|
||||
|
||||
def execute(self):
|
||||
while True:
|
||||
@ -20,11 +21,16 @@ class Workflow(object):
|
||||
context['/dataSource'] = self._data
|
||||
context['/commandDispatcher'] = self._command_dispatcher
|
||||
context['/config'] = self._config
|
||||
context['/reporter'] = self._reporter
|
||||
if not self._engine.execute(context):
|
||||
break
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
for part in path:
|
||||
if isinstance(current, types.ListType):
|
||||
@ -52,65 +58,87 @@ class Workflow(object):
|
||||
raise ValueError()
|
||||
|
||||
@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('##'):
|
||||
config = context['/config']
|
||||
return config[path[2:]]
|
||||
elif path.startswith('#'):
|
||||
return context[path[1:]]
|
||||
|
||||
position = context['dataSource_currentPosition'] or []
|
||||
data = context['dataSource']
|
||||
|
||||
index = 0
|
||||
for c in path:
|
||||
if c == ':':
|
||||
if len(position) > 0:
|
||||
position = position[:-1]
|
||||
elif c == '/':
|
||||
position = []
|
||||
else:
|
||||
break
|
||||
|
||||
index += 1
|
||||
|
||||
return Workflow._get_path(data, position + path[index:].split('.'))
|
||||
if source is not None:
|
||||
return Workflow._get_path(
|
||||
context[source], path.split('.'))
|
||||
else:
|
||||
return Workflow._get_path(
|
||||
context['/dataSource'],
|
||||
Workflow._correct_position(path, context))
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
if path[0] == '#':
|
||||
context[path[1:]] = body_data
|
||||
return
|
||||
|
||||
position = context['dataSource_currentPosition'] or []
|
||||
data = context['dataSource']
|
||||
if path.startswith('##'):
|
||||
raise RuntimeError('Cannot modify config from XML-code')
|
||||
elif path.startswith('#'):
|
||||
context[':' + path[1:]] = body_data
|
||||
return
|
||||
|
||||
index = 0
|
||||
for c in path:
|
||||
if c == ':':
|
||||
if len(position) > 0:
|
||||
position = position[:-1]
|
||||
elif c == '/':
|
||||
position = []
|
||||
else:
|
||||
break
|
||||
|
||||
index += 1
|
||||
|
||||
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
|
||||
if target:
|
||||
data = context[target]
|
||||
position = path.split('.')
|
||||
if Workflow._get_path(data, position) != body_data:
|
||||
Workflow._set_path(data, position, body_data)
|
||||
context['/hasSideEffects'] = True
|
||||
else:
|
||||
data = context['/dataSource']
|
||||
new_position = Workflow._correct_position(path, context)
|
||||
if Workflow._get_path(data, new_position) != body_data:
|
||||
Workflow._set_path(data, new_position, body_data)
|
||||
context['/hasSideEffects'] = True
|
||||
|
||||
@staticmethod
|
||||
def _rule_func(match, context, body, engine, limit = 0, **kwargs):
|
||||
position = context['dataSource_currentPosition'] or []
|
||||
data = context['dataSource_currentObj']
|
||||
if data is None:
|
||||
data = context['dataSource']
|
||||
match = re.sub(r'@\.([\w.]+)', r"Workflow._get_path(@, '\1'.split('.'))", match)
|
||||
def _rule_func(match, context, body, engine, limit=0, name=None, **kwargs):
|
||||
position = context['__dataSource_currentPosition'] or []
|
||||
|
||||
if name == 'marker':
|
||||
print "!"
|
||||
# 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 []
|
||||
|
||||
index = 0
|
||||
@ -119,8 +147,9 @@ class Workflow(object):
|
||||
break
|
||||
index += 1
|
||||
new_position = position + found_match
|
||||
context['dataSource_currentPosition'] = new_position
|
||||
context['dataSource_currentObj'] = Workflow._get_path(context['dataSource'], new_position)
|
||||
context['__dataSource_currentPosition'] = new_position
|
||||
context['__dataSource_currentObj'] = Workflow._get_path(
|
||||
context['/dataSource'], new_position)
|
||||
for element in body:
|
||||
engine.evaluate(element, context)
|
||||
if element.tag == 'rule' and context['/hasSideEffects']:
|
||||
@ -136,7 +165,14 @@ class Workflow(object):
|
||||
return False
|
||||
|
||||
|
||||
xml_code_engine.XmlCodeEngine.register_function(Workflow._rule_func, 'rule')
|
||||
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')
|
||||
xml_code_engine.XmlCodeEngine.register_function(
|
||||
Workflow._rule_func, 'rule')
|
||||
|
||||
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
|
||||
import xml.etree.ElementTree as etree
|
||||
import types
|
||||
|
||||
import function_context
|
||||
|
||||
|
||||
class XmlCodeEngine(object):
|
||||
_functionMap = {}
|
||||
|
||||
@ -24,13 +27,14 @@ class XmlCodeEngine(object):
|
||||
|
||||
definition = self._functionMap[name]
|
||||
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():
|
||||
args[key] = value
|
||||
|
||||
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)
|
||||
|
||||
@ -44,21 +48,22 @@ class XmlCodeEngine(object):
|
||||
if sub_element.tag == 'parameter':
|
||||
continue
|
||||
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 '')
|
||||
|
||||
result = []
|
||||
|
||||
for t in parts:
|
||||
if not isinstance(t, (str, unicode)):
|
||||
if not isinstance(t, types.StringTypes):
|
||||
result.append(t)
|
||||
|
||||
return_value = result
|
||||
if len(result) == 0:
|
||||
return_value = ''.join(parts)
|
||||
if do_strip: return_value = return_value.strip()
|
||||
elif len(result) == 1:
|
||||
return_value = result[0]
|
||||
else:
|
||||
return_value = result
|
||||
|
||||
return return_value
|
||||
|
||||
@ -75,27 +80,34 @@ def _dict_func(engine, body, context, **kwargs):
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
|
||||
def _array_func(engine, body, context, **kwargs):
|
||||
result = []
|
||||
for item in body:
|
||||
result.append(engine.evaluate(item, context))
|
||||
return result
|
||||
|
||||
|
||||
def _text_func(engine, body, context, **kwargs):
|
||||
return str(engine.evaluate_content(body, context))
|
||||
|
||||
|
||||
def _int_func(engine, body, context, **kwargs):
|
||||
return int(engine.evaluate_content(body, context))
|
||||
|
||||
|
||||
def _function_func(engine, body, context, **kwargs):
|
||||
return lambda: engine.evaluate_content(body, context)
|
||||
|
||||
|
||||
def _null_func(**kwargs):
|
||||
return None
|
||||
|
||||
|
||||
def _true_func(**kwargs):
|
||||
return True
|
||||
|
||||
|
||||
def _false_func(**kwargs):
|
||||
return False
|
||||
|
||||
@ -115,27 +127,8 @@ def xprint(context, body, **kwargs):
|
||||
for arg in kwargs:
|
||||
print "%s = %s" % (arg, kwargs[arg])
|
||||
print 'context = ', context
|
||||
print 'body = %s (%s)' %(body, body.text)
|
||||
print 'body = %s (%s)' % (body, body.text)
|
||||
print "------------------------- end -------------------------"
|
||||
|
||||
|
||||
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",
|
||||
"Arguments": {
|
||||
"DomainName": "$dc_name",
|
||||
"SafeModePassword": "$recovery_password"
|
||||
"DomainName": "$domain",
|
||||
"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",
|
||||
"Arguments": {
|
||||
"UserName": "Administrator",
|
||||
"Password": "$adm_password",
|
||||
"Password": "$adminPassword",
|
||||
"Force": true
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,8 @@
|
||||
"Properties": {
|
||||
"InstanceType" : { "Ref" : "InstanceType" },
|
||||
"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",
|
||||
"id": "adc6d143f9584d10808c7ef4d07e4802",
|
||||
"services": {
|
||||
"activeDirectory": [
|
||||
"activeDirectories": [
|
||||
{
|
||||
"id": "1234567890",
|
||||
"id": "9571747991184642B95F430A014616F9",
|
||||
"domain": "acme.loc",
|
||||
"adminPassword": "SuperP@ssw0rd!",
|
||||
"units": [
|
||||
{
|
||||
"id": "273c9183b6e74c9c9db7fdd532c5eb25",
|
||||
"name": "dc01",
|
||||
"isMaster": true,
|
||||
"recoveryPassword": "2SuperP@ssw0rd2"
|
||||
},
|
||||
{
|
||||
"id": "377c6f16d17a416791f80724dab360c6",
|
||||
"name": "dc02",
|
||||
"isMaster": false,
|
||||
"adminPassword": "SuperP@ssw0rd",
|
||||
"recoveryPassword": "2SuperP@ssw0rd2"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"webServers": [
|
||||
{
|
||||
"id": "e9657ceef84a4e669e31795040080262",
|
||||
"domain": "acme.loc",
|
||||
"units": [
|
||||
{
|
||||
"id": "e6f9cfd07ced48fba64e6bd9e65aba64",
|
||||
"name": "iis01",
|
||||
"adminPassword": "SuperP@ssw0rd"
|
||||
}
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user