From 28bc7ff1d5bada47229682d38347d2a6eaa72aef Mon Sep 17 00:00:00 2001 From: Stan Lagun Date: Mon, 11 Mar 2013 19:11:27 +0400 Subject: [PATCH] Workflows, ExecutionPlanGenerator, Reporting, UserData, conductor improvements --- WindowsAgent/WindowsAgent.sln | 6 + WindowsAgent/WindowsAgent/App.config | 15 +- WindowsAgent/WindowsAgent/MqMessage.cs | 5 + WindowsAgent/WindowsAgent/PlanExecutor.cs | 35 ++- WindowsAgent/WindowsAgent/Program.cs | 111 ++++++++-- WindowsAgent/WindowsAgent/RabbitMqClient.cs | 29 +-- WindowsAgent/WindowsAgent/WindowsAgent.csproj | 1 + conductor/conductor/app.py | 23 +- conductor/conductor/cloud_formation.py | 37 +++- .../conductor/commands/cloud_formation.py | 30 +-- conductor/conductor/commands/command.py | 1 - conductor/conductor/commands/dispatcher.py | 5 +- conductor/conductor/commands/windows_agent.py | 42 ++-- conductor/conductor/config.py | 4 +- conductor/conductor/function_context.py | 3 +- conductor/conductor/helpers.py | 26 ++- conductor/conductor/rabbitmq.py | 36 ++-- conductor/conductor/reporting.py | 29 +++ conductor/conductor/windows_agent.py | 22 +- conductor/conductor/workflow.py | 142 ++++++++----- conductor/conductor/xml_code_engine.py | 49 ++--- conductor/data/init.ps1 | 14 ++ .../templates/agent-config/Default.template | 30 +++ .../data/templates/agent/AskDnsIp.template | 12 ++ .../templates/agent/CreatePrimaryDC.template | 4 +- .../agent/CreateSecondaryDC.template | 23 ++ .../data/templates/agent/InstallIIS.template | 12 ++ .../data/templates/agent/JoinDomain.template | 27 +++ .../data/templates/agent/SetPassword.template | 2 +- conductor/data/templates/cf/Windows.template | 3 +- conductor/data/workflows/AD.xml | 199 ++++++++++++++++++ conductor/data/workflows/IIS.xml | 60 ++++++ conductor/data/workflows/testDC.xml | 69 ------ conductor/test.json | 22 +- 34 files changed, 852 insertions(+), 276 deletions(-) create mode 100644 conductor/conductor/reporting.py create mode 100644 conductor/data/init.ps1 create mode 100644 conductor/data/templates/agent-config/Default.template create mode 100644 conductor/data/templates/agent/AskDnsIp.template create mode 100644 conductor/data/templates/agent/CreateSecondaryDC.template create mode 100644 conductor/data/templates/agent/InstallIIS.template create mode 100644 conductor/data/templates/agent/JoinDomain.template create mode 100644 conductor/data/workflows/AD.xml create mode 100644 conductor/data/workflows/IIS.xml delete mode 100644 conductor/data/workflows/testDC.xml diff --git a/WindowsAgent/WindowsAgent.sln b/WindowsAgent/WindowsAgent.sln index 71a494b..b77d57c 100644 --- a/WindowsAgent/WindowsAgent.sln +++ b/WindowsAgent/WindowsAgent.sln @@ -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 diff --git a/WindowsAgent/WindowsAgent/App.config b/WindowsAgent/WindowsAgent/App.config index 3df6972..5cde5a4 100644 --- a/WindowsAgent/WindowsAgent/App.config +++ b/WindowsAgent/WindowsAgent/App.config @@ -1,4 +1,4 @@ - +
@@ -11,20 +11,23 @@ - + + - - - + + + - + + \ No newline at end of file diff --git a/WindowsAgent/WindowsAgent/MqMessage.cs b/WindowsAgent/WindowsAgent/MqMessage.cs index d77ab79..255fcb2 100644 --- a/WindowsAgent/WindowsAgent/MqMessage.cs +++ b/WindowsAgent/WindowsAgent/MqMessage.cs @@ -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() { diff --git a/WindowsAgent/WindowsAgent/PlanExecutor.cs b/WindowsAgent/WindowsAgent/PlanExecutor.cs index ad4910c..e1d8292 100644 --- a/WindowsAgent/WindowsAgent/PlanExecutor.cs +++ b/WindowsAgent/WindowsAgent/PlanExecutor.cs @@ -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(File.ReadAllText(this.path)); - var resultPath = this.path + ".result"; List 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()).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 + {} + } } } diff --git a/WindowsAgent/WindowsAgent/Program.cs b/WindowsAgent/WindowsAgent/Program.cs index 32041ae..08fa125 100644 --- a/WindowsAgent/WindowsAgent/Program.cs +++ b/WindowsAgent/WindowsAgent/Program.cs @@ -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(); } diff --git a/WindowsAgent/WindowsAgent/RabbitMqClient.cs b/WindowsAgent/WindowsAgent/RabbitMqClient.cs index c927bd8..6dfe5c6 100644 --- a/WindowsAgent/WindowsAgent/RabbitMqClient.cs +++ b/WindowsAgent/WindowsAgent/RabbitMqClient.cs @@ -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) diff --git a/WindowsAgent/WindowsAgent/WindowsAgent.csproj b/WindowsAgent/WindowsAgent/WindowsAgent.csproj index f467e1d..d19fa32 100644 --- a/WindowsAgent/WindowsAgent/WindowsAgent.csproj +++ b/WindowsAgent/WindowsAgent/WindowsAgent.csproj @@ -21,6 +21,7 @@ DEBUG;TRACE prompt 4 + false AnyCPU diff --git a/conductor/conductor/app.py b/conductor/conductor/app.py index 7a5ba0f..f4cf9a7 100644 --- a/conductor/conductor/app.py +++ b/conductor/conductor/app.py @@ -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(): diff --git a/conductor/conductor/cloud_formation.py b/conductor/conductor/cloud_formation.py index 7b2a200..3ef3b41 100644 --- a/conductor/conductor/cloud_formation.py +++ b/conductor/conductor/cloud_formation.py @@ -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") \ No newline at end of file +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") + diff --git a/conductor/conductor/commands/cloud_formation.py b/conductor/conductor/commands/cloud_formation.py index b85140d..0d12083 100644 --- a/conductor/conductor/commands/cloud_formation.py +++ b/conductor/conductor/commands/cloud_formation.py @@ -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 - - - - - - - diff --git a/conductor/conductor/commands/command.py b/conductor/conductor/commands/command.py index fe34b13..ca9d144 100644 --- a/conductor/conductor/commands/command.py +++ b/conductor/conductor/commands/command.py @@ -1,4 +1,3 @@ - class CommandBase(object): def execute(self, **kwargs): pass diff --git a/conductor/conductor/commands/dispatcher.py b/conductor/conductor/commands/dispatcher.py index 40e88d5..b815ddb 100644 --- a/conductor/conductor/commands/dispatcher.py +++ b/conductor/conductor/commands/dispatcher.py @@ -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 diff --git a/conductor/conductor/commands/windows_agent.py b/conductor/conductor/commands/windows_agent.py index 3bbb106..c4747b6 100644 --- a/conductor/conductor/commands/windows_agent.py +++ b/conductor/conductor/commands/windows_agent.py @@ -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 diff --git a/conductor/conductor/config.py b/conductor/conductor/config.py index ba6255a..881d4ad 100644 --- a/conductor/conductor/config.py +++ b/conductor/conductor/config.py @@ -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]) diff --git a/conductor/conductor/function_context.py b/conductor/conductor/function_context.py index 07bb08a..237f23e 100644 --- a/conductor/conductor/function_context.py +++ b/conductor/conductor/function_context.py @@ -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): diff --git a/conductor/conductor/helpers.py b/conductor/conductor/helpers.py index b9b8047..4128e16 100644 --- a/conductor/conductor/helpers.py +++ b/conductor/conductor/helpers.py @@ -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 diff --git a/conductor/conductor/rabbitmq.py b/conductor/conductor/rabbitmq.py index ddf0907..d7c3351 100644 --- a/conductor/conductor/rabbitmq.py +++ b/conductor/conductor/rabbitmq.py @@ -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)) diff --git a/conductor/conductor/reporting.py b/conductor/conductor/reporting.py new file mode 100644 index 0000000..4dbef12 --- /dev/null +++ b/conductor/conductor/reporting.py @@ -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") + + + + diff --git a/conductor/conductor/windows_agent.py b/conductor/conductor/windows_agent.py index f008941..287abb0 100644 --- a/conductor/conductor/windows_agent.py +++ b/conductor/conductor/windows_agent.py @@ -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") \ No newline at end of file diff --git a/conductor/conductor/workflow.py b/conductor/conductor/workflow.py index 59fafeb..a39a7da 100644 --- a/conductor/conductor/workflow.py +++ b/conductor/conductor/workflow.py @@ -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') diff --git a/conductor/conductor/xml_code_engine.py b/conductor/conductor/xml_code_engine.py index 05b3198..fe676b0 100644 --- a/conductor/conductor/xml_code_engine.py +++ b/conductor/conductor/xml_code_engine.py @@ -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 - diff --git a/conductor/data/init.ps1 b/conductor/data/init.ps1 new file mode 100644 index 0000000..620792c --- /dev/null +++ b/conductor/data/init.ps1 @@ -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!' \ No newline at end of file diff --git a/conductor/data/templates/agent-config/Default.template b/conductor/data/templates/agent-config/Default.template new file mode 100644 index 0000000..54d9cb9 --- /dev/null +++ b/conductor/data/templates/agent-config/Default.template @@ -0,0 +1,30 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/conductor/data/templates/agent/AskDnsIp.template b/conductor/data/templates/agent/AskDnsIp.template new file mode 100644 index 0000000..ee2057c --- /dev/null +++ b/conductor/data/templates/agent/AskDnsIp.template @@ -0,0 +1,12 @@ +{ + "Scripts": [ + "ZnVuY3Rpb24gR2V0LURuc0xpc3RlbmluZ0lwQWRkcmVzc2VzIHsNCiAgICAoR2V0LUROU1NlcnZlciAtQ29tcHV0ZXJOYW1lIGxvY2FsaG9zdCkuU2VydmVyU2V0dGluZy5MaXN0ZW5pbmdJcEFkZHJlc3MNCn0NCg==" + ], + "Commands": [ + { + "Name": "Get-DnsListeningIpAddress", + "Arguments": {} + } + ], + "RebootOnCompletion": 0 +} \ No newline at end of file diff --git a/conductor/data/templates/agent/CreatePrimaryDC.template b/conductor/data/templates/agent/CreatePrimaryDC.template index dcd71f0..b181dba 100644 --- a/conductor/data/templates/agent/CreatePrimaryDC.template +++ b/conductor/data/templates/agent/CreatePrimaryDC.template @@ -12,8 +12,8 @@ { "Name": "Install-RolePrimaryDomainController", "Arguments": { - "DomainName": "$dc_name", - "SafeModePassword": "$recovery_password" + "DomainName": "$domain", + "SafeModePassword": "$recoveryPassword" } } ], diff --git a/conductor/data/templates/agent/CreateSecondaryDC.template b/conductor/data/templates/agent/CreateSecondaryDC.template new file mode 100644 index 0000000..a5ad7f4 --- /dev/null +++ b/conductor/data/templates/agent/CreateSecondaryDC.template @@ -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 +} \ No newline at end of file diff --git a/conductor/data/templates/agent/InstallIIS.template b/conductor/data/templates/agent/InstallIIS.template new file mode 100644 index 0000000..baeaec1 --- /dev/null +++ b/conductor/data/templates/agent/InstallIIS.template @@ -0,0 +1,12 @@ +{ + "Scripts": [ + "ZnVuY3Rpb24gSW5zdGFsbC1XZWJTZXJ2ZXIgew0KICAgIEltcG9ydC1Nb2R1bGUgU2VydmVyTWFuYWdlcg0KICAgIEluc3RhbGwtV2luZG93c0ZlYXR1cmUgV2ViLVNlcnZlciAtSW5jbHVkZU1hbmFnZW1lbnRUb29scw0KfQ0K" + ], + "Commands": [ + { + "Name": "Install-WebServer", + "Arguments": {} + } + ], + "RebootOnCompletion": 0 +} \ No newline at end of file diff --git a/conductor/data/templates/agent/JoinDomain.template b/conductor/data/templates/agent/JoinDomain.template new file mode 100644 index 0000000..85e4b9c --- /dev/null +++ b/conductor/data/templates/agent/JoinDomain.template @@ -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 +} \ No newline at end of file diff --git a/conductor/data/templates/agent/SetPassword.template b/conductor/data/templates/agent/SetPassword.template index 125ba89..1cc9dcc 100644 --- a/conductor/data/templates/agent/SetPassword.template +++ b/conductor/data/templates/agent/SetPassword.template @@ -13,7 +13,7 @@ "Name": "Set-LocalUserPassword", "Arguments": { "UserName": "Administrator", - "Password": "$adm_password", + "Password": "$adminPassword", "Force": true } } diff --git a/conductor/data/templates/cf/Windows.template b/conductor/data/templates/cf/Windows.template index 134a310..e14069f 100644 --- a/conductor/data/templates/cf/Windows.template +++ b/conductor/data/templates/cf/Windows.template @@ -51,7 +51,8 @@ "Properties": { "InstanceType" : { "Ref" : "InstanceType" }, "ImageId" : { "Ref" : "ImageName" }, - "KeyName" : { "Ref" : "KeyName" } + "KeyName" : { "Ref" : "KeyName" }, + "UserData": "$userData" } } }, diff --git a/conductor/data/workflows/AD.xml b/conductor/data/workflows/AD.xml new file mode 100644 index 0000000..8c20087 --- /dev/null +++ b/conductor/data/workflows/AD.xml @@ -0,0 +1,199 @@ + + + + + Creating instance + + + + + + + + + keero-linux-keys + m1.medium + ws-2012-full-agent + + + + + + Instance + + + + + + + + + + + + + + + + + + + + + Creating Primary Domain Controller on unit + + + + + + + + + + + Primary Domain Controller created + + + + + + + + + + + + + + + + + + + + ' and @.state.primaryDcIp)] + + + + + + + + + + + + + + + Unit + + + + + + + + + + + + + + + + + + Primary Domain Controller created + + + + + + + + + + + + + + + + + created + + + + + + + + + + + + + IIS - - - - - - keero-linux-keys - m1.medium - ws-2012-full-agent - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/conductor/test.json b/conductor/test.json index 9d02511..2427070 100644 --- a/conductor/test.json +++ b/conductor/test.json @@ -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" } ]