RT# 83450 - fixed rateplan export
[freeside.git] / FS / FS / part_export / shellcommands_expect.pm
1 package FS::part_export::shellcommands_expect;
2 use base qw( FS::part_export::shellcommands );
3
4 use strict;
5 use Tie::IxHash;
6 use Net::OpenSSH;
7 use Expect;
8 #use FS::Record qw( qsearch qsearchs );
9
10 tie my %options, 'Tie::IxHash',
11   'user'      => { label =>'Remote username', default=>'root' },
12   'useradd'   => { label => 'Insert commands',    type => 'textarea', },
13   'userdel'   => { label => 'Delete commands',    type => 'textarea', },
14   'usermod'   => { label => 'Modify commands',    type => 'textarea', },
15   'suspend'   => { label => 'Suspend commands',   type => 'textarea', },
16   'unsuspend' => { label => 'Unsuspend commands', type => 'textarea', },
17   'debug'     => { label => 'Enable debugging',
18                    type  => 'checkbox',
19                    value => 1,
20                  },
21 ;
22
23 our %info = (
24   'svc'     => 'svc_acct',
25   'desc'    => 'Real time export via remote SSH, with interactive ("Expect"-like) scripting, for svc_acct services',
26   'options' => \%options,
27   'notes'   => q[
28 Interactively run commands via SSH in a remote terminal, like "Expect".  In
29 most cases, you probably want a regular shellcommands (or broadband_shellcommands, etc.) export instead, unless
30 you have a specific need to interact with a terminal-based interface in an
31 "Expect"-like fashion.
32 <BR><BR>
33
34 Each line specifies a string to match and a command to
35 run after that string is found, separated by the first space.  For example, to
36 run "exit" after a prompt ending in "#" is sent, "# exit".  You will need to
37 <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Administration:SSH_Keys">setup SSH for unattended operation</a>.
38 <BR><BR>
39
40 In commands, all variable substitutions of the regular shellcommands (or
41 broadband_shellcommands, etc.) export are available (use a backslash to escape
42 a literal $).
43 ]
44 );
45
46 sub _export_command {
47   my ( $self, $action, $svc_acct) = (shift, shift, shift);
48   my @lines = split("\n", $self->option($action) );
49
50   return '' unless @lines;
51
52   my @commands = ();
53   foreach my $line (@lines) {
54     my($match, $command) = split(' ', $line, 2);
55     my( $command_string ) = $self->_export_subvars( $svc_acct, $command, '' );
56     push @commands, [ $match, $command_string ];
57   }
58
59   $self->shellcommands_expect_queue( $svc_acct->svcnum, @commands );
60 }
61
62 sub _export_replace {
63   my( $self, $new, $old ) = (shift, shift, shift);
64   my @lines = split("\n", $self->option('replace') );
65
66   return '' unless @lines;
67
68   my @commands = ();
69   foreach my $line (@lines) {
70     my($match, $command) = split(' ', $line, 2);
71     my( $command_string ) = $self->_export_subvars_replace( $new, $old, $command, '' );
72     push @commands, [ $match, $command_string ];
73   }
74
75   $self->shellcommands_expect_queue( $new->svcnum, @commands );
76 }
77
78 sub shellcommands_expect_queue {
79   my( $self, $svcnum, @commands ) = @_;
80
81   my $queue = new FS::queue {
82     'svcnum' => $svcnum,
83     'job'    => "FS::part_export::shellcommands_expect::ssh_expect",
84   };
85   $queue->insert(
86     user          => $self->option('user') || 'root',
87     host          => $self->machine,
88     debug         => $self->option('debug'),
89     commands      => \@commands,
90   );
91 }
92
93 sub ssh_expect { #subroutine, not method
94   my $opt = { @_ };
95
96   my $dest = $opt->{'user'}.'@'.$opt->{'host'};
97
98   open my $def_in, '<', '/dev/null' or die "unable to open /dev/null\n";
99   my $ssh = Net::OpenSSH->new( $dest, 'default_stdin_fh' => $def_in );
100   # ignore_all_errors doesn't override SSH connection/auth errors--
101   # probably correct
102   die "Couldn't establish SSH connection to $dest: ". $ssh->error
103     if $ssh->error;
104
105   my ($pty, $pid) = $ssh->open2pty
106     or die "Couldn't start a remote terminal session";
107   my $expect = Expect->init($pty);
108   #not useful #$expect->debug($opt->{debug} ? 3 : 0);
109
110   foreach my $line ( @{ $opt->{commands} } ) {
111     my( $match, $command ) = @$line;
112
113     warn "Waiting for '$match'\n" if $opt->{debug};
114
115     my $matched = $expect->expect(30, $match);
116     unless ( $matched ) {
117       my $err = "Never saw '$match'\n";
118       warn $err;
119       die $err;
120     }
121     warn "Running '$command'\n" if $opt->{debug};
122     $expect->send("$command\n");
123   }
124
125   '';
126 }
127
128 1;