!-- -->
Bri Hatch | Personal | Work |
---|---|---|
Onsight, Inc bri@ifokr.org |
ExtraHop Networks bri@extrahop.com |
- 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_prerungraph
ValidateMovie
--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-x11
DummyJobs
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