#!/usr/local/bin/python3.8
# -*- coding: utf-8 -*-

# Copyright 2015, Perceivon Hosting Inc.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY [COPYRIGHT HOLDER] AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL [COPYRIGHT HOLDER] OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import sys
import re

DOCUMENTATION = """
---
module: 
version_added:
short_description: FreeBSD iocage jail handling
description:
    - The M(iocage) module allows several iocage commands to be executed through ansible.
    - document use-cases here
options:
    state:
      description:
          - state of the desired result
      choices: ['basejail', 'thickjail',  'template', 'present', 'cloned', 'started', 'stopped', 'restarted', 'fetched', 'exec', 'pkg', 'exists', 'absent', 'set']
      required: true
      default: exec
      required: false
    name:
      description:
          - name of the jail (former uuid)
      required: false
    pkglist:
      description:
          - path to a JSON file containing packages to install. Only applicable when creating a jail.
      required: false
author: xmj
"""

EXAMPLES = """
# create jail (without cloning):
- iocage:
    name: "foo"
    state: present
    pkglist: /path/to/pkglist.json
    properties:
      ip4_addr: 'lo1|10.1.0.5'
      boot: 'on'
      allow_sysvipc: 1
      defaultrouter: '10.1.0.1'

# create template:
- iocage:
    name: "tplfoo"
    state: template
    pkglist: /path/to/pkglist.json
    properties:
      ip4_addr: 'lo1|10.1.0.5'
      boot: 'on'
      allow_sysvipc: 1
      defaultrouter: '10.1.0.1'

# create a cloned jail (creates basejail if needed):
- iocage:
    name: "foo"
    state: present
    clone_from: "tplfoo"
    pkglist: /path/to/pkglist.json
    properties:
      ip4_addr: 'lo1|10.1.0.5'
      boot: 'on'
      allow_sysvipc: 1
      defaultrouter: '10.1.0.1'

# start existing jail:
- iocage: name="foo" state=started

# stop existing jail:
- iocage: name="foo" state=stopped

# restart existing jail:
- iocage: name="foo" state=restarted

# execute command in (running) jail:
- iocage: name="foo" state=exec cmd="service sshd start"

# force destroy jail
- iocage: name="foo" state=absent
"""

def _get_iocage_facts(module, iocage_path, argument="all", name=None):
    if argument == 'jails':
        cmd = "{0} list -hl".format(iocage_path)
    elif argument == 'templates':
        cmd = "{0} list -hlt".format(iocage_path)
    elif argument == 'releases':
        cmd = "{0} list -hr".format(iocage_path)
    elif argument == 'init':
        cmd = "{0} list -h".format(iocage_path)
    elif argument == "all":
        _init = _get_iocage_facts(module, iocage_path, "init")
        _jails = _get_iocage_facts(module, iocage_path, "jails")
        _templates = _get_iocage_facts(module, iocage_path, "templates")
        _releases = _get_iocage_facts(module, iocage_path, "releases")
        return { "iocage_jails": _jails, "iocage_templates": _templates, "iocage_releases": _releases }
    else:
        module.fail_json(msg="_get_iocage_facts({0}): argument not understood".format(argument))

    rc, state, err = module.run_command(cmd)

    if rc != 0 and argument != "init":
        module.fail_json(msg="_get_iocage_facts():\ncmd: '{0}' return {1}\nstdout: '{2}'\nstderr: '{3}')".format(cmd, rc, state, err))
    elif argument == "init":
        return {}

    if argument == 'releases':
        _releases=[]
        for l in state.split('\n'):
            if re.match('\s*\d',l):
                _releases.append(l.strip())
        return _releases

    _jails = {}
#    module.fail_json(msg=state)
    try:
        for l in state.split('\n'):
            if l == "":
                continue
            _jid=l.split('\t')[0]
            if _jid == '---':
                # non-iocage jails: skip all
                break
            elif re.match('(\d+|-)',_jid):
                _fragments = l.split('\t')
                if len(_fragments) == 10:
                    (_jid,_name,_boot,_state,_type,_release,_ip4,_ip6,_template,_basejail) = _fragments
                else:
                    (_jid,_name,_boot,_state,_type,_release,_ip4,_ip6,_template) = _fragments
                if _name != "":
                    _properties = _jail_get_properties(module, iocage_path, _name)
                    _jails[_name] = { "jid": _jid, "name": _name, "state": _state, "properties": _properties }
            else:
                module.fail_json(msg="_get_iocage_facts():\nUnreadable stdout line from cmd '{0}': '{1}'".format(cmd,l))
    except:
        module.fail_json(msg="unable to parse {0}".format(state))

    if name:
        if name in _jails:
            return _jails[name]
        else:
            return {}

    return _jails

def _jail_started(module, iocage_path, name):
    cmd = "{0} list -h".format(iocage_path)
    rc, state, err = module.run_command(cmd)
    if rc != 0:
        module.fail_json(msg="jail_started({0}):\ncmd: '{1}' return {2}\nstdout: '{3}'\nstderr: '{4}')".format(name, cmd, rc, state, err))

    st = None
    for l in state.split('\n'):
        u = l.split('\t')[1]
        if u == name:
            s = l.split('\t')[2]
            if s == 'up':
                st = True
                break
            elif s == 'down':
                st = False
                break
            else:
                module.fail_json(msg="Jail {0} unknown state: {1}".format(name, l))

    return st

def jail_exists(module, iocage_path, argument=None, assume_absent=False):
    cmd = "%s get host_hostuuid %s" %(iocage_path, argument)
    rc, name, err = module.run_command(cmd)
    _msg = ""

    if not rc == 0:
        name = ""

    if name != "" and assume_absent:
        module.fail_json(msg="Jail {0} exists.".format(argument))

    return name.strip()

def jail_start(module, iocage_path, name, force=None):
    cmd = "{0} start {1}".format(iocage_path, name)
    if force == "yes":
        cmd = "{0} start {1}".format(iocage_path, name)
    rc = 1
    out = ""
    _msg = ""
    if not module.check_mode:
        rc, out, err = module.run_command(cmd)
        if not rc == 0:
            module.fail_json(msg="Jail {} could not be started.\ncmd:{}\nstdout:\n{}\nstderr:\n{}".format(name, cmd, out, err))
        _msg = "Jail {0} was started.\n{1}".format(name,out)
    else:
        _msg = "Jail {0} would have been started".format(name)

    return True, _msg

def _props_to_str(props):
    argstr = ""
    minargs = ""
    for _prop in props:
        _val = props[_prop]
        if _val == '-' or _val == '' or not _val:
            continue
        if _val in ['on', True]:
            argstr += '{0}=1 '.format(_prop)
        elif _val in ['off', False]:
            argstr += '{0}=0 '.format(_prop)
        elif _val in ['-','none']:
            argstr += '{0}={1} '.format(_prop,_val)
        else:
            argstr += '{0}="{1}" '.format(_prop,str(_val))

    return argstr

def release_fetch(module, iocage_path, update=False, release="NO-RELEASE", components=[], args=""):
    if not module.check_mode:
        if update:
            args += " -U"
        for _component in components:
            if _component != "":
                args += " -F {}".format(_component)
        cmd = "{0} fetch -r {1} {2}".format(iocage_path, release, args)
        rc = 1
        rc, out, err = module.run_command(cmd)
        if not rc == 0:
            module.fail_json(msg="Release {0} could not be fetched.\nstdout:\n{1}\nstderr:\n{2}".format(release, out, err))
        _changed = True
        if update:
             _msg = "Release {0} was successfully updated.".format(release)
        else:
             _msg = "Release {0} was successfully fetched.".format(release)
    else:
        _changed = True
        _msg = "Release {0} would have been fetched".format(release)

    return release, True, _msg

def jail_restart(module, iocage_path, name):
    cmd = "%s restart %s" % (iocage_path, name)
    rc = 1
    out = ""
    _msg = ""
    if not module.check_mode:
        rc, out, err = module.run_command(cmd)
        if not rc == 0:
            module.fail_json(msg="Jail {0} could not be restarted.\nstdout:\n{1}\nstderr:\n{2}".format(name, out, err))
        _msg = "Jail {0} was restarted.\n{1}".format(name,out)
    else:
        _msg = "Jail {0} would have been restarted.".format(name)

    return True, _msg

def jail_stop(module, iocage_path, name):
    cmd = "{0} stop {1}".format(iocage_path, name)
    _changed = False
    rc = 1
    out = ""
    _msg = ""

    if not module.check_mode:
        rc, out, err = module.run_command(cmd)
        if not rc == 0:
            module.fail_json(msg="Jail {0} could not be stopped.\nstdout:\n{1}\nstderr:\n{2}".format(name, out, err))
        _msg = "Jail {0} was stopped.\n".format(name,out)
    else:
        _msg = "Jail {0} would have been stopped".format(name)

    return True, _msg

def jail_exec(module, iocage_path, name, user="root", cmd='/usr/bin/true'):
    rc = 1
    out = ""
    err = ""
    _msg = ""
    if not module.check_mode:
        rc, out, err = module.run_command("{0} exec -u {1} {2} -- {3}".format(iocage_path, user, name, cmd))
        if not rc == 0:
            module.fail_json(msg="Command '{0}' could not be executed in jail '{1}'.\nstdout:\n{2}\nstderr:\n{3}".format(cmd, name, out, err))
        _msg = "Command '{0}' was executed in jail '{1}'.\nstdout:\n{2}\nstderr:\n{3}".format(cmd, name, out, err)
    else:
        _msg = "Command '{0}' would have been executed in jail '{1}'".format(cmd, name)

    _changed = True

    return True, _msg, out, err

def jail_pkg(module, iocage_path, name, cmd='info'):
    rc = 1
    out = ""
    err = ""
    _msg = ""
    if not module.check_mode:
        rc, out, err = module.run_command("{0} pkg {1} {2}".format(iocage_path, name, cmd))
        if not rc == 0:
            module.fail_json(msg="pkg '{0}' could not be executed in jail '{1}'.\nstdout:\n{2}\nstderr:\n{3}".format(cmd, name, out, err))
        _msg = "pkg '{0}' was executed in jail '{1}'.\nstdout:\n{2}\nstderr:\n{3}".format(cmd, name, out, err)
    else:
        _msg = "pkg '{0}' would have been executed in jail '{1}'".format(cmd, name)

    _changed = True

    return _changed, _msg, out, err

def _jail_get_properties(module, iocage_path, name):
    rc = 1
    out = ""
    if name and name != "":
        properties = {}
        cmd = "{0} get all {1}".format(iocage_path, name)
        rc, out, err = module.run_command(cmd)
        if rc == 0:
            _properties = [ l.strip() for l in out.strip().split('\n') ]
            for _property in [ p.split(':',1) for p in _properties]:
                if len(_property) == 2:
                    properties[_property[0]] = _property[1]
                else:
                    module.fail_json(msg="error parsing property {0} from {1}".format(p,str(properties)))
        else:
            module.fail_json(msg="command {0} failed with {1}".format(cmd, err))
    elif module.check_mode and name == "CHECK_MODE_FAKE_UUID":
        properties = { "CHECK_NEW_JAIL": True }
    else:
        module.fail_json(msg="jail {0} not found".format(name))

    return properties

def jail_set(module, iocage_path, name, properties={}):

    _changed = False
    rc = 1
    out = ""
    _msg = ""
    cmd = ""
    _existing_props = _jail_get_properties(module, iocage_path, name)
    _props_to_be_changed = {}
    for _property in properties:
        if _property not in _existing_props:
            continue
        if _existing_props[_property] == '-' and not properties[_property]:
            continue
        if _property == "template":
            continue

        propval=None
        _val = properties[_property]
        _oval = _existing_props[_property]
        if _val in [0,'off',False]:
            propval = 0
        elif _val in [1,'on',True]:
            propval = 1
        elif isinstance(_oval,str):
            if _val == '':
                propval = 'none'
            else:
                propval = '{0}'.format(_val)
        else:
            module.fail_json(msg="Unable to set attribute {0} to {1} for jail {2}".format(_property,str(_val).replace("'","'\\''"),name))

        if 'CHECK_NEW_JAIL' in _existing_props or ( _property in _existing_props.keys() and str(_existing_props[_property]) != str(propval) ) and propval != None:
            _props_to_be_changed[_property] = propval

    if len(_props_to_be_changed) > 0:
        need_restart = False
        for p in _props_to_be_changed.keys():
            if p in ['ip4_addr','ip6_addr','template','interfaces','vnet','host_hostname']:
                need_restart = _jail_started(module, iocage_path, name)

        cmd = '{0} set {1} {2}'.format(iocage_path, _props_to_str(_props_to_be_changed), name)

        if not module.check_mode:
            if need_restart:
                jail_stop(module, iocage_path, name)
            rc, out, err = module.run_command(cmd)
            if need_restart:
                jail_start(module, iocage_path, name)

            if not rc == 0 or ( rc == 1 and "is already a jail!" in err ):
                module.fail_json(msg="Attributes could not be set on jail '{0}'.\ncmd:\n{1}\nstdout:\n{2}\nstderr:\n{3}".format(name, cmd, out, err))

            _msg = "properties {0} were set on jail '{1}' with cmd={2}.".format(str(_props_to_be_changed.keys()) ,name, cmd)


        else:
            _msg = "properties {0} would have been changed for jail {1} with command {2}".format(str(_props_to_be_changed.keys()), name, cmd)
            _msg += str(_props_to_be_changed)
        _changed = True

    else:
        _changed = False
        _msg = "properties {0} already set for jail {1}".format(properties.keys(),name)

    return _changed, _msg

def jail_create(module, iocage_path, name=None, properties={}, clone_from_name=None, clone_from_template=None, release=None, basejail=False, thickjail=False, pkglist=None):
    rc = 1
    out = ""
    _msg = ""

    if clone_from_name == None and clone_from_template == None:
        if basejail:
            cmd = "{0} create -b -r {2} -n {1}".format(iocage_path, name, release)

        elif thickjail:
            cmd = "{0} create -T -r {2} -n {1} {3}".format(iocage_path, name, release, _props_to_str(properties))

        else:
            cmd = "{0} create -r {2} -n {1} {3}".format(iocage_path, name, release, _props_to_str(properties))

        if pkglist:
            cmd += " --pkglist=" + pkglist

    elif clone_from_name:
        cmd = "{0} clone {1} -n {2} {3}".format(iocage_path, clone_from_name, name, _props_to_str(properties))
    elif clone_from_template:
        cmd = "{0} create -t {1} -n {2} {3}".format(iocage_path, clone_from_template, name, _props_to_str(properties))

    if not module.check_mode:
        rc, out, err = module.run_command(cmd)
        if not rc == 0:
            module.fail_json(msg="Jail '{0}' could not be created.\ncmd: {1}\nstdout:\n{2}\nstderr:\n{3}".format(name, cmd, out, err))
        _msg += "Jail '{0}' was created with properties {1}.\n\n{2}".format(name, str(properties), cmd)
        name = jail_exists(module, iocage_path, name)
        if not name:
            module.fail_json(msg="Jail '{0}' not created ???\ncmd: {1}\nstdout:\n{2}\nstderr:\n{3}".format(name, cmd, out, err))

    else:
        _msg += "Jail {0} would be created with command:\n{1}\n".format(name, cmd)
        name = "CHECK_MODE_FAKE_UUID_FOR_{}".format(name)

    return name, True, _msg

def jail_update(module, iocage_path, name):
    _changed = False
    rc = 1
    out = ""
    _msg = ""
    cmd = "{0} update {1}".format(iocage_path, name)
    if not module.check_mode:
        rc, out, err = module.run_command(cmd)
        if not rc == 0:
            module.fail_json(msg="Jail '{0}' not updated: {1} returned {2}\n\t{3}\n\t{4}".format(name, cmd, str(rc), out, err))
        if "No updates needed" in out:
            _changed = False
        elif "updating to" in out:
            nv=re.search(r' ([^ ]*):$', filter((lambda x:'updating to' in x), out.split('\n'))[0]).group(1)
            _msg = "jail {0} updated to {1}".format(name,nv)
            _changed = True
    else:
        _msg = "Unable to check for updates in check_mode"

    return _changed, _msg

def jail_destroy(module, iocage_path, name):
    rc = 1
    out = ""
    _msg = ""
    if not module.check_mode:
        rc, out, err = module.run_command("{0} destroy -f {1}".format(iocage_path, name))
        if not rc == 0:
            module.fail_json(msg="Jail '{0}' could not be destroyed.\nstdout:\n{1}\nstderr:\n{2}".format(name, out, err))
        _msg = "Jail '{0}' was destroyed.".format(name)
        jail_exists(module, iocage_path, name, True)

    else:
        _msg = "Jail {0} would have been destroyed".format(name)

    return name, True, _msg

def main():
    module = AnsibleModule(
        argument_spec    = dict(
            state        = dict(default="facts", choices=["basejail","thickjail", "template", "cloned", "started", "stopped", "restarted", "fetched", "exec", "pkg", "exists", "absent", "set","present","facts"], required=False),
            name         = dict(default="", required=False),
            pkglist      = dict(default=None, required=False),
            stdout       = dict(default="", required=False),
            stderr       = dict(default="", required=False),
            properties   = dict(default={}, required=False, type="dict"),
            args         = dict(default="", required=False),
            force        = dict(default="", required=False),
            user         = dict(default="", required=False),
            cmd          = dict(default="", required=False),
            clone_from   = dict(default="", required=False),
            release      = dict(default="", required=False),
            update       = dict(default=False, required=False, type='bool'),
            components   = dict(default="", aliases=["files","component"], required=False, type='list')),
        supports_check_mode = True
    )

    iocage_path = module.get_bin_path('iocage', True)

    p = module.params

    name = p["name"]
    properties = p["properties"]
    force = p["force"]
    cmd = p["cmd"]
    args = p["args"]
    clone_from= p["clone_from"]
    user = p["user"] or "root"
    release = p["release"]
    update = p["update"]
    components = p["components"]
    upgrade = False
    pkglist = p["pkglist"]

    msgs = []
    changed = False
    out = ""
    err = ""

    facts = _get_iocage_facts(module,iocage_path,"all")

    jails = {}
    for u in facts["iocage_jails"]:
        jails[u] = facts["iocage_jails"][u]
    for u in facts["iocage_templates"]:
        jails[u] = facts["iocage_templates"][u]

    if p["state"] == "facts":
        module.exit_json(changed=False, ansible_facts=facts)

    ### Input validation

    # states that need name of jail
    if not name and p["state"] in [ "started", "stopped", "restarted", "exists", "set", "exec", "pkg", "absent" ]:
        module.fail_json(msg="name needed for state ".format(p["state"]))

    # states that need release defined
    if p["state"] in [ "basejail", "thickjail", "template", "fetched", "present" ] or p["update"]:
        if not release or release == "":
            # if name and not (upgrade):
            #     _jail_props = _jail_get_properties(module, iocage_path, name)
            #     release = _jail_props["release"]
            #else:
            rc, out, err = module.run_command("uname -r")
            if rc != 0:
                module_fail_json(msg="unable to run uname -r ???")

            try:
                release = re.match('(\d+\.\d+)\-(RELEASE|RC\d+).*',out.strip()).group(1)+"-RELEASE"
            except:
                raise Exception("Release not recognised: {}".format(out))

    # need existing jail
    if p["state"] in [ "started", "stopped", "restarted", "set", "exec", "pkg", "exists" ]:
        if name not in facts["iocage_jails"] and name not in facts["iocage_templates"]:
            module.fail_json(msg="Jail '{0}' doesn't exist".format(name))

    # states that need running jail
    if p["state"] in [ "exec", "pkg" ] and facts["iocage_jails"][name]["state"] != "up":
        module.fail_json(msg="Jail '{0}' not running".format(name))

    if p["state"] == "started":
        if jails[name]["state"] != "up":
            changed, _msg = jail_start(module, iocage_path, name, force)
            msgs.append(_msg)
            jails[name] = _get_iocage_facts(module,iocage_path,"jails",name)
            if jails[name]["state"] != "up" and not module.check_mode:
                module.fail_json(msg="Starting jail {} failed with {} :(".format(name,_msg))
        else:
            msgs.append("Jail {0} already started".format(name))

    elif p["state"] == "stopped":
        if jails[name]["state"] == "up":
            changed, _msg = jail_stop(module, iocage_path, name)
            msgs.append(_msg)
            if not module.check_mode:
                jails[name] = _get_iocage_facts(module,iocage_path,"jails",name)
                if jails[name]["state"] != "down":
                    module.fail_json(msg="Stopping jail {} failed with {} :(".format(name,_msg))
        else:
            msgs.append("Jail {0} already stopped".format(name))

    elif p["state"] == "restarted":
        changed, _msg = jail_restart(module, iocage_path, name)
        jails[name] = _get_iocage_facts(module,iocage_path,"jails",name)
        if jails[name]["state"] != "up":
            module.fail_json(msg="Starting jail {} failed with {} :(".format(name,_msg))
        msgs.append(_msg)

    elif p["state"] == "exec":
        changed, _msg, out, err = jail_exec(module, iocage_path, name, user, cmd)
        msgs.append(_msg)

    elif p["state"] == "pkg":
        changed, _msg, out, err = jail_pkg(module, iocage_path, name, cmd)
        msgs.append(_msg)

    elif p["state"] == "exists":
        msgs.append("Jail {0} exists".format(name))

    elif p["state"] == "fetched":
        if update or release not in facts["iocage_releases"]:
            rel, changed, _msg = release_fetch(module, iocage_path, update, release, components, args)
            msgs.append(_msg)
            facts["iocage_releases"] = _get_iocage_facts(module,iocage_path,"releases")
            if release not in facts["iocage_releases"] or update:
                module.fail_json(msg="Fetching release {} failed with {}".format(release,_msg))
        else:
            msgs.append("Release {0} already fetched".format(release))

    elif p["state"] == "set":
        changed, _msg = jail_set(module, iocage_path, name, properties)
        msgs.append(_msg)
        jails[name] = _get_iocage_facts(module,iocage_path,"jails",name)

    elif p["state"] in ["present", "cloned", "template", "basejail", "thickjail"]:

        do_basejail = False
        do_thickjail = False
        clone_from_name = None
        clone_from_template = None
        jail_exists = False

        if p["state"] != "cloned" and release not in facts["iocage_releases"]:
            release, _release_changed, _release_msg = release_fetch(module,iocage_path,update,release, components, args)
            if _release_changed:
                facts["iocage_releases"] = _get_iocage_facts(module,iocage_path,"releases")
                msgs.append(_release_msg)

        if p["state"] == "template":
            properties["template"] = "yes"
            properties["boot"] = "off"
            if name in facts["iocage_templates"]:
                jail_exists = True

        elif p["state"] == "basejail":
            properties = {}
            do_basejail = True

        elif p["state"] == "thickjail":
            do_thickjail = True

        elif clone_from:
            if clone_from in facts["iocage_jails"]:
                clone_from_name = clone_from
            elif clone_from in facts["iocage_templates"]:
                clone_from_template = clone_from
            else:
                if module.check_mode:
                    # todo: use facts to check if basejail would have been created before
                    msgs.append("Jail {0} would have been cloned from (nonexisting) jail or template {1}".format(name,clone_from))
                else:
                    module.fail_json(msg="unable to create jail {0}\nbasejail {1} doesn't exist".format(name,clone_from))

        if not name in facts["iocage_templates"] and not name in facts["iocage_jails"]:
            name, changed, _msg = jail_create(module, iocage_path, name, properties, clone_from_name, clone_from_template, release, do_basejail, do_thickjail, pkglist)
            msgs.append(_msg)
        else:
            changed, _msg = jail_set(module, iocage_path, name, properties)
            msgs.append("{} already exists".format(name))
            if changed:
                msgs.append(_msg)

        if p["update"]:
            if not release in facts["iocage_releases"]:
                release, _release_changed, _release_msg = release_fetch(module,iocage_path,update,release, components, args)
                if _release_changed:
                    _msg += _release_msg
                    facts["iocage_releases"] = _get_iocage_facts(module,iocage_path,"releases")

            release, changed, _msg = jail_update(module, iocage_path, name, release)
            msgs.append(_msg)

#        # re-set properties (iocage missing them on creation - iocage-sh bug)
#        if len(p["properties"]) > 0:
#            changed, _msg = jail_set(module, iocage_path, name, properties)
#            if changed:
#                msgs.append(_msg)

        if changed:
            if p["state"] == "template":
                facts["iocage_templates"][name] = _get_iocage_facts(module,iocage_path,"templates",name)
            else:
                facts["iocage_jails"][name] = _get_iocage_facts(module,iocage_path,"jails",name)

    elif p["state"] == "absent":
        if name in jails:
            if jails[name]['state'] == "up":
                changed, _msg = jail_stop(module, iocage_path, name)
                msgs.append(_msg)
            name, changed, _msg = jail_destroy(module, iocage_path, name)
            msgs.append(_msg)
            del(jails[name])
        if name in facts["iocage_jails"]:
            del(facts["iocage_jails"][name])
        if name in facts["iocage_templates"]:
            del(facts["iocage_templates"][name])

    module.exit_json(changed=changed, msg=", ".join(msgs), name="", ansible_facts=facts, stdout=out, stderr=err)

# import module snippets
from ansible.module_utils.basic import *

if __name__ == '__main__':
    main()
