From 335221d79e9b7a219ba0ed25dedb235e18e12d14 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 3 Oct 2014 14:53:28 -0400 Subject: [PATCH] This is an exceedingly rough sketch of what attributes might look like - metaclass implementations NOT complete. --- Makefile | 2 +- lib/ansible/modules/core | 2 +- test/v2/playbook/__init__.py | 2 + test/v2/playbook/task.py | 37 +++++++++ v2/ansible/playbook/__init__.py | 2 - v2/ansible/playbook/base.py | 18 ++++- v2/ansible/playbook/task.py | 128 ++++++++++++++++++-------------- v2/ansible/runner/__init__.py | 4 +- 8 files changed, 130 insertions(+), 65 deletions(-) create mode 100644 test/v2/playbook/__init__.py create mode 100644 test/v2/playbook/task.py diff --git a/Makefile b/Makefile index e228c1e9f65..7702f5162da 100644 --- a/Makefile +++ b/Makefile @@ -94,7 +94,7 @@ tests: PYTHONPATH=./lib $(NOSETESTS) -d -w test/units -v newtests: - PYTHONPATH=./v2/ $(NOSETESTS) -d -w v2/tests -v + PYTHONPATH=./v2 $(NOSETESTS) -d -w test/v2 -v authors: diff --git a/lib/ansible/modules/core b/lib/ansible/modules/core index cb69744bcee..f624689bad2 160000 --- a/lib/ansible/modules/core +++ b/lib/ansible/modules/core @@ -1 +1 @@ -Subproject commit cb69744bcee4b4217d83b4a30006635ba69e2aa0 +Subproject commit f624689bad24cb3a7b2ef43d5280b5f4fbabb5bd diff --git a/test/v2/playbook/__init__.py b/test/v2/playbook/__init__.py new file mode 100644 index 00000000000..ec86ee61015 --- /dev/null +++ b/test/v2/playbook/__init__.py @@ -0,0 +1,2 @@ +# TODO: header + diff --git a/test/v2/playbook/task.py b/test/v2/playbook/task.py new file mode 100644 index 00000000000..9f65a35cc5d --- /dev/null +++ b/test/v2/playbook/task.py @@ -0,0 +1,37 @@ +# TODO: header + +from ansible.playbook.task import Task +import unittest + +basic_shell_task = dict( + name = 'Test Task', + shell = 'echo hi' +) + +class TestTask(unittest.TestCase): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_can_construct_empty_task(): + t = Task() + + def test_can_construct_task_with_role(): + pass + + def test_can_construct_task_with_block(): + pass + + def test_can_construct_task_with_role_and_block(): + pass + + def test_can_load_simple_task(): + t = Task.load(basic_shell_task) + assert t.name == basic_shell_task['name'] + assert t.module == 'shell' + assert t.args == 'echo hi' + + diff --git a/v2/ansible/playbook/__init__.py b/v2/ansible/playbook/__init__.py index 6b9cfa6ce69..d2430dfc0cb 100644 --- a/v2/ansible/playbook/__init__.py +++ b/v2/ansible/playbook/__init__.py @@ -15,8 +15,6 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . -import ansible.utils - class Playbook(object): def __init__(self, filename): self.ds = v2.utils.load_yaml_from_file(filename) diff --git a/v2/ansible/playbook/base.py b/v2/ansible/playbook/base.py index a16e22f15f7..9c2ad358c44 100644 --- a/v2/ansible/playbook/base.py +++ b/v2/ansible/playbook/base.py @@ -15,11 +15,23 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . -from errors import AnsibleError -from playbook.tag import Tag +#from ansible.cmmon.errors import AnsibleError +#from playbook.tag import Tag class Base(object): - def __init__(self): + def __init__(self, attribute): pass + def add_attribute(self): + self.attributes.push(attribute) + + def load(self, data): + for attribute in self.attributes: + attribute.load(data) + + def validate(self): + for attribute in self.attributes: + attribute.validate(self) + + diff --git a/v2/ansible/playbook/task.py b/v2/ansible/playbook/task.py index 9600d85b18e..4767c95106e 100644 --- a/v2/ansible/playbook/task.py +++ b/v2/ansible/playbook/task.py @@ -15,10 +15,11 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . -from playbook.base import Base -from playbook.conditional import Conditional -from errors import AnsibleError -from ansible import utils +from ansible.playbook.base import Base +from ansible.playbook.attribute import Attribute, FieldAttribute +from ansible.playbook.conditional import Conditional +#from ansible.common.errors import AnsibleError +#from ansible import utils # TODO: it would be fantastic (if possible) if a task new where in the YAML it was defined for describing # it in error conditions @@ -36,81 +37,94 @@ class Task(Base): """ # ================================================================================= - # KEYS AND SLOTS: defines what variables in are valid in the data structure and - # the object itself - - VALID_KEYS = [ - 'always_run', 'any_errors_fatal', 'async', 'connection', 'delay', 'delegate_to', 'environment', - 'first_available_file', 'ignore_errors', 'include', 'local_action', 'meta', 'name', 'no_log', - 'notify', 'poll', 'register', 'remote_user', 'retries', 'run_once', 'su', 'su_pass', 'su_user', - 'sudo', 'sudo_pass', 'sudo_user', 'transport', 'until' - ] - - __slots__ = [ - '_always_run', '_any_errors_fatal', '_async', '_connection', '_delay', '_delegate_to', '_environment', - '_first_available_file', '_ignore_errors', '_include', '_local_action', '_meta', '_name', '_no_log', - '_notify', '_poll', '_register', '_remote_user', '_retries', '_run_once', '_su', '_su_pass', '_su_user', - '_sudo', '_sudo_pass', '_sudo_user', '_transport', '_until' - ] + # ATTRIBUTES + # load_ and + # validate_ + # will be used if defined + # might be possible to define others + + always_run = FieldAttribute(isa='bool') + any_errors_fatal = FieldAttribute(isa='bool') + async = FieldAttribute(isa='int') + connection = FieldAttribute(isa='string') + delay = FieldAttribute(isa='int') + delegate_to = FieldAttribute(isa='string') + environment = FieldAttribute(isa='dict') + first_available_file = FieldAttribute(isa='list') + ignore_errors = FieldAttribute(isa='bool') + + # FIXME: this should not be a Task + # include = FieldAttribute(isa='string') + + local_action = FieldAttribute(isa='string', alias='action', post_validate='_set_local_action') + + # FIXME: this should not be a Task + meta = FieldAttribute(isa='string') + + name = FieldAttribute(isa='string', post_validate='_set_name') + no_log = FieldAttribute(isa='bool') + notify = FieldAttribute(isa='list') + poll = FieldAttribute(isa='integer') + register = FieldAttribute(isa='string') + remote_user = FieldAttribute(isa='string') + retries = FieldAttribute(isa='integer') + run_once = FieldAttribute(isa='bool') + su = FieldAttribute(isa='bool') + su_pass = FieldAttribute(isa='string') + su_user = FieldAttribute(isa='string') + sudo = FieldAttribute(isa='bool') + sudo_user = FieldAttribute(isa='string') + sudo_pass = FieldAttribute(isa='string') + transport = FieldAttribute(isa='string') + until = FieldAttribute(isa='list') # ? + + role = Attribute() + block = Attribute() # ================================================================================== def __init__(self, block=None, role=None): ''' constructors a task, without the Task.load classmethod, it will be pretty blank ''' - self._block = block - self._role = role - self._reset() + self.block = block + self.role = role super(Task, self).__init__() - # TODO: move to BaseObject - def _reset(self): - ''' clear out the object ''' - - for x in __slots__: - setattr(x, None) - # ================================================================================== # BASIC ACCESSORS def get_name(self): ''' return the name of the task ''' if self._role: - return "%s : %s" % (self._role.get_name(), self._name) - else: - return self._name + return "%s : %s" % (self._role.get_name(), self._name) + else: + return self._name def __repr__(self): ''' returns a human readable representation of the task ''' return "TASK: %s" % self.get_name() - # FIXME: does a task have variables? - def get_vars(self): - ''' return the variables associated with the task ''' - raise exception.NotImplementedError() - - def get_role(self): - ''' return the role associated with the task ''' - return self._role + @classmethod + def load(self, block=None, role=None, data=None): + self = Task(block=block, role=role) + self._load_field_attributes(data) # from BaseObject + self._load_plugin_attributes(data) # from here, becuase of lookupPlugins + return self - def get_block(self): - ''' return the block the task is in ''' - return self._block + def _load_plugin_attributes(self, data): + module_names = self._module_names() + for (k,v) in data.iteritems(): + if k in module_names: + self.module = k + self.args = v # ================================================================================== - # LOAD: functions related to walking the datastructure and storing data + # BELOW THIS LINE + # info below this line is "old" and is before the attempt to build Attributes + # use as reference but plan to replace and radically simplify + # ================================================================================== - def _load_parameters(data): - ''' validate/transmogrify/assign any module parameters for this task ''' - - if isinstance(data, dict): - return dict(_parameters=data) - elif isinstance(data, basestring): - return dict(_parameters=utils.parse_kv(data)) - elif isinstance(data, None): - return dict(_parameters='') - else: - raise AnsibleError("invalid arguments specified, got '%s' (type=%s')" % (data, type(data))) +LEGACY = """ def _load_action(self, ds, k, v): ''' validate/transmogrify/assign the module and parameters if used in 'action/local_action' format ''' @@ -301,3 +315,5 @@ class Task(Base): if self._first_available_file and self._lookup_plugin: raise AnsibleError("with_(plugin), and first_available_file are mutually incompatible in a single task") +""" + diff --git a/v2/ansible/runner/__init__.py b/v2/ansible/runner/__init__.py index ebebb9cfc01..b8cc0a9219a 100644 --- a/v2/ansible/runner/__init__.py +++ b/v2/ansible/runner/__init__.py @@ -15,8 +15,8 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . -from v2.inventory import Host -from v2.playbook import Task +#from v2.inventory import Host +#from v2.playbook import Task class Runner(object): pass