#!/usr/bin/perl -w # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) # # # LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have # been provided with this software, but in any event can be snarfed # from www.gnu.org. # # This work 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 or visit their web page on the internet at # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. # # # CONTRIBUTION SUBMISSION POLICY: # # (The following paragraph is not intended to limit the rights granted # to you to modify and distribute this software under the terms of # the GNU General Public License and is only of importance to you if # you choose to contribute your changes and enhancements to the # community by submitting them to Best Practical Solutions, LLC.) # # By intentionally submitting any modifications, corrections or # derivatives to this work, or any other work intended for use with # Request Tracker, to Best Practical Solutions, LLC, you confirm that # you are the copyright holder for those contributions and you grant # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, # royalty-free, perpetual, license to use, copy, create derivative # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # # END BPS TAGGED BLOCK }}} use strict; use warnings; # As we specify that XML is UTF-8 and we output it to STDOUT, we must be sure # it is UTF-8 so further XMLin will not break binmode( STDOUT, ":utf8" ); # fix lib paths, some may be relative BEGIN { # BEGIN RT CMD BOILERPLATE require File::Spec; require Cwd; my @libs = ("lib", "local/lib"); my $bin_path; for my $lib (@libs) { unless ( File::Spec->file_name_is_absolute($lib) ) { $bin_path ||= ( File::Spec->splitpath(Cwd::abs_path(__FILE__)) )[1]; $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); } unshift @INC, $lib; } } use Getopt::Long; my %opt; GetOptions( \%opt, "help|h", "limit-to-privileged|l", "skip-disabled|s", "all|a", ); if ( $opt{help} ) { require Pod::Usage; Pod::Usage::pod2usage( { verbose => 2 } ); exit; } require RT; require XML::Simple; RT::LoadConfig(); RT::Init(); my %RV; my %Ignore = ( All => [ qw( id Created Creator LastUpdated LastUpdatedBy ) ], ); my $SystemUserId = RT->SystemUser->Id; my @classes = qw( Users Groups Queues ScripActions ScripConditions Templates Scrips ACL CustomFields ); foreach my $class (@classes) { require "RT/$class.pm"; my $objects = "RT::$class"->new( RT->SystemUser ); $objects->{find_disabled_rows} = 1 unless $opt{'skip-disabled'}; $objects->UnLimit; $objects->LimitToPrivileged if $class eq 'Users' && $opt{'limit-to-privileged'}; $objects->Limit( FIELD => 'Domain', OPERATOR => '=', VALUE => 'UserDefined', CASESENSITIVE => 0, ) if $class eq 'Groups'; if ( $class eq 'CustomFields' ) { $objects->OrderByCols( { FIELD => 'LookupType' }, { FIELD => 'SortOrder' }, { FIELD => 'Id' }, ); } else { $objects->OrderBy( FIELD => 'Id' ); } unless ($opt{all}) { next if $class eq 'ACL'; # XXX - would go into infinite loop - XXX $objects->Limit( FIELD => 'LastUpdatedBy', OPERATOR => '!=', VALUE => $SystemUserId ) unless $class eq 'Groups'; $objects->Limit( FIELD => 'Id', OPERATOR => '!=', VALUE => $SystemUserId ) if $class eq 'Users'; } my %fields; OBJECT: while ( my $obj = $objects->Next ) { next if $obj->can('LastUpdatedBy') and $obj->LastUpdatedBy == $SystemUserId; if ( !%fields ) { %fields = map { $_ => 1 } keys %{ $obj->_ClassAccessible }; delete @fields{ @{ $Ignore{$class} ||= [] }, @{ $Ignore{All} ||= [] }, }; } my $rv; if ( $class ne 'ACL' ) { # next if $obj-> # skip default names foreach my $field ( sort keys %fields ) { my $value = $obj->__Value($field); $rv->{$field} = $value if ( defined($value) && length($value) ); } delete $rv->{Disabled} unless $rv->{Disabled}; foreach my $record ( map { /ACL/ ? 'ACE' : substr( $_, 0, -1 ) } @classes ) { foreach my $key ( map "$record$_", ( '', 'Id' ) ) { next unless exists $rv->{$key}; my $id = $rv->{$key} or next; my $obj = "RT::$record"->new( RT->SystemUser ); $obj->LoadByCols( Id => $id ) or next; $rv->{$key} = $obj->__Value('Name') || 0; } } if ( $class eq 'Users' and defined $obj->Privileged ) { $rv->{Privileged} = int( $obj->Privileged ); } elsif ( $class eq 'CustomFields' ) { my $values = $obj->Values; while ( my $value = $values->Next ) { push @{ $rv->{Values} }, { map { ( $_ => $value->__Value($_) ) } qw( Name Description SortOrder ), }; } if ( $obj->LookupType eq 'RT::Queue-RT::Ticket' ) { # XXX-TODO: unused CF's turn into global CF when importing # as the sub InsertData in RT::Handle creates a global CF # when no queue is specified. $rv->{Queue} = []; my $applies = $obj->AppliedTo; while ( my $queue = $applies->Next ) { push @{ $rv->{Queue} }, $queue->Name; } } } } else { # 1) pick the right $rv->{Right} = $obj->RightName; # 2) Pick a level: Granted on Queue, CF, CF+Queue, or Globally? for ( $obj->ObjectType ) { if ( /^RT::Queue$/ ) { next OBJECT if $opt{'skip-disabled'} && $obj->Object->Disabled; $rv->{Queue} = $obj->Object->Name; } elsif ( /^RT::CustomField$/ ) { next OBJECT if $opt{'skip-disabled'} && $obj->Object->Disabled; $rv->{CF} = $obj->Object->Name; } elsif ( /^RT::Group$/ ) { # No support for RT::Group ACLs in RT::Handle yet. next OBJECT; } elsif ( /^RT::System$/ ) { # skip setting anything on $rv; # "Specifying none of the above will get you a global right." } } # 3) Pick a Principal; User or Group or Role if ( $obj->PrincipalType eq 'Group' ) { next OBJECT if $opt{'skip-disabled'} && $obj->PrincipalObj->Disabled; my $group = $obj->PrincipalObj->Object; for ( $group->Domain ) { # An internal user group if ( /^SystemInternal$/ ) { $rv->{GroupDomain} = $group->Domain; $rv->{GroupType} = $group->Type; } # An individual user elsif ( /^ACLEquivalence$/ ) { my $member = $group->MembersObj->Next->MemberObj; next OBJECT if $opt{'skip-disabled'} && $member->Disabled; $rv->{UserId} = $member->Object->Name; } # A group you created elsif ( /^UserDefined$/ ) { $rv->{GroupDomain} = 'UserDefined'; $rv->{GroupId} = $group->Name; } } } else { $rv->{GroupType} = $obj->PrincipalType; # A system-level role if ( $obj->ObjectType eq 'RT::System' ) { $rv->{GroupDomain} = 'RT::System-Role'; } # A queue-level role elsif ( $obj->ObjectType eq 'RT::Queue' ) { $rv->{GroupDomain} = 'RT::Queue-Role'; } } } if ( RT::Attributes->require ) { my $attributes = $obj->Attributes; while ( my $attribute = $attributes->Next ) { my $content = $attribute->Content; if ( $class eq 'Users' and $attribute->Name eq 'Bookmarks' ) { next; } $rv->{Attributes}{ $attribute->Name } = $content if length($content); } } push @{ $RV{$class} }, $rv; } } print(<< "."); no strict; use XML::Simple; *_ = XMLin(do { local \$/; readline(DATA) }, ForceArray => [qw( @classes Values )], NoAttr => 1, SuppressEmpty => ''); *\$_ = (\$_{\$_} || []) for keys \%_; 1; # vim: ft=xml __DATA__ . print XML::Simple::XMLout( { map { ( $_ => ( $RV{$_} || [] ) ) } @classes }, RootName => 'InitialData', NoAttr => 1, SuppressEmpty => '', XMLDecl => '', ); __END__ =head1 NAME rt-dump-metadata - dump configuration metadata from an RT database =head1 SYNOPSIS rt-dump-metdata [--all] =head1 DESCRIPTION C is a tool that dumps configuration metadata from the Request Tracker database into XML format, suitable for feeding into C. To dump and load a full RT database, you should generally use the native database tools instead, as well as performing any necessary steps from UPGRADING. This is NOT a tool for backing up an RT database. See also L for more straightforward means of importing data. =head1 OPTIONS =over =item C<--all> or C<-a> When run with C<--all>, the dump will include all configuration metadata; otherwise, the metadata dump will only include 'local' configuration changes, i.e. those done manually in the web interface. =item C<--limit-to-privileged> or C<-l> Causes the dumper to only dump privileged users. =item C<--skip-disabled> or C<-s> Ignores disabled rows in the database. =back =cut