[DRE-commits] [redmine] 05/05: Add multi-tenancy support
Antonio Terceiro
terceiro at moszumanska.debian.org
Fri Jan 22 13:01:26 UTC 2016
This is an automated email from the git hooks/post-receive script.
terceiro pushed a commit to branch patch-queue/master
in repository redmine.
commit 2c035ff89dfcba456adc9e5d788ecc375d97d66c
Author: Antonio Terceiro <terceiro at debian.org>
Date: Mon Sep 21 12:31:42 2015 -0300
Add multi-tenancy support
This is an improved version of the combination of a few patches that
were carried in the Redmine package for Debian GNU/Linux for a few
years.
Documentation is provided as a man page produced by
`./bin/redmine-instances help`
Signed-off-by: Antonio Terceiro <terceiro at debian.org>
Signed-off-by: Jérémy Lal <kapouer at melix.org>
Signed-off-by: Ondřej Surý <ondrej at sury.org>
Gbp-Pq: Name 0004-Add-multi-tenancy-support.patch
---
.gitignore | 1 +
Gemfile | 9 +-
app/models/attachment.rb | 4 +-
bin/redmine-instances | 280 +++++++++++++++++++++
config/application.rb | 1 +
config/multitenancy_environment.rb | 42 ++++
.../lib/open_id_authentication.rb | 2 +-
lib/redmine/configuration.rb | 4 +-
lib/redmine/export/pdf.rb | 2 +-
lib/redmine/multi_tenancy.rb | 43 ++++
lib/redmine/scm/adapters/abstract_adapter.rb | 2 +-
lib/tasks/initializers.rake | 2 +-
12 files changed, 383 insertions(+), 9 deletions(-)
diff --git a/.gitignore b/.gitignore
index 173b030..d9e39ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@
/db/*.sqlite3
/db/schema.rb
/files/*
+/instances
/lib/redmine/scm/adapters/mercurial/redminehelper.pyc
/lib/redmine/scm/adapters/mercurial/redminehelper.pyo
/log/*.log*
diff --git a/Gemfile b/Gemfile
index 24d8257..cd7936b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -52,7 +52,14 @@ end
# configuration file
require 'erb'
require 'yaml'
-database_file = File.join(File.dirname(__FILE__), "config/database.yml")
+
+# XXX this duplicates logic in lib/redmine/multi_tenancy.rb, but we can't load
+# XXX that at this point. X_DEBIAN_SITEID is kept for compatibility with
+# existing XXX Debian installations.
+instance = ENV['X_DEBIAN_SITEID'] || ENV['REDMINE_INSTANCE']
+root = instance && File.join(File.dirname(__FILE__), 'instances', instance) || File.dirname(__FILE__)
+database_file = File.join(root, "config/database.yml")
+
if File.exist?(database_file)
database_config = YAML::load(ERB.new(IO.read(database_file)).result)
adapters = database_config.values.map {|c| c['adapter']}.compact.uniq
diff --git a/app/models/attachment.rb b/app/models/attachment.rb
index d1d7c6d..8037666 100644
--- a/app/models/attachment.rb
+++ b/app/models/attachment.rb
@@ -47,10 +47,10 @@ class Attachment < ActiveRecord::Base
"LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id")
cattr_accessor :storage_path
- @@storage_path = Redmine::Configuration['attachments_storage_path'] || File.join(Rails.root, "files")
+ @@storage_path = Redmine::Configuration['attachments_storage_path'] || File.join(Redmine.root, "files")
cattr_accessor :thumbnails_storage_path
- @@thumbnails_storage_path = File.join(Rails.root, "tmp", "thumbnails")
+ @@thumbnails_storage_path = File.join(Redmine.root, "tmp", "thumbnails")
before_create :files_to_final_location
after_destroy :delete_from_disk
diff --git a/bin/redmine-instances b/bin/redmine-instances
new file mode 100755
index 0000000..5bf3159
--- /dev/null
+++ b/bin/redmine-instances
@@ -0,0 +1,280 @@
+#!/bin/sh
+
+# Copyright (C) 2015 Antonio Terceiro
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+set -eu
+
+REDMINE_INSTANCES_OWNERSHIP=$(stat -c %U:%G $(dirname $0/..))
+REDMINE_INSTANCES_FOLLOW_FHS=""
+REDMINE_INSTANCES_ROOT="$(readlink -f $(dirname $0)/..)/instances"
+
+if [ -r /etc/default/redmine ]; then
+ . /etc/default/redmine
+fi
+
+usage() {
+ local rc="${1:-0}"
+ local program="$(basename $0)"
+ echo "usage: $program list"
+ echo "usage: $program create INSTANCE"
+ echo "usage: $program remove INSTANCE"
+ echo "usage: $program help"
+ exit $rc
+}
+
+# map directories support for placing the files in the correct locations
+# according to the FHS (http://www.pathname.com/fhs/). Required for official
+# Debian packages.
+fhs_directory_mapping() {
+ if [ -n "$REDMINE_INSTANCES_FOLLOW_FHS" ]; then
+ local instance="$1"
+ local dir="$2"
+ case "$dir" in
+ config)
+ echo "/etc/redmine/$instance"
+ ;;
+ log)
+ echo "/var/log/redmine/$instance"
+ ;;
+ files)
+ echo "/var/lib/redmine/$instance/files"
+ ;;
+ tmp)
+ echo "/var/cache/redmine/$instance"
+ ;;
+ esac
+ fi
+}
+
+make_instance_directory() {
+ local instance="$1"
+ local dirname="$2"
+ local target="$REDMINE_INSTANCES_ROOT/$instance/$dirname"
+ local dir=$(fhs_directory_mapping "$instance" "$dirname")
+ if [ -z "$dir" ]; then
+ dir="$target"
+ fi
+
+ mkdir -p "$dir"
+ chown -R "$REDMINE_INSTANCES_OWNERSHIP" "$dir"
+
+ if [ "$dir" != $target ]; then
+ mkdir -p $(dirname "$target")
+ ln -sfT "$dir" "$target"
+ fi
+}
+
+call_pager() {
+ if [ -t 1 ]; then
+ man -l -
+ else
+ cat
+ fi
+}
+
+if [ $# -lt 1 ]; then
+ usage 1
+fi
+cmd="$1"
+shift
+
+case "$cmd" in
+ list)
+ if [ -d "$REDMINE_INSTANCES_ROOT" ]; then
+ ls -1 "$REDMINE_INSTANCES_ROOT"
+ fi
+ ;;
+
+ create)
+ instance="${1:-}"
+ [ -n "$instance" ] || usage 1
+
+ make_instance_directory "$instance" config
+ make_instance_directory "$instance" log
+ make_instance_directory "$instance" files
+ make_instance_directory "$instance" tmp
+ ;;
+
+ remove)
+ instance="${1:-}"
+ [ -n "$instance" ] || usage 1
+
+ printf "Are you sure you want to remove instance $instance? There is no going back [y/N] "
+ read confirm
+ if [ "$confirm" = 'y' -o "$confirm" = 'Y' ]; then
+ rm -rf "$REDMINE_INSTANCES_ROOT/$instance"
+ else
+ echo "Aborted."
+ fi
+ ;;
+
+ help)
+ sed -e '1,/<<DOCUMENTATION$/d; /^DOCUMENTATION/d' "$0" \
+ | pod2man --name='redmine-instances' --center "" --release "" - \
+ | call_pager
+ ;;
+
+ *)
+ if [ -n "$cmd" ]; then
+ echo "E: invalid command: $command"
+ fi
+ usage 1
+ ;;
+esac
+
+exit
+: <<DOCUMENTATION
+=encoding UTF-8
+
+=head1 redmine-instances
+
+redmine-instances - manage multiple instances in a single Redmine install
+
+=head1 SYNOPSIS
+
+B<redmine-instances> I<COMMAND> [I<INSTANCE>]
+
+See "I<THE REDMINE MULTITENANCY SYSTEM>" below for a description of how it all
+works.
+
+=head1 COMMANDS
+
+=head2 list
+
+Lists the existing instances.
+
+=head2 create INSTANCE
+
+Creates an instance. This command is idempotent, so calling it multiple times
+is not a problem. Not only that, but existing instances will also be repaired
+and gain the expected directory structure.
+
+=head2 remove INSTANCE
+
+Removes an instance. After this command finished, all files in the instance
+directory will be removed. Any MySQL/PostgreSQL databases will I<not> be
+removed, and must be removed manually.
+
+=head2 help
+
+Displays this documentation.
+
+=head1 THE REDMINE MULTITENANCY SYSTEM
+
+The Redmine multitenancy system is implemented in such a way that it is
+unobstrusive: if you don't do anything to configure multiple instances, Redmine
+will just work as it normally does.
+
+To run redmine against a specific instance, you must set the
+I<REDMINE_INSTANCE> environment variable for Redmine processes. Since such
+environment variable is queried at compile times in some parts of the Redmine
+codebase, this means that multiple Redmine instances must be handled as
+I<separate processes>.
+
+When Redmine is started with the I<REDMINE_INSTANCE> environment variable set
+to an instance name, then a few parameters will be adjusted:
+
+I<Logs> will be stored in /path/to/redmine/instances/I<$REDMINE_INSTANCE>/log/
+
+I<Database configuration> will be read from
+/path/to/redmine/instances/I<$REDMINE_INSTANCE>/config/database.yml
+
+I<Temporary files>, such as caches, will be stored in
+/path/to/redmine/instances/I<$REDMINE_INSTANCE>/tmp/
+
+I<File attachments> will be stored in
+/path/to/redmine/instances/I<$REDMINE_INSTANCE>/files/
+
+=head1 NOTES FOR DEBIAN GNU/LINUX
+
+When using the official redmine Debian package, the directories mentioned above
+will actually be symbolic links to the expected locations according to FHS,
+namely:
+
+I<config> will be a link to /etc/redmine/I<$REDMINE_INSTANCE>/
+
+I<log> will be a link /var/log/redmine/I<$REDMINE_INSTANCE>/
+
+I<tmp> will be a link /var/cache/redmine/I<$REDMINE_INSTANCE>/
+
+I<files> will be stored directly in /var/lib/redmine/I<$REDMINE_INSTANCE>/files/
+
+Users of the Debian package can also find B<redmine-instances> in I<$PATH>,
+and can call it directly (as root) from any directory.
+
+=head1 EXAMPLE: CREATING AND USING A NEW INSTANCE
+
+=head2 Creating the directory structure
+
+ $ cd /path/to/redmine
+ $ ./bin/redmine-instances create myinstance
+ $ edit instances/myinstance/config/database.yml
+ [...]
+ $ export REDMINE_INSTANCE=myinstance
+ $ bundle exec rake db:migrate
+ $ bundle exec rake redmine:load_default_data
+ $ bundle exec rake generate_secret_token
+
+=head2 Web server configuration
+
+For Passenger with Apache, you will want something like the example below. Note
+the I<PassengerAppGroupName>: it must be set to a different value for each of
+your Redmine instances, and PassengerAppGroupName will make sure that all your
+instances are isolated from each other.
+
+ <VirtualHost *:80>
+ ServerName my.domain.name
+ RailsEnv production
+ SetEnv REDMINE_INSTANCE "myinstance"
+ PassengerAppGroupName redmine_myinstance
+ PassengerDefaultUser www-data
+ DocumentRoot /path/to/redmine/public
+ <Directory "/path/to/redmine/public">
+ Allow from all
+ Options -MultiViews
+ Require all granted
+ </Directory>
+ </VirtualHost>
+
+=head1 SEE ALSO
+
+Filesystem Hierarchy Standard (FHS), I<http://www.pathname.com/fhs/>.
+
+=head1 COPYRIGHT AND AUTHORS
+
+B<redmine-instances> is Copyright (C) 2015, Antonio Terceiro.
+
+The Multi-Tenancy support for Redmine was originally developed for Debian
+GNU/Linux, and is Copyright (C) 2014-2015 Antonio Terceiro, 2011-2014 Jérémy
+Lal, 2011-2014 Ondrej Surý.
+
+B<Redmine> is Copyright (C) 2006-2015, Jean-Philippe Lang.
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; either version 2 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+DOCUMENTATION
diff --git a/config/application.rb b/config/application.rb
index 0f75247..b659f30 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -65,6 +65,7 @@ module RedmineApp
config.session_store :cookie_store, :key => '_redmine_session'
+ instance_eval File.read(File.join(File.dirname(__FILE__), 'multitenancy_environment.rb'))
if File.exists?(File.join(File.dirname(__FILE__), 'additional_environment.rb'))
instance_eval File.read(File.join(File.dirname(__FILE__), 'additional_environment.rb'))
end
diff --git a/config/multitenancy_environment.rb b/config/multitenancy_environment.rb
new file mode 100644
index 0000000..f3d9248
--- /dev/null
+++ b/config/multitenancy_environment.rb
@@ -0,0 +1,42 @@
+# Copyright (C) 2014-2015 Antonio Terceiro
+# Copyright (C) 2011-2014 Jérémy Lal
+# Copyright (C) 2011-2014 Ondřej Surý
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require 'redmine/multi_tenancy'
+
+if Redmine.tenant?
+
+ # per-instance tmp/local cache directories
+ config.paths['tmp'] = Redmine.root.join('tmp').to_s
+ config.cache_store = :file_store, Redmine.root.join('tmp', 'cache').to_s
+
+ # per-instance logs
+ config.paths['log'] = Redmine.root.join('log', Rails.env + '.log').to_s
+
+ # per-instance database configuration
+ config.paths['config/database'] = Redmine.root.join('config', 'database.yml').to_s
+
+ # per-instance session cookie
+ path = ENV.fetch('RAILS_RELATIVE_URL_ROOT', '/')
+ key = Redmine.name || '_redmine_session'
+ config.session_store :cookie_store, :key => key, :path => path
+ secret_file = Redmine.root.join('config', 'secret_key.txt')
+ if File.exists?(secret_file)
+ config.secret_key_base = File.read(secret_file).strip
+ end
+
+end
diff --git a/lib/plugins/open_id_authentication/lib/open_id_authentication.rb b/lib/plugins/open_id_authentication/lib/open_id_authentication.rb
index 48be8dd..1bcbbe4 100644
--- a/lib/plugins/open_id_authentication/lib/open_id_authentication.rb
+++ b/lib/plugins/open_id_authentication/lib/open_id_authentication.rb
@@ -25,7 +25,7 @@ module OpenIdAuthentication
OpenID::Store::Memory.new
when :file
require 'openid/store/filesystem'
- OpenID::Store::Filesystem.new(Rails.root.join('tmp/openids'))
+ OpenID::Store::Filesystem.new(Redmine.root.join('tmp/openids'))
when :memcache
require 'memcache'
require 'openid/store/memcache'
diff --git a/lib/redmine/configuration.rb b/lib/redmine/configuration.rb
index 9109a00..3d93ff7 100644
--- a/lib/redmine/configuration.rb
+++ b/lib/redmine/configuration.rb
@@ -32,7 +32,7 @@ module Redmine
# * <tt>:file</tt>: the configuration file to load (default: config/configuration.yml)
# * <tt>:env</tt>: the environment to load the configuration for (default: Rails.env)
def load(options={})
- filename = options[:file] || File.join(Rails.root, 'config', 'configuration.yml')
+ filename = options[:file] || File.join(Redmine.root, 'config', 'configuration.yml')
env = options[:env] || Rails.env
@config = @defaults.dup
@@ -104,7 +104,7 @@ module Redmine
end
def load_deprecated_email_configuration(env)
- deprecated_email_conf = File.join(Rails.root, 'config', 'email.yml')
+ deprecated_email_conf = File.join(Redmine.root, 'config', 'email.yml')
if File.file?(deprecated_email_conf)
warn "Storing outgoing emails configuration in config/email.yml is deprecated. You should now store it in config/configuration.yml using the email_delivery setting."
@config.merge!({'email_delivery' => load_from_yaml(deprecated_email_conf, env)})
diff --git a/lib/redmine/export/pdf.rb b/lib/redmine/export/pdf.rb
index e3861b6..31ee580 100644
--- a/lib/redmine/export/pdf.rb
+++ b/lib/redmine/export/pdf.rb
@@ -27,7 +27,7 @@ module Redmine
attr_accessor :footer_date
def initialize(lang, orientation='P')
- @@k_path_cache = Rails.root.join('tmp', 'pdf')
+ @@k_path_cache = Redmine.root.join('tmp', 'pdf')
FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache)
set_language_if_valid lang
super(orientation, 'mm', 'A4')
diff --git a/lib/redmine/multi_tenancy.rb b/lib/redmine/multi_tenancy.rb
new file mode 100644
index 0000000..0d100f7
--- /dev/null
+++ b/lib/redmine/multi_tenancy.rb
@@ -0,0 +1,43 @@
+# Copyright (C) 2014-2015 Antonio Terceiro
+# Copyright (C) 2011-2014 Jérémy Lal
+# Copyright (C) 2011-2014 Ondřej Surý
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module Redmine
+
+ class << self
+
+ def tenant
+ # X_DEBIAN_SITEID is kept here for backwards compatibility with existing
+ # Debian installations.
+ ENV['X_DEBIAN_SITEID'] || ENV['REDMINE_INSTANCE']
+ end
+
+ def tenant?
+ !tenant.nil?
+ end
+
+ def root
+ if tenant
+ Pathname.new(File.join(Rails.root, 'instances', tenant))
+ else
+ Rails.root
+ end
+ end
+
+ end
+
+end
diff --git a/lib/redmine/scm/adapters/abstract_adapter.rb b/lib/redmine/scm/adapters/abstract_adapter.rb
index 98683b0..f525bb7 100644
--- a/lib/redmine/scm/adapters/abstract_adapter.rb
+++ b/lib/redmine/scm/adapters/abstract_adapter.rb
@@ -218,7 +218,7 @@ module Redmine
if @stderr_log_file.nil?
writable = false
path = Redmine::Configuration['scm_stderr_log_file'].presence
- path ||= Rails.root.join("log/#{Rails.env}.scm.stderr.log").to_s
+ path ||= Redmine.root.join("log/#{Rails.env}.scm.stderr.log").to_s
if File.exists?(path)
if File.file?(path) && File.writable?(path)
writable = true
diff --git a/lib/tasks/initializers.rake b/lib/tasks/initializers.rake
index 6da60c1..eb96177 100644
--- a/lib/tasks/initializers.rake
+++ b/lib/tasks/initializers.rake
@@ -15,7 +15,7 @@ file 'config/initializers/secret_token.rb' do
# change this key, all old sessions will become invalid! Make sure the
# secret is at least 30 characters and all random, no regular words or
# you'll be exposed to dictionary attacks.
-RedmineApp::Application.config.secret_key_base = '#{secret}'
+RedmineApp::Application.config.secret_key_base = [Redmine.tenant, '#{secret}'].compact.join(':')
EOF
end
end
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/redmine.git
More information about the Pkg-ruby-extras-commits
mailing list