Workflows, ExecutionPlanGenerator, Reporting, UserData, conductor improvements

This commit is contained in:
Stan Lagun 2013-03-11 19:11:27 +04:00
parent 9d0b7e39fe
commit 28bc7ff1d5
34 changed files with 852 additions and 276 deletions

View File

@ -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

View File

@ -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}: &lt;${logger:shortName=true}&gt; ${message} ${exception:format=tostring}"/>
<target name="console" xsi:type="Console"
layout="${date} ${level}: &lt;${logger:shortName=true}&gt; ${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>

View File

@ -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()
{

View File

@ -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
{}
}
}
}

View File

@ -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();
}

View File

@ -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)

View File

@ -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>

View File

@ -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():

View File

@ -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")

View File

@ -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

View File

@ -1,4 +1,3 @@
class CommandBase(object):
def execute(self, **kwargs):
pass

View File

@ -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

View File

@ -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

View File

@ -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])

View File

@ -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):

View File

@ -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

View File

@ -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))

View 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")

View File

@ -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")

View File

@ -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')

View File

@ -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
View 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!'

View 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}: &lt;${logger:shortName=true}&gt; ${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>

View File

@ -0,0 +1,12 @@
{
"Scripts": [
"ZnVuY3Rpb24gR2V0LURuc0xpc3RlbmluZ0lwQWRkcmVzc2VzIHsNCiAgICAoR2V0LUROU1NlcnZlciAtQ29tcHV0ZXJOYW1lIGxvY2FsaG9zdCkuU2VydmVyU2V0dGluZy5MaXN0ZW5pbmdJcEFkZHJlc3MNCn0NCg=="
],
"Commands": [
{
"Name": "Get-DnsListeningIpAddress",
"Arguments": {}
}
],
"RebootOnCompletion": 0
}

View File

@ -12,8 +12,8 @@
{
"Name": "Install-RolePrimaryDomainController",
"Arguments": {
"DomainName": "$dc_name",
"SafeModePassword": "$recovery_password"
"DomainName": "$domain",
"SafeModePassword": "$recoveryPassword"
}
}
],

View 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
}

View File

@ -0,0 +1,12 @@
{
"Scripts": [
"ZnVuY3Rpb24gSW5zdGFsbC1XZWJTZXJ2ZXIgew0KICAgIEltcG9ydC1Nb2R1bGUgU2VydmVyTWFuYWdlcg0KICAgIEluc3RhbGwtV2luZG93c0ZlYXR1cmUgV2ViLVNlcnZlciAtSW5jbHVkZU1hbmFnZW1lbnRUb29scw0KfQ0K"
],
"Commands": [
{
"Name": "Install-WebServer",
"Arguments": {}
}
],
"RebootOnCompletion": 0
}

View 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
}

View File

@ -13,7 +13,7 @@
"Name": "Set-LocalUserPassword",
"Arguments": {
"UserName": "Administrator",
"Password": "$adm_password",
"Password": "$adminPassword",
"Force": true
}
}

View File

@ -51,7 +51,8 @@
"Properties": {
"InstanceType" : { "Ref" : "InstanceType" },
"ImageId" : { "Ref" : "ImageName" },
"KeyName" : { "Ref" : "KeyName" }
"KeyName" : { "Ref" : "KeyName" },
"UserData": "$userData"
}
}
},

View 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>

View 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>

View File

@ -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>

View File

@ -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"
}
]