source: projects/casparbuster/devel/bin/cbusterize @ 1547

Last change on this file since 1547 was 1547, checked in by bruno, 7 years ago
  • Add the shorewall plugin
  • cbusterize now uses only Net::SSH2 and supports getting files owned by root with sudo called remotely (no passwd asked)
  • Property svn:executable set to *
File size: 12.6 KB
Line 
1#!/usr/bin/perl -w
2#
3=head1 NAME
4
5cbusterize - Creates the correct CasparBuster structure in your CMS environment
6
7=head1 SYNOPSIS
8
9cbusterize [options] [--source /path/to/file/to/CasparBusterize | --plugin PluginName]
10
11 Options:
12   --debug  |-d         debug mode
13   --help   |-h         brief help message
14   --man            full documentation
15   --force   |-f            force copy of files, even if they exist
16   --source |-s <file/dir>  directory or files to copy in the CasparBuster tree (',' separated if many)
17   --plugin |-p <plugin name>   plugin defining what to copy in the CasparBuster tree (',' separated if many)
18   --machine|-m <machine>   machine to consider in the subtree
19
20=head1 OPTIONS
21
22=over 4
23
24=item B<--debug>
25
26Enter debug mode. This will print what would be done. No commands are executed,
27so this is safe to use when testing.
28
29=item B<--help>
30
31Print a brief help message and exits.
32
33=item B<--man>
34
35Prints the manual page and exits.
36
37=item B<--machine> I<machine name>
38
39Specify the machine to consider when dealing with the CasparBuster structure.
40The file will be taken from this machine, and a subdirectory named after the machine
41will be used under the basedir to host the directory structure to manage
42
43=item B<--source> I<path>
44
45Specify the path to the source file or directory to manage with CasparBuster. Multiple paths can be specified separated by ','.
46
47=item B<--plugin> I<name>
48
49Specify the name of the plugin to manage with CasparBuster. Multiple plugins can be specified separated by ','.
50A plugin defines a set of files (with their mode and owner), a set of directories (with their mode and owner) and a set of scripts to launch once the files are copied remotely.
51
52=back
53
54=head1 DESCRIPTION
55
56Creates a directory under the machine dir passed as parameter in working
57directory or directory passed as parameter named like the last path
58element of the parameter, and creates the standard CasparBuster setup
59that refers to the parameter path in the new directory, and configuration
60files when possible. It also copies the original config file into the new dir.
61Is reasonably picky about path names, tries to avoid common errors.
62
63Schema looks like:
64
65Base dir
66   |
67   |- machine1 (optional)
68   |     |
69   |     |-- conf dir1
70   |     |       |
71   |     |       |- conf file 1
72   |   [...]    [...]
73   |
74   |- machine2 (optional)
75   |     |
76   |     |-- conf dir2
77   |     |       |
78   |     |       |- conf file 2
79   |   [...]    [...]
80
81Use of machines require use of option -m (if using cbusemachines in cb.conf)
82If not, the conf dirs are directly attached to the base dir
83
84=head1 EXAMPLES
85
86    # this will create the appropriate CasparBuster environment
87    # under the base ~/prj/musique-ancienne.org directory (Cf cbbasedir in cb.conf)
88    # containing the directory victoria2 for this machine
89    # under which it will copy the required structure if needed (/etc/ssh)
90    # to finaly put a copy of the file sshd_conf in it from the victoria2 machine
91
92    cbusterize -m victoria2 -s /etc/ssh/sshd_config
93
94=head1 AUTHOR
95
96=over 4
97
98Bruno Cornec, http://brunocornec.wordpress.com
99
100=back
101
102=head1 LICENSE
103
104Copyright (C) 2012  Bruno Cornec <bruno@project-builder.org>
105Released under the GPLv2 or the Artistic license at your will.
106
107=cut
108use strict;
109use CasparBuster::Version;
110use CasparBuster::Env;
111use CasparBuster::Plugin;
112use File::Basename;
113use File::Path;
114use Getopt::Long;
115use Pod::Usage;
116use Data::Dumper;
117use Net::SSH2;
118use Cwd;
119use Archive::Tar;
120use Carp;
121use ProjectBuilder::Base;
122use ProjectBuilder::Conf;
123use ProjectBuilder::VCS;
124
125# settings
126my $debug = 0;
127my $help = undef;
128my $man = undef;
129my $source = undef;
130my $machine = undef;
131my $plugin = undef;
132my $quiet = undef;
133my $force = undef;
134my $log = undef;
135my $LOG = undef;
136my $ssh2 = undef;
137my $chan = undef;
138
139my ($cbver,$cbrev) = cb_version_init();
140my $appname = "cb";
141$ENV{'PBPROJ'} = $appname;
142pb_temp_init();
143
144# Initialize the syntax string
145pb_syntax_init("$appname (aka CasparBuster) Version $cbver-$cbrev\n");
146
147# parse command-line options
148GetOptions(
149    'machine|m=s' => \$machine,
150    'debug|d+'    => \$debug,
151    'help|h'      => \$help,
152    'quiet|q'     => \$quiet,
153    'force|f'     => \$force,
154    'man'         => \$man,
155    'logfile|l=s' => \$log,
156    'source|s=s'  => \$source,
157    'plugin|p=s'  => \$plugin,
158) || pb_syntax(-1,0);
159
160if (defined $help) {
161    pb_syntax(0,1);
162}
163if (defined $man) {
164    pb_syntax(0,2);
165}
166if (defined $quiet) {
167    $debug=-1;
168}
169if (defined $log) {
170    open(LOG,"> $log") || die "Unable to log to $log: $!";
171    $LOG = \*LOG;
172    $debug = 0  if ($debug == -1);
173}
174
175$pbdebug = $debug;
176pb_log_init($debug, $LOG);
177pb_log(0,"Starting cbusterize\n");
178
179# Get conf file in context
180pb_conf_init($appname);
181# The personal one if there is such
182pb_conf_add("$ENV{'HOME'}/.cbrc") if (-f "$ENV{'HOME'}/.cbrc");
183# The system one
184pb_conf_add(cb_env_conffile());
185
186# Get configuration parameters
187my %cb;
188my $cb = \%cb;
189($cb->{'basedir'},$cb->{'usemachines'},$cb->{'cms'}) = pb_conf_get("cbbasedir","cbusemachines","cbcms");
190pb_log(2,"%cb: ",Dumper($cb));
191
192# Check for mandatory params
193pod2usage("Error: --source or --plugin is a mandatory argument\n") if ((not defined $source) && (not defined $plugin));
194pod2usage("Error: --machine is a mandatory argument when configure with cbusemachines = true\n") if (($cb->{'usemachines'}->{$appname} =~ /true/) && (not defined $machine));
195
196if (defined $plugin) {
197    # Load plugins
198    cb_plugin_load();
199}
200
201my $basedir = $cb->{'basedir'}->{$appname};
202eval { $basedir =~ s/(\$ENV.+\})/$1/eeg };
203
204pb_log(1, "DEBUG MODE, not doing anything, just printing\nDEBUG: basedir = $basedir\n");
205pb_log(1, "DEBUG: source = $source\n") if (defined $source);
206pb_log(1, "DEBUG: machine = $machine\n") if (defined $machine);
207
208# Use potentially a remote account if defined
209my $account = undef;
210my $remote = undef;
211($account) = pb_conf_get_if("cbaccount") if (defined $machine);
212$remote = $account->{$machine} if ((defined $account) && (defined $machine) && (defined $account->{$machine}));
213pb_log(1, "DEBUG: remote account1 = $remote\n") if (defined $remote);
214$remote = getpwuid($<) if (not defined $remote);
215pb_log(1, "DEBUG: remote account2 = $remote\n");
216
217# Create basedir if it doesn't exist
218if (not -d $basedir) {
219    if ($debug) {
220        pb_log(1, "DEBUG: Creating recursively directory $basedir\n");
221    } else {
222        pb_mkdir_p($basedir) || die "Unable to recursively create $basedir: $!";
223    }
224}
225
226if (defined $source) {
227    foreach my $f (split(/,/,$source)) {   
228        cb_busterize($f,"true");
229    }
230}
231
232# Now handle plugins if any
233my $cbp = ();
234
235if (defined $plugin) {
236    foreach my $p (split(/,/,$plugin)) {   
237        pb_log(1,"Getting context for plugin $p\n");
238        $cbp = cb_plugin_get($p,$cbp);
239        pb_log(2,"cbp: ".Dumper($cbp)."\n");
240        foreach my $k (keys %{$cbp->{$plugin}->{'dirsandfiles'}}) {
241            cb_busterize($k,"true");
242        }
243        foreach my $k ((keys %{$cbp->{$plugin}->{'dirs'}}),(keys %{$cbp->{$plugin}->{'files'}})) {
244            cb_busterize($k,"false");
245        }
246    }
247}
248
249$ssh2->disconnect() if (defined $machine);
250
251sub cb_busterize {
252
253my $source = shift;
254my $recur = shift;
255
256pb_log(2,"Entering cb_busterize source: $source\n");
257# Is the source a file or a dir ? Split the source parameter in 2
258my $srcdir = undef;
259my $srcfile = undef;
260my $cmd = undef;
261my $sftp;
262
263if (not defined $machine) {
264    if (-d $source) {
265        $srcdir = $source;
266    } else {
267        $srcdir = dirname($source);
268        $srcfile = basename($source);
269    }
270} else {
271    if (not defined $ssh2) {
272        pb_log(1,"DEBUG: First time so we need to create the SSH::Net2 object\n");
273        $ssh2 = Net::SSH2->new();
274        if ($debug >= 3) {
275            $ssh2->debug(1);
276        }
277        $ssh2->connect($machine) || confess "Unable to connect to $remote\@$machine: $!";
278        my $hdir = (getpwnam(getpwuid($<)))[7];
279        confess "Unable to connect to $remote\@$machine: $!" if (not $ssh2->auth_publickey($remote,"$hdir/.ssh/id_dsa.pub","$hdir/.ssh/id_dsa"));
280        $chan = $ssh2->channel();
281        die "Unable to create channel for $remote\@$machine: $!" if (not defined $chan);
282        my ($code, $error_name, $error_string) = $ssh2->error();
283        if ($code ne 0) {
284            pb_log(0,"code = $code");
285            pb_log(0,"error_name = $error_name");
286            pb_log(0,"error_string = $error_string");
287        }
288        #$ssh2->blocking(0);
289        if ($debug) {
290            pb_log(1,"DEBUG: launching a shell via Net:SSH2 ($remote\@$machine)");
291        }
292        confess "Unable to launch remote shell through Net:SSH2 ($remote\@$machine)" if (not $chan->shell());
293    }
294    $sftp = $ssh2->sftp;
295    die "Unable to create sftp channel for $remote\@$machine: $!" if (not defined $sftp);
296    my %dirs = $sftp->stat("$source/.");
297    my $res = 0;
298    $res = -1 if (not defined $dirs{'mode'});
299    pb_log(2,"DEBUG: Found res = $res\n");
300    if ($res == 0) {
301        $srcdir = $source;
302        pb_log(1,"DEBUG: Found remote dir = $source\n");
303    } else {
304        $srcdir = dirname($source);
305        $srcfile = basename($source);
306        pb_log(1,"DEBUG: Found remote file = $source\n");
307    }
308}
309
310pb_log(1,"DEBUG: Found srcdir = $srcdir\n");
311if (defined $srcfile) {
312    pb_log(1,"DEBUG: Found srcfile = $srcfile\n");
313} else {
314    pb_log(1,"DEBUG: Found no srcfile\n");
315}
316
317# Deduce the target directory from the local structure and the source
318my $target = $basedir;
319$target .= "/$machine" if defined ($machine);
320$target .= "$srcdir";
321
322my $scheme = $cb->{'cms'}->{$appname};
323
324# If both source and target are dirs, then copy into the parent of the target
325$target = basename($target) if ((not defined $srcfile) && (-d $target));
326
327# Create target if it doesn't exist
328if (not -d $target) {
329    if ($debug) {
330        pb_log(1,"DEBUG: Creating recursively directory $target\n");
331    } else {
332        pb_mkdir_p($target) || confess "Unable to recursively create $target: $!";
333    }
334    # Add all the dirs in it to VCS (in reverse order)
335    my $tdir = $target;
336    my @tab = ();
337    while ($tdir ne $basedir) {
338        push(@tab,$tdir);
339        $tdir = dirname($tdir);
340        pb_log(3,"tdir is now $tdir\n");
341    }
342    if ($debug) {
343        pb_log(0,"INFO: Added to your $scheme system the dirs: ".join(' ',reverse(@tab))."\n");
344    } else {
345        pb_vcs_add($scheme,reverse(@tab));
346    }
347}
348
349# For local handling
350my $cmdopt = "";
351# Recursive if we copy dirs
352$cmdopt = "-r" if (not defined $srcfile);
353$cmd = "cp -p $cmdopt $source $target";
354
355if (defined $srcfile) {
356    # File case
357    if ((! -f "$target/$srcfile") || (defined $force)) {
358        # doesn't exist locally
359        if (defined $machine) {
360            # Remote
361            cb_ssh_do($remote,$source,$target,$debug);
362        } else {
363            # Local
364            if ($debug) {
365                pb_log(1,"DEBUG: launching $cmd\n");
366            } else {
367                pb_system($cmd);
368            }
369        }
370        if ($debug) {
371            pb_log(1,"DEBUG: Adding $target/$srcfile to your $scheme system\n");
372        } else {
373            pb_vcs_add($scheme,"$target/$srcfile");
374            pb_log(0,"INFO: Created $target/$srcfile and added it to your $scheme system\n");
375        }
376    } else {
377        pb_log(0,"INFO: File $target/$srcfile already there\n");
378    }
379} else {
380    # Directory case
381    if ($recur eq "true") {
382        # with files in it
383        if ((! -d "$target") || (defined $force)) {
384            # doesn't exist locally
385            if (defined $machine) {
386                # Remote
387                cb_ssh_do($remote,$source,$target,$debug);
388            } else {
389                # Local
390                if ($debug) {
391                    pb_log(1,"DEBUG: launching $cmd\n");
392                } else {
393                    pb_system($cmd);
394                }
395            }
396        } else {
397            pb_log(0,"INFO: Directory $target already there\n");
398        }
399    } else {
400        # Only deal with that dir, nothing below, so just created locally
401        if ($debug) {
402            pb_log(1,"DEBUG: mkdir -p $target\n");
403        } else {
404            pb_mkdir_p("$target");
405        }
406    }
407    if ($debug) {
408        pb_log(1,"DEBUG: Adding $target to your $scheme system\n");
409    } else {
410        pb_vcs_add($scheme,"$target");
411        pb_log(0,"INFO: Created $target and added it to your $scheme system\n");
412    }
413}
414
415pb_log(2,"Exiting cb_busterize\n");
416}
417
418sub cb_ssh_do {
419
420my $remote = shift;
421my $source = shift;
422my $target = shift;
423my $debug = shift;
424
425# TODO: if sudo asks a passwd it won't work.
426my $cmd = "sudo tar cvhf /tmp/cb.$$.tar $source\n"; 
427if ($debug) {
428    pb_log(1,"DEBUG: launching through Net:SSH2 ($remote\@$machine) $cmd");
429}
430print $chan "$cmd";
431pb_log(1,"DEBUG: LINE : $_") while <$chan>;
432$cmd = "sudo chmod 600 /tmp/cb.$$.tar\n"; 
433if ($debug) {
434    pb_log(1,"DEBUG: launching through Net:SSH2 ($remote\@$machine) $cmd");
435}
436print $chan "$cmd";
437pb_log(1,"DEBUG: LINE : $_") while <$chan>;
438$cmd = "sudo chown $remote /tmp/cb.$$.tar\n"; 
439if ($debug) {
440    pb_log(1,"DEBUG: launching through Net:SSH2 ($remote\@$machine) $cmd");
441}
442print $chan "$cmd";
443pb_log(1,"DEBUG: LINE : $_") while <$chan>;
444if ($debug) {
445    pb_log(1,"DEBUG: gettting through Net:SSH2 ($remote\@$machine) /tmp/cb.$$.tar\n");
446}
447$ssh2->scp_get("/tmp/cb.$$.tar","/tmp/cb.$$.tar");
448if ($debug) {
449    pb_log(1,"DEBUG: erasing through Net:SSH2 ($remote\@$machine) /tmp/cb.$$.tar\n");
450} else {
451    print $chan "sudo rm -f /tmp/cb.$$.tar\n";
452}
453my $tar = Archive::Tar->new("/tmp/cb.$$.tar");
454$tar->setcwd($target);
455if ($debug) {
456    pb_log(1,"DEBUG: Extracting /tmp/cb.$$.tar\n");
457    foreach my $f ($tar->list_files()) {
458        pb_log(1,"DEBUG: $f\n");
459    }
460} else {
461    $tar->extract();
462}
463if ($debug) {
464    pb_log(1,"DEBUG: cleanup\n");
465} else {
466    $tar->clear;
467    unlink("/tmp/cb.$$.tar");
468}
469}
Note: See TracBrowser for help on using the repository browser.