diff --git a/bootstrapvz/base/main.py b/bootstrapvz/base/main.py index 7b07a0a..ec14ea6 100644 --- a/bootstrapvz/base/main.py +++ b/bootstrapvz/base/main.py @@ -63,10 +63,11 @@ def run(opts): manifest = Manifest(opts['MANIFEST']) # Get the tasklist + from tasklist import load_tasks from tasklist import TaskList - tasklist = TaskList() + tasks = load_tasks('resolve_tasks', manifest) + tasklist = TaskList(tasks) # 'resolve_tasks' is the name of the function to call on the provider and plugins - tasklist.load('resolve_tasks', manifest) # Create the bootstrap information object that'll be used throughout the bootstrapping process from bootstrapinfo import BootstrapInformation @@ -85,23 +86,23 @@ def run(opts): raw_input('Press Enter to commence rollback') log.error('Rolling back') - # Create a new tasklist to gather the necessary tasks for rollback - rollback_tasklist = TaskList() - # Create a useful little function for the provider and plugins to use, # when figuring out what tasks should be added to the rollback list. - def counter_task(task, counter): + def counter_task(taskset, task, counter): """counter_task() adds the second argument to the rollback tasklist if the first argument is present in the list of completed tasks + :param set taskset: The taskset to add the rollback task to :param Task task: The task to look for in the completed tasks list :param Task counter: The task to add to the rollback tasklist """ if task in tasklist.tasks_completed and counter not in tasklist.tasks_completed: - rollback_tasklist.tasks.add(counter) + taskset.add(counter) + # Ask the provider and plugins for tasks they'd like to add to the rollback tasklist # Any additional arguments beyond the first two are passed directly to the provider and plugins - rollback_tasklist.load('resolve_rollback_tasks', manifest, tasklist.tasks_completed, counter_task) + rollback_tasks = load_tasks('resolve_rollback_tasks', manifest, tasklist.tasks_completed, counter_task) + rollback_tasklist = TaskList(rollback_tasks) # Run the rollback tasklist rollback_tasklist.run(info=bootstrap_info, dry_run=opts['--dry-run']) diff --git a/bootstrapvz/base/tasklist.py b/bootstrapvz/base/tasklist.py index 37d2a2a..8e5dbb1 100644 --- a/bootstrapvz/base/tasklist.py +++ b/bootstrapvz/base/tasklist.py @@ -11,28 +11,10 @@ class TaskList(object): and orders them according to their dependencies. """ - def __init__(self): - self.tasks = set() + def __init__(self, tasks): + self.tasks = tasks self.tasks_completed = [] - def load(self, function, manifest, *args): - """Calls 'function' on the provider and all plugins that have been loaded by the manifest. - Any additional arguments are passed directly to 'function'. - The function that is called shall accept the taskset as its first argument and the manifest - as its second argument. - - :param str function: Name of the function to call - :param Manifest manifest: The manifest - :param list *args: Additional arguments that should be passed to the function that is called - """ - # Call 'function' on the provider - getattr(manifest.modules['provider'], function)(self.tasks, manifest, *args) - for plugin in manifest.modules['plugins']: - # Plugins are not required to have whatever function we call - fn = getattr(plugin, function, None) - if callable(fn): - fn(self.tasks, manifest, *args) - def run(self, info, dry_run=False): """Converts the taskgraph into a list and runs all tasks in that list @@ -40,7 +22,7 @@ class TaskList(object): :param bool dry_run: Whether to actually run the tasks or simply step through them """ # Create a list for us to run - task_list = self.create_list() + task_list = create_list(self.tasks) # Output the tasklist log.debug('Tasklist:\n\t' + ('\n\t'.join(map(repr, task_list)))) @@ -57,185 +39,212 @@ class TaskList(object): # Remember which tasks have been run for later use (e.g. when rolling back, because of an error) self.tasks_completed.append(task) - def create_list(self): - """Creates a list of all the tasks that should be run. - """ - from bootstrapvz.common.phases import order - # Get a hold of all tasks - tasks = self.get_all_tasks() - # Make sure the taskset is a subset of all the tasks we have gathered - self.tasks.issubset(tasks) - # Create a graph over all tasks by creating a map of each tasks successors - graph = {} - for task in tasks: - # Do a sanity check first - self.check_ordering(task) - successors = set() - # Add all successors mentioned in the task - successors.update(task.successors) - # Add all tasks that mention this task as a predecessor - successors.update(filter(lambda succ: task in succ.predecessors, tasks)) - # Create a list of phases that succeed the phase of this task - succeeding_phases = order[order.index(task.phase) + 1:] - # Add all tasks that occur in above mentioned succeeding phases - successors.update(filter(lambda succ: succ.phase in succeeding_phases, tasks)) - # Map the successors to the task - graph[task] = successors - # Use the strongly connected components algorithm to check for cycles in our task graph - components = self.strongly_connected_components(graph) - cycles_found = 0 - for component in components: - # Node of 1 is also a strongly connected component but hardly a cycle, so we filter them out - if len(component) > 1: - cycles_found += 1 - log.debug('Cycle: {list}\n' + (', '.join(map(repr, component)))) - if cycles_found > 0: - msg = ('{num} cycles were found in the tasklist, ' - 'consult the logfile for more information.'.format(num=cycles_found)) +def load_tasks(function, manifest, *args): + """Calls ``function`` on the provider and all plugins that have been loaded by the manifest. + Any additional arguments are passed directly to ``function``. + The function that is called shall accept the taskset as its first argument and the manifest + as its second argument. + + :param str function: Name of the function to call + :param Manifest manifest: The manifest + :param list args: Additional arguments that should be passed to the function that is called + """ + tasks = set() + # Call 'function' on the provider + getattr(manifest.modules['provider'], function)(tasks, manifest, *args) + for plugin in manifest.modules['plugins']: + # Plugins are not required to have whatever function we call + fn = getattr(plugin, function, None) + if callable(fn): + fn(tasks, manifest, *args) + return tasks + + +def create_list(subset): + """Creates a list of all the tasks that should be run. + """ + from bootstrapvz.common.phases import order + # Get a hold of all tasks + tasks = get_all_tasks() + # Make sure the taskset is a subset of all the tasks we have gathered + subset.issubset(tasks) + # Create a graph over all tasks by creating a map of each tasks successors + graph = {} + for task in tasks: + # Do a sanity check first + check_ordering(task) + successors = set() + # Add all successors mentioned in the task + successors.update(task.successors) + # Add all tasks that mention this task as a predecessor + successors.update(filter(lambda succ: task in succ.predecessors, tasks)) + # Create a list of phases that succeed the phase of this task + succeeding_phases = order[order.index(task.phase) + 1:] + # Add all tasks that occur in above mentioned succeeding phases + successors.update(filter(lambda succ: succ.phase in succeeding_phases, tasks)) + # Map the successors to the task + graph[task] = successors + + # Use the strongly connected components algorithm to check for cycles in our task graph + components = strongly_connected_components(graph) + cycles_found = 0 + for component in components: + # Node of 1 is also a strongly connected component but hardly a cycle, so we filter them out + if len(component) > 1: + cycles_found += 1 + log.debug('Cycle: {list}\n' + (', '.join(map(repr, component)))) + if cycles_found > 0: + msg = ('{num} cycles were found in the tasklist, ' + 'consult the logfile for more information.'.format(num=cycles_found)) + raise TaskListError(msg) + + # Run a topological sort on the graph, returning an ordered list + sorted_tasks = topological_sort(graph) + + # Filter out any tasks not in the tasklist + # We want to maintain ordering, so we don't use set intersection + sorted_tasks = filter(lambda task: task in subset, sorted_tasks) + return sorted_tasks + + +def get_all_tasks(): + """Gets a list of all task classes in the package + + :return: A list of all tasks in the package + :rtype: list + """ + # Get a generator that returns all classes in the package + import os.path + pkg_path = os.path.normpath(os.path.join(os.path.dirname(__file__), '..')) + classes = get_all_classes(pkg_path, 'bootstrapvz.') + + # lambda function to check whether a class is a task (excluding the superclass Task) + def is_task(obj): + from task import Task + return issubclass(obj, Task) and obj is not Task + return filter(is_task, classes) # Only return classes that are tasks + + +def get_all_classes(path=None, prefix=''): + """ Given a path to a package, this function retrieves all the classes in it + + :param str path: Path to the package + :param str prefix: Name of the package followed by a dot + :return: A generator that yields classes + :rtype: generator + :raises Exception: If a module cannot be inspected. + """ + import pkgutil + import importlib + import inspect + + def walk_error(module): + raise Exception('Unable to inspect module ' + module) + walker = pkgutil.walk_packages([path], prefix, walk_error) + for _, module_name, _ in walker: + module = importlib.import_module(module_name) + classes = inspect.getmembers(module, inspect.isclass) + for class_name, obj in classes: + # We only want classes that are defined in the module, and not imported ones + if obj.__module__ == module_name: + yield obj + + +def check_ordering(task): + """Checks the ordering of a task in relation to other tasks and their phases. + + This function checks for a subset of what the strongly connected components algorithm does, + but can deliver a more precise error message, namely that there is a conflict between + what a task has specified as its predecessors or successors and in which phase it is placed. + + :param Task task: The task to check the ordering for + :raises TaskListError: If there is a conflict between task precedence and phase precedence + """ + for successor in task.successors: + # Run through all successors and check whether the phase of the task + # comes before the phase of a successor + if task.phase > successor.phase: + msg = ("The task {task} is specified as running before {other}, " + "but its phase '{phase}' lies after the phase '{other_phase}'" + .format(task=task, other=successor, phase=task.phase, other_phase=successor.phase)) + raise TaskListError(msg) + for predecessor in task.predecessors: + # Run through all predecessors and check whether the phase of the task + # comes after the phase of a predecessor + if task.phase < predecessor.phase: + msg = ("The task {task} is specified as running after {other}, " + "but its phase '{phase}' lies before the phase '{other_phase}'" + .format(task=task, other=predecessor, phase=task.phase, other_phase=predecessor.phase)) raise TaskListError(msg) - # Run a topological sort on the graph, returning an ordered list - sorted_tasks = self.topological_sort(graph) - # Filter out any tasks not in the tasklist - # We want to maintain ordering, so we don't use set intersection - sorted_tasks = filter(lambda task: task in self.tasks, sorted_tasks) - return sorted_tasks +def strongly_connected_components(graph): + """Find the strongly connected components in a graph using Tarjan's algorithm. - def get_all_tasks(self): - """Gets a list of all task classes in the package + Source: http://www.logarithmic.net/pfh-files/blog/01208083168/sort.py - :return: A list of all tasks in the package - :rtype: list - """ - # Get a generator that returns all classes in the package - import os.path - pkg_path = os.path.normpath(os.path.join(os.path.dirname(__file__), '..')) - classes = self.get_all_classes(pkg_path, 'bootstrapvz.') + :param dict graph: mapping of tasks to lists of successor tasks + :return: List of tuples that are strongly connected comoponents + :rtype: list + """ - # lambda function to check whether a class is a task (excluding the superclass Task) - def is_task(obj): - from task import Task - return issubclass(obj, Task) and obj is not Task - return filter(is_task, classes) # Only return classes that are tasks + result = [] + stack = [] + low = {} - def get_all_classes(self, path=None, prefix=''): - """ Given a path to a package, this function retrieves all the classes in it + def visit(node): + if node in low: + return - :param str path: Path to the package - :param str prefix: Name of the package followed by a dot - :return: A generator that yields classes - :rtype: generator - :raises Exception: If a module cannot be inspected. - """ - import pkgutil - import importlib - import inspect + num = len(low) + low[node] = num + stack_pos = len(stack) + stack.append(node) - def walk_error(module): - raise Exception('Unable to inspect module ' + module) - walker = pkgutil.walk_packages([path], prefix, walk_error) - for _, module_name, _ in walker: - module = importlib.import_module(module_name) - classes = inspect.getmembers(module, inspect.isclass) - for class_name, obj in classes: - # We only want classes that are defined in the module, and not imported ones - if obj.__module__ == module_name: - yield obj + for successor in graph[node]: + visit(successor) + low[node] = min(low[node], low[successor]) - def check_ordering(self, task): - """Checks the ordering of a task in relation to other tasks and their phases. + if num == low[node]: + component = tuple(stack[stack_pos:]) + del stack[stack_pos:] + result.append(component) + for item in component: + low[item] = len(graph) - This function checks for a subset of what the strongly connected components algorithm does, - but can deliver a more precise error message, namely that there is a conflict between - what a task has specified as its predecessors or successors and in which phase it is placed. + for node in graph: + visit(node) - :param Task task: The task to check the ordering for - :raises TaskListError: If there is a conflict between task precedence and phase precedence - """ - for successor in task.successors: - # Run through all successors and check whether the phase of the task - # comes before the phase of a successor - if task.phase > successor.phase: - msg = ("The task {task} is specified as running before {other}, " - "but its phase '{phase}' lies after the phase '{other_phase}'" - .format(task=task, other=successor, phase=task.phase, other_phase=successor.phase)) - raise TaskListError(msg) - for predecessor in task.predecessors: - # Run through all predecessors and check whether the phase of the task - # comes after the phase of a predecessor - if task.phase < predecessor.phase: - msg = ("The task {task} is specified as running after {other}, " - "but its phase '{phase}' lies before the phase '{other_phase}'" - .format(task=task, other=predecessor, phase=task.phase, other_phase=predecessor.phase)) - raise TaskListError(msg) + return result - def strongly_connected_components(self, graph): - """Find the strongly connected components in a graph using Tarjan's algorithm. - Source: http://www.logarithmic.net/pfh-files/blog/01208083168/sort.py +def topological_sort(graph): + """Runs a topological sort on a graph. - :param dict graph: mapping of tasks to lists of successor tasks - :return: List of tuples that are strongly connected comoponents - :rtype: list - """ + Source: http://www.logarithmic.net/pfh-files/blog/01208083168/sort.py - result = [] - stack = [] - low = {} + :param dict graph: mapping of tasks to lists of successor tasks + :return: A list of all tasks in the graph sorted according to ther dependencies + :rtype: list + """ + count = {} + for node in graph: + count[node] = 0 + for node in graph: + for successor in graph[node]: + count[successor] += 1 - def visit(node): - if node in low: - return + ready = [node for node in graph if count[node] == 0] - num = len(low) - low[node] = num - stack_pos = len(stack) - stack.append(node) + result = [] + while ready: + node = ready.pop(-1) + result.append(node) - for successor in graph[node]: - visit(successor) - low[node] = min(low[node], low[successor]) + for successor in graph[node]: + count[successor] -= 1 + if count[successor] == 0: + ready.append(successor) - if num == low[node]: - component = tuple(stack[stack_pos:]) - del stack[stack_pos:] - result.append(component) - for item in component: - low[item] = len(graph) - - for node in graph: - visit(node) - - return result - - def topological_sort(self, graph): - """Runs a topological sort on a graph. - - Source: http://www.logarithmic.net/pfh-files/blog/01208083168/sort.py - - :param dict graph: mapping of tasks to lists of successor tasks - :return: A list of all tasks in the graph sorted according to ther dependencies - :rtype: list - """ - count = {} - for node in graph: - count[node] = 0 - for node in graph: - for successor in graph[node]: - count[successor] += 1 - - ready = [node for node in graph if count[node] == 0] - - result = [] - while ready: - node = ready.pop(-1) - result.append(node) - - for successor in graph[node]: - count[successor] -= 1 - if count[successor] == 0: - ready.append(successor) - - return result + return result diff --git a/bootstrapvz/plugins/minimize_size/__init__.py b/bootstrapvz/plugins/minimize_size/__init__.py index 4dfacde..3f8c583 100644 --- a/bootstrapvz/plugins/minimize_size/__init__.py +++ b/bootstrapvz/plugins/minimize_size/__init__.py @@ -22,4 +22,4 @@ def resolve_tasks(taskset, manifest): def resolve_rollback_tasks(taskset, manifest, completed, counter_task): - counter_task(tasks.AddFolderMounts, tasks.RemoveFolderMounts) + counter_task(taskset, tasks.AddFolderMounts, tasks.RemoveFolderMounts) diff --git a/bootstrapvz/plugins/prebootstrapped/__init__.py b/bootstrapvz/plugins/prebootstrapped/__init__.py index 31014e7..dbda042 100644 --- a/bootstrapvz/plugins/prebootstrapped/__init__.py +++ b/bootstrapvz/plugins/prebootstrapped/__init__.py @@ -52,6 +52,6 @@ def resolve_tasks(taskset, manifest): def resolve_rollback_tasks(taskset, manifest, completed, counter_task): if manifest.volume['backing'] == 'ebs': - counter_task(CreateFromSnapshot, volume.Delete) + counter_task(taskset, CreateFromSnapshot, volume.Delete) else: - counter_task(CreateFromImage, volume.Delete) + counter_task(taskset, CreateFromImage, volume.Delete) diff --git a/bootstrapvz/plugins/vagrant/__init__.py b/bootstrapvz/plugins/vagrant/__init__.py index 6f7d8da..af0c6c5 100644 --- a/bootstrapvz/plugins/vagrant/__init__.py +++ b/bootstrapvz/plugins/vagrant/__init__.py @@ -31,4 +31,4 @@ def resolve_tasks(taskset, manifest): def resolve_rollback_tasks(taskset, manifest, completed, counter_task): - counter_task(tasks.CreateVagrantBoxDir, tasks.RemoveVagrantBoxDir) + counter_task(taskset, tasks.CreateVagrantBoxDir, tasks.RemoveVagrantBoxDir) diff --git a/bootstrapvz/providers/ec2/__init__.py b/bootstrapvz/providers/ec2/__init__.py index b77dfdb..fd5c804 100644 --- a/bootstrapvz/providers/ec2/__init__.py +++ b/bootstrapvz/providers/ec2/__init__.py @@ -113,6 +113,6 @@ def resolve_tasks(taskset, manifest): def resolve_rollback_tasks(taskset, manifest, completed, counter_task): taskset.update(task_groups.get_standard_rollback_tasks(completed)) - counter_task(tasks.ebs.Create, volume.Delete) - counter_task(tasks.ebs.Attach, volume.Detach) - counter_task(tasks.ami.BundleImage, tasks.ami.RemoveBundle) + counter_task(taskset, tasks.ebs.Create, volume.Delete) + counter_task(taskset, tasks.ebs.Attach, volume.Detach) + counter_task(taskset, tasks.ami.BundleImage, tasks.ami.RemoveBundle) diff --git a/docs/_static/graph.json b/docs/_static/graph.json index 7f2411d..3caaa2b 100644 --- a/docs/_static/graph.json +++ b/docs/_static/graph.json @@ -1 +1 @@ -{"phases": [{"name": "Preparation", "description": "Initializing connections, fetching data etc."}, {"name": "Volume creation", "description": "Creating the volume to bootstrap onto"}, {"name": "Volume preparation", "description": "Formatting the bootstrap volume"}, {"name": "Volume mounting", "description": "Mounting bootstrap volume"}, {"name": "OS installation", "description": "Installing the operating system"}, {"name": "Package installation", "description": "Installing software"}, {"name": "System modification", "description": "Modifying configuration files, adding resources, etc."}, {"name": "System cleaning", "description": "Removing sensitive data, temporary files and other leftovers"}, {"name": "Volume unmounting", "description": "Unmounting the bootstrap volume"}, {"name": "Image registration", "description": "Uploading/Registering with the provider"}, {"name": "Cleaning", "description": "Removing temporary files"}], "modules": [{"name": "common.tasks.apt"}, {"name": "common.tasks.boot"}, {"name": "common.tasks.bootstrap"}, {"name": "common.tasks.cleanup"}, {"name": "common.tasks.development"}, {"name": "common.tasks.filesystem"}, {"name": "common.tasks.host"}, {"name": "common.tasks.initd"}, {"name": "common.tasks.locale"}, {"name": "common.tasks.loopback"}, {"name": "common.tasks.network"}, {"name": "common.tasks.packages"}, {"name": "common.tasks.partitioning"}, {"name": "common.tasks.security"}, {"name": "common.tasks.volume"}, {"name": "common.tasks.workspace"}, {"name": "plugins.admin_user.tasks"}, {"name": "plugins.build_metadata.tasks"}, {"name": "plugins.cloud_init.tasks"}, {"name": "plugins.image_commands.tasks"}, {"name": "plugins.minimize_size.tasks"}, {"name": "plugins.opennebula.tasks"}, {"name": "plugins.prebootstrapped.tasks"}, {"name": "plugins.puppet.tasks"}, {"name": "plugins.root_password.tasks"}, {"name": "plugins.unattended_upgrades.tasks"}, {"name": "plugins.vagrant.tasks"}, {"name": "providers.ec2.tasks.ami"}, {"name": "providers.ec2.tasks.boot"}, {"name": "providers.ec2.tasks.connection"}, {"name": "providers.ec2.tasks.ebs"}, {"name": "providers.ec2.tasks.filesystem"}, {"name": "providers.ec2.tasks.host"}, {"name": "providers.ec2.tasks.initd"}, {"name": "providers.ec2.tasks.network"}, {"name": "providers.ec2.tasks.packages"}, {"name": "providers.kvm.tasks.packages"}, {"name": "providers.kvm.tasks.virtio"}, {"name": "providers.virtualbox.tasks.guest_additions"}, {"name": "providers.virtualbox.tasks.packages"}], "nodes": [{"phase": 0, "name": "AddDefaultSources", "module": 0}, {"phase": 0, "name": "AddManifestSources", "module": 0}, {"phase": 7, "name": "AptClean", "module": 0}, {"phase": 5, "name": "AptUpdate", "module": 0}, {"phase": 5, "name": "AptUpgrade", "module": 0}, {"phase": 5, "name": "DisableDaemonAutostart", "module": 0}, {"phase": 7, "name": "EnableDaemonAutostart", "module": 0}, {"phase": 5, "name": "InstallTrustedKeys", "module": 0}, {"phase": 7, "name": "PurgeUnusedPackages", "module": 0}, {"phase": 5, "name": "WriteSources", "module": 0}, {"phase": 0, "name": "AddExtlinuxPackage", "module": 1}, {"phase": 0, "name": "AddGrubPackage", "module": 1}, {"phase": 6, "name": "BlackListModules", "module": 1}, {"phase": 6, "name": "DisableGetTTYs", "module": 1}, {"phase": 6, "name": "InstallExtLinux", "module": 1}, {"phase": 6, "name": "InstallGrub", "module": 1}, {"phase": 4, "name": "Bootstrap", "module": 2}, {"phase": 4, "name": "MakeTarball", "module": 2}, {"phase": 7, "name": "CleanTMP", "module": 3}, {"phase": 7, "name": "ClearMOTD", "module": 3}, {"phase": 7, "name": "ShredHostkeys", "module": 3}, {"phase": 10, "name": "TriggerRollback", "module": 4}, {"phase": 0, "name": "AddXFSProgs", "module": 5}, {"phase": 3, "name": "CreateBootMountDir", "module": 5}, {"phase": 3, "name": "CreateMountDir", "module": 5}, {"phase": 8, "name": "DeleteMountDir", "module": 5}, {"phase": 6, "name": "FStab", "module": 5}, {"phase": 2, "name": "Format", "module": 5}, {"phase": 3, "name": "MountBoot", "module": 5}, {"phase": 3, "name": "MountRoot", "module": 5}, {"phase": 4, "name": "MountSpecials", "module": 5}, {"phase": 2, "name": "TuneVolumeFS", "module": 5}, {"phase": 8, "name": "UnmountRoot", "module": 5}, {"phase": 0, "name": "CheckHostDependencies", "module": 6}, {"phase": 0, "name": "HostDependencies", "module": 6}, {"phase": 6, "name": "AddExpandRoot", "module": 7}, {"phase": 6, "name": "AddSSHKeyGeneration", "module": 7}, {"phase": 6, "name": "AdjustExpandRootScript", "module": 7}, {"phase": 6, "name": "InstallInitScripts", "module": 7}, {"phase": 6, "name": "RemoveHWClock", "module": 7}, {"phase": 5, "name": "GenerateLocale", "module": 8}, {"phase": 0, "name": "LocaleBootstrapPackage", "module": 8}, {"phase": 6, "name": "SetTimezone", "module": 8}, {"phase": 1, "name": "Create", "module": 9}, {"phase": 9, "name": "MoveImage", "module": 9}, {"phase": 6, "name": "ConfigureNetworkIF", "module": 10}, {"phase": 6, "name": "RemoveDNSInfo", "module": 10}, {"phase": 6, "name": "RemoveHostname", "module": 10}, {"phase": 0, "name": "AddManifestPackages", "module": 11}, {"phase": 5, "name": "InstallPackages", "module": 11}, {"phase": 2, "name": "MapPartitions", "module": 12}, {"phase": 2, "name": "PartitionVolume", "module": 12}, {"phase": 8, "name": "UnmapPartitions", "module": 12}, {"phase": 6, "name": "DisableSSHDNSLookup", "module": 13}, {"phase": 6, "name": "DisableSSHPasswordAuthentication", "module": 13}, {"phase": 6, "name": "EnableShadowConfig", "module": 13}, {"phase": 1, "name": "Attach", "module": 14}, {"phase": 10, "name": "Delete", "module": 14}, {"phase": 8, "name": "Detach", "module": 14}, {"phase": 0, "name": "CreateWorkspace", "module": 15}, {"phase": 10, "name": "DeleteWorkspace", "module": 15}, {"phase": 0, "name": "AddSudoPackage", "module": 16}, {"phase": 6, "name": "AdminUserCredentials", "module": 16}, {"phase": 6, "name": "CreateAdminUser", "module": 16}, {"phase": 6, "name": "DisableRootLogin", "module": 16}, {"phase": 6, "name": "PasswordlessSudo", "module": 16}, {"phase": 10, "name": "WriteMetadata", "module": 17}, {"phase": 0, "name": "AddBackports", "module": 18}, {"phase": 0, "name": "AddCloudInitPackages", "module": 18}, {"phase": 6, "name": "DisableModules", "module": 18}, {"phase": 6, "name": "SetMetadataSource", "module": 18}, {"phase": 6, "name": "SetUsername", "module": 18}, {"phase": 6, "name": "ImageExecuteCommand", "module": 19}, {"phase": 4, "name": "AddFolderMounts", "module": 20}, {"phase": 0, "name": "CheckVMWareDMCommand", "module": 20}, {"phase": 0, "name": "CheckZerofreePath", "module": 20}, {"phase": 7, "name": "RemoveFolderMounts", "module": 20}, {"phase": 8, "name": "ShrinkVolume", "module": 20}, {"phase": 8, "name": "Zerofree", "module": 20}, {"phase": 0, "name": "AddONEContextPackage", "module": 21}, {"phase": 6, "name": "OpenNebulaContext", "module": 21}, {"phase": 5, "name": "CopyImage", "module": 22}, {"phase": 1, "name": "CreateFromImage", "module": 22}, {"phase": 1, "name": "CreateFromSnapshot", "module": 22}, {"phase": 5, "name": "Snapshot", "module": 22}, {"phase": 0, "name": "AddPackages", "module": 23}, {"phase": 6, "name": "ApplyPuppetManifest", "module": 23}, {"phase": 0, "name": "CheckPaths", "module": 23}, {"phase": 6, "name": "CopyPuppetAssets", "module": 23}, {"phase": 6, "name": "SetRootPassword", "module": 24}, {"phase": 0, "name": "AddUnattendedUpgradesPackage", "module": 25}, {"phase": 6, "name": "EnablePeriodicUpgrades", "module": 25}, {"phase": 6, "name": "AddInsecurePublicKey", "module": 26}, {"phase": 0, "name": "AddPackages", "module": 26}, {"phase": 0, "name": "CheckBoxPath", "module": 26}, {"phase": 0, "name": "CreateVagrantBoxDir", "module": 26}, {"phase": 6, "name": "CreateVagrantUser", "module": 26}, {"phase": 9, "name": "PackageBox", "module": 26}, {"phase": 6, "name": "PasswordlessSudo", "module": 26}, {"phase": 10, "name": "RemoveVagrantBoxDir", "module": 26}, {"phase": 6, "name": "SetHostname", "module": 26}, {"phase": 6, "name": "SetRootPassword", "module": 26}, {"phase": 0, "name": "AMIName", "module": 27}, {"phase": 9, "name": "BundleImage", "module": 27}, {"phase": 9, "name": "RegisterAMI", "module": 27}, {"phase": 10, "name": "RemoveBundle", "module": 27}, {"phase": 9, "name": "UploadImage", "module": 27}, {"phase": 6, "name": "ConfigurePVGrub", "module": 28}, {"phase": 0, "name": "Connect", "module": 29}, {"phase": 0, "name": "GetCredentials", "module": 29}, {"phase": 1, "name": "Attach", "module": 30}, {"phase": 1, "name": "Create", "module": 30}, {"phase": 9, "name": "Snapshot", "module": 30}, {"phase": 6, "name": "S3FStab", "module": 31}, {"phase": 0, "name": "GetInfo", "module": 32}, {"phase": 0, "name": "HostDependencies", "module": 32}, {"phase": 6, "name": "AddEC2InitScripts", "module": 33}, {"phase": 0, "name": "AddBuildEssentialPackage", "module": 34}, {"phase": 6, "name": "EnableDHCPCDDNS", "module": 34}, {"phase": 5, "name": "InstallEnhancedNetworking", "module": 34}, {"phase": 0, "name": "DefaultPackages", "module": 35}, {"phase": 0, "name": "DefaultPackages", "module": 36}, {"phase": 6, "name": "VirtIO", "module": 37}, {"phase": 5, "name": "AddGuestAdditionsPackages", "module": 38}, {"phase": 0, "name": "CheckGuestAdditionsPath", "module": 38}, {"phase": 5, "name": "InstallGuestAdditions", "module": 38}, {"phase": 0, "name": "DefaultPackages", "module": 39}], "links": [{"source": 32, "target": 58, "definer": 32}, {"source": 35, "target": 38, "definer": 35}, {"source": 36, "target": 38, "definer": 36}, {"source": 39, "target": 38, "definer": 39}, {"source": 43, "target": 56, "definer": 43}, {"source": 50, "target": 27, "definer": 50}, {"source": 52, "target": 58, "definer": 52}, {"source": 57, "target": 60, "definer": 57}, {"source": 76, "target": 2, "definer": 76}, {"source": 78, "target": 58, "definer": 78}, {"source": 82, "target": 56, "definer": 82}, {"source": 83, "target": 110, "definer": 83}, {"source": 86, "target": 47, "definer": 86}, {"source": 86, "target": 46, "definer": 86}, {"source": 99, "target": 60, "definer": 99}, {"source": 105, "target": 60, "definer": 105}, {"source": 115, "target": 33, "definer": 115}, {"source": 116, "target": 38, "definer": 116}, {"source": 123, "target": 49, "definer": 123}, {"source": 1, "target": 0, "definer": 0}, {"source": 40, "target": 3, "definer": 3}, {"source": 9, "target": 3, "definer": 3}, {"source": 3, "target": 4, "definer": 4}, {"source": 5, "target": 4, "definer": 4}, {"source": 7, "target": 9, "definer": 9}, {"source": 0, "target": 10, "definer": 10}, {"source": 0, "target": 11, "definer": 11}, {"source": 26, "target": 14, "definer": 14}, {"source": 26, "target": 15, "definer": 15}, {"source": 17, "target": 16, "definer": 16}, {"source": 0, "target": 22, "definer": 22}, {"source": 29, "target": 23, "definer": 23}, {"source": 32, "target": 25, "definer": 25}, {"source": 23, "target": 28, "definer": 28}, {"source": 24, "target": 29, "definer": 29}, {"source": 16, "target": 30, "definer": 30}, {"source": 27, "target": 31, "definer": 31}, {"source": 34, "target": 33, "definer": 33}, {"source": 38, "target": 37, "definer": 37}, {"source": 0, "target": 48, "definer": 48}, {"source": 4, "target": 49, "definer": 49}, {"source": 51, "target": 50, "definer": 50}, {"source": 32, "target": 52, "definer": 52}, {"source": 0, "target": 61, "definer": 61}, {"source": 38, "target": 62, "definer": 62}, {"source": 0, "target": 68, "definer": 68}, {"source": 67, "target": 68, "definer": 68}, {"source": 16, "target": 73, "definer": 73}, {"source": 58, "target": 77, "definer": 77}, {"source": 32, "target": 78, "definer": 78}, {"source": 52, "target": 78, "definer": 78}, {"source": 49, "target": 81, "definer": 81}, {"source": 125, "target": 81, "definer": 81}, {"source": 49, "target": 84, "definer": 84}, {"source": 125, "target": 84, "definer": 84}, {"source": 0, "target": 85, "definer": 85}, {"source": 88, "target": 86, "definer": 86}, {"source": 0, "target": 90, "definer": 90}, {"source": 96, "target": 92, "definer": 92}, {"source": 0, "target": 93, "definer": 93}, {"source": 59, "target": 95, "definer": 95}, {"source": 94, "target": 95, "definer": 95}, {"source": 108, "target": 102, "definer": 102}, {"source": 112, "target": 104, "definer": 104}, {"source": 106, "target": 104, "definer": 104}, {"source": 103, "target": 106, "definer": 106}, {"source": 109, "target": 108, "definer": 108}, {"source": 114, "target": 108, "definer": 108}, {"source": 111, "target": 110, "definer": 110}, {"source": 0, "target": 117, "definer": 117}, {"source": 0, "target": 120, "definer": 120}, {"source": 0, "target": 121, "definer": 121}, {"source": 49, "target": 125, "definer": 125}, {"source": 0, "target": 126, "definer": 126}]} \ No newline at end of file +{"phases": [{"name": "Preparation", "description": "Initializing connections, fetching data etc."}, {"name": "Volume creation", "description": "Creating the volume to bootstrap onto"}, {"name": "Volume preparation", "description": "Formatting the bootstrap volume"}, {"name": "Volume mounting", "description": "Mounting bootstrap volume"}, {"name": "OS installation", "description": "Installing the operating system"}, {"name": "Package installation", "description": "Installing software"}, {"name": "System modification", "description": "Modifying configuration files, adding resources, etc."}, {"name": "System cleaning", "description": "Removing sensitive data, temporary files and other leftovers"}, {"name": "Volume unmounting", "description": "Unmounting the bootstrap volume"}, {"name": "Image registration", "description": "Uploading/Registering with the provider"}, {"name": "Cleaning", "description": "Removing temporary files"}], "modules": [{"name": "bootstrapvz.common.tasks.apt"}, {"name": "bootstrapvz.common.tasks.boot"}, {"name": "bootstrapvz.common.tasks.bootstrap"}, {"name": "bootstrapvz.common.tasks.cleanup"}, {"name": "bootstrapvz.common.tasks.development"}, {"name": "bootstrapvz.common.tasks.filesystem"}, {"name": "bootstrapvz.common.tasks.host"}, {"name": "bootstrapvz.common.tasks.initd"}, {"name": "bootstrapvz.common.tasks.locale"}, {"name": "bootstrapvz.common.tasks.loopback"}, {"name": "bootstrapvz.common.tasks.network"}, {"name": "bootstrapvz.common.tasks.packages"}, {"name": "bootstrapvz.common.tasks.partitioning"}, {"name": "bootstrapvz.common.tasks.security"}, {"name": "bootstrapvz.common.tasks.ssh"}, {"name": "bootstrapvz.common.tasks.volume"}, {"name": "bootstrapvz.common.tasks.workspace"}, {"name": "bootstrapvz.plugins.admin_user.tasks"}, {"name": "bootstrapvz.plugins.apt_proxy.tasks"}, {"name": "bootstrapvz.plugins.chef.tasks"}, {"name": "bootstrapvz.plugins.cloud_init.tasks"}, {"name": "bootstrapvz.plugins.image_commands.tasks"}, {"name": "bootstrapvz.plugins.minimize_size.tasks"}, {"name": "bootstrapvz.plugins.ntp.tasks"}, {"name": "bootstrapvz.plugins.opennebula.tasks"}, {"name": "bootstrapvz.plugins.prebootstrapped.tasks"}, {"name": "bootstrapvz.plugins.puppet.tasks"}, {"name": "bootstrapvz.plugins.root_password.tasks"}, {"name": "bootstrapvz.plugins.salt.tasks"}, {"name": "bootstrapvz.plugins.unattended_upgrades.tasks"}, {"name": "bootstrapvz.plugins.vagrant.tasks"}, {"name": "bootstrapvz.providers.azure.tasks.boot"}, {"name": "bootstrapvz.providers.azure.tasks.image"}, {"name": "bootstrapvz.providers.azure.tasks.packages"}, {"name": "bootstrapvz.providers.ec2.tasks.ami"}, {"name": "bootstrapvz.providers.ec2.tasks.boot"}, {"name": "bootstrapvz.providers.ec2.tasks.connection"}, {"name": "bootstrapvz.providers.ec2.tasks.ebs"}, {"name": "bootstrapvz.providers.ec2.tasks.filesystem"}, {"name": "bootstrapvz.providers.ec2.tasks.host"}, {"name": "bootstrapvz.providers.ec2.tasks.initd"}, {"name": "bootstrapvz.providers.ec2.tasks.network"}, {"name": "bootstrapvz.providers.ec2.tasks.packages"}, {"name": "bootstrapvz.providers.gce.tasks.apt"}, {"name": "bootstrapvz.providers.gce.tasks.boot"}, {"name": "bootstrapvz.providers.gce.tasks.configuration"}, {"name": "bootstrapvz.providers.gce.tasks.host"}, {"name": "bootstrapvz.providers.gce.tasks.image"}, {"name": "bootstrapvz.providers.gce.tasks.packages"}, {"name": "bootstrapvz.providers.kvm.tasks.packages"}, {"name": "bootstrapvz.providers.kvm.tasks.virtio"}, {"name": "bootstrapvz.providers.virtualbox.tasks.guest_additions"}, {"name": "bootstrapvz.providers.virtualbox.tasks.packages"}], "nodes": [{"phase": 0, "name": "AddDefaultSources", "module": 0}, {"phase": 0, "name": "AddManifestPreferences", "module": 0}, {"phase": 0, "name": "AddManifestSources", "module": 0}, {"phase": 7, "name": "AptClean", "module": 0}, {"phase": 5, "name": "AptUpdate", "module": 0}, {"phase": 5, "name": "AptUpgrade", "module": 0}, {"phase": 5, "name": "DisableDaemonAutostart", "module": 0}, {"phase": 7, "name": "EnableDaemonAutostart", "module": 0}, {"phase": 5, "name": "InstallTrustedKeys", "module": 0}, {"phase": 7, "name": "PurgeUnusedPackages", "module": 0}, {"phase": 5, "name": "WritePreferences", "module": 0}, {"phase": 5, "name": "WriteSources", "module": 0}, {"phase": 0, "name": "AddExtlinuxPackage", "module": 1}, {"phase": 0, "name": "AddGrubPackage", "module": 1}, {"phase": 6, "name": "BlackListModules", "module": 1}, {"phase": 6, "name": "ConfigureGrub", "module": 1}, {"phase": 6, "name": "DisableGetTTYs", "module": 1}, {"phase": 6, "name": "InstallExtLinux", "module": 1}, {"phase": 6, "name": "InstallGrub", "module": 1}, {"phase": 0, "name": "AddRequiredCommands", "module": 2}, {"phase": 4, "name": "Bootstrap", "module": 2}, {"phase": 0, "name": "ExcludePackagesInBootstrap", "module": 2}, {"phase": 0, "name": "IncludePackagesInBootstrap", "module": 2}, {"phase": 4, "name": "MakeTarball", "module": 2}, {"phase": 7, "name": "CleanTMP", "module": 3}, {"phase": 7, "name": "ClearMOTD", "module": 3}, {"phase": 10, "name": "TriggerRollback", "module": 4}, {"phase": 0, "name": "AddRequiredCommands", "module": 5}, {"phase": 0, "name": "AddXFSProgs", "module": 5}, {"phase": 3, "name": "CreateBootMountDir", "module": 5}, {"phase": 3, "name": "CreateMountDir", "module": 5}, {"phase": 8, "name": "DeleteMountDir", "module": 5}, {"phase": 6, "name": "FStab", "module": 5}, {"phase": 2, "name": "Format", "module": 5}, {"phase": 3, "name": "MountBoot", "module": 5}, {"phase": 3, "name": "MountRoot", "module": 5}, {"phase": 4, "name": "MountSpecials", "module": 5}, {"phase": 2, "name": "TuneVolumeFS", "module": 5}, {"phase": 8, "name": "UnmountRoot", "module": 5}, {"phase": 0, "name": "CheckExternalCommands", "module": 6}, {"phase": 6, "name": "AddExpandRoot", "module": 7}, {"phase": 6, "name": "AdjustExpandRootScript", "module": 7}, {"phase": 6, "name": "InstallInitScripts", "module": 7}, {"phase": 6, "name": "RemoveHWClock", "module": 7}, {"phase": 5, "name": "GenerateLocale", "module": 8}, {"phase": 0, "name": "LocaleBootstrapPackage", "module": 8}, {"phase": 6, "name": "SetTimezone", "module": 8}, {"phase": 0, "name": "AddRequiredCommands", "module": 9}, {"phase": 1, "name": "Create", "module": 9}, {"phase": 9, "name": "MoveImage", "module": 9}, {"phase": 6, "name": "ConfigureNetworkIF", "module": 10}, {"phase": 6, "name": "RemoveDNSInfo", "module": 10}, {"phase": 6, "name": "RemoveHostname", "module": 10}, {"phase": 6, "name": "SetHostname", "module": 10}, {"phase": 0, "name": "AddManifestPackages", "module": 11}, {"phase": 5, "name": "AddTaskselStandardPackages", "module": 11}, {"phase": 5, "name": "InstallPackages", "module": 11}, {"phase": 0, "name": "AddRequiredCommands", "module": 12}, {"phase": 2, "name": "MapPartitions", "module": 12}, {"phase": 2, "name": "PartitionVolume", "module": 12}, {"phase": 8, "name": "UnmapPartitions", "module": 12}, {"phase": 6, "name": "EnableShadowConfig", "module": 13}, {"phase": 0, "name": "AddOpenSSHPackage", "module": 14}, {"phase": 6, "name": "AddSSHKeyGeneration", "module": 14}, {"phase": 6, "name": "DisableSSHDNSLookup", "module": 14}, {"phase": 6, "name": "DisableSSHPasswordAuthentication", "module": 14}, {"phase": 7, "name": "ShredHostkeys", "module": 14}, {"phase": 1, "name": "Attach", "module": 15}, {"phase": 10, "name": "Delete", "module": 15}, {"phase": 8, "name": "Detach", "module": 15}, {"phase": 0, "name": "CreateWorkspace", "module": 16}, {"phase": 10, "name": "DeleteWorkspace", "module": 16}, {"phase": 0, "name": "AddSudoPackage", "module": 17}, {"phase": 6, "name": "AdminUserCredentials", "module": 17}, {"phase": 6, "name": "CreateAdminUser", "module": 17}, {"phase": 6, "name": "DisableRootLogin", "module": 17}, {"phase": 6, "name": "PasswordlessSudo", "module": 17}, {"phase": 7, "name": "RemoveAptProxy", "module": 18}, {"phase": 5, "name": "SetAptProxy", "module": 18}, {"phase": 0, "name": "AddPackages", "module": 19}, {"phase": 0, "name": "CheckAssetsPath", "module": 19}, {"phase": 6, "name": "CopyChefAssets", "module": 19}, {"phase": 0, "name": "AddBackports", "module": 20}, {"phase": 0, "name": "AddCloudInitPackages", "module": 20}, {"phase": 6, "name": "DisableModules", "module": 20}, {"phase": 5, "name": "SetMetadataSource", "module": 20}, {"phase": 6, "name": "SetUsername", "module": 20}, {"phase": 6, "name": "ImageExecuteCommand", "module": 21}, {"phase": 4, "name": "AddFolderMounts", "module": 22}, {"phase": 0, "name": "AddRequiredCommands", "module": 22}, {"phase": 7, "name": "RemoveFolderMounts", "module": 22}, {"phase": 8, "name": "ShrinkVolume", "module": 22}, {"phase": 8, "name": "Zerofree", "module": 22}, {"phase": 5, "name": "AddNtpPackage", "module": 23}, {"phase": 6, "name": "SetNtpServers", "module": 23}, {"phase": 0, "name": "AddBackports", "module": 24}, {"phase": 0, "name": "AddONEContextPackage", "module": 24}, {"phase": 5, "name": "CopyImage", "module": 25}, {"phase": 1, "name": "CreateFromImage", "module": 25}, {"phase": 1, "name": "CreateFromSnapshot", "module": 25}, {"phase": 5, "name": "Snapshot", "module": 25}, {"phase": 0, "name": "AddPackages", "module": 26}, {"phase": 6, "name": "ApplyPuppetManifest", "module": 26}, {"phase": 0, "name": "CheckAssetsPath", "module": 26}, {"phase": 0, "name": "CheckManifestPath", "module": 26}, {"phase": 6, "name": "CopyPuppetAssets", "module": 26}, {"phase": 6, "name": "EnableAgent", "module": 26}, {"phase": 6, "name": "SetRootPassword", "module": 27}, {"phase": 5, "name": "BootstrapSaltMinion", "module": 28}, {"phase": 5, "name": "InstallSaltDependencies", "module": 28}, {"phase": 6, "name": "SetSaltGrains", "module": 28}, {"phase": 0, "name": "AddUnattendedUpgradesPackage", "module": 29}, {"phase": 6, "name": "EnablePeriodicUpgrades", "module": 29}, {"phase": 6, "name": "AddInsecurePublicKey", "module": 30}, {"phase": 0, "name": "AddPackages", "module": 30}, {"phase": 0, "name": "CheckBoxPath", "module": 30}, {"phase": 0, "name": "CreateVagrantBoxDir", "module": 30}, {"phase": 6, "name": "CreateVagrantUser", "module": 30}, {"phase": 9, "name": "PackageBox", "module": 30}, {"phase": 6, "name": "PasswordlessSudo", "module": 30}, {"phase": 10, "name": "RemoveVagrantBoxDir", "module": 30}, {"phase": 6, "name": "SetRootPassword", "module": 30}, {"phase": 6, "name": "ConfigureGrub", "module": 31}, {"phase": 9, "name": "ConvertToVhd", "module": 32}, {"phase": 0, "name": "DefaultPackages", "module": 33}, {"phase": 5, "name": "Waagent", "module": 33}, {"phase": 0, "name": "AMIName", "module": 34}, {"phase": 9, "name": "BundleImage", "module": 34}, {"phase": 9, "name": "RegisterAMI", "module": 34}, {"phase": 10, "name": "RemoveBundle", "module": 34}, {"phase": 9, "name": "UploadImage", "module": 34}, {"phase": 6, "name": "ConfigurePVGrub", "module": 35}, {"phase": 0, "name": "Connect", "module": 36}, {"phase": 0, "name": "GetCredentials", "module": 36}, {"phase": 1, "name": "Attach", "module": 37}, {"phase": 1, "name": "Create", "module": 37}, {"phase": 9, "name": "Snapshot", "module": 37}, {"phase": 6, "name": "S3FStab", "module": 38}, {"phase": 0, "name": "AddExternalCommands", "module": 39}, {"phase": 0, "name": "GetInstanceMetadata", "module": 39}, {"phase": 0, "name": "SetRegion", "module": 39}, {"phase": 6, "name": "AddEC2InitScripts", "module": 40}, {"phase": 0, "name": "AddBuildEssentialPackage", "module": 41}, {"phase": 6, "name": "EnableDHCPCDDNS", "module": 41}, {"phase": 5, "name": "InstallEnhancedNetworking", "module": 41}, {"phase": 0, "name": "DefaultPackages", "module": 42}, {"phase": 7, "name": "CleanGoogleRepositoriesAndKeys", "module": 43}, {"phase": 5, "name": "ImportGoogleKey", "module": 43}, {"phase": 0, "name": "SetPackageRepositories", "module": 43}, {"phase": 6, "name": "ConfigureGrub", "module": 44}, {"phase": 6, "name": "GatherReleaseInformation", "module": 45}, {"phase": 6, "name": "DisableIPv6", "module": 46}, {"phase": 6, "name": "SetHostname", "module": 46}, {"phase": 9, "name": "CreateTarball", "module": 47}, {"phase": 9, "name": "RegisterImage", "module": 47}, {"phase": 9, "name": "UploadImage", "module": 47}, {"phase": 0, "name": "DefaultPackages", "module": 48}, {"phase": 0, "name": "GooglePackages", "module": 48}, {"phase": 5, "name": "InstallGSUtil", "module": 48}, {"phase": 0, "name": "DefaultPackages", "module": 49}, {"phase": 6, "name": "VirtIO", "module": 50}, {"phase": 5, "name": "AddGuestAdditionsPackages", "module": 51}, {"phase": 0, "name": "CheckGuestAdditionsPath", "module": 51}, {"phase": 5, "name": "InstallGuestAdditions", "module": 51}, {"phase": 0, "name": "DefaultPackages", "module": 52}], "links": [{"source": 19, "target": 39, "definer": 19}, {"source": 21, "target": 20, "definer": 21}, {"source": 22, "target": 20, "definer": 22}, {"source": 27, "target": 39, "definer": 27}, {"source": 38, "target": 69, "definer": 38}, {"source": 40, "target": 42, "definer": 40}, {"source": 43, "target": 42, "definer": 43}, {"source": 47, "target": 39, "definer": 47}, {"source": 48, "target": 67, "definer": 48}, {"source": 55, "target": 56, "definer": 55}, {"source": 57, "target": 39, "definer": 57}, {"source": 58, "target": 33, "definer": 58}, {"source": 60, "target": 69, "definer": 60}, {"source": 63, "target": 42, "definer": 63}, {"source": 68, "target": 71, "definer": 68}, {"source": 78, "target": 4, "definer": 78}, {"source": 85, "target": 4, "definer": 85}, {"source": 89, "target": 39, "definer": 89}, {"source": 90, "target": 3, "definer": 90}, {"source": 92, "target": 60, "definer": 92}, {"source": 92, "target": 69, "definer": 92}, {"source": 93, "target": 56, "definer": 93}, {"source": 98, "target": 67, "definer": 98}, {"source": 99, "target": 134, "definer": 99}, {"source": 102, "target": 52, "definer": 102}, {"source": 102, "target": 51, "definer": 102}, {"source": 120, "target": 71, "definer": 120}, {"source": 122, "target": 18, "definer": 122}, {"source": 129, "target": 71, "definer": 129}, {"source": 138, "target": 39, "definer": 138}, {"source": 141, "target": 42, "definer": 141}, {"source": 146, "target": 3, "definer": 146}, {"source": 147, "target": 11, "definer": 147}, {"source": 148, "target": 2, "definer": 148}, {"source": 149, "target": 18, "definer": 149}, {"source": 161, "target": 56, "definer": 161}, {"source": 2, "target": 0, "definer": 0}, {"source": 44, "target": 4, "definer": 4}, {"source": 11, "target": 4, "definer": 4}, {"source": 4, "target": 5, "definer": 5}, {"source": 6, "target": 5, "definer": 5}, {"source": 11, "target": 10, "definer": 10}, {"source": 8, "target": 11, "definer": 11}, {"source": 0, "target": 12, "definer": 12}, {"source": 0, "target": 13, "definer": 13}, {"source": 32, "target": 15, "definer": 15}, {"source": 32, "target": 17, "definer": 17}, {"source": 32, "target": 18, "definer": 18}, {"source": 23, "target": 20, "definer": 20}, {"source": 0, "target": 28, "definer": 28}, {"source": 35, "target": 29, "definer": 29}, {"source": 38, "target": 31, "definer": 31}, {"source": 29, "target": 34, "definer": 34}, {"source": 30, "target": 35, "definer": 35}, {"source": 20, "target": 36, "definer": 36}, {"source": 33, "target": 37, "definer": 37}, {"source": 42, "target": 41, "definer": 41}, {"source": 0, "target": 54, "definer": 54}, {"source": 4, "target": 55, "definer": 55}, {"source": 5, "target": 56, "definer": 56}, {"source": 59, "target": 58, "definer": 58}, {"source": 38, "target": 60, "definer": 60}, {"source": 0, "target": 62, "definer": 62}, {"source": 0, "target": 72, "definer": 72}, {"source": 42, "target": 73, "definer": 73}, {"source": 0, "target": 79, "definer": 79}, {"source": 0, "target": 83, "definer": 83}, {"source": 82, "target": 83, "definer": 83}, {"source": 44, "target": 85, "definer": 85}, {"source": 20, "target": 88, "definer": 88}, {"source": 69, "target": 91, "definer": 91}, {"source": 38, "target": 92, "definer": 92}, {"source": 0, "target": 96, "definer": 96}, {"source": 95, "target": 96, "definer": 96}, {"source": 56, "target": 97, "definer": 97}, {"source": 163, "target": 97, "definer": 97}, {"source": 56, "target": 100, "definer": 100}, {"source": 163, "target": 100, "definer": 100}, {"source": 0, "target": 101, "definer": 101}, {"source": 105, "target": 102, "definer": 102}, {"source": 56, "target": 108, "definer": 108}, {"source": 0, "target": 109, "definer": 109}, {"source": 0, "target": 111, "definer": 111}, {"source": 117, "target": 113, "definer": 113}, {"source": 0, "target": 114, "definer": 114}, {"source": 70, "target": 116, "definer": 116}, {"source": 115, "target": 116, "definer": 116}, {"source": 0, "target": 124, "definer": 124}, {"source": 56, "target": 125, "definer": 125}, {"source": 132, "target": 126, "definer": 126}, {"source": 136, "target": 128, "definer": 128}, {"source": 130, "target": 128, "definer": 128}, {"source": 127, "target": 130, "definer": 130}, {"source": 133, "target": 132, "definer": 132}, {"source": 139, "target": 132, "definer": 132}, {"source": 140, "target": 132, "definer": 132}, {"source": 135, "target": 134, "definer": 134}, {"source": 0, "target": 142, "definer": 142}, {"source": 0, "target": 145, "definer": 145}, {"source": 8, "target": 147, "definer": 147}, {"source": 50, "target": 151, "definer": 151}, {"source": 49, "target": 153, "definer": 153}, {"source": 155, "target": 154, "definer": 154}, {"source": 153, "target": 155, "definer": 155}, {"source": 0, "target": 156, "definer": 156}, {"source": 156, "target": 157, "definer": 157}, {"source": 0, "target": 159, "definer": 159}, {"source": 56, "target": 163, "definer": 163}, {"source": 0, "target": 164, "definer": 164}]} \ No newline at end of file diff --git a/taskoverview.py b/taskoverview.py new file mode 100755 index 0000000..f9811b1 --- /dev/null +++ b/taskoverview.py @@ -0,0 +1,76 @@ +#!/usr/bin/python + + +def main(opts): + from bootstrapvz.base.tasklist import get_all_tasks + tasks = get_all_tasks() + + def distinct(seq): + seen = set() + return [x for x in seq if x not in seen and not seen.add(x)] + modules = distinct([task.__module__ for task in tasks]) + task_links = [] + task_links.extend([{'source': task, + 'target': succ, + 'definer': task, + } + for task in tasks + for succ in task.successors]) + task_links.extend([{'source': pre, + 'target': task, + 'definer': task, + } + for task in tasks + for pre in task.predecessors]) + + def mk_phase(phase): + return {'name': phase.name, + 'description': phase.description, + } + + def mk_module(module): + return {'name': module, + } + + from bootstrapvz.common import phases + + def mk_node(task): + return {'name': task.__name__, + 'module': modules.index(task.__module__), + 'phase': (i for i, phase in enumerate(phases.order) if phase is task.phase).next(), + } + + def mk_link(link): + for key in ['source', 'target', 'definer']: + link[key] = tasks.index(link[key]) + return link + + data = {'phases': map(mk_phase, phases.order), + 'modules': map(mk_module, modules), + 'nodes': map(mk_node, tasks), + 'links': map(mk_link, task_links)} + + write_data(data, opts.get('--output', None)) + + +def write_data(data, output_path=None): + import json + if output_path is None: + import sys + json.dump(data, sys.stdout, indent=4, separators=(',', ': ')) + else: + with open(output_path, 'w') as output: + json.dump(data, output) + + +if __name__ == '__main__' and __package__ is None: + from docopt import docopt + usage = """Usage: taskoverview.py [options] + + Options: + --output output + -h, --help show this help +""" + opts = docopt(usage) + + main(opts)