summaryrefslogtreecommitdiff
path: root/puppet/modules
diff options
context:
space:
mode:
authorMicah <micah@leap.se>2016-07-12 16:45:54 -0400
committerMicah <micah@leap.se>2016-07-12 16:45:54 -0400
commitf2019755fd724fb1020cb2d97cdf82b751450ebc (patch)
tree1c2bd3a4f03b84795ea0ce0b7ccc0f28a2ecbadd /puppet/modules
parent81210aea5cf136194598e7a399ce307ecbe088f1 (diff)
git subrepo clone https://leap.se/git/puppet_couchdb puppet/modules/couchdb
subrepo: subdir: "puppet/modules/couchdb" merged: "76ff149" upstream: origin: "https://leap.se/git/puppet_couchdb" branch: "master" commit: "76ff149" git-subrepo: version: "0.3.0" origin: "https://github.com/ingydotnet/git-subrepo" commit: "1e79595" Change-Id: I9ccb1a9dfdaa083814ea395132c42a778052f59b
Diffstat (limited to 'puppet/modules')
-rw-r--r--puppet/modules/couchdb/.fixtures.yml6
-rw-r--r--puppet/modules/couchdb/.gitrepo11
-rw-r--r--puppet/modules/couchdb/Gemfile11
-rw-r--r--puppet/modules/couchdb/README.md32
-rw-r--r--puppet/modules/couchdb/Rakefile19
-rwxr-xr-xpuppet/modules/couchdb/files/Debian/couchdb160
-rw-r--r--puppet/modules/couchdb/files/couch-doc-diff17
-rw-r--r--puppet/modules/couchdb/files/couch-doc-update219
-rw-r--r--puppet/modules/couchdb/files/local.ini84
-rw-r--r--puppet/modules/couchdb/lib/facter/couchdb_pwhash_alg.rb43
-rw-r--r--puppet/modules/couchdb/lib/facter/couchdb_version.rb34
-rw-r--r--puppet/modules/couchdb/lib/puppet/parser/functions/couchdblookup.rb55
-rw-r--r--puppet/modules/couchdb/lib/puppet/parser/functions/pbkdf2.rb62
-rw-r--r--puppet/modules/couchdb/manifests/add_user.pp39
-rw-r--r--puppet/modules/couchdb/manifests/backup.pp51
-rw-r--r--puppet/modules/couchdb/manifests/base.pp124
-rw-r--r--puppet/modules/couchdb/manifests/bigcouch.pp51
-rw-r--r--puppet/modules/couchdb/manifests/bigcouch/add_node.pp8
-rw-r--r--puppet/modules/couchdb/manifests/bigcouch/debian.pp11
-rw-r--r--puppet/modules/couchdb/manifests/bigcouch/document.pp14
-rw-r--r--puppet/modules/couchdb/manifests/bigcouch/package/cloudant.pp35
-rw-r--r--puppet/modules/couchdb/manifests/create_db.pp21
-rw-r--r--puppet/modules/couchdb/manifests/debian.pp15
-rw-r--r--puppet/modules/couchdb/manifests/deploy_config.pp12
-rw-r--r--puppet/modules/couchdb/manifests/document.pp47
-rw-r--r--puppet/modules/couchdb/manifests/init.pp31
-rw-r--r--puppet/modules/couchdb/manifests/mirror_db.pp21
-rw-r--r--puppet/modules/couchdb/manifests/params.pp23
-rw-r--r--puppet/modules/couchdb/manifests/query.pp12
-rw-r--r--puppet/modules/couchdb/manifests/query/setup.pp10
-rw-r--r--puppet/modules/couchdb/manifests/redhat.pp1
-rw-r--r--puppet/modules/couchdb/manifests/ssl/deploy_cert.pp28
-rw-r--r--puppet/modules/couchdb/manifests/ssl/generate_cert.pp25
-rw-r--r--puppet/modules/couchdb/manifests/update.pp12
-rw-r--r--puppet/modules/couchdb/spec/classes/couchdb_spec.rb35
-rw-r--r--puppet/modules/couchdb/spec/fixtures/manifests/site.pp8
-rw-r--r--puppet/modules/couchdb/spec/functions/versioncmp_spec.rb9
-rw-r--r--puppet/modules/couchdb/spec/spec_helper.rb9
-rw-r--r--puppet/modules/couchdb/templates/admin.ini.erb9
-rw-r--r--puppet/modules/couchdb/templates/bigcouch/default.ini172
-rw-r--r--puppet/modules/couchdb/templates/bigcouch/vm.args32
-rw-r--r--puppet/modules/couchdb/templates/couchdb-backup.py.erb32
42 files changed, 1650 insertions, 0 deletions
diff --git a/puppet/modules/couchdb/.fixtures.yml b/puppet/modules/couchdb/.fixtures.yml
new file mode 100644
index 00000000..50c6c9ac
--- /dev/null
+++ b/puppet/modules/couchdb/.fixtures.yml
@@ -0,0 +1,6 @@
+fixtures:
+ symlinks:
+ couchdb: "#{source_dir}"
+ repositories:
+ stdlib: " https://leap.se/git/puppet_stdlib"
+
diff --git a/puppet/modules/couchdb/.gitrepo b/puppet/modules/couchdb/.gitrepo
new file mode 100644
index 00000000..d72ab390
--- /dev/null
+++ b/puppet/modules/couchdb/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_couchdb
+ branch = master
+ commit = 76ff149a095023611c05bbb00157d06f87b07c05
+ parent = 81210aea5cf136194598e7a399ce307ecbe088f1
+ cmdver = 0.3.0
diff --git a/puppet/modules/couchdb/Gemfile b/puppet/modules/couchdb/Gemfile
new file mode 100644
index 00000000..1c86e980
--- /dev/null
+++ b/puppet/modules/couchdb/Gemfile
@@ -0,0 +1,11 @@
+source "https://rubygems.org"
+
+group :test do
+ gem "rake"
+ gem "puppet", ENV['PUPPET_VERSION'] || '~> 3.7.0'
+ gem "rspec", '< 3.2.0'
+ gem "rspec-puppet"
+ gem "puppetlabs_spec_helper"
+ gem "metadata-json-lint"
+ gem "rspec-puppet-facts"
+end
diff --git a/puppet/modules/couchdb/README.md b/puppet/modules/couchdb/README.md
new file mode 100644
index 00000000..096221a4
--- /dev/null
+++ b/puppet/modules/couchdb/README.md
@@ -0,0 +1,32 @@
+# Couchdb Puppet module
+
+This module is based on the one from Camptocamp_.
+
+.. _Camptocamp: http://www.camptocamp.com/
+
+For more information about couchdb see http://couchdb.apache.org/
+
+# Dependencies
+
+- ruby module from the shared-modules group
+
+# Couchdb debian packages
+
+## Jessie
+
+There are no couchdb packages for jessie, so the only way is to
+to configure apt to install couchdb from unstable by adding a
+sources list file to `/etc/apt/sources.list.d`.
+
+## Example usage
+
+This will setup couchdb:
+
+ # needed for wget call, which is unqualified by purpose so we don't force
+ # a location for the wget binary
+ Exec { path => '/usr/bin:/usr/sbin/:/bin:/sbin:/usr/local/bin:/usr/local/sbin' }
+
+ class { 'couchdb':
+ admin_pw => '123'
+ }
+
diff --git a/puppet/modules/couchdb/Rakefile b/puppet/modules/couchdb/Rakefile
new file mode 100644
index 00000000..85326bb4
--- /dev/null
+++ b/puppet/modules/couchdb/Rakefile
@@ -0,0 +1,19 @@
+require 'puppetlabs_spec_helper/rake_tasks'
+require 'puppet-lint/tasks/puppet-lint'
+PuppetLint.configuration.send('disable_80chars')
+PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"]
+
+desc "Validate manifests, templates, and ruby files"
+task :validate do
+ Dir['manifests/**/*.pp'].each do |manifest|
+ sh "puppet parser validate --noop #{manifest}"
+ end
+ Dir['spec/**/*.rb','lib/**/*.rb'].each do |ruby_file|
+ sh "ruby -c #{ruby_file}" unless ruby_file =~ /spec\/fixtures/
+ end
+ Dir['templates/**/*.erb'].each do |template|
+ sh "erb -P -x -T '-' #{template} | ruby -c"
+ end
+end
+
+task :test => [:lint, :syntax , :validate, :spec]
diff --git a/puppet/modules/couchdb/files/Debian/couchdb b/puppet/modules/couchdb/files/Debian/couchdb
new file mode 100755
index 00000000..ccdfe716
--- /dev/null
+++ b/puppet/modules/couchdb/files/Debian/couchdb
@@ -0,0 +1,160 @@
+#!/bin/sh -e
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+### BEGIN INIT INFO
+# Provides: couchdb
+# Required-Start: $local_fs $remote_fs
+# Required-Stop: $local_fs $remote_fs
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Apache CouchDB init script
+# Description: Apache CouchDB init script for the database server.
+### END INIT INFO
+
+SCRIPT_OK=0
+SCRIPT_ERROR=1
+
+DESCRIPTION="database server"
+NAME=couchdb
+SCRIPT_NAME=`basename $0`
+COUCHDB=/usr/bin/couchdb
+CONFIGURATION_FILE=/etc/default/couchdb
+RUN_DIR=/var/run/couchdb
+LSB_LIBRARY=/lib/lsb/init-functions
+
+if test ! -x $COUCHDB; then
+ exit $SCRIPT_ERROR
+fi
+
+if test -r $CONFIGURATION_FILE; then
+ . $CONFIGURATION_FILE
+fi
+
+log_daemon_msg () {
+ # Dummy function to be replaced by LSB library.
+
+ echo $@
+}
+
+log_end_msg () {
+ # Dummy function to be replaced by LSB library.
+
+ if test "$1" != "0"; then
+ echo "Error with $DESCRIPTION: $NAME"
+ fi
+ return $1
+}
+
+if test -r $LSB_LIBRARY; then
+ . $LSB_LIBRARY
+fi
+
+run_command () {
+ command="$1"
+ if test -n "$COUCHDB_OPTIONS"; then
+ command="$command $COUCHDB_OPTIONS"
+ fi
+ if test -n "$COUCHDB_USER"; then
+ if su $COUCHDB_USER -c "$command"; then
+ return $SCRIPT_OK
+ else
+ return $SCRIPT_ERROR
+ fi
+ else
+ if $command; then
+ return $SCRIPT_OK
+ else
+ return $SCRIPT_ERROR
+ fi
+ fi
+}
+
+start_couchdb () {
+ # Start Apache CouchDB as a background process.
+
+ mkdir -p "$RUN_DIR"
+ chown -R "$COUCHDB_USER" "$RUN_DIR"
+ command="$COUCHDB -b"
+ if test -n "$COUCHDB_STDOUT_FILE"; then
+ command="$command -o $COUCHDB_STDOUT_FILE"
+ fi
+ if test -n "$COUCHDB_STDERR_FILE"; then
+ command="$command -e $COUCHDB_STDERR_FILE"
+ fi
+ if test -n "$COUCHDB_RESPAWN_TIMEOUT"; then
+ command="$command -r $COUCHDB_RESPAWN_TIMEOUT"
+ fi
+ run_command "$command" > /dev/null
+}
+
+stop_couchdb () {
+ # Stop the running Apache CouchDB process.
+
+ run_command "$COUCHDB -d" > /dev/null
+ pkill -u couchdb
+ # always return true even if no remaining couchdb procs got killed
+ /bin/true
+}
+
+display_status () {
+ # Display the status of the running Apache CouchDB process.
+
+ run_command "$COUCHDB -s"
+}
+
+parse_script_option_list () {
+ # Parse arguments passed to the script and take appropriate action.
+
+ case "$1" in
+ start)
+ log_daemon_msg "Starting $DESCRIPTION" $NAME
+ if start_couchdb; then
+ log_end_msg $SCRIPT_OK
+ else
+ log_end_msg $SCRIPT_ERROR
+ fi
+ ;;
+ stop)
+ log_daemon_msg "Stopping $DESCRIPTION" $NAME
+ if stop_couchdb; then
+ log_end_msg $SCRIPT_OK
+ else
+ log_end_msg $SCRIPT_ERROR
+ fi
+ ;;
+ restart|force-reload)
+ log_daemon_msg "Restarting $DESCRIPTION" $NAME
+ if stop_couchdb; then
+ if start_couchdb; then
+ log_end_msg $SCRIPT_OK
+ else
+ log_end_msg $SCRIPT_ERROR
+ fi
+ else
+ log_end_msg $SCRIPT_ERROR
+ fi
+ ;;
+ status)
+ display_status
+ ;;
+ *)
+ cat << EOF >&2
+Usage: $SCRIPT_NAME {start|stop|restart|force-reload|status}
+EOF
+ exit $SCRIPT_ERROR
+ ;;
+ esac
+}
+
+parse_script_option_list $@
diff --git a/puppet/modules/couchdb/files/couch-doc-diff b/puppet/modules/couchdb/files/couch-doc-diff
new file mode 100644
index 00000000..a5907d5e
--- /dev/null
+++ b/puppet/modules/couchdb/files/couch-doc-diff
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# Run a diff between a couch document specified as the first parameter
+# and the second parameter.
+# Diff returns 0 if there is no difference. This way you can tell the data
+# is already on the couch.
+# Both the couch document and the second paramter will be pretty printed
+# before comparison so differences in spaces etc. do not matter.
+# All keys starting with an underscore on the couch such as _id and _rev
+# will be removed before the comparison - we assume we want to compare
+# the real data, not the metadata about the document as we usually do not
+# know or care about what the id and revision will be.
+
+curl -s --netrc-file /etc/couchdb/couchdb.netrc $1 \
+ | python -mjson.tool \
+ | grep -v '^\s*"_' \
+ | diff -w - <(echo $2 | python -mjson.tool)
diff --git a/puppet/modules/couchdb/files/couch-doc-update b/puppet/modules/couchdb/files/couch-doc-update
new file mode 100644
index 00000000..a137e7ff
--- /dev/null
+++ b/puppet/modules/couchdb/files/couch-doc-update
@@ -0,0 +1,219 @@
+#!/usr/bin/ruby
+require 'syslog'
+
+#
+# This script will delete or update the values of a particular couchdb document. The benefit of this little script over
+# using a simple curl command for updating a document is this:
+#
+# * exit non-zero status if document was not updated.
+# * updates existing documents easily, taking care of the _rev id for you.
+# * if document doesn't exist, it is created
+#
+# REQUIREMENTS
+#
+# gem 'couchrest'
+#
+# USAGE
+#
+# see the ouput of
+#
+# couch-doc-update
+#
+# the content of <file> will be merged with the data provided.
+# If you only want the file content use --data '{}'
+#
+# EXAMPLE
+#
+# create a new user:
+# couch-doc-update --db _users --id org.couchdb.user:ca_daemon --data '{"type": "user", "name": "ca_daemon", "roles": ["certs"], "password": "sshhhh"}'
+#
+# update a user:
+# couch-doc-update --db _users --id org.couchdb.user:ca_daemon --data '{"password":"sssshhh"}'
+#
+# To update the _users DB on bigcouch, you must connect to port 5986 instead of the default couchdb port 5984
+#
+# delete a doc:
+# couch-doc-update --delete --db invite_codes --id dfaf0ee65670c16d5a9161dc86f3bff8
+#
+
+begin; require 'rubygems'; rescue LoadError; end # optionally load rubygems
+require 'couchrest'
+
+def main
+ db, id, data, delete = process_options
+
+ result = if delete
+ delete_document(db, id)
+ else
+ set_document(db, id, data)
+ end
+
+ exit 0 if result['ok']
+ raise StandardError.new(result.inspect)
+rescue StandardError => exc
+ db_without_password = db.to_s.sub(/:[^\/]*@/, ':PASSWORD_HIDDEN@')
+ indent = " "
+ log "ERROR: " + exc.to_s
+ log indent + $@[0..4].join("\n#{indent}")
+ log indent + "Failed writing to #{db_without_password}/#{id}"
+ exit 1
+end
+
+def log(message)
+ $stderr.puts message
+ Syslog.open('couch-doc-update') do |logger|
+ logger.log(Syslog::LOG_CRIT, message)
+ end
+end
+
+def process_options
+ #
+ # parse options
+ #
+ host = nil
+ db_name = nil
+ doc_id = nil
+ new_data = nil
+ filename = nil
+ netrc_file = nil
+ delete = false
+ loop do
+ case ARGV[0]
+ when '--host' then ARGV.shift; host = ARGV.shift
+ when '--db' then ARGV.shift; db_name = ARGV.shift
+ when '--id' then ARGV.shift; doc_id = ARGV.shift
+ when '--data' then ARGV.shift; new_data = ARGV.shift
+ when '--file' then ARGV.shift; filename = ARGV.shift
+ when '--netrc-file' then ARGV.shift; netrc_file = ARGV.shift
+ when '--delete' then ARGV.shift; delete = true
+ when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")
+ else break
+ end
+ end
+ usage("Missing required option") unless db_name && doc_id && (new_data || delete)
+
+ unless delete
+ new_data = MultiJson.load(new_data)
+ new_data.merge!(read_file(filename)) if filename
+ end
+ db = CouchRest.database(connection_string(db_name, host, netrc_file))
+ return db, doc_id, new_data, delete
+end
+
+def read_file(filename)
+ data = MultiJson.load( IO.read(filename) )
+ # strip off _id and _rev to avoid conflicts
+ data.delete_if {|k,v| k.start_with? '_'}
+end
+
+ #
+ # update document
+ #
+def set_document(db, id, data)
+ attempt ||= 1
+ doc = get_document(db, id)
+ if doc
+ doc.id ||= id
+ update_document(db, doc, data)
+ else
+ create_document(db, id, data)
+ end
+rescue RestClient::Conflict
+ # retry once, reraise if that does not work
+ raise if attempt > 1
+ attempt += 1
+ retry
+end
+
+COUCH_RESPONSE_OK = { 'ok' => true }
+
+# Deletes document, if exists, with retry
+def delete_document(db, id)
+ attempts ||= 1
+ doc = get_document(db, id)
+ if doc
+ db.delete_doc(doc)
+ else
+ COUCH_RESPONSE_OK
+ end
+rescue RestClient::ExceptionWithResponse => e
+ if attempts < 6 && !e.response.nil? && RETRY_CODES.include?(e.response.code)
+ attempts += 1
+ sleep 10
+ retry
+ else
+ raise e
+ end
+end
+
+def get_document(db, doc_id)
+ begin
+ db.get(doc_id)
+ rescue RestClient::ResourceNotFound
+ nil
+ end
+end
+
+# if the response status code is one of these
+# then retry instead of failing.
+RETRY_CODES = [500, 422].freeze
+
+def update_document(db, doc, data)
+ attempts ||= 1
+ doc.reject! {|k,v| !["_id", "_rev"].include? k}
+ doc.merge! data
+ db.save_doc(doc)
+rescue RestClient::ExceptionWithResponse => e
+ if attempts < 6 && !e.response.nil? && RETRY_CODES.include?(e.response.code)
+ attempts += 1
+ sleep 10
+ retry
+ else
+ raise e
+ end
+end
+
+def create_document(db, doc_id, data)
+ attempts ||= 1
+ data["_id"] = doc_id
+ db.save_doc(data)
+rescue RestClient::ExceptionWithResponse => e
+ if attempts < 6 && !e.response.nil? && RETRY_CODES.include?(e.response.code)
+ attempts += 1
+ sleep 10
+ retry
+ else
+ raise e
+ end
+end
+
+def connection_string(database, host, netrc_file = nil)
+ protocol = "http"
+ #hostname = "127.0.0.1"
+ port = "5984"
+ username = "admin"
+ password = ""
+
+ netrc = File.read(netrc_file || '/etc/couchdb/couchdb.netrc')
+ netrc.scan(/\w+ [\w\.]+/).each do |key_value|
+ key, value = key_value.split ' '
+ case key
+ when "machine" then host ||= value + ':' + port
+ when "login" then username = value
+ when "password" then password = value
+ end
+ end
+
+ host ||= '127.0.0.1:5984'
+
+ "%s://%s:%s@%s/%s" % [protocol, username, password, host, database]
+end
+
+def usage(s)
+ $stderr.puts(s)
+ $stderr.puts("Usage: #{File.basename($0)} --host <host> --db <db> --id <doc_id> --data <json> [--file <file>] [--netrc-file <netrc-file>]")
+ $stderr.puts(" #{File.basename($0)} --host <host> --db <db> --id <doc_id> --delete [--netrc-file <netrc-file>]")
+ exit(2)
+end
+
+main()
diff --git a/puppet/modules/couchdb/files/local.ini b/puppet/modules/couchdb/files/local.ini
new file mode 100644
index 00000000..7365b6c6
--- /dev/null
+++ b/puppet/modules/couchdb/files/local.ini
@@ -0,0 +1,84 @@
+; CouchDB Configuration Settings
+
+; Custom settings should be made in this file. They will override settings
+; in default.ini, but unlike changes made to default.ini, this file won't be
+; overwritten on server upgrade.
+
+[couchdb]
+;max_document_size = 4294967296 ; bytes
+
+[httpd]
+;port = 5984
+;bind_address = 127.0.0.1
+; Options for the MochiWeb HTTP server.
+;server_options = [{backlog, 128}, {acceptor_pool_size, 16}]
+; For more socket options, consult Erlang's module 'inet' man page.
+;socket_options = [{recbuf, 262144}, {sndbuf, 262144}, {nodelay, true}]
+
+; Uncomment next line to trigger basic-auth popup on unauthorized requests.
+;WWW-Authenticate = Basic realm="administrator"
+
+; Uncomment next line to set the configuration modification whitelist. Only
+; whitelisted values may be changed via the /_config URLs. To allow the admin
+; to change this value over HTTP, remember to include {httpd,config_whitelist}
+; itself. Excluding it from the list would require editing this file to update
+; the whitelist.
+;config_whitelist = [{httpd,config_whitelist}, {log,level}, {etc,etc}]
+
+[httpd_global_handlers]
+;_google = {couch_httpd_proxy, handle_proxy_req, <<"http://www.google.com">>}
+
+[couch_httpd_auth]
+; If you set this to true, you should also uncomment the WWW-Authenticate line
+; above. If you don't configure a WWW-Authenticate header, CouchDB will send
+; Basic realm="server" in order to prevent you getting logged out.
+; require_valid_user = false
+
+[log]
+;level = debug
+
+[os_daemons]
+; For any commands listed here, CouchDB will attempt to ensure that
+; the process remains alive while CouchDB runs as well as shut them
+; down when CouchDB exits.
+;foo = /path/to/command -with args
+
+[daemons]
+; enable SSL support by uncommenting the following line and supply the PEM's below.
+; the default ssl port CouchDB listens on is 6984
+; httpsd = {couch_httpd, start_link, [https]}
+
+[ssl]
+;cert_file = /full/path/to/server_cert.pem
+;key_file = /full/path/to/server_key.pem
+;password = somepassword
+; set to true to validate peer certificates
+verify_ssl_certificates = false
+; Path to file containing PEM encoded CA certificates (trusted
+; certificates used for verifying a peer certificate). May be omitted if
+; you do not want to verify the peer.
+;cacert_file = /full/path/to/cacertf
+; The verification fun (optionnal) if not specidied, the default
+; verification fun will be used.
+;verify_fun = {Module, VerifyFun}
+ssl_certificate_max_depth = 1
+; To enable Virtual Hosts in CouchDB, add a vhost = path directive. All requests to
+; the Virual Host will be redirected to the path. In the example below all requests
+; to http://example.com/ are redirected to /database.
+; If you run CouchDB on a specific port, include the port number in the vhost:
+; example.com:5984 = /database
+
+[vhosts]
+;example.com = /database/
+
+[update_notification]
+;unique notifier name=/full/path/to/exe -with "cmd line arg"
+
+; To create an admin account uncomment the '[admins]' section below and add a
+; line in the format 'username = password'. When you next start CouchDB, it
+; will change the password to a hash (so that your passwords don't linger
+; around in plain-text files). You can add more admin accounts with more
+; 'username = password' lines. Don't forget to restart CouchDB after
+; changing this.
+[admins]
+;admin = mysecretpassword
diff --git a/puppet/modules/couchdb/lib/facter/couchdb_pwhash_alg.rb b/puppet/modules/couchdb/lib/facter/couchdb_pwhash_alg.rb
new file mode 100644
index 00000000..60ae701a
--- /dev/null
+++ b/puppet/modules/couchdb/lib/facter/couchdb_pwhash_alg.rb
@@ -0,0 +1,43 @@
+require 'facter'
+
+def version_parts ( version )
+ # gives back a hash containing major, minor and patch numbers
+ # of a give version string
+
+ parts = Hash.new
+ first, *rest = version.split(".")
+ parts["major"] = first
+ parts["minor"] = rest[0]
+ parts["patch"] = rest[1]
+ return parts
+end
+
+def couchdb_pwhash_alg
+ # couchdb uses sha1 as pw hash algorithm until v. 1.2,
+ # but pbkdf2 from v.1.3 on.
+ # see http://docs.couchdb.org/en/1.4.x/configuring.html for
+ # details
+
+ couchdb_version = Facter.value(:couchdb_version)
+ version = version_parts(couchdb_version)
+ major = version["major"].to_i
+ alg = case major
+ when 0 then alg = 'n/a'
+ when 1 then
+ minor = version['minor'].to_i
+ if minor < 3
+ alg = 'sha1'
+ else
+ alg = 'pbkdf2'
+ end
+ else
+ alg = 'pbkdf2'
+ end
+ return alg
+end
+
+Facter.add(:couchdb_pwhash_alg) do
+ setcode do
+ couchdb_pwhash_alg
+ end
+end
diff --git a/puppet/modules/couchdb/lib/facter/couchdb_version.rb b/puppet/modules/couchdb/lib/facter/couchdb_version.rb
new file mode 100644
index 00000000..3a721169
--- /dev/null
+++ b/puppet/modules/couchdb/lib/facter/couchdb_version.rb
@@ -0,0 +1,34 @@
+require 'facter'
+
+def deb_installed_version ( name )
+ # returns an empty string if package is not installed,
+ # otherwise the version
+
+ version = `apt-cache policy #{name} | grep Installed 2>&1`
+ version.slice! " Installed: "
+ version.slice! "(none)"
+ return version.strip.chomp
+end
+
+def couchdb_version
+ bigcouch = deb_installed_version("bigcouch")
+ if bigcouch.empty?
+ couchdb = deb_installed_version("couchdb")
+ if couchdb.empty?
+ version = 'n/a'
+ else
+ version = couchdb
+ end
+ else
+ # bigcouch is currently only available in one version (0.4.2),
+ # which includes couchdb 1.1.1
+ version = '1.1.1'
+ end
+ return version
+end
+
+Facter.add(:couchdb_version) do
+ setcode do
+ couchdb_version
+ end
+end
diff --git a/puppet/modules/couchdb/lib/puppet/parser/functions/couchdblookup.rb b/puppet/modules/couchdb/lib/puppet/parser/functions/couchdblookup.rb
new file mode 100644
index 00000000..b9067d2a
--- /dev/null
+++ b/puppet/modules/couchdb/lib/puppet/parser/functions/couchdblookup.rb
@@ -0,0 +1,55 @@
+#
+# A basic function to retrieve data in couchdb
+#
+
+
+module Puppet::Parser::Functions
+ newfunction(:couchdblookup, :type => :rvalue) do |args|
+ require 'json'
+ require 'open-uri'
+
+ raise Puppet::ParseError, ("couchdblookup(): wrong number of arguments (#{args.length}; must be 2 or 3)") unless args.length.between?(2, 3)
+
+ url = args[0]
+ key = args[1]
+ default = args[2] if args.length >= 3
+
+ begin
+ json = JSON.parse(open(URI.parse(url)).read)
+ rescue OpenURI::HTTPError => error
+ raise Puppet::ParseError, "couchdblookup(): fetching URL #{url} failed with status '#{error.message}'"
+ rescue Timeout::Error => error
+ raise Puppet::ParseError, "couchdblookup(): connection to couchdb server timed out: '#{error.message}'"
+ rescue Errno::ECONNREFUSED => error
+ raise Puppet::ParseError, "couchdblookup(): connection to couchdb server failed: '#{error.message}'"
+ rescue JSON::ParserError => error
+ raise Puppet::ParseError, "couchdblookup(): failed to parse JSON received from couchdb: '#{error.message}'"
+ rescue StandardError => error
+ raise Puppet::ParseError, "couchdblookup(): something unexpected happened: '#{error.inspect}'"
+ end
+
+ result = nil
+
+ if json.has_key?("rows")
+
+ if json['rows'].length > 1
+ arr = json['rows'].collect do |x|
+ x[key] if x.is_a?(Hash) and x.has_key?(key)
+ end
+ arr.compact!
+ result = arr unless arr.empty?
+
+ elsif json['rows'].length == 1
+ hash = json['rows'].pop
+ result = hash[key] if hash.is_a?(Hash)
+ end
+
+ elsif json.has_key?(key)
+ result = json[key]
+ end
+
+ result or default or raise Puppet::ParseError, "couchdblookup(): key '#{key}' not found in JSON object !"
+
+ end
+end
+
diff --git a/puppet/modules/couchdb/lib/puppet/parser/functions/pbkdf2.rb b/puppet/modules/couchdb/lib/puppet/parser/functions/pbkdf2.rb
new file mode 100644
index 00000000..46400c9c
--- /dev/null
+++ b/puppet/modules/couchdb/lib/puppet/parser/functions/pbkdf2.rb
@@ -0,0 +1,62 @@
+#
+# pbkdf2.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:pbkdf2, :type => :rvalue, :doc => <<-EOS
+This converts a password and a salt (and optional iterations and keylength
+parameters) to a hash containing the salted SHA1 password hash, salt,
+iterations and keylength.
+pbkdf2 is used i.e. for couchdb passwords since v1.3.
+
+Example usage:
+ $pbkdf2 = pbkdf2($::couchdb::admin_pw, $::couchdb::admin_salt)
+ $sha1 = $pbkdf2['sha1']
+EOS
+ ) do |arguments|
+ require 'openssl'
+ require 'base64'
+
+ raise(Puppet::ParseError, "pbkdf2(): Wrong number of arguments " +
+ "passed (#{arguments.size} but we require at least 2)") if arguments.size < 2
+
+ unless arguments.is_a?(Array)
+ raise(Puppet::ParseError, 'pbkdf2(): Requires a ' +
+ "Array argument, you passed: #{password.class}")
+ end
+
+ password = arguments[0]
+ salt = arguments[1]
+
+ if arguments.size > 2
+ iterations = arguments[2].to_i
+ else
+ iterations = 1000
+ end
+
+ if arguments.size > 3
+ keylength = arguments[3].to_i
+ else
+ keylength = 20
+ end
+
+ pbkdf2 = OpenSSL::PKCS5::pbkdf2_hmac_sha1(
+ password,
+ salt,
+ iterations,
+ keylength
+ )
+
+ return_hash = Hash.new()
+ # return hex encoded string
+ return_hash['sha1'] = pbkdf2.unpack('H*')[0]
+ return_hash['password'] = password
+ return_hash['salt'] = salt
+ return_hash['iterations'] = iterations
+ return_hash['keylength'] = keylength
+
+ return return_hash
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/couchdb/manifests/add_user.pp b/puppet/modules/couchdb/manifests/add_user.pp
new file mode 100644
index 00000000..29c6a8c8
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/add_user.pp
@@ -0,0 +1,39 @@
+define couchdb::add_user ( $roles, $pw, $salt = '' ) {
+ # Couchdb < 1.2 needs a pre-hashed pw and salt
+ # If you provide a salt, couchdb::add_user will assume that
+ # $pw is prehashed and pass both parameters to couchdb::update
+ # If $salt is empty, couchdb::add_user will assume that the pw
+ # is plaintext and will pass it to couchdb::update
+
+ if $::couchdb::bigcouch == true {
+ $port = 5986
+ } else {
+ $port = 5984
+ }
+
+ if $salt == '' {
+ # unhashed, plaintext pw, no salt. For couchdb >= 1.2
+ $data = "{\"type\": \"user\", \"name\": \"${name}\", \"roles\": ${roles}, \"password\": \"${pw}\"}"
+ } else {
+ # prehashed pw with salt, for couchdb < 1.2
+ # salt and encrypt pw
+ # str_and_salt2sha1 is a function from leap's stdlib module
+ $pw_and_salt = [ $pw, $salt ]
+ $sha = str_and_salt2sha1($pw_and_salt)
+ $data = "{\"type\": \"user\", \"name\": \"${name}\", \"roles\": ${roles}, \"password_sha\": \"${sha}\", \"salt\": \"${salt}\"}"
+ }
+
+ # update the user with the given password unless they already work
+ couchdb::document { "update_user_${name}":
+ host => "127.0.0.1:${port}",
+ db => '_users',
+ id => "org.couchdb.user:${name}",
+ data => $data
+ }
+
+ couchdb::query::setup { $name:
+ user => $name,
+ pw => $pw,
+ }
+
+}
diff --git a/puppet/modules/couchdb/manifests/backup.pp b/puppet/modules/couchdb/manifests/backup.pp
new file mode 100644
index 00000000..a477b5b1
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/backup.pp
@@ -0,0 +1,51 @@
+# configure backup using couchdb-backup.py
+class couchdb::backup {
+
+ include couchdb::params
+
+ # used in ERB templates
+ $bind_address = $couchdb::params::bind_address
+ $port = $couchdb::params::port
+ $backupdir = $couchdb::params::backupdir
+
+ file { $couchdb::params::backupdir:
+ ensure => directory,
+ mode => '0755',
+ require => Package['couchdb'],
+ }
+
+ file { '/usr/local/sbin/couchdb-backup.py':
+ ensure => present,
+ owner => root,
+ group => root,
+ mode => '0755',
+ content => template('couchdb/couchdb-backup.py.erb'),
+ require => File[$couchdb::params::backupdir],
+ }
+
+ cron { 'couchdb-backup':
+ command => '/usr/local/sbin/couchdb-backup.py 2> /dev/null',
+ hour => 3,
+ minute => 0,
+ require => File['/usr/local/sbin/couchdb-backup.py'],
+ }
+
+ case $::operatingsystem {
+ /Debian|Ubunu/: {
+ # note: python-couchdb >= 0.8 required, which is found in debian wheezy.
+ ensure_packages (['python-couchdb', 'python-simplejson'], {
+ before => File['/usr/local/sbin/couchdb-backup.py']
+ })
+ }
+ /RedHat|Centos/: {
+ exec {'install python-couchdb using easy_install':
+ command => 'easy_install http://pypi.python.org/packages/2.6/C/CouchDB/CouchDB-0.8-py2.6.egg',
+ creates => '/usr/lib/python2.6/site-packages/CouchDB-0.8-py2.6.egg',
+ }
+ }
+ default: {
+ err('This module has not been written to support your operating system')
+ }
+ }
+
+}
diff --git a/puppet/modules/couchdb/manifests/base.pp b/puppet/modules/couchdb/manifests/base.pp
new file mode 100644
index 00000000..6c7bf25f
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/base.pp
@@ -0,0 +1,124 @@
+# configure couchdb
+class couchdb::base {
+
+ if $::couchdb::bigcouch == true {
+ $couchdb_user = 'bigcouch'
+ include couchdb::bigcouch
+ } else {
+ $couchdb_user = 'couchdb'
+ }
+
+ # we use package{} here because bigcouch.pp overwrites it and
+ # this won't work with ensure_packages()
+ package {'couchdb':
+ ensure => installed
+ }
+
+ service { 'couchdb':
+ ensure => running,
+ hasstatus => true,
+ enable => true,
+ require => Package['couchdb']
+ }
+
+ # todo: make host/port configurable
+ exec { 'wait_for_couchdb':
+ command => 'wget --retry-connrefused --tries 10 --quiet "http://127.0.0.1:5984" -O /dev/null',
+ require => Service['couchdb']
+ }
+
+
+ # couchrest gem is required for couch-doc-update script,
+ # and it needs the ruby-dev package installed to build
+
+ if versioncmp($::operatingsystemrelease, '8') < 0 {
+ $couchrest_version = '1.2'
+ }
+ else {
+ # couchrest v1.2.1 doesn't build with default debian jessie rake version
+ # shipped as debian package (10.3.2)
+ # see https://leap.se/code/issues/7754
+ $couchrest_version = '1.2.0'
+ }
+
+ ensure_packages('ruby-dev')
+ ensure_packages('couchrest', {
+ provider => 'gem',
+ ensure => $couchrest_version,
+ require => Package['ruby-dev']
+ })
+
+ File['/usr/local/bin/couch-doc-update'] -> Couchdb::Update <| |>
+ File['/usr/local/bin/couch-doc-diff'] -> Couchdb::Update <| |>
+
+ Couchdb::Update <| |> -> Couchdb::Document <| |>
+
+ file {
+ '/usr/local/bin/couch-doc-update':
+ source => 'puppet:///modules/couchdb/couch-doc-update',
+ mode => '0755',
+ owner => 'root',
+ group => 'root',
+ require => Package['couchrest'];
+
+ '/usr/local/bin/couch-doc-diff':
+ source => 'puppet:///modules/couchdb/couch-doc-diff',
+ mode => '0755',
+ owner => 'root',
+ group => 'root',
+ require => Package['couchrest'];
+
+ '/etc/couchdb/local.ini':
+ source => [ "puppet:///modules/site_couchdb/${::fqdn}/local.ini",
+ 'puppet:///modules/site_couchdb/local.ini',
+ 'puppet:///modules/couchdb/local.ini' ],
+ notify => Service[couchdb],
+ owner => $couchdb_user,
+ group => $couchdb_user,
+ mode => '0660',
+ require => Package['couchdb'];
+
+ '/etc/couchdb/local.d':
+ ensure => directory,
+ require => Package['couchdb'];
+ }
+
+ $alg = $::couchdb::pwhash_alg
+ $salt = $::couchdb::admin_salt
+ case $alg {
+ 'sha1': {
+ # str_and_salt2sha1 is a function from leap's stdlib module
+ $pw_and_salt = [ $::couchdb::admin_pw, $salt ]
+ $sha1 = str_and_salt2sha1($pw_and_salt)
+ $admin_hash = "-hashed-${sha1},${salt}"
+ }
+ 'pbkdf2': {
+ $pbkdf2 = pbkdf2($::couchdb::admin_pw, $::couchdb::admin_salt, 10)
+ $sha1 = $pbkdf2['sha1']
+ $admin_hash = "-pbkdf2-${sha1},${salt},10"
+ }
+ default: { fail ("Unknown fact couchdb_pwhash_alg ${::couchdb_pwhash_alg} - Exiting.") }
+ }
+
+ file { '/etc/couchdb/local.d/admin.ini':
+ content => template('couchdb/admin.ini.erb'),
+ mode => '0600',
+ owner => $couchdb_user,
+ group => $couchdb_user,
+ notify => Service[couchdb],
+ require => File['/etc/couchdb/local.d'];
+ }
+
+ case $::couchdb::bigcouch {
+ true: { $restart_command = '/etc/init.d/bigcouch restart; sleep 6' }
+ default: { $restart_command = '/etc/init.d/couchdb restart; sleep 6' }
+ }
+
+ exec { 'couchdb_restart':
+ command => $restart_command,
+ path => ['/bin', '/usr/bin',],
+ subscribe => File['/etc/couchdb/local.d/admin.ini',
+ '/etc/couchdb/local.ini'],
+ refreshonly => true
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/bigcouch.pp b/puppet/modules/couchdb/manifests/bigcouch.pp
new file mode 100644
index 00000000..a97411bf
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/bigcouch.pp
@@ -0,0 +1,51 @@
+class couchdb::bigcouch inherits couchdb::base {
+
+ file {
+ '/opt/bigcouch':
+ ensure => directory,
+ mode => '0755';
+
+ '/etc/couchdb':
+ ensure => directory,
+ mode => '0755',
+ before => Package['couchdb'];
+
+ '/opt/bigcouch/etc':
+ ensure => link,
+ target => '/etc/couchdb',
+ before => Package['couchdb'];
+ }
+
+ # there's no bigcouch in the official debian repo, you need
+ # to setup a repository for that. You can use class
+ # couchdb::bigcouch::package::cloudant for unauthenticated 0.4.0 packages,
+ # or site_apt::leap_repo from the leap_platfrom repository
+ # for signed 0.4.2 packages
+
+ Package['couchdb'] {
+ name => 'bigcouch'
+ }
+
+ file { '/opt/bigcouch/etc/vm.args':
+ content => template('couchdb/bigcouch/vm.args'),
+ mode => '0640',
+ owner => 'bigcouch',
+ group => 'bigcouch',
+ require => Package['couchdb'],
+ notify => Service[couchdb]
+ }
+
+ file { '/opt/bigcouch/etc/default.ini':
+ content => template('couchdb/bigcouch/default.ini'),
+ mode => '0640',
+ owner => 'bigcouch',
+ group => 'bigcouch',
+ require => Package['couchdb'],
+ notify => Service[couchdb]
+ }
+
+ Service['couchdb'] {
+ name => 'bigcouch'
+ }
+
+}
diff --git a/puppet/modules/couchdb/manifests/bigcouch/add_node.pp b/puppet/modules/couchdb/manifests/bigcouch/add_node.pp
new file mode 100644
index 00000000..ed9db94b
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/bigcouch/add_node.pp
@@ -0,0 +1,8 @@
+define couchdb::bigcouch::add_node {
+
+ couchdb::bigcouch::document { "add_${name}":
+ db => 'nodes',
+ id => "bigcouch@${name}",
+ ensure => 'present'
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/bigcouch/debian.pp b/puppet/modules/couchdb/manifests/bigcouch/debian.pp
new file mode 100644
index 00000000..645c6da8
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/bigcouch/debian.pp
@@ -0,0 +1,11 @@
+class couchdb::bigcouch::debian inherits couchdb::debian {
+
+ File['/etc/init.d/couchdb'] {
+ ensure => absent
+ }
+
+ file {'/etc/init.d/bigcouch':
+ ensure => link,
+ target => '/usr/bin/sv'
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/bigcouch/document.pp b/puppet/modules/couchdb/manifests/bigcouch/document.pp
new file mode 100644
index 00000000..13f4ac17
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/bigcouch/document.pp
@@ -0,0 +1,14 @@
+define couchdb::bigcouch::document (
+ $db,
+ $id,
+ $host = '127.0.0.1:5986',
+ $data ='{}',
+ $ensure ='content') {
+ couchdb::document { $name:
+ ensure => $ensure,
+ host => $host,
+ db => $db,
+ id => $id,
+ data => $data
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/bigcouch/package/cloudant.pp b/puppet/modules/couchdb/manifests/bigcouch/package/cloudant.pp
new file mode 100644
index 00000000..cfdcf10c
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/bigcouch/package/cloudant.pp
@@ -0,0 +1,35 @@
+class couchdb::bigcouch::package::cloudant (
+ $ensure = 'present'
+) {
+
+ # cloudant's signing key can be fetched from
+ # http://packages.cloudant.com/KEYS, please use the apt module to
+ # distribute it on your servers after verifying its fingerprint
+
+ # cloudant's wheezy repo will fail cause in their Release file
+ # (http://packages.cloudant.com/debian/dists/wheezy/Release) they
+ # wrongly marked the packages for squeeze
+ # so we will use their squeeze repo here
+ apt::sources_list {'bigcouch-cloudant.list':
+ ensure => $ensure,
+ content => 'deb http://packages.cloudant.com/debian squeeze main'
+ }
+
+ # right now, cloudant only provides authenticated bigcouch 0.4.2 packages
+ # for squeeze, therefore we need to allow the installation of the depending
+ # packages libicu44 and libssl0.9.8 from squeeze
+
+ if $::lsbdistcodename == 'wheezy' {
+ apt::sources_list {'squeeze.list':
+ ensure => $ensure,
+ content => 'deb http://http.debian.net/debian squeeze main
+deb http://security.debian.org/ squeeze/updates main
+' }
+ apt::preferences_snippet { 'bigcouch_squeeze_deps':
+ ensure => $ensure,
+ package => 'libicu44 libssl0.9.8',
+ priority => '980',
+ pin => 'release o=Debian,n=squeeze'
+ }
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/create_db.pp b/puppet/modules/couchdb/manifests/create_db.pp
new file mode 100644
index 00000000..8a8d1144
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/create_db.pp
@@ -0,0 +1,21 @@
+define couchdb::create_db (
+ $host='127.0.0.1:5984',
+ $admins="{\"names\": [], \"roles\": [] }",
+ $members="{\"names\": [], \"roles\": [] }" )
+{
+
+ couchdb::query { "create_db_${name}":
+ cmd => 'PUT',
+ host => $host,
+ path => $name,
+ unless => "/usr/bin/curl -s -f --netrc-file /etc/couchdb/couchdb.netrc ${host}/${name}"
+ }
+
+ couchdb::document { "${name}_security":
+ db => $name,
+ id => '_security',
+ host => $host,
+ data => "{ \"admins\": ${admins}, \"members\": ${members} }",
+ require => Couchdb::Query["create_db_${name}"]
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/debian.pp b/puppet/modules/couchdb/manifests/debian.pp
new file mode 100644
index 00000000..b83b227a
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/debian.pp
@@ -0,0 +1,15 @@
+# installs initscript and dependent packages on debian
+class couchdb::debian inherits couchdb::base {
+
+ ensure_packages('libjs-jquery')
+
+ file { '/etc/init.d/couchdb':
+ source => [
+ 'puppet:///modules/site_couchdb/Debian/couchdb',
+ 'puppet:///modules/couchdb/Debian/couchdb' ],
+ mode => '0755',
+ owner => 'root',
+ group => 'root',
+ require => Package['couchdb']
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/deploy_config.pp b/puppet/modules/couchdb/manifests/deploy_config.pp
new file mode 100644
index 00000000..2ce1fd20
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/deploy_config.pp
@@ -0,0 +1,12 @@
+class couchdb::deploy_config {
+
+ file { '/etc/couchdb/local.ini':
+ source => [ "puppet:///modules/site_couchdb/${::fqdn}/local.ini",
+ 'puppet:///modules/site_couchdb/local.ini',
+ 'puppet:///modules/couchdb/local.ini' ],
+ notify => Service[couchdb],
+ owner => couchdb,
+ group => couchdb,
+ mode => '0660'
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/document.pp b/puppet/modules/couchdb/manifests/document.pp
new file mode 100644
index 00000000..6180474b
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/document.pp
@@ -0,0 +1,47 @@
+# Usage:
+# couchdb::document { id:
+# db => "database",
+# data => "content",
+# ensure => {absent,present,*content*}
+# }
+#
+define couchdb::document(
+ $db,
+ $id,
+ $host = '127.0.0.1:5984',
+ $data = '{}',
+ $netrc = '/etc/couchdb/couchdb.netrc',
+ $ensure = 'content') {
+
+ $url = "${host}/${db}/${id}"
+
+ case $ensure {
+ default: { err ( "unknown ensure value '${ensure}'" ) }
+ content: {
+ exec { "couch-doc-update --netrc-file ${netrc} --host ${host} --db ${db} --id ${id} --data \'${data}\'":
+ require => Exec['wait_for_couchdb'],
+ unless => "couch-doc-diff $url '$data'"
+ }
+ }
+
+ present: {
+ couchdb::query { "create_${db}_${id}":
+ cmd => 'PUT',
+ host => $host,
+ path => "${db}/${id}",
+ require => Exec['wait_for_couchdb'],
+ unless => "/usr/bin/curl -s -f --netrc-file ${netrc} ${url}"
+ }
+ }
+
+ absent: {
+ couchdb::query { "destroy_${db}_${id}":
+ cmd => 'DELETE',
+ host => $host,
+ path => "${db}/${id}",
+ require => Exec['wait_for_couchdb'],
+ unless => "/usr/bin/curl -s -f --netrc-file ${netrc} ${url}"
+ }
+ }
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/init.pp b/puppet/modules/couchdb/manifests/init.pp
new file mode 100644
index 00000000..12598ba0
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/init.pp
@@ -0,0 +1,31 @@
+# initial couchdb class
+class couchdb (
+ $admin_pw,
+ $admin_salt = '',
+ $bigcouch = false,
+ $bigcouch_cookie = '',
+ $ednp_port = '9001',
+ $chttpd_bind_address = '0.0.0.0',
+ $pwhash_alg = 'pbkdf2' )
+{
+
+ # stdlib is needed i.e. for ensure_packages()
+ include ::stdlib
+
+ case $::operatingsystem {
+ Debian: {
+ case $::lsbdistcodename {
+ /lenny|squeeze|wheezy|jessie/: {
+ include couchdb::debian
+ if $bigcouch == true {
+ include couchdb::bigcouch::debian
+ }
+ }
+ default: { fail "couchdb not available for ${::operatingsystem}/${::lsbdistcodename}" }
+ }
+ }
+ RedHat: { include couchdb::redhat }
+ }
+
+ ensure_packages('curl')
+}
diff --git a/puppet/modules/couchdb/manifests/mirror_db.pp b/puppet/modules/couchdb/manifests/mirror_db.pp
new file mode 100644
index 00000000..b07b6749
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/mirror_db.pp
@@ -0,0 +1,21 @@
+define couchdb::mirror_db (
+ $host='127.0.0.1:5984',
+ $from='',
+ $to='',
+ $user='replication',
+ $role='replication'
+ )
+{
+ $source = "${from}/${name}"
+ if $to == '' { $target = $name }
+ else { $target = "${to}/${name}" }
+
+ couchdb::document { "${name}_replication":
+ db => "_replicator",
+ id => "${name}_replication",
+ netrc => "/etc/couchdb/couchdb-${user}.netrc",
+ host => $host,
+ data => "{ \"source\": \"${source}\", \"target\": \"${target}\", \"continuous\": true, \"user_ctx\": { \"name\": \"${user}\", \"roles\": [\"${role}\"] }, \"owner\": \"${user}\" }",
+ require => Couchdb::Query["create_db_${name}"]
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/params.pp b/puppet/modules/couchdb/manifests/params.pp
new file mode 100644
index 00000000..02d5f02e
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/params.pp
@@ -0,0 +1,23 @@
+class couchdb::params {
+
+ $bind_address = $::couchdb_bind_address ? {
+ '' => '127.0.0.1',
+ default => $::couchdb_bind_address,
+ }
+
+ $port = $::couchdb_port ? {
+ '' => '5984',
+ default => $::couchdb_port,
+ }
+
+ $backupdir = $::couchdb_backupdir ? {
+ '' => '/var/backups/couchdb',
+ default => $::couchdb_backupdir,
+ }
+
+ $cert_path = $::couchdb_cert_path ? {
+ "" => '/etc/couchdb',
+ default => $::couchdb_cert_path,
+ }
+
+}
diff --git a/puppet/modules/couchdb/manifests/query.pp b/puppet/modules/couchdb/manifests/query.pp
new file mode 100644
index 00000000..9507ca1e
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/query.pp
@@ -0,0 +1,12 @@
+define couchdb::query (
+ $cmd, $path,
+ $netrc='/etc/couchdb/couchdb.netrc',
+ $host='127.0.0.1:5984',
+ $data = '{}',
+ $unless = undef) {
+
+ exec { "/usr/bin/curl -s --netrc-file ${netrc} -X ${cmd} ${host}/${path} --data \'${data}\'":
+ require => [ Package['curl'], Exec['wait_for_couchdb'] ],
+ unless => $unless
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/query/setup.pp b/puppet/modules/couchdb/manifests/query/setup.pp
new file mode 100644
index 00000000..451eb536
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/query/setup.pp
@@ -0,0 +1,10 @@
+define couchdb::query::setup ($user, $pw, $host='127.0.0.1') {
+
+ file { "/etc/couchdb/couchdb-${user}.netrc":
+ content => "machine ${host} login ${user} password ${pw}",
+ mode => '0600',
+ owner => $::couchdb::base::couchdb_user,
+ group => $::couchdb::base::couchdb_user,
+ require => Package['couchdb'];
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/redhat.pp b/puppet/modules/couchdb/manifests/redhat.pp
new file mode 100644
index 00000000..defa0a94
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/redhat.pp
@@ -0,0 +1 @@
+class couchdb::redhat inherits couchdb::base {}
diff --git a/puppet/modules/couchdb/manifests/ssl/deploy_cert.pp b/puppet/modules/couchdb/manifests/ssl/deploy_cert.pp
new file mode 100644
index 00000000..d3e743f1
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/ssl/deploy_cert.pp
@@ -0,0 +1,28 @@
+define couchdb::ssl::deploy_cert ($cert, $key) {
+
+ include couchdb::params
+
+ file { 'couchdb_cert_directory':
+ ensure => 'directory',
+ path => $couchdb::params::cert_path,
+ mode => '0600',
+ owner => 'couchdb',
+ group => 'couchdb';
+ }
+
+ file { 'couchdb_cert':
+ path => "${couchdb::params::cert_path}/server_cert.pem",
+ mode => '0644',
+ owner => 'couchdb',
+ group => 'couchdb',
+ content => $cert
+ }
+
+ file { 'couchdb_key':
+ path => "${couchdb::params::cert_path}/server_key.pem",
+ mode => '0600',
+ owner => 'couchdb',
+ group => 'couchdb',
+ content => $key
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/ssl/generate_cert.pp b/puppet/modules/couchdb/manifests/ssl/generate_cert.pp
new file mode 100644
index 00000000..a443250e
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/ssl/generate_cert.pp
@@ -0,0 +1,25 @@
+# configures cert for ssl access
+class couchdb::ssl::generate_cert {
+
+ ensure_packages('openssl')
+
+ file { $couchdb::cert_path:
+ ensure => 'directory',
+ mode => '0600',
+ owner => 'couchdb',
+ group => 'couchdb';
+ }
+
+exec { 'generate-certs':
+ command => "/usr/bin/openssl req -new -inform PEM -x509 -nodes -days 150 -subj \
+'/C=ZZ/ST=AutoSign/O=AutoSign/localityName=AutoSign/commonName=${::hostname}/organizationalUnitName=AutoSign/emailAddress=AutoSign/' \
+-newkey rsa:2048 -out ${couchdb::cert_path}/couchdb_cert.pem -keyout ${couchdb::cert_path}/couchdb_key.pem",
+ unless => "/usr/bin/test -f ${couchdb::cert_path}/couchdb_cert.pem &&
+/usr/bin/test -f ${couchdb::params::cert_path}/couchdb_key.pem",
+ require => [
+ File[$couchdb::params::cert_path],
+ Exec['make-install']
+ ],
+ notify => Service['couchdb'],
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/update.pp b/puppet/modules/couchdb/manifests/update.pp
new file mode 100644
index 00000000..b1dba84c
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/update.pp
@@ -0,0 +1,12 @@
+define couchdb::update (
+ $db,
+ $id,
+ $data,
+ $host='127.0.0.1:5984',
+ $unless=undef) {
+
+ exec { "couch-doc-update --host ${host} --db ${db} --id ${id} --data \'${data}\'":
+ require => Exec['wait_for_couchdb'],
+ unless => $unless
+ }
+}
diff --git a/puppet/modules/couchdb/spec/classes/couchdb_spec.rb b/puppet/modules/couchdb/spec/classes/couchdb_spec.rb
new file mode 100644
index 00000000..e8e4174e
--- /dev/null
+++ b/puppet/modules/couchdb/spec/classes/couchdb_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe 'couchdb' do
+ context 'given it is a wheezy system' do
+ let(:params) { {:admin_pw => 'foo'} }
+ let(:facts) do
+ {
+ :operatingsystemrelease => '7',
+ :operatingsystem => 'Debian',
+ :lsbdistcodename => 'wheezy',
+ }
+ end
+ it "should install couchrest 1.2" do
+ should contain_package('couchrest').with({
+ 'ensure'=> '1.2',
+ })
+ end
+ end
+ context 'given it is a jessie system' do
+ let(:params) { {:admin_pw => 'foo'} }
+ let(:facts) do
+ {
+ :operatingsystemrelease => '8',
+ :operatingsystem => 'Debian',
+ :lsbdistcodename => 'jessie',
+ }
+ end
+ it "should install latest couchrest version" do
+ should contain_package('couchrest').with({
+ 'ensure'=> 'latest',
+ })
+ end
+ end
+end
+
diff --git a/puppet/modules/couchdb/spec/fixtures/manifests/site.pp b/puppet/modules/couchdb/spec/fixtures/manifests/site.pp
new file mode 100644
index 00000000..a959fb77
--- /dev/null
+++ b/puppet/modules/couchdb/spec/fixtures/manifests/site.pp
@@ -0,0 +1,8 @@
+# set a default exec path
+# the logoutput exec parameter defaults to "on_error" in puppet 3,
+# but to "false" in puppet 2.7, so we need to set this globally here
+Exec {
+ logoutput => on_failure,
+ path => '/usr/bin:/usr/sbin/:/bin:/sbin:/usr/local/bin:/usr/local/sbin'
+}
+
diff --git a/puppet/modules/couchdb/spec/functions/versioncmp_spec.rb b/puppet/modules/couchdb/spec/functions/versioncmp_spec.rb
new file mode 100644
index 00000000..0a244275
--- /dev/null
+++ b/puppet/modules/couchdb/spec/functions/versioncmp_spec.rb
@@ -0,0 +1,9 @@
+require 'spec_helper'
+
+describe 'versioncmp' do
+ it { should run.with_params('7.2','8').and_return(-1) }
+ it { should run.with_params('7','8').and_return(-1) }
+ it { should run.with_params('8','8').and_return(0) }
+ it { should run.with_params('8.1','8').and_return(1) }
+end
+
diff --git a/puppet/modules/couchdb/spec/spec_helper.rb b/puppet/modules/couchdb/spec/spec_helper.rb
new file mode 100644
index 00000000..b55ede81
--- /dev/null
+++ b/puppet/modules/couchdb/spec/spec_helper.rb
@@ -0,0 +1,9 @@
+require 'rspec-puppet'
+
+fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures'))
+
+RSpec.configure do |c|
+ c.module_path = File.join(fixture_path, 'modules')
+ c.manifest_dir = File.join(fixture_path, 'manifests')
+ c.environmentpath = File.join(Dir.pwd, 'spec')
+end
diff --git a/puppet/modules/couchdb/templates/admin.ini.erb b/puppet/modules/couchdb/templates/admin.ini.erb
new file mode 100644
index 00000000..479f8bfc
--- /dev/null
+++ b/puppet/modules/couchdb/templates/admin.ini.erb
@@ -0,0 +1,9 @@
+<%- require 'digest' -%>
+[admins]
+admin = <%= @admin_hash %>
+
+[couchdb]
+<%- # uuid uniquely identifies this couchdb instance. if not set, couchdb will set a random one
+ # but we want a stable one so that this config file doesn't change all the time.
+ # Md5 of hostname and ipaddress seems reasonable, but it could be based on anything. -%>
+uuid = <%= Digest::MD5.hexdigest(Facter.value("hostname") + Facter.value("ipaddress")) %>
diff --git a/puppet/modules/couchdb/templates/bigcouch/default.ini b/puppet/modules/couchdb/templates/bigcouch/default.ini
new file mode 100644
index 00000000..a315ddab
--- /dev/null
+++ b/puppet/modules/couchdb/templates/bigcouch/default.ini
@@ -0,0 +1,172 @@
+[couchdb]
+database_dir = /opt/bigcouch/var/lib
+view_index_dir = /opt/bigcouch/var/lib
+max_document_size = 67108864
+os_process_timeout = 5000
+max_dbs_open = 500
+delayed_commits = false
+
+[cluster]
+; Default number of shards for a new database
+q = 8
+; Default number of copies of each shard
+n = 3
+
+[chttpd]
+port = 5984
+docroot = /opt/bigcouch/share/www
+
+; Options for the MochiWeb HTTP server.
+;server_options = [{backlog, 128}, {acceptor_pool_size, 16}]
+
+; For more socket options, consult Erlang's module 'inet' man page.
+;socket_options = [{recbuf, 262144}, {sndbuf, 262144}, {nodelay, true}]
+
+bind_address = <%= scope.lookupvar('couchdb::chttpd_bind_address') %>
+
+[chttps]
+port = 6984
+
+; cert_file = /full/path/to/server_cert.pem
+; key_file = /full/path/to/server_key.pem
+; password = somepassword
+; also remember to enable the chttps daemon in [daemons] section.
+
+; set to true to validate peer certificates
+verify_ssl_certificates = false
+
+; Path to file containing PEM encoded CA certificates (trusted
+; certificates used for verifying a peer certificate). May be omitted if
+; you do not want to verify the peer.
+;cacert_file = /full/path/to/cacertf
+
+; The verification fun (optional) if not specified, the default
+; verification fun will be used.
+;verify_fun = {Module, VerifyFun}
+ssl_certificate_max_depth = 1
+
+[httpd]
+port = 5986
+bind_address = 127.0.0.1
+authentication_handlers = {couch_httpd_oauth, oauth_authentication_handler}, {couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}
+default_handler = {couch_httpd_db, handle_request}
+secure_rewrites = true
+vhost_global_handlers = _utils, _uuids, _session, _oauth, _users
+allow_jsonp = false
+log_max_chunk_size = 1000000
+
+[ssl]
+port = 6984
+
+[log]
+file = /opt/bigcouch/var/log/bigcouch.log
+level = info
+include_sasl = true
+
+[couch_httpd_auth]
+authentication_db = _users
+authentication_redirect = /_utils/session.html
+require_valid_user = false
+timeout = 43200 ; (default to 12 hours) number of seconds before automatic logout
+auth_cache_size = 50 ; size is number of cache entries
+
+[query_servers]
+javascript = /opt/bigcouch/bin/couchjs /opt/bigcouch/share/couchjs/main.js
+
+[query_server_config]
+reduce_limit = true
+os_process_soft_limit = 100
+
+[daemons]
+view_manager={couch_view, start_link, []}
+external_manager={couch_external_manager, start_link, []}
+query_servers={couch_proc_manager, start_link, []}
+httpd={couch_httpd, start_link, []}
+stats_aggregator={couch_stats_aggregator, start, []}
+stats_collector={couch_stats_collector, start, []}
+uuids={couch_uuids, start, []}
+auth_cache={couch_auth_cache, start_link, []}
+replication_manager={couch_replication_manager, start_link, []}
+vhosts={couch_httpd_vhost, start_link, []}
+os_daemons={couch_os_daemons, start_link, []}
+; Uncomment next line to enable SSL daemon
+; chttpsd = {chttpd, start_link, [https]}
+
+[httpd_global_handlers]
+/ = {couch_httpd_misc_handlers, handle_welcome_req, <<"Welcome">>}
+favicon.ico = {couch_httpd_misc_handlers, handle_favicon_req, "/opt/bigcouch/share/www"}
+
+_utils = {couch_httpd_misc_handlers, handle_utils_dir_req, "/opt/bigcouch/share/www"}
+_all_dbs = {couch_httpd_misc_handlers, handle_all_dbs_req}
+_active_tasks = {couch_httpd_misc_handlers, handle_task_status_req}
+_config = {couch_httpd_misc_handlers, handle_config_req}
+_replicate = {couch_httpd_misc_handlers, handle_replicate_req}
+_uuids = {couch_httpd_misc_handlers, handle_uuids_req}
+_restart = {couch_httpd_misc_handlers, handle_restart_req}
+_stats = {couch_httpd_stats_handlers, handle_stats_req}
+_log = {couch_httpd_misc_handlers, handle_log_req}
+_session = {couch_httpd_auth, handle_session_req}
+_oauth = {couch_httpd_oauth, handle_oauth_req}
+_system = {chttpd_misc, handle_system_req}
+
+[httpd_db_handlers]
+_view_cleanup = {couch_httpd_db, handle_view_cleanup_req}
+_compact = {couch_httpd_db, handle_compact_req}
+_design = {couch_httpd_db, handle_design_req}
+_temp_view = {couch_httpd_view, handle_temp_view_req}
+_changes = {couch_httpd_db, handle_changes_req}
+
+[httpd_design_handlers]
+_view = {couch_httpd_view, handle_view_req}
+_show = {couch_httpd_show, handle_doc_show_req}
+_list = {couch_httpd_show, handle_view_list_req}
+_info = {couch_httpd_db, handle_design_info_req}
+_rewrite = {couch_httpd_rewrite, handle_rewrite_req}
+_update = {couch_httpd_show, handle_doc_update_req}
+
+; enable external as an httpd handler, then link it with commands here.
+; note, this api is still under consideration.
+; [external]
+; mykey = /path/to/mycommand
+
+; Here you can setup commands for CouchDB to manage
+; while it is alive. It will attempt to keep each command
+; alive if it exits.
+; [os_daemons]
+; some_daemon_name = /path/to/script -with args
+
+
+[uuids]
+; Known algorithms:
+; random - 128 bits of random awesome
+; All awesome, all the time.
+; sequential - monotonically increasing ids with random increments
+; First 26 hex characters are random. Last 6 increment in
+; random amounts until an overflow occurs. On overflow, the
+; random prefix is regenerated and the process starts over.
+; utc_random - Time since Jan 1, 1970 UTC with microseconds
+; First 14 characters are the time in hex. Last 18 are random.
+algorithm = sequential
+
+[stats]
+; rate is in milliseconds
+rate = 1000
+; sample intervals are in seconds
+samples = [0, 60, 300, 900]
+
+[attachments]
+compression_level = 8 ; from 1 (lowest, fastest) to 9 (highest, slowest), 0 to disable compression
+compressible_types = text/*, application/javascript, application/json, application/xml
+
+[replicator]
+db = _replicator
+; Maximum replicaton retry count can be a non-negative integer or "infinity".
+max_replication_retry_count = 10
+max_http_sessions = 20
+max_http_pipeline_size = 50
+; set to true to validate peer certificates
+verify_ssl_certificates = false
+; file containing a list of peer trusted certificates (PEM format)
+; ssl_trusted_certificates_file = /etc/ssl/certs/ca-certificates.crt
+; maximum peer certificate depth (must be set even if certificate validation is off)
+ssl_certificate_max_depth = 3
diff --git a/puppet/modules/couchdb/templates/bigcouch/vm.args b/puppet/modules/couchdb/templates/bigcouch/vm.args
new file mode 100644
index 00000000..4618a52c
--- /dev/null
+++ b/puppet/modules/couchdb/templates/bigcouch/vm.args
@@ -0,0 +1,32 @@
+# Each node in the system must have a unique name. A name can be short
+# (specified using -sname) or it can by fully qualified (-name). There can be
+# no communication between nodes running with the -sname flag and those running
+# with the -name flag.
+-name bigcouch
+
+# All nodes must share the same magic cookie for distributed Erlang to work.
+# Comment out this line if you synchronized the cookies by other means (using
+# the ~/.erlang.cookie file, for example).
+-setcookie <%= scope.lookupvar('couchdb::bigcouch_cookie') %>
+
+# Tell SASL not to log progress reports
+-sasl errlog_type error
+
+# Use kernel poll functionality if supported by emulator
++K true
+
+# Start a pool of asynchronous IO threads
++A 16
+
+# Comment this line out to enable the interactive Erlang shell on startup
++Bd -noinput
+
+# read config files
+# otherwise /etc/couchdb/local.d/admin.ini wouldn't be read mysteriously
+-couch_ini /etc/couchdb/default.ini /etc/couchdb/local.ini /etc/couchdb/local.d/admin.ini /etc/couchdb/default.ini /etc/couchdb/local.ini /etc/couchdb/local.d/admin.ini
+#
+
+# make firewalling easier, see
+# http://stackoverflow.com/questions/8459949/bigcouch-cluster-connection-issue#comment10467603_8463814
+
+-kernel inet_dist_listen_min <%= scope.lookupvar('couchdb::ednp_port') %> inet_dist_use_interface "{127,0,0,1}"
diff --git a/puppet/modules/couchdb/templates/couchdb-backup.py.erb b/puppet/modules/couchdb/templates/couchdb-backup.py.erb
new file mode 100644
index 00000000..c49df65b
--- /dev/null
+++ b/puppet/modules/couchdb/templates/couchdb-backup.py.erb
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+# file manage by puppet
+
+import os
+import gzip
+import tarfile
+import datetime
+import urllib2
+import simplejson
+import couchdb.tools.dump
+from os.path import join
+
+DB_URL="http://127.0.0.1:5984"
+DUMP_DIR="<%= backupdir %>"
+TODAY=datetime.datetime.today().strftime("%A").lower()
+
+ftar = os.path.join(DUMP_DIR,"%s.tar" % TODAY)
+tmp_ftar = os.path.join(DUMP_DIR,"_%s.tar" % TODAY)
+tar = tarfile.open(tmp_ftar, "w")
+
+databases = simplejson.load(urllib2.urlopen("%s/_all_dbs" % DB_URL))
+
+for db in databases:
+ db_file = os.path.join(DUMP_DIR,"%s.gz" % db)
+ f = gzip.open(db_file, 'wb')
+ couchdb.tools.dump.dump_db(os.path.join(DB_URL,db), output=f)
+ f.close()
+ tar.add(db_file,"%s.gz" % db)
+ os.remove(db_file)
+
+tar.close()
+os.rename(tmp_ftar,ftar)