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.
reviewable/pr18780/r1
Paul Markham 9 years ago
parent eb44a5b6b8
commit c5c3b8133f

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

Loading…
Cancel
Save