diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py index b4d7cd4db1..d20b750f7e 100644 --- a/lib/ansible/module_utils/cloudstack.py +++ b/lib/ansible/module_utils/cloudstack.py @@ -34,6 +34,18 @@ try: except ImportError: has_lib_cs = False +def cs_argument_spec(): + return dict( + api_key = dict(default=None), + api_secret = dict(default=None, no_log=True), + api_url = dict(default=None), + api_http_method = dict(choices=['get', 'post'], default='get'), + api_timeout = dict(type='int', default=10), + api_region = dict(default='cloudstack'), + ) + +def cs_required_together(): + return [['api_key', 'api_secret', 'api_url']] class AnsibleCloudStack(object): @@ -63,6 +75,8 @@ class AnsibleCloudStack(object): # Init returns dict for use in subclasses self.returns = {} + # these values will be casted to int + self.returns_to_int = {} self.module = module self._connect() @@ -76,6 +90,7 @@ class AnsibleCloudStack(object): self.os_type = None self.hypervisor = None self.capabilities = None + self.tags = None def _connect(self): @@ -305,47 +320,44 @@ class AnsibleCloudStack(object): def get_tags(self, resource=None): - existing_tags = self.cs.listTags(resourceid=resource['id']) - if existing_tags: - return existing_tags['tag'] - return [] + if not self.tags: + args = {} + args['projectid'] = self.get_project(key='id') + args['account'] = self.get_account(key='name') + args['domainid'] = self.get_domain(key='id') + args['resourceid'] = resource['id'] + response = self.cs.listTags(**args) + self.tags = response.get('tag', []) + + existing_tags = [] + if self.tags: + for tag in self.tags: + existing_tags.append({'key': tag['key'], 'value': tag['value']}) + return existing_tags - def _delete_tags(self, resource, resource_type, tags): - existing_tags = resource['tags'] - tags_to_delete = [] - for existing_tag in existing_tags: - if existing_tag['key'] in tags: - if existing_tag['value'] != tags[key]: - tags_to_delete.append(existing_tag) - else: - tags_to_delete.append(existing_tag) - if tags_to_delete: + def _process_tags(self, resource, resource_type, tags, operation="create"): + if tags: self.result['changed'] = True if not self.module.check_mode: args = {} args['resourceids'] = resource['id'] args['resourcetype'] = resource_type - args['tags'] = tags_to_delete - self.cs.deleteTags(**args) + args['tags'] = tags + if operation == "create": + self.cs.createTags(**args) + else: + self.cs.deleteTags(**args) - def _create_tags(self, resource, resource_type, tags): - tags_to_create = [] - for i, tag_entry in enumerate(tags): - tag = { - 'key': tag_entry['key'], - 'value': tag_entry['value'], - } - tags_to_create.append(tag) - if tags_to_create: - self.result['changed'] = True - if not self.module.check_mode: - args = {} - args['resourceids'] = resource['id'] - args['resourcetype'] = resource_type - args['tags'] = tags_to_create - self.cs.createTags(**args) + def _tags_that_should_exist_or_be_updated(self, resource, tags): + existing_tags = self.get_tags(resource) + return [tag for tag in tags if tag not in existing_tags] + + + def _tags_that_should_not_exist(self, resource, tags): + existing_tags = self.get_tags(resource) + return [tag for tag in existing_tags if tag not in tags] def ensure_tags(self, resource, resource_type=None): @@ -355,8 +367,9 @@ class AnsibleCloudStack(object): if 'tags' in resource: tags = self.module.params.get('tags') if tags is not None: - self._delete_tags(resource, resource_type, tags) - self._create_tags(resource, resource_type, tags) + self._process_tags(resource, resource_type, self._tags_that_should_exist_or_be_updated(resource, tags)) + self._process_tags(resource, resource_type, self._tags_that_should_not_exist(resource, tags), operation="delete") + self.tags = None resource['tags'] = self.get_tags(resource) return resource @@ -396,6 +409,11 @@ class AnsibleCloudStack(object): if search_key in resource: self.result[return_key] = resource[search_key] + # Bad bad API does not always return int when it should. + for search_key, return_key in self.returns_to_int.iteritems(): + if search_key in resource: + self.result[return_key] = int(resource[search_key]) + # Special handling for tags if 'tags' in resource: self.result['tags'] = [] diff --git a/test/integration/roles/test_cs_account/tasks/main.yml b/test/integration/roles/test_cs_account/tasks/main.yml index af7a54f52b..0061c66639 100644 --- a/test/integration/roles/test_cs_account/tasks/main.yml +++ b/test/integration/roles/test_cs_account/tasks/main.yml @@ -224,3 +224,117 @@ that: - acc|success - not acc|changed + +- name: test create user disabled account + cs_account: + name: "{{ cs_resource_prefix }}_user" + username: "{{ cs_resource_prefix }}_username" + password: "{{ cs_resource_prefix }}_password" + last_name: "{{ cs_resource_prefix }}_last_name" + first_name: "{{ cs_resource_prefix }}_first_name" + email: "{{ cs_resource_prefix }}@example.com" + network_domain: "{{ cs_resource_prefix }}.local" + state: disabled + register: acc +- name: verify results of create disabled account + assert: + that: + - acc|success + - acc|changed + - acc.name == "{{ cs_resource_prefix }}_user" + - acc.network_domain == "{{ cs_resource_prefix }}.local" + - acc.account_type == "user" + - acc.state == "disabled" + - acc.domain == "ROOT" + +- name: test remove disabled user account + cs_account: + name: "{{ cs_resource_prefix }}_user" + state: absent + register: acc +- name: verify results of remove disabled user account + assert: + that: + - acc|success + - acc|changed + - acc.name == "{{ cs_resource_prefix }}_user" + - acc.network_domain == "{{ cs_resource_prefix }}.local" + - acc.account_type == "user" + - acc.state == "disabled" + - acc.domain == "ROOT" + +- name: test create user locked account + cs_account: + name: "{{ cs_resource_prefix }}_user" + username: "{{ cs_resource_prefix }}_username" + password: "{{ cs_resource_prefix }}_password" + last_name: "{{ cs_resource_prefix }}_last_name" + first_name: "{{ cs_resource_prefix }}_first_name" + email: "{{ cs_resource_prefix }}@example.com" + network_domain: "{{ cs_resource_prefix }}.local" + state: locked + register: acc +- name: verify results of create locked account + assert: + that: + - acc|success + - acc|changed + - acc.name == "{{ cs_resource_prefix }}_user" + - acc.network_domain == "{{ cs_resource_prefix }}.local" + - acc.account_type == "user" + - acc.state == "locked" + - acc.domain == "ROOT" + +- name: test remove locked user account + cs_account: + name: "{{ cs_resource_prefix }}_user" + state: absent + register: acc +- name: verify results of remove locked user account + assert: + that: + - acc|success + - acc|changed + - acc.name == "{{ cs_resource_prefix }}_user" + - acc.network_domain == "{{ cs_resource_prefix }}.local" + - acc.account_type == "user" + - acc.state == "locked" + - acc.domain == "ROOT" + +- name: test create user unlocked/enabled account + cs_account: + name: "{{ cs_resource_prefix }}_user" + username: "{{ cs_resource_prefix }}_username" + password: "{{ cs_resource_prefix }}_password" + last_name: "{{ cs_resource_prefix }}_last_name" + first_name: "{{ cs_resource_prefix }}_first_name" + email: "{{ cs_resource_prefix }}@example.com" + network_domain: "{{ cs_resource_prefix }}.local" + state: unlocked + register: acc +- name: verify results of create unlocked/enabled account + assert: + that: + - acc|success + - acc|changed + - acc.name == "{{ cs_resource_prefix }}_user" + - acc.network_domain == "{{ cs_resource_prefix }}.local" + - acc.account_type == "user" + - acc.state == "enabled" + - acc.domain == "ROOT" + +- name: test remove unlocked/enabled user account + cs_account: + name: "{{ cs_resource_prefix }}_user" + state: absent + register: acc +- name: verify results of remove unlocked/enabled user account + assert: + that: + - acc|success + - acc|changed + - acc.name == "{{ cs_resource_prefix }}_user" + - acc.network_domain == "{{ cs_resource_prefix }}.local" + - acc.account_type == "user" + - acc.state == "enabled" + - acc.domain == "ROOT" diff --git a/test/integration/roles/test_cs_instance/tasks/main.yml b/test/integration/roles/test_cs_instance/tasks/main.yml index a457a3c710..d1a67e1781 100644 --- a/test/integration/roles/test_cs_instance/tasks/main.yml +++ b/test/integration/roles/test_cs_instance/tasks/main.yml @@ -1,6 +1,6 @@ --- - include: setup.yml - include: present.yml -#- include: tags.yml +- include: tags.yml - include: absent.yml - include: cleanup.yml diff --git a/test/integration/roles/test_cs_instance/tasks/tags.yml b/test/integration/roles/test_cs_instance/tasks/tags.yml index a86158df0f..50836384b2 100644 --- a/test/integration/roles/test_cs_instance/tasks/tags.yml +++ b/test/integration/roles/test_cs_instance/tasks/tags.yml @@ -2,6 +2,8 @@ - name: test add tags to instance cs_instance: name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + template: "{{ test_cs_instance_template }}" + service_offering: "{{ test_cs_instance_offering_1 }}" tags: - { key: "{{ cs_resource_prefix }}-tag1", value: "{{ cs_resource_prefix }}-value1" } - { key: "{{ cs_resource_prefix }}-tag2", value: "{{ cs_resource_prefix }}-value2" } @@ -12,11 +14,10 @@ - instance|success - instance|changed - instance.tags|length == 2 - - instance.tags[0]['key'] == "{{ cs_resource_prefix }}-tag1" - - instance.tags[1]['key'] == "{{ cs_resource_prefix }}-tag2" - - instance.tags[0]['value'] == "{{ cs_resource_prefix }}-value1" - - instance.tags[1]['value'] == "{{ cs_resource_prefix }}-value2" - + - "instance.tags[0]['key'] in [ '{{ cs_resource_prefix }}-tag2', '{{ cs_resource_prefix }}-tag1' ]" + - "instance.tags[1]['key'] in [ '{{ cs_resource_prefix }}-tag2', '{{ cs_resource_prefix }}-tag1' ]" + - "instance.tags[0]['value'] in [ '{{ cs_resource_prefix }}-value2', '{{ cs_resource_prefix }}-value1' ]" + - "instance.tags[1]['value'] in [ '{{ cs_resource_prefix }}-value2', '{{ cs_resource_prefix }}-value1' ]" - name: test tags to instance idempotence cs_instance: @@ -31,10 +32,10 @@ - instance|success - not instance|changed - instance.tags|length == 2 - - instance.tags[0]['key'] == "{{ cs_resource_prefix }}-tag1" - - instance.tags[1]['key'] == "{{ cs_resource_prefix }}-tag2" - - instance.tags[0]['value'] == "{{ cs_resource_prefix }}-value1" - - instance.tags[1]['value'] == "{{ cs_resource_prefix }}-value2" + - "instance.tags[0]['key'] in [ '{{ cs_resource_prefix }}-tag2', '{{ cs_resource_prefix }}-tag1' ]" + - "instance.tags[1]['key'] in [ '{{ cs_resource_prefix }}-tag2', '{{ cs_resource_prefix }}-tag1' ]" + - "instance.tags[0]['value'] in [ '{{ cs_resource_prefix }}-value2', '{{ cs_resource_prefix }}-value1' ]" + - "instance.tags[1]['value'] in [ '{{ cs_resource_prefix }}-value2', '{{ cs_resource_prefix }}-value1' ]" - name: test change tags of instance cs_instance: @@ -47,12 +48,12 @@ assert: that: - instance|success - - not instance|changed + - instance|changed - instance.tags|length == 2 - - instance.tags[0]['key'] == "{{ cs_resource_prefix }}-tag1" - - instance.tags[1]['key'] == "{{ cs_resource_prefix }}-tag3" - - instance.tags[0]['value'] == "{{ cs_resource_prefix }}-value1" - - instance.tags[1]['value'] == "{{ cs_resource_prefix }}-value3" + - "instance.tags[0]['key'] in [ '{{ cs_resource_prefix }}-tag2', '{{ cs_resource_prefix }}-tag3' ]" + - "instance.tags[1]['key'] in [ '{{ cs_resource_prefix }}-tag2', '{{ cs_resource_prefix }}-tag3' ]" + - "instance.tags[0]['value'] in [ '{{ cs_resource_prefix }}-value2', '{{ cs_resource_prefix }}-value3' ]" + - "instance.tags[1]['value'] in [ '{{ cs_resource_prefix }}-value2', '{{ cs_resource_prefix }}-value3' ]" - name: test not touch tags of instance if no param tags cs_instance: @@ -64,10 +65,10 @@ - instance|success - not instance|changed - instance.tags|length == 2 - - instance.tags[0]['key'] == "{{ cs_resource_prefix }}-tag1" - - instance.tags[1]['key'] == "{{ cs_resource_prefix }}-tag3" - - instance.tags[0]['value'] == "{{ cs_resource_prefix }}-value1" - - instance.tags[1]['value'] == "{{ cs_resource_prefix }}-value3" + - "instance.tags[0]['key'] in [ '{{ cs_resource_prefix }}-tag2', '{{ cs_resource_prefix }}-tag3' ]" + - "instance.tags[1]['key'] in [ '{{ cs_resource_prefix }}-tag2', '{{ cs_resource_prefix }}-tag3' ]" + - "instance.tags[0]['value'] in [ '{{ cs_resource_prefix }}-value2', '{{ cs_resource_prefix }}-value3' ]" + - "instance.tags[1]['value'] in [ '{{ cs_resource_prefix }}-value2', '{{ cs_resource_prefix }}-value3' ]" - name: test remove tags cs_instance: @@ -78,5 +79,5 @@ assert: that: - instance|success - - not instance|changed + - instance|changed - instance.tags|length == 0 diff --git a/test/integration/roles/test_cs_user/tasks/main.yml b/test/integration/roles/test_cs_user/tasks/main.yml index c5aaed8d58..e88612bc21 100644 --- a/test/integration/roles/test_cs_user/tasks/main.yml +++ b/test/integration/roles/test_cs_user/tasks/main.yml @@ -232,10 +232,10 @@ - user.state == "enabled" - user.domain == "ROOT" -- name: test enable user idempotence +- name: test enable user idempotence using unlocked cs_user: username: "{{ cs_resource_prefix }}_user" - state: enabled + state: unlocked register: user - name: verify results of enable user idempotence assert: @@ -274,3 +274,123 @@ that: - user|success - not user|changed + +- name: test create locked user + cs_user: + username: "{{ cs_resource_prefix }}_user" + password: "{{ cs_resource_prefix }}_password" + last_name: "{{ cs_resource_prefix }}_last_name" + first_name: "{{ cs_resource_prefix }}_first_name" + email: "{{ cs_resource_prefix }}@example.com" + account: "admin" + state: locked + register: user +- name: verify results of create locked user + assert: + that: + - user|success + - user|changed + - user.username == "{{ cs_resource_prefix }}_user" + - user.first_name == "{{ cs_resource_prefix }}_first_name" + - user.last_name == "{{ cs_resource_prefix }}_last_name" + - user.email == "{{ cs_resource_prefix }}@example.com" + - user.account_type == "root_admin" + - user.account == "admin" + - user.state == "locked" + - user.domain == "ROOT" + +- name: test remove locked user + cs_user: + username: "{{ cs_resource_prefix }}_user" + state: absent + register: user +- name: verify results of remove locked user + assert: + that: + - user|success + - user|changed + - user.username == "{{ cs_resource_prefix }}_user" + - user.account_type == "root_admin" + - user.account == "admin" + - user.state == "locked" + - user.domain == "ROOT" + +- name: test create disabled user + cs_user: + username: "{{ cs_resource_prefix }}_user" + password: "{{ cs_resource_prefix }}_password" + last_name: "{{ cs_resource_prefix }}_last_name" + first_name: "{{ cs_resource_prefix }}_first_name" + email: "{{ cs_resource_prefix }}@example.com" + account: "admin" + state: disabled + register: user +- name: verify results of create disabled user + assert: + that: + - user|success + - user|changed + - user.username == "{{ cs_resource_prefix }}_user" + - user.first_name == "{{ cs_resource_prefix }}_first_name" + - user.last_name == "{{ cs_resource_prefix }}_last_name" + - user.email == "{{ cs_resource_prefix }}@example.com" + - user.account_type == "root_admin" + - user.account == "admin" + - user.state == "disabled" + - user.domain == "ROOT" + +- name: test remove disabled user + cs_user: + username: "{{ cs_resource_prefix }}_user" + state: absent + register: user +- name: verify results of remove disabled user + assert: + that: + - user|success + - user|changed + - user.username == "{{ cs_resource_prefix }}_user" + - user.account_type == "root_admin" + - user.account == "admin" + - user.state == "disabled" + - user.domain == "ROOT" + +- name: test create enabled user + cs_user: + username: "{{ cs_resource_prefix }}_user" + password: "{{ cs_resource_prefix }}_password" + last_name: "{{ cs_resource_prefix }}_last_name" + first_name: "{{ cs_resource_prefix }}_first_name" + email: "{{ cs_resource_prefix }}@example.com" + account: "admin" + state: enabled + register: user +- name: verify results of create enabled user + assert: + that: + - user|success + - user|changed + - user.username == "{{ cs_resource_prefix }}_user" + - user.first_name == "{{ cs_resource_prefix }}_first_name" + - user.last_name == "{{ cs_resource_prefix }}_last_name" + - user.email == "{{ cs_resource_prefix }}@example.com" + - user.account_type == "root_admin" + - user.account == "admin" + - user.state == "enabled" + - user.domain == "ROOT" + +- name: test remove enabled user + cs_user: + username: "{{ cs_resource_prefix }}_user" + state: absent + register: user +- name: verify results of remove enabled user + assert: + that: + - user|success + - user|changed + - user.username == "{{ cs_resource_prefix }}_user" + - user.account_type == "root_admin" + - user.account == "admin" + - user.state == "enabled" + - user.domain == "ROOT"