!-- -->
| Bri Hatch | Personal | Work |
|---|---|---|
|
Onsight, Inc bri@ifokr.org |
ExtraHop Networks bri@extrahop.com |
Idempotency Examples:
- name: install packages
apt: pkg={{ item }} state=present install_recommends=no
with_items:
- python-authprogs
- python-crypto
- virtualenvwrapper
tags:
- apt
- name: create service user
user:
name: wendell_bagg
comment: "Foo Service User"
shell: /bin/tcsh
system: yes
- name: install packages
apt: pkg={{ item }} state=present install_recommends=no
with_items:
- python-authprogs
- python-crypto
- virtualenvwrapper
tags:
- apt
- name: create service user
user:
name: wendell_bagg
comment: "Foo Service User"
shell: /bin/tcsh
system: yes
(Sunday 2018-04-29 15:00, CC-236, Mark Foster)
- name: create service user
user:
name: wendell_bagg
comment: "Foo Service User"
shell: /bin/tcsh
system: yes
Jobs are python classs with two methods, Check and Run, called as follows:
Check/Recheck are considered successful if they do not throw an Exception.
class CreateUser(Job):
"""Create our user's account."""
def Check(self, *args, **kwargs):
"""Verify the user exists."""
import pwd
pwd.getpwnam(kwargs['username'])
def Run(self, *args, **kwargs):
"""Create user given the commandline username/gecos arguments."""
import subprocess
subprocess.call([
'sudo', '/usr/sbin/adduser',
'--shell', '/bin/tcsh',
'--gecos', kwargs['gecos'],
kwargs['username'])
Job CreateUser's Check and Run methods
class CreateUser(Job):
"""Create our user's account."""
def Check(self, *args, **kwargs):
"""Verify the user exists."""
import pwd
pwd.getpwnam(kwargs['username'])
def Run(self, *args, **kwargs):
"""Create user given the commandline username/gecos arguments."""
import subprocess
subprocess.call([
'sudo', '/usr/sbin/adduser',
'--shell', '/bin/tcsh',
'--gecos', kwargs['gecos'],
kwargs['username'])
Variables come in via args and kwargs.
$ cat deploy_script #!/bin/bash set -u set -e id wendell_bagg >/dev/null 2>&1 || /usr/sbin/adduser \ --shell /bin/tcsh \ --gecos "Foo Service User" wendell_bagg mkdir -p /srv/service_directory || /bin/true rsync -C --chmod=Dg+s,ug+w,Fo-w,+X -tlrvCO \ /src/git/service/ \ /srv/service_directory chown -R wendell_bagg /srv/service_directory apt-get install python-authprogs ... $ wc -l deploy_script.sh 41900 730927 2004121 deploy_script.sh
However in this example we actually only have the following dependencies: chown is dependent on user, chown is dependent on rsync, rsync is dependent on directory creation.
If we stop at the first error we don't do other non-blocked actions.
class CreateUser(Job):
...
class CreateDir(Job):
...
class Rsync(Job):
DEPS = (CreateDir,)
...
class ChownFiles(Job):
DEPS = (CreateUser, Rsync)
class PackageInstall(Job):
...
dojobber_example.py:
--help (note movie)--display_prerungraphValidateMovie--movie Zardoz--battery_state charged --couch_space
# Set your arguments manually:
dojob = dojobber.DoJobber()
dojob.configure(RootJob)
dojob.set_args('arg1', 'arg2', foo='foo', bar='bar', ...)
dojob.checknrun()
# Or via argparse / optparse automagically:
myparser = argparse.ArgumentParser()
myparser.add_argument('--movie', dest='movie',
help='Movie to watch.')
args = myparser.parse_args()
...
dojob = dojobber.DoJobber()
dojob.configure(RootJob)
dojob.set_args(**args.__dict__)
dojob.checknrun()
# Set your arguments manually:
dojob = dojobber.DoJobber()
dojob.configure(RootJob, ...)
dojob.set_args('arg1', 'arg2', foo='foo', bar='bar', ...)
dojob.checknrun()
# Or via argparse / optparse automagically:
myparser = argparse.ArgumentParser()
myparser.add_argument('--movie', dest='movie',
help='Movie to watch.')
args = myparser.parse_args()
...
dojob = dojobber.DoJobber()
dojob.configure(RootJob, ...)
dojob.set_args(**args.__dict__)
dojob.checknrun()
# Set your arguments manually:
dojob = dojobber.DoJobber()
dojob.configure(RootJob, ...)
dojob.set_args('arg1', 'arg2', foo='foo', bar='bar', ...)
dojob.checknrun()
# Or via argparse / optparse automagically:
myparser = argparse.ArgumentParser()
myparser.add_argument('--movie', dest='movie',
help='Movie to watch.')
args = myparser.parse_args()
...
dojob = dojobber.DoJobber()
dojob.configure(RootJob, ...)
dojob.set_args(**args.__dict__)
dojob.checknrun()
configure takes some keyword arguments:
verbose: output a line per Job showing Check/Run statusdebug: same as verbose but with full stacktrace on exceptionno_act: only run Check methodscleanup: run Cleanup upon checknrun completion (default)dojobber_example.py:
-v --no-x11-d --no-x11DummyJobs have neither Check nor Run. Useful for holding dependencies.
class PlaceHolder(DummyJob):
DEPS = (Dependency1, Dependency2, ...)
RunonlyJobs have no Check. If the Run does not raise an exception, it is
considered successful.
class RemoveDangerously(RunonlyJob):
DEPS = (UserAcceptedTheConsequences,)
def Run(...):
os.system('rm -rf /')
self.storage dictionary allows
you to store state between Check and Run phases of a single Job.
class UselessExample(Job):
def Check(self, *dummy_args, **dummy_kwargs):
if not self.storage.get('sql_username'):
self.storage['sql_username'] = (some expensive API call)
(check something)
def Run(self, *dummy_args, **kwargs):
subprocess.call(COMMAND + [self.storage['sql_username']])
This storage is not available to any other Jobs!
Usually employed when you have some expensive setup you wish to cache.
self.global_storage dictionary allows
you to store state between all subsequently executed Jobs.
# Store the number of CPUs on this machine for later
# Jobs to use for nefarious purposes.
class CountCPUs(Job):
def Check(self, *args, **kwargs):
self.global_storage['num_cpus'] = len(
[x
for x in open('/proc/cpuinfo').readlines()
if 'vendor_id' in x])
class FixFanSpeed(Job):
DEPS = (CountCPUs,)
def Check(self, *args, **kwargs):
for cpu in range(self.global_storage['num_cpus']):
....
Cleanup method, if supplied, is run at the end
of the checknrun in LIFO order.
class GitRepo(Job):
...
def Run(self, *args, **kwargs):
self.global_storage['gitrepodir'] = tempfile.mkdtemp(
prefix='tmp-git-%s-' % kwargs['reponame'])
os.chdir(self._repodir)
subprocess.coll(GIT_CLONE_CMD)
def Cleanup(self, *args, **kwargs):
shutil.rmtree(self.global_storage['gitrepodir'])
DEPS to include new jobs at will.
# Base Job
class SendInvite(Job):
EMAIL = None
NAME = None
def Check(self, *args, **kwargs):
(do something with self.EMAIL and self.NAME)
# Create a Job per person as an InviteFriends dependency
for person in people:
job = type('Invite {}'.format(person['name']),
(SendInvite,), {})
job.EMAIL = person['email']
job.NAME = person['name']
InviteFriends.DEPS.append(job)
Github: https://github.com/ExtraHop/DoJobber
Installation: pip install dojobber
| Personal | Work |
|---|---|
| Bri Hatch Onsight, Inc bri@ifokr.org |
Bri Hatch |
Copyright 2018, Bri Hatch, Creative Commons BY-NC-SA License