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

Last change on this file since 1547 was 1547, checked in by Bruno Cornec, 12 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.