diff options
Diffstat (limited to 'puppet/modules/couchdb')
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) | 
