This pattern which I thought up (but is likely not unique) can very nicely manage your service configurations, automatically reload/restart your service, and even give non-root users (if desired) the ability to modify system config files.
All through the magic of git hooks. This example tracks config files for Apache (httpd).
The general flow of events is the following:
- try to commit a change to your local repo
- this change is tested for validity before being allowed into the repo
- if the tests pass great, you now have an updated repo
- push this change to your master repo
- this push triggers an update in the service repo
The following proof on concept was done on Fedora.
Prerequisites
For these scripts I use plumbum but feel free to re-implement these ideas any way you see fit.
# `easy_install pip` if required
pip install plumbum
Create the initial git repo in your httpd directory.
cd /etc/httpd
echo run >> .gitignore
echo logs >> .gitignore
echo modules >> .gitignore
git init .
git add * # this won't actually work, add all files/dirs individually
git commit -a -m "initial commit"
Now create the master repo, which needs to be bare.
mkdir /var/lib/git # you can of course put this wherever you want
git clone --bare file:///etc/httpd /var/lib/git/httpd.git
And point the service repo to the master
cd /etc/httpd
git remote add origin file:///var/lib/git/httpd.git
Now create your working repo
cd
git clone file:///var/lib/git/httpd.git httpd
So now we have three repos:
/etc/httpd
which is our service repo/var/lib/git/httpd.git
which is our master repo/root/httpd
which is our local repo
Hooks
Now we install the hooks, which is where the magic lives.
# /var/lib/git/httpd.git/hooks/post-receive:
#!/usr/bin/env python
from plumbum import local
from plumbum.cmd import git, httpd, service, sudo
with local.cwd( '/etc/httpd' ):
print "cwd: ", local.cwd
# there is some goofiness here that I don't understand
# use sudo and []() syntax
print sudo[ git['pull','--rebase']]()
print httpd('-t')
print service('httpd','reload')
Make sure that file is executable via chmod +x /var/lib/git/httpd.git/hooks/post-receive
This is hard example because httpd has lots of files and they have some paths defined that lookup other files. Hence the need for sed
.
# /root/httpd/.git/hooks/pre-commit:
#!/usr/bin/env python
import os, sys
import plumbum
from plumbum.cmd import git, sed, httpd
# bonus awesome class, read more here: /tech/tempdir-context-manager/
class TempDir(object):
def __init__(self, suffix='', prefix='tmp', dir=None):
import tempfile
self.tdir = tempfile.mkdtemp(suffix, prefix, dir)
def __enter__(self):
return self.tdir
def __exit__(self, type, value, traceback):
try:
import shutil
shutil.rmtree( self.tdir )
except:
import sys
print >> sys.stderr, "could not delete '%'" % self.tdir
with TempDir() as tdir:
try:
httpd_conf = os.path.join( tdir, 'httpd.conf' )
git_dir = os.path.abspath( os.path.join( os.environ['GIT_DIR'], '..') )
print "testing %s based on %s" % (httpd_conf,git_dir)
# copytree requires dest to not exist
shutil.rmtree( tdir )
shutil.copytree( git_dir, tdir )
with plumbum.local.cwd( tdir ):
# change the environment for the test
ln('-s', '/etc/httpd/modules' )
ln('-s', '/etc/httpd/logs' )
ln('-s', '/etc/httpd/run' )
sed('-i', '-e', 's/^ServerRoot.*/ServerRoot "%s"/' % tdir.replace('/','\/'), httpd_conf)
print httpd['-t', '-f', httpd_conf]()
except Exception as e:
print >> sys.stderr, str(e)
sys.exit(1)
Again, make sure it executable.
If you want to enforce this pattern…
# /etc/httpd/.git/hooks/pre-commit:
#!/usr/bin/env python
import sys
print >> sys.stderr, "no commits allowed, use a local repo"
sys.exit(1)
Usage
From
/root/httpd
make an edit.git commit
# only valid configs allowed ingit push
# trigger service repo update and reload??
profit!
Sample output
[root@www /tmp/httpd]
# git commit -a -m "bad"
testing /tmp/tmpkNb2gx/httpd.conf
Command line: ['/sbin/httpd', '-t', '-f', '/tmp/tmpkNb2gx/httpd.conf']
Exit code: 1
Stderr: | AH00526: Syntax error on line 367 of /tmp/tmpkNb2gx/httpd.conf:
| Invalid command 'asdf', perhaps misspelled or defined by a module not included in the server configuration
# git commit -a -m "good"
testing /tmp/tmpOPk1be/httpd.conf
[master 1f02632] good
1 file changed, 1 insertion(+), 1 deletion(-)
# git push
Counting objects: 7, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 365 bytes, done.
Total 4 (delta 2), reused 0 (delta 0)
remote: cwd: /etc/httpd
remote: First, rewinding head to replay your work on top of it...
remote: Fast-forwarded master to 1f0263203bb98e5a8eaa9c28bec1c41c101d8a25.
remote:
remote:
remote:
To /var/lib/git/httpd.git/
2a9d2d1..1f02632 master -> master