2 # BEGIN BPS TAGGED BLOCK {{{
6 # This software is Copyright (c) 1996-2017 Best Practical Solutions, LLC
7 # <sales@bestpractical.com>
9 # (Except where explicitly superseded by other copyright notices)
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
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.
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.
31 # CONTRIBUTION SUBMISSION POLICY:
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.)
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.
48 # END BPS TAGGED BLOCK }}}
52 # fix lib paths, some may be relative
55 my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@");
59 unless ( File::Spec->file_name_is_absolute($lib) ) {
61 if ( File::Spec->file_name_is_absolute(__FILE__) ) {
62 $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
67 $bin_path = $FindBin::Bin;
70 $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
81 @RT::Record::ISA = qw( DBIx::SearchBuilder::Record RT::Base );
84 use RT::Migrate::Importer::File;
89 my %OPT = (resume => 1);
98 "exclude-organization",
104 ) or Pod::Usage::pod2usage();
106 Pod::Usage::pod2usage(-verbose => 1) if $OPT{help};
108 Pod::Usage::pod2usage() unless @ARGV == 1;
111 die "No such directory $dir\n" unless -d $dir;
112 die "$dir doesn't appear to contain serialized data\n"
113 unless -f "$dir/001.dat";
116 die "Dumping objects only works in conjunction with --list\n"
119 $OPT{dump} = [ split /,/, join(',', @{$OPT{dump}}) ];
124 die "Interactive mode (--ask) doesn't work when STDERR and STDIN aren't terminals.\n"
125 unless -t STDERR and -t STDIN;
127 $error_handler = sub {
128 my $importer = shift;
130 print STDERR "\n", @_, "\n";
131 print STDERR "Hit any key to abort import, or type 'ignore' to continue anyway.\n";
132 print STDERR "Continuing may leave you with a corrupt database. > ";
133 chomp( my $resp = <STDIN> );
134 return lc($resp) eq 'ignore';
137 elsif ($OPT{'ignore-errors'}) {
138 $error_handler = sub {
139 my $importer = shift;
140 warn "Ignoring error: ", @_;
145 my $import = RT::Migrate::Importer::File->new(
147 OriginalId => $OPT{originalid},
148 ExcludeOrganization => $OPT{'exclude-organization'},
149 DumpObjects => $OPT{dump},
150 Resume => $OPT{resume},
151 HandleError => $error_handler,
154 if ($import->Metadata and -t STDOUT and not $OPT{quiet}) {
156 RT::Migrate::progress(
157 counts => sub { $import->ObjectCount },
158 max => $import->Metadata->{ObjectCount},
163 my $log = RT::Migrate::setup_logging( $dir => 'importer.log' );
164 print "Logging warnings and errors to $log\n" if $log;
168 %counts = $import->List;
170 my $org = $import->Organization;
171 print "=========== Dump of $org ===========\n\n";
173 %counts = $import->Import;
175 my $org = $import->Organization;
176 print "========== Import of $org ==========\n\n";
179 print "Total object counts:\n";
180 for (sort {$counts{$b} <=> $counts{$a}} keys %counts) {
181 printf "%8d %s\n", $counts{$_}, $_;
184 my @missing = $import->Missing;
186 warn "The following UIDs were expected but never observed:\n";
187 warn " $_\n" for @missing;
190 my @invalid = $import->Invalid;
192 warn "The following UIDs (serialized => imported) referred to objects missing from the original database:\n";
193 for my $info (@invalid) {
194 my $uid = delete $info->{uid};
195 my $obj = $import->LookupObj($uid);
196 warn sprintf " %s => %s (%s)\n",
198 ($obj && $obj->Id ? $obj->UID : '(not imported)'),
199 join(", ", map { "$_ => $info->{$_}" }
200 grep { defined $info->{$_} }
205 if ($log and -s $log) {
206 print STDERR "\n! Some warnings or errors occurred during import."
207 ."\n! Please see $log for details.\n\n";
214 rt-importer - Import a serialized RT database on top of the current one
218 rt-importer path/to/export/directory
220 This script is used to import the contents of a dump created by
221 C<rt-serializer>. It will create all of the objects in the dump in the
222 current database; this may include users, queues, and tickets.
224 It is possible to stop the import process with ^C; it can be later
225 resumed by re-running the importer.
227 Certain records (notably queues and groups) will have their original
228 Organization name prepended to them on import. This is primarily to avoid
229 duplicate names (for example importing a General queue into an RT that
230 already has one would otherwise cause a name collision error). If you are
231 confident you won't have any name collisions in queues or groups, you may
232 suppress this behavior by passing the B<--exclude-organization> flag to
241 Print a summary of the data contained in the dump.
243 =item B<--originalid> I<cfname>
245 Places the original ticket organization and ID into a global custom
246 field with the given name. If no global ticket custom field with that
247 name is found in the current database, it will create one.
249 =item B<--exclude-organization>
251 Ordinarily certain records (groups, queues, the B<--originalid> custom field)
252 include the organization name of the original RT instance. Use this option to
253 suppress that behavior and use the original name directly.
257 Prompt for action when an error occurs inserting a record into the
258 database. This can often happen when importing data from very old RTs
259 where some attachments (usually spam) contain invalid UTF-8.
261 The importer will pause and ask if you want to ignore the error and
262 continue on or abort (potentially to restart later). Ignoring errors
263 will result in missing records in the database, which may cause database
264 integrity problems later. If you ignored any errors, you should run
265 C<rt-validator> after import.
267 =item B<--ignore-errors>
269 Ignore all record creation errors and continue on when importing. This
270 is equivalent to running with C<--ask> and manually typing "ignore" at
271 every prompt. You should always run C<rt-validator> after importing
274 B<This option can be dangerous and leave you with a broken RT!>
276 =item B<--dump> I<class>[,I<class>]
278 Prints L<Data::Dumper> representations of the objects of type I<class> in the
279 serialized data. This is mostly useful for debugging.
281 Works only in conjunction with C<--list>.
288 Some dumps may have been taken as complete clones of the RT system,
289 which are only suitable for inserting into a schema with no data in it.
290 You can setup the required database state for the receiving RT instance
293 @RT_SBIN_PATH_R@/rt-setup-database --action create,schema,acl --prompt-for-dba-password
295 The normal C<make initdb> step will B<not> work because it also inserts