diff --git a/bin/kokki b/bin/kokki deleted file mode 100755 index 7125a8c..0000000 --- a/bin/kokki +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python - -from kokki.command import main - -if __name__ == "__main__": - main() diff --git a/kokki/cookbooks/jenkins/metadata.py b/kokki/cookbooks/jenkins/metadata.py new file mode 100644 index 0000000..d13db41 --- /dev/null +++ b/kokki/cookbooks/jenkins/metadata.py @@ -0,0 +1,9 @@ + +__description__ = "Jenkins CI" +__config__ = { + "jenkins.http_port": dict( + description = "HTTP port to listen on", + default = 8080, + ) +} + diff --git a/kokki/cookbooks/jenkins/recipes/default.py b/kokki/cookbooks/jenkins/recipes/default.py new file mode 100644 index 0000000..7128e47 --- /dev/null +++ b/kokki/cookbooks/jenkins/recipes/default.py @@ -0,0 +1,32 @@ + +from kokki import * + +if env.system.platform in ("ubuntu", "debian"): + Execute("wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add -", + not_if = "(apt-key list | grep 'Kohsuke Kawaguchi' > /dev/null)") + + + apt = "deb http://pkg.jenkins-ci.org/debian binary/" + apt_list_path = '/etc/apt/sources.list.d/jenkins.list' + + Execute("apt-update-jenkins", + command = "apt-get update", + action = "nothing") + + File(apt_list_path, + owner = "root", + group ="root", + mode = 0644, + content = apt+"\n", + notifies = [("run", env.resources["Execute"]["apt-update-jenkins"], True)]) + +Package("jenkins") + +Service("jenkins") + +File("/etc/default/jenkins", + owner = "root", + group = "root", + mode = 0644, + content = Template("jenkins/default.j2"), + notifies = [("restart", env.resources["Service"]["jenkins"])]) diff --git a/kokki/cookbooks/jenkins/templates/default.j2 b/kokki/cookbooks/jenkins/templates/default.j2 new file mode 100644 index 0000000..db99abb --- /dev/null +++ b/kokki/cookbooks/jenkins/templates/default.j2 @@ -0,0 +1,53 @@ +# defaults for jenkins continuous integration server + +# pulled in from the init script; makes things easier. +NAME=jenkins + +# location of java +JAVA=/usr/bin/java + +# arguments to pass to java +#JAVA_ARGS="-Xmx256m" + +PIDFILE=/var/run/jenkins/jenkins.pid + +# user id to be invoked as (otherwise will run as root; not wise!) +JENKINS_USER=jenkins + +# location of the jenkins war file +JENKINS_WAR=/usr/share/jenkins/jenkins.war + +# jenkins home location +JENKINS_HOME=/var/lib/jenkins + +# set this to false if you don't want Hudson to run by itself +# in this set up, you are expected to provide a servlet containr +# to host jenkins. +RUN_STANDALONE=true + +# log location. this may be a syslog facility.priority +JENKINS_LOG=/var/log/jenkins/$NAME.log +#HUDSON_LOG=daemon.info + +# OS LIMITS SETUP +# comment this out to observe /etc/security/limits.conf +# this is on by default because http://github.com/feniix/hudson/commit/d13c08ea8f5a3fa730ba174305e6429b74853927 +# reported that Ubuntu's PAM configuration doesn't include pam_limits.so, and as a result the # of file +# descriptors are forced to 1024 regardless of /etc/security/limits.conf +MAXOPENFILES=8192 + +# port for HTTP connector (default 8080; disable with -1) +HTTP_PORT={{ env.config.jenkins.http_port }} + +# port for AJP connector (disabled by default) +AJP_PORT=-1 + +# arguments to pass to jenkins. +# --javahome=$JAVA_HOME +# --httpPort=$HTTP_PORT (default 8080; disable with -1) +# --httpsPort=$HTTP_PORT +# --ajp13Port=$AJP_PORT +# --argumentsRealm.passwd.$ADMIN_USER=[password] +# --argumentsRealm.$ADMIN_USER=admin +# --webroot=~/.jenkins/war +JENKINS_ARGS="--webroot=/var/run/jenkins/war --httpPort=$HTTP_PORT --ajp13Port=$AJP_PORT" diff --git a/kokki/cookbooks/mongodb/libraries/server.py b/kokki/cookbooks/mongodb/libraries/server.py index e6c163f..3f63117 100644 --- a/kokki/cookbooks/mongodb/libraries/server.py +++ b/kokki/cookbooks/mongodb/libraries/server.py @@ -31,19 +31,27 @@ def setup(name, **kwargs): content = Template("mongodb/mongodb.conf.j2", variables=dict(mongodb=config))) # notifies = [("restart", env.resources["MonitService"]["mongodb-%s" % name])]) - # env.include_recipe("monit") - # env.cookbooks.monit.rc("mongodb-%s" % name, - # Template("mongodb/monit.conf.j2", variables=dict(name=name, mongodb=config))) - # env.cookbooks.monit.MonitService("mongodb-%s" % name, - # subscribes = [("restart", env.resources["File"][config.configpath])]) - - Service("mongodb-%s" % name, - subscribes = [("restart", env.resources["File"][config.configpath])]) - File("/etc/init/mongodb-%s.conf" % name, - owner = "root", - group = "root", - mode = 0644, - content = Template("mongodb/upstart.conf.j2", variables=dict(mongodb=config)), - notifies = [ - ("reload", env.resources["Service"]["mongodb-%s" % name], True), - ]) + controller = kwargs.get("controller") + if controller == "monit": + env.include_recipe("monit") + env.cookbooks.monit.rc("mongodb-%s" % name, + Template("mongodb/monit.conf.j2", variables=dict(name=name, mongodb=config))) + env.cookbooks.monit.MonitService("mongodb-%s" % name, + subscribes = [("restart", env.resources["File"][config.configpath])]) + elif controller == "supervisord": + env.include_recipe("supervisor") + env.cookbooks.supervisor.configuration("mongodb-%s" % name, + Template("mongodb/supervisord.conf.j2", variables=dict(name=name, mongodb=config))) + env.cookbooks.supervisor.SupervisorService("mongodb-%s" % name, + subscribes = [("restart", env.resources["File"][config.configpath])]) + else: + Service("mongodb-%s" % name, + subscribes = [("restart", env.resources["File"][config.configpath])]) + File("/etc/init/mongodb-%s.conf" % name, + owner = "root", + group = "root", + mode = 0644, + content = Template("mongodb/upstart.conf.j2", variables=dict(mongodb=config)), + notifies = [ + ("reload", env.resources["Service"]["mongodb-%s" % name], True), + ]) diff --git a/kokki/cookbooks/mongodb/recipes/default.py b/kokki/cookbooks/mongodb/recipes/default.py index 4c667bf..81c5a4e 100644 --- a/kokki/cookbooks/mongodb/recipes/default.py +++ b/kokki/cookbooks/mongodb/recipes/default.py @@ -58,7 +58,7 @@ mode = 0644, content = Template("mongodb/upstart.conf.j2", variables=dict(mongodb=env.config.mongodb)), notifies = [ - ("reload", env.resources["Service"]["mongodb"], True), + ("restart", env.resources["Service"]["mongodb"], True), ]) File(env.config.mongodb.configpath, diff --git a/kokki/cookbooks/mongodb/templates/supervisord.conf.j2 b/kokki/cookbooks/mongodb/templates/supervisord.conf.j2 new file mode 100644 index 0000000..fb2e9bd --- /dev/null +++ b/kokki/cookbooks/mongodb/templates/supervisord.conf.j2 @@ -0,0 +1,4 @@ +[program:mongodb_{{ name }}] +command=/usr/bin/mongod --config {{ mongodb.configpath }} {{ " ".join(mongodb.options) }} +# autostart=true +stopwaitsecs=300 diff --git a/kokki/cookbooks/monit/libraries/providers.py b/kokki/cookbooks/monit/libraries/providers.py index becc42b..58be171 100644 --- a/kokki/cookbooks/monit/libraries/providers.py +++ b/kokki/cookbooks/monit/libraries/providers.py @@ -1,18 +1,9 @@ import subprocess from kokki import * +from kokki.providers.service import ServiceProvider -class MonitServiceProvider(Provider): - def action_start(self): - if not self.status(): - self._init_cmd("start", 0) - self.resource.updated() - - def action_stop(self): - if self.status(): - self._init_cmd("stop", 0) - self.resource.updated() - +class MonitServiceProvider(ServiceProvider): def action_restart(self): self._init_cmd("restart", 0) self.resource.updated() diff --git a/kokki/cookbooks/pip/libraries/resources.py b/kokki/cookbooks/pip/libraries/resources.py index 282a4c9..6ee48d2 100644 --- a/kokki/cookbooks/pip/libraries/resources.py +++ b/kokki/cookbooks/pip/libraries/resources.py @@ -10,5 +10,6 @@ class PipPackage(Resource): action = ForcedListArgument(default="install") package_name = ResourceArgument(default=lambda obj:obj.name) + location = ResourceArgument(default=lambda obj:obj.package_name) version = ResourceArgument(required = True) - actions = ["install", "upgrade", "remove", "purge"] \ No newline at end of file + actions = ["install", "upgrade", "remove", "purge"] \ No newline at end of file diff --git a/kokki/cookbooks/powerdns/metadata.py b/kokki/cookbooks/powerdns/metadata.py index dea05e2..1e9359f 100644 --- a/kokki/cookbooks/powerdns/metadata.py +++ b/kokki/cookbooks/powerdns/metadata.py @@ -9,4 +9,12 @@ description = "Pipe command", default = None, ), + "powerdns.allow_recursion": dict( + description = "List of addresses from which to allow recursion", + default = "127.0.0.1", + ), + "powerdns.recursor": dict( + description = "IP address of recursing nameserver if desired", + default = None, + ), } diff --git a/kokki/cookbooks/powerdns/templates/pdns.conf b/kokki/cookbooks/powerdns/templates/pdns.conf index 1893efc..9b2fd8d 100644 --- a/kokki/cookbooks/powerdns/templates/pdns.conf +++ b/kokki/cookbooks/powerdns/templates/pdns.conf @@ -8,7 +8,7 @@ ################################# # allow-recursion List of netmasks that are allowed to recurse # -allow-recursion=127.0.0.1 +allow-recursion={{ env.config.powerdns.allow_recursion }} ################################# # allow-recursion-override Local data even about hosts that don't exist will @@ -197,7 +197,9 @@ module-dir=/usr/lib/powerdns ################################# # recursor If recursion is desired, IP address of a recursing nameserver # -# recursor= +{% if env.config.powerdns.recursor %} +recursor={{ env.config.powerdns.recursor }} +{% endif %} ################################# # setgid If set, change group id to this gid for more security diff --git a/kokki/cookbooks/rabbitmq/metadata.py b/kokki/cookbooks/rabbitmq/metadata.py new file mode 100644 index 0000000..364c79f --- /dev/null +++ b/kokki/cookbooks/rabbitmq/metadata.py @@ -0,0 +1,8 @@ + +__description__ = "RabbitMQ Messaging Server" +__config__ = { + "rabbitmq.path": dict( + description = "Install path for rabbitmq", + default = "/usr/local/rabbitmq", + ) +} diff --git a/kokki/cookbooks/rabbitmq/recipes/default.py b/kokki/cookbooks/rabbitmq/recipes/default.py new file mode 100644 index 0000000..74bc9f1 --- /dev/null +++ b/kokki/cookbooks/rabbitmq/recipes/default.py @@ -0,0 +1,10 @@ + +from kokki import * + +Package("erlang") + +if env.system.platform in ("ubuntu", "debian"): + pkg_url = "http://www.rabbitmq.com/releases/rabbitmq-server/v2.3.1/rabbitmq-server_2.3.1-1_all.deb" + Execute("cd /tmp ; wget %s ; dpkg -i %s ; rm rabbitmq*deb" % (pkg_url, pkg_url.rsplit('/', 1)[-1]), + not_if = "dpkg-query -c rabbitmq-server > /dev/null") + diff --git a/kokki/cookbooks/supervisor/libraries/config.py b/kokki/cookbooks/supervisor/libraries/config.py index 3be27eb..9e2c08e 100644 --- a/kokki/cookbooks/supervisor/libraries/config.py +++ b/kokki/cookbooks/supervisor/libraries/config.py @@ -1,12 +1,13 @@ +import os from kokki import * -def config(name, content): +def configuration(name, content): env = Environment.get_instance() return File("supervisor-%s" % name, content = content, owner = "root", group = "root", mode = 0644, - path = "%s/supervisor.d/%s" % (env.config.supervisor.config_path, name), - notifies = [("restart", env.resources["Service"]["supervisor"])]) + path = os.path.join(env.config.supervisor.custom_config_path, name) + ".conf", + notifies = [("reload", env.resources["Service"]["supervisor"])]) diff --git a/kokki/cookbooks/supervisor/libraries/providers.py b/kokki/cookbooks/supervisor/libraries/providers.py index 6911477..1cafeb6 100644 --- a/kokki/cookbooks/supervisor/libraries/providers.py +++ b/kokki/cookbooks/supervisor/libraries/providers.py @@ -22,7 +22,7 @@ def action_restart(self): self.resource.updated() def action_reload(self): - self._init_cmd("reload", 0) + self._init_cmd("update", 0) self.resource.updated() def status(self): diff --git a/kokki/cookbooks/supervisor/metadata.py b/kokki/cookbooks/supervisor/metadata.py index 912c01f..ab3265d 100644 --- a/kokki/cookbooks/supervisor/metadata.py +++ b/kokki/cookbooks/supervisor/metadata.py @@ -2,22 +2,26 @@ __description__ = "Process monitoring" __config__ = { "supervisor.config_path": dict( - display_name = "Supervisor config path", description = "Config file path for supervisor", - default = "/etc", + default = "/etc/supervisor/supervisord.conf", + ), + "supervisor.socket_path": dict( + description = "Unix socket path", + default = "/var/run/supervisor.sock", + ), + "supervisor.custom_config_path": dict( + description = "Path to custom supervisor config files", + default = "/etc/supervisor/conf.d", ), "supervisor.binary_path": dict( - display_name = "Supervisor binary path", description = "Path to the supervisor binaries", - default = "/usr/local/bin", + default = "/usr/bin", ), "supervisor.pidfile": dict( - display_name = "Supervisor pid file path", description = "Path to the supervisor pid file", default = "/var/run/supervisord.pid", ), "supervisor.logfile": dict( - display_name = "Supervisor log file path", description = "Path to the supervisor log file", default = "/var/log/supervisord.log", ), diff --git a/kokki/cookbooks/supervisor/recipes/default.py b/kokki/cookbooks/supervisor/recipes/default.py index 02cd809..d92e8b2 100644 --- a/kokki/cookbooks/supervisor/recipes/default.py +++ b/kokki/cookbooks/supervisor/recipes/default.py @@ -1,20 +1,27 @@ +import os from kokki import * -env.include_recipe("monit") +# env.include_recipe("monit") -Package("supervisor", - provider = "kokki.providers.package.easy_install.EasyInstallProvider") +Package("supervisor") +# provider = "kokki.providers.package.easy_install.EasyInstallProvider") File("supervisord.conf", - path = "%s/supervisord.conf" % env.config.supervisor.config_path, + path = env.config.supervisor.config_path, content = Template("supervisor/supervisord.conf.j2")) Directory("supervisor.d", - path = "%s/supervisor.d" % env.config.supervisor.config_path) + path = env.config.supervisor.custom_config_path) -env.cookbooks.monit.rc("supervisord", - content = Template("supervisor/monit.conf.j2")) +supervisorctl = os.path.join(env.config.supervisor.binary_path, "supervisorctl") +Service("supervisor", + restart_command = "%s reload" % supervisorctl, + reload_command = "%s update" % supervisorctl, + subscribes = [("reload", env.resources["File"]["supervisord.conf"])]) -env.cookbooks.monit.MonitService("supervisord", - subscribes = [("restart", env.resources["File"]["supervisord.conf"])]) +#env.cookbooks.monit.rc("supervisord", +# content = Template("supervisor/monit.conf.j2")) + +#env.cookbooks.monit.MonitService("supervisord", +# subscribes = [("restart", env.resources["File"]["supervisord.conf"])]) diff --git a/kokki/cookbooks/supervisor/templates/supervisord.conf.j2 b/kokki/cookbooks/supervisor/templates/supervisord.conf.j2 index f149197..6ebb772 100644 --- a/kokki/cookbooks/supervisor/templates/supervisord.conf.j2 +++ b/kokki/cookbooks/supervisor/templates/supervisord.conf.j2 @@ -1,5 +1,6 @@ [unix_http_server] -file=/tmp/supervisor.sock ; (the path to the socket file) +file={{ env.config.supervisor.socket_path }} ; (the path to the socket file) +chmod=0700 [supervisord] logfile={{ env.config.supervisor.logfile }} ; (main log file;default $CWD/supervisord.log) @@ -10,12 +11,13 @@ pidfile={{ env.config.supervisor.pidfile }} ; (supervisord pidfile;default super nodaemon=false ; (start in foreground if true;default false) minfds=1024 ; (min. avail startup file descriptors;default 1024) minprocs=200 ; (min. avail process descriptors;default 200) +childlogdir=/var/log/supervisor [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface [supervisorctl] -serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket +serverurl=unix://{{ env.config.supervisor.socket_path }} ; use a unix:// URL for a unix socket [include] -files = supervisor.d/* +files = {{ env.config.supervisor.custom_config_path }}/*.conf diff --git a/kokki/cookbooks/users/metadata.py b/kokki/cookbooks/users/metadata.py index 63110f7..c514ebe 100644 --- a/kokki/cookbooks/users/metadata.py +++ b/kokki/cookbooks/users/metadata.py @@ -1,8 +1,8 @@ __description__ = "Manage user accounts and sysadmins" __config__ = { - "sysadmins": dict( - description = "List of sysadmins (id,username,sshkey_id,sshkey_type,sshkey)", - default = [], + "users": dict( + description = "Disctionary of sysadmins with username as the key and value as a dictionary of (id,sshkey_id,sshkey_type,sshkey)", + default = {}, ) } diff --git a/kokki/cookbooks/users/recipes/default.py b/kokki/cookbooks/users/recipes/default.py index 446c5be..a5b5ca2 100644 --- a/kokki/cookbooks/users/recipes/default.py +++ b/kokki/cookbooks/users/recipes/default.py @@ -7,26 +7,26 @@ Group("sysadmin", gid = 2300) -for user in env.config.users: - home = "/home/%s" % user['username'] +for username, user in env.config.users.items(): + home = "/home/%s" % username - User(user['username'], + User(username, uid = user['id'], home = home, groups = user.get('groups', []), password = user.get('password')) - Directory(env.cookbooks.ssh.ssh_path_for_user(user['username']), - owner = user['username'], - group = user['username'], + Directory(env.cookbooks.ssh.ssh_path_for_user(username), + owner = username, + group = username, mode = 0700) if user.get('sshkey'): - env.cookbooks.ssh.SSHAuthorizedKey("%s-%s" % (user['username'], user['sshkey_id']), - user = user['username'], + env.cookbooks.ssh.SSHAuthorizedKey("%s-%s" % (username, user['sshkey_id']), + user = username, keytype = user['sshkey_type'], key = user['sshkey']) - File(os.path.join(env.cookbooks.ssh.ssh_path_for_user(user['username']), "authorized_keys"), - owner = user['username'], - group = user['username'], + File(os.path.join(env.cookbooks.ssh.ssh_path_for_user(username), "authorized_keys"), + owner = username, + group = username, mode = 0600) diff --git a/kokki/providers/package/__init__.py b/kokki/providers/package/__init__.py index 189343f..013255e 100644 --- a/kokki/providers/package/__init__.py +++ b/kokki/providers/package/__init__.py @@ -21,10 +21,10 @@ def action_install(self): if not install_version: raise Fail("No version specified, and no candidate version available.") - self.log.info("Install %s version %s (resource %s, current %s, candidate %s)" % - (self.resource.package_name, install_version, self.resource.version, self.current_version, self.candidate_version)) + self.log.info("Install %s version %s (resource %s, current %s, candidate %s) location %s" % + (self.resource.package_name, install_version, self.resource.version, self.current_version, self.candidate_version, self.resource.location)) - status = self.install_package(self.resource.package_name, install_version) + status = self.install_package(self.resource.location, install_version) if status: self.resource.updated() @@ -33,7 +33,7 @@ def action_upgrade(self): orig_version = self.current_version or "uninstalled" self.log.info("Upgrading %s from version %s to %s" % (self.resource, orig_version, self.candidate_version)) - status = self.upgrade_package(self.resource.package_name, self.candidate_version) + status = self.upgrade_package(self.resource.location, self.candidate_version) if status: self.resource.updated() diff --git a/kokki/providers/service/__init__.py b/kokki/providers/service/__init__.py index d8de102..abce445 100644 --- a/kokki/providers/service/__init__.py +++ b/kokki/providers/service/__init__.py @@ -7,36 +7,37 @@ class ServiceProvider(Provider): def action_start(self): if not self.status(): - self._init_cmd("start", 0) + self._exec_cmd("start", 0) self.resource.updated() def action_stop(self): if self.status(): - self._init_cmd("stop", 0) + self._exec_cmd("stop", 0) self.resource.updated() def action_restart(self): if not self.status(): - self._init_cmd("start", 0) + self._exec_cmd("start", 0) self.resource.updated() else: - self._init_cmd("restart", 0) + self._exec_cmd("restart", 0) self.resource.updated() def action_reload(self): if not self.status(): - self._init_cmd("start", 0) + self._exec_cmd("start", 0) self.resource.updated() else: - self._init_cmd("reload", 0) + self._exec_cmd("reload", 0) self.resource.updated() def status(self): - return self._init_cmd("status") == 0 + return self._exec_cmd("status") == 0 - def _init_cmd(self, command, expect=None): + def _exec_cmd(self, command, expect=None): if command != "status": self.log.info("%s command '%s'" % (self.resource, command)) + custom_cmd = getattr(self.resource, "%s_command" % command, None) if custom_cmd: self.log.debug("%s executing '%s'" % (self.resource, custom_cmd)) @@ -48,7 +49,15 @@ def _init_cmd(self, command, expect=None): else: ret = subprocess.call(custom_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - elif self._upstart: + else: + ret = self._init_cmd(command) + + if expect is not None and expect != ret: + raise Fail("%r command %s for service %s failed" % (self, command, self.resource.service_name)) + return ret + + def _init_cmd(self, command): + if self._upstart: if command == "status": p = subprocess.Popen(["/sbin/"+command, self.resource.service_name], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) @@ -61,8 +70,6 @@ def _init_cmd(self, command, expect=None): else: ret = subprocess.call(["/etc/init.d/%s" % self.resource.service_name, command], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - if expect is not None and expect != ret: - raise Fail("%r command %s for service %s failed" % (self, command, self.resource.service_name)) return ret @property diff --git a/kokki/resources/packaging.py b/kokki/resources/packaging.py index 5320c90..b3f5c4e 100644 --- a/kokki/resources/packaging.py +++ b/kokki/resources/packaging.py @@ -7,6 +7,7 @@ class Package(Resource): action = ForcedListArgument(default="install") package_name = ResourceArgument(default=lambda obj:obj.name) + location = ResourceArgument(default=lambda obj:obj.package_name) version = ResourceArgument() actions = ["install", "upgrade", "remove", "purge"] - build_vars = ForcedListArgument(default=[]) \ No newline at end of file + build_vars = ForcedListArgument(default=[]) diff --git a/setup.py b/setup.py index 4bf973d..e835894 100644 --- a/setup.py +++ b/setup.py @@ -13,8 +13,12 @@ author_email = 'samuel@descolada.com', url = 'http://samuelks.com/kokki/', packages = find_packages(), - scripts = ['bin/kokki'], test_suite = "tests", + entry_points = { + "console_scripts": [ + "kokki = kokki.command:main", + ], + }, classifiers = [ 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License',