# Sumavi Inc (C) 2010 # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html ##################################################### # imgport will export and import xCAT stateless, statelite, and diskful templates. # This will make it so that you can easily share your images with others. # All your images are belong to us! package xCAT_plugin::imgport; BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; } use lib "$::XCATROOT/lib/perl"; use strict; use warnings; #use xCAT::Table; #use xCAT::Schema; #use xCAT::NodeRange qw/noderange abbreviate_noderange/; #use xCAT::Utils; use xCAT::TableUtils; use Data::Dumper; use XML::Simple; use POSIX qw/strftime/; use Getopt::Long; use File::Temp; use File::Copy; use File::Path qw/mkpath/; use File::Basename; use xCAT::NodeRange; use Cwd; my $requestcommand; $::VERBOSE = 0; 1; #some quick aliases to table/value my %shortnames = ( groups => [qw(nodelist groups)], tags => [qw(nodelist groups)], mgt => [qw(nodehm mgt)], #switch => [qw(switch switch)], ); ##################################################### # Return list of commands handled by this plugin ##################################################### sub handled_commands { return { imgexport => "imgport", imgimport => "imgport", }; } ##################################################### # Process the command ##################################################### sub process_request { #use Getopt::Long; Getopt::Long::Configure("bundling"); #Getopt::Long::Configure("pass_through"); Getopt::Long::Configure("no_pass_through"); my $request = shift; my $callback = shift; $requestcommand = shift; my $command = $request->{command}->[0]; my $args = $request->{arg}; if ($command eq "imgexport"){ return xexport($request, $callback); }elsif ($command eq "imgimport"){ return ximport($request, $callback); }else{ print "Error: $command not found in export\n"; $callback->({error=>["Error: $command not found in this module."],errorcode=>[1]}); #return (1, "$command not found in sumavinode"); } } # extract the bundle, then add it to the osimage table. Basically the ying of the yang of the xexport # function. sub ximport { my $request = shift; my $callback = shift; my %rsp; # response my $help; my $nodes; my $new_profile; my $xusage = sub { my $ec = shift; push@{ $rsp{data} }, "imgimport: Takes in an xCAT image bundle and defines it to xCAT so you can use it"; push@{ $rsp{data} }, "Usage: "; push@{ $rsp{data} }, "\timgimport [-h|--help]"; push@{ $rsp{data} }, "\timgimport [-p|--postscripts ] [-f|--profile ] [-v]"; if($ec){ $rsp{errorcode} = $ec; } $callback->(\%rsp); }; unless(defined($request->{arg})){ $xusage->(1); return; } @ARGV = @{ $request->{arg}}; if($#ARGV eq -1){ $xusage->(1); return; } GetOptions( 'h|?|help' => \$help, 'v|verbose' => \$::VERBOSE, 'p|postscripts=s' => \$nodes, 'f|profile=s' => \$new_profile, ); if($help){ $xusage->(0); return; } # first extract the bundle extract_bundle($request, $callback,$nodes,$new_profile); } # function to export your image. The image should already be in production, work well, and have # no bugs. Lots of places will have problems because the image may not be in osimage table # or they may have hardcoded things, or have post install scripts. sub xexport { my $request = shift; my $callback = shift; my %rsp; # response my $help; my @extra; my $node; my $xusage = sub { my $ec = shift; push@{ $rsp{data} }, "imgexport: Creates a tarball (bundle) of an existing xCAT image"; push@{ $rsp{data} }, "Usage: "; push@{ $rsp{data} }, "\timgexport [-h|--help]"; push@{ $rsp{data} }, "\timgexport [destination] [[-e|--extra ] ... ] [-p|--postscripts ] [-v]"; if($ec){ $rsp{errorcode} = $ec; } $callback->(\%rsp); }; unless(defined($request->{arg})){ $xusage->(1); return; } @ARGV = @{ $request->{arg}}; if($#ARGV eq -1){ $xusage->(1); return; } GetOptions( 'h|?|help' => \$help, 'p|postscripts=s' => \$node, 'e|extra=s' => \@extra, 'v|verbose' => \$::VERBOSE ); if($help){ $xusage->(0); return; } # ok, we're done with all that. Now lets actually start doing some work. my $img_name = shift @ARGV; my $dest = shift @ARGV; my $cwd = $request->{cwd}; #getcwd; $cwd = $cwd->[0]; $callback->( {data => ["Exporting $img_name to $cwd..."]}); # check if all files are in place my $attrs = get_image_info($img_name, $callback, $node, @extra); #print Dumper($attrs); unless($attrs){ return 1; } # make manifest and tar it up. make_bundle($img_name, $dest, $attrs, $callback,$cwd); } # verify the image and return the values sub get_image_info { my $imagename = shift; my $callback = shift; my $node = shift; my @extra = @_; my $errors = 0; my $ostab = new xCAT::Table('osimage', -create=>1); unless($ostab){ $callback->( {error => ["Unable to open table 'osimage'."],errorcode=>1} ); return 0; } (my $attrs) = $ostab->getAttribs({imagename => $imagename}, 'profile', 'imagetype', 'provmethod', 'osname', 'osvers', 'osdistro', 'osarch', 'synclists'); if (!$attrs) { $callback->({error=>["Cannot find image \'$imagename\' from the osimage table."],errorcode=>[1]}); return 0; } unless($attrs->{provmethod}){ $callback->({error=>["The 'provmethod' field is not set for \'$imagename\' in the osimage table."],errorcode=>[1]}); $errors++; } unless($attrs->{profile}){ $callback->({error=>["The 'profile' field is not set for \'$imagename\' in the osimage table."],errorcode=>[1]}); $errors++; } unless($attrs->{osvers}){ $callback->({error=>["The 'osvers' field is not set for \'$imagename\' in the osimage table."],errorcode=>[1]}); $errors++; } unless($attrs->{osarch}){ $callback->({error=>["The 'osarch' field is not set for \'$imagename\' in the osimage table."],errorcode=>[1]}); $errors++; } unless($attrs->{provmethod} =~ /install|netboot|statelite/){ $callback->({error=>["Exporting images with 'provemethod' " . $attrs->{provmethod} . " is not supported. Hint: install, netboot, or statelite"],errorcode=>[1]}); $errors++; } $attrs->{imagename} = $imagename; if($errors){ return 0; } my $linuximagetab = new xCAT::Table('linuximage', -create=>1); unless($linuximagetab){ $callback->( {error => ["Unable to open table 'linuximage'"],errorcode=>1} ); return 0; } #from linuximage table (my $attrs1) = $linuximagetab->getAttribs({imagename => $imagename}, 'template', 'pkglist', 'pkgdir', 'otherpkglist', 'otherpkgdir', 'exlist', 'postinstall', 'rootimgdir', 'nodebootif', 'otherifce', 'netdrivers', 'kernelver', 'permission'); if (!$attrs1) { $callback->({error=>["Cannot find image \'$imagename\' from the linuximage table."],errorcode=>[1]}); return 0; } #merge attrs with attrs1 foreach (keys %$attrs1) { $attrs->{$_} = $attrs1->{$_}; } $attrs = get_files($imagename, $callback, $attrs); if($#extra > -1){ my $ex = get_extra($callback, @extra); if($ex){ $attrs->{extra} = $ex; } } #get postscripts if ($node) { $attrs = get_postscripts($node, $callback, $attrs) } # if we get nothing back, then we couldn't find the files. How sad, return nuthin' return $attrs; } sub get_postscripts { my $node = shift; my $errors = 0; my $callback = shift; my $attrs = shift; my @nodes = noderange($node); if (@nodes > 0) { $node = $nodes[0]; } else { $callback->( {error => ["Unable to get postscripts, $node is not a valide node."],errorcode=>1} ); return 0; } my $postscripts; my $postbootscripts; my $ptab = new xCAT::Table('postscripts', -create=>1); unless($ptab){ $callback->( {error => ["Unable to open table 'postscripts'."],errorcode=>1} ); return 0; } my $ent = $ptab->getNodeAttribs($node, ['postscripts', 'postbootscripts']); if ($ent) { if ($ent->{postscripts}) { $postscripts = $ent->{postscripts}; } if ($ent->{postbootscripts}) { $postbootscripts = $ent->{postbootscripts}; } } (my $attrs1) = $ptab->getAttribs({node => "xcatdefaults"}, 'postscripts', 'postbootscripts'); if ($attrs1) { if ($attrs1->{postscripts}) { if ($postscripts) { $postscripts = $attrs1->{postscripts} . ",$postscripts"; } else { $postscripts = $attrs1->{postscripts}; } } if ($attrs1->{postbootscripts}) { if ($postbootscripts) { $postbootscripts = $attrs1->{postbootscripts} . ",$postbootscripts"; } else { $postbootscripts = $attrs1->{postbootscripts}; } } } if ($postscripts) { $attrs->{postscripts} = $postscripts; } if ($postbootscripts) { $attrs->{postbootscripts} = $postbootscripts; } return $attrs; } # returns a hash of files # extra { # file => dir # file => dir # } sub get_extra { my $callback = shift; my @extra = @_; my $extra; # make sure that the extra is formatted correctly: foreach my $e (@extra){ my ($file , $to_dir) = split(/:/, $e); unless( -r $file){ $callback->({error=>["Can not find Extra file $file. Argument will be ignored"],errorcode=>[1]}); next; } #print "$file => $to_dir"; if (! $to_dir) { if (-d $file) { $to_dir=$file; } else { $to_dir=dirname($file); } } push @{ $extra}, { 'src' => $file, 'dest' => $to_dir }; } return $extra; } # well we check to make sure the files exist and then we return them. sub get_files{ my $imagename = shift; my $errors = 0; my $callback = shift; my $attrs = shift; # we'll hopefully get a reference to it and modify this variable. my @arr; # array of directory search paths my $template = ''; # todo is XCATROOT not going to be /opt/xcat/ in normal situations? We'll always # assume it is for now my $xcatroot = "/opt/xcat"; # get the install root my $installroot = xCAT::TableUtils->getInstallDir(); unless($installroot){ $installroot = '/install'; } my $provmethod = $attrs->{provmethod}; my $osvers = $attrs->{osvers}; # here's the case for the install. All we need at first is the # template. That should do it. if($provmethod =~ /install/){ @arr = ("$installroot/custom/install", "$xcatroot/share/xcat/install"); #get .tmpl file if ((! $attrs->{template}) || (! -r $attrs->{template})) { my $template = look_for_file('tmpl', $callback, $attrs, @arr); unless($template){ $callback->({error=>["Couldn't find install template for $imagename"],errorcode=>[1]}); $errors++; }else{ $callback->( {data => ["$template"]}); $attrs->{template} = $template; } } $attrs->{media} = "required"; } # for stateless I need to save the # ramdisk # the kernel # the rootimg.gz if($osvers !~ /esx/){ # don't do anything because these files don't exist for ESX stateless. if($provmethod =~ /netboot/){ @arr = ("$installroot/custom/netboot", "$xcatroot/share/xcat/netboot"); #get .pkglist file if ((! $attrs->{pkglist}) || (! -f $attrs->{pkglist})) { # we need to get the .pkglist for this one! my $temp = look_for_file('pkglist', $callback, $attrs, @arr); unless($temp){ $callback->({error=>["Couldn't find pkglist file for $imagename"],errorcode=>[1]}); $errors++; }else{ $attrs->{pkglist} = $temp; } } @arr = ("$installroot/netboot"); # look for ramdisk my $ramdisk = look_for_file('initrd-stateless.gz', $callback, $attrs, @arr); unless($ramdisk){ $callback->({error=>["Couldn't find ramdisk (initrd-stateless.gz) for $imagename"],errorcode=>[1]}); $errors++; }else{ $attrs->{ramdisk} = $ramdisk; } # look for kernel my $kernel = look_for_file('kernel', $callback, $attrs, @arr); unless($kernel){ $callback->({error=>["Couldn't find kernel (kernel) for $imagename"],errorcode=>[1]}); $errors++; }else{ $attrs->{kernel} = $kernel; } # look for rootimg.gz my $rootimg = look_for_file('rootimg.gz', $callback, $attrs, @arr); unless($rootimg){ $callback->({error=>["Couldn't find rootimg (rootimg.gz) for $imagename"],errorcode=>[1]}); $errors++; }else{ $attrs->{rootimg} = $rootimg; } } elsif ($provmethod =~ /statelite/) { @arr = ("$installroot/custom/netboot", "$xcatroot/share/xcat/netboot"); #get .pkglist file if ((! $attrs->{pkglist}) || (! -f $attrs->{pkglist})) { # we need to get the .pkglist for this one! my $temp = look_for_file('pkglist', $callback, $attrs, @arr); unless($temp){ $callback->({error=>["Couldn't find pkglist file for $imagename"],errorcode=>[1]}); $errors++; }else{ $attrs->{pkglist} = $temp; } } @arr = ("$installroot/netboot"); # look for kernel my $kernel = look_for_file('kernel', $callback, $attrs, @arr); unless($kernel){ $callback->({error=>["Couldn't find kernel (kernel) for $imagename"],errorcode=>[1]}); $errors++; }else{ $attrs->{kernel} = $kernel; } # look for ramdisk my $ramdisk = look_for_file('initrd-statelite.gz', $callback, $attrs, @arr); unless($ramdisk){ $callback->({error=>["Couldn't find ramdisk (initrd-statelite.gz) for $imagename"],errorcode=>[1]}); $errors++; }else{ $attrs->{ramdisk} = $ramdisk; } } } if($errors){ $attrs = 0; } return $attrs; } # argument: # type of file: This is usually the suffix of the file, or the file name. # attributes: These are the paramaters you got from the osimage table in a hash. # @dirs: Some search paths where we'll start looking for them. # then we just return a string of the full path to where the file is. # mostly because we just ooze awesomeness. sub look_for_file { my $file = shift; my $callback = shift; my $attrs = shift; my @dirs = @_; my $r_file = ''; my $profile = $attrs->{profile}; my $arch = $attrs->{osarch}; my $distname = $attrs->{osvers}; # go through the directories and look for the file. We hopefully will find it... foreach my $d (@dirs){ # widdle down rhel5.4, rhel5., rhel5, rhel, rhe, rh, r, my $dd = $distname; # dd is distro directory, or disco dave, whichever you prefer. if($dd =~ /win/){ $dd = 'windows' }; until(-r "$d/$dd" or not $dd){ $callback->({data=>["not in $d/$dd..."]}) if $::VERBOSE; chop($dd); } if($distname && (($file eq 'tmpl') || ($file eq 'pkglist'))){ $callback->({data=>["looking in $d/$dd..."]}) if $::VERBOSE; # now look for the file name: foo.rhel5.x86_64.tmpl (-r "$d/$dd/$profile.$distname.$arch.$file") && (return "$d/$dd/$profile.$distname.$arch.$file"); # now look for the file name: foo.rhel5.tmpl (-r "$d/$dd/$profile.$distname.$file") && (return "$d/$dd/$profile.$distname.$file"); # now look for the file name: foo.x86_64.tmpl (-r "$d/$dd/$profile.$arch.$file") && (return "$d/$dd/$profile.$arch.$file"); # finally, look for the file name: foo.tmpl (-r "$d/$dd/$profile.$file") && (return "$d/$dd/$profile.$file"); }else{ # this may find the ramdisk: /install/netboot/ (-r "$d/$dd/$arch/$profile/$file") && (return "$d/$dd/$arch/$profile/$file"); } } # I got nothing man. Can't find it. Sorry 'bout that. # returning nothing: return ''; } # here's where we make the tarball sub make_bundle { my $imagename = shift; my $dest = shift; my $attribs = shift; my $callback = shift; # tar ball is made in local working directory. Sometimes doing this in /tmp # is bad. In the case of my development machine, the / filesystem was nearly full. # so doing it in cwd is easy and predictable. my $dir = shift; #my $dir = getcwd; # get rid of spaces and put in underlines. $imagename =~ s/\s+/_/g; # we may find that cwd doesn't work, so we use the request cwd. my $ttpath = mkdtemp("$dir/imgexport.$$.XXXXXX"); $callback->({data=>["Creating $ttpath..."]}) if $::VERBOSE; my $tpath = "$ttpath/$imagename"; mkdir("$tpath"); chmod 0755,$tpath; #for statelite if ($attribs->{provmethod} eq 'statelite') { #copy the rootimgdir over my $rootimgdir=$attribs->{rootimgdir}; if ($rootimgdir) { $callback->({data=>["Packing root image. It will take a while"]}); system("cd $rootimgdir; find rootimg |cpio -H newc -o | gzip -c - > $tpath/rootimgtree.gz"); $attribs->{'rootimgtree'} = "$rootimgdir/rootimgtree.gz"; } else { $callback->({error=>["Couldn't locate the root image directory. "],errorcode=>[1]}); return 0; } #get litefile table setting for the image my $lftab= xCAT::Table->new("litefile" ,-create=>1); if (!$lftab) { $callback->({error=>["Could not open the litefile table."],errorcode=>[1]}); return 0; } $callback->({data=>["Getting litefile settings"]}); my @imageInfo; my @imagegroupsattr = ('groups'); # Check if this image contains osimage.groups attribute. # if so, means user wants to use specific directories to this image. my $osimagetab = xCAT::Table->new("osimage",-create=>1); my $imagegroups = $osimagetab->getAttribs({imagename => $imagename}, @imagegroupsattr); if ($imagegroups and $imagegroups->{groups}) { # get the directories with no names push @imageInfo, $lftab->getAttribs({image => ''}, ('file','options')); # get for the image groups specific directories push @imageInfo, $lftab->getAttribs({image => $imagegroups->{groups}}, ('file','options')); # get for the image specific directories push @imageInfo, $lftab->getAttribs({image => $imagename}, ('file','options')); } else { # get the directories with no names push @imageInfo, $lftab->getAttribs({image => ''}, ('file','options')); # get the ALL directories push @imageInfo, $lftab->getAttribs({image => 'ALL'}, ('file','options')); # get for the image specific directories push @imageInfo, $lftab->getAttribs({image => $imagename}, ('file','options')); } open(FILE,">$tpath/litefile.csv") or die "Could not open $tpath/litefile.csv"; foreach(@imageInfo){ my $file=$_->{file}; if(!$file){ next; } my $o = $_->{options}; if(!$o){ $o = "tmpfs"; } print FILE "\"$imagename\",\"$file\",\"$o\",,\n"; } close(FILE); $attribs->{'litefile'} = "$rootimgdir/litefile.csv"; } # make manifest.xml file. So easy! This is why we like XML. I didn't like # the idea at first though. my $xml = new XML::Simple(RootName =>'xcatimage'); open(FILE,">$tpath/manifest.xml") or die "Could not open $tpath/manifest.xml"; print FILE $xml->XMLout($attribs, noattr => 1, xmldecl => ''); #print $xml->XMLout($attribs, noattr => 1, xmldecl => ''); close(FILE); # these are the only files we copy in. (unless you have extras) for my $a ("kernel", "template", "ramdisk", "rootimg", "pkglist", "synclists", "otherpkglist", "postinstall", "exlist"){ my $fn=$attribs->{$a}; if($fn) { $callback->({data => ["$fn"]}); if (-r $fn) { system("cp $fn $tpath"); } else { $callback->({error=>["Couldn't find file $fn for $imagename. Skip."],errorcode=>[1]}); } } } # extra files get copied in the extra directory. if($attribs->{extra}){ mkdir("$tpath/extra"); chmod 0755,"$tpath/extra"; foreach(@{ $attribs->{extra} }){ my $fromf = $_->{src}; print " $fromf\n"; if(-d $fromf ){ print "fromf is a directory"; mkpath("$tpath/extra/$fromf"); `cp -a $fromf/* $tpath/extra/$fromf/`; }else{ `cp $fromf $tpath/extra`; } } } # now get right below all this stuff and tar it up. chdir($ttpath); $callback->( {data => ["Inside $ttpath."]}); unless($dest){ $dest = "$dir/$imagename.tgz"; } # if no absolute path specified put it in the cwd unless($dest =~ /^\//){ $dest = "$dir/$dest"; } $callback->( {data => ["Compressing $imagename bundle. Please be patient."]}); my $rc; if($::VERBOSE){ $callback->({data => ["tar czvf $dest . "]}); $rc = system("tar czvf $dest . "); }else{ $rc = system("tar czf $dest . "); } $callback->( {data => ["Done!"]}); if($rc) { $callback->({error=>["Failed to compress archive! (Maybe there was no space left?)"],errorcode=>[1]}); return; } chdir($dir); $rc = system("rm -rf $ttpath"); if ($rc) { $callback->({error=>["Failed to clean up temp space $ttpath"],errorcode=>[1]}); return; } } sub extract_bundle { my $request = shift; #print Dumper($request); my $callback = shift; my $nodes=shift; my $new_profile=shift; @ARGV = @{ $request->{arg} }; my $xml; my $data; my $datas; my $error = 0; my $bundle = shift @ARGV; # extract the image in temp path in cwd my $dir = $request->{cwd}; #getcwd; $dir = $dir->[0]; #print Dumper($dir); unless(-r $bundle){ $bundle = "$dir/$bundle"; } unless(-r $bundle){ $callback->({error => ["Can not find $bundle"],errorcode=>[1]}); return; } my $tpath = mkdtemp("$dir/imgimport.$$.XXXXXX"); $callback->({data=>["Unbundling image..."]}); my $rc; if($::VERBOSE){ $callback->({data=>["tar zxvf $bundle -C $tpath"]}); $rc = system("tar zxvf $bundle -C $tpath"); $rc = system("tar zxvf $bundle -C $tpath"); }else{ $rc = system("tar zxf $bundle -C $tpath"); } if($rc){ $callback->({error => ["Failed to extract bundle $bundle"],errorcode=>[1]}); } # get all the files in the tpath. These should be all the image names. my @files = < $tpath/* >; # go through each image directory. Find the XML and put it into the array. If there are any # errors then the whole thing is over and we error and leave. foreach my $imgdir (@files){ unless(-r "$imgdir/manifest.xml"){ $callback->({error=>["Failed to find manifest.xml file in image bundle"],errorcode=>[1]}); return; } $xml = new XML::Simple; # get the data! # put it in an eval string so that it $data = eval { $xml->XMLin("$imgdir/manifest.xml") }; if($@){ $callback->({error=>["invalid manifest.xml file inside the bundle. Please verify the XML"],errorcode=>[1]}); #my $foo = $@; #$foo =~ s/\n//; #$callback->({error=>[$foo],errorcode=>[1]}); #foreach($@){ # last; #} return; } #print Dumper($data); #push @{$datas}, $data; # now we need to import the files... unless(verify_manifest($data, $callback)){ $error++; next; } # check media first unless(check_media($data, $callback)){ $error++; next; } #change profile name if needed if ($new_profile) { $data=change_profile($data, $callback, $new_profile, $imgdir); } #import manifest.xml into xCAT database unless(set_config($data, $callback)){ $error++; next; } # now place files in appropriate directories. unless(make_files($data, $imgdir, $callback)){ $error++; next; } # put postscripts in the postsctipts table if ($nodes) { unless(set_postscripts($data, $callback, $nodes)){ $error++; next; } } my $osimage = $data->{imagename}; $callback->({data=>["Successfully imported the image."]}); } # remove temp file only if there were no problems. unless($error){ $rc = system("rm -rf $tpath"); if ($rc) { $callback->({error=>["Failed to clean up temp space $tpath"],errorcode=>[1]}); return; } } } sub change_profile { my $data = shift; my $callback = shift; my $new_profile=shift; my $srcdir=shift; $data->{profile}=$new_profile; my $installdir = xCAT::TableUtils->getInstallDir(); unless($installdir){ $installdir = '/install'; } if ($data->{rootimgdir}) { $data->{rootimgdir}="$installdir/netboot/" . $data->{osvers} . "/" . $data->{osarch} . "/$new_profile"; for my $a ("kernel", "ramdisk", "rootimg", "rootimgtree", "litefile") { if ($data->{$a}) { my $fn=basename($data->{$a}); $data->{$a}=$data->{rootimgdir} . "/$fn"; } } } my $prov="netboot"; if ($data->{provmethod} eq "install") { $prov = "install"; } my $platform; my $os=$data->{osvers}; if ($os) { if ($os =~ /rh.*/) { $platform = "rh"; } elsif ($os =~ /centos.*/) { $platform = "centos"; } elsif ($os =~ /fedora.*/) { $platform = "fedora"; } elsif ($os =~ /sles.*/) { $platform = "sles"; } elsif ($os =~ /SL.*/) { $platform = "SL"; } elsif ($os =~ /win/) {$platform = "windows"; } } for my $a ("template", "pkglist", "synclists", "otherpkglist", "postinstall", "exlist") { if ($data->{$a}) { my $fn=basename($data->{$a}); my $oldfn=$fn; $fn =~ s/^([^\.]+)\.(.*)$/$new_profile.$2/; move("$srcdir/$oldfn", "$srcdir/$fn"); $data->{$a}= "$installdir/custom/$prov/$platform/$fn"; } } #change the image name $data->{imagename}=$data->{osvers} . "-" . $data->{osarch} . "-" . $data->{provmethod} . "-$new_profile"; return $data; } # return 1 for true 0 for false. # need to make sure media is copied before importing image. sub check_media { my $data = shift; my $callback = shift; my $rc = 0; unless( $data->{'media'}) { $rc = 1; }elsif($data->{media} eq 'required'){ my $os = $data->{osvers}; my $arch = $data->{osarch}; my $installroot = xCAT::TableUtils->getInstallDir(); unless($installroot){ $installroot = '/install'; } unless(-d "$installroot/$os/$arch"){ $callback->({error=>["This image requires that you first copy media for $os-$arch"],errorcode=>[1]}); }else{ $rc = 1; } } return $rc; } sub set_postscripts { my $data = shift; my $callback = shift; my $nodes=shift; $callback->({data=>["Adding postscripts."]}); my @good_nodes=noderange($nodes); if (@good_nodes > 0) { my @missed = nodesmissed(); if (@missed > 0) { $callback->( {warning => ["The following nodes will be skipped because they are not in the nodelist table.\n " . join(',', @missed)],errorcode=>1} ); } } else { $callback->( {error => ["The nodes $nodes are not defined in xCAT DB."],errorcode=>1} ); return 0; } my $ptab = xCAT::Table->new('postscripts',-create => 1,-autocommit => 0); unless($ptab){ $callback->( {error => ["Unable to open table 'postscripts'"],errorcode=>1} ); return 0; } # get xcatdefaults settings my @a1=(); my @a2=(); (my $attrs1) = $ptab->getAttribs({node => "xcatdefaults"}, 'postscripts', 'postbootscripts'); if ($attrs1) { if ($attrs1->{postscripts}) { @a1=split(',', $attrs1->{postscripts}); } if ($attrs1->{postbootscripts}) { @a2=split(',', $attrs1->{postbootscripts}); } } #remove the script if it is already in xcatdefaults my @a3=(); my @a4=(); my $postscripts = $data->{postscripts}; my $postbootscripts = $data->{postbootscripts}; if ($postscripts) { @a3 = split(',', $postscripts); } if ($postbootscripts) { @a4 = split(',', $postbootscripts); } my @a30; my @a40; if (@a1>0 && @a3>0) { foreach my $tmp1 (@a3) { if (! grep /^$tmp1$/, @a1) { push(@a30, $tmp1); } } $postscripts=join(',', @a30); } if (@a2>0 && @a4>0) { foreach my $tmp2 (@a4) { if (! grep /^$tmp2$/, @a2) { push(@a40, $tmp2); } } $postbootscripts=join(',', @a40); } #now save to the db my %keyhash; if ($postscripts || $postbootscripts) { $keyhash{postscripts} = $postscripts; $keyhash{postbootscripts} = $postbootscripts; $ptab->setNodesAttribs(\@good_nodes, \%keyhash ); $ptab->commit; } return 1; } sub set_config { my $data = shift; my $callback = shift; my $ostab = xCAT::Table->new('osimage',-create => 1,-autocommit => 0); my $linuxtab = xCAT::Table->new('linuximage',-create => 1,-autocommit => 0); my %keyhash; my $osimage = $data->{imagename}; unless($ostab){ $callback->( {error => ["Unable to open table 'osimage'"],errorcode=>1} ); return 0; } unless($linuxtab){ $callback->( {error => ["Unable to open table 'linuximage'"],errorcode=>1} ); return 0; } $callback->({data=>["Adding $osimage"]}) if $::VERBOSE; # now we make a quick hash of what we want to put into this $keyhash{provmethod} = $data->{provmethod}; $keyhash{profile} = $data->{profile}; $keyhash{osvers} = $data->{osvers}; $keyhash{osarch} = $data->{osarch}; if ($data->{imagetype}) { $keyhash{imagetype} = $data->{imagetype}; }; if ($data->{osname}) { $keyhash{osname} = $data->{osname}; }; if ($data->{osdistro}) { $keyhash{osdistro} = $data->{osdistro}; }; if ($data->{synclists}) { $keyhash{synclists} = $data->{synclists}; }; $ostab->setAttribs({imagename => $osimage }, \%keyhash ); $ostab->commit; %keyhash=(); if ($data->{template}) { $keyhash{template} = $data->{template}; }; if ($data->{pkglist}) { $keyhash{pkglist} = $data->{pkglist}; }; if ($data->{pkgdir}) { $keyhash{pkgdir} = $data->{pkgdir}; }; if ($data->{otherpkglist}) { $keyhash{otherpkglist} = $data->{otherpkglist}; }; if ($data->{otherpkgdir}) { $keyhash{otherpkgdir} = $data->{otherpkgdir}; }; if ($data->{exlist}) { $keyhash{exlist} = $data->{exlist}; }; if ($data->{postinstall}) { $keyhash{postinstall} = $data->{postinstall}; }; if ($data->{rootimgdir}) { $keyhash{rootimgdir} = $data->{rootimgdir}; }; if ($data->{netdrivers}) { $keyhash{netdrivers} = $data->{netdrivers}; }; if ($data->{kernelver}) { $keyhash{kernelver} = $data->{kernelver}; }; if ($data->{permission}) { $keyhash{permission} = $data->{permission}; }; if ($data->{nodebootif}) { $keyhash{nodebootif} = $data->{nodebootif}; }; if ($data->{otherifce}) { $keyhash{otherifce} = $data->{otherifce}; }; $linuxtab->setAttribs({imagename => $osimage }, \%keyhash ); $linuxtab->commit; return 1; } sub verify_manifest { my $data = shift; my $callback = shift; my $errors = 0; # first make sure that the stuff is defined! unless($data->{imagename}){ $callback->({error=>["The 'imagename' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{provmethod}){ $callback->({error=>["The 'provmethod' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{profile}){ $callback->({error=>["The 'profile' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{osvers}){ $callback->({error=>["The 'osvers' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{osarch}){ $callback->({error=>["The 'osarch' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{provmethod} =~ /install|netboot|statelite/){ $callback->({error=>["Importing images with 'provemethod' " . $data->{provmethod} . " is not supported. Hint: install, netboot, or statelite"],errorcode=>[1]}); $errors++; } # if the install method is used, then we need to have certain files in place. if($data->{provmethod} =~ /install/){ # we need to get the template for this one! unless($data->{template}){ $callback->({error=>["The 'osarch' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } #$attrs->{media} = "required"; (need to do something to verify media! }elsif($data->{osvers} =~ /esx/){ $callback->({info => ['this is an esx image']}); # do nothing for ESX 1; }elsif($data->{provmethod} =~ /netboot/){ unless($data->{ramdisk}){ $callback->({error=>["The 'ramdisk' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{kernel}){ $callback->({error=>["The 'kernel' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{rootimg}){ $callback->({error=>["The 'rootimg' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } }elsif($data->{provmethod} =~ /statelite/){ unless($data->{kernel}){ $callback->({error=>["The 'kernel' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{ramdisk}){ $callback->({error=>["The 'ramdisk' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{'rootimgtree'}){ $callback->({error=>["The 'rootimgtree' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } } if($errors){ # we had problems, error and exit. return 0; } # returning 1 means everything went good! return 1; } sub make_files { my $data = shift; my $imgdir = shift; my $callback = shift; my $os = $data->{osvers}; my $arch = $data->{osarch}; my $profile = $data->{profile}; my $installroot = xCAT::TableUtils->getInstallDir(); unless($installroot){ $installroot = '/install'; } # you'll get a hash like this for install: #$VAR1 = { # 'provmethod' => 'install', # 'profile' => 'all', # 'template' => '/opt/xcat/share/xcat/install/centos/all.tmpl', # 'pkglist' => '/opt/xcat/share/xcat/install/centos/all.pkglist', # 'otherpkglist' => '/opt/xcat/share/xcat/install/centos/all.othetpkgs.pkglist', # 'synclists' => '/opt/xcat/share/xcat/install/centos/all.othetpkgs.synclist', # 'imagename' => 'Default_Stateful', # 'osarch' => 'x86_64', # 'media' => 'required', # 'osvers' => 'centos5.4' # }; # data will look something like this for netboot: #$VAR1 = { # 'provmethod' => 'netboot', # 'profile' => 'compute', # 'ramdisk' => '/install/netboot/centos5.4/x86_64/compute/initrd-stateless.gz', # 'kernel' => '/install/netboot/centos5.4/x86_64/compute/kernel', # 'imagename' => 'Default_Stateless_1265981465', # 'osarch' => 'x86_64', # 'pkglist' => '/opt/xcat/share/xcat/install/centos/compute.pkglist', # 'otherpkglist' => '/opt/xcat/share/xcat/install/centos/compute.othetpkgs.pkglist', # 'synclists' => '/opt/xcat/share/xcat/install/centos/compute.othetpkgs.synclist', # 'exlist' => '/opt/xcat/share/xcat/install/centos/compute.exlist', # 'postinstall' => '/opt/xcat/share/xcat/install/centos/compute.postinstall', # 'extra' => [ # { # 'dest' => '/install/custom/netboot/centos', # 'src' => '/opt/xcat/share/xcat/netboot/centos/compute.centos5.4.pkglist' # }, # { # 'dest' => '/install/custom/netboot/centos', # 'src' => '/opt/xcat/share/xcat/netboot/centos/compute.exlist' # } # ], # 'osvers' => 'centos5.4', # 'rootimg' => '/install/netboot/centos5.4/x86_64/compute/rootimg.gz' # }; # data will look something like this for statelite: #$VAR1 = { # 'provmethod' => 'statelite', # 'profile' => 'compute', # 'ramdisk' => '/install/netboot/centos5.4/x86_64/compute/initrd-statelite.gz', # 'kernel' => '/install/netboot/centos5.4/x86_64/compute/kernel', # 'imagename' => 'Default_Stateless_1265981465', # 'osarch' => 'x86_64', # 'pkglist' => '/opt/xcat/share/xcat/install/centos/compute.pkglist', # 'otherpkglist' => '/opt/xcat/share/xcat/install/centos/compute.othetpkgs.pkglist', # 'synclists' => '/opt/xcat/share/xcat/install/centos/compute.othetpkgs.synclist', # 'exlist' => '/opt/xcat/share/xcat/install/centos/compute.exlist', # 'postinstall' => '/opt/xcat/share/xcat/install/centos/compute.postinstall', # 'extra' => [ # { # 'dest' => '/install/custom/netboot/centos', # 'src' => '/opt/xcat/share/xcat/netboot/centos/compute.centos5.4.pkglist' # }, # { # 'dest' => '/install/custom/netboot/centos', # 'src' => '/opt/xcat/share/xcat/netboot/centos/compute.exlist' # } # ], # 'osvers' => 'centos5.4', # 'rootimgtree' => '/install/netboot/centos5.4/x86_64/compute/rootimg/rootimgtree.gz' # }; for my $a ("kernel", "template", "ramdisk", "rootimg", "rootimgtree", "litefile", "pkglist", "synclists", "otherpkglist", "postinstall", "exlist") { my $fn=$data->{$a}; if($fn) { $callback->({data => ["$fn"]}); my $basename=basename($fn); my $dirname=dirname($fn); if (! -r $dirname) { mkpath("$dirname", { verbose => 1, mode => 0755 }); } if (-r $fn) { $callback->( {data => [" Moving old $fn to $fn.ORIG."]}); move("$fn", "$fn.ORIG"); } move("$imgdir/$basename",$fn); } } #unpack the rootimgtree.gz for statelite my $fn=$data->{'rootimgtree'}; if($fn) { if (-r $fn) { my $basename=basename($fn); my $dirname=dirname($fn); #print "dirname=$dirname, basename=$basename\n"; $callback->({data => ["Extracting rootimgtree.gz. It will take a while."]}); system("mkdir -p $dirname; cd $dirname; zcat $basename |cpio -idum; rm $basename"); } } if($data->{extra}){ # have to copy extras print "copying extras\n" if $::VERBOSE; #if its just a hash then there is only one entry. if (ref($data->{extra}) eq 'HASH'){ my $ex = $data->{extra}; #my $f = basename($ex->{src}); my $ff = $ex->{src}; my $dest = $ex->{dest}; unless(moveExtra($callback, $ff, $dest, $imgdir)){ return 0; } # if its an array go through each item. }else{ foreach(@{ $data->{extra} }) { #my $f = basename($_->{src}); my $ff = $_->{src}; my $dest = $_->{dest}; unless(moveExtra($callback, $ff, $dest, $imgdir)){ return 0; } } } } #litefile table for statelite if ($data->{provmethod} eq 'statelite') { $callback->( {data => ["Updating the litefile table."]}); my $fn=$data->{litefile}; if (!$fn) { $callback->({error=>["Could not find liefile.csv."],errorcode=>[1]}); return 1; } elsif (! -r $fn) { $callback->({error=>["Could not find $fn."],errorcode=>[1]}); return 1; } my $lftab= xCAT::Table->new("litefile" ,-create=>1); if (!$lftab) { $callback->({error=>["Could not open the litefile table."],errorcode=>[1]}); return 0; } open(FILE,"$fn") or die "Could not open $fn."; foreach my $line () { chomp($line); print "$line\n"; my @tmp=split('"', $line); my %keyhash; my %updates; $keyhash{image}=$data->{imagename}; $keyhash{file}=$tmp[3]; $updates{options}=$tmp[5]; $lftab->setAttribs(\%keyhash, \%updates ); } close(FILE); $lftab->commit; $callback->( {data => ["The litetree and statelite talbes are untouched. You can update them if needed."]}); } # return 1 meant everything was successful! return 1; } sub moveExtra { my $callback = shift; my $ff = shift; my $dest = shift; my $imgdir = shift; my $f = basename($ff); if(-d "$imgdir/extra/$ff"){ #print "This is a directory\n"; # this extra file is a directory, so we are moving the directory over. $callback->( {data => ["$dest"]}); unless(-d $dest){ unless(mkpath($dest)){ $callback->( {error=>["Failed to create $dest"], errorcode => 1}); return 0; } } # this could cause some problems. This is one of the reasons we may not want to # allow copying of directories. `cp -a -f $imgdir/extra/$ff/* $dest`; if($?){ $callback->( {error=>["Failed to cp -a $imgdir/extra/$ff/* to $dest"], errorcode => 1}); return 0; } }else{ #print "This is a file\n"; # this extra file is a file and we can just copy to the destination. $callback->( {data => ["$dest/$f"]}) ; if(-r "$dest/$f"){ $callback->( {data => [" Moving old $dest/$f to $dest/$f.ORIG."]}); move("$dest/$f", "$dest/$f.ORIG"); } `cp $imgdir/extra/$f $dest`; if ($?) { $callback->( {error=>["Failed to copy $imgdir/extra/$f to $dest"], errorcode => 1}); return 0; } } return 1; }