1# cluster-software-lib.pl 2# common functions for installing packages across a cluster 3# XXX refresh all packages after installing 4 5BEGIN { push(@INC, ".."); }; 6use WebminCore; 7&init_config(); 8&foreign_require("servers", "servers-lib.pl"); 9&foreign_require("software", "software-lib.pl"); 10$parallel_max = 20; 11%access = &get_module_acl(); 12 13# list_software_hosts() 14# Returns a list of all hosts whose software is being managed by this module 15sub list_software_hosts 16{ 17local @rv; 18local %smap = map { $_->{'id'}, $_ } &list_servers(); 19local $hdir = "$module_config_directory/hosts"; 20opendir(DIR, $hdir); 21foreach $h (readdir(DIR)) { 22 next if ($h =~ /\.host$/ || $h eq '.' || $h eq '..'); 23 local %host = ( 'id', $h ); 24 next if (!$smap{$h}); # underlying server was deleted 25 opendir(PDIR, "$hdir/$h") || next; 26 foreach $p (readdir(PDIR)) { 27 next if ($p eq "." || $p eq ".."); 28 local %pkg; 29 &read_file("$hdir/$h/$p", \%pkg); 30 push(@{$host{'packages'}}, \%pkg); 31 } 32 closedir(PDIR); 33 &read_file("$hdir/$h.host", \%host); 34 push(@rv, \%host); 35 } 36closedir(DIR); 37return @rv; 38} 39 40# save_software_host(&host) 41# Add or update a managed host with it's package list 42sub save_software_host 43{ 44local $hdir = "$module_config_directory/hosts"; 45mkdir($hdir, 0700); 46if (-d "$hdir/$_[0]->{'id'}") { 47 opendir(DIR, "$hdir/$_[0]->{'id'}"); 48 foreach $f (readdir(DIR)) { 49 unlink("$hdir/$_[0]->{'id'}/$f"); 50 } 51 closedir(DIR); 52 } 53else { 54 mkdir("$hdir/$_[0]->{'id'}", 0700); 55 } 56foreach $p (@{$_[0]->{'packages'}}) { 57 local $pname = $p->{'name'}; 58 $pname =~ s/\//_/g; 59 &write_file("$hdir/$_[0]->{'id'}/$pname", $p); 60 } 61local %h = %{$_[0]}; 62delete($h{'packages'}); 63&write_file("$hdir/$_[0]->{'id'}.host", \%h); 64} 65 66# delete_software_host(&host) 67sub delete_software_host 68{ 69&unlink_file("$module_config_directory/hosts/$_[0]->{'id'}.host"); 70&unlink_file("$module_config_directory/hosts/$_[0]->{'id'}"); 71} 72 73# list_servers() 74# Returns a list of all servers from the webmin servers module that can be 75# managed, plus this server 76sub list_servers 77{ 78local @servers = &servers::list_servers_sorted(); 79return ( &servers::this_server(), grep { $_->{'user'} } @servers ); 80} 81 82# host_to_server(&host|id) 83sub host_to_server 84{ 85local $id = ref($_[0]) ? $_[0]->{'id'} : $_[0]; 86local ($serv) = grep { $_->{'id'} eq $id } &list_servers(); 87return $serv; 88} 89 90# server_name(&server) 91sub server_name 92{ 93return $_[0]->{'desc'} || $_[0]->{'realhost'} || $_[0]->{'host'}; 94} 95 96# get_heiropen(hostid) 97# Returns an array of open categories 98sub get_heiropen 99{ 100open(HEIROPEN, "<$module_config_directory/heiropen.$_[0]"); 101local @heiropen = <HEIROPEN>; 102chop(@heiropen); 103close(HEIROPEN); 104return @heiropen; 105} 106 107# save_heiropen(&heir, hostid) 108sub save_heiropen 109{ 110&open_tempfile(HEIR, ">$module_config_directory/heiropen.$_[1]"); 111foreach $h (@{$_[0]}) { 112 &print_tempfile(HEIR, $h,"\n"); 113 } 114&close_tempfile(HEIR); 115} 116 117# create_on_input(desc, [no-donthave], [no-have]) 118sub create_on_input 119{ 120local @hosts = &list_software_hosts(); 121local @servers = &list_servers(); 122local @opts; 123push(@opts, [ -1, $text{'edit_all'} ]); 124push(@opts, [ -2, $text{'edit_donthave'} ]) if (!$_[1]); 125push(@opts, [ -3, $text{'edit_have'} ]) if (!$_[2]); 126local @groups = &servers::list_all_groups(\@servers); 127local $h; 128foreach $h (@hosts) { 129 local ($s) = grep { $_->{'id'} == $h->{'id'} } @servers; 130 if ($s) { 131 push(@opts, [ $s->{'id'}, 132 $s->{'desc'} || $s->{'realhost'} || $s->{'host'} ]); 133 $gothost{$s->{'host'}}++; 134 } 135 } 136local $g; 137foreach $g (@groups) { 138 local ($found, $m); 139 foreach $m (@{$g->{'members'}}) { 140 ($found++, last) if ($gothost{$m}); 141 } 142 push(@opts, [ "group_$g->{'name'}", 143 &text('edit_group', $g->{'name'}) ]) if ($found); 144 } 145local $sel = &ui_select("server", undef, \@opts); 146if ($_[0]) { 147 print &ui_table_row($_[0], $sel); 148 } 149else { 150 print $sel; 151 } 152} 153 154# create_on_parse(prefix, &already, name) 155sub create_on_parse 156{ 157local @hosts = &list_software_hosts(); 158local @servers = &list_servers(); 159if ($in{'server'} == -2) { 160 # Install on hosts that don't have it 161 local %already = map { $_->{'id'}, 1 } @{$_[1]}; 162 @hosts = grep { !$already{$_->{'id'}} } @hosts; 163 print "<b>",&text($_[0].'3', $_[2]),"</b><p>\n"; 164 } 165elsif ($in{'server'} == -3) { 166 # Install on hosts that do have it 167 local %already = map { $_->{'id'}, 1 } @{$_[1]}; 168 @hosts = grep { $already{$_->{'id'}} } @hosts; 169 print "<b>",&text($_[0].'6', $_[2]),"</b><p>\n"; 170 } 171elsif ($in{'server'} =~ /^group_(.*)/) { 172 # Install on members of some group 173 local ($group) = grep { $_->{'name'} eq $1 } 174 &servers::list_all_groups(\@servers); 175 @hosts = grep { local $hid = $_->{'id'}; 176 local ($s) = grep { $_->{'id'} == $hid } @servers; 177 &indexof($s->{'host'}, @{$group->{'members'}}) >= 0 } 178 @hosts; 179 print "<b>",&text($_[0].'4', $_[2], $group->{'name'}), 180 "</b><p>\n"; 181 } 182elsif ($in{'server'} != -1) { 183 # Just install on one host 184 @hosts = grep { $_->{'id'} == $in{'server'} } @hosts; 185 local ($s) = grep { $_->{'id'} == $hosts[0]->{'id'} } @servers; 186 print "<b>",&text($_[0].'5', $_[2], 187 &server_name($s)),"</b><p>\n"; 188 } 189else { 190 # Installing on every host 191 print "<b>",&text($_[0], join(" ", @names)),"</b><p>\n"; 192 } 193return @hosts; 194} 195 196# Setup error handler for down hosts 197sub add_error 198{ 199$add_error_msg = join("", @_); 200} 201 202# add_managed_host(&server) 203# Adds a new system to this module for management, and returns a status code 204# (0 or 1) and error or information message 205sub add_managed_host 206{ 207local ($s) = @_; 208 209&remote_error_setup(\&add_error); 210 211# Get the packages for each host 212local %sconfig = &foreign_config("software"); 213$add_error_msg = undef; 214local $host = { 'id' => $s->{'id'} }; 215local $soft = &remote_foreign_check($s->{'host'}, "software"); 216if ($add_error_msg) { 217 return (0, $add_error_msg); 218 } 219if (!$soft) { 220 return (0, &text('add_echeck', $s->{'host'})); 221 } 222&remote_foreign_require($s->{'host'}, "software", "software-lib.pl"); 223local $rconfig = &remote_foreign_config($s->{'host'}, "software"); 224#if ($rconfig->{'package_system'} ne $sconfig{'package_system'}) { 225# return (0, &text('add_esystem', $s->{'host'})); 226# } 227$host->{'package_system'} = $rconfig->{'package_system'}; 228local $gconfig = &remote_foreign_config($s->{'host'}, undef); 229foreach $g ('os_type', 'os_version', 230 'real_os_type', 'real_os_version') { 231 $host->{$g} = $gconfig->{$g}; 232 } 233local $n = &remote_foreign_call($s->{'host'}, "software", 234 "list_packages"); 235local $packages = &remote_eval($s->{'host'}, "software", "\\%packages"); 236for(my $i=0; $i<$n; $i++) { 237 push(@{$host->{'packages'}}, 238 { 'name' => $packages->{$i,'name'}, 239 'class' => $packages->{$i,'class'}, 240 'desc' => $packages->{$i,'desc'}, 241 'version' => $packages->{$i,'version'}, 242 'nouninstall' => $packages->{$i,'nouninstall'}, 243 'nolist' => $packages->{$i,'nolist'}, }); 244 } 245&save_software_host($host); 246return (1, &text('add_ok', &server_name($s), $n)); 247} 248 249# refresh_packages(&hosts) 250# Update the local cache with actual installed packages. Returns an array 251# of either an error messages or two arrays of added and removed packages. 252sub refresh_packages 253{ 254local ($hosts) = @_; 255local @servers = &list_servers(); 256 257# Setup error handler for down hosts 258sub ref_error 259{ 260$ref_error_msg = join("", @_); 261} 262&remote_error_setup(\&ref_error); 263 264local $p = 0; 265foreach my $h (@$hosts) { 266 local ($s) = grep { $_->{'id'} == $h->{'id'} } @servers; 267 268 local ($rh = "READ$p", $wh = "WRITE$p"); 269 pipe($rh, $wh); 270 if (!fork()) { 271 close($rh); 272 if ($s) { 273 # Refresh the list 274 &remote_foreign_require($s->{'host'}, "software", 275 "software-lib.pl"); 276 if ($ref_error_msg) { 277 # Host is down .. 278 print $wh &serialise_variable($ref_error_msg); 279 exit; 280 } 281 local $gconfig = &remote_foreign_config($s->{'host'}, undef); 282 foreach $g ('os_type', 'os_version', 283 'real_os_type', 'real_os_version') { 284 $h->{$g} = $gconfig->{$g}; 285 } 286 local @old = map { $_->{'name'} } @{$h->{'packages'}}; 287 undef($h->{'packages'}); 288 local $n = &remote_foreign_call($s->{'host'}, "software", 289 "list_packages"); 290 local $packages = &remote_eval($s->{'host'}, "software", 291 "\\%packages"); 292 local @added; 293 for($i=0; $i<$n; $i++) { 294 next if (!$packages->{$i,'name'}); 295 push(@{$h->{'packages'}}, 296 { 'name' => $packages->{$i,'name'}, 297 'class' => $packages->{$i,'class'}, 298 'desc' => $packages->{$i,'desc'}, 299 'version' => $packages->{$i,'version'}, 300 'nouninstall' => $packages->{$i,'nouninstall'}, 301 'nolist' => $packages->{$i,'nolist'}, }); 302 $idx = &indexof($packages->{$i,'name'}, @old); 303 if ($idx < 0) { 304 push(@added, $packages->{$i,'name'}); 305 } 306 else { 307 splice(@old, $idx, 1); 308 } 309 } 310 &save_software_host($h); 311 $rv = [ \@added, \@old ]; 312 } 313 else { 314 # remove from managed list 315 &delete_software_host($h); 316 $rv = undef; 317 } 318 print $wh &serialise_variable($rv); 319 close($wh); 320 exit; 321 } 322 close($wh); 323 $p++; 324 } 325 326# Read back results 327$p = 0; 328local @results; 329foreach my $h (@hosts) { 330 local ($s) = grep { $_->{'id'} == $h->{'id'} } @servers; 331 local $rh = "READ$p"; 332 local $line = <$rh>; 333 local $rv = &unserialise_variable($line); 334 close($rh); 335 push(@results, $rv); 336 $p++; 337 } 338 339return @results; 340} 341 342# same_package_system(&host) 343# Returns 1 if some host is using the same package system as this master 344sub same_package_system 345{ 346local ($host) = @_; 347return !$host->{'package_system'} || 348 $host->{'package_system'} eq $software::config{'package_system'}; 349} 350 3511; 352 353