Updates based on community review.

* Changed 'config' from a list to a string so any valid zonecfg(1M) syntax is accepted.
* Made default state 'present'
* Added 'attached', 'detached' and 'configured' states to allow zones to be moved between hosts.
* Updated documentation and examples.
* Code tidy up and refactoring.
This commit is contained in:
Paul Markham 2015-07-03 14:28:28 +10:00
parent eb44a5b6b8
commit c5c3b8133f

View file

@ -1,6 +1,6 @@
#!/usr/bin/python
# (c) 2013, Paul Markham <pmarkham@netrefinery.com>
# (c) 2015, Paul Markham <pmarkham@netrefinery.com>
#
# This file is part of Ansible
#
@ -37,13 +37,18 @@ options:
state:
required: true
description:
- C(present), create the zone.
- C(running), if the zone already exists, boot it, otherwise, create the zone
first, then boot it.
- C(present), configure and install the zone.
- C(installed), synonym for C(present).
- C(running), if the zone already exists, boot it, otherwise, configure and install
the zone first, then boot it.
- C(started), synonym for C(running).
- C(stopped), shutdown a zone.
- C(absent), destroy the zone.
choices: ['present', 'started', 'running', 'stopped', 'absent']
- C(configured), configure the ready so that it's to be attached.
- C(attached), attach a zone, but do not boot it.
- C(detach), stop and detach a zone
choices: ['present', 'installed', 'started', 'running', 'stopped', 'absent', 'configured', 'attached', 'detached']
default: present
name:
description:
- Zone name.
@ -68,19 +73,25 @@ options:
config:
required: false
description:
- 'The zonecfg configuration commands for this zone, separated by commas, e.g.
"set auto-boot=true,add net,set physical=bge0,set address=10.1.1.1,end"
See the Solaris Systems Administrator guide for a list of all configuration commands
that can be used.'
- 'The zonecfg configuration commands for this zone. See zonecfg(1M) for the valid options
and syntax. Typically this is a list of options separated by semi-colons or new lines, e.g.
"set auto-boot=true;add net;set physical=bge0;set address=10.1.1.1;end"'
required: false
default: null
default: empty string
create_options:
required: false
description:
- 'Extra options to the zonecfg create command. For example, this can be used to create a
Solaris 11 kernel zone'
- 'Extra options to the zonecfg(1M) create command.'
required: false
default: null
default: empty string
attach_options:
required: false
description:
- 'Extra options to the zoneadm attach command. For example, this can be used to specify
whether a minimum or full update of packages is required and if any packages need to
be deleted. For valid values, see zoneadm(1M)'
required: false
default: empty string
timeout:
description:
- Timeout, in seconds, for zone to boot.
@ -89,15 +100,15 @@ options:
'''
EXAMPLES = '''
# Create a zone, but don't boot it
# Create and install a zone, but don't boot it
solaris_zone: name=zone1 state=present path=/zones/zone1 sparse=true root_password="Be9oX7OSwWoU."
config='set autoboot=true, add net, set physical=bge0, set address=10.1.1.1, end'
config='set autoboot=true; add net; set physical=bge0; set address=10.1.1.1; end'
# Create a zone and boot it
# Create and install a zone and boot it
solaris_zone: name=zone1 state=running path=/zones/zone1 root_password="Be9oX7OSwWoU."
config='set autoboot=true, add net, set physical=bge0, set address=10.1.1.1, end'
config='set autoboot=true; add net; set physical=bge0; set address=10.1.1.1; end'
# Boot an already created zone
# Boot an already installed zone
solaris_zone: name=zone1 state=running
# Stop a zone
@ -105,6 +116,16 @@ solaris_zone: name=zone1 state=stopped
# Destroy a zone
solaris_zone: name=zone1 state=absent
# Detach a zone
solaris_zone: name=zone1 state=detached
# Configure a zone, ready to be attached
solaris_zone: name=zone1 state=configured path=/zones/zone1 root_password="Be9oX7OSwWoU."
config='set autoboot=true; add net; set physical=bge0; set address=10.1.1.1; end'
# Attach a zone
solaris_zone: name=zone1 state=attached attach_options='-u'
'''
class Zone(object):
@ -120,29 +141,28 @@ class Zone(object):
self.timeout = self.module.params['timeout']
self.config = self.module.params['config']
self.create_options = self.module.params['create_options']
self.attach_options = self.module.params['attach_options']
self.zoneadm_cmd = self.module.get_bin_path('zoneadm', True)
self.zonecfg_cmd = self.module.get_bin_path('zonecfg', True)
self.ssh_keygen_cmd = self.module.get_bin_path('ssh-keygen', True)
def create(self):
def configure(self):
if not self.path:
self.module.fail_json(msg='Missing required argument: path')
if not self.module.check_mode:
t = tempfile.NamedTemporaryFile(delete = False)
if self.sparse:
t.write('create %s\n' % self.create_options)
self.msg.append('creating sparse root zone')
self.msg.append('creating sparse-root zone')
else:
t.write('create -b %s\n' % self.create_options)
self.msg.append('creating whole root zone')
self.msg.append('creating whole-root zone')
t.write('set zonepath=%s\n' % self.path)
if self.config:
for line in self.config:
t.write('%s\n' % line)
t.write('%s\n' % self.config)
t.close()
cmd = '%s -z %s -f %s' % (self.zonecfg_cmd, self.name, t.name)
@ -151,14 +171,30 @@ class Zone(object):
self.module.fail_json(msg='Failed to create zone. %s' % (out + err))
os.unlink(t.name)
self.changed = True
self.msg.append('zone configured')
def install(self):
if not self.module.check_mode:
cmd = '%s -z %s install' % (self.zoneadm_cmd, self.name)
(rc, out, err) = self.module.run_command(cmd)
if rc != 0:
self.module.fail_json(msg='Failed to install zone. %s' % (out + err))
self.configure_sysid()
self.configure_password()
self.configure_ssh_keys()
self.changed = True
self.msg.append('zone installed')
def uninstall(self):
if self.is_installed():
if not self.module.check_mode:
cmd = '%s -z %s uninstall -F' % (self.zoneadm_cmd, self.name)
(rc, out, err) = self.module.run_command(cmd)
if rc != 0:
self.module.fail_json(msg='Failed to uninstall zone. %s' % (out + err))
self.changed = True
self.msg.append('zone uninstalled')
def configure_sysid(self):
if os.path.isfile('%s/root/etc/.UNCONFIGURED' % self.path):
@ -220,6 +256,7 @@ class Zone(object):
f.close()
def boot(self):
if not self.module.check_mode:
cmd = '%s -z %s boot' % (self.zoneadm_cmd, self.name)
(rc, out, err) = self.module.run_command(cmd)
if rc != 0:
@ -241,18 +278,48 @@ class Zone(object):
break
time.sleep(10)
elapsed += 10
self.changed = True
self.msg.append('zone booted')
def destroy(self):
if self.is_running():
self.stop()
if self.is_installed():
self.uninstall()
if not self.module.check_mode:
cmd = '%s -z %s delete -F' % (self.zonecfg_cmd, self.name)
(rc, out, err) = self.module.run_command(cmd)
if rc != 0:
self.module.fail_json(msg='Failed to delete zone. %s' % (out + err))
self.changed = True
self.msg.append('zone deleted')
def stop(self):
if not self.module.check_mode:
cmd = '%s -z %s halt' % (self.zoneadm_cmd, self.name)
(rc, out, err) = self.module.run_command(cmd)
if rc != 0:
self.module.fail_json(msg='Failed to stop zone. %s' % (out + err))
self.changed = True
self.msg.append('zone stopped')
def detach(self):
if not self.module.check_mode:
cmd = '%s -z %s detach' % (self.zoneadm_cmd, self.name)
(rc, out, err) = self.module.run_command(cmd)
if rc != 0:
self.module.fail_json(msg='Failed to detach zone. %s' % (out + err))
self.changed = True
self.msg.append('zone detached')
def attach(self):
if not self.module.check_mode:
cmd = '%s -z %s attach %s' % (self.zoneadm_cmd, self.name, self.attach_options)
(rc, out, err) = self.module.run_command(cmd)
if rc != 0:
self.module.fail_json(msg='Failed to attach zone. %s' % (out + err))
self.changed = True
self.msg.append('zone attached')
def exists(self):
cmd = '%s -z %s list' % (self.zoneadm_cmd, self.name)
@ -262,74 +329,85 @@ class Zone(object):
else:
return False
def running(self):
def is_running(self):
return self.status() == 'running'
def is_installed(self):
return self.status() == 'installed'
def is_configured(self):
return self.status() == 'configured'
def status(self):
cmd = '%s -z %s list -p' % (self.zoneadm_cmd, self.name)
(rc, out, err) = self.module.run_command(cmd)
if rc != 0:
self.module.fail_json(msg='Failed to determine zone state. %s' % (out + err))
if out.split(':')[2] == 'running':
return True
else:
return False
return out.split(':')[2]
def state_present(self):
if self.exists():
self.msg.append('zone already exists')
else:
if not self.module.check_mode:
self.create()
self.changed = True
self.msg.append('zone created')
self.configure()
self.install()
def state_running(self):
self.state_present()
if self.running():
if self.is_running():
self.msg.append('zone already running')
else:
if not self.module.check_mode:
self.boot()
self.changed = True
self.msg.append('zone booted')
def state_stopped(self):
if self.exists():
if self.running():
if not self.module.check_mode:
self.stop()
self.changed = True
self.msg.append('zone stopped')
else:
self.msg.append('zone not running')
else:
self.module.fail_json(msg='zone does not exist')
def state_absent(self):
if self.exists():
self.state_stopped()
if not self.module.check_mode:
if self.is_running():
self.stop()
self.destroy()
self.changed = True
self.msg.append('zone deleted')
else:
self.msg.append('zone does not exist')
def exit_with_msg(self):
msg = ', '.join(self.msg)
self.module.exit_json(changed=self.changed, msg=msg)
def state_configured(self):
if self.exists():
self.msg.append('zone already exists')
else:
self.configure()
def state_detached(self):
if not self.exists():
self.module.fail_json(msg='zone does not exist')
if self.is_configured():
self.msg.append('zone already detached')
else:
self.stop()
self.detach()
def state_attached(self):
if not self.exists():
self.msg.append('zone does not exist')
if self.is_configured():
self.attach()
else:
self.msg.append('zone already attached')
def main():
module = AnsibleModule(
argument_spec = dict(
name = dict(required=True),
state = dict(required=True, choices=['running', 'started', 'present', 'stopped', 'absent']),
state = dict(default='present', choices=['running', 'started', 'present', 'installed', 'stopped', 'absent', 'configured', 'detached', 'attached']),
path = dict(defalt=None),
sparse = dict(default=False, type='bool'),
root_password = dict(default=None),
timeout = dict(default=600, type='int'),
config = dict(default=None, type='list'),
config = dict(default=''),
create_options = dict(default=''),
attach_options = dict(default=''),
),
supports_check_mode=True
)
@ -347,16 +425,22 @@ def main():
if state == 'running' or state == 'started':
zone.state_running()
elif state == 'present':
elif state == 'present' or state == 'installed':
zone.state_present()
elif state == 'stopped':
zone.state_stopped()
elif state == 'absent':
zone.state_absent()
elif state == 'configured':
zone.state_configured()
elif state == 'detached':
zone.state_detached()
elif state == 'attached':
zone.state_attached()
else:
module.fail_json(msg='Invalid state: %s' % state)
zone.exit_with_msg()
module.exit_json(changed=zone.changed, msg=', '.join(zone.msg))
from ansible.module_utils.basic import *
main()