Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic nutanix module ,modules for images and vms from prism #11

Merged
merged 16 commits into from
Jan 17, 2022
Empty file added nutanix/__init__.py
Empty file.
48 changes: 48 additions & 0 deletions nutanix/ncp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Nutanix Ansible Collection - nutanix.ncp
Ansible collections to automate Nutanix Cloud Platform (ncp).

# Building and installing the collection locally
```
ansible-galaxy collection build
ansible-galaxy collection install nutanix.ncp-1.0.0.tar.gz
```

##or

### Installing the collection from GitHub repository
```
ansible-galaxy collection install git+https://github.com/nutanix/nutanix.ansible.git#nutanix,<branch>
```
_Add `--force` option for rebuilding or reinstalling to overwrite existing data_

# Included modules
```
nutanix_vms
nutanix_images
nutanix_subnets
```

# Inventory plugin
`ncp_prism_vm_inventory`

# Module documentation and examples
```
ansible-doc nutanix.ncp.<module_name>
```

# Examples
## Playbook to print name of vms in PC
```
- hosts: localhost
collections:
- nutanix.ncp
tasks:
- ncp_prism_vm_info:
pc_hostname: {{ pc_hostname }}
pc_username: {{ pc_username }}
pc_password: {{ pc_password }}
validate_certs: False
register: result
- debug:
msg: "{{ result.vms }}"
```
Empty file added nutanix/ncp/__init__.py
Empty file.
17 changes: 17 additions & 0 deletions nutanix/ncp/galaxy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace: "nutanix"
name: "ncp"
version: "1.0.0"
readme: "README.md"
authors:
- "Balu George (@balugeorge)"
- "Sarath Kumar K (@kumarsarath588)"
- "Prem Karat (@premkarat)"
- "Gevorg Khachatryan (@Gevorg-Khachatryan-97)"
description: Ansible collection for v3 Nutanix APIs https://www.nutanix.dev/api-reference-v3/
license_file: 'LICENSE'
tags: [nutanix, prism, ahv]
repository: https://github.com/nutanix/nutanix.ansible
documentation: https://github.com/nutanix/nutanix.ansible/nutanix/prism/docs
homepage: https://github.com/nutanix/nutanix.ansible/nutanix/prism
issues: https://github.com/nutanix/nutanix.ansible/issues
build_ignore: []
31 changes: 31 additions & 0 deletions nutanix/ncp/plugins/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Collections Plugins Directory

This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that
is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that
would contain module utils and modules respectively.

Here is an example directory of the majority of plugins currently supported by Ansible:

```
└── plugins
├── action
├── become
├── cache
├── callback
├── cliconf
├── connection
├── filter
├── httpapi
├── inventory
├── lookup
├── module_utils
├── modules
├── netconf
├── shell
├── strategy
├── terminal
├── test
└── vars
```

A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible-core/devel/plugins/plugins.html).
Empty file.
25 changes: 25 additions & 0 deletions nutanix/ncp/plugins/module_utils/base_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright: 2021, Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause )
from ansible.module_utils.basic import AnsibleModule


class BaseModule(AnsibleModule):
"""Basic module with common arguments"""

argument_spec = dict(
action=dict(type="str", required=True),
auth=dict(type="dict", required=True),
data=dict(type="dict", required=False),
operations=dict(type="list", required=False),
wait=dict(type="bool", required=False, default=True),
wait_timeout=dict(type="int", required=False, default=300),
validate_certs=dict(type="bool", required=False, default=False),
)

def __init__(self, **kwargs):
if not kwargs.get("argument_spec"):
kwargs["argument_spec"] = self.argument_spec
else:
kwargs["argument_spec"].update(self.argument_spec)
kwargs["supports_check_mode"] = True
super(BaseModule, self).__init__(**kwargs)
212 changes: 212 additions & 0 deletions nutanix/ncp/plugins/module_utils/entity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# Copyright: 2021, Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause )
from base64 import b64encode
import time
import uuid

from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.urls import fetch_url
import json

try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse


class Entity:
"""Basic functionality for Nutanix modules"""

result = dict(
changed=False,
original_message="",
message=""
)

methods_of_actions = {
"create": "post",
"list": "post",
"update": "put",
"delete": "delete"
}
kind = ""
api_version = "3.1.0"
__BASEURL__ = ""

def __init__(self, module):
self.run_module(module)

def parse_data(self):
if self.action != "list":
if self.data.get("metadata"):
if not self.data["metadata"].get("kind"):
self.data["metadata"].update({"kind": self.kind})
else:
self.data.update({"metadata": {"kind": self.kind}})
if not self.data.get("api_version"):
self.data["api_version"] = self.api_version

def check_response(self):

if self.response.get("task_uuid") and self.wait:
task = self.validate_request(self.module, self.response.get("task_uuid"), self.netloc, self.wait_timeout)
self.result["task_information"] = task

if not self.response.get("status"):
if self.response.get("api_response_list"):
self.response["status"] = self.response.get("api_response_list")[0].get("api_response").get("status")
elif "entities" in self.response:
if self.response["entities"]:
self.response["status"] = self.response.get("entities")[0].get("status")
else:
self.response["status"] = {"state": "complete"}

if self.response.get("status") and self.wait:
state = self.response.get("status").get("state")
if "pending" in state.lower() or "running" in state.lower():
task = self.validate_request(self.module,
self.response.get("status").get("execution_context").get("task_uuid"),
self.netloc,
self.wait_timeout)
self.response["status"]["state"] = task.get("status")
self.result["task_information"] = task

self.result["changed"] = True
status = self.response.get("state") or self.response.get("status").get("state")
if status and status.lower() != "succeeded" or self.action == "list":
self.result["changed"] = False
if status.lower() != "complete":
self.result["failed"] = True

self.result["response"] = self.response

def create(self):
pass

def update(self):
item_uuid = self.data["metadata"]["uuid"]
if not self.data.get("operations"):
self.url += "/" + str(uuid.UUID(item_uuid))
else:
self.url += "/" + str(uuid.UUID(item_uuid)) + "/file"
response = self.send_request(self.module, "get", self.url, self.data, self.username, self.password)

if response.get("state") and response.get("state").lower() == "error":
self.result["changed"] = False
self.result["failed"] = True
self.result["message"] = response["message_list"]
else:
self.data["metadata"]["spec_version"] = response["metadata"]["spec_version"]

def delete(self):
item_uuid = self.data["metadata"]["uuid"]
self.url += "/" + str(uuid.UUID(item_uuid))

def list(self):
self.url += "/" + self.action
if not self.data:
self.data = {"kind": self.kind}

@staticmethod
def send_request(module, method, req_url, req_data, username, password, timeout=30):
try:
credentials = bytes(username + ":" + password, encoding="ascii")
except:
credentials = bytes(username + ":" + password).encode("ascii")

encoded_credentials = b64encode(credentials).decode("ascii")
authorization = "Basic " + encoded_credentials
headers = {"Accept": "application/json", "Content-Type": "application/json",
"Authorization": authorization, "cache-control": "no-cache"}
if req_data is None:
payload = {}
else:
payload = req_data
resp, info = fetch_url(module=module, url=req_url, headers=headers,
method=method, data=module.jsonify(payload), timeout=timeout)
if not 300 > info['status'] > 199:
module.fail_json(msg="Fail: %s" % ("Status: " + str(info['msg']) + ", Message: " + str(info.get('body'))))

body = resp.read() if resp else info.get("body")
try:
resp_json = json.loads(to_text(body)) if body else None
except ValueError:
resp_json = None
return resp_json

def validate_request(self, module, task_uuid, netloc, wait_timeout):
timer = 5
retries = wait_timeout // timer
response = None
succeeded = False
task_uuid = str(uuid.UUID(task_uuid))
url = self.generate_url_from_operations("tasks", netloc, [task_uuid])
while retries > 0 or not succeeded:
response = self.send_request(module, "get", url, None, self.username, self.password)
if response.get("status"):
status = response.get("status")
if "running" not in status.lower():
succeeded = True
return response
time.sleep(timer)
retries -= 1
return response

def generate_url_from_operations(self, name, netloc=None, ops=None):
name = name.split("_")[-1]
url = "https://" + netloc
path = self.__BASEURL__ + '/' + name
if ops:
for each in ops:
if type(each) is str:
path += "/" + each
elif type(each) is dict:
key = list(each.keys())[0]
val = each[key]
path += "/{0}/{1}".format(key, val)
url += path
return self.validate_url(url, netloc, path)

@staticmethod
def validate_url(url, netloc, path=""):
parsed_url = urlparse(url)
if url and netloc and "http" in parsed_url.scheme and netloc == parsed_url.netloc and path == parsed_url.path:
return url
raise ValueError("Incorrect URL :", url)

def build(self):

self.username = self.credentials["username"]
self.password = self.credentials["password"]

self.parse_data()

self.url = self.generate_url_from_operations(self.module_name, self.netloc, self.operations)

getattr(self, self.action)()

self.response = self.send_request(self.module, self.methods_of_actions[self.action],
self.url, self.data, self.username, self.password)

self.check_response()

def run_module(self, module):

if module.check_mode:
module.exit_json(**self.result)

for key, value in module.params.items():
setattr(self, key, value)

self.url = self.auth.get("url")
self.credentials = self.auth.get("credentials")

if not self.url:
self.url = str(self.auth.get("ip_address")) + ":" + str(self.auth.get("port"))

self.netloc = self.url
self.module_name = module._name
self.module = module
self.build()

module.exit_json(**self.result)
Empty file.
8 changes: 8 additions & 0 deletions nutanix/ncp/plugins/module_utils/prism/images.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This file is part of Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from .prism import Prism


class Image(Prism):
kind = 'image'
8 changes: 8 additions & 0 deletions nutanix/ncp/plugins/module_utils/prism/prism.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type

from ..entity import Entity


class Prism(Entity):
__BASEURL__ = "/api/nutanix/v3"
9 changes: 9 additions & 0 deletions nutanix/ncp/plugins/module_utils/prism/subnets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# This file is part of Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from .prism import Prism


class Subnet(Prism):
kind = 'subnet'

14 changes: 14 additions & 0 deletions nutanix/ncp/plugins/module_utils/prism/vms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# This file is part of Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from .prism import Prism


class VM(Prism):
kind = 'vm'


data_tmp = {

}

Empty file.
Loading