]>
Commit | Line | Data |
---|---|---|
1 | #!/usr/bin/perl | |
2 | # BEGIN BPS TAGGED BLOCK {{{ | |
3 | # | |
4 | # COPYRIGHT: | |
5 | # | |
6 | # This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC | |
7 | # <sales@bestpractical.com> | |
8 | # | |
9 | # (Except where explicitly superseded by other copyright notices) | |
10 | # | |
11 | # | |
12 | # LICENSE: | |
13 | # | |
14 | # This work is made available to you under the terms of Version 2 of | |
15 | # the GNU General Public License. A copy of that license should have | |
16 | # been provided with this software, but in any event can be snarfed | |
17 | # from www.gnu.org. | |
18 | # | |
19 | # This work is distributed in the hope that it will be useful, but | |
20 | # WITHOUT ANY WARRANTY; without even the implied warranty of | |
21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
22 | # General Public License for more details. | |
23 | # | |
24 | # You should have received a copy of the GNU General Public License | |
25 | # along with this program; if not, write to the Free Software | |
26 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
27 | # 02110-1301 or visit their web page on the internet at | |
28 | # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. | |
29 | # | |
30 | # | |
31 | # CONTRIBUTION SUBMISSION POLICY: | |
32 | # | |
33 | # (The following paragraph is not intended to limit the rights granted | |
34 | # to you to modify and distribute this software under the terms of | |
35 | # the GNU General Public License and is only of importance to you if | |
36 | # you choose to contribute your changes and enhancements to the | |
37 | # community by submitting them to Best Practical Solutions, LLC.) | |
38 | # | |
39 | # By intentionally submitting any modifications, corrections or | |
40 | # derivatives to this work, or any other work intended for use with | |
41 | # Request Tracker, to Best Practical Solutions, LLC, you confirm that | |
42 | # you are the copyright holder for those contributions and you grant | |
43 | # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, | |
44 | # royalty-free, perpetual, license to use, copy, create derivative | |
45 | # works based on those contributions, and sublicense and distribute | |
46 | # those contributions and any derivatives thereof. | |
47 | # | |
48 | # END BPS TAGGED BLOCK }}} | |
49 | use strict; | |
50 | use warnings; | |
51 | ||
52 | use vars qw($Nobody $SystemUser $item); | |
53 | ||
54 | # fix lib paths, some may be relative | |
55 | BEGIN { # BEGIN RT CMD BOILERPLATE | |
56 | require File::Spec; | |
57 | require Cwd; | |
58 | my @libs = ("lib", "local/lib"); | |
59 | my $bin_path; | |
60 | ||
61 | for my $lib (@libs) { | |
62 | unless ( File::Spec->file_name_is_absolute($lib) ) { | |
63 | $bin_path ||= ( File::Spec->splitpath(Cwd::abs_path(__FILE__)) )[1]; | |
64 | $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); | |
65 | } | |
66 | unshift @INC, $lib; | |
67 | } | |
68 | ||
69 | } | |
70 | ||
71 | use Term::ReadKey; | |
72 | use Getopt::Long; | |
73 | use Data::GUID; | |
74 | ||
75 | $| = 1; # unbuffer all output. | |
76 | ||
77 | my %args = ( | |
78 | package => 'RT', | |
79 | ); | |
80 | GetOptions( | |
81 | \%args, | |
82 | 'action=s', | |
83 | 'force', 'debug', | |
84 | 'dba=s', 'dba-password=s', 'prompt-for-dba-password', 'package=s', | |
85 | 'datafile=s', 'datadir=s', 'skip-create', 'root-password-file=s', | |
86 | 'package=s', 'ext-version=s', | |
87 | 'upgrade-from=s', 'upgrade-to=s', | |
88 | 'help|h', | |
89 | ); | |
90 | ||
91 | no warnings 'once'; | |
92 | if ( $args{help} || ! $args{'action'} ) { | |
93 | require Pod::Usage; | |
94 | Pod::Usage::pod2usage({ verbose => 2 }); | |
95 | exit; | |
96 | } | |
97 | ||
98 | require RT; | |
99 | RT->LoadConfig(); | |
100 | RT->InitClasses(); | |
101 | ||
102 | # Force warnings to be output to STDERR if we're not already logging | |
103 | # them at a higher level | |
104 | RT->Config->Set( LogToSTDERR => 'warning') | |
105 | unless ( RT->Config->Get( 'LogToSTDERR' ) | |
106 | && RT->Config->Get( 'LogToSTDERR' ) =~ /^(debug|info|notice)$/ ); | |
107 | RT::InitLogging(); | |
108 | ||
109 | # get customized root password | |
110 | my $root_password; | |
111 | if ( $args{'root-password-file'} ) { | |
112 | open( my $fh, '<', $args{'root-password-file'} ) | |
113 | or die "Couldn't open 'args{'root-password-file'}' for reading: $!"; | |
114 | $root_password = <$fh>; | |
115 | chomp $root_password; | |
116 | my $min_length = RT->Config->Get('MinimumPasswordLength'); | |
117 | if ($min_length) { | |
118 | die | |
119 | "password needs to be at least $min_length long, please check file '$args{'root-password-file'}'" | |
120 | if length $root_password < $min_length; | |
121 | } | |
122 | close $fh; | |
123 | } | |
124 | ||
125 | ||
126 | # check and setup @actions | |
127 | my @actions = grep $_, split /,/, $args{'action'}; | |
128 | if ( @actions > 1 && $args{'datafile'} ) { | |
129 | print STDERR "You can not use --datafile option with multiple actions.\n"; | |
130 | exit(-1); | |
131 | } | |
132 | foreach ( @actions ) { | |
133 | unless ( /^(?:init|create|drop|schema|acl|indexes|coredata|insert|upgrade)$/ ) { | |
134 | print STDERR "$0 called with an invalid --action parameter.\n"; | |
135 | exit(-1); | |
136 | } | |
137 | if ( /^(?:init|drop|upgrade)$/ && @actions > 1 ) { | |
138 | print STDERR "You can not mix init, drop or upgrade action with any action.\n"; | |
139 | exit(-1); | |
140 | } | |
141 | } | |
142 | ||
143 | # convert init to multiple actions | |
144 | my $init = 0; | |
145 | if ( $actions[0] eq 'init' ) { | |
146 | if ($args{'skip-create'}) { | |
147 | @actions = qw(schema coredata insert); | |
148 | } else { | |
149 | @actions = qw(create schema acl coredata insert); | |
150 | } | |
151 | $init = 1; | |
152 | } | |
153 | ||
154 | # set options from environment | |
155 | foreach my $key(qw(Type Host Name User Password)) { | |
156 | next unless exists $ENV{ 'RT_DB_'. uc $key }; | |
157 | print "Using Database$key from RT_DB_". uc($key) ." environment variable.\n"; | |
158 | RT->Config->Set( "Database$key", $ENV{ 'RT_DB_'. uc $key }); | |
159 | } | |
160 | ||
161 | my $db_type = RT->Config->Get('DatabaseType') || ''; | |
162 | my $db_host = RT->Config->Get('DatabaseHost') || ''; | |
163 | my $db_port = RT->Config->Get('DatabasePort') || ''; | |
164 | my $db_name = RT->Config->Get('DatabaseName') || ''; | |
165 | my $db_user = RT->Config->Get('DatabaseUser') || ''; | |
166 | my $db_pass = RT->Config->Get('DatabasePassword') || ''; | |
167 | ||
168 | # load it here to get error immidiatly if DB type is not supported | |
169 | require RT::Handle; | |
170 | ||
171 | if ( $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name) ) { | |
172 | $db_name = File::Spec->catfile($RT::VarPath, $db_name); | |
173 | RT->Config->Set( DatabaseName => $db_name ); | |
174 | } | |
175 | ||
176 | my $dba_user = $args{'dba'} || $ENV{'RT_DBA_USER'} || RT->Config->Get('DatabaseAdmin') || ''; | |
177 | my $dba_pass = $args{'dba-password'} || $ENV{'RT_DBA_PASSWORD'}; | |
178 | ||
179 | if ($args{'skip-create'}) { | |
180 | $dba_user = $db_user; | |
181 | $dba_pass = $db_pass; | |
182 | } else { | |
183 | if ( !$args{force} && ( !defined $dba_pass || $args{'prompt-for-dba-password'} ) ) { | |
184 | $dba_pass = get_dba_password(); | |
185 | chomp $dba_pass if defined($dba_pass); | |
186 | } | |
187 | } | |
188 | ||
189 | my $version_word_regex = join '|', RT::Handle->version_words; | |
190 | my $version_dir = qr/^\d+\.\d+\.\d+(?:$version_word_regex)?\d*$/; | |
191 | ||
192 | print "Working with:\n" | |
193 | ."Type:\t$db_type\nHost:\t$db_host\nPort:\t$db_port\nName:\t$db_name\n" | |
194 | ."User:\t$db_user\nDBA:\t$dba_user" . ($args{'skip-create'} ? ' (No DBA)' : '') . "\n"; | |
195 | ||
196 | my $package = $args{'package'} || 'RT'; | |
197 | my $ext_version = $args{'ext-version'}; | |
198 | my $full_id = Data::GUID->new->as_string; | |
199 | ||
200 | my $log_actions = 0; | |
201 | if ($args{'package'} ne 'RT') { | |
202 | RT->ConnectToDatabase(); | |
203 | RT->InitSystemObjects(); | |
204 | $log_actions = 1; | |
205 | } | |
206 | ||
207 | foreach my $action ( @actions ) { | |
208 | no strict 'refs'; | |
209 | my ($status, $msg) = *{ 'action_'. $action }{'CODE'}->( %args ); | |
210 | error($action, $msg) unless $status; | |
211 | print $msg .".\n" if $msg; | |
212 | print "Done.\n"; | |
213 | } | |
214 | ||
215 | sub action_create { | |
216 | my %args = @_; | |
217 | my $dbh = get_system_dbh(); | |
218 | my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'create' ); | |
219 | return ($status, $msg) unless $status; | |
220 | ||
221 | print "Now creating a $db_type database $db_name for RT.\n"; | |
222 | return RT::Handle->CreateDatabase( $dbh ); | |
223 | } | |
224 | ||
225 | sub action_drop { | |
226 | my %args = @_; | |
227 | ||
228 | print "Dropping $db_type database $db_name.\n"; | |
229 | unless ( $args{'force'} ) { | |
230 | print <<END; | |
231 | ||
232 | About to drop $db_type database $db_name on $db_host (port '$db_port'). | |
233 | WARNING: This will erase all data in $db_name. | |
234 | ||
235 | END | |
236 | exit(-2) unless _yesno(); | |
237 | } | |
238 | ||
239 | my $dbh = get_system_dbh(); | |
240 | return RT::Handle->DropDatabase( $dbh ); | |
241 | } | |
242 | ||
243 | sub action_schema { | |
244 | my %args = @_; | |
245 | my $dbh = get_admin_dbh(); | |
246 | my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'schema' ); | |
247 | return ($status, $msg) unless $status; | |
248 | ||
249 | my $individual_id = Data::GUID->new->as_string(); | |
250 | my %upgrade_data = ( | |
251 | action => 'schema', | |
252 | filename => Cwd::abs_path($args{'datafile'} || $args{'datadir'} || ''), | |
253 | stage => 'before', | |
254 | full_id => $full_id, | |
255 | individual_id => $individual_id, | |
256 | ); | |
257 | $upgrade_data{'ext_version'} = $ext_version if $ext_version; | |
258 | RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions; | |
259 | ||
260 | print "Now populating database schema.\n"; | |
261 | my @ret = RT::Handle->InsertSchema( $dbh, $args{'datafile'} || $args{'datadir'} ); | |
262 | ||
263 | %upgrade_data = ( | |
264 | stage => 'after', | |
265 | individual_id => $individual_id, | |
266 | return_value => [ @ret ], | |
267 | ); | |
268 | RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions; | |
269 | ||
270 | return @ret; | |
271 | } | |
272 | ||
273 | sub action_acl { | |
274 | my %args = @_; | |
275 | my $dbh = get_admin_dbh(); | |
276 | my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'acl' ); | |
277 | return ($status, $msg) unless $status; | |
278 | ||
279 | my $individual_id = Data::GUID->new->as_string(); | |
280 | my %upgrade_data = ( | |
281 | action => 'acl', | |
282 | filename => Cwd::abs_path($args{'datafile'} || $args{'datadir'} || ''), | |
283 | stage => 'before', | |
284 | full_id => $full_id, | |
285 | individual_id => $individual_id, | |
286 | ); | |
287 | $upgrade_data{'ext_version'} = $ext_version if $ext_version; | |
288 | RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions; | |
289 | ||
290 | print "Now inserting database ACLs.\n"; | |
291 | my @ret = RT::Handle->InsertACL( $dbh, $args{'datafile'} || $args{'datadir'} ); | |
292 | ||
293 | %upgrade_data = ( | |
294 | stage => 'after', | |
295 | individual_id => $individual_id, | |
296 | return_value => [ @ret ], | |
297 | ); | |
298 | RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions; | |
299 | ||
300 | return @ret; | |
301 | } | |
302 | ||
303 | sub action_indexes { | |
304 | my %args = @_; | |
305 | RT->ConnectToDatabase; | |
306 | my $individual_id = Data::GUID->new->as_string(); | |
307 | my %upgrade_data = ( | |
308 | action => 'indexes', | |
309 | filename => Cwd::abs_path($args{'datafile'} || $args{'datadir'} || ''), | |
310 | stage => 'before', | |
311 | full_id => $full_id, | |
312 | individual_id => $individual_id, | |
313 | ); | |
314 | $upgrade_data{'ext_version'} = $ext_version if $ext_version; | |
315 | RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions; | |
316 | ||
317 | my $dbh = get_admin_dbh(); | |
318 | $RT::Handle = RT::Handle->new; | |
319 | $RT::Handle->dbh( $dbh ); | |
320 | RT::InitLogging(); | |
321 | ||
322 | print "Now inserting database indexes.\n"; | |
323 | my @ret = RT::Handle->InsertIndexes( $dbh, $args{'datafile'} || $args{'datadir'} ); | |
324 | ||
325 | $RT::Handle = RT::Handle->new; | |
326 | $RT::Handle->dbh( undef ); | |
327 | RT->ConnectToDatabase; | |
328 | %upgrade_data = ( | |
329 | stage => 'after', | |
330 | individual_id => $individual_id, | |
331 | return_value => [ @ret ], | |
332 | ); | |
333 | RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions; | |
334 | ||
335 | return @ret; | |
336 | } | |
337 | ||
338 | sub action_coredata { | |
339 | my %args = @_; | |
340 | $RT::Handle = RT::Handle->new; | |
341 | $RT::Handle->dbh( undef ); | |
342 | RT::ConnectToDatabase(); | |
343 | my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'coredata' ); | |
344 | return ($status, $msg) unless $status; | |
345 | ||
346 | print "Now inserting RT core system objects.\n"; | |
347 | return $RT::Handle->InsertInitialData; | |
348 | } | |
349 | ||
350 | sub action_insert { | |
351 | my %args = @_; | |
352 | $RT::Handle = RT::Handle->new; | |
353 | RT::Init(); | |
354 | $log_actions = 1; | |
355 | ||
356 | my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'insert' ); | |
357 | return ($status, $msg) unless $status; | |
358 | ||
359 | print "Now inserting data.\n"; | |
360 | my $file = $args{'datafile'}; | |
361 | $file = $RT::EtcPath . "/initialdata" if $init && !$file; | |
362 | $file ||= $args{'datadir'}."/content"; | |
363 | ||
364 | my $individual_id = Data::GUID->new->as_string(); | |
365 | my %upgrade_data = ( | |
366 | action => 'insert', | |
367 | filename => Cwd::abs_path($file), | |
368 | stage => 'before', | |
369 | full_id => $full_id, | |
370 | individual_id => $individual_id | |
371 | ); | |
372 | $upgrade_data{'ext_version'} = $ext_version if $ext_version; | |
373 | ||
374 | open my $handle, '<', $file or warn "Unable to open $file: $!"; | |
375 | $upgrade_data{content} = do {local $/; <$handle>} if $handle; | |
376 | ||
377 | RT->System->AddUpgradeHistory($package => \%upgrade_data); | |
378 | ||
379 | my @ret; | |
380 | ||
381 | my $upgrade = sub { @ret = $RT::Handle->InsertData( $file, $root_password ) }; | |
382 | ||
383 | for my $file (@{$args{backcompat} || []}) { | |
384 | my $lines = do {local $/; local @ARGV = ($file); <>}; | |
385 | my $sub = eval "sub {\n# line 1 $file\n$lines\n}"; | |
386 | unless ($sub) { | |
387 | warn "Failed to load backcompat $file: $@"; | |
388 | next; | |
389 | } | |
390 | my $current = $upgrade; | |
391 | $upgrade = sub { $sub->($current) }; | |
392 | } | |
393 | ||
394 | $upgrade->(); | |
395 | ||
396 | # XXX Reconnecting to insert the history entry | |
397 | # until we can sort out removing | |
398 | # the disconnect at the end of InsertData. | |
399 | RT->ConnectToDatabase(); | |
400 | ||
401 | %upgrade_data = ( | |
402 | stage => 'after', | |
403 | individual_id => $individual_id, | |
404 | return_value => [ @ret ], | |
405 | ); | |
406 | ||
407 | RT->System->AddUpgradeHistory($package => \%upgrade_data); | |
408 | ||
409 | my $db_type = RT->Config->Get('DatabaseType'); | |
410 | $RT::Handle->Disconnect() unless $db_type eq 'SQLite'; | |
411 | ||
412 | return @ret; | |
413 | } | |
414 | ||
415 | sub action_upgrade { | |
416 | my %args = @_; | |
417 | my $base_dir = $args{'datadir'} || "./etc/upgrade"; | |
418 | return (0, "Couldn't read dir '$base_dir' with upgrade data") | |
419 | unless -d $base_dir || -r _; | |
420 | ||
421 | my $upgrading_from = undef; | |
422 | do { | |
423 | if ( defined $upgrading_from ) { | |
424 | print "Doesn't match #.#.#: "; | |
425 | } else { | |
426 | print "Enter $args{package} version you're upgrading from: "; | |
427 | } | |
428 | $upgrading_from = $args{'upgrade-from'} || scalar <STDIN>; | |
429 | chomp $upgrading_from; | |
430 | $upgrading_from =~ s/\s+//g; | |
431 | } while $upgrading_from !~ /$version_dir/; | |
432 | ||
433 | my $upgrading_to = $RT::VERSION; | |
434 | return (0, "The current version $upgrading_to is lower than $upgrading_from") | |
435 | if RT::Handle::cmp_version( $upgrading_from, $upgrading_to ) > 0; | |
436 | ||
437 | return (1, "The version $upgrading_to you're upgrading to is up to date") | |
438 | if RT::Handle::cmp_version( $upgrading_from, $upgrading_to ) == 0; | |
439 | ||
440 | my @versions = get_versions_from_to($base_dir, $upgrading_from, undef); | |
441 | return (1, "No DB changes since $upgrading_from") | |
442 | unless @versions; | |
443 | ||
444 | if (RT::Handle::cmp_version($versions[-1], $upgrading_to) > 0) { | |
445 | print "\n***** There are upgrades for $versions[-1], which is later than $upgrading_to,\n"; | |
446 | print "***** which you are nominally upgrading to. Upgrading to $versions[-1] instead.\n"; | |
447 | $upgrading_to = $versions[-1]; | |
448 | } | |
449 | ||
450 | print "\nGoing to apply following upgrades:\n"; | |
451 | print map "* $_\n", @versions; | |
452 | ||
453 | { | |
454 | my $custom_upgrading_to = undef; | |
455 | do { | |
456 | if ( defined $custom_upgrading_to ) { | |
457 | print "Doesn't match #.#.#: "; | |
458 | } else { | |
459 | print "\nEnter $args{package} version if you want to stop upgrade at some point,\n"; | |
460 | print " or leave it blank if you want apply above upgrades: "; | |
461 | } | |
462 | $custom_upgrading_to = $args{'upgrade-to'} || scalar <STDIN>; | |
463 | chomp $custom_upgrading_to; | |
464 | $custom_upgrading_to =~ s/\s+//g; | |
465 | last unless $custom_upgrading_to; | |
466 | } while $custom_upgrading_to !~ /$version_dir/; | |
467 | ||
468 | if ( $custom_upgrading_to ) { | |
469 | return ( | |
470 | 0, "The version you entered ($custom_upgrading_to) is lower than\n" | |
471 | ."version you're upgrading from ($upgrading_from)" | |
472 | ) if RT::Handle::cmp_version( $upgrading_from, $custom_upgrading_to ) > 0; | |
473 | ||
474 | return (1, "The version you're upgrading to is up to date") | |
475 | if RT::Handle::cmp_version( $upgrading_from, $custom_upgrading_to ) == 0; | |
476 | ||
477 | if ( RT::Handle::cmp_version( $RT::VERSION, $custom_upgrading_to ) < 0 ) { | |
478 | print "Version you entered is greater than installed ($RT::VERSION).\n"; | |
479 | _yesno() or exit(-2); | |
480 | } | |
481 | # ok, checked everything no let's refresh list | |
482 | $upgrading_to = $custom_upgrading_to; | |
483 | @versions = get_versions_from_to($base_dir, $upgrading_from, $upgrading_to); | |
484 | ||
485 | return (1, "No DB changes between $upgrading_from and $upgrading_to") | |
486 | unless @versions; | |
487 | ||
488 | print "\nGoing to apply following upgrades:\n"; | |
489 | print map "* $_\n", @versions; | |
490 | } | |
491 | } | |
492 | ||
493 | unless ( $args{'force'} ) { | |
494 | print "\nIT'S VERY IMPORTANT TO BACK UP BEFORE THIS STEP\n\n"; | |
495 | _yesno() or exit(-2); | |
496 | } | |
497 | ||
498 | RT->ConnectToDatabase(); | |
499 | RT->InitSystemObjects(); | |
500 | $log_actions = 1; | |
501 | ||
502 | RT->System->AddUpgradeHistory($package => { | |
503 | type => 'full upgrade', | |
504 | action => 'upgrade', | |
505 | stage => 'before', | |
506 | from => $upgrading_from, | |
507 | to => $upgrading_to, | |
508 | versions => [@versions], | |
509 | full_id => $full_id, | |
510 | individual_id => $full_id | |
511 | }); | |
512 | ||
513 | # Ensure that the Attributes column is big enough to hold the | |
514 | # upgrade steps we're going to add; this step exists in 4.0.6 for | |
515 | # mysql, but that may be too late. Run it as soon as possible. | |
516 | if (RT->Config->Get('DatabaseType') eq 'mysql' | |
517 | and RT::Handle::cmp_version( $upgrading_from, '4.0.6') < 0) { | |
518 | my $dbh = get_admin_dbh(); | |
519 | # Before the binary switch in 3.7.87, we want to alter text -> | |
520 | # longtext, not blob -> longblob | |
521 | if (RT::Handle::cmp_version( $upgrading_from, '3.7.87') < 0) { | |
522 | $dbh->do("ALTER TABLE Attributes MODIFY Content LONGTEXT") | |
523 | } else { | |
524 | $dbh->do("ALTER TABLE Attributes MODIFY Content LONGBLOB") | |
525 | } | |
526 | } | |
527 | ||
528 | my $previous = $upgrading_from; | |
529 | my ( $ret, $msg ); | |
530 | foreach my $n ( 0..$#versions ) { | |
531 | my $v = $versions[$n]; | |
532 | my $individual_id = Data::GUID->new->as_string(); | |
533 | ||
534 | my @back = grep {-e $_} map {"$base_dir/$versions[$_]/backcompat"} $n+1..$#versions; | |
535 | print "Processing $v\n"; | |
536 | ||
537 | RT->System->AddUpgradeHistory($package => { | |
538 | action => 'upgrade', | |
539 | type => 'individual upgrade', | |
540 | stage => 'before', | |
541 | from => $previous, | |
542 | to => $v, | |
543 | full_id => $full_id, | |
544 | individual_id => $individual_id, | |
545 | }); | |
546 | ||
547 | my %tmp = (%args, datadir => "$base_dir/$v", datafile => undef, backcompat => \@back); | |
548 | ||
549 | if ( -e "$base_dir/$v/schema.$db_type" ) { | |
550 | ( $ret, $msg ) = action_schema( %tmp ); | |
551 | return ( $ret, $msg ) unless $ret; | |
552 | } | |
553 | if ( -e "$base_dir/$v/acl.$db_type" ) { | |
554 | ( $ret, $msg ) = action_acl( %tmp ); | |
555 | return ( $ret, $msg ) unless $ret; | |
556 | } | |
557 | if ( -e "$base_dir/$v/indexes" ) { | |
558 | ( $ret, $msg ) = action_indexes( %tmp ); | |
559 | return ( $ret, $msg ) unless $ret; | |
560 | } | |
561 | if ( -e "$base_dir/$v/content" ) { | |
562 | ( $ret, $msg ) = action_insert( %tmp ); | |
563 | return ( $ret, $msg ) unless $ret; | |
564 | } | |
565 | ||
566 | # XXX: Another connect since the insert called | |
567 | # previous to this step will disconnect. | |
568 | ||
569 | RT->ConnectToDatabase(); | |
570 | ||
571 | RT->System->AddUpgradeHistory($package => { | |
572 | stage => 'after', | |
573 | individual_id => $individual_id, | |
574 | }); | |
575 | ||
576 | $previous = $v; | |
577 | } | |
578 | ||
579 | RT->System->AddUpgradeHistory($package => { | |
580 | stage => 'after', | |
581 | individual_id => $full_id, | |
582 | }); | |
583 | ||
584 | return 1; | |
585 | } | |
586 | ||
587 | sub get_versions_from_to { | |
588 | my ($base_dir, $from, $to) = @_; | |
589 | ||
590 | opendir( my $dh, $base_dir ) or die "couldn't open dir: $!"; | |
591 | my @versions = grep -d "$base_dir/$_" && /$version_dir/, readdir $dh; | |
592 | closedir $dh; | |
593 | ||
594 | die "\nERROR: No upgrade data found in '$base_dir'! Perhaps you specified the wrong --datadir?\n" | |
595 | unless @versions; | |
596 | ||
597 | return | |
598 | grep defined $to ? RT::Handle::cmp_version($_, $to) <= 0 : 1, | |
599 | grep RT::Handle::cmp_version($_, $from) > 0, | |
600 | sort RT::Handle::cmp_version @versions; | |
601 | } | |
602 | ||
603 | sub error { | |
604 | my ($action, $msg) = @_; | |
605 | print STDERR "Couldn't finish '$action' step.\n\n"; | |
606 | print STDERR "ERROR: $msg\n\n"; | |
607 | exit(-1); | |
608 | } | |
609 | ||
610 | sub get_dba_password { | |
611 | print "In order to create or update your RT database," | |
612 | . " this script needs to connect to your " | |
613 | . " $db_type instance on $db_host (port '$db_port') as $dba_user\n"; | |
614 | print "Please specify that user's database password below. If the user has no database\n"; | |
615 | print "password, just press return.\n\n"; | |
616 | print "Password: "; | |
617 | ReadMode('noecho'); | |
618 | my $password = ReadLine(0); | |
619 | ReadMode('normal'); | |
620 | print "\n"; | |
621 | return ($password); | |
622 | } | |
623 | ||
624 | # get_system_dbh | |
625 | # Returns L<DBI> database handle connected to B<system> with DBA credentials. | |
626 | # See also L<RT::Handle/SystemDSN>. | |
627 | ||
628 | ||
629 | sub get_system_dbh { | |
630 | return _get_dbh( RT::Handle->SystemDSN, $dba_user, $dba_pass ); | |
631 | } | |
632 | ||
633 | sub get_admin_dbh { | |
634 | return _get_dbh( RT::Handle->DSN, $dba_user, $dba_pass ); | |
635 | } | |
636 | ||
637 | # get_rt_dbh [USER, PASSWORD] | |
638 | ||
639 | # Returns L<DBI> database handle connected to RT database, | |
640 | # you may specify credentials(USER and PASSWORD) to connect | |
641 | # with. By default connects with credentials from RT config. | |
642 | ||
643 | sub get_rt_dbh { | |
644 | return _get_dbh( RT::Handle->DSN, $db_user, $db_pass ); | |
645 | } | |
646 | ||
647 | sub _get_dbh { | |
648 | my ($dsn, $user, $pass) = @_; | |
649 | my $dbh = DBI->connect( | |
650 | $dsn, $user, $pass, | |
651 | { RaiseError => 0, PrintError => 0 }, | |
652 | ); | |
653 | unless ( $dbh ) { | |
654 | my $msg = "Failed to connect to $dsn as user '$user': ". $DBI::errstr; | |
655 | if ( $args{'debug'} ) { | |
656 | require Carp; Carp::confess( $msg ); | |
657 | } else { | |
658 | print STDERR $msg; exit -1; | |
659 | } | |
660 | } | |
661 | return $dbh; | |
662 | } | |
663 | ||
664 | sub _yesno { | |
665 | print "Proceed [y/N]:"; | |
666 | my $x = scalar(<STDIN>); | |
667 | $x =~ /^y/i; | |
668 | } | |
669 | ||
670 | 1; | |
671 | ||
672 | __END__ | |
673 | ||
674 | =head1 NAME | |
675 | ||
676 | rt-setup-database - Set up RT's database | |
677 | ||
678 | =head1 SYNOPSIS | |
679 | ||
680 | rt-setup-database --action ... | |
681 | ||
682 | =head1 OPTIONS | |
683 | ||
684 | =over | |
685 | ||
686 | =item action | |
687 | ||
688 | Several actions can be combined using comma separated list. | |
689 | ||
690 | =over | |
691 | ||
692 | =item init | |
693 | ||
694 | Initialize the database. This is combination of multiple actions listed below. | |
695 | Create DB, schema, setup acl, insert core data and initial data. | |
696 | ||
697 | =item upgrade | |
698 | ||
699 | Apply all needed schema/acl/content updates (will ask for version to upgrade | |
700 | from) | |
701 | ||
702 | =item create | |
703 | ||
704 | Create the database. | |
705 | ||
706 | =item drop | |
707 | ||
708 | Drop the database. This will B<ERASE ALL YOUR DATA>. | |
709 | ||
710 | =item schema | |
711 | ||
712 | Initialize only the database schema | |
713 | ||
714 | To use a local or supplementary datafile, specify it using the '--datadir' | |
715 | option below. | |
716 | ||
717 | =item acl | |
718 | ||
719 | Initialize only the database ACLs | |
720 | ||
721 | To use a local or supplementary datafile, specify it using the '--datadir' | |
722 | option below. | |
723 | ||
724 | =item coredata | |
725 | ||
726 | Insert data into RT's database. This data is required for normal functioning of | |
727 | any RT instance. | |
728 | ||
729 | =item insert | |
730 | ||
731 | Insert data into RT's database. By default, will use RT's installation data. | |
732 | To use a local or supplementary datafile, specify it using the '--datafile' | |
733 | option below. | |
734 | ||
735 | =back | |
736 | ||
737 | =item datafile | |
738 | ||
739 | file path of the data you want to action on | |
740 | ||
741 | e.g. C<--datafile /path/to/datafile> | |
742 | ||
743 | =item datadir | |
744 | ||
745 | Used to specify a path to find the local database schema and acls to be | |
746 | installed. | |
747 | ||
748 | e.g. C<--datadir /path/to/> | |
749 | ||
750 | =item dba | |
751 | ||
752 | dba's username | |
753 | ||
754 | =item dba-password | |
755 | ||
756 | dba's password | |
757 | ||
758 | =item prompt-for-dba-password | |
759 | ||
760 | Ask for the database administrator's password interactively | |
761 | ||
762 | =item skip-create | |
763 | ||
764 | for 'init': skip creating the database and the user account, so we don't need | |
765 | administrator privileges | |
766 | ||
767 | =item root-password-file | |
768 | ||
769 | for 'init' and 'insert': rather than using the default administrative password | |
770 | for RT's "root" user, use the password in this file. | |
771 | ||
772 | =item package | |
773 | ||
774 | the name of the entity performing a create or upgrade. Used for logging changes | |
775 | in the DB. Defaults to RT, otherwise it should be the fully qualified package name | |
776 | of the extension or plugin making changes to the DB. | |
777 | ||
778 | =item ext-version | |
779 | ||
780 | current version of extension making a change. Not needed for RT since RT has a | |
781 | more elaborate system to track upgrades across multiple versions. | |
782 | ||
783 | =item upgrade-from | |
784 | ||
785 | for 'upgrade': specifies the version to upgrade from, and do not prompt | |
786 | for it if it appears to be a valid version. | |
787 | ||
788 | =item upgrade-to | |
789 | ||
790 | for 'upgrade': specifies the version to upgrade to, and do not prompt | |
791 | for it if it appears to be a valid version. | |
792 | ||
793 | =back | |
794 | ||
795 | =cut |