From 2185c2298a86ea821e2f41a473ccc488249af340 Mon Sep 17 00:00:00 2001 From: Stan Lagun Date: Mon, 4 Mar 2013 23:06:13 +0400 Subject: [PATCH 1/3] Initial conductor implementation --- conductor/bin/app.py | 3 + conductor/conductor/__init__.py | 0 conductor/conductor/app.py | 59 +++++++ conductor/conductor/cloud_formation.py | 10 ++ conductor/conductor/commands/__init__.py | 1 + .../conductor/commands/cloud_formation.py | 75 +++++++++ conductor/conductor/commands/command.py | 13 ++ conductor/conductor/commands/dispatcher.py | 44 +++++ conductor/conductor/commands/windows_agent.py | 58 +++++++ conductor/conductor/config.py | 17 ++ conductor/conductor/function_context.py | 50 ++++++ conductor/conductor/helpers.py | 36 ++++ conductor/conductor/rabbitmq.py | 62 +++++++ conductor/conductor/windows_agent.py | 13 ++ conductor/conductor/workflow.py | 142 ++++++++++++++++ conductor/conductor/xml_code_engine.py | 141 ++++++++++++++++ .../templates/agent/CreatePrimaryDC.template | 21 +++ .../data/templates/agent/SetPassword.template | 22 +++ conductor/data/templates/cf/Windows.template | 61 +++++++ conductor/data/workflows/testDC.xml | 69 ++++++++ conductor/etc/app.config | 5 + conductor/test.json | 23 +++ conductor/tools/install_venv.py | 154 ++++++++++++++++++ conductor/tools/pip-requires | 3 + conductor/tools/test-requires | 8 + conductor/tools/with_venv.sh | 4 + 26 files changed, 1094 insertions(+) create mode 100644 conductor/bin/app.py create mode 100644 conductor/conductor/__init__.py create mode 100644 conductor/conductor/app.py create mode 100644 conductor/conductor/cloud_formation.py create mode 100644 conductor/conductor/commands/__init__.py create mode 100644 conductor/conductor/commands/cloud_formation.py create mode 100644 conductor/conductor/commands/command.py create mode 100644 conductor/conductor/commands/dispatcher.py create mode 100644 conductor/conductor/commands/windows_agent.py create mode 100644 conductor/conductor/config.py create mode 100644 conductor/conductor/function_context.py create mode 100644 conductor/conductor/helpers.py create mode 100644 conductor/conductor/rabbitmq.py create mode 100644 conductor/conductor/windows_agent.py create mode 100644 conductor/conductor/workflow.py create mode 100644 conductor/conductor/xml_code_engine.py create mode 100644 conductor/data/templates/agent/CreatePrimaryDC.template create mode 100644 conductor/data/templates/agent/SetPassword.template create mode 100644 conductor/data/templates/cf/Windows.template create mode 100644 conductor/data/workflows/testDC.xml create mode 100644 conductor/etc/app.config create mode 100644 conductor/test.json create mode 100644 conductor/tools/install_venv.py create mode 100644 conductor/tools/pip-requires create mode 100644 conductor/tools/test-requires create mode 100644 conductor/tools/with_venv.sh diff --git a/conductor/bin/app.py b/conductor/bin/app.py new file mode 100644 index 0000000..3c2619b --- /dev/null +++ b/conductor/bin/app.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python + +from conductor import app \ No newline at end of file diff --git a/conductor/conductor/__init__.py b/conductor/conductor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/conductor/conductor/app.py b/conductor/conductor/app.py new file mode 100644 index 0000000..7a5ba0f --- /dev/null +++ b/conductor/conductor/app.py @@ -0,0 +1,59 @@ +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 + +config = Config(sys.argv[1] if len(sys.argv) > 1 else None) + +rmqclient = rabbitmq.RabbitMqClient( + virtual_host=config.get_setting('rabbitmq', 'vhost', '/'), + login=config.get_setting('rabbitmq', 'login', 'guest'), + 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)) + +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) + workflows.append(workflow) + + def loop(callback): + for workflow in workflows: + workflow.execute() + if not command_dispatcher.execute_pending(lambda: schedule(loop, callback)): + callback() + + def shutdown(): + command_dispatcher.close() + rmqclient.send('task-results', json.dumps(task)) + print "Done!!!!!!!!!!" + + loop(shutdown) + + +def message_received(body): + task_received(json.loads(body)) + + +def start(): + rmqclient.subscribe("tasks", message_received) + +rmqclient.start(start) +tornado.ioloop.IOLoop.instance().start() + diff --git a/conductor/conductor/cloud_formation.py b/conductor/conductor/cloud_formation.py new file mode 100644 index 0000000..7b2a200 --- /dev/null +++ b/conductor/conductor/cloud_formation.py @@ -0,0 +1,10 @@ +import xml_code_engine + +def update_cf_stack(engine, context, body, template, mappings, arguments, **kwargs): + command_dispatcher = context['/commandDispatcher'] + + 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 diff --git a/conductor/conductor/commands/__init__.py b/conductor/conductor/commands/__init__.py new file mode 100644 index 0000000..551f6ea --- /dev/null +++ b/conductor/conductor/commands/__init__.py @@ -0,0 +1 @@ +import command diff --git a/conductor/conductor/commands/cloud_formation.py b/conductor/conductor/commands/cloud_formation.py new file mode 100644 index 0000000..b85140d --- /dev/null +++ b/conductor/conductor/commands/cloud_formation.py @@ -0,0 +1,75 @@ +import json +import os +import uuid + +import conductor.helpers +from command import CommandBase +from subprocess import call + +class HeatExecutor(CommandBase): + def __init__(self, stack): + self._pending_list = [] + self._stack = stack + + def execute(self, template, mappings, arguments, callback): + 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) + + self._pending_list.append({ + 'template': template_data, + 'arguments': arguments, + 'callback': callback + }) + + def has_pending_commands(self): + return len(self._pending_list) > 0 + + def execute_pending(self, callback): + if not self._pending_list: + 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) + + 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()) + 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()]) + call([ + "./heat_run","stack-create", + "-f" + file_name, + "-P" + arguments_str, + self._stack + ]) + + callbacks = [] + for t in self._pending_list: + if t['callback']: + callbacks.append(t['callback']) + + self._pending_list = [] + + for cb in callbacks: + cb(True) + + callback() + + return True + + + + + + + diff --git a/conductor/conductor/commands/command.py b/conductor/conductor/commands/command.py new file mode 100644 index 0000000..fe34b13 --- /dev/null +++ b/conductor/conductor/commands/command.py @@ -0,0 +1,13 @@ + +class CommandBase(object): + def execute(self, **kwargs): + pass + + def execute_pending(self, callback): + return False + + def has_pending_commands(self): + return False + + def close(self): + pass diff --git a/conductor/conductor/commands/dispatcher.py b/conductor/conductor/commands/dispatcher.py new file mode 100644 index 0000000..40e88d5 --- /dev/null +++ b/conductor/conductor/commands/dispatcher.py @@ -0,0 +1,44 @@ +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) + } + + def execute(self, name, **kwargs): + self._command_map[name].execute(**kwargs) + + def execute_pending(self, callback): + result = 0 + count = [0] + + def on_result(): + count[0] -= 1 + if not count[0]: + callback() + + for command in self._command_map.values(): + count[0] += 1 + result += 1 + if not command.execute_pending(on_result): + count[0] -= 1 + result -= 1 + + + return result > 0 + + + def has_pending_commands(self): + result = False + for command in self._command_map.values(): + result |= command.has_pending_commands() + + return result + + def close(self): + for t in self._command_map.values(): + t.close() diff --git a/conductor/conductor/commands/windows_agent.py b/conductor/conductor/commands/windows_agent.py new file mode 100644 index 0000000..3bbb106 --- /dev/null +++ b/conductor/conductor/commands/windows_agent.py @@ -0,0 +1,58 @@ +import json + +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 + 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: + template_data = template_file.read() + + template_data = json.dumps(conductor.helpers.transform_json(json.loads(template_data), mappings)) + + self._pending_list.append({ + '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() + item['callback'](json.loads(body)) + if self._callback and not self._pending_list: + cb = self._callback + self._callback = None + cb() + + def has_pending_commands(self): + return len(self._pending_list) > 0 + + def execute_pending(self, callback): + if not self._pending_list: + return False + + 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']) + + return True + + def close(self): + self._rmqclient.unsubscribe('-execution-results') + diff --git a/conductor/conductor/config.py b/conductor/conductor/config.py new file mode 100644 index 0000000..ba6255a --- /dev/null +++ b/conductor/conductor/config.py @@ -0,0 +1,17 @@ +from ConfigParser import SafeConfigParser + +class Config(object): + CONFIG_PATH = './etc/app.config' + + def __init__(self, filename=None): + self.config = SafeConfigParser() + self.config.read(filename or self.CONFIG_PATH) + + def get_setting(self, section, name, default=None): + if not self.config.has_option(section, name): + return default + return self.config.get(section, name) + + def __getitem__(self, item): + parts = item.rsplit('.', 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 new file mode 100644 index 0000000..07bb08a --- /dev/null +++ b/conductor/conductor/function_context.py @@ -0,0 +1,50 @@ +class Context(object): + def __init__(self, parent=None): + self._parent = parent + self._data = None + + def _get_data(self): + if self._data is None: + self._data = {} if self._parent is None else self._parent._get_data().copy() + return self._data + + def __getitem__(self, item): + context, path = self._parseContext(item) + return context._get_data().get(path) + + def __setitem__(self, key, value): + context, path = self._parseContext(key) + context._get_data()[path] = value + + def _parseContext(self, path): + context = self + index = 0 + for c in path: + if c == ':' and context._parent is not None: + context = context._parent + elif c == '/': + while context._parent is not None: + context = context._parent + else: + break + + index += 1 + + return context, path[index:] + + def assign_from(self, context, copy=False): + self._parent = context._parent + self._data = context._data + if copy and self._data is not None: + self._data = self._data.copy() + + @property + def parent(self): + return self._parent + + def __str__(self): + if self._data is not None: + return str(self._data) + if self._parent: + return str(self._parent) + return str({}) \ No newline at end of file diff --git a/conductor/conductor/helpers.py b/conductor/conductor/helpers.py new file mode 100644 index 0000000..b9b8047 --- /dev/null +++ b/conductor/conductor/helpers.py @@ -0,0 +1,36 @@ +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 + + if isinstance(json, types.DictionaryType): + result = {} + for key, value in json.items(): + result[transform_json(key, mappings)] = transform_json(value, mappings) + return result + + if isinstance(json, types.StringTypes) and json.startswith('$'): + value = mappings.get(json[1:]) + if value is not None: + return value + + 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): + result[key] = other_value + else: + 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 diff --git a/conductor/conductor/rabbitmq.py b/conductor/conductor/rabbitmq.py new file mode 100644 index 0000000..ddf0907 --- /dev/null +++ b/conductor/conductor/rabbitmq.py @@ -0,0 +1,62 @@ +import uuid +import pika +from pika.adapters import TornadoConnection +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='/'): + credentials = pika.PlainCredentials(login, password) + self._connection_parameters = pika.ConnectionParameters( + 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) + + def _on_connected(self, connection): + self._channel = connection.channel(self._on_channel_open) + + def _on_channel_open(self, channel): + self._channel = channel + if self._started_callback: + self._started_callback() + + def _on_queue_declared(self, frame, queue, callback, ctag): + def invoke_callback(ch, method_frame, header_frame, body): + callback(body) + + 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)) + + 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) + + 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)) + + + diff --git a/conductor/conductor/windows_agent.py b/conductor/conductor/windows_agent.py new file mode 100644 index 0000000..f008941 --- /dev/null +++ b/conductor/conductor/windows_agent.py @@ -0,0 +1,13 @@ +import xml_code_engine + +def send_command(engine, context, body, template, mappings, host, **kwargs): + command_dispatcher = context['/commandDispatcher'] + + def callback(result): + print "Received ", result + engine.evaluate_content(body.find('success'), 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 new file mode 100644 index 0000000..59fafeb --- /dev/null +++ b/conductor/conductor/workflow.py @@ -0,0 +1,142 @@ +import jsonpath +import types +import re + +import xml_code_engine +import function_context + +class Workflow(object): + def __init__(self, filename, data, command_dispatcher, config): + 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 + + def execute(self): + while True: + context = function_context.Context() + context['/dataSource'] = self._data + context['/commandDispatcher'] = self._command_dispatcher + context['/config'] = self._config + if not self._engine.execute(context): + break + + @staticmethod + def _get_path(obj, path, create_non_existing=False): + current = obj + for part in path: + if isinstance(current, types.ListType): + current = current[int(part)] + elif isinstance(current, types.DictionaryType): + if part not in current: + if create_non_existing: + current[part] = {} + else: + return None + current = current[part] + else: + raise ValueError() + + return current + + @staticmethod + def _set_path(obj, path, value): + current = Workflow._get_path(obj, path[:-1], True) + if isinstance(current, types.ListType): + current[int(path[-1])] = value + elif isinstance(current, types.DictionaryType): + current[path[-1]] = value + else: + raise ValueError() + + @staticmethod + def _select_func(path, context, **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('.')) + + @staticmethod + def _set_func(path, context, body, engine, **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'] + + 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 + + @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) + selected = jsonpath.jsonpath(data, match, 'IPATH') or [] + + index = 0 + for found_match in selected: + if 0 < int(limit) <= index: + break + index += 1 + new_position = position + found_match + 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']: + break + + @staticmethod + def _workflow_func(context, body, engine, **kwargs): + context['/hasSideEffects'] = False + for element in body: + engine.evaluate(element, context) + if element.tag == 'rule' and context['/hasSideEffects']: + return True + 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') diff --git a/conductor/conductor/xml_code_engine.py b/conductor/conductor/xml_code_engine.py new file mode 100644 index 0000000..05b3198 --- /dev/null +++ b/conductor/conductor/xml_code_engine.py @@ -0,0 +1,141 @@ +#from lxml import etree +import xml.etree.ElementTree as etree +import function_context + +class XmlCodeEngine(object): + _functionMap = {} + + def __init__(self): + self._document = None + + def load(self, file_obj): + self._document = etree.parse(file_obj) + + @staticmethod + def register_function(func, name): + XmlCodeEngine._functionMap[name] = func + + def _execute_function(self, name, element, parent_context): + if name == 'parameter': + return None + + if name not in self._functionMap: + raise KeyError('Unknown function %s' % name) + + definition = self._functionMap[name] + context = function_context.Context(parent_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) + + return definition(**args) + + def evaluate(self, element, parent_context): + return self._execute_function(element.tag, element, parent_context) + + def evaluate_content(self, element, context): + parts = [element.text or ''] + do_strip = False + for sub_element in element: + if sub_element.tag == 'parameter': + continue + do_strip = True + 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)): + result.append(t) + 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 + + def execute(self, parent_context=None): + root = self._document.getroot() + return self.evaluate(root, parent_context) + + +def _dict_func(engine, body, context, **kwargs): + result = {} + for item in body: + key = item.get('name') + value = engine.evaluate_content(item, context) + 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 + + +XmlCodeEngine.register_function(_dict_func, "map") +XmlCodeEngine.register_function(_array_func, "list") +XmlCodeEngine.register_function(_text_func, "text") +XmlCodeEngine.register_function(_int_func, "int") +XmlCodeEngine.register_function(_function_func, "function") +XmlCodeEngine.register_function(_null_func, "null") +XmlCodeEngine.register_function(_true_func, "true") +XmlCodeEngine.register_function(_false_func, "false") + + +def xprint(context, body, **kwargs): + print "------------------------ start ------------------------" + for arg in kwargs: + print "%s = %s" % (arg, kwargs[arg]) + print 'context = ', context + 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/templates/agent/CreatePrimaryDC.template b/conductor/data/templates/agent/CreatePrimaryDC.template new file mode 100644 index 0000000..dcd71f0 --- /dev/null +++ b/conductor/data/templates/agent/CreatePrimaryDC.template @@ -0,0 +1,21 @@ +{ + "Scripts": [ + "RnVuY3Rpb24gU2V0LUxvY2FsVXNlclBhc3N3b3JkIHsNCiAgICBwYXJhbSAoDQogICAgICAgIFtTdHJpbmddICRVc2VyTmFtZSwNCiAgICAgICAgW1N0cmluZ10gJFBhc3N3b3JkLA0KICAgICAgICBbU3dpdGNoXSAkRm9yY2UNCiAgICApDQogICAgDQogICAgdHJhcCB7IFN0b3AtRXhlY3V0aW9uICRfIH0NCiAgICANCiAgICBpZiAoKEdldC1XbWlPYmplY3QgV2luMzJfVXNlckFjY291bnQgLUZpbHRlciAiTG9jYWxBY2NvdW50ID0gJ1RydWUnIEFORCBOYW1lPSckVXNlck5hbWUnIikgLWVxICRudWxsKSB7DQogICAgICAgIHRocm93ICJVbmFibGUgdG8gZmluZCBsb2NhbCB1c2VyIGFjY291bnQgJyRVc2VyTmFtZSciDQogICAgfQ0KICAgIA0KICAgIGlmICgkRm9yY2UpIHsNCiAgICAgICAgV3JpdGUtTG9nICJDaGFuZ2luZyBwYXNzd29yZCBmb3IgdXNlciAnJFVzZXJOYW1lJyB0byAnKioqKionIiAjIDopDQogICAgICAgIChbQURTSV0gIldpbk5UOi8vLi8kVXNlck5hbWUiKS5TZXRQYXNzd29yZCgkUGFzc3dvcmQpDQogICAgfQ0KICAgIGVsc2Ugew0KICAgICAgICBXcml0ZS1Mb2dXYXJuaW5nICJZb3UgYXJlIHRyeWluZyB0byBjaGFuZ2UgcGFzc3dvcmQgZm9yIHVzZXIgJyRVc2VyTmFtZScuIFRvIGRvIHRoaXMgcGxlYXNlIHJ1biB0aGUgY29tbWFuZCBhZ2FpbiB3aXRoIC1Gb3JjZSBwYXJhbWV0ZXIuIg0KICAgICAgICAkVXNlckFjY291bnQNCiAgICB9DQp9DQoNCg0KDQpGdW5jdGlvbiBJbnN0YWxsLVJvbGVQcmltYXJ5RG9tYWluQ29udHJvbGxlcg0Kew0KPCMNCi5TWU5PUFNJUw0KQ29uZmlndXJlIG5vZGUncyBuZXR3b3JrIGFkYXB0ZXJzLg0KQ3JlYXRlIGZpcnN0IGRvbWFpbiBjb250cm9sbGVyIGluIHRoZSBmb3Jlc3QuDQoNCi5FWEFNUExFDQpQUz4gSW5zdGFsbC1Sb2xlUHJpbWFyeURvbWFpbkNvbnRyb2xsZXIgLURvbWFpbk5hbWUgYWNtZS5sb2NhbCAtU2FmZU1vZGVQYXNzd29yZCAiUEBzc3cwcmQiDQoNCkluc3RhbGwgRE5TIGFuZCBBRERTLCBjcmVhdGUgZm9yZXN0IGFuZCBkb21haW4gJ2FjbWUubG9jYWwnLg0KU2V0IERDIHJlY292ZXJ5IG1vZGUgcGFzc3dvcmQgdG8gJ1BAc3N3MHJkJy4NCiM+DQoJDQoJcGFyYW0NCgkoDQoJCVtTdHJpbmddDQoJCSMgTmV3IGRvbWFpbiBuYW1lLg0KCQkkRG9tYWluTmFtZSwNCgkJDQoJCVtTdHJpbmddDQoJCSMgRG9tYWluIGNvbnRyb2xsZXIgcmVjb3ZlcnkgbW9kZSBwYXNzd29yZC4NCgkJJFNhZmVNb2RlUGFzc3dvcmQNCgkpDQoNCgl0cmFwIHsgU3RvcC1FeGVjdXRpb24gJF8gfQ0KDQogICAgICAgICMgQWRkIHJlcXVpcmVkIHdpbmRvd3MgZmVhdHVyZXMNCglBZGQtV2luZG93c0ZlYXR1cmVXcmFwcGVyIGANCgkJLU5hbWUgIkROUyIsIkFELURvbWFpbi1TZXJ2aWNlcyIsIlJTQVQtREZTLU1nbXQtQ29uIiBgDQoJCS1JbmNsdWRlTWFuYWdlbWVudFRvb2xzIGANCiAgICAgICAgLU5vdGlmeVJlc3RhcnQNCg0KDQoJV3JpdGUtTG9nICJDcmVhdGluZyBmaXJzdCBkb21haW4gY29udHJvbGxlciAuLi4iDQoJCQ0KCSRTTUFQID0gQ29udmVydFRvLVNlY3VyZVN0cmluZyAtU3RyaW5nICRTYWZlTW9kZVBhc3N3b3JkIC1Bc1BsYWluVGV4dCAtRm9yY2UNCgkJDQoJSW5zdGFsbC1BRERTRm9yZXN0IGANCgkJLURvbWFpbk5hbWUgJERvbWFpbk5hbWUgYA0KCQktU2FmZU1vZGVBZG1pbmlzdHJhdG9yUGFzc3dvcmQgJFNNQVAgYA0KCQktRG9tYWluTW9kZSBEZWZhdWx0IGANCgkJLUZvcmVzdE1vZGUgRGVmYXVsdCBgDQoJCS1Ob1JlYm9vdE9uQ29tcGxldGlvbiBgDQoJCS1Gb3JjZSBgDQoJCS1FcnJvckFjdGlvbiBTdG9wIHwgT3V0LU51bGwNCg0KCVdyaXRlLUhvc3QgIldhaXRpbmcgZm9yIHJlYm9vdCAuLi4iCQkNCiMJU3RvcC1FeGVjdXRpb24gLUV4aXRDb2RlIDMwMTAgLUV4aXRTdHJpbmcgIkNvbXB1dGVyIG11c3QgYmUgcmVzdGFydGVkIHRvIGZpbmlzaCBkb21haW4gY29udHJvbGxlciBwcm9tb3Rpb24uIg0KIwlXcml0ZS1Mb2cgIlJlc3RhcmluZyBjb21wdXRlciAuLi4iDQojCVJlc3RhcnQtQ29tcHV0ZXIgLUZvcmNlDQp9DQo=" + ], + "Commands": [ + { + "Name": "Import-Module", + "Arguments": { + "Name": "CoreFunctions" + } + }, + { + "Name": "Install-RolePrimaryDomainController", + "Arguments": { + "DomainName": "$dc_name", + "SafeModePassword": "$recovery_password" + } + } + ], + "RebootOnCompletion": 1 +} \ No newline at end of file diff --git a/conductor/data/templates/agent/SetPassword.template b/conductor/data/templates/agent/SetPassword.template new file mode 100644 index 0000000..125ba89 --- /dev/null +++ b/conductor/data/templates/agent/SetPassword.template @@ -0,0 +1,22 @@ +{ + "Scripts": [ + "RnVuY3Rpb24gU2V0LUxvY2FsVXNlclBhc3N3b3JkIHsNCiAgICBwYXJhbSAoDQogICAgICAgIFtTdHJpbmddICRVc2VyTmFtZSwNCiAgICAgICAgW1N0cmluZ10gJFBhc3N3b3JkLA0KICAgICAgICBbU3dpdGNoXSAkRm9yY2UNCiAgICApDQogICAgDQogICAgdHJhcCB7IFN0b3AtRXhlY3V0aW9uICRfIH0NCiAgICANCiAgICBpZiAoKEdldC1XbWlPYmplY3QgV2luMzJfVXNlckFjY291bnQgLUZpbHRlciAiTG9jYWxBY2NvdW50ID0gJ1RydWUnIEFORCBOYW1lPSckVXNlck5hbWUnIikgLWVxICRudWxsKSB7DQogICAgICAgIHRocm93ICJVbmFibGUgdG8gZmluZCBsb2NhbCB1c2VyIGFjY291bnQgJyRVc2VyTmFtZSciDQogICAgfQ0KICAgIA0KICAgIGlmICgkRm9yY2UpIHsNCiAgICAgICAgV3JpdGUtTG9nICJDaGFuZ2luZyBwYXNzd29yZCBmb3IgdXNlciAnJFVzZXJOYW1lJyB0byAnKioqKionIiAjIDopDQogICAgICAgIChbQURTSV0gIldpbk5UOi8vLi8kVXNlck5hbWUiKS5TZXRQYXNzd29yZCgkUGFzc3dvcmQpDQogICAgfQ0KICAgIGVsc2Ugew0KICAgICAgICBXcml0ZS1Mb2dXYXJuaW5nICJZb3UgYXJlIHRyeWluZyB0byBjaGFuZ2UgcGFzc3dvcmQgZm9yIHVzZXIgJyRVc2VyTmFtZScuIFRvIGRvIHRoaXMgcGxlYXNlIHJ1biB0aGUgY29tbWFuZCBhZ2FpbiB3aXRoIC1Gb3JjZSBwYXJhbWV0ZXIuIg0KICAgICAgICAkVXNlckFjY291bnQNCiAgICB9DQp9DQoNCg0KDQpGdW5jdGlvbiBJbnN0YWxsLVJvbGVQcmltYXJ5RG9tYWluQ29udHJvbGxlcg0Kew0KPCMNCi5TWU5PUFNJUw0KQ29uZmlndXJlIG5vZGUncyBuZXR3b3JrIGFkYXB0ZXJzLg0KQ3JlYXRlIGZpcnN0IGRvbWFpbiBjb250cm9sbGVyIGluIHRoZSBmb3Jlc3QuDQoNCi5FWEFNUExFDQpQUz4gSW5zdGFsbC1Sb2xlUHJpbWFyeURvbWFpbkNvbnRyb2xsZXIgLURvbWFpbk5hbWUgYWNtZS5sb2NhbCAtU2FmZU1vZGVQYXNzd29yZCAiUEBzc3cwcmQiDQoNCkluc3RhbGwgRE5TIGFuZCBBRERTLCBjcmVhdGUgZm9yZXN0IGFuZCBkb21haW4gJ2FjbWUubG9jYWwnLg0KU2V0IERDIHJlY292ZXJ5IG1vZGUgcGFzc3dvcmQgdG8gJ1BAc3N3MHJkJy4NCiM+DQoJDQoJcGFyYW0NCgkoDQoJCVtTdHJpbmddDQoJCSMgTmV3IGRvbWFpbiBuYW1lLg0KCQkkRG9tYWluTmFtZSwNCgkJDQoJCVtTdHJpbmddDQoJCSMgRG9tYWluIGNvbnRyb2xsZXIgcmVjb3ZlcnkgbW9kZSBwYXNzd29yZC4NCgkJJFNhZmVNb2RlUGFzc3dvcmQNCgkpDQoNCgl0cmFwIHsgU3RvcC1FeGVjdXRpb24gJF8gfQ0KDQogICAgICAgICMgQWRkIHJlcXVpcmVkIHdpbmRvd3MgZmVhdHVyZXMNCglBZGQtV2luZG93c0ZlYXR1cmVXcmFwcGVyIGANCgkJLU5hbWUgIkROUyIsIkFELURvbWFpbi1TZXJ2aWNlcyIsIlJTQVQtREZTLU1nbXQtQ29uIiBgDQoJCS1JbmNsdWRlTWFuYWdlbWVudFRvb2xzIGANCiAgICAgICAgLU5vdGlmeVJlc3RhcnQNCg0KDQoJV3JpdGUtTG9nICJDcmVhdGluZyBmaXJzdCBkb21haW4gY29udHJvbGxlciAuLi4iDQoJCQ0KCSRTTUFQID0gQ29udmVydFRvLVNlY3VyZVN0cmluZyAtU3RyaW5nICRTYWZlTW9kZVBhc3N3b3JkIC1Bc1BsYWluVGV4dCAtRm9yY2UNCgkJDQoJSW5zdGFsbC1BRERTRm9yZXN0IGANCgkJLURvbWFpbk5hbWUgJERvbWFpbk5hbWUgYA0KCQktU2FmZU1vZGVBZG1pbmlzdHJhdG9yUGFzc3dvcmQgJFNNQVAgYA0KCQktRG9tYWluTW9kZSBEZWZhdWx0IGANCgkJLUZvcmVzdE1vZGUgRGVmYXVsdCBgDQoJCS1Ob1JlYm9vdE9uQ29tcGxldGlvbiBgDQoJCS1Gb3JjZSBgDQoJCS1FcnJvckFjdGlvbiBTdG9wIHwgT3V0LU51bGwNCg0KCVdyaXRlLUhvc3QgIldhaXRpbmcgZm9yIHJlYm9vdCAuLi4iCQkNCiMJU3RvcC1FeGVjdXRpb24gLUV4aXRDb2RlIDMwMTAgLUV4aXRTdHJpbmcgIkNvbXB1dGVyIG11c3QgYmUgcmVzdGFydGVkIHRvIGZpbmlzaCBkb21haW4gY29udHJvbGxlciBwcm9tb3Rpb24uIg0KIwlXcml0ZS1Mb2cgIlJlc3RhcmluZyBjb21wdXRlciAuLi4iDQojCVJlc3RhcnQtQ29tcHV0ZXIgLUZvcmNlDQp9DQo=" + ], + "Commands": [ + { + "Name": "Import-Module", + "Arguments": { + "Name": "CoreFunctions" + } + }, + { + "Name": "Set-LocalUserPassword", + "Arguments": { + "UserName": "Administrator", + "Password": "$adm_password", + "Force": true + } + } + ], + "RebootOnCompletion": 0 +} \ No newline at end of file diff --git a/conductor/data/templates/cf/Windows.template b/conductor/data/templates/cf/Windows.template new file mode 100644 index 0000000..134a310 --- /dev/null +++ b/conductor/data/templates/cf/Windows.template @@ -0,0 +1,61 @@ +{ + "AWSTemplateFormatVersion" : "2010-09-09", + + "Description" : "", + + "Parameters" : { + "KeyName" : { + "Description" : "Name of an existing Amazon EC2 key pair for RDP access", + "Type" : "String", + "Default" : "keero_key" + }, + "InstanceType" : { + "Description" : "Amazon EC2 instance type", + "Type" : "String", + "Default" : "m1.medium", + "AllowedValues" : [ "m1.small", "m1.medium", "m1.large" ] + }, + "ImageName" : { + "Description" : "Image name", + "Type" : "String", + "Default" : "ws-2012-full-agent", + "AllowedValues" : [ "ws-2012-full", "ws-2012-core", "ws-2012-full-agent" ] + } + }, + + "Resources" : { + "IAMUser" : { + "Type" : "AWS::IAM::User", + "Properties" : { + "Path": "/", + "Policies": [{ + "PolicyName": "root", + "PolicyDocument": { "Statement":[{ + "Effect": "Allow", + "Action": "CloudFormation:DescribeStackResource", + "Resource": "*" + }]} + }] + } + }, + + "IAMUserAccessKey" : { + "Type" : "AWS::IAM::AccessKey", + "Properties" : { + "UserName" : {"Ref": "IAMUser"} + } + }, + + "$instanceName": { + "Type" : "AWS::EC2::Instance", + "Properties": { + "InstanceType" : { "Ref" : "InstanceType" }, + "ImageId" : { "Ref" : "ImageName" }, + "KeyName" : { "Ref" : "KeyName" } + } + } + }, + + "Outputs" : { + } +} \ No newline at end of file diff --git a/conductor/data/workflows/testDC.xml b/conductor/data/workflows/testDC.xml new file mode 100644 index 0000000..44d7065 --- /dev/null +++ b/conductor/data/workflows/testDC.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + created + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DNS IP = + + + /$.services.activeDirectories[?(@.domain == ' + + + + + + + + + + + has joined domain + Creating Secondary Domain Controller on unit + + + + + + + + + + + + + Primary Domain Controller created + + + + + \ No newline at end of file diff --git a/conductor/data/workflows/IIS.xml b/conductor/data/workflows/IIS.xml new file mode 100644 index 0000000..40bf0ab --- /dev/null +++ b/conductor/data/workflows/IIS.xml @@ -0,0 +1,60 @@ + + + + + Creating instance + + + + + + + + + keero-linux-keys + m1.medium + ws-2012-full-agent + + + + + + Instance + Creating IIS Web Server on unit + + + + + has started + + + + + \ No newline at end of file diff --git a/conductor/data/workflows/testDC.xml b/conductor/data/workflows/testDC.xml deleted file mode 100644 index 44d7065..0000000 --- a/conductor/data/workflows/testDC.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Primary Domain Controller created + Secondary Domain Controller created created