rt 4.2.16
[freeside.git] / rt / sbin / rt-importer.in
1 #!@PERL@
2 # BEGIN BPS TAGGED BLOCK {{{
3 #
4 # COPYRIGHT:
5 #
6 # This software is Copyright (c) 1996-2019 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 # fix lib paths, some may be relative
53 BEGIN {
54     require File::Spec;
55     my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@");
56     my $bin_path;
57
58     for my $lib (@libs) {
59         unless ( File::Spec->file_name_is_absolute($lib) ) {
60             unless ($bin_path) {
61                 if ( File::Spec->file_name_is_absolute(__FILE__) ) {
62                     $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
63                 }
64                 else {
65                     require FindBin;
66                     no warnings "once";
67                     $bin_path = $FindBin::Bin;
68                 }
69             }
70             $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
71         }
72         unshift @INC, $lib;
73     }
74
75 }
76
77 use RT;
78 RT::LoadConfig();
79 RT::Init();
80
81 @RT::Record::ISA = qw( DBIx::SearchBuilder::Record RT::Base );
82
83 use RT::Migrate;
84 use RT::Migrate::Importer::File;
85 use Getopt::Long;
86 use Pod::Usage qw//;
87 use Time::HiRes qw//;
88
89 my %OPT = (resume => 1);
90 GetOptions(
91     \%OPT,
92     "help|?",
93     "quiet|q!",
94     "list|l!",
95
96     "resume!",
97     "originalid|i=s",
98     "exclude-organization",
99
100     "ask",
101     "ignore-errors",
102
103     "dump=s@",
104 ) or Pod::Usage::pod2usage();
105
106 Pod::Usage::pod2usage(-verbose => 1) if $OPT{help};
107
108 Pod::Usage::pod2usage() unless @ARGV == 1;
109 my ($dir) = @ARGV;
110 $dir =~ s|/$||;
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";
114
115 if ($OPT{dump}) {
116     die "Dumping objects only works in conjunction with --list\n"
117         unless $OPT{list};
118
119     $OPT{dump} = [ split /,/, join(',', @{$OPT{dump}}) ];
120 }
121
122 my $error_handler;
123 if ($OPT{ask}) {
124     die "Interactive mode (--ask) doesn't work when STDERR and STDIN aren't terminals.\n"
125         unless -t STDERR and -t STDIN;
126
127     $error_handler = sub {
128         my $importer = shift;
129         local $| = 1;
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';
135     };
136 }
137 elsif ($OPT{'ignore-errors'}) {
138     $error_handler = sub {
139         my $importer = shift;
140         warn "Ignoring error: ", @_;
141         return 1;
142     };
143 }
144
145 my $import = RT::Migrate::Importer::File->new(
146     Directory           => $dir,
147     OriginalId          => $OPT{originalid},
148     ExcludeOrganization => $OPT{'exclude-organization'},
149     DumpObjects         => $OPT{dump},
150     Resume              => $OPT{resume},
151     HandleError         => $error_handler,
152 );
153
154 if ($import->Metadata and -t STDOUT and not $OPT{quiet}) {
155     $import->Progress(
156         RT::Migrate::progress(
157             counts => sub { $import->ObjectCount },
158             max    => $import->Metadata->{ObjectCount},
159         )
160     );
161 }
162
163 my $log = RT::Migrate::setup_logging( $dir => 'importer.log' );
164 print "Logging warnings and errors to $log\n" if $log;
165
166 my %counts;
167 if ($OPT{list}) {
168     %counts = $import->List;
169
170     my $org = $import->Organization;
171     print "=========== Dump of $org ===========\n\n";
172 } else {
173     %counts = $import->Import;
174
175     my $org = $import->Organization;
176     print "========== Import of $org ==========\n\n";
177 }
178
179 print "Total object counts:\n";
180 for (sort {$counts{$b} <=> $counts{$a}} keys %counts) {
181     printf "%8d %s\n", $counts{$_}, $_;
182 }
183
184 my @missing = $import->Missing;
185 if (@missing) {
186     warn "The following UIDs were expected but never observed:\n";
187     warn "    $_\n" for @missing;
188 }
189
190 my @invalid = $import->Invalid;
191 if (@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",
197                 $uid,
198                 ($obj && $obj->Id ? $obj->UID : '(not imported)'),
199                 join(", ", map  { "$_ => $info->{$_}" }
200                            grep { defined $info->{$_} }
201                                 sort keys %$info);
202     }
203 }
204
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";
208 }
209
210 exit @missing;
211
212 =head1 NAME
213
214 rt-importer - Import a serialized RT database on top of the current one
215
216 =head1 SYNOPSIS
217
218     rt-importer path/to/export/directory
219
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.
223
224 It is possible to stop the import process with ^C; it can be later
225 resumed by re-running the importer.
226
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
233 C<rt-importer>.
234
235 =head2 OPTIONS
236
237 =over
238
239 =item B<--list>
240
241 Print a summary of the data contained in the dump.
242
243 =item B<--originalid> I<cfname>
244
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.
248
249 =item B<--exclude-organization>
250
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.
254
255 =item B<--ask>
256
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.
260
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.
266
267 =item B<--ignore-errors>
268
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
272 with errors ignored.
273
274 B<This option can be dangerous and leave you with a broken RT!>
275
276 =item B<--dump> I<class>[,I<class>]
277
278 Prints L<Data::Dumper> representations of the objects of type I<class> in the
279 serialized data.  This is mostly useful for debugging.
280
281 Works only in conjunction with C<--list>.
282
283 =back
284
285
286 =head1 CLONED DATA
287
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
291 by running:
292
293     @RT_SBIN_PATH_R@/rt-setup-database --action create,schema,acl --prompt-for-dba-password
294
295 The normal C<make initdb> step will B<not> work because it also inserts
296 core system data.
297
298
299 =cut