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

Revision 1547, 12.6 KB checked in by bruno, 12 months ago (diff)
  • 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 *
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.