{"action":"created","comment":{"url":"https://api.github.com/repos/resmo/ansible-cloudstack/pulls/comments/37074955","id":37074955,"diff_hunk":"@@ -0,0 +1,749 @@\n+#!/usr/bin/python\n+# -*- coding: utf-8 -*-\n+#\n+# (c) 2015, Jefferson Girão <jefferson@girao.net>\n+#\n+# This file is part of Ansible\n+#\n+# Ansible is free software: you can redistribute it and/or modify\n+# it under the terms of the GNU General Public License as published by\n+# the Free Software Foundation, either version 3 of the License, or\n+# (at your option) any later version.\n+#\n+# Ansible is distributed in the hope that it will be useful,\n+# but WITHOUT ANY WARRANTY; without even the implied warranty of\n+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n+# GNU General Public License for more details.\n+#\n+# You should have received a copy of the GNU General Public License\n+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.\n+\n+DOCUMENTATION = '''\n+---\n+module: cs_volume\n+short_description: Manages volume on Apache CloudStack based clouds.\n+description:\n+    - Create, destroy, attach, detach volumes\n+version_added: '2.0'\n+author: \"Jefferson Girão (@jeffersongirao)\"\n+options:\n+  name:\n+    description:\n+      - Name of the volume. C(name) can only contain ASCII letters.\n+    required: true\n+  account:\n+    description:\n+      - Account the volume is related to.\n+    required: false\n+    default: null\n+  custom_id:\n+    description:\n+      - Custom id to the resource. Allowed to Root Admins only\n+    required: false\n+    default: null\n+  disk_offering:\n+    description:\n+      - Name of the disk offering to be used.\n+    required: false\n+    default: null\n+  display_volume:\n+    description:\n+      - Whether to display the volume to the end user or not.\n+    required: false\n+    default: false\n+  domain:\n+    description:\n+      - Name of the project the volume to be deployed in.\n+    required: false\n+    default: null\n+  max_iops:\n+    description:\n+      - Max iops\n+    required: false\n+    default: null\n+  min_iops:\n+    description:\n+      - Min iops\n+    required: false\n+    default: null\n+  project:\n+    description:\n+      - Name of the project the volume to be deployed in.\n+    required: false\n+    default: null\n+  size:\n+    description:\n+      - Size of disk in GB\n+      - Required on C(state=present).\n+    required: false\n+    default: null\n+  snapshot_id:\n+    description:\n+      - The snapshot ID for the disk volume. Either disk_offering or snapshot_id must be passed in.\n+    required: false\n+    default: null\n+  vm:\n+    description:\n+      - Name of the virtual machine to attach the volume\n+    required: false\n+    default: null\n+  zone:\n+    description:\n+      - Name of the zone in which the volume shoud be deployed.\n+      - If not set, default zone is used.\n+    required: false\n+    default: null\n+  state:\n+    description:\n+      - State of the volume.\n+    required: false\n+    default: 'present'\n+    choices: [ 'present', 'absent', 'attached', 'detached' ]\n+  poll_async:\n+    description:\n+      - Poll async jobs until job has finished.\n+    required: false\n+    default: true\n+extends_documentation_fragment: cloudstack\n+'''\n+\n+EXAMPLES = '''\n+# Create volume within project, zone with specified storage options\n+- local_action:\n+    module: cs_volume\n+    name: web-vm-1-volume\n+    project: Integration\n+    zone: ch-zrh-ix-01\n+    disk_offering: PerfPlus Storage\n+    size: 20\n+\n+# Create/attach volume to instance\n+- local_action:\n+    module: cs_volume\n+    name: web-vm-1-volume\n+    disk_offering: PerfPlus Storage\n+    size: 20\n+    vm: web-vm-1\n+    state: attached\n+\n+# Detach volume\n+- local_action:\n+    module: cs_volume\n+    name: web-vm-1-volume\n+    state: detached\n+\n+# Remove volume\n+- local_action:\n+    module: cs_volume\n+    name: web-vm-1-volume\n+    state: absent\n+'''\n+\n+RETURN = '''\n+id:\n+  description: ID of the volume.\n+  returned: success\n+  type: string\n+  sample:\n+name:\n+  description: Name of the volume.\n+  returned: success\n+  type: string\n+  sample: web-volume-01\n+display_name:\n+  description: Display name of the volume.\n+  returned: success\n+  type: string\n+  sample: web-volume-01\n+group:\n+  description: Group the volume belongs to\n+  returned: success\n+  type: string\n+  sample: web\n+domain:\n+  description: Domain the volume belongs to\n+  returned: success\n+  type: string\n+  sample: example domain\n+project:\n+  description: Project the volume belongs to\n+  returned: success\n+  type: string\n+  sample: Production\n+zone:\n+  description: Name of zone the volume is in.\n+  returned: success\n+  type: string\n+  sample: ch-gva-2\n+created:\n+  description: Date of the volume was created.\n+  returned: success\n+  type: string\n+  sample: 2014-12-01T14:57:57+0100\n+attached:\n+  description: Date of the volume was attached.\n+  returned: success\n+  type: string\n+  sample: 2014-12-01T14:57:57+0100\n+vm_id:\n+  description: ID of the vm the volume is attached to (not returned when detached)\n+  returned: success\n+  type: string\n+  sample: 3d6d6e63-cf6b-4277-9daf-426602306187\n+vm_display_name:\n+  description: Name of the vm the volume is attached to (not returned when detached)\n+  returned: success\n+  type: string\n+  sample: web-01\n+state:\n+  description: State of the volume\n+  returned: success\n+  type: string\n+  sample: Running\n+device_id:\n+  description: Id of the device on user vm the volume is attached to (not returned when detached)\n+  returned: success\n+  type: string\n+  sample: 1\n+'''\n+\n+try:\n+    from cs import CloudStack, CloudStackException, read_config\n+    has_lib_cs = True\n+except ImportError:\n+    has_lib_cs = False\n+\n+\n+# import cloudstack common\n+class AnsibleCloudStack:\n+\n+    def __init__(self, module):\n+        if not has_lib_cs:\n+            module.fail_json(msg=\"python library cs required: pip install cs\")\n+\n+        self.result = {\n+            'changed': False,\n+        }\n+\n+        self.module = module\n+        self._connect()\n+\n+        self.domain = None\n+        self.account = None\n+        self.project = None\n+        self.ip_address = None\n+        self.zone = None\n+        self.vm = None\n+        self.os_type = None\n+        self.hypervisor = None\n+        self.capabilities = None\n+\n+    def _connect(self):\n+        api_key = self.module.params.get('api_key')\n+        api_secret = self.module.params.get('secret_key')\n+        api_url = self.module.params.get('api_url')\n+        api_http_method = self.module.params.get('api_http_method')\n+        api_timeout = self.module.params.get('api_timeout')\n+\n+        if api_key and api_secret and api_url:\n+            self.cs = CloudStack(\n+                endpoint=api_url,\n+                key=api_key,\n+                secret=api_secret,\n+                timeout=api_timeout,\n+                method=api_http_method\n+                )\n+        else:\n+            self.cs = CloudStack(**read_config())\n+\n+    def get_or_fallback(self, key=None, fallback_key=None):\n+        value = self.module.params.get(key)\n+        if not value:\n+            value = self.module.params.get(fallback_key)\n+        return value\n+\n+    # TODO: for backward compatibility only, remove if not used anymore\n+    def _has_changed(self, want_dict, current_dict, only_keys=None):\n+        return self.has_changed(want_dict=want_dict, current_dict=current_dict, only_keys=only_keys)\n+\n+    def has_changed(self, want_dict, current_dict, only_keys=None):\n+        for key, value in want_dict.iteritems():\n+\n+            # Optionally limit by a list of keys\n+            if only_keys and key not in only_keys:\n+                continue\n+\n+            # Skip None values\n+            if value is None:\n+                continue\n+\n+            if key in current_dict:\n+\n+                # API returns string for int in some cases, just to make sure\n+                if isinstance(value, int):\n+                    current_dict[key] = int(current_dict[key])\n+                elif isinstance(value, str):\n+                    current_dict[key] = str(current_dict[key])\n+\n+                # Only need to detect a singe change, not every item\n+                if value != current_dict[key]:\n+                    return True\n+        return False\n+\n+    def _get_by_key(self, key=None, my_dict={}):\n+        if key:\n+            if key in my_dict:\n+                return my_dict[key]\n+            self.module.fail_json(msg=\"Something went wrong: %s not found\" % key)\n+        return my_dict\n+\n+    def get_project(self, key=None):\n+        if self.project:\n+            return self._get_by_key(key, self.project)\n+\n+        project = self.module.params.get('project')\n+        if not project:\n+            return None\n+        args = {}\n+        args['account'] = self.get_account(key='name')\n+        args['domainid'] = self.get_domain(key='id')\n+        projects = self.cs.listProjects(**args)\n+        if projects:\n+            for p in projects['project']:\n+                if project.lower() in [p['name'].lower(), p['id']]:\n+                    self.project = p\n+                    return self._get_by_key(key, self.project)\n+        self.module.fail_json(msg=\"project '%s' not found\" % project)\n+\n+    def get_ip_address(self, key=None):\n+        if self.ip_address:\n+            return self._get_by_key(key, self.ip_address)\n+\n+        ip_address = self.module.params.get('ip_address')\n+        if not ip_address:\n+            self.module.fail_json(msg=\"IP address param 'ip_address' is required\")\n+\n+        args = {}\n+        args['ipaddress'] = ip_address\n+        args['account'] = self.get_account(key='name')\n+        args['domainid'] = self.get_domain(key='id')\n+        args['projectid'] = self.get_project(key='id')\n+        ip_addresses = self.cs.listPublicIpAddresses(**args)\n+\n+        if not ip_addresses:\n+            self.module.fail_json(msg=\"IP address '%s' not found\" % args['ipaddress'])\n+\n+        self.ip_address = ip_addresses['publicipaddress'][0]\n+        return self._get_by_key(key, self.ip_address)\n+\n+    def get_vm(self, key=None):\n+        if self.vm:\n+            return self._get_by_key(key, self.vm)\n+\n+        vm = self.module.params.get('vm')\n+        if not vm:\n+            self.module.fail_json(msg=\"Virtual machine param 'vm' is required\")\n+\n+        args = {}\n+        args['account'] = self.get_account(key='name')\n+        args['domainid'] = self.get_domain(key='id')\n+        args['projectid'] = self.get_project(key='id')\n+        args['zoneid'] = self.get_zone(key='id')\n+        vms = self.cs.listVirtualMachines(**args)\n+        if vms:\n+            for v in vms['virtualmachine']:\n+                if vm in [v['name'], v['displayname'], v['id']]:\n+                    self.vm = v\n+                    return self._get_by_key(key, self.vm)\n+        self.module.fail_json(msg=\"Virtual machine '%s' not found\" % vm)\n+\n+    def get_zone(self, key=None):\n+        if self.zone:\n+            return self._get_by_key(key, self.zone)\n+\n+        zone = self.module.params.get('zone')\n+        zones = self.cs.listZones()\n+\n+        # use the first zone if no zone param given\n+        if not zone:\n+            self.zone = zones['zone'][0]\n+            return self._get_by_key(key, self.zone)\n+\n+        if zones:\n+            for z in zones['zone']:\n+                if zone in [z['name'], z['id']]:\n+                    self.zone = z\n+                    return self._get_by_key(key, self.zone)\n+        self.module.fail_json(msg=\"zone '%s' not found\" % zone)\n+\n+    def get_os_type(self, key=None):\n+        if self.os_type:\n+            return self._get_by_key(key, self.zone)\n+\n+        os_type = self.module.params.get('os_type')\n+        if not os_type:\n+            return None\n+\n+        os_types = self.cs.listOsTypes()\n+        if os_types:\n+            for o in os_types['ostype']:\n+                if os_type in [o['description'], o['id']]:\n+                    self.os_type = o\n+                    return self._get_by_key(key, self.os_type)\n+        self.module.fail_json(msg=\"OS type '%s' not found\" % os_type)\n+\n+    def get_hypervisor(self):\n+        if self.hypervisor:\n+            return self.hypervisor\n+\n+        hypervisor = self.module.params.get('hypervisor')\n+        hypervisors = self.cs.listHypervisors()\n+\n+        # use the first hypervisor if no hypervisor param given\n+        if not hypervisor:\n+            self.hypervisor = hypervisors['hypervisor'][0]['name']\n+            return self.hypervisor\n+\n+        for h in hypervisors['hypervisor']:\n+            if hypervisor.lower() == h['name'].lower():\n+                self.hypervisor = h['name']\n+                return self.hypervisor\n+        self.module.fail_json(msg=\"Hypervisor '%s' not found\" % hypervisor)\n+\n+    def get_account(self, key=None):\n+        if self.account:\n+            return self._get_by_key(key, self.account)\n+\n+        account = self.module.params.get('account')\n+        if not account:\n+            return None\n+\n+        domain = self.module.params.get('domain')\n+        if not domain:\n+            self.module.fail_json(msg=\"Account must be specified with Domain\")\n+\n+        args = {}\n+        args['name'] = account\n+        args['domainid'] = self.get_domain(key='id')\n+        args['listall'] = True\n+        accounts = self.cs.listAccounts(**args)\n+        if accounts:\n+            self.account = accounts['account'][0]\n+            return self._get_by_key(key, self.account)\n+        self.module.fail_json(msg=\"Account '%s' not found\" % account)\n+\n+    def get_domain(self, key=None):\n+        if self.domain:\n+            return self._get_by_key(key, self.domain)\n+\n+        domain = self.module.params.get('domain')\n+        if not domain:\n+            return None\n+\n+        args = {}\n+        args['listall'] = True\n+        domains = self.cs.listDomains(**args)\n+        if domains:\n+            for d in domains['domain']:\n+                if d['path'].lower() in [domain.lower(), \"root/\" + domain.lower(), \"root\" + domain.lower()]:\n+                    self.domain = d\n+                    return self._get_by_key(key, self.domain)\n+        self.module.fail_json(msg=\"Domain '%s' not found\" % domain)\n+\n+    def get_tags(self, resource=None):\n+        existing_tags = self.cs.listTags(resourceid=resource['id'])\n+        if existing_tags:\n+            return existing_tags['tag']\n+        return []\n+\n+    def _delete_tags(self, resource, resource_type, tags):\n+        existing_tags = resource['tags']\n+        tags_to_delete = []\n+        for existing_tag in existing_tags:\n+            if existing_tag['key'] in tags:\n+                if existing_tag['value'] != tags[key]:\n+                    tags_to_delete.append(existing_tag)\n+            else:\n+                tags_to_delete.append(existing_tag)\n+        if tags_to_delete:\n+            self.result['changed'] = True\n+            if not self.module.check_mode:\n+                args = {}\n+                args['resourceids'] = resource['id']\n+                args['resourcetype'] = resource_type\n+                args['tags'] = tags_to_delete\n+                self.cs.deleteTags(**args)\n+\n+    def _create_tags(self, resource, resource_type, tags):\n+        tags_to_create = []\n+        for i, tag_entry in enumerate(tags):\n+            tag = {\n+                'key':   tag_entry['key'],\n+                'value': tag_entry['value'],\n+            }\n+            tags_to_create.append(tag)\n+        if tags_to_create:\n+            self.result['changed'] = True\n+            if not self.module.check_mode:\n+                args = {}\n+                args['resourceids'] = resource['id']\n+                args['resourcetype'] = resource_type\n+                args['tags'] = tags_to_create\n+                self.cs.createTags(**args)\n+\n+    def ensure_tags(self, resource, resource_type=None):\n+        if not resource_type or not resource:\n+            self.module.fail_json(msg=\"Error: Missing resource or resource_type for tags.\")\n+\n+        if 'tags' in resource:\n+            tags = self.module.params.get('tags')\n+            if tags is not None:\n+                self._delete_tags(resource, resource_type, tags)\n+                self._create_tags(resource, resource_type, tags)\n+                resource['tags'] = self.get_tags(resource)\n+        return resource\n+\n+    def get_capabilities(self, key=None):\n+        if self.capabilities:\n+            return self._get_by_key(key, self.capabilities)\n+        capabilities = self.cs.listCapabilities()\n+        self.capabilities = capabilities['capability']\n+        return self._get_by_key(key, self.capabilities)\n+\n+    def get_disk_offering(self, key=None):\n+        disk_offering = self.module.params.get('disk_offering')\n+\n+        if not disk_offering:\n+            return None\n+\n+        disk_offerings = self.cs.listDiskOfferings()\n+        if disk_offerings:\n+            for d in disk_offerings['diskoffering']:\n+                if disk_offering in [d['displaytext'], d['name'], d['id']]:\n+                    return self._get_by_key(key, d)\n+        self.module.fail_json(msg=\"Disk offering '%s' not found\" % disk_offering)\n+\n+    # TODO: for backward compatibility only, remove if not used anymore\n+    def _poll_job(self, job=None, key=None):\n+        return self.poll_job(job=job, key=key)\n+\n+    def poll_job(self, job=None, key=None):\n+        if 'jobid' in job:\n+            while True:\n+                res = self.cs.queryAsyncJobResult(jobid=job['jobid'])\n+                if res['jobstatus'] != 0 and 'jobresult' in res:\n+                    if 'errortext' in res['jobresult']:\n+                        self.module.fail_json(msg=\"Failed: '%s'\" % res['jobresult']['errortext'])\n+                    if key and key in res['jobresult']:\n+                        job = res['jobresult'][key]\n+                    break\n+                time.sleep(2)\n+        return job\n+\n+\n+class AnsibleCloudStackVolume(AnsibleCloudStack):\n+    def __init__(self, module):\n+        AnsibleCloudStack.__init__(self, module)\n+        self.volume = None\n+\n+    def get_volume(self):\n+        if not self.volume:\n+            args = {}\n+            args['account'] = self.get_account(key='name')\n+            args['domainid'] = self.get_domain(key='id')\n+            args['projectid'] = self.get_project(key='id')\n+            args['listall'] = True\n+\n+            volumes = self.cs.listVolumes(**args)\n+\n+            if volumes:\n+                volume_name = self.module.params.get('name')\n+                for v in volumes['volume']:\n+                    if volume_name.lower() == v['name'].lower():\n+                        self.volume = v\n+                        break\n+        return self.volume\n+\n+    def present_volume(self):\n+        volume = self.get_volume()\n+\n+        if not volume:\n+            self.result['changed'] = True\n+\n+            args = {}\n+            args['name'] = self.module.params.get('name')\n+            args['account'] = self.get_account(key='name')\n+            args['domainid'] = self.get_domain(key='id')\n+            args['diskofferingid'] = self.get_disk_offering(key='id')\n+            args['displayvolume'] = self.module.params.get('display_volume')\n+            args['maxiops'] = self.module.params.get('max_iops')\n+            args['miniops'] = self.module.params.get('min_iops')\n+            args['projectid'] = self.get_project(key='id')\n+            args['size'] = self.module.params.get('size')\n+            args['zoneid'] = self.get_zone(key='id')\n+\n+            if not self.module.check_mode:\n+                res = self.cs.createVolume(**args)\n+                if 'errortext' in res:\n+                    self.module.fail_json(msg=\"Failed: '%s'\" % res['errortext'])\n+                poll_async = self.module.params.get('poll_async')\n+                if poll_async:\n+                    volume = self.poll_job(res, 'volume')\n+        return volume\n+\n+    def attached_volume(self):\n+        missing_params = []\n+\n+        if missing_params:\n+            self.module.fail_json(msg=\"missing required arguments: %s\" % ','.join(missing_params))\n+\n+        volume = self.present_volume()\n+\n+        if 'virtualmachineid' in volume and volume['virtualmachineid'] != self.get_vm(key='id'):\n+            self.result['changed'] = True\n+\n+            if not self.module.check_mode:\n+                volume = self.detached_volume()\n+\n+        if 'attached' not in volume:\n+            self.result['changed'] = True\n+\n+            args = {}\n+            args['id'] = volume['id']\n+            args['virtualmachineid'] = self.get_vm(key='id')\n+            args['deviceid'] = self.module.params.get('device_id')\n+\n+            if not self.module.check_mode:\n+                res = self.cs.attachVolume(**args)\n+                if 'errortext' in res:\n+                    self.module.fail_json(msg=\"Failed: '%s'\" % res['errortext'])\n+                poll_async = self.module.params.get('poll_async')\n+                if poll_async:\n+                    volume = self.poll_job(res, 'volume')\n+        return volume\n+\n+    def detached_volume(self):\n+        volume = self.get_volume()\n+\n+        if volume:","path":"cs_volume.py","position":628,"original_position":628,"commit_id":"1dfef14ac828898738f94c92853e79662583d8be","original_commit_id":"1dfef14ac828898738f94c92853e79662583d8be","user":{"login":"resmo","id":23809,"avatar_url":"https://avatars.githubusercontent.com/u/23809?v=3","gravatar_id":"","url":"https://api.github.com/users/resmo","html_url":"https://github.com/resmo","followers_url":"https://api.github.com/users/resmo/followers","following_url":"https://api.github.com/users/resmo/following{/other_user}","gists_url":"https://api.github.com/users/resmo/gists{/gist_id}","starred_url":"https://api.github.com/users/resmo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/resmo/subscriptions","organizations_url":"https://api.github.com/users/resmo/orgs","repos_url":"https://api.github.com/users/resmo/repos","events_url":"https://api.github.com/users/resmo/events{/privacy}","received_events_url":"https://api.github.com/users/resmo/received_events","type":"User","site_admin":false},"body":"Mustn't we check if the volume is attached here too?\r\n~~~\r\nif volume and and 'attached' in volume:\r\n~~~","created_at":"2015-08-14T13:19:28Z","updated_at":"2015-08-14T13:19:28Z","html_url":"https://github.com/resmo/ansible-cloudstack/pull/26#discussion_r37074955","pull_request_url":"https://api.github.com/repos/resmo/ansible-cloudstack/pulls/26","_links":{"self":{"href":"https://api.github.com/repos/resmo/ansible-cloudstack/pulls/comments/37074955"},"html":{"href":"https://github.com/resmo/ansible-cloudstack/pull/26#discussion_r37074955"},"pull_request":{"href":"https://api.github.com/repos/resmo/ansible-cloudstack/pulls/26"}}},"pull_request":{"url":"https://api.github.com/repos/resmo/ansible-cloudstack/pulls/26","id":42241240,"html_url":"https://github.com/resmo/ansible-cloudstack/pull/26","diff_url":"https://github.com/resmo/ansible-cloudstack/pull/26.diff","patch_url":"https://github.com/resmo/ansible-cloudstack/pull/26.patch","issue_url":"https://api.github.com/repos/resmo/ansible-cloudstack/issues/26","number":26,"state":"open","locked":false,"title":"Initial version of cs_volume","user":{"login":"jeffersongirao","id":70795,"avatar_url":"https://avatars.githubusercontent.com/u/70795?v=3","gravatar_id":"","url":"https://api.github.com/users/jeffersongirao","html_url":"https://github.com/jeffersongirao","followers_url":"https://api.github.com/users/jeffersongirao/followers","following_url":"https://api.github.com/users/jeffersongirao/following{/other_user}","gists_url":"https://api.github.com/users/jeffersongirao/gists{/gist_id}","starred_url":"https://api.github.com/users/jeffersongirao/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jeffersongirao/subscriptions","organizations_url":"https://api.github.com/users/jeffersongirao/orgs","repos_url":"https://api.github.com/users/jeffersongirao/repos","events_url":"https://api.github.com/users/jeffersongirao/events{/privacy}","received_events_url":"https://api.github.com/users/jeffersongirao/received_events","type":"User","site_admin":false},"body":"As it seems like the AnsibleCloudStack is being extracted to a different file in the ansible-modules-extras repo I just copied over from cs_instance and added a new method to lookup diskofferings. Let me know if it is ok, thanks!","created_at":"2015-08-12T13:47:30Z","updated_at":"2015-08-14T13:19:28Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6718243c33efbb6edb6587df99c564d9c790dc52","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/resmo/ansible-cloudstack/pulls/26/commits","review_comments_url":"https://api.github.com/repos/resmo/ansible-cloudstack/pulls/26/comments","review_comment_url":"https://api.github.com/repos/resmo/ansible-cloudstack/pulls/comments{/number}","comments_url":"https://api.github.com/repos/resmo/ansible-cloudstack/issues/26/comments","statuses_url":"https://api.github.com/repos/resmo/ansible-cloudstack/statuses/1dfef14ac828898738f94c92853e79662583d8be","head":{"label":"jeffersongirao:cs_volume","ref":"cs_volume","sha":"1dfef14ac828898738f94c92853e79662583d8be","user":{"login":"jeffersongirao","id":70795,"avatar_url":"https://avatars.githubusercontent.com/u/70795?v=3","gravatar_id":"","url":"https://api.github.com/users/jeffersongirao","html_url":"https://github.com/jeffersongirao","followers_url":"https://api.github.com/users/jeffersongirao/followers","following_url":"https://api.github.com/users/jeffersongirao/following{/other_user}","gists_url":"https://api.github.com/users/jeffersongirao/gists{/gist_id}","starred_url":"https://api.github.com/users/jeffersongirao/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jeffersongirao/subscriptions","organizations_url":"https://api.github.com/users/jeffersongirao/orgs","repos_url":"https://api.github.com/users/jeffersongirao/repos","events_url":"https://api.github.com/users/jeffersongirao/events{/privacy}","received_events_url":"https://api.github.com/users/jeffersongirao/received_events","type":"User","site_admin":false},"repo":{"id":40602885,"name":"ansible-cloudstack","full_name":"jeffersongirao/ansible-cloudstack","owner":{"login":"jeffersongirao","id":70795,"avatar_url":"https://avatars.githubusercontent.com/u/70795?v=3","gravatar_id":"","url":"https://api.github.com/users/jeffersongirao","html_url":"https://github.com/jeffersongirao","followers_url":"https://api.github.com/users/jeffersongirao/followers","following_url":"https://api.github.com/users/jeffersongirao/following{/other_user}","gists_url":"https://api.github.com/users/jeffersongirao/gists{/gist_id}","starred_url":"https://api.github.com/users/jeffersongirao/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jeffersongirao/subscriptions","organizations_url":"https://api.github.com/users/jeffersongirao/orgs","repos_url":"https://api.github.com/users/jeffersongirao/repos","events_url":"https://api.github.com/users/jeffersongirao/events{/privacy}","received_events_url":"https://api.github.com/users/jeffersongirao/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jeffersongirao/ansible-cloudstack","description":"ansible modules for using cloudstack","fork":true,"url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack","forks_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/forks","keys_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/teams","hooks_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/hooks","issue_events_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/issues/events{/number}","events_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/events","assignees_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/assignees{/user}","branches_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/branches{/branch}","tags_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/tags","blobs_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/git/refs{/sha}","trees_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/statuses/{sha}","languages_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/languages","stargazers_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/stargazers","contributors_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/contributors","subscribers_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/subscribers","subscription_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/subscription","commits_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/commits{/sha}","git_commits_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/git/commits{/sha}","comments_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/comments{/number}","issue_comment_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/issues/comments{/number}","contents_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/contents/{+path}","compare_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/merges","archive_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/downloads","issues_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/issues{/number}","pulls_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/pulls{/number}","milestones_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/milestones{/number}","notifications_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/labels{/name}","releases_url":"https://api.github.com/repos/jeffersongirao/ansible-cloudstack/releases{/id}","created_at":"2015-08-12T13:42:22Z","updated_at":"2015-08-13T15:56:59Z","pushed_at":"2015-08-13T09:15:57Z","git_url":"git://github.com/jeffersongirao/ansible-cloudstack.git","ssh_url":"git@github.com:jeffersongirao/ansible-cloudstack.git","clone_url":"https://github.com/jeffersongirao/ansible-cloudstack.git","svn_url":"https://github.com/jeffersongirao/ansible-cloudstack","homepage":"","size":935,"stargazers_count":1,"watchers_count":1,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"resmo:master","ref":"master","sha":"1db2c61b2722da0fb468003ddb2a1489eeb4b207","user":{"login":"resmo","id":23809,"avatar_url":"https://avatars.githubusercontent.com/u/23809?v=3","gravatar_id":"","url":"https://api.github.com/users/resmo","html_url":"https://github.com/resmo","followers_url":"https://api.github.com/users/resmo/followers","following_url":"https://api.github.com/users/resmo/following{/other_user}","gists_url":"https://api.github.com/users/resmo/gists{/gist_id}","starred_url":"https://api.github.com/users/resmo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/resmo/subscriptions","organizations_url":"https://api.github.com/users/resmo/orgs","repos_url":"https://api.github.com/users/resmo/repos","events_url":"https://api.github.com/users/resmo/events{/privacy}","received_events_url":"https://api.github.com/users/resmo/received_events","type":"User","site_admin":false},"repo":{"id":16319523,"name":"ansible-cloudstack","full_name":"resmo/ansible-cloudstack","owner":{"login":"resmo","id":23809,"avatar_url":"https://avatars.githubusercontent.com/u/23809?v=3","gravatar_id":"","url":"https://api.github.com/users/resmo","html_url":"https://github.com/resmo","followers_url":"https://api.github.com/users/resmo/followers","following_url":"https://api.github.com/users/resmo/following{/other_user}","gists_url":"https://api.github.com/users/resmo/gists{/gist_id}","starred_url":"https://api.github.com/users/resmo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/resmo/subscriptions","organizations_url":"https://api.github.com/users/resmo/orgs","repos_url":"https://api.github.com/users/resmo/repos","events_url":"https://api.github.com/users/resmo/events{/privacy}","received_events_url":"https://api.github.com/users/resmo/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/resmo/ansible-cloudstack","description":"ansible modules for using cloudstack","fork":false,"url":"https://api.github.com/repos/resmo/ansible-cloudstack","forks_url":"https://api.github.com/repos/resmo/ansible-cloudstack/forks","keys_url":"https://api.github.com/repos/resmo/ansible-cloudstack/keys{/key_id}","collaborators_url":"https://api.github.com/repos/resmo/ansible-cloudstack/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/resmo/ansible-cloudstack/teams","hooks_url":"https://api.github.com/repos/resmo/ansible-cloudstack/hooks","issue_events_url":"https://api.github.com/repos/resmo/ansible-cloudstack/issues/events{/number}","events_url":"https://api.github.com/repos/resmo/ansible-cloudstack/events","assignees_url":"https://api.github.com/repos/resmo/ansible-cloudstack/assignees{/user}","branches_url":"https://api.github.com/repos/resmo/ansible-cloudstack/branches{/branch}","tags_url":"https://api.github.com/repos/resmo/ansible-cloudstack/tags","blobs_url":"https://api.github.com/repos/resmo/ansible-cloudstack/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/resmo/ansible-cloudstack/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/resmo/ansible-cloudstack/git/refs{/sha}","trees_url":"https://api.github.com/repos/resmo/ansible-cloudstack/git/trees{/sha}","statuses_url":"https://api.github.com/repos/resmo/ansible-cloudstack/statuses/{sha}","languages_url":"https://api.github.com/repos/resmo/ansible-cloudstack/languages","stargazers_url":"https://api.github.com/repos/resmo/ansible-cloudstack/stargazers","contributors_url":"https://api.github.com/repos/resmo/ansible-cloudstack/contributors","subscribers_url":"https://api.github.com/repos/resmo/ansible-cloudstack/subscribers","subscription_url":"https://api.github.com/repos/resmo/ansible-cloudstack/subscription","commits_url":"https://api.github.com/repos/resmo/ansible-cloudstack/commits{/sha}","git_commits_url":"https://api.github.com/repos/resmo/ansible-cloudstack/git/commits{/sha}","comments_url":"https://api.github.com/repos/resmo/ansible-cloudstack/comments{/number}","issue_comment_url":"https://api.github.com/repos/resmo/ansible-cloudstack/issues/comments{/number}","contents_url":"https://api.github.com/repos/resmo/ansible-cloudstack/contents/{+path}","compare_url":"https://api.github.com/repos/resmo/ansible-cloudstack/compare/{base}...{head}","merges_url":"https://api.github.com/repos/resmo/ansible-cloudstack/merges","archive_url":"https://api.github.com/repos/resmo/ansible-cloudstack/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/resmo/ansible-cloudstack/downloads","issues_url":"https://api.github.com/repos/resmo/ansible-cloudstack/issues{/number}","pulls_url":"https://api.github.com/repos/resmo/ansible-cloudstack/pulls{/number}","milestones_url":"https://api.github.com/repos/resmo/ansible-cloudstack/milestones{/number}","notifications_url":"https://api.github.com/repos/resmo/ansible-cloudstack/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/resmo/ansible-cloudstack/labels{/name}","releases_url":"https://api.github.com/repos/resmo/ansible-cloudstack/releases{/id}","created_at":"2014-01-28T17:28:25Z","updated_at":"2015-07-21T10:50:05Z","pushed_at":"2015-08-13T09:17:46Z","git_url":"git://github.com/resmo/ansible-cloudstack.git","ssh_url":"git@github.com:resmo/ansible-cloudstack.git","clone_url":"https://github.com/resmo/ansible-cloudstack.git","svn_url":"https://github.com/resmo/ansible-cloudstack","homepage":"","size":935,"stargazers_count":19,"watchers_count":19,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":10,"mirror_url":null,"open_issues_count":2,"forks":10,"open_issues":2,"watchers":19,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/resmo/ansible-cloudstack/pulls/26"},"html":{"href":"https://github.com/resmo/ansible-cloudstack/pull/26"},"issue":{"href":"https://api.github.com/repos/resmo/ansible-cloudstack/issues/26"},"comments":{"href":"https://api.github.com/repos/resmo/ansible-cloudstack/issues/26/comments"},"review_comments":{"href":"https://api.github.com/repos/resmo/ansible-cloudstack/pulls/26/comments"},"review_comment":{"href":"https://api.github.com/repos/resmo/ansible-cloudstack/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/resmo/ansible-cloudstack/pulls/26/commits"},"statuses":{"href":"https://api.github.com/repos/resmo/ansible-cloudstack/statuses/1dfef14ac828898738f94c92853e79662583d8be"}}}}