Brett Thurber
2017-04-17 f0a0f0dca5b4ec145d7ae217a5a24d7a7c9ae3e7
added the ravello module and contents under ansible_agnostic_deployer/tree/master/ansible/cloud_providers
14 files added
1528 ■■■■■ changed files
ansible/cloud_providers/ravello/Ansible_Ravello_module.adoc 224 ●●●●● patch | view | raw | blame | history
ansible/cloud_providers/ravello/README.md 12 ●●●●● patch | view | raw | blame | history
ansible/cloud_providers/ravello/app_template.yml 190 ●●●●● patch | view | raw | blame | history
ansible/cloud_providers/ravello/app_template_short.yml 49 ●●●●● patch | view | raw | blame | history
ansible/cloud_providers/ravello/deploy_environment.yml 10 ●●●●● patch | view | raw | blame | history
ansible/cloud_providers/ravello/group_vars/all 9 ●●●●● patch | view | raw | blame | history
ansible/cloud_providers/ravello/inventory/ansible_tower_ravello_inventory.py 218 ●●●●● patch | view | raw | blame | history
ansible/cloud_providers/ravello/inventory/ravello.ini 11 ●●●●● patch | view | raw | blame | history
ansible/cloud_providers/ravello/library/ravello_app.py 672 ●●●●● patch | view | raw | blame | history
ansible/cloud_providers/ravello/roles/application_create_from_blueprint/tasks/main.yml 40 ●●●●● patch | view | raw | blame | history
ansible/cloud_providers/ravello/roles/blueprint_create_from_application/tasks/main.yml 33 ●●●●● patch | view | raw | blame | history
ansible/cloud_providers/ravello/roles/blueprint_design/tasks/main.yml 24 ●●●●● patch | view | raw | blame | history
ansible/cloud_providers/ravello/roles/warm_up/tasks/main.yml 24 ●●●●● patch | view | raw | blame | history
ansible/cloud_providers/ravello/save_environment.yml 12 ●●●●● patch | view | raw | blame | history
ansible/cloud_providers/ravello/Ansible_Ravello_module.adoc
New file
@@ -0,0 +1,224 @@
:scrollbar:
:data-uri:
:toc2:
== Requirements & Instructions on Using the Ansible Ravello Module
:numbered:
== Requirements
. Install Ansible `http://docs.ansible.com/ansible/intro_installation.html`
. Install the Ravello SDK via **pip**
+
NOTE: For manual setup instructions see `https://github.com/ravello/python-sdk` and for additional documentation see `http://ravello-sdk.readthedocs.io/en/ravello-sdk-1.4/`
+
----
pip install ravello-sdk
----
=== Test Ravello SDK
. Clone the ravello-sdk locally to use the example scripts
+
----
cd <desired_dir>
git clone https://github.com/ravello/python-sdk.git
----
+
. Change to the **examples** directory
+
----
cd python-sdk/examples
----
+
. Set up credentials file for Ravello using your username and password
+
----
./set-creds
Enter username: your_login@redhat.com
Enter a Password:
----
+
. Verify saved credentials
+
NOTE: This will display your login into on the terminal however the password is hashed.
+
----
cat ~/.ravello_login
<username>
<password>
----
+
. Test the script to get all blueprints you have access to
+
----
./bp-get-all
<all blueprints you have access to>
----
=== Test Capture of Ravello Environment Metadata to JSON
. Test the script to get all applications you have access to
+
----
./app-get-all
<all apps you have access to>
----
+
. Test the script to get all metadata for a specific application and write it to a JSON file
+
NOTE: The option **-a** is for a full JSON dump of extended application metadata
+
----
./app-get-data -a <one app name from output in previous step > myapp.json
----
== Using the Ravello Ansible Module
=== Clone the Ansible Ravello Repository
. Change to a directory of your chosing
+
----
git clone https://github.com/RedHatDemos/ansible-ravello.git
----
=== Create YAML Template
. Within the cloned repository directory, create template from existing app:
+
----
./app-get-data -a <your_application> 2>/dev/null|python -c 'import sys, yaml, json; yaml.safe_dump(json.load(sys.stdin), sys.stdout, default_flow_style=False)' > out.yaml
----
. Remove everything that is not in the *design:* section.  This is at the beginning and end of the file.
. Under *design:* remove all subsections except *vms:*
. Remove any line with *applicationId:*, *creationTime:*, *loadingStatus:*, *loadingPercentage:*, *fqdn:*, *allocatedIp:*, *externalAccessState:*, *ipConfigLuid:*, and *useLuidForIpConfig:*.
=== Deploy an Environment
. This playbook will create a blueprint based on a set of virtual machines described in *app_template.yml*. It will then create an application from this blueprint and wait for it to start.
. To use the default values just run the *deploy_environment.yml.
+
----
ansible-playbook deploy_environment.yml
----
+
NOTE: It is likely there may be a conflict if the default names have already been used. Instead, specify a *unique_name* and optionally a *version* which will be used to make sure your blueprints and applications have unique names
+
. Specify *unique_name* and *version* if desired:
+
----
ansible-playbook deploy_environment.yml -e "unique_name=vvaldez-demo version=1.6"
----
. It is also possible to load your own variables from a yaml file:
+
----
ansible-playbook deploy_environment.yml -e @vars.yml
----
. Contents of *vars.yml*:
+
----
unique_name=vvaldez-demo
version=1.6
----
. To skip the blueprint creation phase, just provide an existing *blueprint_id*:
+
----
ansible-playbook deploy_environment.yml -e "blueprint_id=12345678"
----
. Example *deploy_environment.yml* playbook template provided in this repo:
+
----
---
- hosts: localhost
  tasks:
      # Create app, based on blueprint, start it and wait for started
    - local_action:
        module: ravello_app
        # Make sure you update the version number as the name must be unique
        name: 'example_app-v1.0'
        description: 'Example blueprint created by ansible'
        app_template: '/home/user/ansible-ravello/app_template.yml'
        state: design
----
+
[NOTE]
The *name* field must be unique.  All blueprints must have a unique name.  Typically this is achieved by changing the version number in the name.
=== Create Blueprint From Template
----
ansible-playbook design.yml -vvv
----
----
Using /etc/ansible/ansible.cfg as config file
 [WARNING]: provided hosts list is empty, only localhost is available
PLAYBOOK: design.yml ***********************************************************
1 plays in design.yml
PLAY [localhost] ***************************************************************
TASK [setup] *******************************************************************
Using module file /usr/lib/python2.6/site-packages/ansible/modules/core/system/setup.py
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: prutledg
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1481666189.73-18007598170313 `" && echo ansible-tmp-1481666189.73-18007598170313="` echo $HOME/.ansible/tmp/ansible-tmp-1481666189.73-18007598170313 `" ) && sleep 0'
<127.0.0.1> PUT /tmp/tmp7TPuB1 TO /home/prutledg/.ansible/tmp/ansible-tmp-1481666189.73-18007598170313/setup.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /home/prutledg/.ansible/tmp/ansible-tmp-1481666189.73-18007598170313/ /home/prutledg/.ansible/tmp/ansible-tmp-1481666189.73-18007598170313/setup.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '/usr/bin/python2.6 /home/prutledg/.ansible/tmp/ansible-tmp-1481666189.73-18007598170313/setup.py; rm -rf "/home/prutledg/.ansible/tmp/ansible-tmp-1481666189.73-18007598170313/" > /dev/null 2>&1 && sleep 0'
ok: [localhost]
TASK [ravello_app] *************************************************************
task path: /home/prutledg/ansible-ravello/design.yml:5
Using module file /home/prutledg/ansible-ravello/library/ravello_app.py
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: prutledg
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1481666190.57-63460892520707 `" && echo ansible-tmp-1481666190.57-63460892520707="` echo $HOME/.ansible/tmp/ansible-tmp-1481666190.57-63460892520707 `" ) && sleep 0'
<localhost> PUT /tmp/tmp7TPuB1 TO /home/prutledg/.ansible/tmp/ansible-tmp-1481666190.57-63460892520707/ravello_app.py
<localhost> EXEC /bin/sh -c 'chmod u+x /home/prutledg/.ansible/tmp/ansible-tmp-1481666190.57-63460892520707/ /home/prutledg/.ansible/tmp/ansible-tmp-1481666190.57-63460892520707/ravello_app.py && sleep 0'
<localhost> EXEC /bin/sh -c '/usr/bin/python2.6 /home/prutledg/.ansible/tmp/ansible-tmp-1481666190.57-63460892520707/ravello_app.py; rm -rf "/home/prutledg/.ansible/tmp/ansible-tmp-1481666190.57-63460892520707/" > /dev/null 2>&1 && sleep 0'
changed: [localhost -> localhost] => {
    "blueprint_id": "76743737",
    "changed": true,
    "invocation": {
        "module_args": {
            "app_template": "/home/prutledg/ansible-ravello/app_template.yml",
            "application_ttl": -1,
            "blueprint_description": null,
            "blueprint_id": null,
            "blueprint_name": null,
            "cloud": null,
            "description": "Example blueprint created by ansible",
            "name": "example_app-v1.0",
            "password": null,
            "publish_optimization": "cost",
            "region": null,
            "service_name": "ssh",
            "state": "design",
            "url": null,
            "username": null,
            "wait": true,
            "wait_timeout": 1200
        },
        "module_name": "ravello_app"
    },
    "name": "example_app-v1.0"
}
----
=== Check for Blueprint
. Log into Ravello and check if the blueprint exists.
. The next steps would be to deploy the blueprint with this ansible module as a new application, inventory the app, and apply final playbook(s) to the resulting VMs then create final blueprint from that.
ansible/cloud_providers/ravello/README.md
New file
@@ -0,0 +1,12 @@
# Synopsis
The Ansible Ravello module is intended to provide an easy way to interact with Ravello using the Ravello SDK to interact with the Ravello API.  The goal is to use the automation capabilities of Ansible to easily deploy new applications, customize the applications and save the fully configured application back as a blueprint in Ravello allow for quick, scalable, pre-configured deployments.
# Pre-reqs, Installation, Examples
Refer to: https://github.com/RedHatDemos/ansible-ravello/blob/master/Ansible_Ravello_module.adoc for installation dependencies and module usage.
# Contributors
The main contributors for this project are: </br>
*Pat Rutledge*  prutledge@redhat.com </br>
*Vinny Valdez*  vvaldez@redhat.com </br>
*Nenad Peric*   nperic@redhat.com </br>
*Brett Thurber* bthurber@redhat.com
ansible/cloud_providers/ravello/app_template.yml
New file
@@ -0,0 +1,190 @@
vms:
- name: "Bastion Host"
  tag: "bastion"
  description: "Bastion Host\nnohbac: true\n"
  numCpus: 1
  memorySize:
    unit: "GB"
    value: 1
  hostnames:
  - "bastion-REPL.rhpds.opentlc.com"
  - "bastion.example.com"
  hardDrives:
  - index: 0
    imageName: "rhel-guest-image-7.3-35.x86_64"
    boot: true
    controller: "virtio"
    name: "root disk"
    size:
      unit: "GB"
      value: 40
    type: "DISK"
  networkConnections:
  - name: "eth0"
    device:
      index: 0
      deviceType: "virtio"
      useAutomaticMac: false
      mac: "2c:c2:60:14:42:52"
    ipConfig:
      autoIpConfig:
        reservedIp: "192.168.1.10"
      hasPublicIp: true
  - name: "eth1"
    device:
      index: 1
      deviceType: "virtio"
      useAutomaticMac: true
    ipConfig:
      autoIpConfig:
        reservedIp: "192.168.2.10"
  stopTimeOut: "300"
  suppliedServices:
  - external: true
    ip: "192.168.1.10"
    name: "ssh"
    portRange: "22"
    protocol: "SSH"
  supportsCloudInit: true
  keypairName: "opentlc-admin-backdoor"
- name: "App Server"
  tag: "app_server"
  description: "App Server"
  numCpus: 2
  memorySize:
    unit: "GB"
    value: 4
  hostnames:
  - "app-REPL.rhpds.opentlc.com"
  - "app.example.com"
  hardDrives:
  - index: 0
    imageName: "rhel-guest-image-7.3-35.x86_64"
    boot: true
    controller: "virtio"
    name: "root disk"
    size:
      unit: "GB"
      value: 40
    type: "DISK"
  networkConnections:
  - name: "eth0"
    device:
      index: 0
      deviceType: "virtio"
      useAutomaticMac: true
    ipConfig:
      autoIpConfig:
        reservedIp: "192.168.1.20"
  - name: "eth1"
    device:
      index: 1
      deviceType: "virtio"
      useAutomaticMac: true
    ipConfig:
      autoIpConfig:
        reservedIp: "192.168.2.20"
  stopTimeOut: "300"
- name: "Web Server"
  tag: "web_server"
  description: "Web Server"
  numCpus: 2
  memorySize:
    unit: "GB"
    value: 2
  hostnames:
  - "www-REPL.rhpds.opentlc.com"
  - "www.example.com"
  hardDrives:
  - index: 0
    imageName: "rhel-guest-image-7.3-35.x86_64"
    boot: true
    controller: "virtio"
    name: "root disk"
    size:
      unit: "GB"
      value: 40
    type: "DISK"
  - index: 1
    controller: "virtio"
    name: "blank web root disk"
    size:
      unit: "GB"
      value: 10
    type: "DISK"
  networkConnections:
  - name: "eth0"
    device:
      index: 0
      deviceType: "virtio"
      useAutomaticMac: true
    ipConfig:
      autoIpConfig:
        reservedIp: "192.168.1.30"
      hasPublicIp: true
  stopTimeOut: "300"
  suppliedServices:
  - ip: "192.168.1.30"
    name: "http"
    portRange: "80"
    protocol: "HTTP"
  - ip: "192.168.1.30"
    name: "https"
    portRange: "443"
    protocol: "HTTPS"
  - ip: "192.168.1.30"
    name: "example TCP"
    portRange: "8080"
    protocol: "TCP"
  - ip: "192.168.1.30"
    name: "example UDP"
    portRange: "901"
    protocol: "UDP"
  supportsCloudInit: true
  keypairName: "opentlc-admin-backdoor"
- name: "DB Server"
  tag: "db_server"
  description: "DB Server"
  numCpus: 4
  memorySize:
    unit: "GB"
    value: 4
  hostnames:
  - "db-REPL.rhpds.opentlc.com"
  - "db.example.com"
  hardDrives:
  - index: 0
    imageName: "rhel-guest-image-7.3-35.x86_64"
    boot: true
    controller: "virtio"
    name: "root disk"
    size:
      unit: "GB"
      value: 40
    type: "DISK"
  - index: 1
    controller: "virtio"
    name: "blank db disk"
    size:
      unit: "GB"
      value: 20
    type: "DISK"
  networkConnections:
  - name: "eth0"
    device:
      index: 0
      deviceType: "virtio"
      useAutomaticMac: true
    ipConfig:
      autoIpConfig:
        reservedIp: "192.168.1.40"
  - name: "eth1"
    device:
      index: 1
      deviceType: "virtio"
      useAutomaticMac: true
    ipConfig:
      autoIpConfig:
        reservedIp: "192.168.2.40"
  stopTimeOut: "300"
ansible/cloud_providers/ravello/app_template_short.yml
New file
@@ -0,0 +1,49 @@
vms:
- name: "Bastion Host"
  tag: "bastion"
  description: "Bastion Host\nnohbac: true\n"
  numCpus: 1
  memorySize:
    unit: "GB"
    value: 1
  hostnames:
  - "bastion-REPL.rhpds.opentlc.com"
  - "bastion.example.com"
  hardDrives:
  - index: 0
    imageName: "rhel-guest-image-7.3-35.x86_64"
    boot: true
    controller: "virtio"
    name: "root disk"
    size:
      unit: "GB"
      value: 40
    type: "DISK"
  networkConnections:
  - name: "eth0"
    device:
      index: 0
      deviceType: "virtio"
      useAutomaticMac: false
      mac: "2c:c2:60:14:42:52"
    ipConfig:
      autoIpConfig:
        reservedIp: "192.168.1.10"
      hasPublicIp: true
  - name: "eth1"
    device:
      index: 1
      deviceType: "virtio"
      useAutomaticMac: true
    ipConfig:
      autoIpConfig:
        reservedIp: "192.168.2.10"
  stopTimeOut: "300"
  suppliedServices:
  - external: true
    ip: "192.168.1.10"
    name: "ssh"
    portRange: "22"
    protocol: "SSH"
  supportsCloudInit: true
  keypairName: "opentlc-admin-backdoor"
ansible/cloud_providers/ravello/deploy_environment.yml
New file
@@ -0,0 +1,10 @@
---
- hosts: localhost
  roles:
    - role: warm_up
    - role: blueprint_design
      when: not blueprint_id is defined
    - role: application_create_from_blueprint
      blueprint_id: "{{ design_results.blueprint_id }}"
    - role: blueprint_create_from_application
      application_name: "{{ app_results.app_name }}"
ansible/cloud_providers/ravello/group_vars/all
New file
@@ -0,0 +1,9 @@
---
unique_name: 'example'
version: '1.0'
wait_timeout: '1800'
blueprint_name: "{{ 'ansible-' + unique_name + '-v' + version }}"
blueprint_description: "{{ unique_name + ' blueprint created by ansible' }}"
application_name: "{{ 'ansible-' + unique_name + '-app-v' + version }}"
application_description: "{{ unique_name + ' application created by ansible' }}"
app_template: "app_template.yml"
ansible/cloud_providers/ravello/inventory/ansible_tower_ravello_inventory.py
New file
@@ -0,0 +1,218 @@
#!/usr/bin/python
'''
Ravello external inventory script
==================================================
Generates inventory that Ansible can understand by making an API request to Ravello.
Modeled after https://raw.githubusercontent.com/jameslabocki/ansible_api/master/python/ansible_tower_cloudforms_inventory.py
Required: Ravello Python SDK https://github.com/ravello/python-sdk
Useful: https://www.ravellosystems.com/ravello-api-doc/
Notes: In my testing, with >200 applications and ~1,000 virtual machines this took 30 seconds to execute.
       If the get_applications call in the Ravello Python SDK supported dumping design information this could be dramatically reduced.
jlabocki <at> redhat.com or @jameslabocki on twitter
'''
import os
import re
import argparse
import ConfigParser
import requests
import json
from argparse import ArgumentParser
import base64
import getpass
import logging
import logging.handlers
from ravello_sdk import *
def get_credentials():
    with open(os.path.expanduser("~/.ravello_login"),"r") as pf:
        username = pf.readline().strip()
        encrypted_password = pf.readline().strip()
    password = base64.b64decode(encrypted_password).decode()
    return username,password
def get_user_credentials(username):
    password = None
    if username:
        password = getpass.getpass('Enter a Password: ')
    else:
        #read user credentials from .ravello_login file in user HOMEDIR
        username,password = get_credentials()
    if not username or not password:
        log.error('User credentials are not set')
        print('Error: User credentials are not set')
        return None,None
    return username,password
def connect(username, password):
        client = RavelloClient()
        try:
                client.login(username, password)
        except Exception as e:
                print('Error: Invalid user credentials, username {0}'.format(username))
                return None
        return client
def get_app_id(app_name,client):
        app_id=0
        for app in client.get_applications():
                if app['name'].lower() == app_name.lower():
                        app_id = app['id']
                        break
        return app_id
class RavelloInventory(object):
    def _empty_inventory(self):
        return {"_meta" : {"hostvars" : {}}}
    def __init__(self):
        ''' Main execution path '''
        # Inventory grouped by instance IDs, tags, security groups, regions,
        # and availability zones
        self.inventory = self._empty_inventory()
        # Index of hostname (address) to instance ID
        self.index = {}
        # Read CLI arguments
        self.read_settings()
        self.parse_cli_args()
        # If --apps is set then run get_apps_all
        #if self.args.apps is True:
        #  self.get_apps_all()
        # If --list is set then run get_app with ID of application
        if self.args.list is not None:
          self.get_app()
    def parse_cli_args(self):
        ''' Command line argument processing '''
        parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on Ravello')
        parser.add_argument('--apps', action='store_false',
                           help='List all app names (default: False)')
        parser.add_argument('--list', action='store', default=False,
                           help='Get the group(s) and hostname(s) from a specific application by specifying the app name')
        self.args = parser.parse_args()
    def read_settings(self):
        ''' Reads the settings from the ravello.ini file '''
        config = ConfigParser.SafeConfigParser()
        config_paths = [
            os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ravello.ini'),
            "/etc/ansible/ravello.ini",
        ]
        env_value = os.environ.get('RAVELLO_INI_PATH')
        if env_value is not None:
            config_paths.append(os.path.expanduser(os.path.expandvars(env_value)))
        config.read(config_paths)
        # Get Auth from INI
        INI=True
        if config.has_option('ravello', 'username'):
            self.ravello_username = config.get('ravello', 'username')
        else:
            self.ravello_username = "none"
            INI=False
        if config.has_option('ravello', 'password'):
            self.ravello_password = config.get('ravello', 'password')
        else:
            self.ravello_password = "none"
            INI=False
        if INI is False:
            self.ravello_username, self.ravello_password  = get_user_credentials(None)
        if not self.ravello_username or not self.ravello_password:
            print("ERROR: Could not get Ravello credentials from INI file or .ravello_login (SDK Auth)")
            exit(1)
    def get_apps_all(self):
        #Connect to Ravello
        client = connect(self.ravello_username, self.ravello_password)
        if not client:
            exit (1)
        apps = client.get_applications()
        names = []
        for app in apps:
          #Only get the published apps
          if app['published']:
            myname = (json.dumps(app['name']))
            names.append(myname)
        for name in names:
          print name
    def get_app(self):
        #Connect to Ravello
        myappname = self.args.list
        client = connect(self.ravello_username, self.ravello_password)
        if not client:
                exit (1)
        apps = client.get_applications()
        myappid = ""
        for app in apps:
          #Only get the published apps
          if app['published']:
            if str(app['name']) == myappname:
              myappid = app['id']
        #First, define empty lists for the the tags, groups, subgroups for tags/vms, and the formatted list for tower.
        groups = {}
        groups['_meta'] = {}
        groups['_meta']['hostvars'] = {}
        app = client.get_application(myappid, aspect="deployment")
        if app['deployment']:
          appname = app['name']
          vmsFlag = True if "vms" in app["deployment"] else False
          if vmsFlag == True:
            vms = app['deployment']['vms']
            for vm in vms:
              #if 'externalFqdn' in vm:
              #  hostname = vm['externalFqdn']
              #else:
              hostnames = vm['hostnames']
              hostname = hostnames[0]
              desc = vm['description']
              for line in desc.splitlines():
                if re.match("^tag:", line):
                  t = line.split(':')
                  tag = t[1]
                  if tag in groups.keys():
                    groups[tag]['hosts'].append(hostname)
                  else:
                    groups[tag] = {}
                    groups[tag]['hosts'] = {}
                    groups[tag]['hosts'] = [hostname]
                  if 'externalFqdn' in vm:
                    groups['_meta']['hostvars'][hostname] = { 'externalFqdn': vm['externalFqdn'] }
                  if tag == 'bastion' and 'externalFqdn' in vm:
                    groups['_meta']['hostvars'][hostname].update({ 'bastion': True })
        print json.dumps(groups, indent=5)
#Run the script
RavelloInventory()
ansible/cloud_providers/ravello/inventory/ravello.ini
New file
@@ -0,0 +1,11 @@
# Ansible Ravello external inventory script settings
# Uncomment and add your credentials
# Alternative, use SDK to configure ~/.ravello_login
[ravello]
# Username for ravello
#username = yourusername
# Password for ravello
#password = yourpassword
ansible/cloud_providers/ravello/library/ravello_app.py
New file
@@ -0,0 +1,672 @@
#!/usr/bin/python
# (c) 2015, ravellosystems
#
# author zoza
#
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
######################################################################
import random, string
try:
    from ravello_sdk import *
    HAS_RAVELLO_SDK = True
except ImportError:
    HAS_RAVELLO_SDK = False
except ImportError:
    print "failed=True msg='ravello sdk required for this module'"
    sys.exit(1)
from ravello_cli import get_diskimage
DOCUMENTATION = '''
---
module: ravello_app
short_description: Create/delete/start/stop an application in ravellosystems
description:
     - Create/delete/start/stop an application in ravellosystems and wait for it (optionally) to be 'running'
     - list state will return a fqdn list of exist application hosts with their external services
     - blueprint state wil create a blueprint from an existing app (must provide blueprint_name)
options:
  state:
    description:
     - Indicate desired state of the target.
    default: present
    choices: ['design', 'present', 'started', 'absent', 'stopped','list','blueprint']
  username:
     description:
      - ravello username
  password:
    description:
     - ravello password
  service_name:
      description:
     - Supplied Service name for list state
    default: ssh
  name:
    description:
     - application name
  description:
    description:
     - application description
  blueprint_id:
    description:
     - create app, based on this blueprint
  #publish options
  cloud:
    description:
     - cloud to publish
  region:
    description:
     - region to publish
  publish_optimization:
    default: cost
    choices: ['cost', 'performance']
  application_ttl:
    description:
     - application autostop in mins
    default: -1 # never
  wait
    description:
     - Wait for the app to be in state 'running' before returning.
    default: True
    choices: [ True, False ]
  wait_timeout:
    description:
     - How long before wait gives up, in seconds.
    default: 600
  blueprint_name:
    description:
     - Specify a name for a new blueprint based on existing app
  blueprint_description:
    description:
     - Description of new blueprint
  app_template:
    description:
     - Path to a YML file that defines an application infrastructure then creates a blueprint for further processing with follow-on playbooks.  Must use state=design
'''
EXAMPLES = '''
# Create app, based on blueprint, start it and wait for started
- local_action:
    module: ravello_app
    username: user@ravello.com
    password: password
    name: 'my-application-name'
    description: 'app desc'
    blueprint_id: '2452'
    wait: True
    wait_timeout: 600
    state: present
# Create app, based on blueprint
- local_action:
    module: ravello_app
    username: user@ravello.com
    password: password
    name: 'my-application-name'
    description: 'app desc'
    publish_optimization: performance
    cloud:AMAZON
    region: Oregon
    state: present
# List application example
- local_action:
    module: ravello_app
    name: 'my-application-name'
    service_name: 'ssh'
    state: list
# Delete application example
- local_action:
    module: ravello_app
    name: 'my-application-name'
    state: absent
# Create blueprint from existing app
- local_action:
    module: ravello_app
    name: 'my-application-name'
    blueprint_name: 'my-application-bp'
    blueprint_description: 'Blueprint of app xyz'
    state: blueprint
# Create blueprint based on app_template.yml
- local_action:
    module: ravello_app
    name: 'my-new-baseline'
    description: 'My new baseline'
    app_template: 'app_template.yml'
    state: design
  register: design_results
'''
import os
import base64
import getpass
import logging
import logging.handlers
def get_credentials():
        with open(os.path.expanduser("~/.ravello_login"),"r") as pf:
                username = pf.readline().strip()
                encrypted_password = pf.readline().strip()
        password = base64.b64decode(encrypted_password).decode()
        return username,password
def get_user_credentials(username):
        password = None
        if username:
                password = getpass.getpass('Enter a Password: ')
        else:
                #read user credentials from .ravello_login file in user HOMEDIR
                username,password = get_credentials()
        if not username or not password:
                log.error('User credentials are not set')
                print('Error: User credentials are not set')
                return None,None
        return username,password
def initlog(log_file):
        logger = logging.getLogger()
        logger.setLevel(logging.INFO)
        logpath=os.path.join(os.getcwd(),log_file)
        handler = logging.handlers.RotatingFileHandler(logpath, maxBytes=1048576, backupCount=10)
        fmt = '%(asctime)s: %(filename)-20s %(levelname)-8s %(message)s'
        handler.setFormatter(logging.Formatter(fmt))
        logger.addHandler(handler)
def connect(username, password):
        client = RavelloClient()
        try:
                client.login(username, password)
        except Exception as e:
                sys.stderr.write('Error: {!s}\n'.format(e))
                log.error('Invalid user credentials, username {0}'.format(username))
                print('Error: Invalid user credentials, username {0}'.format(username))
                return None
        return client
def get_app_id(app_name,client):
        app_id=0
        for app in client.get_applications():
                if app['app_name'].lower() == app_name.lower():
                        app_id = app['id']
                        break
        if app_id == 0:
          module.fail_json(msg = 'ERROR: Cloud not find app: %s' % app_name)
        return app_id
def get_blueprint_id(blueprint_name,client):
        blueprint_id=0
        for blueprint in client.get_blueprints():
                if blueprint['name'].lower() == blueprint_name.lower():
                        blueprint_id = blueprint['id']
                        break
        if blueprint_id == 0:
          module.fail_json(msg = 'ERROR: Cloud not find blueprint: %s' % blueprint_name)
        return blueprint_id
def get_image_id(image_name,client):
        image_id=0
        for image in client.get_images():
                if image['name'].lower() == image_name.lower():
                        image_id = image['id']
                        break
        if image_id == 0:
          module.fail_json(msg = 'ERROR: Cloud not find VM image named: %s' % image_name)
        return image_id
def get_image(image_id,client):
        try:
          image = client.get_image(image_id)
        except Exception as e:
          module.fail_json(msg = 'ERROR: Cloud not find VM image id: %s' % image_id)
        return image
def main():
    ch = logging.StreamHandler(log_capture_string)
    ch.setLevel(logging.DEBUG)
    ### Optionally add a formatter
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    ch.setFormatter(formatter)
    ### Add the console handler to the logger
    logger.addHandler(ch)
    argument_spec=dict(
            # for nested babu only
            url=dict(required=False, type='str'),
            state=dict(default='present', choices=['design', 'present', 'started', 'absent', 'stopped', 'list', 'test', 'blueprint','blueprint_delete','blueprint_location']),
            username=dict(required=False, type='str'),
            password=dict(required=False, type='str'),
            name=dict(required=False, type='str'),
            app_name=dict(required=False, type='str'),
            description=dict(required=False, type='str'),
            blueprint_id=dict(required=False, type='str'),
            app_template=dict(required=False, default=None, type='path'),
            cloud=dict(required=False, type='str'),
            region=dict(required=False, type='str'),
            publish_optimization=dict(default='cost', choices=['cost', 'performance']),
            application_ttl=dict(default='-1', type='int'),
            service_name=dict(default='ssh', type='str'),
            blueprint_description=dict(required=False, type='str'),
            blueprint_name=dict(required=False, type='str'),
            wait=dict(type='bool', default=True ,choices=BOOLEANS),
            wait_timeout=dict(default=1200, type='int')
    )
    module = AnsibleModule(
        argument_spec=argument_spec,
        mutually_exclusive=[['blueprint', 'app_template']],
        # We really really should support this...
        # supports_check_mode = True
    )
    if not HAS_RAVELLO_SDK:
      module.fail_json(msg='ravello_sdk required for this module')
    # Get User credentials from Ansible (not too secure) or ENV variables (a little more secure)
    username = module.params.get('username', os.environ.get('RAVELLO_USERNAME', None))
    password = module.params.get('password', os.environ.get('RAVELLO_PASSWORD', None))
    if username and password:
      try:
        client = RavelloClient(username, password, module.params.get('url'))
      except Exception, e:
        log_contents = log_capture_string.getvalue()
        log_capture_string.close()
        module.fail_json(msg = 'ERROR: Failed to authenticate to Ravello using ansiblie provided credentials %s' % e,stdout='%s' % log_contents)
    else:
      #Get user credentials from SDK auth cache file (better)
      try:
        username, password  = get_user_credentials(None)
      except Exception, e:
        log_contents = log_capture_string.getvalue()
        log_capture_string.close()
        module.fail_json(msg = 'ERROR: Failed to retrieve credentials from Ravello SDK credentials cache %s' % e,stdout='%s' % log_contents)
      if not username or not password:
        module.fail_json(msg = 'ERROR: Unable to get any Ravello credentials!')
      try:
        client = connect(username, password)
      except Exception, e:
        log_contents = log_capture_string.getvalue()
        log_capture_string.close()
        module.fail_json(msg = 'ERROR: Failed to authenticate to Ravello using Ravello SDK credentials cache %s' % e,stdout='%s' % log_contents)
    if module.params.get('state') == 'design':
      create_app(client, module)
    elif module.params.get('state') == 'present':
      create_app_and_publish(client, module)
    elif module.params.get('state') == 'absent':
      action_on_app(module, client, client.delete_application, lambda: None, 'Deleted')
    elif module.params.get('state') == 'started':
      action_on_app(module, client, client.start_application, functools.partial(_wait_for_state,client,'STARTED',module), 'Started')
    elif module.params.get('state') == 'stopped':
      action_on_app(module, client, client.stop_application, functools.partial(_wait_for_state,client,'STOPPED',module), 'Stopped')
    elif module.params.get('state') == 'list':
      list_app(client, module)
    elif module.params.get('state') == 'blueprint':
      create_blueprint(module, client, client.create_blueprint)
    elif module.params.get('state') == 'blueprint_delete':
      action_on_blueprint(module, client, client.delete_blueprint)
    elif module.params.get('state') == 'blueprint_location':
      action_on_blueprint(module, client, client.get_blueprint_publish_locations)
    elif module.params.get('state') == 'test':
      module.exit_json(msg = 'Authentication to Ravello successful')
def _wait_for_state(client, state, module):
    if module.params.get('wait') == False:
        return
    wait_timeout = module.params.get('wait_timeout')
    app_id = 0
    wait_till = time.time() + wait_timeout
    while wait_till > time.time():
        if app_id > 0:
            app = client.get_application(app_id)
        else:
            app =  client.get_application_by_name(module.params.get('app_name'))
            app_id = app['id']
        states = list(set((vm['state'] for vm in app.get('deployment', {}).get('vms', []))))
        if "ERROR" in states:
            log_contents = log_capture_string.getvalue()
            log_capture_string.close()
            module.fail_json(msg = 'Vm got ERROR state',stdout='%s' % log_contents)
        if len(states) == 1 and states[0] == state:
            return
        time.sleep(10)
    log_contents = log_capture_string.getvalue()
    log_capture_string.close()
    module.fail_json(msg = 'Timed out waiting for async operation to complete.',  stdout='%s' % log_contents)
def is_wait_for_external_service(supplied_service,module):
    return supplied_service['name'].lower() == module.params.get('service_name').lower() and supplied_service['external'] == True
def get_list_app_vm_result(app, vm, module):
    for supplied_service in vm['suppliedServices']:
        if is_wait_for_external_service(supplied_service, module):
            for network_connection in vm['networkConnections']:
                if network_connection['ipConfig']['id'] == supplied_service['ipConfigLuid']:
                    dest = network_connection['ipConfig'].get('fqdn')
                    port = int(supplied_service['externalPort'].split(",")[0].split("-")[0])
                    return (dest,port)
def list_app(client, module):
    try:
        app_name = module.params.get("app_name")
        app = client.get_application_by_name(app_name)
        results = []
        for vm in app['deployment']['vms']:
            if vm['state'] != "STARTED":
                continue
            (dest,port) = get_list_app_vm_result(app, vm, module)
            results.append({'host': dest, 'port': port})
        log_contents = log_capture_string.getvalue()
        log_capture_string.close()
        module.exit_json(changed=True, app_name='%s' % app_name, results='%s' % results,stdout='%s' % log_contents)
    except Exception, e:
        log_contents = log_capture_string.getvalue()
        log_capture_string.close()
        module.fail_json(msg = '%s' % e,stdout='%s' % log_contents)
def action_on_app(module, client, runner_func, waiter_func, action):
    try:
        app_name = module.params.get("app_name")
        app = client.get_application_by_name(app_name)
        runner_func(app['id'])
        waiter_func()
        log_contents = log_capture_string.getvalue()
        log_capture_string.close()
        module.exit_json(changed=True, app_name='%s application: %s' %(action, app_name),stdout='%s' % log_contents)
    except Exception, e:
        log_contents = log_capture_string.getvalue()
        log_capture_string.close()
        module.fail_json(msg = '%s' % e,stdout='%s' % log_contents)
def create_blueprint(module, client, runner_func):
    app_name = module.params.get("app_name")
    app = client.get_application_by_name(app_name)
    blueprint_name = module.params.get("blueprint_name")
    blueprint_description = module.params.get("blueprint_description")
    blueprint_dict = {"applicationId":app['id'], "blueprintName":blueprint_name, "offline": True,  "description":blueprint_description }
    try:
        blueprint_id=((runner_func(blueprint_dict))['_href'].split('/'))[2]
        log_contents = log_capture_string.getvalue()
        log_capture_string.close()
        module.exit_json(changed=True, app_name='%s' % app_name, blueprint_name='%s' % blueprint_name, blueprint_id='%s' % blueprint_id)
    except Exception, e:
        log_contents = log_capture_string.getvalue()
        log_capture_string.close()
        module.fail_json(msg = '%s' % e,stdout='%s' % log_contents)
def action_on_blueprint(module, client, runner_func):
    if module.params.get("blueprint_id"):
      blueprint_id = module.params.get("blueprint_id")
    elif module.params.get("blueprint_name"):
      blueprint_name = module.params.get("blueprint_name")
      blueprint_id = get_blueprint_id(blueprint_name, client)
    try:
        output = runner_func(blueprint_id)
        log_contents = log_capture_string.getvalue()
        log_capture_string.close()
        module.exit_json(changed=True, stdout='%s' % log_contents, blueprint_id='%s' % blueprint_id, output='%s' % output)
    except Exception, e:
        log_contents = log_capture_string.getvalue()
        log_capture_string.close()
        module.fail_json(msg = '%s' % e,stdout='%s' % log_contents)
def create_app(client, module):
    app_name = module.params.get("app_name")
    cap = client.get_applications({'name': app_name})
    if cap:
      module.fail_json(msg='ERROR: Application %s already exists!' % app_name, changed=False)
    blueprint_name = app_name + "-bp"
    bp = client.get_blueprints({'name': blueprint_name})
    if bp:
      module.fail_json(msg='ERROR: Blueprint %s already exists!' % blueprint_name, changed=False)
    app_description = module.params.get("description")
    if not module.params.get("app_template"):
        module.fail_json(msg='Must supply an app_template for design state.', changed=False)
    app_template = module.params.get("app_template")
    with open(app_template, 'r') as data:
      try:
        read_app = yaml.load(data)
      except yaml.YAMLError as exc:
        print(exc)
    rand_str = lambda n: ''.join([random.choice(string.lowercase) for i in xrange(n)])
    new_app = {}
    new_app['name'] = "tmp-app-build-" + rand_str(10)
    new_app['description'] = app_description
    new_app['design'] = {}
    new_app['design']['vms'] = []
    for vm in read_app['vms']:
      pubip = False
      if not 'description' in vm:
        vm['description'] = ""
      if 'tag' in vm:
        vm['description'] = vm['description'] + "\ntag:" + vm['tag'] + "\n"
      if not 'numCpus' in vm:
        module.fail_json(msg = 'ERROR numCpus not specified for VM!')
      new_vm = {'name': vm['name'],
                'description': vm['description'],
                'baseVmId': 0,
                'os': 'linux_manuel',
                'numCpus': vm['numCpus']
               }
      if 'hostnames' in vm:
        new_vm['hostnames'] = vm['hostnames']
      if not 'memorySize' in vm:
        module.fail_json(msg = 'ERROR memorySize subsection not specified for VM!')
      else:
        new_vm['memorySize'] = { 'unit': vm['memorySize']['unit'],
                                 'value': vm['memorySize']['value']
                               }
      if 'keypairName' in vm:
        new_vm['keypairName'] = vm['keypairName']
      if 'supportsCloudInit' in vm:
        new_vm['supportsCloudInit'] = vm['supportsCloudInit']
      if 'stopTimeOut' in vm:
        new_vm['stopTimeOut'] = vm['stopTimeOut']
      else:
        new_vm['stopTimeOut'] = 300
      if 'allowNested' in vm:
        new_vm['allowNested'] = vm['allowNested']
      if 'bootOrder' in vm:
        new_vm['bootOrder'] = vm['bootOrder']
      else:
        new_vm['bootOrder'] = ['DISK', 'CDROM']
      if not 'hardDrives' in vm:
        module.fail_json(msg = 'ERROR no hardDrives subsection defined in template!')
      drives = new_vm['hardDrives'] = []
      for hd in vm['hardDrives']:
        if not 'index' in hd:
          module.fail_json(msg = 'You must specify an index for all HDs!')
        if not 'type' in hd:
          hd['type'] = "DISK"
        if hd['type'] != "DISK" and hd['type'] != "CDROM":
          module.fail_json(msg = 'For HD type specify DISK or CDROM!')
        if not 'controller' in hd:
          hd['controller'] = "virtio"
        if hd['controller'] != "virtio" and hd['controller'] != "ide":
          module.fail_json(msg = 'For HD controller specify virtio or ide!')
        if not 'boot' in hd:
          hd['boot'] = False
        if not 'name' in hd:
          hd['name'] = "Disk ", hd['index']
        new_drive = { 'index': hd['index'],
                      'type': hd['type'],
                      'boot': hd['boot'],
                      'controller': hd['controller'],
                      'name': hd['name'],
                    }
        if not 'size' in hd:
          module.fail_json(msg = 'ERROR HD size not specified for VM!')
        else:
          if not 'unit' in hd['size']:
            module.fail_json(msg = 'ERROR HD size unit not defined')
          if not 'value' in hd['size']:
            module.fail_json(msg = 'ERROR HD size value not defined')
          if hd['size']['unit'] != "GB" and hd['size']['unit'] != "MB":
            module.fail_json(msg = 'ERROR HD size unit must be GB or MB')
          if not int(hd['size']['value']):
            module.fail_json(msg = 'ERROR HD size value must be an int')
          new_drive['size'] = { 'unit': hd['size']['unit'],
                                'value': hd['size']['value']
                              }
        image = {}
        if 'baseDiskImageId' in hd:
          image = get_diskimage(client, hd['baseDiskImageId'])
          if image is None:
            module.fail_json(msg = 'FATAL ERROR nonexistent baseDiskImageId %s specified!' % hd['baseDiskImageId'])
        elif 'imageName' in hd:
          image = get_diskimage(client, hd['imageName'])
          if image is None:
            module.fail_json(msg = 'FATAL ERROR nonexistent imageName %s specified!' % hd['imageName'])
        if 'baseDiskImageId' in hd or 'imageName' in hd:
          if hd['size']['value'] < image['size']['value']:
            module.fail_json(msg = 'ERROR HD size value (%s) is smaller than the image (%s)' % (hd['size']['value'], image['size']['value']))
          else:
            new_drive['baseDiskImageId'] = image['id']
        #else:
        #    new_drive['baseDiskImageId'] = 0
        drives.append(new_drive)
      if not 'networkConnections' in vm:
         module.fail_json(msg = 'FATAL ERROR networkConnections subsection not configured in template!')
      connections = new_vm['networkConnections'] = []
      for nic in vm['networkConnections']:
        if not 'device' in nic:
         module.fail_json(msg = 'FATAL ERROR device subsection not configured in networkConnection!')
        if not 'ipConfig' in nic:
         module.fail_json(msg = 'FATAL ERROR ipConfig subsection not configured in networkConnection!')
        if not 'index' in nic['device']:
          module.fail_json(msg = 'You must specify an index for all NICs!')
        if not 'name' in nic['device']:
          nic['device']['name'] = "Nic ", nic['device']['index']
        if not 'deviceType' in nic['device']:
          nic['device']['deviceType'] = "virtio"
        if nic['device']['deviceType'] != "virtio" and nic['device']['deviceType'] != "e1000":
          module.fail_json(msg = 'For NIC device deviceType specify virtio or e1000!')
        new_nic = { 'name': nic['name'] }
        new_nic['device'] = { 'index': nic['device']['index'],
                              'deviceType': nic['device']['deviceType']
                            }
        if 'useAutomaticMac' in nic['device']:
          if nic['device']['useAutomaticMac'] == False:
            new_nic['device']['useAutomaticMac'] = False
            if 'mac' in nic['device']:
              new_nic['device']['mac'] = nic['device']['mac']
            else:
              module.fail_json(msg = 'ERROR useAutomaticMac set to False but no static mac set for VM %s NIC index %s!' % (new_vm['name'], new_nic['device']['index']))
          else:
            new_nic['device']['useAutomaticMac'] = True
        new_nic['ipConfig'] = {}
        if 'autoIpConfig' in nic['ipConfig']:
          if 'reservedIp' in nic['ipConfig']['autoIpConfig']:
            new_nic['ipConfig']['autoIpConfig'] = { 'reservedIp': nic['ipConfig']['autoIpConfig']['reservedIp'] }
        elif 'staticIpConfig' in nic['ipConfig']:
          if not 'ip' in nic['ipConfig']['staticIpConfig']:
            module.fail_json(msg = 'FATAL ERROR ipConfig/staticIpConfig is missing ip!')
          if not 'mask' in nic['ipConfig']['staticIpConfig']:
            module.fail_json(msg = 'FATAL ERROR ipConfig/staticIpConfig is missing mask!')
          new_nic['ipConfig']['staticIpConfig'] = { 'ip': nic['ipConfig']['staticIpConfig']['ip'],
                                                    'mask': nic['ipConfig']['staticIpConfig']['mask']
                                                  }
          if 'gateway' in nic['ipConfig']['staticIpConfig']:
            new_nic['ipConfig']['staticIpConfig']['gateway'] = nic['ipConfig']['staticIpConfig']['gateway']
          if 'dns' in nic['ipConfig']['staticIpConfig']:
            new_nic['ipConfig']['staticIpConfig']['dns'] = nic['ipConfig']['staticIpConfig']['dns']
        if 'hasPublicIp' in nic['ipConfig']:
          new_nic['ipConfig']['hasPublicIp'] = True
          pubip = True
        connections.append(new_nic)
      if pubip and 'suppliedServices' in vm:
        services = new_vm['suppliedServices'] = []
        for svc in vm['suppliedServices']:
          if not 'name' in svc:
            module.fail_json(msg = 'FATAL ERROR supplied service missing name!')
          if not 'ip' in svc:
            module.fail_json(msg = 'FATAL ERROR supplied service missing ip!')
          if not 'portRange' in svc:
            module.fail_json(msg = 'FATAL ERROR supplied service missing portRange!')
          new_svc = { 'external': True,
                      'name': svc['name'],
                      'ip': svc['ip'],
                      'portRange': svc['portRange']
                    }
          if 'protocol' in svc:
            new_svc['protocol'] = svc['protocol']
          services.append(new_svc)
      new_app['design']['vms'].append(new_vm)
    try:
        created_app = client.create_application(new_app)
    except Exception, e:
        log_contents = log_capture_string.getvalue()
        log_capture_string.close()
        module.fail_json(msg = '%s' % e,stdout='%s' % log_contents, jsonout='%s' % new_app)
    appID = created_app['id']
    blueprint_dict = {"applicationId":appID, "blueprintName":blueprint_name, "offline": False, "description":app_description }
    try:
        blueprint_id=((client.create_blueprint(blueprint_dict))['_href'].split('/'))[2]
        client.delete_application(created_app)
        module.exit_json(changed=True, app_name='%s' % app_name, blueprint_name='%s' % blueprint_name, blueprint_id='%s' % blueprint_id)
    except Exception, e:
        log_contents = log_capture_string.getvalue()
        log_capture_string.close()
        module.fail_json(msg = '%s' % e,stdout='%s' % log_contents)
def create_app_and_publish(client, module):
    #validation
    if not module.params.get("blueprint_id"):
            module.fail_json(msg='Must supply a blueprint_id', changed=False)
    if 'performance' == module.params.get("publish_optimization"):
        if not module.params.get("cloud"):
            module.fail_json(msg='Must supply a cloud when publish optimization is performance', changed=False)
        if not module.params.get("region"):
            module.fail_json(msg='Must supply a region when publish optimization is performance', changed=False)
    app = {'name': module.params.get("app_name"), 'description': module.params.get("description",''), 'baseBlueprintId': module.params.get("blueprint_id")}
    app = client.create_application(app)
    req = {}
    if 'performance' == module.params.get("publish_optimization"):
        req = {'id': app['id'] ,'preferredCloud': module.params.get("cloud"),'preferredRegion': module.params.get("region"), 'optimizationLevel': 'PERFORMANCE_OPTIMIZED'}
    ttl=module.params.get("application_ttl")
    if ttl != -1:
        ttl =ttl * 60
        exp_req = {'expirationFromNowSeconds': ttl}
        client.set_application_expiration(app,exp_req)
    client.publish_application(app, req)
    _wait_for_state(client,'STARTED',module)
    log_contents = log_capture_string.getvalue()
    log_capture_string.close()
    module.exit_json(changed=True, app_name='%s' % module.params.get("app_name"),stdout='%s' % log_contents, app_id='%s' % app['id'])
# import module snippets
import ansible
import os
import functools
import logging
import io
import datetime
import sys
import yaml
import json
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
log_capture_string = io.BytesIO()
from ansible.module_utils.basic import *
main()
ansible/cloud_providers/ravello/roles/application_create_from_blueprint/tasks/main.yml
New file
@@ -0,0 +1,40 @@
---
- name: display variables for create application based on deployed blueprint and wait for start
  debug:
    var: "{{ item }}"
    verbosity: 1
  with_items:
    - application_name
    - application_description
    - blueprint_id
    - wait_timeout
- name: create application based on deployed blueprint and wait for start
  local_action:
    module: ravello_app
    app_name: "{{ application_name }}"
    description: "{{ application_description }}"
    state: present
    blueprint_id: "{{ blueprint_id }}"
    wait_timeout: "{{ wait_timeout }}"
  register: app_results
- debug:
    var: "{{ item }}"
    verbosity: 1
  with_items:
  - app_results.app_name
  - app_results.app_id
- name: delete blueprint used to create application
  local_action:
    blueprint_name: "{{ design_results.app_name + '-bp' }}"
    blueprint_id: "{{ design_results.blueprint_id }}"
    app_name: "{{ app_results.app_name }}"
    module: ravello_app
    state: blueprint_delete
  register: bp_delete_results
- debug:
    var: bp_delete_results
    verbosity: 1
ansible/cloud_providers/ravello/roles/blueprint_create_from_application/tasks/main.yml
New file
@@ -0,0 +1,33 @@
---
- name: display variables for create blueprint from application
  debug:
    var: "{{ item }}"
    verbosity: 1
  with_items:
    - application_name
    - blueprint_name
    - blueprint_description
- name: stop application
  local_action:
    module: ravello_app
    app_name: "{{ application_name }}"
    state: stopped
  register: app_stop_results
- debug:
    var: app_stop_results
    verbosity: 1
- name: create blueprint from application
  local_action:
    module: ravello_app
    app_name: "{{ application_name }}"
    blueprint_name: "{{ blueprint_name }}"
    blueprint_description: "{{ 'blueprint created via ansible from ' + application_name }}"
    state: blueprint
  register: bp_create_results
- debug:
    var: bp_create_results
    verbosity: 1
ansible/cloud_providers/ravello/roles/blueprint_design/tasks/main.yml
New file
@@ -0,0 +1,24 @@
---
- name: display variables for create blueprint based on {{ app_template }}
  debug:
    var: "{{ item }}"
    verbosity: 1
  with_items:
    - blueprint_name
    - blueprint_description
- name: create blueprint based on {{ app_template }}
  local_action:
    module: ravello_app
    app_name: "{{ blueprint_name }}"
    description: "{{ blueprint_description }}"
    app_template: '{{ app_template}}'
    state: design
  register: design_results
- debug:
    var: "{{ item }}"
    verbosity: 1
  with_items:
    - design_results.name
    - design_results.blueprint_id
ansible/cloud_providers/ravello/roles/warm_up/tasks/main.yml
New file
@@ -0,0 +1,24 @@
---
- name: display variables
  debug:
    var: "{{ item }}"
    verbosity: 1
  with_items:
    - unique_name
    - version
    - application_name
    - application_description
    - blueprint_name
    - blueprint_id
    - wait_timeout
- name: test login
  local_action:
    module: ravello_app
    app_name: "{{ application_name }}"
    state: test
  register: test_results
- debug:
    var: test_results
    verbosity: 1
ansible/cloud_providers/ravello/save_environment.yml
New file
@@ -0,0 +1,12 @@
# Create blueprint from existing app
---
  - hosts: localhost
    tasks:
     - local_action:
         module: ravello_app
         name: 'ansible-brett-demo-version1.1-app-v1.0'
#         app_name: 'brett-demo-version1.1'
#        state: 'stopped'
         blueprint_name: 'test-brett-bp-save'
         blueprint_description: 'Blueprint of app xyz'
         state: blueprint