├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── conf.py ├── index.rst ├── make.bat └── resources.rst ├── kokki ├── __init__.py ├── base.py ├── command.py ├── cookbooks │ ├── __init__.py │ ├── apache2 │ │ ├── libraries │ │ │ ├── conf.py │ │ │ ├── module.py │ │ │ └── site.py │ │ ├── metadata.py │ │ ├── recipes │ │ │ ├── default.py │ │ │ ├── prefork.py │ │ │ └── worker.py │ │ └── templates │ │ │ ├── apache2.conf.j2 │ │ │ ├── charset.j2 │ │ │ ├── mods │ │ │ ├── alias.conf.j2 │ │ │ └── status.conf.j2 │ │ │ ├── ports.conf.j2 │ │ │ └── security.j2 │ ├── aspersa │ │ ├── metadata.py │ │ └── recipes │ │ │ └── default.py │ ├── avatartare │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ ├── local_settings.py.j2 │ │ │ └── supervisor.j2 │ ├── aws │ │ ├── libraries │ │ │ ├── providers.py │ │ │ ├── resources.py │ │ │ └── volume.py │ │ ├── metadata.py │ │ └── recipes │ │ │ └── default.py │ ├── boto │ │ ├── metadata.py │ │ └── recipes │ │ │ └── default.py │ ├── busket │ │ ├── metadata.py │ │ └── recipes │ │ │ └── default.py │ ├── cassandra │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ └── cassandra.yaml.j2 │ ├── cloudera │ │ ├── metadata.py │ │ └── recipes │ │ │ └── default.py │ ├── cloudkick │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ └── cloudkick.conf.j2 │ ├── exim4 │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ ├── passwd.client.j2 │ │ │ └── update-exim4.conf.conf.j2 │ ├── flume │ │ ├── metadata.py │ │ ├── recipes │ │ │ ├── default.py │ │ │ ├── master.py │ │ │ └── node.py │ │ └── templates │ │ │ ├── flume-conf.xml.j2 │ │ │ ├── flume-daemon.sh.j2 │ │ │ ├── flume-site.xml.j2 │ │ │ └── log4j.properties.j2 │ ├── gearmand │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ └── monit.conf.j2 │ ├── java │ │ ├── metadata.py │ │ └── recipes │ │ │ ├── default.py │ │ │ └── jre.py │ ├── jenkins │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ └── default.j2 │ ├── librato │ │ ├── metadata.py │ │ ├── recipes │ │ │ ├── default.py │ │ │ └── silverline.py │ │ └── templates │ │ │ ├── lmc.conf.j2 │ │ │ └── lmd.conf.j2 │ ├── limits │ │ ├── libraries │ │ │ └── limit.py │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ └── limits.conf.j2 │ ├── mdadm │ │ ├── libraries │ │ │ ├── providers.py │ │ │ └── resources.py │ │ ├── metadata.py │ │ └── recipes │ │ │ └── default.py │ ├── membase │ │ ├── metadata.py │ │ └── recipes │ │ │ ├── default.py │ │ │ ├── moxi.py │ │ │ └── server.py │ ├── memcached │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ └── memcached.conf.j2 │ ├── minecraft │ │ ├── metadata.py │ │ └── recipes │ │ │ └── server.py │ ├── mongodb │ │ ├── libraries │ │ │ └── server.py │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ ├── mongodb.conf.j2 │ │ │ ├── monit.conf.j2 │ │ │ ├── supervisord.conf.j2 │ │ │ └── upstart.conf.j2 │ ├── monit │ │ ├── libraries │ │ │ ├── providers.py │ │ │ ├── rc.py │ │ │ └── resources.py │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ ├── default.j2 │ │ │ └── monitrc.j2 │ ├── munin │ │ ├── metadata.py │ │ ├── recipes │ │ │ ├── master.py │ │ │ └── node.py │ │ └── templates │ │ │ ├── munin-node.conf.j2 │ │ │ └── munin.conf.j2 │ ├── mysql │ │ ├── metadata.py │ │ ├── recipes │ │ │ ├── client.py │ │ │ ├── default.py │ │ │ ├── php5.py │ │ │ ├── python.py │ │ │ └── server.py │ │ └── templates │ │ │ ├── debian.cnf.j2 │ │ │ ├── grants.sql.j2 │ │ │ ├── kokki.cnf.j2 │ │ │ └── mysql-server.seed.j2 │ ├── nagios3 │ │ ├── libraries │ │ │ ├── contact.py │ │ │ ├── host.py │ │ │ └── service.py │ │ ├── metadata.py │ │ ├── recipes │ │ │ ├── default.py │ │ │ └── nrpe.py │ │ └── templates │ │ │ ├── apache2-site.j2 │ │ │ ├── cgi.cfg.j2 │ │ │ ├── contacts.cfg.j2 │ │ │ ├── hostgroups.cfg.j2 │ │ │ ├── hosts.cfg.j2 │ │ │ ├── nagios.cfg.j2 │ │ │ ├── nrpe.cfg.j2 │ │ │ └── service.cfg.j2 │ ├── nginx │ │ ├── libraries │ │ │ └── sites.py │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ ├── default-site.j2 │ │ │ ├── nginx.conf.j2 │ │ │ ├── nxdissite.j2 │ │ │ └── nxensite.j2 │ ├── pip │ │ ├── libraries │ │ │ ├── providers.py │ │ │ └── resources.py │ │ ├── metadata.py │ │ └── recipes │ │ │ └── default.py │ ├── postgresql84 │ │ ├── metadata.py │ │ ├── recipes │ │ │ ├── client.py │ │ │ ├── server.py │ │ │ ├── skytools.py │ │ │ ├── skytools_londiste.py │ │ │ └── skytools_ticker.py │ │ └── templates │ │ │ ├── pg_hba.conf.j2 │ │ │ ├── postgresql.conf.j2 │ │ │ ├── skytools-londiste.ini.j2 │ │ │ └── skytools-ticker.ini.j2 │ ├── postgresql9 │ │ ├── metadata.py │ │ ├── recipes │ │ │ ├── default.py │ │ │ └── server.py │ │ └── templates │ │ │ ├── pg_hba.conf.j2 │ │ │ └── postgresql.conf.j2 │ ├── powerdns │ │ ├── metadata.py │ │ ├── recipes │ │ │ ├── default.py │ │ │ ├── recursor.py │ │ │ └── server.py │ │ └── templates │ │ │ └── pdns.conf │ ├── rabbitmq │ │ ├── metadata.py │ │ └── recipes │ │ │ └── default.py │ ├── redis │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ ├── init.conf.j2 │ │ │ ├── monit.conf.j2 │ │ │ ├── redis.conf.j2 │ │ │ └── upstart.conf.j2 │ ├── serverdensity │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ └── config.cfg.j2 │ ├── ssh │ │ ├── libraries │ │ │ ├── config.py │ │ │ ├── providers.py │ │ │ ├── resources.py │ │ │ └── utils.py │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ ├── config.j2 │ │ │ └── sshd_config.j2 │ ├── sudo │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ └── sudoers.j2 │ ├── supervisor │ │ ├── libraries │ │ │ ├── config.py │ │ │ ├── providers.py │ │ │ └── resources.py │ │ ├── metadata.py │ │ ├── recipes │ │ │ └── default.py │ │ └── templates │ │ │ ├── monit.conf.j2 │ │ │ └── supervisord.conf.j2 │ ├── users │ │ ├── metadata.py │ │ └── recipes │ │ │ └── default.py │ └── zookeeper │ │ ├── metadata.py │ │ └── recipes │ │ ├── default.py │ │ └── server.py ├── environment.py ├── exceptions.py ├── kitchen.py ├── providers │ ├── __init__.py │ ├── accounts.py │ ├── mount.py │ ├── package │ │ ├── __init__.py │ │ ├── apt.py │ │ ├── easy_install.py │ │ ├── emerge.py │ │ └── yumrpm.py │ ├── service │ │ ├── __init__.py │ │ ├── debian.py │ │ ├── gentoo.py │ │ └── redhat.py │ └── system.py ├── resources │ ├── __init__.py │ ├── accounts.py │ ├── packaging.py │ ├── service.py │ └── system.py ├── source.py ├── system.py ├── utils.py └── version.py ├── setup.py └── tests ├── __init__.py └── cookbooks └── test ├── files └── static.txt ├── libraries └── blah.py ├── metadata.py ├── recipes └── default.py └── templates └── test.j2 /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | *.swp 3 | build/ 4 | dist/ 5 | *.egg-info/ 6 | .bzr 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Samuel Stauffer 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Samuel Stauffer nor the names of its contributors may be used 15 | to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include MANIFEST.in 3 | include bin/kokki 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Overview 3 | ======== 4 | 5 | Kokki is a system configuration management framework styled after Chef. It can 6 | be used to build a full configuration system, but it also includes a basic 7 | command line interface for simple uses. 8 | 9 | Documentation 10 | ------------- 11 | 12 | http://github.com/samuel/kokki/wiki 13 | 14 | Mailing List 15 | ------------ 16 | 17 | https://convore.com/kokki/ 18 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | 9 | # Internal variables. 10 | PAPEROPT_a4 = -D latex_paper_size=a4 11 | PAPEROPT_letter = -D latex_paper_size=letter 12 | ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 13 | 14 | .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest 15 | 16 | help: 17 | @echo "Please use \`make ' where is one of" 18 | @echo " html to make standalone HTML files" 19 | @echo " dirhtml to make HTML files named index.html in directories" 20 | @echo " pickle to make pickle files" 21 | @echo " json to make JSON files" 22 | @echo " htmlhelp to make HTML files and a HTML help project" 23 | @echo " qthelp to make HTML files and a qthelp project" 24 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 25 | @echo " changes to make an overview of all changed/added/deprecated items" 26 | @echo " linkcheck to check all external links for integrity" 27 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 28 | 29 | clean: 30 | -rm -rf _build/* 31 | 32 | html: 33 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html 34 | @echo 35 | @echo "Build finished. The HTML pages are in _build/html." 36 | 37 | dirhtml: 38 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml 39 | @echo 40 | @echo "Build finished. The HTML pages are in _build/dirhtml." 41 | 42 | pickle: 43 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle 44 | @echo 45 | @echo "Build finished; now you can process the pickle files." 46 | 47 | json: 48 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json 49 | @echo 50 | @echo "Build finished; now you can process the JSON files." 51 | 52 | htmlhelp: 53 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp 54 | @echo 55 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 56 | ".hhp project file in _build/htmlhelp." 57 | 58 | qthelp: 59 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp 60 | @echo 61 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 62 | ".qhcp project file in _build/qthelp, like this:" 63 | @echo "# qcollectiongenerator _build/qthelp/kokki.qhcp" 64 | @echo "To view the help file:" 65 | @echo "# assistant -collectionFile _build/qthelp/kokki.qhc" 66 | 67 | latex: 68 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex 69 | @echo 70 | @echo "Build finished; the LaTeX files are in _build/latex." 71 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ 72 | "run these through (pdf)latex." 73 | 74 | changes: 75 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes 76 | @echo 77 | @echo "The overview file is in _build/changes." 78 | 79 | linkcheck: 80 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck 81 | @echo 82 | @echo "Link check complete; look for any errors in the above output " \ 83 | "or in _build/linkcheck/output.txt." 84 | 85 | doctest: 86 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest 87 | @echo "Testing of doctests in the sources finished, look at the " \ 88 | "results in _build/doctest/output.txt." 89 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | set SPHINXBUILD=sphinx-build 6 | set ALLSPHINXOPTS=-d _build/doctrees %SPHINXOPTS% . 7 | if NOT "%PAPER%" == "" ( 8 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 9 | ) 10 | 11 | if "%1" == "" goto help 12 | 13 | if "%1" == "help" ( 14 | :help 15 | echo.Please use `make ^` where ^ is one of 16 | echo. html to make standalone HTML files 17 | echo. dirhtml to make HTML files named index.html in directories 18 | echo. pickle to make pickle files 19 | echo. json to make JSON files 20 | echo. htmlhelp to make HTML files and a HTML help project 21 | echo. qthelp to make HTML files and a qthelp project 22 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 23 | echo. changes to make an overview over all changed/added/deprecated items 24 | echo. linkcheck to check all external links for integrity 25 | echo. doctest to run all doctests embedded in the documentation if enabled 26 | goto end 27 | ) 28 | 29 | if "%1" == "clean" ( 30 | for /d %%i in (_build\*) do rmdir /q /s %%i 31 | del /q /s _build\* 32 | goto end 33 | ) 34 | 35 | if "%1" == "html" ( 36 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% _build/html 37 | echo. 38 | echo.Build finished. The HTML pages are in _build/html. 39 | goto end 40 | ) 41 | 42 | if "%1" == "dirhtml" ( 43 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% _build/dirhtml 44 | echo. 45 | echo.Build finished. The HTML pages are in _build/dirhtml. 46 | goto end 47 | ) 48 | 49 | if "%1" == "pickle" ( 50 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% _build/pickle 51 | echo. 52 | echo.Build finished; now you can process the pickle files. 53 | goto end 54 | ) 55 | 56 | if "%1" == "json" ( 57 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% _build/json 58 | echo. 59 | echo.Build finished; now you can process the JSON files. 60 | goto end 61 | ) 62 | 63 | if "%1" == "htmlhelp" ( 64 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% _build/htmlhelp 65 | echo. 66 | echo.Build finished; now you can run HTML Help Workshop with the ^ 67 | .hhp project file in _build/htmlhelp. 68 | goto end 69 | ) 70 | 71 | if "%1" == "qthelp" ( 72 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% _build/qthelp 73 | echo. 74 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 75 | .qhcp project file in _build/qthelp, like this: 76 | echo.^> qcollectiongenerator _build\qthelp\kokki.qhcp 77 | echo.To view the help file: 78 | echo.^> assistant -collectionFile _build\qthelp\kokki.ghc 79 | goto end 80 | ) 81 | 82 | if "%1" == "latex" ( 83 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% _build/latex 84 | echo. 85 | echo.Build finished; the LaTeX files are in _build/latex. 86 | goto end 87 | ) 88 | 89 | if "%1" == "changes" ( 90 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% _build/changes 91 | echo. 92 | echo.The overview file is in _build/changes. 93 | goto end 94 | ) 95 | 96 | if "%1" == "linkcheck" ( 97 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% _build/linkcheck 98 | echo. 99 | echo.Link check complete; look for any errors in the above output ^ 100 | or in _build/linkcheck/output.txt. 101 | goto end 102 | ) 103 | 104 | if "%1" == "doctest" ( 105 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% _build/doctest 106 | echo. 107 | echo.Testing of doctests in the sources finished, look at the ^ 108 | results in _build/doctest/output.txt. 109 | goto end 110 | ) 111 | 112 | :end 113 | -------------------------------------------------------------------------------- /docs/resources.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | Resource Types 3 | ============== 4 | 5 | File 6 | ==== 7 | 8 | action 9 | create(default), delete, touch 10 | path 11 | String: Path to the file (defaults to 'name') 12 | backup 13 | Boolean: Create a backup of any replaced files (unimplemented) 14 | mode 15 | Integer: Numerical mode for the file 16 | owner 17 | String/Integer: UID or username 18 | group 19 | String/Integer: GID or groupname 20 | content 21 | Source: Template or string 22 | 23 | Directory 24 | ========= 25 | 26 | action 27 | create(default), delete 28 | path 29 | String: Path to the directory (defaults to 'name') 30 | mode 31 | Integer: Numerical mode for the file 32 | owner 33 | String/Integer: UID or username 34 | group 35 | String/Integer: GID or groupname 36 | recursive 37 | Boolean: Recursively create or delete the folder and it's parents (default False) 38 | 39 | Link 40 | ==== 41 | 42 | action 43 | create(default), delete 44 | path 45 | String: Path to the link (defaults to 'name') 46 | to 47 | String: Path to the file or directory to link to 48 | hard 49 | Boolean: Create a hard link (default False) 50 | 51 | Execute 52 | ======= 53 | 54 | action 55 | run(default) 56 | command 57 | String: Command to execute (defaults to 'name') 58 | creates 59 | String: Path to a file or directory that is created by the command. If the path exists then don't execute the command. 60 | cwd 61 | String: Working directory when executing the command 62 | environment: 63 | Dict: Extra environment variables 64 | user: 65 | String/Integer: UID or username to run the command as (unimplemented) 66 | group: 67 | String/Integer: GID or groupname to run the command as (unimplemented) 68 | returns: 69 | Integer: Expected return value for success (default 0) 70 | timeout: 71 | Integer: Max number of seconds the command is allowed to execute for (unimplemented) 72 | 73 | Script 74 | ====== 75 | 76 | action 77 | run(default) 78 | code 79 | String: Shell script 80 | cwd 81 | String: Working directory when executing the script 82 | interpreter 83 | String: Interpreter to run the script with (default /bin/bash) 84 | 85 | Mount 86 | ===== 87 | 88 | action 89 | mount(default), umount, remount, enable, disable 90 | mount_point 91 | String: Path to the mount point (defaults to 'name') 92 | device 93 | String: Device to mount 94 | fstype 95 | String: Filesystem type 96 | options 97 | List: List of options given to mount (default ["defaults"]) 98 | dump 99 | Integer: dump value in fstab (default 0) 100 | passno 101 | Integer: passno value in fstab (default 2) 102 | 103 | Package 104 | ======= 105 | 106 | action 107 | install(default), upgrade, remove, purge 108 | package_name 109 | String: Name of package (defaults to 'name') 110 | version 111 | String: Version of package to install 112 | -------------------------------------------------------------------------------- /kokki/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki.base import * 3 | from kokki.environment import * 4 | from kokki.exceptions import * 5 | from kokki.kitchen import * 6 | from kokki.providers import * 7 | from kokki.resources import * 8 | from kokki.source import * 9 | from kokki.system import * 10 | from kokki.version import * 11 | 12 | __version__ = VERSION 13 | -------------------------------------------------------------------------------- /kokki/cookbooks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuel/kokki/da98da55e0bba8db5bda993666a43c6fdc4cacdb/kokki/cookbooks/__init__.py -------------------------------------------------------------------------------- /kokki/cookbooks/apache2/libraries/conf.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Environment, File, Template 3 | 4 | def config(name): 5 | env = Environment.get_instance() 6 | 7 | File("%s/mods-available/%s.conf" % (env.config.apache.dir, name), 8 | content = Template('apache2/mods/%s.conf.j2' % name), 9 | notifies = [("restart", env.resources["Service"]["apache2"])]) 10 | -------------------------------------------------------------------------------- /kokki/cookbooks/apache2/libraries/module.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from kokki import Environment, Execute 4 | 5 | def module(name, enable=True, conf=False): 6 | env = Environment.get_instance() 7 | 8 | if conf: 9 | env.cookbooks.apache2.config(name) 10 | 11 | if enable: 12 | Execute("a2enmod %s" % name, 13 | command = "/usr/sbin/a2enmod %s" % name, 14 | notifies = [("restart", env.resources["Service"]["apache2"])], 15 | not_if = lambda:os.path.exists("%s/mods-enabled/%s.load" % (env.config.apache.dir, name))) 16 | else: 17 | Execute("a2dismod %s" % name, 18 | command = "/usr/sbin/a2dismod %s" % name, 19 | notifies = [("restart", env.resources["Service"]["apache2"])], 20 | only_if = lambda:os.path.exists("%s/mods-enabled/%s.load" % (env.config.apache.dir, name))) 21 | -------------------------------------------------------------------------------- /kokki/cookbooks/apache2/libraries/site.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from kokki import Environment, Execute 4 | 5 | def site(name, enable=True): 6 | env = Environment.get_instance() 7 | 8 | if enable: 9 | Execute("a2ensite %s" % name, 10 | command = "/usr/sbin/a2ensite %s" % name, 11 | notifies = [("restart", env.resources["Service"]["apache2"])], 12 | not_if = lambda:os.path.exists("%s/sites-enabled/%s" % (env.config.apache.dir, name)), 13 | only_if = lambda:os.path.exists("%s/sites-available/%s" % (env.config.apache.dir, name))) 14 | else: 15 | Execute("a2dissite %s" % name, 16 | command = "/usr/sbin/a2dissite %s" % name, 17 | notifies = [("restart", env.resources["Service"]["apache2"])], 18 | only_if = lambda:os.path.exists("%s/sites-enabled/%s" % (env.config.apache.dir, name))) 19 | -------------------------------------------------------------------------------- /kokki/cookbooks/apache2/recipes/prefork.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package 3 | 4 | env.include_recipe("apache2") 5 | 6 | Package("apache2-mpm-prefork") 7 | -------------------------------------------------------------------------------- /kokki/cookbooks/apache2/recipes/worker.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package 3 | 4 | env.include_recipe("apache2") 5 | 6 | Package("apache2-mpm-worker") 7 | -------------------------------------------------------------------------------- /kokki/cookbooks/apache2/templates/charset.j2: -------------------------------------------------------------------------------- 1 | # Managed by Kokki. Do not edit. 2 | # {{ env.config.kokki.long_version }} 3 | 4 | # Read the documentation before enabling AddDefaultCharset. 5 | # In general, it is only a good idea if you know that all your files 6 | # have this encoding. It will override any encoding given in the files 7 | # in meta http-equiv or xml encoding tags. 8 | 9 | #AddDefaultCharset UTF-8 10 | -------------------------------------------------------------------------------- /kokki/cookbooks/apache2/templates/mods/alias.conf.j2: -------------------------------------------------------------------------------- 1 | # Manage by Kokki. Do not edit. 2 | # {{ env.config.kokki.long_version }} 3 | 4 | 5 | # 6 | # Aliases: Add here as many aliases as you need (with no limit). The format is 7 | # Alias fakename realname 8 | # 9 | # Note that if you include a trailing / on fakename then the server will 10 | # require it to be present in the URL. So "/icons" isn't aliased in this 11 | # example, only "/icons/". If the fakename is slash-terminated, then the 12 | # realname must also be slash terminated, and if the fakename omits the 13 | # trailing slash, the realname must also omit it. 14 | # 15 | # We include the /icons/ alias for FancyIndexed directory listings. If 16 | # you do not use FancyIndexing, you may comment this out. 17 | # 18 | Alias /icons/ "{{ env.config.apache.icondir }}" 19 | 20 | 21 | Options Indexes MultiViews 22 | AllowOverride None 23 | Order allow,deny 24 | Allow from all 25 | 26 | 27 | -------------------------------------------------------------------------------- /kokki/cookbooks/apache2/templates/mods/status.conf.j2: -------------------------------------------------------------------------------- 1 | # Manage by Kokki. Do not edit. 2 | # {{ env.config.kokki.long_version }} 3 | 4 | 5 | # 6 | # Allow server status reports generated by mod_status, 7 | # with the URL of http://servername/server-status 8 | # Uncomment and change the ".example.com" to allow 9 | # access from other hosts. 10 | # 11 | 12 | SetHandler server-status 13 | Order deny,allow 14 | Deny from all 15 | Allow from localhost ip6-localhost 16 | # Allow from .example.com 17 | 18 | 19 | -------------------------------------------------------------------------------- /kokki/cookbooks/apache2/templates/ports.conf.j2: -------------------------------------------------------------------------------- 1 | # Managed by Kokki. Do not edit. 2 | # {{ env.config.kokki.long_version }} 3 | 4 | {% for port in env.config.apache.listen_ports %} 5 | Listen {{ port }} 6 | NameVirtualHost *:{{ port }} 7 | {% endfor %} 8 | -------------------------------------------------------------------------------- /kokki/cookbooks/apache2/templates/security.j2: -------------------------------------------------------------------------------- 1 | # Managed by Kokki. Do not edit. 2 | # {{ env.config.kokki.long_version }} 3 | 4 | # 5 | # Disable access to the entire file system except for the directories that 6 | # are explicitly allowed later. 7 | # 8 | # This currently breaks the configurations that come with some web application 9 | # Debian packages. It will be made the default for the release after lenny. 10 | # 11 | # 12 | # AllowOverride None 13 | # Order Deny,Allow 14 | # Deny from all 15 | # 16 | 17 | 18 | # Changing the following options will not really affect the security of the 19 | # server, but might make attacks slightly more difficult in some cases. 20 | 21 | # 22 | # ServerTokens 23 | # This directive configures what you return as the Server HTTP response 24 | # Header. The default is 'Full' which sends information about the OS-Type 25 | # and compiled in modules. 26 | # Set to one of: Full | OS | Minimal | Minor | Major | Prod 27 | # where Full conveys the most information, and Prod the least. 28 | # 29 | #ServerTokens Minimal 30 | ServerTokens {{ env.config.apache.servertokens }} 31 | 32 | # 33 | # Optionally add a line containing the server version and virtual host 34 | # name to server-generated pages (internal error documents, FTP directory 35 | # listings, mod_status and mod_info output etc., but not CGI generated 36 | # documents or custom error documents). 37 | # Set to "EMail" to also include a mailto: link to the ServerAdmin. 38 | # Set to one of: On | Off | EMail 39 | # 40 | #ServerSignature Off 41 | ServerSignature {{ env.config.apache.serversignature }} 42 | 43 | # 44 | # Allow TRACE method 45 | # 46 | # Set to "extended" to also reflect the request body (only for testing and 47 | # diagnostic purposes). 48 | # 49 | # Set to one of: On | Off | extended 50 | # 51 | #TraceEnable Off 52 | TraceEnable {{ env.config.apache.traceenable }} 53 | -------------------------------------------------------------------------------- /kokki/cookbooks/aspersa/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = """ 3 | This project is a repository of simple utilities written in various scripting 4 | languages, which are designed to make slow tasks a bit faster. The name is 5 | taken from the common garden snail. Many of these tools are developed by 6 | consultants at Percona, where the author works. 7 | 8 | http://code.google.com/p/aspersa/ 9 | """ 10 | __config__ = { 11 | "aspersa.install_path": dict( 12 | description = "Path where to install the scripts", 13 | default = "/usr/local/bin", 14 | ), 15 | "aspersa.scripts": dict( 16 | description = "List of scripts to install", 17 | default = [ 18 | "align", "collect", "diskstats", "iodump", "ioprofile", "mext", 19 | "mext2", "mysql-summary", "pmp", "rel", "sif", "slowlog", 20 | "snoop-to-tcpdump", "stalk", "summary", "usl", 21 | ], 22 | ), 23 | } 24 | -------------------------------------------------------------------------------- /kokki/cookbooks/aspersa/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from kokki import Execute, File 4 | 5 | base_url = "http://aspersa.googlecode.com/svn/trunk/{name}" 6 | 7 | for name in env.config.aspersa.scripts: 8 | path = os.path.join(env.config.aspersa.install_path, name) 9 | url = base_url.format(name=name) 10 | Execute("wget -q -O {path} {url}".format(path=path, url=url), 11 | creates = path) 12 | File(path, 13 | owner = "root", 14 | group = "root", 15 | mode = 0755) 16 | -------------------------------------------------------------------------------- /kokki/cookbooks/avatartare/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Avatar server" 3 | __config__ = { 4 | "avatartare.path": dict( 5 | description = "Path to avatartare installation", 6 | default = "/var/www/avatartare", 7 | ), 8 | "avatartare.aws_access_key_id": dict( 9 | description = "AWS key to use for S3", 10 | default = None, 11 | ), 12 | "avatartare.aws_secret_access_key": dict( 13 | description = "AWS secret key to use for S3", 14 | default = None, 15 | ), 16 | "avatartare.s3_bucket": dict( 17 | description = "S3 bucket where to store avatars", 18 | default = "avatartare", 19 | ), 20 | "avatartare.memcached_servers": dict( 21 | description = "List of memcached servers to use", 22 | default = ["localhost:11211"], 23 | ), 24 | "avatartare.process_count": dict( 25 | description = "Number of processes to run", 26 | default = 6, 27 | ), 28 | } 29 | -------------------------------------------------------------------------------- /kokki/cookbooks/avatartare/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from kokki import Package, Directory, Execute, File, Template 4 | 5 | env.include_recipe("mongodb") 6 | env.include_recipe("supervisor") 7 | 8 | env.cookbooks.supervisor.SupervisorService("avatartare") 9 | 10 | Package("python-pycurl") 11 | Package("python-imaging") 12 | 13 | # Clone project 14 | Directory(os.path.dirname(env.config.avatartare.path), mode=0755) 15 | Execute("git clone git://github.com/samuel/avatartare.git %s" % env.config.avatartare.path, 16 | creates = env.config.avatartare.path, 17 | ) 18 | 19 | # Bootstrap the environment 20 | Execute("avatartare-bootstrap", 21 | command = "python bin/bootstrap.py env", 22 | cwd = env.config.avatartare.path, 23 | creates = "%s/env" % env.config.avatartare.path, 24 | ) 25 | 26 | # Config 27 | File("avatartare-local_settings.py", 28 | path = "%s/local_settings.py" % env.config.avatartare.path, 29 | content = Template("avatartare/local_settings.py.j2"), 30 | notifies = [("restart", env.resources["SupervisorService"]["avatartare"])]) 31 | 32 | # Setup Supervisor to start and monitor the processes 33 | File("%s/avatartare.conf" % env.config.supervisor.custom_config_path, 34 | content = Template("avatartare/supervisor.j2"), 35 | notifies = [("restart", env.resources["SupervisorService"]["avatartare"])]) 36 | -------------------------------------------------------------------------------- /kokki/cookbooks/avatartare/templates/local_settings.py.j2: -------------------------------------------------------------------------------- 1 | settings = dict( 2 | debug = False, 3 | aws_api_key = {{ repr(env.config.avatartare.aws_access_key_id) }}, 4 | aws_secret_key = {{ repr(env.config.avatartare.aws_secret_access_key) }}, 5 | s3_bucket = {{ repr(env.config.avatartare.s3_bucket) }}, 6 | memcached_servers = {{ repr(env.config.avatartare.memcached_servers) }}, 7 | ) 8 | -------------------------------------------------------------------------------- /kokki/cookbooks/avatartare/templates/supervisor.j2: -------------------------------------------------------------------------------- 1 | [program:avatartare] 2 | command = {{ env.config.avatartare.path }}/env/bin/python main_tornado.py 808%(process_num)d 3 | {% if "librato.silverline" in env.included_recipes %} 4 | environment = SL_NAME="avatartare" 5 | {% endif %} 6 | directory = {{ env.config.avatartare.path }} 7 | numprocs = {{ env.config.avatartare.process_count }} 8 | autostart = true 9 | autorestart = true 10 | process_name = avatartare_%(process_num)d 11 | stderr_logfile = /var/log/avatartare-proc.log 12 | startretries = 20 13 | -------------------------------------------------------------------------------- /kokki/cookbooks/aws/libraries/resources.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Resource, ResourceArgument 3 | 4 | class ElasticIP(Resource): 5 | actions = ["associate", "disassociate"] 6 | 7 | aws_access_key = ResourceArgument() 8 | aws_secret_access_key = ResourceArgument() 9 | ip = ResourceArgument() 10 | timeout = ResourceArgument(default=3*60) # None or 0 for no timeout 11 | 12 | class EBSVolume(Resource): 13 | provider = "*aws.EBSVolumeProvider" 14 | 15 | actions = ["create", "attach", "detach", "snapshot"] 16 | 17 | volume_id = ResourceArgument() 18 | aws_access_key = ResourceArgument() 19 | aws_secret_access_key = ResourceArgument() 20 | size = ResourceArgument() 21 | snapshot_id = ResourceArgument() 22 | snapshot_required = ResourceArgument(default=False) 23 | availability_zone = ResourceArgument() 24 | device = ResourceArgument() 25 | linux_device = ResourceArgument() 26 | timeout = ResourceArgument(default=3*60) # None or 0 for no timeout 27 | -------------------------------------------------------------------------------- /kokki/cookbooks/aws/libraries/volume.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Environment, Mount, Execute, Package 3 | 4 | def setup_ebs_volume(name=None, availability_zone=None, volume_id=None, device=None, linux_device=None, snapshot_id=None, size=None, fstype=None, mount_point=None, fsoptions=None): 5 | env = Environment.get_instance() 6 | 7 | if linux_device is None: 8 | linux_device = device 9 | 10 | env.cookbooks.aws.EBSVolume(name or volume_id, 11 | volume_id = volume_id, 12 | availability_zone = availability_zone or env.config.aws.availability_zone, 13 | device = device, 14 | linux_device = linux_device, 15 | snapshot_id = snapshot_id, 16 | size = size, 17 | action = "attach" if volume_id else ["create", "attach"]) 18 | 19 | if fstype: 20 | if fstype == "xfs": 21 | Package("xfsprogs") 22 | Execute("mkfs.%(fstype)s -f %(device)s" % dict(fstype=fstype, device=linux_device), 23 | not_if = """if [ "`file -s %(device)s`" = "%(device)s: data" ]; then exit 1; fi""" % dict(device=linux_device)) 24 | 25 | if mount_point: 26 | Mount(mount_point, 27 | device = linux_device, 28 | fstype = fstype, 29 | options = fsoptions if fsoptions is not None else ["noatime"], 30 | action = ["mount", "enable"]) 31 | -------------------------------------------------------------------------------- /kokki/cookbooks/aws/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Resources and providers to support Amazon's Web Services (EC2, S3, etc..)" 3 | __config__ = { 4 | "aws.access_key_id": dict( 5 | description = "API Key for AWS", 6 | default = None, 7 | ), 8 | "aws.secret_access_key": dict( 9 | description = "Secret key for AWS", 10 | default = None, 11 | ), 12 | "aws.volumes": dict( 13 | description = "Volumes to attach and mount to the current instance (volume_id, device, fstype, fsoptions, mount_point)", 14 | default = [], 15 | ), 16 | } 17 | 18 | def lazyproperty(method): 19 | name = "_"+method.__name__ 20 | @property 21 | def _lazyproperty(self): 22 | try: 23 | return getattr(self, name) 24 | except AttributeError: 25 | value = method(self) 26 | setattr(self, name, value) 27 | return value 28 | return _lazyproperty 29 | 30 | class LazyAWS(object): 31 | def __init__(self, access_key_id, secret_access_key): 32 | self.access_key_id = access_key_id 33 | self.secret_access_key = secret_access_key 34 | 35 | @lazyproperty 36 | def ec2(self): 37 | from boto.ec2 import EC2Connection 38 | self._ec2 = EC2Connection( 39 | self.access_key_id, 40 | self.secret_access_key) 41 | return self._ec2 42 | 43 | @lazyproperty 44 | def volumes(self): 45 | ec2 = self.ec2 46 | 47 | all_volumes = ec2.get_all_volumes() 48 | volumes = [] 49 | for v in all_volumes: 50 | if v.attach_data and v.attach_data.instance_id == self.instance_id: 51 | volumes.append(v) 52 | return volumes 53 | 54 | def get_ec2_metadata(self, key): 55 | import urllib2 56 | res = urllib2.urlopen("http://169.254.169.254/2009-04-04/meta-data/" + key) 57 | return res.read().strip() 58 | 59 | @lazyproperty 60 | def instance_id(self): 61 | return self.get_ec2_metadata('instance-id') 62 | 63 | @lazyproperty 64 | def instance_type(self): 65 | return self.get_ec2_metadata('instance-type') 66 | 67 | @lazyproperty 68 | def availability_zone(self): 69 | return self.get_ec2_metadata('placement/availability-zone') 70 | 71 | @lazyproperty 72 | def tags(self): 73 | res = self.ec2.get_all_instances([self.instance_id]) 74 | if not res: 75 | return {} 76 | return res.instances[0].tags 77 | 78 | def __loader__(kit): 79 | aws = LazyAWS( 80 | kit.config.aws.access_key_id, 81 | kit.config.aws.secret_access_key 82 | ) 83 | 84 | kit.update_config({ 85 | "aws.resources": aws, 86 | "aws.instance_id": aws.instance_id, 87 | "aws.instance_type": aws.instance_type, 88 | "aws.availability_zone": aws.availability_zone, 89 | }) 90 | -------------------------------------------------------------------------------- /kokki/cookbooks/aws/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | env.include_recipe("boto") 3 | 4 | # Mount volumes and format is necessary 5 | 6 | for vol in env.config.aws.volumes: 7 | env.cookbooks.aws.setup_ebs_volume(**vol) 8 | -------------------------------------------------------------------------------- /kokki/cookbooks/boto/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Amazon Web Services library for Python" 3 | __config__ = {} 4 | -------------------------------------------------------------------------------- /kokki/cookbooks/boto/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package 3 | 4 | # Package("python-boto") 5 | 6 | # Package("pip", 7 | # provider = "kokki.providers.package.easy_install.EasyInstallProvider") 8 | # Execute("mv /usr/lib/pymodules/python2.6/boto /tmp/boto.orig", 9 | # only_if = lambda:os.path.exists("/usr/lib/pymodules/python2.6/boto")) 10 | # Execute("pip install git+http://github.com/boto/boto.git#egg=boto", 11 | # not_if = 'python -c "import boto"') 12 | 13 | Package("boto", 14 | action = "upgrade", 15 | provider = "kokki.providers.package.easy_install.EasyInstallProvider") 16 | -------------------------------------------------------------------------------- /kokki/cookbooks/busket/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Service monitoring" 3 | __config__ = { 4 | "busket.path": dict( 5 | description = "Install path for busket", 6 | default = "/usr/local/busket", 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /kokki/cookbooks/busket/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from kokki import Package, File, Service, Script 4 | 5 | Package("erlang") 6 | # ubuntu's erlang is a bit messed up.. remove the man link 7 | File("/usr/lib/erlang/man", 8 | action = "delete") 9 | 10 | # Package("mercurial", 11 | # provider = "kokki.providers.package.easy_install.EasyInstallProvider") 12 | 13 | command = os.path.join(env.config.busket.path, "bin", "busket") 14 | 15 | Service("busket", 16 | start_command = "%s start" % command, 17 | stop_command = "%s stop" % command, 18 | restart_command = "{0} start || {0} restart".format(command), 19 | status_command = "%s ping" % command, 20 | action = "nothing") 21 | 22 | Script("install-busket", 23 | not_if = lambda:os.path.exists(env.config.busket.path), 24 | cwd = "/usr/local/src", 25 | code = ( 26 | "git clone git://github.com/samuel/busket.git busket\n" 27 | "cd busket\n" 28 | "mkdir /tmp/erlhome\n" 29 | "export HOME=/tmp/erlhome\n" 30 | "make release\n" 31 | "mv rel/busket {install_path}\n" 32 | ).format(install_path=env.config.busket.path), 33 | notifies = [("start", env.resources["Service"]["busket"])], 34 | ) 35 | 36 | if "librato.silverline" in env.included_recipes: 37 | File("/etc/default/busket", 38 | owner = "root", 39 | group = "root", 40 | mode = 0644, 41 | content = ( 42 | 'RUNNER_ENV="SL_NAME=busket"\n' 43 | ), 44 | notifies = [("restart", env.resources["Service"]["busket"])]) 45 | -------------------------------------------------------------------------------- /kokki/cookbooks/cassandra/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Cassandra database" 3 | __config__ = { 4 | "cassandra.cluster_name": dict( 5 | display_name = "Cassandra cluster name", 6 | description = "Name of the Cassandra cluster", 7 | default = "Test Cluster", 8 | ), 9 | "cassandra.auto_bootstrap": dict( 10 | display_name = "Auto bootstrap", 11 | description = "Turns on or off the auto-bootstrapping of the node", 12 | default = False, 13 | ), 14 | "cassandra.hinted_handoff_enabled": dict( 15 | display_name = "Hinted hand-off enabled", 16 | description = "Enable or disable hinted hand-off", 17 | default = True, 18 | ), 19 | "cassandra.partitioner": dict( 20 | display_name = "Partitioner", 21 | description = "Partitioner class path", 22 | default = "org.apache.cassandra.dht.RandomPartitioner", 23 | ), 24 | "cassandra.data_file_directories": dict( 25 | display_name = "Data file directories", 26 | description = "List of directories where Cassandra should store data on disk", 27 | default = ['/var/lib/cassandra/data'], 28 | ), 29 | "cassandra.seeds": dict( 30 | display_name = "Seed nodes", 31 | description = "List of addresses for seed nodes", 32 | default = ['127.0.0.1'], 33 | ), 34 | "cassandra.disk_access_mode": dict( 35 | display_name = "Disk access mode", 36 | description = "How Cassandra handles i/o (auto, mmap, mmap_index_only, or standard)", 37 | default = "auto", 38 | ), 39 | "cassandra.listen_address": dict( 40 | display_name = "Listen address", 41 | description = "Address to bind to and tell other nodes to connect to", 42 | default = "127.0.0.1", 43 | ), 44 | "cassandra.rpc_address": dict( 45 | display_name = "RPC Address", 46 | description = "Address to bind the Thrift RPC to", 47 | default = "127.0.0.1", 48 | ), 49 | "cassandra.commitlog_directory": dict( 50 | display_name = "Commit log directory", 51 | description = "Path where to store the commit log", 52 | default = "/var/lib/cassandra/commitlog", 53 | ), 54 | "cassandra.endpoint_snitch": dict( 55 | display_nane = "End-point snitch", 56 | description = "Class that will Cassandra discover topology of the network", 57 | default = "org.apache.cassandra.locator.SimpleSnitch", 58 | ), 59 | } 60 | -------------------------------------------------------------------------------- /kokki/cookbooks/cassandra/recipes/default.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuel/kokki/da98da55e0bba8db5bda993666a43c6fdc4cacdb/kokki/cookbooks/cassandra/recipes/default.py -------------------------------------------------------------------------------- /kokki/cookbooks/cloudera/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Cloudera Repositories" 3 | __config__ = {} 4 | -------------------------------------------------------------------------------- /kokki/cookbooks/cloudera/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Execute, File 3 | 4 | env.include_recipe("java.jre") 5 | 6 | apt_list_path = '/etc/apt/sources.list.d/cloudera.list' 7 | apt = ( 8 | "deb http://archive.cloudera.com/debian {distro}-cdh3 contrib\n" 9 | "deb-src http://archive.cloudera.com/debian {distro}-cdh3 contrib\n" 10 | ).format(distro=env.system.lsb['codename']) 11 | 12 | Execute("apt-update-clouders", 13 | command = "apt-get update", 14 | action = "nothing") 15 | 16 | Execute("curl -s http://archive.cloudera.com/debian/archive.key | sudo apt-key add -", 17 | not_if = "(apt-key list | grep Cloudera > /dev/null)") 18 | 19 | File(apt_list_path, 20 | owner = "root", 21 | group ="root", 22 | mode = 0644, 23 | content = apt, 24 | notifies = [("run", env.resources["Execute"]["apt-update-clouders"], True)]) 25 | -------------------------------------------------------------------------------- /kokki/cookbooks/cloudkick/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "CloudKick server monitoring" 3 | __config__ = { 4 | "cloudkick.oauth_key": dict( 5 | description = "OAuth Key", 6 | default = None, 7 | ), 8 | "cloudkick.oath_secret": dict( 9 | description = "OAuth Secret", 10 | default = None, 11 | ), 12 | "cloudkick.tags": dict( 13 | description = "Tags", 14 | default = [], 15 | ), 16 | "cloudkick.hostname": dict( 17 | description = "Hostname", 18 | default = None, 19 | ), 20 | } 21 | -------------------------------------------------------------------------------- /kokki/cookbooks/cloudkick/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Execute, Fail, File, Template, Package, Service 3 | 4 | assert env.config.cloudkick.oauth_key and env.config.cloudkick.oauth_secret and env.config.cloudkick.hostname 5 | 6 | apt_list_path = '/etc/apt/sources.list.d/cloudkick.list' 7 | apt = None 8 | if env.system.platform == "ubuntu": 9 | ver = env.system.lsb['release'] 10 | if ver in ("10.10", "11.04", "11.10"): 11 | apt = "deb http://packages.cloudkick.com/ubuntu maverick main" 12 | elif ver == "10.04": 13 | apt = "deb http://packages.cloudkick.com/ubuntu lucid main" 14 | elif ver == "9.10": 15 | apt = "deb http://packages.cloudkick.com/ubuntu karmic main" 16 | elif ver == "9.04": 17 | apt = "deb http://packages.cloudkick.com/ubuntu jaunty main" 18 | elif ver == "8.10": 19 | apt = "deb http://packages.cloudkick.com/ubuntu intrepid main" 20 | elif ver == "8.04": 21 | apt = "deb http://packages.cloudkick.com/ubuntu hardy main" 22 | elif ver == "6.04": 23 | apt = "deb http://packages.cloudkick.com/ubuntu dapper main" 24 | elif env.system.platform == "debian": 25 | ver = env.system.lsb['release'] 26 | apt = "deb http://packages.cloudkick.com/ubuntu lucid main" 27 | # if ver == '5.0': 28 | # apt = "deb http://apt.librato.com/debian/ lenny non-free" 29 | 30 | if not apt: 31 | raise Fail("Can't find a cloudkick package for your platform/version") 32 | 33 | Execute("apt-update-cloudkick", 34 | command = "apt-get update", 35 | action = "nothing") 36 | 37 | Execute("curl http://packages.cloudkick.com/cloudkick.packages.key | apt-key add -", 38 | not_if = "(apt-key list | grep 'Cloudkick' > /dev/null)") 39 | 40 | File(apt_list_path, 41 | owner = "root", 42 | group ="root", 43 | mode = 0644, 44 | content = apt+"\n", 45 | notifies = [("run", env.resources["Execute"]["apt-update-cloudkick"], True)]) 46 | 47 | File("/etc/cloudkick.conf", 48 | owner = "root", 49 | group = "root", 50 | mode = 0644, 51 | content = Template("cloudkick/cloudkick.conf.j2")) 52 | 53 | Package("cloudkick-agent", 54 | action = "upgrade") 55 | 56 | Service("cloudkick-agent", 57 | supports_restart = True, 58 | subscribes = [("restart", env.resources["File"]["/etc/cloudkick.conf"])]) 59 | 60 | Package("libssl0.9.8") # This seems to not get installed for some reason 61 | -------------------------------------------------------------------------------- /kokki/cookbooks/cloudkick/templates/cloudkick.conf.j2: -------------------------------------------------------------------------------- 1 | oauth_key {{ env.config.cloudkick.oauth_key }} 2 | oauth_secret {{ env.config.cloudkick.oauth_secret }} 3 | tags {{ ",".join(env.config.cloudkick.tags) }} 4 | name {{ env.config.cloudkick.hostname }} 5 | -------------------------------------------------------------------------------- /kokki/cookbooks/exim4/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Exim4 mail server" 3 | __config__ = { 4 | "exim4.configtype": dict( 5 | description = "Type of config for mail sending (e.g. satellite)", 6 | default = "satellite", 7 | ), 8 | "exim4.other_hostnames": dict( 9 | description = "Other host names to receive mail for", 10 | default = "localhost", 11 | ), 12 | "exim4.local_interfaces": dict( 13 | description = "Local interfaces to bind to", 14 | default = "127.0.0.1", 15 | ), 16 | "exim4.readhost": dict( 17 | description = "Read host", 18 | default = "localhost", 19 | ), 20 | "exim4.smarthost": dict( 21 | description = "Host through which to forward mail", 22 | default = "", 23 | ), 24 | "exim4.auth": dict( 25 | description = "Credentials to use when authenticating to a remote server. List of dictionaries with keys 'domain', 'login', and 'password'.", 26 | default = [], 27 | ), 28 | } 29 | -------------------------------------------------------------------------------- /kokki/cookbooks/exim4/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, Service, File, Template 3 | 4 | Package("exim4", action="upgrade") 5 | Service("exim4", 6 | supports_restart=True) 7 | 8 | File("/etc/exim4/update-exim4.conf.conf", 9 | owner = "root", 10 | group = "root", 11 | mode = 0644, 12 | content = Template("exim4/update-exim4.conf.conf.j2"), 13 | notifies = [("restart", env.resources["Service"]["exim4"])]) 14 | 15 | File("/etc/exim4/exim4.conf.localmacros", 16 | owner = "root", 17 | group = "root", 18 | mode = 0644, 19 | content = "AUTH_CLIENT_ALLOW_NOTLS_PASSWORDS = 1\nMAIN_TLS_ENABLE = 1\n", 20 | notifies = [("restart", env.resources["Service"]["exim4"])]) 21 | 22 | File("/etc/exim4/passwd.client", 23 | mode = 0640, 24 | content = Template("exim4/passwd.client.j2"), 25 | notifies = [("restart", env.resources["Service"]["exim4"])]) 26 | -------------------------------------------------------------------------------- /kokki/cookbooks/exim4/templates/passwd.client.j2: -------------------------------------------------------------------------------- 1 | # password file used when the local exim is authenticating to a remote 2 | # host as a client. 3 | # 4 | # see exim4_passwd_client(5) for more documentation 5 | # 6 | # Example: 7 | ### target.mail.server.example:login:password 8 | {% for auth in env.config.exim4.auth %} 9 | {{ auth.domain }}:{{ auth.login }}:{{ auth.password }} 10 | {% endfor %} 11 | -------------------------------------------------------------------------------- /kokki/cookbooks/exim4/templates/update-exim4.conf.conf.j2: -------------------------------------------------------------------------------- 1 | dc_eximconfig_configtype='{{ env.config.exim4.configtype }}' 2 | dc_other_hostnames='{{ env.config.exim4.other_hostnames }}' 3 | dc_local_interfaces='{{ env.config.exim4.local_interfaces }}' 4 | dc_readhost='{{ env.config.exim4.readhost }}' 5 | dc_relay_domains='' 6 | dc_minimaldns='false' 7 | dc_relay_nets='' 8 | dc_smarthost='{{ env.config.exim4.smarthost }}' 9 | CFILEMODE='644' 10 | dc_use_split_config='false' 11 | dc_hide_mailname='true' 12 | dc_mailname_in_oh='true' 13 | dc_localdelivery='mail_spool' 14 | -------------------------------------------------------------------------------- /kokki/cookbooks/flume/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Data flows" 3 | __config__ = { 4 | "flume.master.servers": dict( 5 | description = "A comma-separated list of hostnames, one for each machine in the Flume Master.", 6 | default = None, 7 | ), 8 | "flume.plugin.classes": dict( 9 | description = "Comma separated list of plugin classes", 10 | default = None, 11 | ), 12 | "flume.collector.event.host": dict( 13 | description = "Host name of the default 'remote' collector", 14 | default = None, 15 | ), 16 | "flume.collector.post": dict( 17 | description = "Default tcp port that the collector listen to in order to receive events it is collecting", 18 | default = None, 19 | ), 20 | "flume.master.zk.logdir": dict( 21 | description = "Base directory in which the ZBCS stores data", 22 | default = "/tmp/flume-zk", 23 | ), 24 | "flume.master.zk.server.quorum.port": dict( 25 | description = "ZooKeeper quorum port", 26 | default = 3182, 27 | ), 28 | "flume.master.zk.server.election.port": dict( 29 | description = "ZooKeeper election port", 30 | default = 3183, 31 | ), 32 | "flume.master.zk.client.port": dict( 33 | description = "ZooKeeper client port", 34 | default = 3181, 35 | ), 36 | "flume.master.zk.use.external": dict( 37 | description = "Use an external ZooKeeper cluter", 38 | default = False, 39 | ), 40 | "flume.master.zk.servers": dict( 41 | description = "Comma-separated list of external ZooKeeper servers", 42 | default = None, 43 | ), 44 | "flume.agent.logdir": dict( 45 | description = "This is the directory that write-ahead logging data" 46 | "or disk-failover data is collected from applicaitons" 47 | "gets written to. The agent watches this directory.", 48 | default = "/tmp/flume/agent", 49 | ), 50 | "flume.collector.dfs.dir": dict( 51 | description = "This is a dfs directory that is the the final resting" 52 | "place for logs to be stored in. This defaults to a local dir in" 53 | "/tmp but can be hadoop URI path that such as hdfs://namenode/path/", 54 | default = "file:///tmp/flume/collected", 55 | ), 56 | "flume.collector.dfs.compress.gzip": dict( 57 | description = "Writes compressed output in gzip format to dfs.", 58 | value = False, 59 | ), 60 | "flume.log_level": dict( 61 | description = "Log level (DEBUG, INFO, WARN, ERROR)", 62 | value = "INFO", 63 | ), 64 | "flume.aws_access_key_id": dict( 65 | description = "AWS access key id to use S3 for storage", 66 | value = None, 67 | ), 68 | "flume.aws_secret_access_key": dict( 69 | description = "AWS secret access key to use S3 for storage", 70 | value = None, 71 | ), 72 | } 73 | -------------------------------------------------------------------------------- /kokki/cookbooks/flume/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, Directory, Link, File, Template 3 | 4 | env.include_recipe("cloudera") 5 | 6 | Package("flume") 7 | 8 | Directory("/etc/flume/conf.kokki", 9 | owner = "root", 10 | group = "root", 11 | mode = 0755) 12 | 13 | Link("/etc/flume/conf", 14 | to = "/etc/flume/conf.kokki") 15 | 16 | File("flume-config", 17 | path = "/etc/flume/conf.kokki/flume-conf.xml", 18 | owner = "root", 19 | group = "root", 20 | mode = 0644, 21 | content = Template("flume/flume-conf.xml.j2")) 22 | 23 | File("flume-site-config", 24 | path = "/etc/flume/conf.kokki/flume-site.xml", 25 | owner = "root", 26 | group = "root", 27 | mode = 0644, 28 | content = Template("flume/flume-site.xml.j2")) 29 | 30 | File("flume-log-config", 31 | path = "/etc/flume/conf.kokki/log4j.properties", 32 | owner = "root", 33 | group = "root", 34 | mode = 0644, 35 | content = Template("flume/log4j.properties.j2")) 36 | 37 | File("flume-daemon-sh", 38 | path = "/usr/lib/flume/bin/flume-daemon.sh", 39 | owner = "root", 40 | group = "root", 41 | mode = 0755, 42 | content = Template("flume/flume-daemon.sh.j2")) 43 | 44 | Directory(env.config.flume.agent.logdir, 45 | owner = "flume", 46 | group = "flume", 47 | recursive = True) 48 | 49 | Directory(env.config.flume.master.zk.logdir, 50 | owner = "flume", 51 | group = "flume", 52 | recursive = True) 53 | -------------------------------------------------------------------------------- /kokki/cookbooks/flume/recipes/master.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, Service 3 | 4 | env.include_recipe("flume") 5 | 6 | Package("flume-master") 7 | 8 | import os 9 | def flume_master_status(): 10 | pid_path = "/var/run/flume/flume-flume-master.pid" 11 | try: 12 | with open(pid_path, "rb") as fp: 13 | pid = int(fp.read().strip()) 14 | os.getpgid(pid) # Throws OSError if processes doesn't exist 15 | except (IOError, OSError, ValueError): 16 | return False 17 | return True 18 | 19 | Service("flume-master", 20 | supports_restart = True, 21 | supports_reload = False, 22 | status_command = flume_master_status, 23 | subscribes = [ 24 | ("restart", env.resources["File"]["flume-config"]), 25 | ("restart", env.resources["File"]["flume-site-config"]), 26 | ("restart", env.resources["File"]["flume-log-config"]), 27 | ("restart", env.resources["File"]["flume-daemon-sh"]), 28 | ]) 29 | -------------------------------------------------------------------------------- /kokki/cookbooks/flume/recipes/node.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, Service 3 | 4 | env.include_recipe("flume") 5 | 6 | Package("flume-node") 7 | 8 | import os 9 | def flume_node_status(): 10 | pid_path = "/var/run/flume/flume-flume-node.pid" 11 | try: 12 | with open(pid_path, "rb") as fp: 13 | pid = int(fp.read().strip()) 14 | os.getpgid(pid) # Throws OSError if processes doesn't exist 15 | except (IOError, OSError, ValueError): 16 | return False 17 | return True 18 | 19 | Service("flume-node", 20 | supports_restart = True, 21 | supports_reload = False, 22 | status_command = flume_node_status, 23 | subscribes = [ 24 | ("restart", env.resources["File"]["flume-config"]), 25 | ("restart", env.resources["File"]["flume-site-config"]), 26 | ("restart", env.resources["File"]["flume-log-config"]), 27 | ("restart", env.resources["File"]["flume-daemon-sh"]), 28 | ]) 29 | -------------------------------------------------------------------------------- /kokki/cookbooks/flume/templates/flume-daemon.sh.j2: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env /bin/bash 2 | # Licensed to Cloudera, Inc. under one 3 | # or more contributor license agreements. See the NOTICE file 4 | # distributed with this work for additional information 5 | # regarding copyright ownership. Cloudera, Inc. licenses this file 6 | # to you under the Apache License, Version 2.0 (the 7 | # "License"); you may not use this file except in compliance 8 | # with the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | usage="Usage: $0.sh (start|stop) " 19 | 20 | # if no args specified, show usage 21 | if [ $# -le 1 ]; then 22 | echo $usage 23 | exit 1 24 | fi 25 | 26 | bin=`dirname "$0"` 27 | bin=`cd "$bin"; pwd` 28 | 29 | # get arguments 30 | startStop=$1 31 | shift 32 | command=$1 33 | shift 34 | 35 | flume_rotate_log () 36 | { 37 | log=$1; 38 | num=5; 39 | if [ -n "$2" ]; then 40 | num=$2 41 | fi 42 | if [ -f "$log" ]; then # rotate logs 43 | while [ $num -gt 1 ]; do 44 | prev=`expr $num - 1` 45 | [ -f "$log.$prev" ] && mv "$log.$prev" "$log.$num" 46 | num=$prev 47 | done 48 | mv "$log" "$log.$num"; 49 | fi 50 | } 51 | 52 | if [ "$FLUME_HOME" = "" ]; then 53 | export FLUME_HOME=/usr/lib/flume 54 | fi 55 | 56 | if [ "$FLUME_LOG_DIR" = "" ]; then 57 | export FLUME_LOG_DIR="/var/log/flume" 58 | fi 59 | 60 | if [ "$FLUME_NICENESS" = "" ]; then 61 | export FLUME_NICENESS=0 62 | fi 63 | 64 | if [ "$FLUME_PID_DIR" = "" ]; then 65 | FLUME_PID_DIR=/var/run/flume 66 | fi 67 | 68 | if [ "$FLUME_IDENT_STRING" = "" ]; then 69 | export FLUME_IDENT_STRING="$USER" 70 | fi 71 | 72 | # some variables 73 | export FLUME_LOGFILE=flume-$FLUME_IDENT_STRING-$command-$HOSTNAME.log 74 | export FLUME_ROOT_LOGGER="{{ env.config.flume.log_level.upper() }},DRFA" 75 | export ZOOKEEPER_ROOT_LOGGER="{{ env.config.flume.log_level.upper() }},zookeeper" 76 | export WATCHDOG_ROOT_LOGGER="{{ env.config.flume.log_level.upper() }},watchdog" 77 | log=$FLUME_LOG_DIR/flume-$FLUME_IDENT_STRING-$command-$HOSTNAME.out 78 | pid=$FLUME_PID_DIR/flume-$FLUME_IDENT_STRING-$command.pid 79 | 80 | case $startStop in 81 | 82 | (start) 83 | mkdir -p "$FLUME_PID_DIR" 84 | if [ -f $pid ]; then 85 | if kill -0 `cat $pid` > /dev/null 2>&1; then 86 | echo $command running as process `cat $pid`. Stop it first. 87 | exit 1 88 | fi 89 | fi 90 | 91 | flume_rotate_log $log 92 | echo starting $command, logging to $log 93 | cd "$FLUME_HOME" 94 | nohup nice -n ${FLUME_NICENESS} "${FLUME_HOME}"/bin/flume $command "$@" > "$log" 2>&1 < /dev/null & 95 | echo $! > $pid 96 | sleep 1; head "$log" 97 | ;; 98 | 99 | (stop) 100 | 101 | if [ -f $pid ]; then 102 | if kill -0 `cat $pid` > /dev/null 2>&1; then 103 | echo stopping $command 104 | kill `cat $pid` 105 | else 106 | echo no $command to stop 107 | fi 108 | else 109 | echo no $command to stop 110 | fi 111 | ;; 112 | 113 | (*) 114 | echo $usage 115 | exit 1 116 | ;; 117 | 118 | esac 119 | -------------------------------------------------------------------------------- /kokki/cookbooks/flume/templates/log4j.properties.j2: -------------------------------------------------------------------------------- 1 | # Define some default values that can be overridden by system properties 2 | 3 | # Licensed to Cloudera, Inc. under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. Cloudera, Inc. licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | flume.root.logger={{ env.config.flume.log_level.upper() }},console 20 | flume.log.dir=. 21 | flume.log.file=flume.log 22 | 23 | # Define the root logger to the system property "flume.root.logger". 24 | log4j.rootLogger=${flume.root.logger} 25 | 26 | # Logging Threshold 27 | log4j.threshhold=ALL 28 | 29 | # 30 | # Daily Rolling File Appender 31 | # 32 | 33 | log4j.appender.DRFA=org.apache.log4j.DailyRollingFileAppender 34 | log4j.appender.DRFA.File=${flume.log.dir}/${flume.log.file} 35 | 36 | # Rollver at midnight 37 | log4j.appender.DRFA.DatePattern=.yyyy-MM-dd 38 | 39 | # 30-day backup 40 | 41 | #log4j.appender.DRFA.MaxBackupIndex=30 42 | log4j.appender.DRFA.layout=org.apache.log4j.PatternLayout 43 | 44 | # Pattern format: Date LogLevel LoggerName LogMessage 45 | log4j.appender.DRFA.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n 46 | 47 | log4j.appender.zookeeper=org.apache.log4j.DailyRollingFileAppender 48 | log4j.appender.zookeeper.File=${flume.log.dir}/zk-${flume.log.file} 49 | log4j.appender.zookeeper.DatePattern=.yyyy-MM-dd 50 | log4j.appender.zookeeper.layout=org.apache.log4j.PatternLayout 51 | log4j.appender.zookeeper.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n 52 | log4j.logger.org.apache.zookeeper=${zookeeper.root.logger} 53 | 54 | log4j.appender.watchdog=org.apache.log4j.DailyRollingFileAppender 55 | log4j.appender.watchdog.File=${flume.log.dir}/wd-${flume.log.file} 56 | log4j.appender.watchdog.DatePattern=.yyyy-MM-dd 57 | log4j.appender.watchdog.layout=org.apache.log4j.PatternLayout 58 | log4j.appender.watchdog.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n 59 | log4j.logger.org.apache.watchdog=${watchdog.root.logger} 60 | 61 | 62 | 63 | # 64 | # console 65 | # Add "console" to rootlogger above if you want to use this 66 | # 67 | 68 | log4j.appender.console=org.apache.log4j.ConsoleAppender 69 | log4j.appender.console.target=System.err 70 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 71 | log4j.appender.console.layout.ConversionPattern=%d{ISO8601} [%t] %p %c{2}: %m%n 72 | -------------------------------------------------------------------------------- /kokki/cookbooks/gearmand/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Gearman RPC broker" 3 | __config__ = { 4 | "gearmand.listen_address": dict( 5 | description = "IP address to bind to", 6 | default = "127.0.0.1", 7 | ), 8 | "gearmand.user": dict( 9 | display_name = "Gearmand user", 10 | description = "User to run the gearmand procses as", 11 | default = "nobody", 12 | ), 13 | "gearmand.pidfile": dict( 14 | display_name = "Gearmand pid file", 15 | description = "Path to the PID file for gearmand", 16 | default = "/var/run/gearmand/gearmand.pid", 17 | ), 18 | } 19 | -------------------------------------------------------------------------------- /kokki/cookbooks/gearmand/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, Directory, Script, Template, Fail 3 | 4 | env.include_recipe("monit") 5 | 6 | def install_package(name, url, creates): 7 | import os 8 | filename = url.rsplit('/', 1)[-1] 9 | dirname = filename 10 | while dirname.rsplit('.', 1)[-1] in ('gz', 'tar', 'tgz', 'bz2'): 11 | dirname = dirname.rsplit('.', 1)[0] 12 | 13 | if not dirname: 14 | raise Fail("Unable to figure out directory name of project for URL %s" % url) 15 | 16 | Script("install-%s" % name, 17 | not_if = lambda:os.path.exists(creates), 18 | cwd = "/usr/local/src", 19 | code = ( 20 | "wget %(url)s\n" 21 | "tar -zxvf %(filename)s\n" 22 | "cd %(dirname)s\n" 23 | "./configure && make install\n" 24 | "ldconfig\n") % dict(url=url, dirname=dirname, filename=filename) 25 | ) 26 | 27 | Package("uuid-dev") 28 | Package("libevent-dev") 29 | Package("g++") 30 | install_package("gearmand", 31 | creates = "/usr/local/sbin/gearmand", 32 | url = "http://launchpad.net/gearmand/trunk/0.14/+download/gearmand-0.14.tar.gz") 33 | 34 | Directory("/var/run/gearmand", 35 | owner = "nobody", 36 | mode = 0755) 37 | env.cookbooks.monit.rc("gearmand", 38 | content = Template("gearmand/monit.conf.j2")) 39 | -------------------------------------------------------------------------------- /kokki/cookbooks/gearmand/templates/monit.conf.j2: -------------------------------------------------------------------------------- 1 | check file gearmand_bin with path /usr/local/sbin/gearmand 2 | if failed checksum then unmonitor 3 | if failed permission 755 then unmonitor 4 | if failed uid root then unmonitor 5 | 6 | check process gearmand with pidfile /var/run/gearmand/gearmand.pid 7 | start program = "/usr/local/sbin/gearmand -P {{ env.config.gearmand.pidfile }} -d -u {{ env.config.gearmand.user }} -L {{ env.config.gearmand.listen_address }}" 8 | stop program = "/usr/bin/killall gearmand" 9 | if 5 restarts within 5 cycles then timeout 10 | depends on gearmand_bin 11 | -------------------------------------------------------------------------------- /kokki/cookbooks/java/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Sun Java 6" 3 | __config__ = {} 4 | -------------------------------------------------------------------------------- /kokki/cookbooks/java/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from kokki import Package, Execute, File, Script 4 | 5 | Package("debconf-utils") 6 | 7 | Execute("apt-update-java", 8 | command = "apt-get update", 9 | action = "nothing") 10 | 11 | if env.system.lsb['codename'] == 'karmic': 12 | def enter_the_multiverse(): 13 | with open("/etc/apt/sources.list", "r") as fp: 14 | source = fp.read().split(' ')[1] 15 | return ( 16 | "deb {source} karmic multiverse\n" 17 | "deb-src {source} karmic multiverse\n" 18 | "deb {source} karmic-updates multiverse\n" 19 | "deb-src {source} karmic-updates multiverse\n" 20 | "deb http://security.ubuntu.com/ubuntu karmic-security multiverse\n" 21 | ).format(source=source) 22 | File("/etc/apt/sources.list.d/multiverse.list", 23 | owner = "root", 24 | group = "root", 25 | mode = 0644, 26 | not_if = lambda:os.path.exists("/etc/apt/sources.list.d/multiverse.list"), 27 | content = enter_the_multiverse, 28 | notifies = [("run", env.resources["Execute"]["apt-update-java"], True)]) 29 | 30 | ubuntu_sources = ("lucid", "maverick") 31 | 32 | if env.system.lsb['codename'] in ubuntu_sources: 33 | Execute('add-apt-repository "deb http://archive.canonical.com/ %s partner" ; apt-get update' % env.system.lsb['codename'], 34 | not_if = "grep '%s partner' /etc/apt/sources.list > /dev/null" % env.system.lsb['codename']) 35 | 36 | Script("accept-java-license", 37 | not_if = "debconf-show sun-java6-jre | grep accepted > /dev/null", 38 | cwd = "/usr/local/src", 39 | code = """#!/bin/sh 40 | echo 'sun-java6-bin shared/accepted-sun-dlj-v1-1 boolean true 41 | sun-java6-jdk shared/accepted-sun-dlj-v1-1 boolean true 42 | sun-java6-jre shared/accepted-sun-dlj-v1-1 boolean true 43 | sun-java6-jre sun-java6-jre/stopthread boolean true 44 | sun-java6-jre sun-java6-jre/jcepolicy note 45 | sun-java6-bin shared/present-sun-dlj-v1-1 note 46 | sun-java6-jdk shared/present-sun-dlj-v1-1 note 47 | sun-java6-jre shared/present-sun-dlj-v1-1 note 48 | '|debconf-set-selections""") 49 | -------------------------------------------------------------------------------- /kokki/cookbooks/java/recipes/jre.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package 3 | 4 | env.include_recipe("java") 5 | 6 | Package("sun-java6-jre") 7 | -------------------------------------------------------------------------------- /kokki/cookbooks/jenkins/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Jenkins CI" 3 | __config__ = { 4 | "jenkins.http_port": dict( 5 | description = "HTTP port to listen on", 6 | default = 8080, 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /kokki/cookbooks/jenkins/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Execute, File, Package, Service, Template 3 | 4 | if env.system.platform in ("ubuntu", "debian"): 5 | Execute("wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add -", 6 | not_if = "(apt-key list | grep 'Kohsuke Kawaguchi' > /dev/null)") 7 | 8 | 9 | apt = "deb http://pkg.jenkins-ci.org/debian binary/" 10 | apt_list_path = '/etc/apt/sources.list.d/jenkins.list' 11 | 12 | Execute("apt-update-jenkins", 13 | command = "apt-get update", 14 | action = "nothing") 15 | 16 | File(apt_list_path, 17 | owner = "root", 18 | group ="root", 19 | mode = 0644, 20 | content = apt+"\n", 21 | notifies = [("run", env.resources["Execute"]["apt-update-jenkins"], True)]) 22 | 23 | Package("jenkins") 24 | 25 | Service("jenkins") 26 | 27 | File("/etc/default/jenkins", 28 | owner = "root", 29 | group = "root", 30 | mode = 0644, 31 | content = Template("jenkins/default.j2"), 32 | notifies = [("restart", env.resources["Service"]["jenkins"])]) 33 | -------------------------------------------------------------------------------- /kokki/cookbooks/jenkins/templates/default.j2: -------------------------------------------------------------------------------- 1 | # defaults for jenkins continuous integration server 2 | 3 | # pulled in from the init script; makes things easier. 4 | NAME=jenkins 5 | 6 | # location of java 7 | JAVA=/usr/bin/java 8 | 9 | # arguments to pass to java 10 | #JAVA_ARGS="-Xmx256m" 11 | 12 | PIDFILE=/var/run/jenkins/jenkins.pid 13 | 14 | # user id to be invoked as (otherwise will run as root; not wise!) 15 | JENKINS_USER=jenkins 16 | 17 | # location of the jenkins war file 18 | JENKINS_WAR=/usr/share/jenkins/jenkins.war 19 | 20 | # jenkins home location 21 | JENKINS_HOME=/var/lib/jenkins 22 | 23 | # set this to false if you don't want Hudson to run by itself 24 | # in this set up, you are expected to provide a servlet containr 25 | # to host jenkins. 26 | RUN_STANDALONE=true 27 | 28 | # log location. this may be a syslog facility.priority 29 | JENKINS_LOG=/var/log/jenkins/$NAME.log 30 | #HUDSON_LOG=daemon.info 31 | 32 | # OS LIMITS SETUP 33 | # comment this out to observe /etc/security/limits.conf 34 | # this is on by default because http://github.com/feniix/hudson/commit/d13c08ea8f5a3fa730ba174305e6429b74853927 35 | # reported that Ubuntu's PAM configuration doesn't include pam_limits.so, and as a result the # of file 36 | # descriptors are forced to 1024 regardless of /etc/security/limits.conf 37 | MAXOPENFILES=8192 38 | 39 | # port for HTTP connector (default 8080; disable with -1) 40 | HTTP_PORT={{ env.config.jenkins.http_port }} 41 | 42 | # port for AJP connector (disabled by default) 43 | AJP_PORT=-1 44 | 45 | # arguments to pass to jenkins. 46 | # --javahome=$JAVA_HOME 47 | # --httpPort=$HTTP_PORT (default 8080; disable with -1) 48 | # --httpsPort=$HTTP_PORT 49 | # --ajp13Port=$AJP_PORT 50 | # --argumentsRealm.passwd.$ADMIN_USER=[password] 51 | # --argumentsRealm.$ADMIN_USER=admin 52 | # --webroot=~/.jenkins/war 53 | JENKINS_ARGS="--webroot=/var/run/jenkins/war --httpPort=$HTTP_PORT --ajp13Port=$AJP_PORT" 54 | -------------------------------------------------------------------------------- /kokki/cookbooks/librato/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Librato Silverline process monitoring and management" 3 | __config__ = { 4 | "librato.api_token": dict( 5 | description = "API token for account", 6 | default = "0", 7 | ), 8 | "librato.server_id_cmd": dict( 9 | description = "Command to run to generate server ID", 10 | default = None, 11 | ), 12 | "librato.template_id": dict( 13 | description = "Template ID for this server", 14 | default = None, 15 | ), 16 | "librato.email_address": dict( 17 | description = "Email address of account", 18 | default = None, 19 | ), 20 | } 21 | -------------------------------------------------------------------------------- /kokki/cookbooks/librato/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Execute, File, Fail 3 | 4 | apt_list_path = '/etc/apt/sources.list.d/librato.list' 5 | apt = None 6 | if env.system.platform == "ubuntu": 7 | ver = env.system.lsb['release'] 8 | if ver == "10.04": 9 | apt = "deb http://apt.librato.com/ubuntu/ lucid non-free" 10 | elif ver in ("10.10", "11.04", "11.10"): 11 | apt = "deb http://apt.librato.com/ubuntu/ maverick non-free" 12 | elif env.system.platform == "debian": 13 | ver = env.system.lsb['release'] 14 | if ver == '5.0': 15 | apt = "deb http://apt.librato.com/debian/ lenny non-free" 16 | 17 | if not apt: 18 | raise Fail("Can't find a librato package for your platform/version") 19 | 20 | Execute("apt-update-librato", 21 | command = "apt-get update", 22 | action = "nothing") 23 | 24 | Execute("curl http://apt.librato.com/packages.librato.key | apt-key add -", 25 | not_if = "(apt-key list | grep Librato > /dev/null)") 26 | 27 | File(apt_list_path, 28 | owner = "root", 29 | group ="root", 30 | mode = 0644, 31 | content = apt+"\n", 32 | notifies = [("run", env.resources["Execute"]["apt-update-librato"], True)]) 33 | -------------------------------------------------------------------------------- /kokki/cookbooks/librato/recipes/silverline.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, Service, Execute, File, Template 3 | 4 | env.include_recipe("librato") 5 | 6 | Package("librato-silverline") 7 | 8 | Service("silverline", action="start") 9 | 10 | Execute("reload-silverline", 11 | command = "killall lmd", 12 | action = "nothing") 13 | 14 | File("/etc/load_manager/lmd.conf", 15 | owner = "root", 16 | group = "root", 17 | mode = 0600, 18 | content = Template("librato/lmd.conf.j2"), 19 | notifies = [("run", env.resources["Execute"]["reload-silverline"])]) 20 | 21 | File("/etc/load_manager/lmc.conf", 22 | owner = "root", 23 | group = "root", 24 | mode = 0600, 25 | content = Template("librato/lmc.conf.j2")) 26 | -------------------------------------------------------------------------------- /kokki/cookbooks/librato/templates/lmc.conf.j2: -------------------------------------------------------------------------------- 1 | ## 2 | ## lmc.conf - Silverline CLI (lmc) configuration file 3 | ## 4 | # 5 | 6 | # Email address used to identify your account when communicating with the Librato 7 | # Silverline Service. 8 | {% if env.config.librato.email_address %} 9 | SL_EMAIL_ADDRESS="{{ env.config.librato.email_address }}" 10 | {% endif %} -------------------------------------------------------------------------------- /kokki/cookbooks/limits/libraries/limit.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Environment 3 | 4 | def Limit(domain, type, item, value, action="include"): 5 | env = Environment().get_instance() 6 | 7 | for i, l in enumerate(env.config.limits): 8 | if l['domain'] == domain and l['type'] == type and l['item'] == item: 9 | del env.config.limits[i] 10 | break 11 | 12 | if action == "include": 13 | env.config.limits.append(dict(domain=domain, type=type, item=item, value=value)) 14 | -------------------------------------------------------------------------------- /kokki/cookbooks/limits/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Security limits" 3 | __config__ = { 4 | "limits": dict( 5 | description = "List of dictionaries with keys domain, type, item, and value.", 6 | default = [ 7 | dict(domain="root", type="soft", item="nofile", value="30000"), 8 | dict(domain="root", type="hard", item="nofile", value="30000"), 9 | ], 10 | ), 11 | } 12 | -------------------------------------------------------------------------------- /kokki/cookbooks/limits/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import File, Template 3 | 4 | File("/etc/security/limits.conf", 5 | owner = "root", 6 | group = "root", 7 | mode = 0644, 8 | content = Template("limits/limits.conf.j2")) 9 | -------------------------------------------------------------------------------- /kokki/cookbooks/limits/templates/limits.conf.j2: -------------------------------------------------------------------------------- 1 | # /etc/security/limits.conf 2 | # 3 | # Managed by Kokki. Do not edit. 4 | # 5 | #Each line describes a limit for a user in the form: 6 | # 7 | # 8 | # 9 | #Where: 10 | # can be: 11 | # - an user name 12 | # - a group name, with @group syntax 13 | # - the wildcard *, for default entry 14 | # - the wildcard %, can be also used with %group syntax, 15 | # for maxlogin limit 16 | # - NOTE: group and wildcard limits are not applied to root. 17 | # To apply a limit to the root user, must be 18 | # the literal username root. 19 | # 20 | # can have the two values: 21 | # - "soft" for enforcing the soft limits 22 | # - "hard" for enforcing hard limits 23 | # 24 | # can be one of the following: 25 | # - core - limits the core file size (KB) 26 | # - data - max data size (KB) 27 | # - fsize - maximum filesize (KB) 28 | # - memlock - max locked-in-memory address space (KB) 29 | # - nofile - max number of open files 30 | # - rss - max resident set size (KB) 31 | # - stack - max stack size (KB) 32 | # - cpu - max CPU time (MIN) 33 | # - nproc - max number of processes 34 | # - as - address space limit (KB) 35 | # - maxlogins - max number of logins for this user 36 | # - maxsyslogins - max number of logins on the system 37 | # - priority - the priority to run user process with 38 | # - locks - max number of file locks the user can hold 39 | # - sigpending - max number of pending signals 40 | # - msgqueue - max memory used by POSIX message queues (bytes) 41 | # - nice - max nice priority allowed to raise to values: [-20, 19] 42 | # - rtprio - max realtime priority 43 | # - chroot - change root to directory (Debian-specific) 44 | # 45 | # 46 | # 47 | 48 | {% for l in env.config.limits %}{{ l.domain }} {{ l.type }} {{ l.item }} {{ l.value }} 49 | {% endfor %} 50 | # End of file 51 | -------------------------------------------------------------------------------- /kokki/cookbooks/mdadm/libraries/providers.py: -------------------------------------------------------------------------------- 1 | 2 | import subprocess 3 | from kokki import Provider 4 | 5 | class ArrayProvider(Provider): 6 | def action_create(self): 7 | if not self.exists(): 8 | subprocess.check_call(["/sbin/mdadm", 9 | "--create", self.resource.name, 10 | "-R", 11 | "-c", str(self.resource.chunksize), 12 | "--level", str(self.resource.level), 13 | "--metadata", self.resource.metadata, 14 | "--raid-devices", str(len(self.resource.devices)), 15 | ] + self.resource.devices) 16 | self.resource.updated() 17 | 18 | def action_stop(self): 19 | if self.exists(): 20 | subprocess.check_call(["/sbin/mdadm", 21 | "--stop", self.resource.name]) 22 | self.resource.updated() 23 | 24 | def action_assemble(self): 25 | if not self.exists(): 26 | subprocess.check_call(["/sbin/mdadm", 27 | "--assemble", self.resource.name, 28 | ] + self.resource.devices) 29 | self.resource.updated() 30 | 31 | def exists(self): 32 | ret = subprocess.call(["/sbin/mdadm", "-Q", self.resource.name]) 33 | return not ret 34 | -------------------------------------------------------------------------------- /kokki/cookbooks/mdadm/libraries/resources.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Resource, ForcedListArgument, ResourceArgument 3 | 4 | class Array(Resource): 5 | provider = "*mdadm.ArrayProvider" 6 | 7 | actions = Resource.actions + ["create", "stop", "assemble"] 8 | 9 | action = ForcedListArgument(default="create") 10 | chunksize = ResourceArgument() 11 | level = ResourceArgument() 12 | metadata = ResourceArgument() 13 | devices = ForcedListArgument() 14 | 15 | def __init__(self, *args, **kwargs): 16 | super(Array, self).__init__(*args, **kwargs) 17 | self.subscribe("run", self.env.resources["Execute"]["mdadm-update-conf"], False) 18 | -------------------------------------------------------------------------------- /kokki/cookbooks/mdadm/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Software RAID for Linux" 3 | __config__ = { 4 | "mdadm.arrays": dict( 5 | description = "List of dictionary with keys name, devices, level, chunksize, and metadata", 6 | default = [], 7 | ), 8 | } 9 | -------------------------------------------------------------------------------- /kokki/cookbooks/mdadm/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, Execute, Mount 3 | 4 | if env.config.mdadm.arrays: 5 | Package("mdadm") 6 | 7 | Execute("mdadm-update-conf", 8 | action = "nothing", 9 | command = ("(" 10 | "echo DEVICE partitions > /etc/mdadm/mdadm.conf" 11 | "; mdadm --detail --scan >> /etc/mdadm/mdadm.conf" 12 | ")" 13 | )) 14 | 15 | for arr in env.config.mdadm.arrays: 16 | array = arr.copy() 17 | fstype = array.pop('fstype', None) 18 | fsoptions = array.pop('fsoptions', None) 19 | mount_point = array.pop('mount_point', None) 20 | 21 | env.cookbooks.mdadm.Array(**array) 22 | 23 | if fstype: 24 | if fstype == "xfs": 25 | Package("xfsprogs") 26 | Execute("mkfs.%(fstype)s -f %(device)s" % dict(fstype=fstype, device=array['name']), 27 | not_if = """if [ "`file -s %(device)s`" = "%(device)s: data" ]; then exit 1; fi""" % dict(device=array['name'])) 28 | 29 | if mount_point: 30 | Mount(mount_point, 31 | device = array['name'], 32 | fstype = fstype, 33 | options = fsoptions if fsoptions is not None else ["noatime"], 34 | action = ["mount", "enable"]) 35 | -------------------------------------------------------------------------------- /kokki/cookbooks/membase/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Database by NorthScale" 3 | __config__ = { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /kokki/cookbooks/membase/recipes/default.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuel/kokki/da98da55e0bba8db5bda993666a43c6fdc4cacdb/kokki/cookbooks/membase/recipes/default.py -------------------------------------------------------------------------------- /kokki/cookbooks/membase/recipes/moxi.py: -------------------------------------------------------------------------------- 1 | 2 | if env.system.platform in ("ubuntu", "debian"): 3 | if env.system.arch == "x86_64": 4 | deb_url = "http://c2493362.cdn.cloudfiles.rackspacecloud.com/moxi-server_x86_64_1.6.0.deb" 5 | elif env.system.arch == "x86_32": 6 | deb_url = "http://c2493362.cdn.cloudfiles.rackspacecloud.com/moxi-server_x86_1.6.0.deb" 7 | # TODO: Install deb 8 | elif env.system.platform in ("fedora", "redhat"): 9 | if env.system.arch == "x86_64": 10 | rpm_url = "http://c2493362.cdn.cloudfiles.rackspacecloud.com/moxi-server_x86_64_1.6.0.rpm" 11 | elif env.system.arch == "x86_32": 12 | rpm_url = "http://c2493362.cdn.cloudfiles.rackspacecloud.com/moxi-server_x86_1.6.0.rpm" 13 | # TODO: Install rpm 14 | -------------------------------------------------------------------------------- /kokki/cookbooks/membase/recipes/server.py: -------------------------------------------------------------------------------- 1 | 2 | if env.system.platform in ("ubuntu", "debian"): 3 | if env.system.arch == "x86_64": 4 | deb_url = "http://c2493362.cdn.cloudfiles.rackspacecloud.com/membase-server-community_x86_64_1.6.0.deb" 5 | elif env.system.arch == "x86_32": 6 | deb_url = "http://c2493362.cdn.cloudfiles.rackspacecloud.com/membase-server-community_x86_1.6.0.deb" 7 | # TODO: Install deb 8 | elif env.system.platform in ("fedora", "redhat"): 9 | if env.system.arch == "x86_64": 10 | rpm_url = "http://c2493362.cdn.cloudfiles.rackspacecloud.com/membase-server-community_x86_64_1.6.0.rpm" 11 | elif env.system.arch == "x86_32": 12 | rpm_url = "http://c2493362.cdn.cloudfiles.rackspacecloud.com/membase-server-community_x86_1.6.0.rpm" 13 | # TODO: Install rpm 14 | -------------------------------------------------------------------------------- /kokki/cookbooks/memcached/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Memcached shared memory cache daemon" 3 | __config__ = { 4 | "memcached.memory": dict( 5 | description = "Memory allocated for memcached instance in megabytes", 6 | default = 64, 7 | ), 8 | "memcached.listen_address": dict( 9 | description = "IP address to bind to", 10 | default = "127.0.0.1", 11 | ), 12 | "memcached.port": dict( 13 | description = "Port to use for memcached instance", 14 | default = 11211, 15 | ), 16 | "memcached.user": dict( 17 | description = "User as which to run the memcached instance", 18 | default = "nobody", 19 | ), 20 | "memcached.threads": dict( 21 | description = "Number of threads to use to process incoming requests", 22 | default = 1, 23 | ), 24 | "memcached.verbose": dict( 25 | description = "Verbose logging output", 26 | default = False, 27 | ), 28 | } 29 | -------------------------------------------------------------------------------- /kokki/cookbooks/memcached/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, Service, File, Template, Link 3 | 4 | Package("memcached", action="upgrade") 5 | Package("libmemcache-dev", action="upgrade") 6 | Service("memcached") 7 | 8 | File("/etc/memcached.conf", 9 | content = Template("memcached/memcached.conf.j2"), 10 | owner = "root", 11 | group = "root", 12 | mode = 0644, 13 | notifies = [("restart", env.resources["Service"]["memcached"], True)]) 14 | 15 | if "librato.silverline" in env.included_recipes: 16 | File("/etc/default/memcached", 17 | owner = "root", 18 | group = "root", 19 | mode = 0644, 20 | content = ( 21 | "ENABLE_MEMCACHED=yes\n" 22 | "export SL_NAME=memcached\n" 23 | ), 24 | notifies = [("restart", env.resources["Service"]["memcached"])]) 25 | 26 | if "munin.node" in env.included_recipes: 27 | for n in ('bytes', 'connections', 'curr_items', 'items', 'queries'): 28 | Link("/etc/munin/plugins/memcached_%s" % n, 29 | to = "/etc/munin/python-munin/plugins/memcached_%s" % n, 30 | notifies = [("restart", env.resources['Service']['munin-node'])]) 31 | -------------------------------------------------------------------------------- /kokki/cookbooks/memcached/templates/memcached.conf.j2: -------------------------------------------------------------------------------- 1 | 2 | # Run memcached as a daemon. This command is implied, and is not needed for the 3 | # daemon to run. See the README.Debian that comes with this package for more 4 | # information. 5 | -d 6 | 7 | # Log memcached's output to /var/log/memcached 8 | logfile /var/log/memcached.log 9 | 10 | # Be verbose 11 | {% if env.config.memcached.verbose %}-v{% endif %} 12 | 13 | # Be even more verbose (print client commands as well) 14 | # -vv 15 | 16 | # Start with a cap of 64 megs of memory. It's reasonable, and the daemon default 17 | # Note that the daemon will grow to this size, but does not start out holding this much 18 | # memory 19 | -m {{ env.config.memcached.memory }} 20 | 21 | # Default connection port is 11211 22 | -p {{ env.config.memcached.port }} 23 | 24 | # Run the daemon as root. The start-memcached will default to running as root if no 25 | # -u command is present in this config file 26 | -u {{ env.config.memcached.user }} 27 | 28 | # Specify which IP address to listen on. The default is to listen on all IP addresses 29 | # This parameter is one of the only security measures that memcached has, so make sure 30 | # it's listening on a firewalled interface. 31 | -l {{ env.config.memcached.listen_address }} 32 | 33 | -t {{ env.config.memcached.threads }} 34 | 35 | # Limit the number of simultaneous incoming connections. The daemon default is 1024 36 | # -c 1024 37 | 38 | # Lock down all paged memory. Consult with the README and homepage before you do this 39 | # -k 40 | 41 | # Return error when memory is exhausted (rather than removing items) 42 | # -M 43 | 44 | # Maximize core file limit 45 | # -r -------------------------------------------------------------------------------- /kokki/cookbooks/minecraft/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Minecraft game server" 3 | __config__ = { 4 | "minecraft.path": dict( 5 | description = "Path where to install Minecraft server", 6 | default = "/var/lib/minecraft", 7 | ), 8 | "minecraft.user": dict( 9 | description = "User to run Minecraft server as", 10 | default = "nobody", 11 | ), 12 | "minecraft.package_url": dict( 13 | description = "URL of the server jar file", 14 | default = "http://www.minecraft.net/download/minecraft_server.jar", #"?v=445", 15 | ), 16 | "minecraft.xms": dict( 17 | description = "Initial Java heap size", 18 | default = "1024M", 19 | ), 20 | "minecraft.xmx": dict( 21 | description = "Maximum Java heap size", 22 | default = "1024M", 23 | ), 24 | } 25 | -------------------------------------------------------------------------------- /kokki/cookbooks/minecraft/recipes/server.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from kokki import Package, Directory, Script, File, Service 4 | 5 | env.include_recipe("java.jre") 6 | 7 | Package("screen") 8 | 9 | Directory(env.config.minecraft.path, 10 | owner = env.config.minecraft.user) 11 | 12 | Script("install-minecraft-server", 13 | not_if = lambda:os.path.exists(os.path.join(env.config.minecraft.path, "minecraft_server.jar")), 14 | code = ( 15 | "cd {config.minecraft.path}\n" 16 | "wget {config.minecraft.package_url}\n" 17 | ).format(config=env.config) 18 | ) 19 | 20 | File("%s/server.sh", 21 | mode = 0755, 22 | content = ( 23 | "#!/bin/sh\n" 24 | "cd {0}\n" 25 | "java -Xmx1024M -Xms1024M -jar {0}/minecraft_server.jar nogui\n" 26 | ).format(env.config.minecraft.path), 27 | ) 28 | 29 | Service("minecraft-server", 30 | start_command = "screen -dmS minecraft -- %s/server.sh" % env.config.minecraft.path, 31 | stop_command = 'screen -S minecraft -X stuff "stop\n"', 32 | status_command = "nc -z localhost 25565", 33 | action = "start", 34 | ) 35 | -------------------------------------------------------------------------------- /kokki/cookbooks/mongodb/libraries/server.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from kokki import Environment, Directory, File, Service, Template 4 | 5 | def setup(name, **kwargs): 6 | env = Environment.get_instance() 7 | config = env.config.mongodb.copy() 8 | config.update(kwargs) 9 | 10 | config['configpath'] = "/etc/mongodb/%s.conf" % name 11 | if 'dbpath' not in kwargs: 12 | config['dbpath'] = os.path.join(config.dbpath, name) 13 | if 'logfilename' not in kwargs: 14 | config['logfilename'] = "%s.log" % name 15 | 16 | Directory("/etc/mongodb", 17 | owner = "root", 18 | group = "root", 19 | mode = 0755) 20 | 21 | Directory(config.dbpath, 22 | owner = "mongodb", 23 | group = "mongodb", 24 | mode = 0755, 25 | recursive = True) 26 | 27 | File(config.configpath, 28 | owner = "root", 29 | group = "root", 30 | mode = 0644, 31 | content = Template("mongodb/mongodb.conf.j2", variables=dict(mongodb=config))) 32 | # notifies = [("restart", env.resources["MonitService"]["mongodb-%s" % name])]) 33 | 34 | controller = kwargs.get("controller") 35 | if controller == "monit": 36 | env.include_recipe("monit") 37 | env.cookbooks.monit.rc("mongodb-%s" % name, 38 | Template("mongodb/monit.conf.j2", variables=dict(name=name, mongodb=config))) 39 | env.cookbooks.monit.MonitService("mongodb-%s" % name, 40 | subscribes = [("restart", env.resources["File"][config.configpath])]) 41 | elif controller == "supervisord": 42 | env.include_recipe("supervisor") 43 | env.cookbooks.supervisor.configuration("mongodb-%s" % name, 44 | Template("mongodb/supervisord.conf.j2", variables=dict(name=name, mongodb=config))) 45 | env.cookbooks.supervisor.SupervisorService("mongodb-%s" % name, 46 | subscribes = [("restart", env.resources["File"][config.configpath])]) 47 | else: 48 | Service("mongodb-%s" % name, 49 | subscribes = [("restart", env.resources["File"][config.configpath])]) 50 | File("/etc/init/mongodb-%s.conf" % name, 51 | owner = "root", 52 | group = "root", 53 | mode = 0644, 54 | content = Template("mongodb/upstart.conf.j2", variables=dict(mongodb=config)), 55 | notifies = [ 56 | ("reload", env.resources["Service"]["mongodb-%s" % name], True), 57 | ]) 58 | -------------------------------------------------------------------------------- /kokki/cookbooks/mongodb/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "MongoDB database" 3 | __config__ = { 4 | "mongodb.nodefault": dict( 5 | description = "Remove the default mongodb.conf and init script", 6 | default = False, 7 | ), 8 | "mongodb.configpath": dict( 9 | description = "Path to the MongoDB config file", 10 | default = "/etc/mongodb.conf", 11 | ), 12 | "mongodb.options": dict( 13 | description = "List of command line options (e.g. ['--configsvr'])", 14 | default = [], 15 | ), 16 | "mongodb.dbpath": dict( 17 | description = "Path where to store the MongoDB database", 18 | default = "/var/lib/mongodb", 19 | ), 20 | "mongodb.logpath": dict( 21 | description = "Path where to store the MongoDB log", 22 | default = "/var/log/mongodb", 23 | ), 24 | "mongodb.logfilename": dict( 25 | description = "Name of log file", 26 | default = "mongodb.log", 27 | ), 28 | "mongodb.port": dict( 29 | description = "Specifies the port number on which Mongo will listen for client connections.", 30 | default = None, 31 | ), 32 | "mongodb.verbose": dict( 33 | description = "Verbose logging output", 34 | default = False, 35 | ), 36 | "mongodb.rest": dict( 37 | description = "Allow extended operations at the HTTP Interface", 38 | default = False, 39 | ), 40 | "mongodb.oplog_size": dict( 41 | description = "Custom size for replication operation log.", 42 | default = None, 43 | ), 44 | "mongodb.op_id_mem": dict( 45 | description = "Size limit for in-memory storage of op ids.", 46 | default = None, 47 | ), 48 | "mongodb.replica_set": dict( 49 | description = "[/] Use replica sets with the specified logical set name. Typically the optional seed host list need not be specified.", 50 | default = None, 51 | ), 52 | "mongodb.limit_nofile": dict( 53 | description = "Open file limit set in upstart config", 54 | default = 32000, 55 | ), 56 | } 57 | -------------------------------------------------------------------------------- /kokki/cookbooks/mongodb/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import File, Template, Execute, Directory, Service, Fail, Package 3 | 4 | apt_list_path = '/etc/apt/sources.list.d/mongodb.list' 5 | apt = None 6 | if env.system.platform == "ubuntu": 7 | ver = env.system.lsb['release'] 8 | if ver in ('10.10', '10.04', '9.10', '9.04'): 9 | ver = ver.replace(".0", ".") 10 | apt = 'deb http://downloads.mongodb.org/distros/ubuntu %s 10gen' % ver 11 | elif env.system.platform == "debian": 12 | ver = env.system.lsb['release'] 13 | if ver == '5.0': 14 | apt = 'deb http://downloads.mongodb.org/distros/debian 5.0 10gen' 15 | 16 | if not apt: 17 | raise Fail("Can't find a mongodb package for your platform/version") 18 | 19 | Execute("apt-update-mongo", 20 | command = "apt-get update", 21 | action = "nothing") 22 | 23 | Execute("apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10", 24 | not_if = "(apt-key list | grep 10gen.com > /dev/null)") 25 | 26 | File(apt_list_path, 27 | owner = "root", 28 | group ="root", 29 | mode = 0644, 30 | content = apt+"\n", 31 | notifies = [("run", env.resources["Execute"]["apt-update-mongo"], True)]) 32 | 33 | ### 34 | 35 | Package("mongodb-stable") 36 | 37 | if env.config.mongodb.nodefault: 38 | Service("mongodb") 39 | File(env.config.mongodb.configpath, 40 | action = "delete", 41 | notifies = [("stop", env.resources["Service"]["mongodb"], True)]) 42 | File("/etc/init/mongodb.conf", action="delete") 43 | File("/etc/init.d/mongodb", action="delete") 44 | else: 45 | Directory(env.config.mongodb.dbpath, 46 | owner = "mongodb", 47 | group = "mongodb", 48 | mode = 0755, 49 | recursive = True) 50 | 51 | Service("mongodb") 52 | 53 | File("/etc/init/mongodb.conf", 54 | owner = "root", 55 | group = "root", 56 | mode = 0644, 57 | content = Template("mongodb/upstart.conf.j2", variables=dict(mongodb=env.config.mongodb)), 58 | notifies = [ 59 | ("restart", env.resources["Service"]["mongodb"], True), 60 | ]) 61 | 62 | File(env.config.mongodb.configpath, 63 | owner = "root", 64 | group = "root", 65 | mode = 0644, 66 | content = Template("mongodb/mongodb.conf.j2", variables=dict(mongodb=env.config.mongodb)), 67 | notifies = [("restart", env.resources["Service"]["mongodb"])]) 68 | -------------------------------------------------------------------------------- /kokki/cookbooks/mongodb/templates/mongodb.conf.j2: -------------------------------------------------------------------------------- 1 | # mongodb.conf 2 | # 3 | # Generated by Kokki 4 | # 5 | 6 | # Where to store the data. 7 | 8 | # Note: if you run mongodb as a non-root user (recommended) you may 9 | # need to create and set permissions for this directory manually, 10 | # e.g., if the parent directory isn't mutable by the mongodb user. 11 | {% if not mongodb.mongos %} 12 | dbpath={{ mongodb.dbpath }} 13 | {% endif %} 14 | 15 | logpath={{ mongodb.logpath }}/{{ mongodb.logfilename }} 16 | 17 | logappend=true 18 | 19 | {% if mongodb.port %} 20 | port = {{ mongodb.port }} 21 | {% endif %} 22 | 23 | {% if not mongodb.mongos %} 24 | rest = {% if mongodb.rest %}true{% else %}false{% endif %} 25 | {% endif %} 26 | 27 | # Enables periodic logging of CPU utilization and I/O wait 28 | #cpu = true 29 | 30 | # Turn on/off security. Off is currently the default 31 | #noauth = true 32 | #auth = true 33 | 34 | # Verbose logging output. 35 | {% if mongodb.verbose %} 36 | verbose = true 37 | {% endif %} 38 | 39 | # Inspect all client data for validity on receipt (useful for 40 | # developing drivers) 41 | #objcheck = true 42 | 43 | # Enable db quota management 44 | #quota = true 45 | 46 | # Set oplogging level where n is 47 | # 0=off (default) 48 | # 1=W 49 | # 2=R 50 | # 3=both 51 | # 7=W+some reads 52 | #oplog = 0 53 | 54 | # Diagnostic/debugging option 55 | #nocursors = true 56 | 57 | # Ignore query hints 58 | #nohints = true 59 | 60 | # Disable the HTTP interface (Defaults to localhost:27018). 61 | #nohttpinterface = true 62 | 63 | # Turns off server-side scripting. This will result in greatly limited 64 | # functionality 65 | #noscripting = true 66 | 67 | # Turns off table scans. Any query that would do a table scan fails. 68 | #notablescan = true 69 | 70 | # Disable data file preallocation. 71 | #noprealloc = true 72 | 73 | # Specify .ns file size for new databases. 74 | # nssize = 75 | 76 | # Accout token for Mongo monitoring server. 77 | #mms-token = 78 | 79 | # Server name for Mongo monitoring server. 80 | #mms-name = 81 | 82 | # Ping interval for Mongo monitoring server. 83 | #mms-interval = 84 | 85 | # Replication Options 86 | 87 | # in replicated mongo databases, specify here whether this is a slave or master 88 | #slave = true 89 | #source = master.example.com 90 | # Slave only: specify a single database to replicate 91 | #only = master.example.com 92 | # or 93 | #master = true 94 | #source = slave.example.com 95 | 96 | # Address of a server to pair with. 97 | #pairwith = 98 | # Address of arbiter server. 99 | #arbiter = 100 | # Automatically resync if slave data is stale 101 | #autoresync 102 | 103 | {% if not mongodb.mongos %} 104 | {% if mongodb.oplog_size %}oplogSize = {{ mongodb.oplog_size }}{% endif %} 105 | {% if mongodb.op_id_mem %}opIdMem = {{ mongodb.op_id_mem }}{% endif %} 106 | {% if mongodb.replica_set %}replSet = {{ mongodb.replica_set }}{% endif %} 107 | {% endif %} -------------------------------------------------------------------------------- /kokki/cookbooks/mongodb/templates/monit.conf.j2: -------------------------------------------------------------------------------- 1 | 2 | check process mongodb_{{ name }} with pidfile {{ mongodb.dbpath }}/mongod.lock 3 | start program = "/sbin/start-stop-daemon -S -p {{ mongodb.dbpath }}/mongod.lock -c mongodb -a {% if mongodb.mongos %}/usr/bin/mongos{% else %}/usr/bin/mongod{% endif %} -- --config {{ mongodb.configpath }} --fork {{ " ".join(mongodb.options) }}" 4 | stop program = "/sbin/start-stop-daemon -K -p {{ mongodb.dbpath }}/mongod.lock" 5 | if 5 restarts within 5 cycles then timeout 6 | -------------------------------------------------------------------------------- /kokki/cookbooks/mongodb/templates/supervisord.conf.j2: -------------------------------------------------------------------------------- 1 | [program:mongodb_{{ name }}] 2 | command=/usr/bin/mongod --config {{ mongodb.configpath }} {{ " ".join(mongodb.options) }} 3 | # autostart=true 4 | stopwaitsecs=300 5 | -------------------------------------------------------------------------------- /kokki/cookbooks/mongodb/templates/upstart.conf.j2: -------------------------------------------------------------------------------- 1 | # Ubuntu upstart file at /etc/init/mongodb.conf 2 | 3 | pre-start script 4 | mkdir -p {{ mongodb.logpath }} 5 | {% if not mongodb.mongos %} 6 | mkdir -p {{ mongodb.dbpath }} 7 | chown -R mongodb.mongodb {{ mongodb.dbpath }} 8 | {% endif %} 9 | chown mongodb.mongodb {{ mongodb.dbpath }} 10 | end script 11 | 12 | limit nofile {{ mongodb.limit_nofile }} {{ mongodb.limit_nofile }} 13 | start on runlevel [2345] 14 | stop on runlevel [06] 15 | 16 | script 17 | ENABLE_MONGODB="yes" 18 | {% if "librato.silverline" in env.included_recipes %} 19 | export SL_NAME=mongodb 20 | {% endif %} 21 | if [ -f /etc/default/mongodb ]; then . /etc/default/mongodb; fi 22 | if [ "x$ENABLE_MONGODB" = "xyes" ]; then 23 | exec start-stop-daemon --start --quiet --chuid mongodb --exec {% if mongodb.mongos %}/usr/bin/mongos{% else %}/usr/bin/mongod{% endif %} -- --config {{ mongodb.configpath }} {{ " ".join(mongodb.options) }} 24 | fi 25 | end script 26 | -------------------------------------------------------------------------------- /kokki/cookbooks/monit/libraries/providers.py: -------------------------------------------------------------------------------- 1 | 2 | import subprocess 3 | from kokki import Fail 4 | from kokki.providers.service import ServiceProvider 5 | 6 | class MonitServiceProvider(ServiceProvider): 7 | def action_restart(self): 8 | self._init_cmd("restart", 0) 9 | self.resource.updated() 10 | 11 | def status(self): 12 | p = subprocess.Popen(["/usr/sbin/monit", "summary"], 13 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 14 | out = p.communicate()[0] 15 | for l in out.split('\n'): 16 | try: 17 | typ, name, status = l.strip().split(' ', 2) 18 | except ValueError: 19 | continue 20 | if typ.strip() == 'Process' and name.strip() == "'%s'" % self.resource.service_name: 21 | return status.strip() == "running" 22 | raise Fail("Service %s not managed by monit" % self.resource.service_name) 23 | 24 | def _init_cmd(self, command, expect=None): 25 | ret = subprocess.call(["/usr/sbin/monit", command, self.resource.service_name], 26 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 27 | if expect is not None and expect != ret: 28 | raise Fail("%r command %s for service %s failed" % (self, command, self.resource.service_name)) 29 | return ret 30 | -------------------------------------------------------------------------------- /kokki/cookbooks/monit/libraries/rc.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Environment, File 3 | 4 | def rc(name, content): 5 | env = Environment.get_instance() 6 | return File("monitrc-%s" % name, 7 | content = content, 8 | owner = "root", 9 | group = "root", 10 | mode = 0644, 11 | path = "%s/monit.d/%s" % (env.config.monit.config_path, name), 12 | notifies = [("restart", env.resources["Service"]["monit"])]) 13 | -------------------------------------------------------------------------------- /kokki/cookbooks/monit/libraries/resources.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Service, BooleanArgument 3 | 4 | class MonitService(Service): 5 | provider = "*monit.MonitServiceProvider" 6 | 7 | supports_restart = BooleanArgument(default=True) 8 | supports_status = BooleanArgument(default=True) 9 | supports_reload = BooleanArgument(default=False) 10 | -------------------------------------------------------------------------------- /kokki/cookbooks/monit/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Process monitoring" 3 | __config__ = { 4 | "monit.alert_emails": dict( 5 | display_name = "Alert emails", 6 | description = "Emails that should receive alerts about service changes.", 7 | default = [], 8 | ), 9 | "monit.password": dict( 10 | dispaly_name = "Password", 11 | description = "Password for accessing web interface", 12 | default = "m0n1t1tup", 13 | ), 14 | 'monit.config_path': dict( 15 | description = "Path to config files", 16 | default = "/etc/monit", 17 | ), 18 | } 19 | -------------------------------------------------------------------------------- /kokki/cookbooks/monit/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, File, Template, Directory, Service 3 | 4 | Package("monit") 5 | 6 | File("%s/monitrc" % env.config.monit.config_path, 7 | owner = "root", 8 | group = "root", 9 | mode = 0700, 10 | content = Template("monit/monitrc.j2")) 11 | 12 | if env.system.platform == "ubuntu": 13 | File("/etc/default/monit", 14 | content = Template("monit/default.j2")) 15 | 16 | Directory("%s/monit.d" % env.config.monit.config_path, 17 | owner = "root", 18 | group = "root", 19 | mode = 0700) 20 | 21 | Directory("/var/monit", 22 | owner = "root", 23 | group = "root", 24 | mode = 0700) 25 | 26 | Service("monit", 27 | supports_restart = True, 28 | supports_status = False, 29 | subscribes = [('restart', env.resources['File']["%s/monitrc" % env.config.monit.config_path])]) 30 | -------------------------------------------------------------------------------- /kokki/cookbooks/monit/templates/default.j2: -------------------------------------------------------------------------------- 1 | # Defaults for monit initscript 2 | # sourced by /etc/init.d/monit 3 | # installed at /etc/default/monit by maintainer scripts 4 | # Fredrik Steen 5 | 6 | # You must set this variable to for monit to start 7 | startup=1 8 | 9 | # To change the intervals which monit should run uncomment 10 | # and change this variable. 11 | # CHECK_INTERVALS=180 12 | -------------------------------------------------------------------------------- /kokki/cookbooks/monit/templates/monitrc.j2: -------------------------------------------------------------------------------- 1 | set daemon 20 2 | set logfile syslog facility log_daemon 3 | 4 | {% if env.config.mailserver and env.config.mailserver != "localhost" %} 5 | set mailserver {{ env.config.mailserver }}, localhost 6 | {% else %} 7 | set mailserver localhost 8 | {% endif %} 9 | 10 | set eventqueue 11 | basedir /var/monit # set the base directory where events will be stored 12 | slots 100 # optionaly limit the queue size 13 | {% if env.config.monit.mail_format %} 14 | set mail-format { from: {{ env.config.monit.mail_format }} } 15 | {% endif %} 16 | 17 | {% for email in env.config.monit.alert_emails %} 18 | set alert {{ email }} 19 | {% endfor %} 20 | 21 | set httpd port 2828 and 22 | use address localhost 23 | allow localhost 24 | allow admin:{{ env.config.monit.password }} 25 | 26 | include {{ env.config.monit.config_path }}/monit.d/* 27 | -------------------------------------------------------------------------------- /kokki/cookbooks/munin/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Munin and munin-node server monitoring" 3 | __config__ = { 4 | "munin.bind": dict( 5 | description = "IP address munin-node should bind to", 6 | default = "127.0.0.1", 7 | ), 8 | "munin.port": dict( 9 | description = "Port number munin-node should listen on", 10 | default = 4949, 11 | ), 12 | "munin.allow": dict( 13 | description = "IP address ranges that are allowed to connect to munin-node", 14 | default = ["127.0.0.1/32"], 15 | ), 16 | "munin.contacts": dict( 17 | description = "Who to contact on alerts. List of dictionaries with keys name, email, and subject(optional).", 18 | default = [], # dict(name='', subject='optional', email='') 19 | ), 20 | "munin.contact_commands": dict( 21 | description = "Dictionary of commands to execute on alerts", 22 | default = {}, 23 | ), 24 | "munin.hosts": dict( 25 | description = "List of hosts to monitor. List of dictionaries with keys name and ip.", 26 | default = [dict(name="localhost", ip="127.0.0.1")], 27 | ), 28 | "munin.dbdir": dict( 29 | description = "Path to directory where rrd files are kept", 30 | default = None, # Usually /var/lib/munin 31 | ), 32 | } 33 | -------------------------------------------------------------------------------- /kokki/cookbooks/munin/recipes/master.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, Directory, File, Template 3 | 4 | Package("munin") 5 | 6 | Directory(env.config.munin.dbdir, 7 | owner = "munin", 8 | group = "munin", 9 | mode = 0755) 10 | 11 | File("/etc/munin/munin.conf", 12 | owner = "root", 13 | group = "root", 14 | mode = 0644, 15 | content = Template("munin/munin.conf.j2")) 16 | -------------------------------------------------------------------------------- /kokki/cookbooks/munin/recipes/node.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, File, Template, Service 3 | 4 | if env.system.platform == "amazon": 5 | Package("perl-NetAddr-IP") 6 | 7 | Package("munin-node") 8 | 9 | File("munin-node.conf", 10 | path = "/etc/munin/munin-node.conf", 11 | owner = "root", 12 | group = "root", 13 | mode = 0644, 14 | content = Template("munin/munin-node.conf.j2")) 15 | 16 | Service("munin-node", 17 | subscribes = [("restart", env.resources["File"]["munin-node.conf"])]) 18 | 19 | File("/etc/munin/plugin-conf.d/python", 20 | owner = "root", 21 | group = "root", 22 | mode = 0644, 23 | content = ( 24 | "[*]\n" 25 | "env.PYTHON_EGG_CACHE /tmp/munin-egg-cache\n" 26 | ), 27 | notifies = [("restart", env.resources["Service"]["munin-node"])]) 28 | 29 | if env.system.ec2: 30 | File("/etc/munin/plugins/if_err_eth0", 31 | action = "delete", 32 | notifies = [("restart", env.resources["Service"]["munin-node"])]) 33 | -------------------------------------------------------------------------------- /kokki/cookbooks/munin/templates/munin-node.conf.j2: -------------------------------------------------------------------------------- 1 | log_level 4 2 | log_file /var/log/munin/munin-node.log 3 | pid_file /var/run/munin/munin-node.pid 4 | 5 | background 1 6 | setseid 1 7 | 8 | user root 9 | group root 10 | setsid yes 11 | 12 | # Regexps for files to ignore 13 | 14 | ignore_file ~$ 15 | ignore_file \.bak$ 16 | ignore_file %$ 17 | ignore_file \.dpkg-(tmp|new|old|dist)$ 18 | ignore_file \.rpm(save|new)$ 19 | ignore_file \.pod$ 20 | 21 | # Set this if the client doesn't report the correct hostname when 22 | # telnetting to localhost, port 4949 23 | # 24 | #host_name localhost.localdomain 25 | 26 | # A list of addresses that are allowed to connect. This must be a 27 | # regular expression, due to brain damage in Net::Server, which 28 | # doesn't understand CIDR-style network notation. You may repeat 29 | # the allow line as many times as you'd like 30 | 31 | {% for cidr in env.config.munin.allow %} 32 | cidr_allow {{ cidr }} 33 | {% endfor %} 34 | 35 | # Which address to bind to; 36 | host {{ env.config.munin.bind }} 37 | 38 | # And which port 39 | port {{ env.config.munin.port }} 40 | -------------------------------------------------------------------------------- /kokki/cookbooks/mysql/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __config__ = { 3 | "mysql.server_root_password": dict( 4 | default = "changeme", 5 | ), 6 | "mysql.server_repl_password": dict( 7 | default = None, 8 | ), 9 | "mysql.server_debian_password": dict( 10 | default = "changeme", 11 | ), 12 | "mysql.grants": dict( 13 | default = [ 14 | # dict(user, host, database, password, permissions) 15 | ], 16 | ), 17 | "mysql.datadir": dict( 18 | description = "Location of MySQL database", 19 | default = "/var/lib/mysql", 20 | ), 21 | "mysql.bind_address": dict( 22 | description = "Address that MySQLd should listen on", 23 | default = "127.0.0.1", 24 | ), 25 | "mysql.ft_min_word_len": dict( 26 | description = "Minimum word length for items in the full-text index", 27 | default = None, 28 | ), 29 | "mysql.tunable.key_buffer": dict( 30 | default = "250M", 31 | ), 32 | "mysql.tunable.max_connections": dict( 33 | default = 800, 34 | ), 35 | "mysql.tunable.wait_timeout": dict( 36 | default = 180, 37 | ), 38 | "mysql.tunable.net_read_timeout": dict( 39 | default = 30, 40 | ), 41 | "mysql.tunable.net_write_timeout": dict( 42 | default = 30, 43 | ), 44 | "mysql.tunable.back_log": dict( 45 | default = 128, 46 | ), 47 | "mysql.tunable.table_cache": dict( 48 | default = 128, 49 | ), 50 | "mysql.tunable.max_heap_table_size": dict( 51 | default = "32M", 52 | ), 53 | "mysql.tunable.thread_stack": dict( 54 | default = "128K" 55 | ), 56 | # Replication 57 | "mysql.server_id": dict( 58 | default = None, 59 | ), 60 | "mysql.log_bin": dict( 61 | default = None, # /var/log/mysql/mysql-bin.log 62 | ), 63 | "mysql.expire_logs_days": dict( 64 | default = 10, 65 | ), 66 | "mysql.max_binlog_size": dict( 67 | default = "100M", 68 | ), 69 | } -------------------------------------------------------------------------------- /kokki/cookbooks/mysql/recipes/client.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package 3 | 4 | Package("libmysqlclient-dev") 5 | Package("mysql-client") 6 | -------------------------------------------------------------------------------- /kokki/cookbooks/mysql/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | env.include_recipe("mysql.client") 3 | -------------------------------------------------------------------------------- /kokki/cookbooks/mysql/recipes/php5.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package 3 | 4 | Package("php5-mysql") 5 | -------------------------------------------------------------------------------- /kokki/cookbooks/mysql/recipes/python.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package 3 | 4 | Package("python-mysqldb") 5 | -------------------------------------------------------------------------------- /kokki/cookbooks/mysql/recipes/server.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Directory, Execute, File, Template, Package, Service 3 | 4 | env.include_recipe("mysql.client") 5 | 6 | if env.system.platform in ("debian", "ubuntu"): 7 | Directory("/var/cache/local/preseeding", 8 | owner = "root", 9 | group = "root", 10 | mode = 0755, 11 | recursive = True) 12 | 13 | Execute("preseed mysql-server", 14 | command = "debconf-set-selections /var/cache/local/preseeding/mysql-server.seed", 15 | action = "nothing") 16 | 17 | File("/var/cache/local/preseeding/mysql-server.seed", 18 | owner = "root", 19 | group = "root", 20 | mode = 0600, 21 | content = Template("mysql/mysql-server.seed.j2"), 22 | notifies = [("run", env.resources["Execute"]["preseed mysql-server"], True)]) 23 | 24 | File("/etc/mysql/debian.cnf", 25 | owner = "root", 26 | group = "root", 27 | mode = 0600, 28 | content = Template("mysql/debian.cnf.j2")) 29 | 30 | Package("mysql-server") 31 | Service("mysql", 32 | supports_status = True, 33 | supports_restart = True) 34 | 35 | Execute("mysql_install_db --user=mysql --datadir=%s" % env.config.mysql.datadir, 36 | creates = env.config.mysql.datadir) 37 | 38 | File("/etc/mysql/conf.d/kokki.cnf", 39 | owner = "root", 40 | group = "root", 41 | mode = 0644, 42 | content = Template("mysql/kokki.cnf.j2"), 43 | notifies = [("restart", env.resources["Service"]["mysql"], True)]) 44 | 45 | File("/etc/mysql/grants.sql", 46 | owner = "root", 47 | group = "root", 48 | mode = 0600, 49 | content = Template("mysql/grants.sql.j2")) 50 | 51 | Execute("/usr/bin/mysql -u root --password='%s' < /etc/mysql/grants.sql" % env.config.mysql.server_root_password, 52 | action = "nothing", 53 | subscribes = [("run", env.resources["File"]["/etc/mysql/grants.sql"], True)]) 54 | -------------------------------------------------------------------------------- /kokki/cookbooks/mysql/templates/debian.cnf.j2: -------------------------------------------------------------------------------- 1 | 2 | [client] 3 | host = localhost 4 | user = debian-sys-maint 5 | password = {{ env.config.mysql.server_debian_password }} 6 | socket = /var/run/mysqld/mysqld.sock 7 | 8 | [mysql_upgrade] 9 | host = localhost 10 | user = debian-sys-maint 11 | password = {{ env.config.mysql.server_debian_password }} 12 | socket = /var/run/mysqld/mysqld.sock 13 | basedir = /usr 14 | -------------------------------------------------------------------------------- /kokki/cookbooks/mysql/templates/grants.sql.j2: -------------------------------------------------------------------------------- 1 | 2 | {% if env.system.platform in ("debian", "ubuntu") %} 3 | GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, SHUTDOWN, PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW DATABASES, SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'debian-sys-maint'@'localhost' IDENTIFIED BY '{{ env.config.mysql.server_debian_password }}' WITH GRANT OPTION; 4 | {% endif %} 5 | 6 | # Grant replication for a slave user. 7 | {% if env.config.mysql.server_repl_password %} 8 | GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%' identified by '{{ env.config.mysql.server_repl_password }}'; 9 | {% endif %} 10 | 11 | # Set the server root password. 12 | # SET PASSWORD FOR 'root'@'%' = PASSWORD('{{ env.config.mysql.server_root_password }}'); 13 | 14 | {% for g in env.config.mysql.grants %} 15 | GRANT {{ ", ".join(g.permissions) }} ON {{ g.database }}.* TO '{{ g.user }}'@'{{ g.host }}' IDENTIFIED BY '{{ g.password }}'; 16 | {% endfor %} 17 | -------------------------------------------------------------------------------- /kokki/cookbooks/mysql/templates/kokki.cnf.j2: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | {% for name in ("datadir", "port", "bind-address", "expire_logs_days", "max_binlog_size", "log_bin", "server-id", "ft_min_word_len") %} 3 | {% if env.config.mysql.get(name.replace('-', '_')) %}{{ name }} = {{ env.config.mysql[name.replace('-', '_')] }}{% endif %}{% endfor %} 4 | 5 | {% for name in ("tunable.key_buffer", "tunable.thread_stack", 6 | "tunable.max_connections", "tunable.wait_timeout", 7 | "tunable.net_read_timeout", "tunable.net_write_timeout", 8 | "tunable.back_log", "tunable.table_cache", 9 | "tunable.max_heap_table_size") %} 10 | {% if env.config.mysql.tunable.get(name) %}{{ name }} = {{ env.config.mysql.tunable[name] }}{% endif %}{% endfor %} 11 | 12 | 13 | -------------------------------------------------------------------------------- /kokki/cookbooks/mysql/templates/mysql-server.seed.j2: -------------------------------------------------------------------------------- 1 | mysql-server-5.1 mysql-server/root_password_again select {{ env.config.mysql.server_root_password }} 2 | mysql-server-5.1 mysql-server/root_password select {{ env.config.mysql.server_root_password }} 3 | mysql-server-5.1 mysql-server-5.1/really_downgrade boolean false 4 | mysql-server-5.1 mysql-server-5.1/need_sarge_compat boolean false 5 | mysql-server-5.1 mysql-server-5.1/start_on_boot boolean true 6 | mysql-server-5.1 mysql-server/error_setting_password boolean false 7 | mysql-server-5.1 mysql-server-5.1/nis_warning note 8 | mysql-server-5.1 mysql-server-5.1/postrm_remove_databases boolean false 9 | mysql-server-5.1 mysql-server/password_mismatch boolean false 10 | mysql-server-5.1 mysql-server-5.1/need_sarge_compat_done boolean true 11 | mysql-server-5.0 mysql-server/root_password_again select {{ env.config.mysql.server_root_password }} 12 | mysql-server-5.0 mysql-server/root_password select {{ env.config.mysql.server_root_password }} 13 | mysql-server-5.0 mysql-server-5.0/really_downgrade boolean false 14 | mysql-server-5.0 mysql-server-5.0/need_sarge_compat boolean false 15 | mysql-server-5.0 mysql-server-5.0/start_on_boot boolean true 16 | mysql-server-5.0 mysql-server/error_setting_password boolean false 17 | mysql-server-5.0 mysql-server-5.0/nis_warning note 18 | mysql-server-5.0 mysql-server-5.0/postrm_remove_databases boolean false 19 | mysql-server-5.0 mysql-server/password_mismatch boolean false 20 | mysql-server-5.0 mysql-server-5.0/need_sarge_compat_done boolean true 21 | -------------------------------------------------------------------------------- /kokki/cookbooks/nagios3/libraries/contact.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Environment 3 | 4 | def Contact(name, 5 | alias, 6 | service_notification_commands = "notify-service-by-email", 7 | host_notification_commands = "notify-host-by-email", 8 | service_notification_period = "24x7", 9 | host_notification_period = "24x7", 10 | service_notification_options = "w,u,c,r", 11 | host_notification_options = "d,r", 12 | email = None, 13 | groups = [], 14 | **kwargs): 15 | env = Environment.get_instance() 16 | 17 | for k in ('service_notification_commands', 18 | 'host_notification_commands', 19 | 'service_notification_period', 20 | 'host_notification_period', 21 | 'service_notification_options', 22 | 'host_notification_options'): 23 | kwargs[k] = locals()[k] 24 | 25 | env.config.nagios3.contacts[name] = kwargs 26 | for g in groups: 27 | env.config.nagios3.contactgroups[g].append(name) 28 | -------------------------------------------------------------------------------- /kokki/cookbooks/nagios3/libraries/host.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Environment 3 | 4 | def Host(name, 5 | alias = None, 6 | address = None, 7 | use = "generic-host", 8 | groups = [], 9 | action = "create", 10 | **kwargs): 11 | env = Environment.get_instance() 12 | 13 | kwargs['name'] = name 14 | kwargs['alias'] = alias or name 15 | kwargs['address'] = address or name 16 | kwargs['use'] = use 17 | kwargs['services'] = {} 18 | kwargs['groups'] = groups 19 | 20 | if action == "delete": 21 | host = env.config.nagios3.hosts.pop(name, None) 22 | if host: 23 | for g in host.get('groups', []): 24 | env.config.nagios3.hostgroups[g]['members'].remove(name) 25 | else: 26 | env.config.nagios3.hosts[name] = kwargs 27 | for g in groups: 28 | env.config.nagios3.hostgroups[g]['members'].append(name) 29 | -------------------------------------------------------------------------------- /kokki/cookbooks/nagios3/libraries/service.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Environment, File, Template 3 | 4 | def Service(service_description, host_name=None, hostgroup_name=None, check_command=None, use="generic-service", notification_interval=0, action="create"): 5 | env = Environment.get_instance() 6 | 7 | values = dict( 8 | host_name = host_name, 9 | hostgroup_name = hostgroup_name, 10 | service_description = service_description, 11 | check_command = check_command, 12 | use = use, 13 | notification_interval = notification_interval, 14 | ) 15 | 16 | if host_name: 17 | env.config.nagios3.hosts[host_name]["services"][service_description] = values 18 | return 19 | 20 | File("/etc/nagios3/conf.d/service_%s.cfg" % service_description.lower(), 21 | content = Template("nagios3/service.cfg.j2", values), 22 | action = action, 23 | notifies = [("restart", env.resources["Service"]["nagios3"])]) 24 | -------------------------------------------------------------------------------- /kokki/cookbooks/nagios3/recipes/nrpe.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package 3 | 4 | Package("nagios-nrpe-server") 5 | Package("nagios-plugins") 6 | # Package("nagios-plugins-basic") 7 | -------------------------------------------------------------------------------- /kokki/cookbooks/nagios3/templates/apache2-site.j2: -------------------------------------------------------------------------------- 1 | 2 | ScriptAlias /cgi-bin/nagios3 /usr/lib/cgi-bin/nagios3 3 | ScriptAlias /nagios3/cgi-bin /usr/lib/cgi-bin/nagios3 4 | 5 | # Where the stylesheets (config files) reside 6 | Alias /nagios3/stylesheets /etc/nagios3/stylesheets 7 | 8 | # Where the HTML pages live 9 | Alias /nagios3 /usr/share/nagios3/htdocs 10 | 11 | 12 | Options FollowSymLinks 13 | 14 | DirectoryIndex index.html 15 | 16 | AllowOverride AuthConfig 17 | Order Allow,Deny 18 | Allow From All 19 | 20 | AuthName "Nagios Access" 21 | AuthType Basic 22 | AuthUserFile /etc/nagios3/htpasswd.users 23 | require valid-user 24 | 25 | 26 | # Enable this ScriptAlias if you want to enable the grouplist patch. 27 | # See http://apan.sourceforge.net/download.html for more info 28 | # It allows you to see a clickable list of all hostgroups in the 29 | # left pane of the Nagios web interface 30 | #ScriptAlias /nagios3/side.html /usr/lib/cgi-bin/nagios3/grouplist.cgi 31 | -------------------------------------------------------------------------------- /kokki/cookbooks/nagios3/templates/contacts.cfg.j2: -------------------------------------------------------------------------------- 1 | # Managed by Kokki. Do not modify. 2 | 3 | ############################################################################### 4 | # contacts.cfg 5 | ############################################################################### 6 | 7 | 8 | 9 | ############################################################################### 10 | ############################################################################### 11 | # 12 | # CONTACTS 13 | # 14 | ############################################################################### 15 | ############################################################################### 16 | 17 | {% for name, contact in env.config.nagios3.contacts.items() %} 18 | define contact { 19 | contact_name {{ name }} 20 | {% for k, v in contact.items() %}{{ k }} {{ v }} 21 | {% endfor %} 22 | } 23 | {% endfor %} 24 | 25 | ############################################################################### 26 | ############################################################################### 27 | # 28 | # CONTACT GROUPS 29 | # 30 | ############################################################################### 31 | ############################################################################### 32 | 33 | {% for name, group in env.config.nagios3.contactgroups.items() %} 34 | define contactgroup { 35 | contactgroup_name {{ name }} 36 | alias {{ group.alias }} 37 | members {{ ",".join(group.members) }} 38 | } 39 | {% endfor %} 40 | -------------------------------------------------------------------------------- /kokki/cookbooks/nagios3/templates/hostgroups.cfg.j2: -------------------------------------------------------------------------------- 1 | 2 | {% for name, group in env.config.nagios3.hostgroups.items() %} 3 | {% if group.members %} 4 | define hostgroup { 5 | hostgroup_name {{ name }} 6 | alias {{ group.alias }} 7 | members {{ ",".join(group.members) }} 8 | } 9 | {% endif %} 10 | {% endfor %} 11 | -------------------------------------------------------------------------------- /kokki/cookbooks/nagios3/templates/hosts.cfg.j2: -------------------------------------------------------------------------------- 1 | 2 | {% for host in env.config.nagios3.hosts.values() %} 3 | define host { 4 | use {{ host.use }} 5 | host_name {{ host.name }} 6 | alias {{ host.alias }} 7 | address {{ host.address }} 8 | } 9 | 10 | {% for s in host.services.values() %} 11 | define service { 12 | use {{ s.use }} 13 | host_name {{ s.host_name }} 14 | service_description {{ s.service_description }} 15 | check_command {{ s.check_command }} 16 | } 17 | {% endfor %} 18 | {% endfor %} 19 | -------------------------------------------------------------------------------- /kokki/cookbooks/nagios3/templates/service.cfg.j2: -------------------------------------------------------------------------------- 1 | 2 | {% if not hostgroup_name or env.config.nagios3.hostgroups[hostgroup_name]["members"] %} 3 | define service { 4 | {% if host_name %}host_name {{ host_name }}{% endif %} 5 | {% if hostgroup_name %}hostgroup_name {{ hostgroup_name }}{% endif %} 6 | service_description {{ service_description }} 7 | check_command {{ check_command }} 8 | use {{ use }} 9 | notification_interval {{ notification_interval }} 10 | } 11 | {% endif %} 12 | -------------------------------------------------------------------------------- /kokki/cookbooks/nginx/libraries/sites.py: -------------------------------------------------------------------------------- 1 | 2 | from os.path import exists 3 | from kokki import Environment, Execute 4 | 5 | def site(name, enable=True): 6 | env = Environment.get_instance() 7 | 8 | if enable: 9 | cmd = 'nxensite' 10 | else: 11 | cmd = 'nxdissite' 12 | 13 | def _not_if(): 14 | e = exists("%s/sites-enabled/%s" % (env.config.nginx.dir, name)) 15 | return e if enable else not e 16 | 17 | Execute("%s %s" % (cmd, name), 18 | command = "/usr/sbin/%s %s" % (cmd, name), 19 | notifies = [("reload", env.resources["Service"]["nginx"])], 20 | not_if = _not_if) 21 | -------------------------------------------------------------------------------- /kokki/cookbooks/nginx/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Installs and configures Nginx" 3 | __config__ = { 4 | "nginx.dir": dict( 5 | description = "Location of nginx configuration files", 6 | default = "/etc/nginx", 7 | ), 8 | "nginx.log_dir": dict( 9 | description = "Location for nginx logs", 10 | default = "/var/log/nginx", 11 | ), 12 | "nginx.log_format": dict( 13 | description = "Format string for the access log. If not set them the default for nginx is used.", 14 | default = None, 15 | ), 16 | "nginx.user": dict( 17 | description = "User nginx will run as", 18 | default = None, 19 | ), 20 | "nginx.binary": dict( 21 | description = "Location of the nginx server binary", 22 | default = "/usr/sbin/nginx", 23 | ), 24 | "nginx.event_model": dict( 25 | description = "Which event model nginx should use (e.g. epoll)", 26 | default = None, 27 | ), 28 | "nginx.sendfile": dict( 29 | description = "Whether sendfile should be used to serve files", 30 | default = True, 31 | ), 32 | "nginx.tcp_nopush": dict( 33 | description = "Whether to enable/disable tcp_nopush", 34 | default = True, 35 | ), 36 | "nginx.tcp_nodelay": dict( 37 | description = "Whether to enable/disable tcp_nodelay", 38 | default = False, 39 | ), 40 | "nginx.gzip": dict( 41 | description = "Whether gzip is enabled", 42 | default = True, 43 | ), 44 | "nginx.gzip_http_version": dict( 45 | description = "Version of HTTP Gzip", 46 | default = 1.0, 47 | ), 48 | "nginx.gzip_comp_level": dict( 49 | description = "Amount of compression to use", 50 | default = 2, 51 | ), 52 | "nginx.gzip_proxied": dict( 53 | description = "Whether gzip is proxied", 54 | default = "any", 55 | ), 56 | "nginx.gzip_vary": dict( 57 | description = "Whether the 'Content-Vary: Accept-Encoding' header should be included when gzipping", 58 | default = "on", 59 | ), 60 | "nginx.gzip_types": dict( 61 | description = "Supported MIME-types for gzip", 62 | default = [ 63 | "text/plain", 64 | "text/css", 65 | "application/x-javascript", 66 | "text/xml", 67 | "application/xml", 68 | "application/xml+rss", 69 | "text/javascript", 70 | ], 71 | ), 72 | "nginx.keepalive": dict( 73 | description = "Whether to enable keepalive", 74 | default = True, 75 | ), 76 | "nginx.keepalive_timeout": dict( 77 | default = 65, 78 | ), 79 | "nginx.worker_processes": dict( 80 | description = "Number of worker processes", 81 | default = 1, 82 | ), 83 | "nginx.worker_connections": dict( 84 | description = "Number of connections per worker", 85 | default = 1024, 86 | ), 87 | "nginx.server_names_hash_max_size": dict( 88 | description = "The maximum size of the server name hash tables. (default 512)", 89 | default = None, 90 | ), 91 | "nginx.server_names_hash_bucket_size": dict( 92 | description = "Directive assigns the size of basket in the hash-tables of the names of servers. (default 32/64/128 depending on architecture)", 93 | default = None, 94 | ), 95 | } 96 | -------------------------------------------------------------------------------- /kokki/cookbooks/nginx/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, Directory, File, Template, Service 3 | 4 | if not env.config.nginx.user: 5 | if env.system.platform == "amazon": 6 | env.config.nginx.user = "nginx" 7 | else: 8 | env.config.nginx.user = "www-data" 9 | 10 | Package("nginx") 11 | 12 | Directory(env.config.nginx.log_dir, 13 | mode = 0755, 14 | owner = env.config.nginx.user, 15 | action = 'create') 16 | 17 | for nxscript in ('nxensite', 'nxdissite'): 18 | File("/usr/sbin/%s" % nxscript, 19 | content = Template("nginx/%s.j2" % nxscript), 20 | mode = 0755, 21 | owner = "root", 22 | group = "root") 23 | 24 | File("nginx.conf", 25 | path = "%s/nginx.conf" % env.config.nginx.dir, 26 | content = Template("nginx/nginx.conf.j2"), 27 | owner = "root", 28 | group = "root", 29 | mode = 0644) 30 | 31 | Directory("%s/sites-available" % env.config.nginx.dir, 32 | mode = 0755, 33 | owner = env.config.nginx.user, 34 | action = "create") 35 | 36 | Directory("%s/sites-enabled" % env.config.nginx.dir, 37 | mode = 0755, 38 | owner = env.config.nginx.user, 39 | action = "create") 40 | 41 | File("%s/sites-available/default" % env.config.nginx.dir, 42 | content = Template("nginx/default-site.j2"), 43 | owner = "root", 44 | group = "root", 45 | mode = 0644) 46 | 47 | Service("nginx", 48 | supports_status = True, 49 | supports_restart = True, 50 | supports_reload = True, 51 | action = "start", 52 | subscribes = [("reload", env.resources["File"]["nginx.conf"])]) 53 | 54 | if "librato.silverline" in env.included_recipes: 55 | File("/etc/default/nginx", 56 | owner = "root", 57 | group = "root", 58 | mode = 0644, 59 | content = ( 60 | "export SL_NAME=nginx\n" 61 | ), 62 | notifies = [("restart", env.resources["Service"]["nginx"])]) 63 | -------------------------------------------------------------------------------- /kokki/cookbooks/nginx/templates/default-site.j2: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | {% if env.config.hostname %} 4 | server_name {{ env.config.hostname }}; 5 | {% endif %} 6 | 7 | access_log {{ env.config.nginx.log_dir }}/localhost.access.log; 8 | 9 | location / { 10 | root /var/www/nginx-default; 11 | index index.html index.htm; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /kokki/cookbooks/nginx/templates/nginx.conf.j2: -------------------------------------------------------------------------------- 1 | user {{ env.config.nginx.user }}; 2 | worker_processes {{ env.config.nginx.worker_processes }}; 3 | 4 | error_log {{ env.config.nginx.log_dir }}/error.log; 5 | pid /var/run/nginx.pid; 6 | 7 | events { 8 | worker_connections {{ env.config.nginx.worker_connections }}; 9 | {% if env.config.nginx.event_model %}use {{ env.config.nginx.event_model }};{% endif %} 10 | } 11 | 12 | http { 13 | include {{ env.config.nginx.dir }}/mime.types; 14 | default_type application/octet-stream; 15 | 16 | {% if env.config.nginx.log_format %} 17 | log_format custom {{ repr(env.config.nginx.log_format.strip()) }}; 18 | access_log {{ env.config.nginx.log_dir }}/access.log custom; 19 | {% else %} 20 | access_log {{ env.config.nginx.log_dir }}/access.log; 21 | {% endif %} 22 | 23 | sendfile {% if env.config.nginx.sendfile %}on{% else %}off{% endif %}; 24 | tcp_nopush {% if env.config.nginx.tcp_nopush %}on{% else %}off{% endif %}; 25 | tcp_nodelay {% if env.config.nginx.tcp_nodelay %}on{% else %}off{% endif %}; 26 | 27 | {% if env.config.nginx.keepalive == "on" %} 28 | keepalive_timeout {{ env.config.nginx.keepalive_timeout }}; 29 | {% endif %} 30 | 31 | {% if env.config.nginx.gzip %} 32 | gzip on; 33 | gzip_http_version {{ env.config.nginx.gzip_http_version }}; 34 | gzip_comp_level {{ env.config.nginx.gzip_comp_level }}; 35 | gzip_proxied {{ env.config.nginx.gzip_proxied }}; 36 | gzip_types {{ " ".join(env.config.nginx.gzip_types) }}; 37 | gzip_vary {{ env.config.nginx.gzip_vary }}; 38 | {% else %} 39 | gzip off; 40 | {% endif %} 41 | 42 | {% if env.config.nginx.server_names_hash_max_size %}server_names_hash_max_size {{ env.config.nginx.server_names_hash_max_size }};{% endif %} 43 | {% if env.config.nginx.server_names_hash_bucket_size %}server_names_hash_bucket_size {{ env.config.nginx.server_names_hash_bucket_size }};{% endif %} 44 | 45 | include {{ env.config.nginx.dir }}/conf.d/*.conf; 46 | include {{ env.config.nginx.dir }}/sites-enabled/*; 47 | } 48 | -------------------------------------------------------------------------------- /kokki/cookbooks/nginx/templates/nxdissite.j2: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | SYSCONFDIR='{{ env.config.nginx.dir }}' 4 | 5 | if [ -z $1 ]; then 6 | echo "Which site would you like to disable?" 7 | echo -n "Your choices are: " 8 | ls $SYSCONFDIR/sites-enabled/* | \ 9 | sed -e "s,$SYSCONFDIR/sites-enabled/,,g" | xargs echo 10 | echo -n "Site name? " 11 | read SITENAME 12 | else 13 | SITENAME=$1 14 | fi 15 | 16 | if [ $SITENAME = "default" ]; then 17 | PRIORITY="000" 18 | fi 19 | 20 | if ! [ -e $SYSCONFDIR/sites-enabled/$SITENAME -o \ 21 | -e $SYSCONFDIR/sites-enabled/"$PRIORITY"-"$SITENAME" ]; then 22 | echo "This site is already disabled, or does not exist!" 23 | exit 1 24 | fi 25 | 26 | if ! rm $SYSCONFDIR/sites-enabled/$SITENAME 2>/dev/null; then 27 | rm -f $SYSCONFDIR/sites-enabled/"$PRIORITY"-"$SITENAME" 28 | fi 29 | echo "Site $SITENAME disabled; reload nginx to disable." -------------------------------------------------------------------------------- /kokki/cookbooks/nginx/templates/nxensite.j2: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | SYSCONFDIR='{{ env.config.nginx.dir }}' 4 | 5 | if [ -z $1 ]; then 6 | echo "Which site would you like to enable?" 7 | echo -n "Your choices are: " 8 | ls $SYSCONFDIR/sites-available/* | \ 9 | sed -e "s,$SYSCONFDIR/sites-available/,,g" | xargs echo 10 | echo -n "Site name? " 11 | read SITENAME 12 | else 13 | SITENAME=$1 14 | fi 15 | 16 | if [ $SITENAME = "default" ]; then 17 | PRIORITY="000" 18 | fi 19 | 20 | if [ -e $SYSCONFDIR/sites-enabled/$SITENAME -o \ 21 | -e $SYSCONFDIR/sites-enabled/"$PRIORITY"-"$SITENAME" ]; then 22 | echo "This site is already enabled!" 23 | exit 0 24 | fi 25 | 26 | if ! [ -e $SYSCONFDIR/sites-available/$SITENAME ]; then 27 | echo "This site does not exist!" 28 | exit 1 29 | fi 30 | 31 | if [ $SITENAME = "default" ]; then 32 | ln -sf $SYSCONFDIR/sites-available/$SITENAME \ 33 | $SYSCONFDIR/sites-enabled/"$PRIORITY"-"$SITENAME" 34 | else 35 | ln -sf $SYSCONFDIR/sites-available/$SITENAME $SYSCONFDIR/sites-enabled/$SITENAME 36 | fi 37 | 38 | echo "Site $SITENAME installed; reload nginx to enable." -------------------------------------------------------------------------------- /kokki/cookbooks/pip/libraries/providers.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ["PipPackageProvider"] 3 | 4 | import re 5 | from subprocess import check_call, Popen, PIPE, STDOUT 6 | 7 | from kokki import Fail 8 | from kokki.providers.package import PackageProvider 9 | 10 | version_re = re.compile(r'\S\S(.*)\/(.*)-(.*)-py(.*).egg\S') 11 | best_match_re = re.compile(r'Best match: (.*) (.*)\n') 12 | 13 | class PipPackageProvider(PackageProvider): 14 | def get_current_status(self): 15 | p = Popen("%s freeze | grep ^%s==" % (self.pip_binary_path, self.resource.package_name), stdout=PIPE, stderr=STDOUT, shell=True) 16 | out = p.communicate()[0] 17 | res = p.wait() 18 | if res != 0: 19 | self.current_version = None 20 | else: 21 | try: 22 | self.current_version = out.split("==", 1)[1].strip() 23 | except IndexError: 24 | raise Fail("pip could not determine installed package version.") 25 | 26 | @property 27 | def candidate_version(self): 28 | if not hasattr(self, '_candidate_version'): 29 | if not self.resource.version and re.match("^[A-Za-z0-9_.-]+$", self.resource.package_name): 30 | p = Popen([self.easy_install_binary_path, "-n", self.resource.package_name], stdout=PIPE, stderr=STDOUT) 31 | out = p.communicate()[0] 32 | res = p.wait() 33 | if res != 0: 34 | self.log.warning("easy_install check returned a non-zero result (%d) %s" % (res, self.resource)) 35 | 36 | m = best_match_re.search(out) 37 | if not m: 38 | self._candidate_version = None 39 | else: 40 | self._candidate_version = m.group(2) 41 | else: 42 | self._candidate_version = self.resource.version 43 | return self._candidate_version 44 | 45 | @property 46 | def pip_binary_path(self): 47 | return "pip" 48 | 49 | @property 50 | def easy_install_binary_path(self): 51 | return "easy_install" 52 | 53 | def install_package(self, name, version): 54 | if name == 'pip' or not version: 55 | check_call([self.pip_binary_path, "install", "--upgrade", name], stdout=PIPE, stderr=STDOUT) 56 | else: 57 | check_call([self.pip_binary_path, "install", '{0}=={1}'.format(name, version)], stdout=PIPE, stderr=STDOUT) 58 | 59 | def upgrade_package(self, name, version): 60 | self.install_package(name, version) 61 | 62 | def remove_package(self, name, version): 63 | check_call([self.pip_binary_path, "uninstall", name]) 64 | 65 | def purge_package(self, name, version): 66 | self.remove_package(name, version) -------------------------------------------------------------------------------- /kokki/cookbooks/pip/libraries/resources.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ["PipPackage"] 3 | 4 | from kokki import Resource, ForcedListArgument, ResourceArgument 5 | 6 | 7 | class PipPackage(Resource): 8 | provider = "*pip.PipPackageProvider" 9 | 10 | action = ForcedListArgument(default="install") 11 | package_name = ResourceArgument(default=lambda obj:obj.name) 12 | location = ResourceArgument(default=lambda obj:obj.package_name) 13 | version = ResourceArgument(required = True) 14 | actions = ["install", "upgrade", "remove", "purge"] -------------------------------------------------------------------------------- /kokki/cookbooks/pip/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Pip Packages" 3 | __config__ = {} 4 | -------------------------------------------------------------------------------- /kokki/cookbooks/pip/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package 3 | 4 | Package("pip", 5 | provider = "kokki.providers.package.easy_install.EasyInstallProvider" 6 | ) -------------------------------------------------------------------------------- /kokki/cookbooks/postgresql84/recipes/client.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, Fail 3 | 4 | if env.system.platform in ("ubuntu", "debian"): 5 | Package("postgresql-client") 6 | elif env.system.platform in ("redhat", "centos", "fedora"): 7 | Package("postgresql-devel") 8 | else: 9 | raise Fail("Unsupported platform %s for recipe postgresql.client" % env.system.platform) 10 | -------------------------------------------------------------------------------- /kokki/cookbooks/postgresql84/recipes/server.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from kokki import Service, File, Package, Template 4 | 5 | Service("postgresql", 6 | supports_restart = True, 7 | supports_reload = True, 8 | supports_status = True, 9 | action = "nothing") 10 | 11 | Package("postgresql-8.4", 12 | notifies = [("stop", env.resources["Service"]["postgresql"], True)]) 13 | 14 | File("pg_hba.conf", 15 | owner = "postgres", 16 | group = "postgres", 17 | mode = 0600, 18 | path = os.path.join(env.config.postgresql84.config_dir, "pg_hba.conf"), 19 | content = Template("postgresql84/pg_hba.conf.j2"), 20 | notifies = [("reload", env.resources["Service"]["postgresql"])]) 21 | 22 | File("postgresql.conf", 23 | owner = "postgres", 24 | group = "postgres", 25 | mode = 0600, 26 | path = os.path.join(env.config.postgresql84.config_dir, "postgresql.conf"), 27 | content = Template("postgresql84/postgresql.conf.j2"), 28 | notifies = [("restart", env.resources["Service"]["postgresql"])]) 29 | -------------------------------------------------------------------------------- /kokki/cookbooks/postgresql84/recipes/skytools.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, Directory, Script, Fail 3 | 4 | Package("postgresql-server-dev", 5 | package_name = "postgresql-server-dev-8.4") 6 | Package("python-dev") 7 | Package("python-psycopg2") 8 | 9 | def install_package(name, url, creates): 10 | import os 11 | filename = url.rsplit('/', 1)[-1] 12 | dirname = filename 13 | while dirname.rsplit('.', 1)[-1] in ('gz', 'tar', 'tgz', 'bz2'): 14 | dirname = dirname.rsplit('.', 1)[0] 15 | 16 | if not dirname: 17 | raise Fail("Unable to figure out directory name of project for URL %s" % url) 18 | 19 | Script("install-%s" % name, 20 | not_if = lambda:os.path.exists(creates), 21 | cwd = "/usr/local/src", 22 | code = ( 23 | "wget %(url)s\n" 24 | "tar -zxvf %(filename)s\n" 25 | "cd %(dirname)s\n" 26 | "./configure && make install\n" 27 | "ldconfig\n") % dict(url=url, dirname=dirname, filename=filename) 28 | ) 29 | 30 | install_package("skytools", 31 | creates = "/usr/local/bin/pgqadm.py", 32 | url = "http://pgfoundry.org/frs/download.php/2370/skytools-2.1.10.tar.gz") 33 | 34 | Directory("/etc/skytools", 35 | owner = "root", 36 | mode = 0755) 37 | -------------------------------------------------------------------------------- /kokki/cookbooks/postgresql84/recipes/skytools_londiste.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import File, Template 3 | 4 | env.include_recipe("postgresql84.skytools") 5 | 6 | File("/etc/skytools/londiste.ini", 7 | owner = "root", 8 | content = Template("postgresql84/skytools-londiste.ini.j2")) 9 | -------------------------------------------------------------------------------- /kokki/cookbooks/postgresql84/recipes/skytools_ticker.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import File, Template 3 | 4 | env.include_recipe("postgresql84.skytools") 5 | 6 | File("/etc/skytools/ticker.ini", 7 | owner = "root", 8 | content = Template("postgresql84/skytools-ticker.ini.j2")) 9 | -------------------------------------------------------------------------------- /kokki/cookbooks/postgresql84/templates/skytools-londiste.ini.j2: -------------------------------------------------------------------------------- 1 | [londiste] 2 | job_name = {{ env.config.postgresql84.skytools.londiste.job_name }} 3 | 4 | provider_db = {{ env.config.postgresql84.skytools.londiste.provider_db }} 5 | subscriber_db = {{ env.config.postgresql84.skytools.londiste.subscriber_db }} 6 | 7 | # it will be used as sql ident so no dots/spaces 8 | pgq_queue_name = {{ env.config.postgresql84.skytools.londiste.pgq_queue_name }} 9 | 10 | logfile = {{ env.config.postgresql84.skytools.ticker.logfile }} 11 | pidfile = {{ env.config.postgresql84.skytools.ticker.pidfile }} 12 | -------------------------------------------------------------------------------- /kokki/cookbooks/postgresql84/templates/skytools-ticker.ini.j2: -------------------------------------------------------------------------------- 1 | [pgqadm] 2 | job_name = {{ env.config.postgresql84.skytools.ticker.job_name }} 3 | db = {{ env.config.postgresql84.skytools.ticker.db }} 4 | 5 | # how often to run maintenance [seconds] 6 | maint_delay = 600 7 | 8 | # how often to check for activity [seconds] 9 | loop_delay = 0.1 10 | logfile = {{ env.config.postgresql84.skytools.ticker.logfile }} 11 | pidfile = {{ env.config.postgresql84.skytools.ticker.pidfile }} 12 | -------------------------------------------------------------------------------- /kokki/cookbooks/postgresql9/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "PostgreSQL database 9" 3 | __config__ = { 4 | "postgresql9.version": dict ( 5 | description = "Version of PostgreSQL 9", 6 | default = "9.1", 7 | ), 8 | "postgresql9.data_dir": dict( 9 | description = "Location of the PostgreSQL databases", 10 | default = "/var/lib/postgresql/9.1/main", 11 | ), 12 | "postgresql9.config_dir": dict( 13 | description = "Location of the PostgreSQL configuration files", 14 | default = "/etc/postgresql/9.1/main", 15 | ), 16 | "postgresql9.pidfile": dict( 17 | description = "Path to the PostgreSQL pid file", 18 | default = "/var/run/postgresql/9.1-main.pid", 19 | ), 20 | "postgresql9.unix_socket_directory": dict( 21 | default = "/var/run/postgresql", 22 | ), 23 | "postgresql9.listen_addresses": dict( 24 | description = "IP addresses PostgreSQL should listen on (* for all interfaces)", 25 | default = ["localhost"], 26 | ), 27 | "postgresql9.port": dict( 28 | description = "Port PostgreSQL should bind to", 29 | default = 5432, 30 | ), 31 | "postgresql9.max_connections": dict( 32 | description = "Maximum numbers of connections", 33 | default = 100, 34 | ), 35 | "postgresql9.auth": dict( 36 | description = "List of auth configs", 37 | default = [ 38 | dict( 39 | type = "local", 40 | database = "all", 41 | user = "all", 42 | method = "ident", 43 | ), 44 | dict( 45 | type = "host", 46 | database = "all", 47 | user = "all", 48 | cidr = "127.0.0.1/32", 49 | method = "md5", 50 | ), 51 | dict( 52 | type = "host", 53 | database = "all", 54 | user = "all", 55 | cidr = "::1/128", 56 | method = "md5", 57 | ), 58 | ], 59 | ), 60 | "postgresql9.ssl": dict( 61 | default = False, 62 | ), 63 | "postgresql9.shared_buffers": dict( 64 | default = "24MB", 65 | ), 66 | "postgresql9.log_min_duration_statement": dict( 67 | description = "-1 is disabled, 0 logs all statements and their durations, > 0 logs only statements running at least this number of milliseconds", 68 | default = -1, 69 | ), 70 | "postgresql9.locale": dict( 71 | default = "en_US.UTF-8", 72 | ), 73 | # Streaming replication 74 | "postgresql9.max_wal_senders": dict( 75 | description = "Maximum number of WAL sender processes", 76 | default = 0, 77 | ), 78 | "postgresql9.wal_sender_delay": dict( 79 | description = "walsender cycle time, 1-10000 milliseconds", 80 | default = "200ms", 81 | ), 82 | # Standby Servers 83 | "postgresql9.hot_standby": dict( 84 | description = "Allow queries during discovery", 85 | default = False, 86 | ), 87 | } 88 | 89 | for k, v in __config__.iteritems(): 90 | if isinstance(v['default'], basestring): 91 | v["default"] = v["default"].format(config=__config__) 92 | -------------------------------------------------------------------------------- /kokki/cookbooks/postgresql9/recipes/default.py: -------------------------------------------------------------------------------- 1 | import os 2 | from kokki import Execute, Package 3 | 4 | # if not (env.system.platform == "ubuntu" and env.system.lsb['release'] in ["11.10"]): 5 | # apt_list_path = '/etc/apt/sources.list.d/pitti-postgresql-lucid.list' 6 | 7 | # Execute("apt-update-postgresql9", 8 | # command = "apt-get update", 9 | # action = "nothing") 10 | 11 | # apt = None 12 | # if env.system.platform == "ubuntu": 13 | # Package("python-software-properties") 14 | # Execute("add-apt-repository ppa:pitti/postgresql -y", 15 | # not_if = lambda:os.path.exists(apt_list_path), 16 | # notifies = [("run", env.resources["Execute"]["apt-update-postgresql9"], True)]) 17 | -------------------------------------------------------------------------------- /kokki/cookbooks/postgresql9/recipes/server.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from kokki import Package, File, Template, Service 4 | 5 | env.include_recipe("postgresql9") 6 | 7 | Service("postgresql", 8 | supports_restart = True, 9 | supports_reload = True, 10 | supports_status = True, 11 | action = "nothing") 12 | 13 | Package("postgresql-" + env.config.postgresql9.version, 14 | notifies = [("stop", env.resources["Service"]["postgresql"], True)]) 15 | 16 | File("pg_hba.conf", 17 | owner = "postgres", 18 | group = "postgres", 19 | mode = 0600, 20 | path = os.path.join(env.config.postgresql9.config_dir, "pg_hba.conf"), 21 | content = Template("postgresql9/pg_hba.conf.j2"), 22 | notifies = [("reload", env.resources["Service"]["postgresql"])]) 23 | 24 | File("postgresql.conf", 25 | owner = "postgres", 26 | group = "postgres", 27 | mode = 0666, 28 | path = os.path.join(env.config.postgresql9.config_dir, "postgresql.conf"), 29 | content = Template("postgresql9/postgresql.conf.j2"), 30 | notifies = [("restart", env.resources["Service"]["postgresql"])]) 31 | -------------------------------------------------------------------------------- /kokki/cookbooks/powerdns/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "PowerDNS server and recursor" 3 | __config__ = { 4 | "powerdns.backends": dict( 5 | description = "List of backend modules to install", 6 | default = ["pipe"], 7 | ), 8 | "powerdns.pipe_command": dict( 9 | description = "Pipe command", 10 | default = None, 11 | ), 12 | "powerdns.allow_recursion": dict( 13 | description = "List of addresses from which to allow recursion", 14 | default = "127.0.0.1", 15 | ), 16 | "powerdns.recursor": dict( 17 | description = "IP address of recursing nameserver if desired", 18 | default = None, 19 | ), 20 | } 21 | -------------------------------------------------------------------------------- /kokki/cookbooks/powerdns/recipes/default.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuel/kokki/da98da55e0bba8db5bda993666a43c6fdc4cacdb/kokki/cookbooks/powerdns/recipes/default.py -------------------------------------------------------------------------------- /kokki/cookbooks/powerdns/recipes/recursor.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package 3 | 4 | Package("pdns-recursor") 5 | -------------------------------------------------------------------------------- /kokki/cookbooks/powerdns/recipes/server.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, Service, File, Template 3 | 4 | Package("pdns-server") 5 | Service("pdns") 6 | 7 | File("/etc/powerdns/pdns.conf", 8 | owner = "root", 9 | group = "root", 10 | mode = 0644, 11 | content = Template("powerdns/pdns.conf"), 12 | notifies = [("reload", env.resources["Service"]["pdns"])]) 13 | 14 | for be in env.config.powerdns.backends: 15 | Package("pdns-backend-%s" % be) 16 | -------------------------------------------------------------------------------- /kokki/cookbooks/rabbitmq/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "RabbitMQ Messaging Server" 3 | __config__ = { 4 | "rabbitmq.path": dict( 5 | description = "Install path for rabbitmq", 6 | default = "/usr/local/rabbitmq", 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /kokki/cookbooks/rabbitmq/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, Execute, File, Fail 3 | 4 | Package("erlang") 5 | 6 | apt_list_path = '/etc/apt/sources.list.d/rabbitmq.list' 7 | apt = None 8 | if env.system.platform in ("ubuntu", "debian"): 9 | apt = "deb http://www.rabbitmq.com/debian/ testing main" 10 | 11 | if not apt: 12 | raise Fail("Can't find a rabbitmq package for your platform/version") 13 | 14 | Execute("apt-update-rabbitmq", 15 | command = "apt-get update", 16 | action = "nothing") 17 | 18 | Execute("curl http://www.rabbitmq.com/rabbitmq-signing-key-public.asc | apt-key add -", 19 | not_if = "(apt-key list | grep rabbitmq > /dev/null)") 20 | 21 | File(apt_list_path, 22 | owner = "root", 23 | group ="root", 24 | mode = 0644, 25 | content = apt+"\n", 26 | notifies = [("run", env.resources["Execute"]["apt-update-rabbitmq"], True)]) 27 | 28 | Package("rabbitmq-server") 29 | -------------------------------------------------------------------------------- /kokki/cookbooks/redis/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Redis in-memory database" 3 | __config__ = { 4 | "redis.configfile": dict( 5 | description = "Path to the config file", 6 | default = "/etc/redis.conf", 7 | ), 8 | "redis.bind": dict( 9 | description = "Interface to listen on", 10 | default = "127.0.0.1", 11 | ), 12 | "redis.port": dict( 13 | description = "Accept connections on the specified port", 14 | default = 6379, 15 | ), 16 | "redis.timeout": dict( 17 | description = "Close the connection after a client is idle for N seconds (0 to disable)", 18 | default = 300, 19 | ), 20 | "redis.pidfile": dict( 21 | description = "The file which to write the pid to.", 22 | default = "/var/run/redis.pid", 23 | ), 24 | "redis.dbdir": dict( 25 | description = "For default save/load DB in/from the working directory", 26 | default = "/var/db/redis/", 27 | ), 28 | "redis.appendonly": dict( 29 | description = "Use the append only file for persistence", 30 | default = True, 31 | ), 32 | "redis.appendfsync": dict( 33 | description = "How often to fsync the AOF file (no, everysec, always)", 34 | default = "everysec", 35 | ), 36 | "redis.databases": dict( 37 | description = "Set the number of databases.", 38 | default = 16, 39 | ), 40 | "redis.logfile": dict( 41 | description = "File for Redis's log", 42 | default = "/var/log/redis.log", 43 | ), 44 | "redis.loglevel": dict( 45 | description = "How much Redis should log", 46 | default = "notice", 47 | ), 48 | "redis.master.ip": dict( 49 | description = "This instance of redis is a slave of a master at the given IP", 50 | default = None, 51 | ), 52 | "redis.master.port": dict( 53 | description = "Port number for the master server. If slaveof.ip is specified but thit is not then defalts to redis.port.", 54 | default = None, 55 | ), 56 | "redis.maxmemory": dict( 57 | description = "Don't use more memory than the specified amount of bytes.", 58 | default = None, 59 | ), 60 | } 61 | -------------------------------------------------------------------------------- /kokki/cookbooks/redis/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from kokki import Script, Directory, File, Service, Package, Link, Template 4 | 5 | # env.include_recipe("monit") 6 | 7 | version = "2.2.0-rc2" 8 | dirname = "redis-%s" % version 9 | filename = "%s.tar.gz" % dirname 10 | url = "http://redis.googlecode.com/files/%s" % filename 11 | 12 | Script("install-redis", 13 | not_if = lambda:os.path.exists("/usr/local/bin/redis-server"), 14 | cwd = "/usr/local/src", 15 | code = ( 16 | "wget %(url)s\n" 17 | "tar -zxvf %(filename)s\n" 18 | "cd %(dirname)s\n" 19 | "make install\n") % dict(url=url, dirname=dirname, filename=filename)) 20 | 21 | Directory(env.config.redis.dbdir, 22 | owner = "root", 23 | group = "root", 24 | mode = 0700, 25 | recursive = True) 26 | 27 | File("redis.conf", 28 | path = env.config.redis.configfile, 29 | owner = "root", 30 | group = "root", 31 | mode = 0644, 32 | content = Template("redis/redis.conf.j2")) 33 | 34 | # env.cookbooks.monit.rc("redis", 35 | # content = Template("redis/monit.conf.j2")) 36 | 37 | File("/etc/init.d/redis", 38 | owner = "root", 39 | group = "root", 40 | mode = 0755, 41 | content = Template("redis/init.conf.j2", 42 | variables = dict( 43 | redis = dict( 44 | logpath = os.path.dirname(env.config.redis.logfile), 45 | dbdir = env.config.redis.dbdir, 46 | configfile = env.config.redis.configfile, 47 | pidfile = env.config.redis.pidfile, 48 | options = [], 49 | )))) 50 | # notifies = [ 51 | # ("reload", env.resources["Service"]["redis"], True), 52 | # ]) 53 | 54 | Service("redis", 55 | subscribes = [ 56 | ("restart", env.resources["Script"]["install-redis"]), 57 | ]) 58 | 59 | if "munin.node" in env.included_recipes: 60 | Package("redis", 61 | provider = "kokki.providers.package.easy_install.EasyInstallProvider") 62 | for n in ('active_connections', 'commands', 'connects', 'used_memory'): 63 | Link("/etc/munin/plugins/redis_%s" % n, 64 | to = "/etc/munin/python-munin/plugins/redis_%s" % n, 65 | notifies = [("restart", env.resources['Service']['munin-node'])]) 66 | -------------------------------------------------------------------------------- /kokki/cookbooks/redis/templates/init.conf.j2: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: redis-server 4 | # Required-Start: $syslog 5 | # Required-Stop: $syslog 6 | # Should-Start: $local_fs 7 | # Should-Stop: $local_fs 8 | # Default-Start: 2 3 4 5 9 | # Default-Stop: 0 1 6 10 | # Short-Description: redis-server - Persistent key-value db 11 | # Description: redis-server - Persistent key-value db 12 | ### END INIT INFO 13 | 14 | set -e 15 | 16 | if [ -r "/lib/lsb/init-functions" ]; then 17 | . /lib/lsb/init-functions 18 | else 19 | echo "E: /lib/lsb/init-functions not found, lsb-base (>= 3.0-6) needed" 20 | exit 1 21 | fi 22 | 23 | 24 | PATH=/opt/redis/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 25 | DAEMON=`which redis-server` 26 | REDIS_CLI=`which redis-cli` 27 | CONFIG_FILE={{ redis.configfile }} 28 | DAEMON_ARGS="$CONFIG_FILE" 29 | NAME=redis-server 30 | DESC=redis-server 31 | DBPATH={{ redis.dbdir }} 32 | PIDFILE={{ redis.pidfile }} 33 | LOGPATH={{ redis.logpath }} 34 | 35 | {% if "librato.silverline" in env.included_recipes %} 36 | export SL_NAME=redis 37 | {% endif %} 38 | 39 | test -x $DAEMON || exit 0 40 | test -x $DAEMONBOOTSTRAP || exit 0 41 | 42 | set -e 43 | 44 | case "$1" in 45 | start) 46 | echo -n "Starting $DESC: " 47 | touch $PIDFILE $LOGFILE 48 | #chown redis:redis $PIDFILE $LOGFILE 49 | #--chuid redis:redis 50 | mkdir -p $DBPATH 51 | # chown -R redis.redis {{ redis.dbdir }} 52 | if start-stop-daemon --start --quiet --umask 007 --pidfile $PIDFILE --exec $DAEMON -- $DAEMON_ARGS 53 | then 54 | echo "$NAME." 55 | else 56 | echo "failed" 57 | fi 58 | ;; 59 | stop) 60 | echo "Stopping $DESC" 61 | if [ ! -e "$PIDFILE" ] 62 | then 63 | echo "failed" 64 | else 65 | LISTENING_PORT=`grep -E "port +([0-9]+)" "$CONFIG_FILE" | grep -Eo "[0-9]+"` 66 | $REDIS_CLI -p $LISTENING_PORT SHUTDOWN 67 | #rm -f $PIDFILE 68 | fi 69 | ;; 70 | status) 71 | if pidofproc -p "$PIDFILE" >/dev/null; then 72 | echo "running" 73 | exit 0 74 | else 75 | echo "not running" 76 | exit 1 77 | fi 78 | ;; 79 | restart|force-reload) 80 | ${0} stop 81 | ${0} start 82 | ;; 83 | *) 84 | echo "Usage: /etc/init.d/$NAME {start|stop|restart|force-reload}" >&2 85 | exit 1 86 | ;; 87 | esac 88 | 89 | exit 0 90 | -------------------------------------------------------------------------------- /kokki/cookbooks/redis/templates/monit.conf.j2: -------------------------------------------------------------------------------- 1 | check process redis with pidfile {{ env.config.redis.pidfile }} 2 | start program = "/usr/local/bin/redis-server {{ env.config.redis.configfile }}" 3 | stop program = "/usr/bin/killall redis-server" 4 | if 5 restarts within 5 cycles then timeout 5 | -------------------------------------------------------------------------------- /kokki/cookbooks/redis/templates/upstart.conf.j2: -------------------------------------------------------------------------------- 1 | # Ubuntu upstart file at /etc/init/redis.conf 2 | 3 | pre-start script 4 | mkdir -p {{ redis.logpath }} 5 | mkdir -p {{ redis.dbdir }} 6 | # chown -R redis.redis {{ redis.dbdir }} 7 | end script 8 | 9 | limit nofile 32000 32000 10 | start on runlevel [2345] 11 | stop on runlevel [06] 12 | 13 | script 14 | exec start-stop-daemon --start --quiet --pidfile {{ redis.pidfile }} --exec /usr/local/bin/redis-server -- {{ redis.configfile }} {{ " ".join(redis.options) }} 15 | end script 16 | -------------------------------------------------------------------------------- /kokki/cookbooks/serverdensity/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "ServerDensity server monitoring" 3 | __config__ = { 4 | "serverdensity.agent_key": dict( 5 | description = "Agent key", 6 | default = "key", 7 | ), 8 | "serverdensity.sd_url": dict( 9 | description = "Service url", 10 | default = "http://name.serverdensity.com", 11 | ), 12 | "serverdensity.plugin_directory": dict( 13 | description = "Path to plugins", 14 | default = "/etc/sd-agent/plugins", 15 | ), 16 | "serverdensity.configs": dict( 17 | description = "Dictionary of additional config variables for sd-agent", 18 | default = {}, 19 | ), 20 | } 21 | -------------------------------------------------------------------------------- /kokki/cookbooks/serverdensity/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import File, Execute, Package, Directory, Service, Template, Fail 3 | 4 | apt_list_path = '/etc/apt/sources.list.d/serverdensity.list' 5 | apt = None 6 | if env.system.platform == "ubuntu": 7 | ver = env.system.lsb['release'] 8 | apt = "deb http://www.serverdensity.com/downloads/linux/debian lenny main" 9 | # if ver == "10.04": 10 | # apt = "deb http://apt.librato.com/ubuntu/ lucid non-free" 11 | elif env.system.platform == "debian": 12 | ver = env.system.lsb['release'] 13 | apt = "deb http://www.serverdensity.com/downloads/linux/debian lenny main" 14 | # if ver == '5.0': 15 | # apt = "deb http://apt.librato.com/debian/ lenny non-free" 16 | 17 | if not apt: 18 | raise Fail("Can't find a serverdensity package for your platform/version") 19 | 20 | Execute("apt-update-serverdensity", 21 | command = "apt-get update", 22 | action = "nothing") 23 | 24 | Execute("curl http://www.serverdensity.com/downloads/boxedice-public.key | apt-key add -", 25 | not_if = "(apt-key list | grep 'Server Density' > /dev/null)") 26 | 27 | File(apt_list_path, 28 | owner = "root", 29 | group ="root", 30 | mode = 0644, 31 | content = apt+"\n", 32 | notifies = [("run", env.resources["Execute"]["apt-update-serverdensity"], True)]) 33 | 34 | Package("sd-agent") 35 | 36 | Directory(env.config.serverdensity.plugin_directory, 37 | owner = "sd-agent", 38 | group = "sd-agent", 39 | mode = 0770, 40 | recursive = True) 41 | 42 | Service("sd-agent", 43 | supports_restart = True) 44 | 45 | File("/etc/sd-agent/config.cfg", 46 | owner = "sd-agent", 47 | group = "sd-agent", 48 | mode = 0660, 49 | content = Template("serverdensity/config.cfg.j2"), 50 | notifies = [("restart", env.resources["Service"]["sd-agent"])]) 51 | -------------------------------------------------------------------------------- /kokki/cookbooks/serverdensity/templates/config.cfg.j2: -------------------------------------------------------------------------------- 1 | [Main] 2 | sd_url: {{ env.config.serverdensity.sd_url }} 3 | agent_key: {{ env.config.serverdensity.agent_key }} 4 | plugin_directory: {{ env.config.serverdensity.plugin_directory }} 5 | {% for key, value in env.config.serverdensity.configs.items() %}{{ key }}: {{ value }} 6 | {% endfor %} -------------------------------------------------------------------------------- /kokki/cookbooks/ssh/libraries/config.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import File, Template 3 | 4 | def SSHConfig(name, hosts, mode=0600, **kwargs): 5 | File(name, 6 | mode = mode, 7 | content = Template("ssh/config.j2", {'hosts': hosts}), 8 | **kwargs) 9 | -------------------------------------------------------------------------------- /kokki/cookbooks/ssh/libraries/providers.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ["SSHKnownHostProvider", "SSHAuthorizedKeyProvider"] 3 | 4 | from kokki import Provider 5 | 6 | class SSHKnownHostProvider(Provider): 7 | def action_include(self): 8 | hosts = self.resource.env.cookbooks.ssh.SSHKnownHostsFile(self.resource.path) 9 | modified = False 10 | for host in self.resource.host.split(','): 11 | if hosts.add_host(host, self.resource.keytype, self.resource.key, hashed=self.resource.hashed): 12 | modified = True 13 | self.log.info("[%s] Added host %s to known_hosts file %s" % (self, host, self.resource.path)) 14 | else: 15 | self.log.debug("[%s] Host %s already in known_hosts file %s" % (self, host, self.resource.path)) 16 | if modified: 17 | hosts.save(self.resource.path) 18 | self.resource.updated() 19 | 20 | def action_exclude(self): 21 | hosts = self.resource.env.cookbooks.ssh.SSHKnownHostsFile(self.resource.path) 22 | modified = False 23 | for host in self.resource.host.split(','): 24 | if hosts.remove_host(host): 25 | modified = True 26 | self.log.info("[%s] Removed host %s from known_hosts file %s" % (self, host, self.resource.path)) 27 | else: 28 | self.log.debug("[%s] Host %s not found in known_hosts file %s" % (self, host, self.resource.path)) 29 | if modified: 30 | hosts.save(self.resource.path) 31 | self.resource.updated() 32 | 33 | class SSHAuthorizedKeyProvider(Provider): 34 | def action_include(self): 35 | keys = self.resource.env.cookbooks.ssh.SSHAuthorizedKeysFile(self.resource.path) 36 | if keys.add_key(self.resource.keytype, self.resource.key, self.resource.name): 37 | self.log.info("[%s] Added key to authorized_keys file %s" % (self, self.resource.path)) 38 | keys.save(self.resource.path) 39 | self.resource.updated() 40 | else: 41 | self.log.debug("[%s] Key already in authorized_keys file %s" % (self, self.resource.path)) 42 | 43 | def action_exclude(self): 44 | keys = self.resource.env.cookbooks.ssh.SSHAuthorizedKeysFile(self.resource.path) 45 | if keys.remove_key(self.resource.keytype, self.resource.key): 46 | self.log.info("[%s] Removed key from authorized_keys file %s" % (self, self.resource.path)) 47 | keys.save(self.resource.path) 48 | self.resource.updated() 49 | else: 50 | self.log.debug("[%s] Key not found in authorized_keys file %s" % (self, self.resource.path)) 51 | -------------------------------------------------------------------------------- /kokki/cookbooks/ssh/libraries/resources.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ["SSHKnownHost", "SSHAuthorizedKey"] 3 | 4 | import os.path 5 | from kokki import Resource, ForcedListArgument, ResourceArgument, BooleanArgument, Fail 6 | 7 | class SSHKnownHost(Resource): 8 | provider = "*ssh.SSHKnownHostProvider" 9 | 10 | action = ForcedListArgument(default="include") 11 | host = ResourceArgument(default=lambda obj:obj.name) 12 | keytype = ResourceArgument() 13 | key = ResourceArgument() 14 | hashed = BooleanArgument(default=True) 15 | user = ResourceArgument() 16 | path = ResourceArgument() 17 | 18 | actions = Resource.actions + ["include", "exclude"] 19 | 20 | def validate(self): 21 | if not self.path: 22 | if not self.user: 23 | raise Fail("[%s] Either path or user is required" % self) 24 | self.path = os.path.join(self.env.cookbooks.ssh.ssh_path_for_user(self.user), "known_hosts") 25 | 26 | class SSHAuthorizedKey(Resource): 27 | provider = "*ssh.SSHAuthorizedKeyProvider" 28 | 29 | action = ForcedListArgument(default="include") 30 | keytype = ResourceArgument() 31 | key = ResourceArgument() 32 | user = ResourceArgument() 33 | path = ResourceArgument() 34 | 35 | actions = Resource.actions + ["include", "exclude"] 36 | 37 | def validate(self): 38 | if not self.path: 39 | if not self.user: 40 | raise Fail("[%s] Either path or user is required" % self) 41 | self.path = os.path.join(self.env.cookbooks.ssh.ssh_path_for_user(self.user), "authorized_keys") 42 | -------------------------------------------------------------------------------- /kokki/cookbooks/ssh/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "SSH Service" 3 | __config__ = { 4 | "sshd.allow_password_login_for_users": dict( 5 | description = "Allows password logins for the given users.", 6 | default = [], 7 | ), 8 | "sshd.password_authentication": dict( 9 | description = "Allow password authentication", 10 | default = False, 11 | ), 12 | "sshd.service_name": dict( 13 | description = "Name of the ssh service", 14 | default = None, 15 | ), 16 | } 17 | -------------------------------------------------------------------------------- /kokki/cookbooks/ssh/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Service, Package, File, Template 3 | 4 | if env.system.os == "linux": 5 | Package("openssh-server", action="upgrade") 6 | Package("openssh-client", action="upgrade") 7 | 8 | if not env.config.sshd.service_name: 9 | if env.system.platform in ("redhat", "fedora", "centos", "amazon"): 10 | env.config.sshd.service_name = "sshd" 11 | else: 12 | env.config.sshd.service_name = "ssh" 13 | 14 | Service("ssh", 15 | service_name = env.config.sshd.service_name) 16 | 17 | File("sshd_config", 18 | path = "/etc/ssh/sshd_config", 19 | content = Template("ssh/sshd_config.j2"), 20 | mode = 0644, 21 | owner = "root", 22 | group = "root", 23 | notifies = [("restart", env.resources["Service"]["ssh"], True)] 24 | ) -------------------------------------------------------------------------------- /kokki/cookbooks/ssh/templates/config.j2: -------------------------------------------------------------------------------- 1 | {% for name, config in hosts.items() %} 2 | Host {{ name }} 3 | {% for k, v in config.items() %} {{ k }} {{ v }} 4 | {% endfor %} 5 | {% endfor %} 6 | -------------------------------------------------------------------------------- /kokki/cookbooks/ssh/templates/sshd_config.j2: -------------------------------------------------------------------------------- 1 | # Package generated configuration file 2 | # See the sshd(8) manpage for details 3 | 4 | # What ports, IPs and protocols we listen for 5 | Port 22 6 | # Use these options to restrict which interfaces/protocols sshd will bind to 7 | #ListenAddress :: 8 | #ListenAddress 0.0.0.0 9 | Protocol 2 10 | # HostKeys for protocol version 2 11 | HostKey /etc/ssh/ssh_host_rsa_key 12 | HostKey /etc/ssh/ssh_host_dsa_key 13 | #Privilege Separation is turned on for security 14 | UsePrivilegeSeparation yes 15 | 16 | # Lifetime and size of ephemeral version 1 server key 17 | KeyRegenerationInterval 3600 18 | ServerKeyBits 768 19 | 20 | # Logging 21 | SyslogFacility AUTH 22 | LogLevel INFO 23 | 24 | # Authentication: 25 | LoginGraceTime 120 26 | PermitRootLogin yes 27 | StrictModes yes 28 | 29 | RSAAuthentication yes 30 | PubkeyAuthentication yes 31 | #AuthorizedKeysFile %h/.ssh/authorized_keys 32 | 33 | # Don't read the user's ~/.rhosts and ~/.shosts files 34 | IgnoreRhosts yes 35 | # For this to work you will also need host keys in /etc/ssh_known_hosts 36 | RhostsRSAAuthentication no 37 | # similar for protocol version 2 38 | HostbasedAuthentication no 39 | # Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication 40 | #IgnoreUserKnownHosts yes 41 | 42 | # To enable empty passwords, change to yes (NOT RECOMMENDED) 43 | PermitEmptyPasswords no 44 | 45 | # Change to yes to enable challenge-response passwords (beware issues with 46 | # some PAM modules and threads) 47 | ChallengeResponseAuthentication no 48 | 49 | # Change to no to disable tunnelled clear text passwords 50 | PasswordAuthentication {% if env.config.sshd.password_authentication %}yes{% else %}no{% endif %} 51 | 52 | # Kerberos options 53 | #KerberosAuthentication no 54 | #KerberosGetAFSToken no 55 | #KerberosOrLocalPasswd yes 56 | #KerberosTicketCleanup yes 57 | 58 | # GSSAPI options 59 | #GSSAPIAuthentication no 60 | #GSSAPICleanupCredentials yes 61 | 62 | X11Forwarding yes 63 | X11DisplayOffset 10 64 | PrintMotd no 65 | PrintLastLog yes 66 | TCPKeepAlive yes 67 | #UseLogin no 68 | 69 | #MaxStartups 10:30:60 70 | #Banner /etc/issue.net 71 | 72 | # Allow client to pass locale environment variables 73 | AcceptEnv LANG LC_* 74 | 75 | Subsystem sftp /usr/lib/openssh/sftp-server 76 | 77 | UsePAM yes 78 | 79 | {% for user in env.config.sshd.allow_password_login_for_users %} 80 | Match User {{ user }} 81 | PasswordAuthentication yes 82 | {% endfor %} -------------------------------------------------------------------------------- /kokki/cookbooks/sudo/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __config__ = { 3 | "sudo.users": dict( 4 | description = "Users who are allowed sudo ALL", 5 | default = [], 6 | ), 7 | "sudo.groups": dict( 8 | description = "Groups who are allowed sudo ALL", 9 | default = [], 10 | ), 11 | } 12 | -------------------------------------------------------------------------------- /kokki/cookbooks/sudo/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package, File, Template 3 | 4 | Package("sudo", action="upgrade") 5 | File("/etc/sudoers", 6 | owner = "root", 7 | group = "root", 8 | mode = 0440, 9 | content = Template("sudo/sudoers.j2"), 10 | ) 11 | -------------------------------------------------------------------------------- /kokki/cookbooks/sudo/templates/sudoers.j2: -------------------------------------------------------------------------------- 1 | # 2 | # /etc/sudoers 3 | # 4 | # Generated by Kokki 5 | # 6 | 7 | Defaults !lecture,tty_tickets,!fqdn 8 | 9 | # User privilege specification 10 | root ALL=(ALL) ALL 11 | 12 | # Members of the sysadmin group may gain root privileges 13 | %sysadmin ALL=(ALL) ALL 14 | 15 | {% for user in env.config.sudo.users %} 16 | {{ user.name }} ALL={% if user.nopassword %}NOPASSWD:{% else %}(ALL){% endif %} ALL 17 | {% endfor %} 18 | 19 | {% for group in env.config.sudo.groups %} 20 | # Members of the group '{{ group }}' may gain root privileges 21 | %{{ group.name }} ALL={% if group.nopassword %}NOPASSWD:{% else %}(ALL){% endif %} ALL 22 | {% endfor %} 23 | -------------------------------------------------------------------------------- /kokki/cookbooks/supervisor/libraries/config.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from kokki import Environment, File 4 | 5 | def configuration(name, content): 6 | env = Environment.get_instance() 7 | return File("supervisor-%s" % name, 8 | content = content, 9 | owner = "root", 10 | group = "root", 11 | mode = 0644, 12 | path = os.path.join(env.config.supervisor.custom_config_path, name) + ".conf", 13 | notifies = [("reload", env.resources["Service"]["supervisor"])]) 14 | -------------------------------------------------------------------------------- /kokki/cookbooks/supervisor/libraries/providers.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import re 4 | import subprocess 5 | from kokki import Provider, Fail 6 | 7 | whitespace_re = re.compile(r'\s+') 8 | 9 | class SupervisorServiceProvider(Provider): 10 | def action_start(self): 11 | if not self.status(): 12 | self._init_cmd("start", 0) 13 | self.resource.updated() 14 | 15 | def action_stop(self): 16 | if self.status(): 17 | self._init_cmd("stop", 0) 18 | self.resource.updated() 19 | 20 | def action_restart(self): 21 | self._init_cmd("restart", 0) 22 | self.resource.updated() 23 | 24 | def action_reload(self): 25 | self._init_cmd("update", 0) 26 | self.resource.updated() 27 | 28 | def status(self): 29 | p = subprocess.Popen([self.supervisorctl_path, "status"], 30 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 31 | out = p.communicate()[0] 32 | for l in out.split('\n'): 33 | try: 34 | svc, status, info = whitespace_re.split(l.strip(), 2) 35 | service, process_name = svc.split(':') 36 | except ValueError: 37 | continue 38 | if service == self.resource.service_name: 39 | return status.strip() == "RUNNING" 40 | raise Fail("Service %s not managed by supervisor" % self.resource.service_name) 41 | 42 | def _init_cmd(self, command, expect=None): 43 | ret = subprocess.call([self.supervisorctl_path, command, self.resource.service_name], 44 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 45 | if expect is not None and expect != ret: 46 | raise Fail("%r command %s for service %s failed" % (self, command, self.resource.service_name)) 47 | return ret 48 | 49 | @property 50 | def supervisorctl_path(self): 51 | return os.path.join(self.resource.env.config.supervisor.binary_path, "supervisorctl") 52 | -------------------------------------------------------------------------------- /kokki/cookbooks/supervisor/libraries/resources.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Service, BooleanArgument 3 | 4 | class SupervisorService(Service): 5 | provider = "*supervisor.SupervisorServiceProvider" 6 | 7 | supports_restart = BooleanArgument(default=True) 8 | supports_status = BooleanArgument(default=True) 9 | supports_reload = BooleanArgument(default=True) 10 | -------------------------------------------------------------------------------- /kokki/cookbooks/supervisor/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Process monitoring" 3 | __config__ = { 4 | "supervisor.config_path": dict( 5 | description = "Config file path for supervisor", 6 | default = "/etc/supervisor/supervisord.conf", 7 | ), 8 | "supervisor.socket_path": dict( 9 | description = "Unix socket path", 10 | default = "/var/run/supervisor.sock", 11 | ), 12 | "supervisor.custom_config_path": dict( 13 | description = "Path to custom supervisor config files", 14 | default = "/etc/supervisor/conf.d", 15 | ), 16 | "supervisor.binary_path": dict( 17 | description = "Path to the supervisor binaries", 18 | default = "/usr/bin", 19 | ), 20 | "supervisor.pidfile": dict( 21 | description = "Path to the supervisor pid file", 22 | default = "/var/run/supervisord.pid", 23 | ), 24 | "supervisor.logfile": dict( 25 | description = "Path to the supervisor log file", 26 | default = "/var/log/supervisord.log", 27 | ), 28 | "supervisor.childlogdir": dict( 29 | description = "Path where to place child log files", 30 | default = "/var/log/supervisor", 31 | ), 32 | } 33 | 34 | # if env.system.platform == "ubuntu": 35 | # env.supervisor.binary_path = "/usr/local/bin" 36 | # else: 37 | # env.supervisor.binary_path = "/usr/bin" 38 | -------------------------------------------------------------------------------- /kokki/cookbooks/supervisor/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from kokki import Package, File, Directory, Service, Template, Link 4 | 5 | # env.include_recipe("monit") 6 | 7 | if env.system.platform == "ubuntu": 8 | Package("supervisor") 9 | else: 10 | Package("supervisor", 11 | provider = "kokki.providers.package.easy_install.EasyInstallProvider") 12 | Directory(os.path.dirname(env.config.supervisor.config_path), 13 | action = "create") 14 | Directory(env.config.supervisor.custom_config_path, 15 | action = "create") 16 | Directory(os.path.dirname(env.config.supervisor.pidfile), 17 | action = "create") 18 | Directory(os.path.dirname(env.config.supervisor.logfile), 19 | action = "create") 20 | Directory(env.config.supervisor.childlogdir, 21 | action = "create") 22 | if env.config.supervisor.config_path != "/etc/supervisord.conf": 23 | Link("/etc/supervisord.conf", 24 | to = env.config.supervisor.config_path) 25 | 26 | File("supervisord.conf", 27 | path = env.config.supervisor.config_path, 28 | content = Template("supervisor/supervisord.conf.j2")) 29 | 30 | Directory("supervisor.d", 31 | path = env.config.supervisor.custom_config_path) 32 | 33 | supervisorctl = os.path.join(env.config.supervisor.binary_path, "supervisorctl") 34 | Service("supervisor", 35 | restart_command = "%s reload" % supervisorctl, 36 | reload_command = "%s update" % supervisorctl, 37 | subscribes = [("reload", env.resources["File"]["supervisord.conf"])]) 38 | 39 | #env.cookbooks.monit.rc("supervisord", 40 | # content = Template("supervisor/monit.conf.j2")) 41 | 42 | #env.cookbooks.monit.MonitService("supervisord", 43 | # subscribes = [("restart", env.resources["File"]["supervisord.conf"])]) 44 | -------------------------------------------------------------------------------- /kokki/cookbooks/supervisor/templates/monit.conf.j2: -------------------------------------------------------------------------------- 1 | check process supervisord with pidfile {{ env.config.supervisor.pidfile }} 2 | start program = "{{ env.config.supervisor.binary_path }}/supervisord -c {{ env.config.supervisor.config_path }}/supervisord.conf" 3 | stop program = "/usr/bin/killall supervisord" 4 | if 5 restarts within 5 cycles then timeout 5 | -------------------------------------------------------------------------------- /kokki/cookbooks/supervisor/templates/supervisord.conf.j2: -------------------------------------------------------------------------------- 1 | [unix_http_server] 2 | file={{ env.config.supervisor.socket_path }} ; (the path to the socket file) 3 | chmod=0700 4 | 5 | [supervisord] 6 | logfile={{ env.config.supervisor.logfile }} ; (main log file;default $CWD/supervisord.log) 7 | logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) 8 | logfile_backups=10 ; (num of main logfile rotation backups;default 10) 9 | loglevel=info ; (log level;default info; others: debug,warn,trace) 10 | pidfile={{ env.config.supervisor.pidfile }} ; (supervisord pidfile;default supervisord.pid) 11 | nodaemon=false ; (start in foreground if true;default false) 12 | minfds=1024 ; (min. avail startup file descriptors;default 1024) 13 | minprocs=200 ; (min. avail process descriptors;default 200) 14 | childlogdir={{ env.config.supervisor.childlogdir }} 15 | 16 | [rpcinterface:supervisor] 17 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 18 | 19 | [supervisorctl] 20 | serverurl=unix://{{ env.config.supervisor.socket_path }} ; use a unix:// URL for a unix socket 21 | 22 | [include] 23 | files = {{ env.config.supervisor.custom_config_path }}/*.conf 24 | -------------------------------------------------------------------------------- /kokki/cookbooks/users/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Manage user accounts and sysadmins" 3 | __config__ = { 4 | "users": dict( 5 | description = "Disctionary of sysadmins with username as the key and value as a dictionary of (id,sshkey_id,sshkey_type,sshkey)", 6 | default = {}, 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /kokki/cookbooks/users/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | import os.path 3 | from kokki import Group, User, Directory, File 4 | 5 | env.include_recipe("ssh") 6 | 7 | Group("sysadmin", 8 | gid = 2300) 9 | 10 | for username, user in env.config.users.items(): 11 | home = "/home/%s" % username 12 | 13 | User(username, 14 | uid = user['id'], 15 | home = home, 16 | groups = user.get('groups', []), 17 | password = user.get('password')) 18 | 19 | Directory(env.cookbooks.ssh.ssh_path_for_user(username), 20 | owner = username, 21 | group = username, 22 | mode = 0700) 23 | 24 | if user.get('sshkey'): 25 | env.cookbooks.ssh.SSHAuthorizedKey("%s-%s" % (username, user['sshkey_id']), 26 | user = username, 27 | keytype = user['sshkey_type'], 28 | key = user['sshkey']) 29 | File(os.path.join(env.cookbooks.ssh.ssh_path_for_user(username), "authorized_keys"), 30 | owner = username, 31 | group = username, 32 | mode = 0600) 33 | -------------------------------------------------------------------------------- /kokki/cookbooks/zookeeper/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __description__ = "Zookeeper" 3 | __config__ = {} 4 | -------------------------------------------------------------------------------- /kokki/cookbooks/zookeeper/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package 3 | 4 | env.include_recipe("cloudera") 5 | 6 | Package("hadoop-zookeeper") 7 | -------------------------------------------------------------------------------- /kokki/cookbooks/zookeeper/recipes/server.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import Package 3 | 4 | env.include_recipe("zookeeper") 5 | 6 | Package("hadoop-zookeeper-server") 7 | -------------------------------------------------------------------------------- /kokki/exceptions.py: -------------------------------------------------------------------------------- 1 | 2 | class Fail(Exception): 3 | pass 4 | 5 | class InvalidArgument(Fail): 6 | pass 7 | -------------------------------------------------------------------------------- /kokki/providers/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ["Provider", "find_provider"] 3 | 4 | import logging 5 | from kokki.exceptions import Fail 6 | 7 | class Provider(object): 8 | def __init__(self, resource): 9 | self.log = logging.getLogger("kokki.provider") 10 | self.resource = resource 11 | 12 | def action_nothing(self): 13 | pass 14 | 15 | def __repr__(self): 16 | return self.__unicode__() 17 | 18 | def __unicode__(self): 19 | return u"%s[%s]" % (self.__class__.__name__, self.resource) 20 | 21 | PROVIDERS = dict( 22 | debian = dict( 23 | Package = "kokki.providers.package.apt.DebianAptProvider", 24 | Service = "kokki.providers.service.debian.DebianServiceProvider", 25 | ), 26 | ubuntu = dict( 27 | Package = "kokki.providers.package.apt.DebianAptProvider", 28 | Service = "kokki.providers.service.debian.DebianServiceProvider", 29 | ), 30 | redhat = dict( 31 | Service = "kokki.providers.service.redhat.RedhatServiceProvider", 32 | Package = "kokki.providers.package.yumrpm.YumProvider", 33 | ), 34 | centos = dict( 35 | Service = "kokki.providers.service.redhat.RedhatServiceProvider", 36 | Package = "kokki.providers.package.yumrpm.YumProvider", 37 | ), 38 | fedora = dict( 39 | Service = "kokki.providers.service.redhat.RedhatServiceProvider", 40 | Package = "kokki.providers.package.yumrpm.YumProvider", 41 | ), 42 | amazon = dict( 43 | Service = "kokki.providers.service.redhat.RedhatServiceProvider", 44 | Package = "kokki.providers.package.yumrpm.YumProvider", 45 | ), 46 | gentoo = dict( 47 | Package = "kokki.providers.package.emerge.GentooEmergeProvider", 48 | Service = "kokki.providers.service.gentoo.GentooServiceProvider", 49 | ), 50 | default = dict( 51 | File = "kokki.providers.system.FileProvider", 52 | Directory = "kokki.providers.system.DirectoryProvider", 53 | Link = "kokki.providers.system.LinkProvider", 54 | Execute = "kokki.providers.system.ExecuteProvider", 55 | Script = "kokki.providers.system.ScriptProvider", 56 | Mount = "kokki.providers.mount.MountProvider", 57 | User = "kokki.providers.accounts.UserProvider", 58 | Group = "kokki.providers.accounts.GroupProvider", 59 | ), 60 | ) 61 | 62 | def find_provider(env, resource, class_path=None): 63 | if not class_path: 64 | try: 65 | class_path = PROVIDERS[env.system.platform][resource] 66 | except KeyError: 67 | class_path = PROVIDERS["default"][resource] 68 | 69 | if class_path.startswith('*'): 70 | cookbook, classname = class_path[1:].split('.') 71 | return getattr(env.cookbooks[cookbook], classname) 72 | 73 | try: 74 | mod_path, class_name = class_path.rsplit('.', 1) 75 | except ValueError: 76 | raise Fail("Unable to find provider for %s as %s" % (resource, class_path)) 77 | mod = __import__(mod_path, {}, {}, [class_name]) 78 | return getattr(mod, class_name) 79 | -------------------------------------------------------------------------------- /kokki/providers/accounts.py: -------------------------------------------------------------------------------- 1 | 2 | from __future__ import with_statement 3 | 4 | import grp 5 | import pwd 6 | import subprocess 7 | from kokki.providers import Provider 8 | 9 | class UserProvider(Provider): 10 | def action_create(self): 11 | if not self.user: 12 | command = ['useradd', "-m"] 13 | 14 | useradd_options = dict( 15 | comment = "-c", 16 | gid = "-g", 17 | uid = "-u", 18 | shell = "-s", 19 | password = "-p", 20 | home = "-d", 21 | ) 22 | 23 | if self.resource.system: 24 | command.append("--system") 25 | 26 | if self.resource.groups: 27 | command += ["-G", ",".join(self.resource.groups)] 28 | 29 | for option_name, option_flag in useradd_options.items(): 30 | option_value = getattr(self.resource, option_name) 31 | if option_flag and option_value: 32 | command += [option_flag, str(option_value)] 33 | 34 | command.append(self.resource.username) 35 | 36 | subprocess.check_call(command) 37 | self.resource.updated() 38 | self.log.info("Added user %s" % self.resource) 39 | 40 | def action_remove(self): 41 | if self.user: 42 | command = ['userdel', self.resource.username] 43 | subprocess.check_call(command) 44 | self.resource.updated() 45 | self.log.info("Removed user %s" % self.resource) 46 | 47 | @property 48 | def user(self): 49 | try: 50 | return pwd.getpwnam(self.resource.username) 51 | except KeyError: 52 | return None 53 | 54 | class GroupProvider(Provider): 55 | def action_create(self): 56 | group = self.group 57 | if not group: 58 | command = ['groupadd'] 59 | 60 | groupadd_options = dict( 61 | gid = "-g", 62 | password = "-p", 63 | ) 64 | 65 | for option_name, option_flag in groupadd_options.items(): 66 | option_value = getattr(self.resource, option_name) 67 | if option_flag and option_value: 68 | command += [option_flag, str(option_value)] 69 | 70 | command.append(self.resource.group_name) 71 | 72 | subprocess.check_call(command) 73 | self.resource.updated() 74 | self.log.info("Added group %s" % self.resource) 75 | 76 | group = self.group 77 | 78 | # if self.resource.members is not None: 79 | # current_members = set(group.gr_mem) 80 | # members = set(self.resource.members) 81 | # for u in current_members - members: 82 | # pass 83 | 84 | def action_remove(self): 85 | if self.user: 86 | command = ['groupdel', self.resource.group_name] 87 | subprocess.check_call(command) 88 | self.resource.updated() 89 | self.log.info("Removed group %s" % self.resource) 90 | 91 | @property 92 | def group(self): 93 | try: 94 | return grp.getgrnam(self.resource.group_name) 95 | except KeyError: 96 | return None 97 | -------------------------------------------------------------------------------- /kokki/providers/package/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki.base import Fail 3 | from kokki.providers import Provider 4 | 5 | class PackageProvider(Provider): 6 | def __init__(self, *args, **kwargs): 7 | super(PackageProvider, self).__init__(*args, **kwargs) 8 | self.get_current_status() 9 | 10 | def get_current_status(self): 11 | raise NotImplementedError() 12 | 13 | def install_package(self, name, version): 14 | raise NotImplementedError() 15 | 16 | def remove_package(self, name): 17 | raise NotImplementedError() 18 | 19 | def purge_package(self, name): 20 | raise NotImplementedError() 21 | 22 | def upgrade_package(self, name, version): 23 | raise NotImplementedError() 24 | 25 | def action_install(self): 26 | if self.resource.version != None and self.resource.version != self.current_version: 27 | install_version = self.resource.version 28 | elif self.current_version is None: 29 | install_version = self.candidate_version 30 | else: 31 | return 32 | 33 | if not install_version: 34 | raise Fail("No version specified, and no candidate version available for package %s." % self.resource.package_name) 35 | 36 | self.log.info("Install %s version %s (resource %s, current %s, candidate %s) location %s", 37 | self.resource.package_name, install_version, self.resource.version, 38 | self.current_version, self.candidate_version, self.resource.location) 39 | 40 | status = self.install_package(self.resource.location, install_version) 41 | if status: 42 | self.resource.updated() 43 | 44 | def action_upgrade(self): 45 | if self.current_version != self.candidate_version: 46 | orig_version = self.current_version or "uninstalled" 47 | self.log.info("Upgrading %s from version %s to %s", 48 | str(self.resource), orig_version, self.candidate_version) 49 | 50 | status = self.upgrade_package(self.resource.location, self.candidate_version) 51 | if status: 52 | self.resource.updated() 53 | 54 | def action_remove(self): 55 | if self.current_version: 56 | self.log.info("Remove %s version %s", self.resource.package_name, self.current_version) 57 | self.remove_package(self.resource.package_name) 58 | self.resource.updated() 59 | 60 | def action_purge(self): 61 | if self.current_version: 62 | self.log.info("Purging %s version %s", self.resource.package_name, self.current_version) 63 | self.purge_package(self.resource.package_name) 64 | self.resource.updated() 65 | -------------------------------------------------------------------------------- /kokki/providers/package/apt.py: -------------------------------------------------------------------------------- 1 | 2 | import glob 3 | import os 4 | import shutil 5 | import tempfile 6 | from subprocess import Popen, STDOUT, PIPE, check_call, CalledProcessError 7 | from kokki.base import Fail 8 | from kokki.providers.package import PackageProvider 9 | 10 | 11 | class DebianAptProvider(PackageProvider): 12 | def get_current_status(self): 13 | self.current_version = None 14 | self.candidate_version = None 15 | 16 | proc = Popen("apt-cache policy %s" % self.resource.package_name, shell=True, stdout=PIPE) 17 | out = proc.communicate()[0] 18 | for line in out.split("\n"): 19 | line = line.strip().split(':', 1) 20 | if len(line) != 2: 21 | continue 22 | 23 | ver = line[1].strip() 24 | if line[0] == "Installed": 25 | self.current_version = None if ver == '(none)' else ver 26 | self.log.debug("Current version of package %s is %s" % (self.resource.package_name, self.current_version)) 27 | elif line[0] == "Candidate": 28 | self.candidate_version = ver 29 | 30 | if self.candidate_version == "(none)": 31 | raise Fail("APT does not provide a version of package %s" % self.resource.package_name) 32 | 33 | def install_package(self, name, version): 34 | if self.resource.build_vars: 35 | self._install_package_source(name, version) 36 | else: 37 | self._install_package_default(name, version) 38 | 39 | def _install_package_default(self, name, version): 40 | return 0 == check_call("DEBIAN_FRONTEND=noninteractive apt-get -q -y install %s=%s" % (name, version), 41 | shell=True, stdout=PIPE, stderr=STDOUT) 42 | 43 | def _install_package_source(self, name, version): 44 | build_vars = " ".join(self.resource.build_vars) 45 | run_check_call = lambda s, **kw: check_call(s, shell = True, stdout=PIPE, stderr=STDOUT, **kw) 46 | pkgdir = tempfile.mkdtemp(suffix = name) 47 | 48 | try: 49 | run_check_call("DEBIAN_FRONTEND=noninteractive apt-get -q -y install fakeroot") 50 | run_check_call("DEBIAN_FRONTEND=noninteractive apt-get -q -y build-dep %s=%s" % (name, version)) 51 | run_check_call("DEBIAN_FRONTEND=noninteractive apt-get -q -y source %s=%s" % (name, version), cwd = pkgdir) 52 | 53 | try: 54 | builddir = [p for p in glob.iglob("%s/%s*" % (pkgdir, name)) if os.path.isdir(p)][0] 55 | except IndexError: 56 | raise Fail("Couldn't install %s from source: apt-get source created an unfamiliar directory structure." % name) 57 | 58 | run_check_call("%s fakeroot debian/rules binary > /dev/null" % build_vars, cwd = builddir) 59 | 60 | # NOTE: I can't figure out why this call returns non-zero sometimes, though everything seems to work. 61 | # Just ignoring checking for now. 62 | try: 63 | run_check_call("dpkg -i *.deb > /dev/null", cwd = pkgdir) 64 | except CalledProcessError: 65 | pass 66 | finally: 67 | shutil.rmtree(pkgdir) 68 | 69 | return True 70 | 71 | def remove_package(self, name): 72 | return 0 == check_call("DEBIAN_FRONTEND=noninteractive apt-get -q -y remove %s" % name, 73 | shell=True, stdout=PIPE, stderr=STDOUT) 74 | 75 | def purge_package(self, name): 76 | return 0 == check_call("DEBIAN_FRONTEND=noninteractive apt-get -q -y purge %s" % name, 77 | shell=True, stdout=PIPE, stderr=STDOUT) 78 | 79 | def upgrade_package(self, name, version): 80 | return self.install_package(name, version) 81 | -------------------------------------------------------------------------------- /kokki/providers/package/easy_install.py: -------------------------------------------------------------------------------- 1 | 2 | import re 3 | from subprocess import check_call, Popen, PIPE, STDOUT 4 | from kokki.providers.package import PackageProvider 5 | 6 | VERSION_RE = re.compile(r'\S\S(.*)\/(.*)-(.*)-py(.*).egg\S') 7 | BEST_MATCH_RE = re.compile(r'Best match: (.*) (.*)\n') 8 | 9 | class EasyInstallProvider(PackageProvider): 10 | def get_current_status(self): 11 | proc = Popen(["python", "-c", "import %s; print %s.__path__" % (self.resource.package_name, self.resource.package_name)], stdout=PIPE, stderr=STDOUT) 12 | path = proc.communicate()[0] 13 | if proc.wait() != 0: 14 | self.current_version = None 15 | else: 16 | match = VERSION_RE.search(path) 17 | if match: 18 | self.current_version = match.group(3) 19 | else: 20 | self.current_version = "unknown" 21 | 22 | @property 23 | def candidate_version(self): 24 | if not hasattr(self, '_candidate_version'): 25 | proc = Popen([self.easy_install_binary_path, "-n", self.resource.package_name], stdout=PIPE, stderr=STDOUT) 26 | out = proc.communicate()[0] 27 | res = proc.wait() 28 | if res != 0: 29 | self.log.warning("easy_install check returned a non-zero result (%d) %s" % (res, self.resource)) 30 | # self._candidate_version = None 31 | # else: 32 | match = BEST_MATCH_RE.search(out) 33 | if not match: 34 | self._candidate_version = None 35 | else: 36 | self._candidate_version = match.group(2) 37 | return self._candidate_version 38 | 39 | @property 40 | def easy_install_binary_path(self): 41 | return "easy_install" 42 | 43 | def install_package(self, name, version): 44 | check_call([self.easy_install_binary_path, "-U", "%s==%s" % (name, version)], stdout=PIPE, stderr=STDOUT) 45 | 46 | def upgrade_package(self, name, version): 47 | self.install_package(name, version) 48 | 49 | def remove_package(self, name): 50 | check_call([self.easy_install_binary_path, "-m", name]) 51 | 52 | def purge_package(self, name): 53 | self.remove_package(name) 54 | -------------------------------------------------------------------------------- /kokki/providers/package/emerge.py: -------------------------------------------------------------------------------- 1 | 2 | from subprocess import Popen, STDOUT, PIPE, check_call 3 | from kokki.base import Fail 4 | from kokki.providers.package import PackageProvider 5 | 6 | class GentooEmergeProvider(PackageProvider): 7 | def get_current_status(self): 8 | self.current_version = None 9 | self.candidate_version = None 10 | 11 | proc = Popen("qlist --installed --exact --verbose --nocolor %s" 12 | % self.resource.package_name, shell=True, stdout=PIPE) 13 | out = proc.communicate()[0] 14 | for line in out.split("\n"): 15 | line = line.split('/', 1) 16 | if len(line) != 2: 17 | continue 18 | _category, nameversion = line 19 | _name, version = nameversion.split('-', 1) 20 | self.current_version = version 21 | self.log.debug("Current version of package %s is %s", 22 | self.resource.package_name, self.current_version) 23 | 24 | proc = Popen("emerge --pretend --quiet --color n %s" % self.resource.package_name, shell=True, stdout=PIPE) 25 | out = proc.communicate()[0] 26 | for line in out.split("\n"): 27 | line = line.strip(' [').split(']', 1) 28 | if len(line) != 2: 29 | continue 30 | 31 | # kind, flag = line[0].split() 32 | _category, nameversion = line[1].split('/', 1) 33 | _name, version = nameversion.split('-', 1) 34 | self.candidate_version = version 35 | self.log.debug("Candidate version of package %s is %s", 36 | self.resource.package_name, self.candidate_version) 37 | 38 | if self.candidate_version is None: 39 | raise Fail("emerge does not provide a version of package %s" % self.resource.package_name) 40 | 41 | def install_package(self, name, version): 42 | return 0 == check_call("emerge --color n =%s-%s" % (name, version), 43 | shell=True, stdout=PIPE, stderr=STDOUT) 44 | 45 | def upgrade_package(self, name, version): 46 | return self.install_package(name, version) 47 | -------------------------------------------------------------------------------- /kokki/providers/package/yumrpm.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki.providers.package import PackageProvider 3 | import yum 4 | 5 | 6 | class DummyCallback(object): 7 | def event(self, state, data=None): 8 | pass 9 | 10 | 11 | class YumProvider(PackageProvider): 12 | def get_current_status(self): 13 | self.candidate_version = None 14 | self.current_version = None 15 | yb = yum.YumBase() 16 | yb.doConfigSetup() 17 | yb.doTsSetup() 18 | yb.doRpmDBSetup() 19 | for pkg in yb.rpmdb.returnPackages(): 20 | if pkg.name == self.resource.package_name: 21 | self.current_version = pkg.version 22 | self.log.debug("Current version of %s is %s" % (self.resource.package_name, self.current_version)) 23 | searchlist = ['name', 'version'] 24 | args = [self.resource.package_name] 25 | matching = yb.searchPackages(searchlist, args) 26 | for po in matching: 27 | if po.name == self.resource.package_name: 28 | self.candidate_version = po.version 29 | self.log.debug("Candidate version of %s is %s" % (self.resource.package_name, self.current_version)) 30 | 31 | def install_package(self, name, version): 32 | yb = yum.YumBase() 33 | yb.doGenericSetup() 34 | yb.doRepoSetup() 35 | #TODO: Handle locks not being available 36 | yb.doLock() 37 | yb.install(pattern=name) 38 | yb.buildTransaction() 39 | #yb.conf.setattr('assumeyes',True) 40 | yb.processTransaction(callback=DummyCallback()) 41 | yb.closeRpmDB() 42 | yb.doUnlock() 43 | 44 | def upgrade_package(self, name, version): 45 | return self.install_package(name, version) 46 | -------------------------------------------------------------------------------- /kokki/providers/service/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | from kokki.base import Fail 5 | from kokki.providers import Provider 6 | 7 | class ServiceProvider(Provider): 8 | def action_start(self): 9 | if not self.status(): 10 | self._exec_cmd("start", 0) 11 | self.resource.updated() 12 | 13 | def action_stop(self): 14 | if self.status(): 15 | self._exec_cmd("stop", 0) 16 | self.resource.updated() 17 | 18 | def action_restart(self): 19 | if not self.status(): 20 | self._exec_cmd("start", 0) 21 | self.resource.updated() 22 | else: 23 | self._exec_cmd("restart", 0) 24 | self.resource.updated() 25 | 26 | def action_reload(self): 27 | if not self.status(): 28 | self._exec_cmd("start", 0) 29 | self.resource.updated() 30 | else: 31 | self._exec_cmd("reload", 0) 32 | self.resource.updated() 33 | 34 | def status(self): 35 | return self._exec_cmd("status") == 0 36 | 37 | def _exec_cmd(self, command, expect=None): 38 | if command != "status": 39 | self.log.info("%s command '%s'" % (self.resource, command)) 40 | 41 | custom_cmd = getattr(self.resource, "%s_command" % command, None) 42 | if custom_cmd: 43 | self.log.debug("%s executing '%s'" % (self.resource, custom_cmd)) 44 | if hasattr(custom_cmd, "__call__"): 45 | if custom_cmd(): 46 | ret = 0 47 | else: 48 | ret = 1 49 | else: 50 | ret = subprocess.call(custom_cmd, shell=True, 51 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 52 | else: 53 | ret = self._init_cmd(command) 54 | 55 | if expect is not None and expect != ret: 56 | raise Fail("%r command %s for service %s failed" % (self, command, self.resource.service_name)) 57 | return ret 58 | 59 | def _init_cmd(self, command): 60 | if self._upstart: 61 | if command == "status": 62 | proc = subprocess.Popen(["/sbin/"+command, self.resource.service_name], 63 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 64 | out = proc.communicate()[0] 65 | _proc, state = out.strip().split(' ', 1) 66 | ret = 0 if state != "stop/waiting" else 1 67 | else: 68 | ret = subprocess.call(["/sbin/"+command, self.resource.service_name], 69 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 70 | else: 71 | ret = subprocess.call(["/etc/init.d/%s" % self.resource.service_name, command], 72 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 73 | return ret 74 | 75 | @property 76 | def _upstart(self): 77 | try: 78 | return self.__upstart 79 | except AttributeError: 80 | self.__upstart = os.path.exists("/sbin/start") \ 81 | and os.path.exists("/etc/init/%s.conf" % self.resource.service_name) 82 | return self.__upstart 83 | -------------------------------------------------------------------------------- /kokki/providers/service/debian.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ["DebianServiceProvider"] 3 | 4 | from kokki.providers.service import ServiceProvider 5 | 6 | class DebianServiceProvider(ServiceProvider): 7 | def enable_runlevel(self, runlevel): 8 | pass 9 | -------------------------------------------------------------------------------- /kokki/providers/service/gentoo.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ["GentooServiceProvider"] 3 | 4 | from kokki.providers.service import ServiceProvider 5 | 6 | class GentooServiceProvider(ServiceProvider): 7 | def enable_runlevel(self, runlevel): 8 | pass 9 | -------------------------------------------------------------------------------- /kokki/providers/service/redhat.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ["RedhatServiceProvider"] 3 | 4 | from kokki.providers.service import ServiceProvider 5 | 6 | class RedhatServiceProvider(ServiceProvider): 7 | def enable_runlevel(self, runlevel): 8 | pass 9 | -------------------------------------------------------------------------------- /kokki/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki.resources.accounts import * 3 | from kokki.resources.packaging import * 4 | from kokki.resources.service import * 5 | from kokki.resources.system import * 6 | -------------------------------------------------------------------------------- /kokki/resources/accounts.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ["Group", "User"] 3 | 4 | from kokki.base import Resource, ForcedListArgument, ResourceArgument, BooleanArgument 5 | 6 | class Group(Resource): 7 | action = ForcedListArgument(default="create") 8 | group_name = ResourceArgument(default=lambda obj:obj.name) 9 | gid = ResourceArgument() 10 | members = ForcedListArgument() 11 | password = ResourceArgument() 12 | # append = BooleanArgument(default=False) # NOT SUPPORTED 13 | 14 | actions = Resource.actions + ["create", "remove", "modify", "manage", "lock", "unlock"] 15 | 16 | class User(Resource): 17 | action = ForcedListArgument(default="create") 18 | username = ResourceArgument(default=lambda obj:obj.name) 19 | comment = ResourceArgument() 20 | uid = ResourceArgument() 21 | gid = ResourceArgument() 22 | groups = ForcedListArgument() # supplementary groups 23 | home = ResourceArgument() 24 | shell = ResourceArgument(default="/bin/bash") 25 | password = ResourceArgument() 26 | system = BooleanArgument(default=False) 27 | 28 | actions = Resource.actions + ["create", "remove", "modify", "manage", "lock", "unlock"] 29 | -------------------------------------------------------------------------------- /kokki/resources/packaging.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ["Package"] 3 | 4 | from kokki.base import Resource, ForcedListArgument, ResourceArgument 5 | 6 | class Package(Resource): 7 | action = ForcedListArgument(default="install") 8 | package_name = ResourceArgument(default=lambda obj:obj.name) 9 | location = ResourceArgument(default=lambda obj:obj.package_name) 10 | version = ResourceArgument() 11 | actions = ["install", "upgrade", "remove", "purge"] 12 | build_vars = ForcedListArgument(default=[]) 13 | -------------------------------------------------------------------------------- /kokki/resources/service.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ["Service"] 3 | 4 | from kokki.base import Resource, ResourceArgument, BooleanArgument 5 | 6 | class Service(Resource): 7 | service_name = ResourceArgument(default=lambda obj:obj.name) 8 | enabled = ResourceArgument() 9 | running = ResourceArgument() 10 | pattern = ResourceArgument() 11 | start_command = ResourceArgument() 12 | stop_command = ResourceArgument() 13 | restart_command = ResourceArgument() 14 | reload_command = ResourceArgument() 15 | status_command = ResourceArgument() 16 | supports_restart = BooleanArgument(default=lambda obj:bool(obj.restart_command)) 17 | supports_reload = BooleanArgument(default=lambda obj:bool(obj.reload_command)) 18 | supports_status = BooleanArgument(default=lambda obj:bool(obj.status_command)) 19 | 20 | actions = ["nothing", "start", "stop", "restart", "reload"] 21 | -------------------------------------------------------------------------------- /kokki/resources/system.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ["File", "Directory", "Link", "Execute", "Script", "Mount"] 3 | 4 | from kokki.base import Resource, ForcedListArgument, ResourceArgument, BooleanArgument 5 | 6 | class File(Resource): 7 | action = ForcedListArgument(default="create") 8 | path = ResourceArgument(default=lambda obj:obj.name) 9 | backup = ResourceArgument() 10 | mode = ResourceArgument() 11 | owner = ResourceArgument() 12 | group = ResourceArgument() 13 | content = ResourceArgument() 14 | 15 | actions = Resource.actions + ["create", "delete", "touch"] 16 | 17 | class Directory(Resource): 18 | action = ForcedListArgument(default="create") 19 | path = ResourceArgument(default=lambda obj:obj.name) 20 | mode = ResourceArgument() 21 | owner = ResourceArgument() 22 | group = ResourceArgument() 23 | recursive = BooleanArgument(default=False) 24 | 25 | actions = Resource.actions + ["create", "delete"] 26 | 27 | class Link(Resource): 28 | action = ForcedListArgument(default="create") 29 | path = ResourceArgument(default=lambda obj:obj.name) 30 | to = ResourceArgument(required=True) 31 | hard = BooleanArgument(default=False) 32 | 33 | actions = Resource.actions + ["create", "delete"] 34 | 35 | class Execute(Resource): 36 | action = ForcedListArgument(default="run") 37 | command = ResourceArgument(default=lambda obj:obj.name) 38 | creates = ResourceArgument() 39 | cwd = ResourceArgument() 40 | environment = ResourceArgument() 41 | user = ResourceArgument() 42 | group = ResourceArgument() 43 | returns = ForcedListArgument(default=0) 44 | timeout = ResourceArgument() 45 | 46 | actions = Resource.actions + ["run"] 47 | 48 | class Script(Resource): 49 | action = ForcedListArgument(default="run") 50 | code = ResourceArgument(required=True) 51 | cwd = ResourceArgument() 52 | environment = ResourceArgument() 53 | interpreter = ResourceArgument(default="/bin/bash") 54 | user = ResourceArgument() 55 | group = ResourceArgument() 56 | 57 | actions = Resource.actions + ["run"] 58 | 59 | class Mount(Resource): 60 | action = ForcedListArgument(default="mount") 61 | mount_point = ResourceArgument(default=lambda obj:obj.name) 62 | device = ResourceArgument() 63 | fstype = ResourceArgument() 64 | options = ResourceArgument(default=["defaults"]) 65 | dump = ResourceArgument(default=0) 66 | passno = ResourceArgument(default=2) 67 | 68 | actions = Resource.actions + ["mount", "umount", "remount", "enable", "disable"] 69 | -------------------------------------------------------------------------------- /kokki/utils.py: -------------------------------------------------------------------------------- 1 | 2 | class AttributeDictionary(object): 3 | def __init__(self, *args, **kwargs): 4 | d = kwargs 5 | if args: 6 | d = args[0] 7 | super(AttributeDictionary, self).__setattr__("_dict", d) 8 | 9 | def __setattr__(self, name, value): 10 | self[name] = value 11 | 12 | def __getattr__(self, name): 13 | if name in self.__dict__: 14 | return self.__dict__[name] 15 | try: 16 | return self[name] 17 | except KeyError: 18 | raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name)) 19 | 20 | def __setitem__(self, name, value): 21 | self._dict[name] = self._convert_value(value) 22 | 23 | def __getitem__(self, name): 24 | return self._convert_value(self._dict[name]) 25 | 26 | def _convert_value(self, value): 27 | if isinstance(value, dict) and not isinstance(value, AttributeDictionary): 28 | return AttributeDictionary(value) 29 | return value 30 | 31 | def copy(self): 32 | return self.__class__(self._dict.copy()) 33 | 34 | def update(self, *args, **kwargs): 35 | self._dict.update(*args, **kwargs) 36 | 37 | def items(self): 38 | return self._dict.items() 39 | 40 | def values(self): 41 | return self._dict.values() 42 | 43 | def keys(self): 44 | return self._dict.keys() 45 | 46 | def pop(self, *args, **kwargs): 47 | return self._dict.pop(*args, **kwargs) 48 | 49 | def get(self, *args, **kwargs): 50 | return self._dict.get(*args, **kwargs) 51 | 52 | def __repr__(self): 53 | return self._dict.__repr__() 54 | 55 | def __unicode__(self): 56 | return self._dict.__unicode__() 57 | 58 | def __str__(self): 59 | return self._dict.__str__() 60 | 61 | def __iter__(self): 62 | return self._dict.__iter__() 63 | 64 | def __getstate__(self): 65 | return self._dict 66 | 67 | def __setstate__(self, state): 68 | super(AttributeDictionary, self).__setattr__("_dict", state) 69 | -------------------------------------------------------------------------------- /kokki/version.py: -------------------------------------------------------------------------------- 1 | VERSION = "0.4.1" 2 | 3 | # August 9, 2010 -- ss -- made a long version to use in generated files 4 | # TODO: make a switch to control generation of "long banners" in generated 5 | # files. I like to have this info when i go back to a system after a 6 | # while but don't want to have to hand-code globs of stuff into every 7 | # template. The switch will control how much information is automatically 8 | # inserted at the top of every file. 9 | LONG_VERSION = "Kokki version %s : http://github.com/samuel/kokki" % VERSION 10 | 11 | def version(): 12 | return VERSION 13 | 14 | def long_version(): 15 | return LONG_VERSION 16 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | import os 6 | execfile(os.path.join('kokki', 'version.py')) 7 | 8 | setup( 9 | name = 'kokki', 10 | version = VERSION, 11 | description = 'Kokki is a system configuration management framework influenced by Chef', 12 | author = 'Samuel Stauffer', 13 | author_email = 'samuel@descolada.com', 14 | url = 'http://samuelks.com/kokki/', 15 | packages = find_packages(), 16 | test_suite = "tests", 17 | entry_points = { 18 | "console_scripts": [ 19 | "kokki = kokki.command:main", 20 | ], 21 | }, 22 | classifiers = [ 23 | 'Intended Audience :: Developers', 24 | 'License :: OSI Approved :: BSD License', 25 | 'Operating System :: OS Independent', 26 | 'Programming Language :: Python', 27 | 'Topic :: Software Development :: Libraries :: Python Modules', 28 | ], 29 | install_requires = [ 30 | 'jinja2', 31 | ], 32 | ) 33 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import shutil 5 | import tempfile 6 | import unittest 7 | from kokki import * 8 | 9 | class TestKitchen(unittest.TestCase): 10 | def setUp(self): 11 | self.kit = Kitchen() 12 | self.kit.add_cookbook_path("kokki.cookbooks", os.path.join(os.path.dirname(os.path.abspath(__file__)), "cookbooks")) 13 | 14 | def testUnknownConfig(self): 15 | self.failUnlessRaises(AttributeError, lambda:self.kit.config.test.config1) 16 | 17 | def testDefaultConfig(self): 18 | self.kit.include_recipe("test") 19 | self.kit.run() 20 | 21 | self.failUnlessEqual("fu", self.kit.config.test.config1) 22 | self.failUnlessEqual("manchu", self.kit.config.test.config2) 23 | self.failUnlessEqual("manchu", self.kit._test) 24 | 25 | def testOverrideConfig(self): 26 | self.kit.update_config({"test.config1": "bar"}) 27 | self.kit.include_recipe("test") 28 | self.kit.run() 29 | 30 | self.failUnlessEqual("bar", self.kit.config.test.config1) 31 | self.failUnlessEqual("manchu", self.kit.config.test.config2) 32 | self.failUnlessEqual("manchu", self.kit._test) 33 | 34 | class ResourceTestBase(unittest.TestCase): 35 | def setUp(self): 36 | self.temp_path = tempfile.mkdtemp(suffix="kokki-tests") 37 | 38 | def tearDown(self): 39 | shutil.rmtree(self.temp_path) 40 | 41 | class TestExecute(ResourceTestBase): 42 | def testOnlyIf(self): 43 | with Environment() as env: 44 | temp_file = os.path.join(self.temp_path, "exists") 45 | Execute("touch %s-lamba-false" % temp_file, 46 | only_if = lambda:False) 47 | Execute("touch %s-cmd-false" % temp_file, 48 | only_if = "false") 49 | Execute("touch %s-lambda-true" % temp_file, 50 | only_if = lambda:True) 51 | Execute("touch %s-cmd-true" % temp_file, 52 | only_if = "true") 53 | env.run() 54 | self.failIf(os.path.exists(temp_file+"-lambda-false")) 55 | self.failIf(os.path.exists(temp_file+"-cmd-false")) 56 | self.failUnless(os.path.exists(temp_file+"-lambda-true")) 57 | self.failUnless(os.path.exists(temp_file+"-cmd-true")) 58 | 59 | if __name__ == '__main__': 60 | unittest.main() 61 | -------------------------------------------------------------------------------- /tests/cookbooks/test/files/static.txt: -------------------------------------------------------------------------------- 1 | fewfewfwe -------------------------------------------------------------------------------- /tests/cookbooks/test/libraries/blah.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import * 3 | 4 | def tester(): 5 | print "FEWFEWFEW" 6 | -------------------------------------------------------------------------------- /tests/cookbooks/test/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | __config__ = { 3 | "test.config1": dict( 4 | default = "fu", 5 | ), 6 | "test.config2": dict( 7 | default = "manchu", 8 | ), 9 | } 10 | __description__ = "This is a test cookbooks" 11 | -------------------------------------------------------------------------------- /tests/cookbooks/test/recipes/default.py: -------------------------------------------------------------------------------- 1 | 2 | from kokki import * 3 | 4 | # File("/tmp/kokki-test", 5 | # content = StaticFile("test/static.txt")) 6 | # # content = Template("test/test.j2")) 7 | 8 | env._test = env.config.test.config2 9 | -------------------------------------------------------------------------------- /tests/cookbooks/test/templates/test.j2: -------------------------------------------------------------------------------- 1 | {{ env.config.test.config1 }} 2 | --------------------------------------------------------------------------------