redmine-installer 1.0.7 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +3 -15
  5. data/README.md +49 -177
  6. data/bin/redmine +4 -5
  7. data/lib/redmine-installer/backup.rb +13 -40
  8. data/lib/redmine-installer/cli.rb +92 -61
  9. data/lib/redmine-installer/command.rb +63 -117
  10. data/lib/redmine-installer/configuration.rb +148 -0
  11. data/lib/redmine-installer/database.rb +204 -0
  12. data/lib/redmine-installer/environment.rb +21 -0
  13. data/lib/redmine-installer/errors.rb +7 -0
  14. data/lib/redmine-installer/install.rb +37 -42
  15. data/lib/redmine-installer/logger.rb +83 -0
  16. data/lib/redmine-installer/package.rb +180 -0
  17. data/lib/redmine-installer/patches/ruby.rb +35 -0
  18. data/lib/redmine-installer/patches/tty.rb +16 -0
  19. data/lib/redmine-installer/profile.rb +24 -55
  20. data/lib/redmine-installer/redmine.rb +551 -0
  21. data/lib/redmine-installer/spec/spec.rb +81 -0
  22. data/lib/redmine-installer/task.rb +18 -77
  23. data/lib/redmine-installer/task_module.rb +12 -0
  24. data/lib/redmine-installer/upgrade.rb +51 -59
  25. data/lib/redmine-installer/utils.rb +46 -233
  26. data/lib/redmine-installer/version.rb +2 -4
  27. data/lib/redmine-installer.rb +69 -56
  28. data/redmine-installer.gemspec +20 -19
  29. data/spec/custom_matchers.rb +21 -0
  30. data/spec/installer_helper.rb +107 -0
  31. data/spec/installer_process.rb +82 -0
  32. data/spec/lib/backup_restore_spec.rb +81 -0
  33. data/spec/lib/install_spec.rb +125 -36
  34. data/spec/lib/upgrade_spec.rb +73 -52
  35. data/spec/packages/redmine-3.1.0.zip +0 -0
  36. data/spec/packages/redmine-3.2.0.zip +0 -0
  37. data/spec/packages/redmine-3.3.0-bad-migration.zip +0 -0
  38. data/spec/packages/redmine-3.3.0.zip +0 -0
  39. data/spec/packages/something-else.zip +0 -0
  40. data/spec/packages_helper.rb +19 -0
  41. data/spec/shared_contexts.rb +13 -0
  42. data/spec/spec_helper.rb +34 -18
  43. metadata +62 -63
  44. data/lib/redmine-installer/config_param.rb +0 -100
  45. data/lib/redmine-installer/error.rb +0 -5
  46. data/lib/redmine-installer/exec.rb +0 -158
  47. data/lib/redmine-installer/ext/module.rb +0 -7
  48. data/lib/redmine-installer/ext/string.rb +0 -15
  49. data/lib/redmine-installer/git.rb +0 -51
  50. data/lib/redmine-installer/helper.rb +0 -5
  51. data/lib/redmine-installer/helpers/generate_config.rb +0 -29
  52. data/lib/redmine-installer/locales/cs.yml +0 -147
  53. data/lib/redmine-installer/locales/en.yml +0 -154
  54. data/lib/redmine-installer/plugin.rb +0 -9
  55. data/lib/redmine-installer/plugins/base.rb +0 -24
  56. data/lib/redmine-installer/plugins/database.rb +0 -180
  57. data/lib/redmine-installer/plugins/email_sending.rb +0 -82
  58. data/lib/redmine-installer/plugins/redmine_plugin.rb +0 -82
  59. data/lib/redmine-installer/plugins/web_server.rb +0 -26
  60. data/lib/redmine-installer/step.rb +0 -16
  61. data/lib/redmine-installer/steps/backup.rb +0 -125
  62. data/lib/redmine-installer/steps/base.rb +0 -79
  63. data/lib/redmine-installer/steps/database_config.rb +0 -15
  64. data/lib/redmine-installer/steps/email_config.rb +0 -11
  65. data/lib/redmine-installer/steps/env_check.rb +0 -20
  66. data/lib/redmine-installer/steps/install.rb +0 -23
  67. data/lib/redmine-installer/steps/load_package.rb +0 -226
  68. data/lib/redmine-installer/steps/move_redmine.rb +0 -22
  69. data/lib/redmine-installer/steps/redmine_root.rb +0 -52
  70. data/lib/redmine-installer/steps/upgrade.rb +0 -57
  71. data/lib/redmine-installer/steps/validation.rb +0 -38
  72. data/lib/redmine-installer/steps/webserver_config.rb +0 -22
  73. data/spec/load_redmine.rb +0 -24
@@ -0,0 +1,21 @@
1
+ module RedmineInstaller
2
+ class Environment < TaskModule
3
+
4
+ def check
5
+ if user_is_root? && !task.options.enable_user_root
6
+ error 'You cannot install redmine under root. Please change user.'
7
+ end
8
+
9
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new(RedmineInstaller::MIN_SUPPORTED_RUBY)
10
+ error "You are using unsupported ruby. Please install at least #{RedmineInstaller::MIN_SUPPORTED_RUBY}."
11
+ end
12
+
13
+ logger.info 'Environemnt checked'
14
+ end
15
+
16
+ def user_is_root?
17
+ Process.euid == 0
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module RedmineInstaller
2
+ class Error < StandardError
3
+ end
4
+
5
+ class ProfileError < Error
6
+ end
7
+ end
@@ -1,50 +1,45 @@
1
- ##
2
- # Install redmine
3
- #
4
- # You can instal redmine package from archive or git.
5
- #
6
- # == Steps:
7
- # 1. Redmine root - where should be new redmine located
8
- # 2. Load package - extract package
9
- # 3. Database configuration - you can choose type of DB which you want to use
10
- # 4. Email sending configuration - email sending configuration
11
- # 5. Install - install commands are executed
12
- # 6. Moving redmine - redmine is moved from temporarily folder to given redmine_root
13
- # 7. Webserve configuration - generating webserver configuration
14
- #
15
- # == Usage:
16
- #
17
- # From archive::
18
- # Supported archives are .zip and .tar.gz.
19
- #
20
- # # minimal
21
- # redmine install PATH_TO_PACKAGE
22
- # redmine install REDMINE_VERSION
23
- #
24
- # # full
25
- # redmine install PATH_TO_PACKAGE --env ENV1,ENV2,ENV3
26
- #
27
- module Redmine::Installer
1
+ module RedmineInstaller
28
2
  class Install < Task
29
3
 
30
- STEPS = [
31
- step::EnvCheck,
32
- step::RedmineRoot,
33
- step::LoadPackage,
34
- step::DatabaseConfig,
35
- step::EmailConfig,
36
- step::Install,
37
- step::MoveRedmine,
38
- step::WebserverConfig
39
- ]
4
+ def initialize(package, redmine_root, **options)
5
+ super(**options)
40
6
 
41
- attr_accessor :package
7
+ @environment = Environment.new(self)
8
+ @package = Package.new(self, package)
9
+ @target_redmine = Redmine.new(self, redmine_root)
10
+ @temp_redmine = Redmine.new(self)
11
+ end
12
+
13
+ def up
14
+ @temp_redmine.valid_options
15
+ @environment.check
16
+ @target_redmine.ensure_and_valid_root
17
+ @package.ensure_and_valid_package
18
+ @package.extract
19
+
20
+ @temp_redmine.root = @package.redmine_root
21
+
22
+ @temp_redmine.create_database_yml
23
+ @temp_redmine.create_configuration_yml
24
+ @temp_redmine.install
25
+
26
+ print_title('Finishing installation')
27
+ ok('Cleaning root'){ @target_redmine.delete_root }
28
+ ok('Moving redmine to target directory'){ @target_redmine.move_from(@temp_redmine) }
29
+ ok('Cleanning up'){ @package.clean_up }
30
+ ok('Moving installer log'){ logger.move_to(@target_redmine, suffix: 'install') }
31
+
32
+ puts
33
+ puts pastel.bold('Redmine was installed')
34
+ logger.info('Redmine was installed')
35
+ end
42
36
 
43
- def initialize(package, options={})
44
- self.package = package
45
- super(options)
37
+ def down
38
+ @temp_redmine.clean_up
39
+ @package.clean_up
46
40
 
47
- check_package
41
+ puts
42
+ puts "(Log is located on #{pastel.bold(logger.path)})"
48
43
  end
49
44
 
50
45
  end
@@ -0,0 +1,83 @@
1
+ require 'digest'
2
+
3
+ module RedmineInstaller
4
+ class Logger
5
+
6
+ def self.verify(log_file)
7
+ log_file = log_file.to_s
8
+
9
+ unless File.exist?(log_file)
10
+ puts "File '#{log_file}' does not exist."
11
+ exit(false)
12
+ end
13
+
14
+ content = File.open(log_file, &:to_a)
15
+ digest1 = content.pop
16
+ digest2 = Digest::SHA256.hexdigest(content.join)
17
+
18
+ if digest1 == digest2
19
+ puts RedmineInstaller.pastel.green("Logfile is OK. Digest verified.")
20
+ else
21
+ puts RedmineInstaller.pastel.red("Logfile is not OK. Digest wasn't verified.")
22
+ end
23
+ end
24
+
25
+ def initialize
26
+ if ENV['REDMINE_INSTALLER_LOGFILE']
27
+ @output = File.open(ENV['REDMINE_INSTALLER_LOGFILE'], 'w')
28
+ else
29
+ @output = Tempfile.create('redmine_installer.log')
30
+ end
31
+ end
32
+
33
+ def path
34
+ @output.path
35
+ end
36
+
37
+ def finish
38
+ close
39
+ digest = Digest::SHA256.file(path).hexdigest
40
+ File.open(path, 'a') { |f| f.write(digest) }
41
+ end
42
+
43
+ def close
44
+ @output.flush
45
+ @output.close
46
+ end
47
+
48
+ def move_to(redmine, suffix: '%d%m%Y_%H%M%S')
49
+ close
50
+
51
+ new_path = File.join(redmine.log_path, Time.now.strftime("redmine_installer_#{suffix}.log"))
52
+
53
+ FileUtils.mkdir_p(redmine.log_path)
54
+ FileUtils.mv(path, new_path)
55
+ @output = File.open(new_path, 'a+')
56
+ end
57
+
58
+ def info(*messages)
59
+ log(' INFO', *messages)
60
+ end
61
+
62
+ def error(*messages)
63
+ log('ERROR', *messages)
64
+ end
65
+
66
+ def warn(*messages)
67
+ log(' WARN', *messages)
68
+ end
69
+
70
+ def std(*messages)
71
+ log(' STD', *messages)
72
+ end
73
+
74
+ def log(severity, *messages)
75
+ messages.each do |message|
76
+ @output.puts("#{severity}: #{message}")
77
+ end
78
+
79
+ @output.flush
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,180 @@
1
+ require 'rubygems/package'
2
+ require 'net/http'
3
+ require 'zlib'
4
+ require 'uri'
5
+
6
+ module RedmineInstaller
7
+ class Package < TaskModule
8
+
9
+ SUPPORTED_ARCHIVE_FORMATS = ['.zip', '.gz', '.tgz']
10
+ TAR_LONGLINK = '././@LongLink'
11
+
12
+ attr_reader :package
13
+
14
+ def initialize(task, package)
15
+ super(task)
16
+ @package = package.to_s
17
+ end
18
+
19
+ def ensure_and_valid_package
20
+ if package.empty?
21
+ @package = prompt.ask('Path to package:', required: true)
22
+ end
23
+
24
+ if !File.exist?(@package)
25
+ if @package =~ /\Av?(\d\.\d\.\d)\Z/
26
+ @package = download_redmine($1)
27
+ else
28
+ error "File doesn't exist #{@package}"
29
+ end
30
+ end
31
+
32
+ @type = File.extname(@package)
33
+ unless SUPPORTED_ARCHIVE_FORMATS.include?(@type)
34
+ error "File #{@package} must have format: #{SUPPORTED_ARCHIVE_FORMATS.join(', ')}"
35
+ end
36
+ end
37
+
38
+ def extract
39
+ print_title('Extracting redmine package')
40
+
41
+ @temp_dir = Dir.mktmpdir
42
+
43
+ case @type
44
+ when '.zip'
45
+ extract_zip
46
+ when '.gz', '.tgz'
47
+ extract_tar_gz
48
+ end
49
+
50
+ logger.info("Package was loaded into #{@temp_dir}.")
51
+ end
52
+
53
+ # Move files from temp dir to target. First check if folder contains redmine
54
+ # or contains folder which contains redmine.
55
+ #
56
+ # Package can have format:
57
+ # |-- redmine-2
58
+ # |-- app
59
+ # `-- config
60
+ # ...
61
+ #
62
+ def redmine_root
63
+ root = @temp_dir
64
+
65
+ loop {
66
+ ls = Dir.glob(File.join(root, '*'))
67
+
68
+ if ls.size == 1
69
+ root = ls.first
70
+ else
71
+ break
72
+ end
73
+ }
74
+
75
+ root
76
+ end
77
+
78
+ def clean_up
79
+ @temp_dir && FileUtils.remove_entry_secure(@temp_dir)
80
+ @temp_file && FileUtils.remove_entry_secure(@temp_file)
81
+ end
82
+
83
+ private
84
+
85
+ def download_redmine(version)
86
+ @temp_file = Tempfile.new(['redmine', '.zip'])
87
+ @temp_file.binmode
88
+
89
+ uri = URI("http://www.redmine.org/releases/redmine-#{version}.zip")
90
+
91
+ Net::HTTP.start(uri.host, uri.port) do |http|
92
+ head = http.request_head(uri)
93
+
94
+ unless head.is_a?(Net::HTTPSuccess)
95
+ error "Cannot download redmine #{version}"
96
+ end
97
+
98
+ print_title("Downloading redmine #{version}")
99
+ progressbar = TTY::ProgressBar.new(PROGRESSBAR_FORMAT, total: head['content-length'].to_i, frequency: 2, clear: true)
100
+
101
+ http.get(uri) do |data|
102
+ @temp_file.write(data)
103
+ progressbar.advance(data.size)
104
+ end
105
+
106
+ progressbar.finish
107
+ end
108
+
109
+ logger.info("Redmine #{version} downloaded")
110
+
111
+ @temp_file.close
112
+ @temp_file.path
113
+ end
114
+
115
+ def extract_zip
116
+ Zip::File.open(@package) do |zip_file|
117
+ # Progressbar
118
+ progressbar = TTY::ProgressBar.new(PROGRESSBAR_FORMAT, total: zip_file.size, frequency: 2, clear: true)
119
+
120
+ zip_file.each do |entry|
121
+ dest_file = File.join(@temp_dir, entry.name)
122
+ FileUtils.mkdir_p(File.dirname(dest_file))
123
+
124
+ entry.extract(dest_file)
125
+ progressbar.advance(1)
126
+ end
127
+
128
+ progressbar.finish
129
+ end
130
+
131
+ end
132
+
133
+ # Extract .tar.gz archive
134
+ # based on http://dracoater.blogspot.cz/2013/10/extracting-files-from-targz-with-ruby.html
135
+ #
136
+ # Originally tar did not support paths longer than 100 chars. GNU tar is better and they
137
+ # implemented support for longer paths, but it was made through a hack called ././@LongLink.
138
+ # Shortly speaking, if you stumble upon an entry in tar archive which path equals to above
139
+ # mentioned ././@LongLink, that means that the following entry path is longer than 100 chars and
140
+ # is truncated. The full path of the following entry is actually the value of the current entry.
141
+ #
142
+ def extract_tar_gz
143
+ Gem::Package::TarReader.new(Zlib::GzipReader.open(@package)) do |tar|
144
+
145
+ # Progressbar
146
+ progressbar = TTY::ProgressBar.new(PROGRESSBAR_FORMAT, total: tar.count, frequency: 2, clear: true)
147
+
148
+ # tar.count move position pointer to end
149
+ tar.rewind
150
+
151
+ dest_file = nil
152
+ tar.each do |entry|
153
+ if entry.full_name == TAR_LONGLINK
154
+ dest_file = File.join(@temp_dir, entry.read.strip)
155
+ next
156
+ end
157
+ dest_file ||= File.join(@temp_dir, entry.full_name)
158
+ if entry.directory?
159
+ FileUtils.rm_rf(dest_file) unless File.directory?(dest_file)
160
+ FileUtils.mkdir_p(dest_file, mode: entry.header.mode, verbose: false)
161
+ elsif entry.file?
162
+ FileUtils.rm_rf(dest_file) unless File.file?(dest_file)
163
+ File.open(dest_file, 'wb') do |f|
164
+ f.write(entry.read)
165
+ end
166
+ FileUtils.chmod(entry.header.mode, dest_file, verbose: false)
167
+ elsif entry.header.typeflag == '2' # symlink
168
+ File.symlink(entry.header.linkname, dest_file)
169
+ end
170
+
171
+ dest_file = nil
172
+ progressbar.advance(1)
173
+ end
174
+
175
+ progressbar.finish
176
+ end
177
+ end
178
+
179
+ end
180
+ end
@@ -0,0 +1,35 @@
1
+ class Object
2
+
3
+ def blank?
4
+ false
5
+ end
6
+
7
+ def present?
8
+ true
9
+ end
10
+
11
+ end
12
+
13
+ class String
14
+
15
+ def blank?
16
+ empty?
17
+ end
18
+
19
+ def present?
20
+ !blank?
21
+ end
22
+
23
+ end
24
+
25
+ class NilClass
26
+
27
+ def blank?
28
+ true
29
+ end
30
+
31
+ def present?
32
+ false
33
+ end
34
+
35
+ end
@@ -0,0 +1,16 @@
1
+ # module TTY
2
+ # class ProgressBar
3
+ # # Every progressbar register new signal handlers
4
+ # def register_signals
5
+ # end
6
+ # end
7
+ # end
8
+
9
+ # module TTY
10
+ # class Prompt
11
+ # class List
12
+ # def keyescape(*)
13
+ # end
14
+ # end
15
+ # end
16
+ # end
@@ -1,73 +1,42 @@
1
- require 'fileutils'
1
+ require 'ostruct'
2
2
  require 'yaml'
3
3
 
4
- module Redmine::Installer
5
- class Profile
4
+ module RedmineInstaller
5
+ class Profile < OpenStruct
6
+ PROFILES_FILE = File.join(Dir.home, '.redmine-installer-profiles.yml')
6
7
 
7
- include Redmine::Installer::Utils
8
+ def self.get!(profile_id)
9
+ data = YAML.load_file(PROFILES_FILE) rescue nil
8
10
 
9
- CONFIG_FILE = File.join(Dir.home, '.redmine-installer-profiles.yml')
10
-
11
- def self.save(task)
12
- return unless check_writable
13
- return unless confirm(:do_you_want_save_step_for_further_use, true)
14
-
15
- profile = Profile.new(task)
16
- profile.save
17
-
18
- say t(:your_profile_can_be_used_as, id: profile.id), 2
19
- end
20
-
21
- def self.load(task, id)
22
- profile = Profile.new(task)
23
- profile.load(id)
24
- end
25
-
26
- def self.check_writable
27
- FileUtils.touch(CONFIG_FILE)
28
- File.writable?(CONFIG_FILE)
11
+ if data.is_a?(Hash) && data.has_key?(profile_id)
12
+ Profile.new(profile_id, data[profile_id])
13
+ else
14
+ raise RedmineInstaller::ProfileError, "Profile ID=#{profile_id} does not exist"
15
+ end
29
16
  end
30
17
 
31
- attr_accessor :task
32
-
33
- def initialize(task)
34
- self.task = task
18
+ attr_reader :id
35
19
 
36
- # Load profiles
37
- @data = YAML.load_file(CONFIG_FILE) rescue nil
38
-
39
- # Make empty Hash if there is no profiles
40
- @data = {} unless @data.is_a?(Hash)
41
- end
42
-
43
- def id
44
- @id ||= @data.keys.map(&:to_i).max.to_i + 1
20
+ def initialize(id=nil, data={})
21
+ super(data)
22
+ @id = id
45
23
  end
46
24
 
47
25
  def save
48
- # All steps save configuration which can be use again
49
- configuration = {}
50
- task.steps.each do |_, step|
51
- step.save(configuration)
52
- end
26
+ FileUtils.touch(PROFILES_FILE)
53
27
 
54
- @data[id] = configuration
28
+ all_data = YAML.load_file(PROFILES_FILE)
29
+ all_data = {} unless all_data.is_a?(Hash)
55
30
 
56
- File.open(CONFIG_FILE, 'w') {|f| f.puts(YAML.dump(@data))}
57
- end
58
-
59
- def load(id)
60
- @id = id.to_i
31
+ @id ||= all_data.keys.last.to_i + 1
61
32
 
62
- configuration = @data[@id]
33
+ all_data[@id] = to_h
63
34
 
64
- return {} if configuration.nil?
65
-
66
- task.steps.each do |_, step|
67
- step.load(configuration)
68
- end
35
+ File.write(PROFILES_FILE, YAML.dump(all_data))
69
36
 
70
- return configuration
37
+ puts "Profile was saved under ID=#{@id}"
38
+ rescue => e
39
+ puts "Profile could not be save due to #{e.message}"
71
40
  end
72
41
 
73
42
  end