#!/usr/bin/perl -w # rhn-cfg-copy v1.0 # recursively clone config channels between organizations/servers # gprocunier@symcor.com [02/23/12] # # Special Thanks to Justin Sherrill at redhat dot com for # troubleshooting some oddities with Frontier::Client =head1 NAME rhn-cfg-copy - Clone config channels between organizations =head1 SYNOPSIS rhn-cfg-copy [options] Options: --src_host --src_auth --src_conf --dst_host --dst_auth --dst_conf --help options in brief --man full documentation =head1 OPTIONS =over 8 =item B<--src_host> The source address of the RHN Satellite/Spacewalk server containing the configuration channel =item B<--src_auth> The file containing the authorization credentials for the source RHN Satellite/Spacewalk Credential files contain 2 lines, the first line is the username and the second line is the password. =item B<--src_conf> The name of the configuration channel to clone off the source RHN Satellite/Spacewalk server =item B<--dst_host> The address of the RHN Satellite/Spacewalk server where the configuration channel will be cloned. =item B<--dst_auth> The file containing the authorization credentials for the destination RHN Satellite/Spacewalk Credential files contain 2 lines, the first line is the username and the second line is the password. =item B<--dst_conf> The name of the configuration channel to be created and cloned to on the destination RHN Satellite/Spacewalk server =item B<--help> Command Help in Brief =item B<--man> Command Manual in Detail =back =head1 DESCRIPTION B will connect to a source satellite and clone a config channel to another satellite server. This tool can also be used to clone config channels between organizations within the same satellite. =head1 AUTHOR by Greg Procunier =cut use strict; use Frontier::Client; use Data::Dumper; use diagnostics; use Getopt::Long; use Pod::Usage; use Switch; my %args = (); ### Main &parse_args; my $src_client = new Frontier::Client(url => "http://$args{src_host}/rpc/api"); my $dst_client = new Frontier::Client(url => "http://$args{dst_host}/rpc/api"); (my $src_session, my $dst_session) = &connect; # check if source channel exists print "\n[-] Checking if source channel exists\n"; my $cfg_src_exists = $src_client->call('configchannel.channelExists', $src_session, $args{src_conf}); if($cfg_src_exists == 0) { &exit_handler(1); } # check if destination channel exists print "[-] Checking that destination channel does not exist\n"; my $cfg_dst_exists = $dst_client->call('configchannel.channelExists', $dst_session, $args{dst_conf}); if($cfg_dst_exists == 1) { &exit_handler(2); } # Get source config channel details print "[-] Getting source channel details\n"; my $cfg_src_details = $src_client->call('configchannel.getDetails', $src_session, $args{src_conf}); # Get Config File List print "[-] Dumping source Channel File List\n"; my $cfg_olist = $src_client->call('configchannel.listFiles', $src_session, $args{src_conf}); my @cfg_files = (); foreach my $cfg_obj (@$cfg_olist) { push @cfg_files, $cfg_obj->{'path'}; } # Extract and store file data print "[-] Extracting source channel data\n"; my $cfg_efiles = $src_client->call('configchannel.lookupFileInfo', $src_session, $args{src_conf}, \@cfg_files); # Create a config channel on the destination server print "[-] Creating config channel destination\n"; my $cfg_dst = $dst_client->call('configchannel.create', $dst_session, $args{dst_conf}, $cfg_src_details->{'name'}, $cfg_src_details->{'description'}); # Replicate source data into new channel print "\nReplicating data:\n\n"; foreach my $src_file (@$cfg_efiles) { my %clone; switch($src_file->{'type'}) { case "file" { $clone{'contents'} = $src_file->{'contents'}; $clone{'owner'} = $src_file->{'owner'}; $clone{'group'} = $src_file->{'group'}; $clone{'permissions'} = $dst_client->string($src_file->{'permissions_mode'}); $clone{'selinux_ctx'} = $src_file->{'selinux_ctx'}; $clone{'macro-start-delimiter'} = $src_file->{'macro-start-delimiter'}; $clone{'macro-end-delimiter'} = $src_file->{'macro-end-delimiter'}; $clone{'revision'} = $src_file->{'revision'}; print "F $src_file->{'path'}\n"; my $dst_file = $dst_client->call('configchannel.createOrUpdatePath', $dst_session, $args{dst_conf}, $src_file->{'path'}, $dst_client->boolean(0), \%clone); } case "directory" { $clone{'owner'} = $src_file->{'owner'}; $clone{'group'} = $src_file->{'group'}; $clone{'permissions'} = $dst_client->string($src_file->{'permissions_mode'}); $clone{'selinux_ctx'} = $src_file->{'selinux_ctx'}; $clone{'revision'} = $src_file->{'revision'}; print "D $src_file->{'path'}\n"; my $dst_file = $dst_client->call('configchannel.createOrUpdatePath', $dst_session, $args{dst_conf}, $src_file->{'path'}, $dst_client->boolean(1), \%clone); } case "symlink" { $clone{'target_path'} = $src_file->{'target_path'}; $clone{'selinux_ctx'} = $src_file->{'selinux_ctx'}; $clone{'revision'} = 1; print "L $src_file->{'path'}\n"; my $dst_file = $dst_client->call('configchannel.createOrUpdateSymlink', $dst_session, $args{dst_conf}, $src_file->{'path'}, \%clone); } } } print "\nDone!\n"; ### Subroutines sub exit_handler { my ($ecode) = (@_); switch($ecode) { case "1" { print "Error: Source config channel does not exist\n"; exit 1; } case "2" { print "Error: Destination config channel already exists\n"; exit 2; } else { print "untrapped exit\n"; } } } sub connect { (my $src_user, my $src_pass) = readauth($args{src_auth}); (my $dst_user, my $dst_pass) = readauth($args{dst_auth}); my $src_session = $src_client->call('auth.login', $src_user, $src_pass); my $dst_session = $dst_client->call('auth.login', $dst_user, $dst_pass); return ($src_session,$dst_session); } sub parse_args { if($#ARGV < 0) { pod2usage( -verbose => 0); exit 1; } GetOptions ( \%args, 'help|h', 'man|m', 'src_host|a=s', 'dst_host|b=s', 'src_auth|c=s', 'dst_auth|d=s', 'src_conf|e=s', 'dst_conf|f=s' ); pod2usage( -verbose => 0 ) if($args{help}); pod2usage( -exitstatus => 0, -verbose => 2 ) if($args{man}); if(!defined (($args{src_host})&&($args{dst_host})&&($args{src_auth})&&($args{dst_auth})&&($args{src_conf})&&($args{dst_conf})) ) { pod2usage( -verbose => 0); exit 1; } } sub readauth { my ($authfile) = @_; my $auth_fh; open($auth_fh,"<$authfile") or die("Unable to read authentication file: $authfile\n"); my $username = readline($auth_fh); my $password = readline($auth_fh); close $auth_fh; chomp($username,$password); return($username,$password); }