Commits

stacklet  committed a422649

Initial implementation of Stacklet Bundler Xul App

  • Participants
  • Parent commits 978c501
  • Tags alpha_release_20100308

Comments (0)

Files changed (6)

File src/stacklet/stackbundler/application.ini

+[App]
+Vendor=Stacklet
+Name=StackBundler
+Version=1.0
+BuildID=20100307
+Copyright=Copyright (c) 2010 Stacklet
+ID=stackbundler@stacklet.com
+
+[Gecko]
+MinVersion=1.8
+

File src/stacklet/stackbundler/chrome/chrome.manifest

+content stackbundler file:content/
+

File src/stacklet/stackbundler/chrome/content/main.js

+function initApp() {
+
+  try {
+    window.stkprocess = Components.classes["@mozilla.org/process/util;1"].createInstance(Components.interfaces.nsIProcess);
+    
+    var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
+    prefs = prefs.getBranch("stacklet.");
+
+    document.getElementById("defaultFormat").selectedIndex = prefs.getIntPref("defaultFormat");
+    document.getElementById("defaultSize").value = prefs.getIntPref("defaultSize");
+    document.getElementById("defaultFsType").selectedIndex = prefs.getIntPref("defaultFsType");
+    document.getElementById("defaultRepo").value = prefs.getCharPref("defaultRepo");
+    document.getElementById("defaultOutputDirectory").value = prefs.getCharPref("defaultOutputDirectory");
+    document.getElementById("stackfactoryPath").value = prefs.getCharPref("stackfactoryPath");
+    document.getElementById("sudoPath").value = prefs.getCharPref("sudoPath");
+    document.getElementById("stackletHome").value = prefs.getCharPref("stackletHome");
+    document.getElementById("ami_home").value = prefs.getCharPref("ami_home");
+    document.getElementById("ami_arch").selectedIndex = prefs.getIntPref("ami_arch");
+    document.getElementById("ami_user").value = prefs.getCharPref("ami_user");
+    document.getElementById("ami_privateKey").value = prefs.getCharPref("ami_privateKey");
+    document.getElementById("ami_cert").value = prefs.getCharPref("ami_cert");
+    document.getElementById("ami_ec2cert").value = prefs.getCharPref("ami_ec2cert");
+    document.getElementById("euca_arch").selectedIndex = prefs.getIntPref("euca_arch");
+    document.getElementById("euca_user").value = prefs.getCharPref("euca_user");
+    document.getElementById("euca_privateKey").value = prefs.getCharPref("euca_privateKey");
+    document.getElementById("euca_cert").value = prefs.getCharPref("euca_cert");
+    document.getElementById("euca_ec2cert").value = prefs.getCharPref("euca_ec2cert");
+    document.getElementById("tablist").selectedIndex="1"
+  } catch (err) {
+    alert(err.description)
+  }
+}
+
+function closeEvent() {
+
+  try {
+    storePrefs();
+    if (window.stkprocess.isRunning) {
+      alert('A bundler process is running!');
+      return false;
+    }    
+  } catch (err) {
+    alert(err.description)
+  }
+}
+
+function closeApp() {
+
+  try {
+    storePrefs();
+    if (window.stkprocess.isRunning) {
+      return 'A bundler process is running!';
+    }
+    
+  } catch (err) {
+    alert(err.description)
+  }
+}
+
+function storePrefs() {
+
+  try {  
+    var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
+    prefs = prefs.getBranch("stacklet.");
+
+    prefs.setIntPref("defaultFormat", document.getElementById("defaultFormat").selectedIndex);
+    prefs.setIntPref("defaultSize", document.getElementById("defaultSize").value);
+    prefs.setCharPref("defaultRepo", document.getElementById("defaultRepo").value);
+    prefs.setIntPref("defaultFsType", document.getElementById("defaultFsType").selectedIndex);
+    prefs.setCharPref("defaultOutputDirectory", document.getElementById("defaultOutputDirectory").value);
+    prefs.setCharPref("stackfactoryPath", document.getElementById("stackfactoryPath").value);
+    prefs.setCharPref("sudoPath", document.getElementById("sudoPath").value);
+    prefs.setCharPref("stackletHome", document.getElementById("stackletHome").value);
+    prefs.setCharPref("ami_home", document.getElementById("ami_home").value);
+    prefs.setIntPref("ami_arch", document.getElementById("ami_arch").selectedIndex);
+    prefs.setCharPref("ami_user", document.getElementById("ami_user").value);
+    prefs.setCharPref("ami_privateKey", document.getElementById("ami_privateKey").value);
+    prefs.setCharPref("ami_cert", document.getElementById("ami_cert").value);
+    prefs.setCharPref("ami_ec2cert", document.getElementById("ami_ec2cert").value);
+    prefs.setIntPref("euca_arch", document.getElementById("euca_arch").selectedIndex);
+    prefs.setCharPref("euca_user", document.getElementById("euca_user").value);
+    prefs.setCharPref("euca_privateKey", document.getElementById("euca_privateKey").value);
+    prefs.setCharPref("euca_cert", document.getElementById("euca_cert").value);
+    prefs.setCharPref("euca_ec2cert", document.getElementById("euca_ec2cert").value);  
+  } catch (err) {
+    alert(err.description)
+  }
+}
+
+function resetToDefaults() {
+
+  document.getElementById("destFormat").selectedIndex = document.getElementById("defaultFormat").selectedIndex;
+  document.getElementById("image_size").value = document.getElementById("defaultSize").value;  
+  document.getElementById("dest_directory").value = document.getElementById("defaultOutputDirectory").value;
+  
+}
+
+function selectTab() {
+
+  try {
+    if (document.getElementById("tabs").selectedItem.id == 'bundlerTab' && document.getElementById("tabs").flag != 'alreadyreset') {
+      resetToDefaults();
+      document.getElementById("tabs").flag = 'alreadyreset';
+    }
+  } catch (err) {
+    alert(err.description)
+  }
+}
+
+/*
+   Kickoff the bundler process.  Only one
+   can run at a time.
+*/       
+function bundle() {
+
+  try {
+    if (window.stkprocess.isRunning) {
+      alert('A bundler process is already running');
+      return;
+    }
+
+    if (!validatePaths()) {
+      return;
+    }
+
+    var args=new Array();
+    var format = document.getElementById('destFormat').value;
+    if (format == 'kvm') {
+      fileFormat = document.getElementById('kvmFormat').value;
+      args = getBundleArgsA(fileFormat);
+    } else if (format == 'vmware') {
+      fileFormat = document.getElementById('vmwareFormat').value;
+      args = getBundleArgsA(fileFormat);
+    } else if (format == 'xen3') {
+      fileFormat = document.getElementById('xenFormat').value;
+      args = getBundleArgsXen(fileFormat);
+    } else if (format == 'euca') {
+      if (!validateAmiEuca('Eucalyptus', 'euca_user', 'euca_privateKey', 'euca_cert', 'euca_ec2cert')) {
+        return;
+      }
+      args = getBundleArgsAmiEuca(format);
+    } else if (format == 'ami') {
+      if (!validateAmiEuca('Amazon AMI', 'ami_user', 'ami_privateKey', 'ami_cert', 'ami_ec2cert')) {
+        return;
+      }
+      args = getBundleArgsAmiEuca(format);
+    }    
+    
+    if (!validateField('source_root', 'Source Root is Invalid', 'validateDirectory', true, true, 'Please choose an existing directory that contains files to be bundled')) {
+      return;
+    }
+    
+    if (!validateField('dest_directory', 'Output Folder is Invalid', 'validateDirectory', true, true, 'Please choose an existing, writable directory')) {
+      return;
+    }
+
+    if (!validateField('dest_imgFile', 'Image File is Invalid', 'validateRegex', true, true, '', /^[a-z,A-Z,0-9,.,_,-]+$/)) {
+      return;
+    }
+    
+    runProc(document.getElementById("stackfactoryPath").value, args, true);
+    document.getElementById('progress').hidden = false;
+    setTimeout(function(){reloadOutput();}, 2000);
+    window.firstUpdateStatus = true;
+    setTimeout(function(){updateStatus();}, 2000);
+  } catch (err) {
+    alert(err);
+  }
+}
+
+/*
+   Returns an array of command line arguments for 
+   stackfactory when we are bundling for kvm or vmware.
+   The exact file format is passed in as an argument
+*/     
+function getBundleArgsA(fileFormat) {
+
+  var args=new Array(); 
+  var destDir = document.getElementById('dest_directory').value;
+  var destimg = destDir + '/' + document.getElementById('dest_imgFile').value;
+  var imgtmp = destimg + '.tmp'
+  var imgFile = '_image.imgFile_=' + imgtmp;
+  var imgSize = '_image.size_=' + document.getElementById('image_size').value;
+  var tmpImgFile = '_temp.imgFile_=' + imgtmp;
+  var sourceRoot = '_source.root_=' + document.getElementById('source_root').value;
+  var destImgFile = '_dest.imgFile_=' + destimg;
+  var destFormat = '_dest.format_=' + fileFormat;
+  var cleanup = '_cleanup_='+ 'false';
+  var specA = document.getElementById("stackletHome").value + '/specs/storage/vbd_simple_disk.xml';
+  var specB = document.getElementById("stackletHome").value + '/specs/storage/bundle_source.xml';
+    
+  args.push(specA);
+  args.push(specB);
+  args.push(imgFile);
+  args.push(imgSize);
+  args.push(tmpImgFile);
+  args.push(sourceRoot);
+  args.push(destImgFile);
+  args.push(destFormat);
+  args.push(cleanup);
+            
+  return args;  
+}
+
+/*
+   Returns an array of command line arguments for 
+   stackfactory when we are bundling for xen.
+   The exact file format is passed in as an argument
+*/     
+function getBundleArgsXen(fileFormat) {
+
+  var args=new Array(); 
+  var destDir = document.getElementById('dest_directory').value;
+  var destimg = destDir + '/' + document.getElementById('dest_imgFile').value;
+  var imgFile = '_image.imgFile_=' + destimg;
+  var imgSize = '_image.size_=' + document.getElementById('image_size').value;
+  var tmpImgFile = '_temp.imgFile_=' + destimg;
+  var sourceRoot = '_source.root_=' + document.getElementById('source_root').value;
+  var destFormat = '_dest.format_=' + fileFormat;
+  var cleanup = '_cleanup_='+ 'false';
+  var specA = document.getElementById("stackletHome").value + '/specs/storage/vbd_partition.xml';
+  var specB = document.getElementById("stackletHome").value + '/specs/storage/bundle_source.xml';
+
+  args.push(specA);
+  args.push(specB);
+  args.push(imgFile);
+  args.push(imgSize);    
+  args.push(tmpImgFile);
+  args.push(sourceRoot);
+  args.push(destFormat);
+  args.push(cleanup);
+  
+  return args;
+    
+}
+
+/*
+   Returns an array of command line arguments for 
+   stackfactory when we are bundling for ami or euca.
+   The exact file format is passed in as an argument
+*/     
+
+function getBundleArgsAmiEuca(fileFormat) {
+
+  var args=new Array(); 
+  var destDir = document.getElementById('dest_directory').value;
+  var destimg = destDir + '/' + document.getElementById('dest_imgFile').value;
+  var imgFile = '_image.imgFile_=' + destimg;
+  var imgSize = '_image.size_=' + document.getElementById('image_size').value;
+  var tmpImgFile = '_temp.imgFile_=' + destimg;
+  var sourceRoot = '_source.root_=' + document.getElementById('source_root').value;
+  var destFormat = '_dest.format_=' + fileFormat;
+  var destDir = '_dest.directory_=' + document.getElementById('dest_directory').value;
+  var ami_home = '';
+  var ami_arch = '_ami.arch_=';
+  var ami_user = '_ami.user_=';
+  var ami_privateKey = '_ami.privateKey_=';
+  var ami_cert = '_ami.cert_=';
+  var ami_ec2cert = '_ami.ec2cert_=';
+  
+  if (fileFormat == 'euca') {
+    ami_arch += document.getElementById('euca_arch').value;
+    ami_user += document.getElementById('euca_user').value;
+    ami_privateKey += document.getElementById('euca_privateKey').value;
+    ami_cert += document.getElementById('euca_cert').value;
+    ami_ec2cert += document.getElementById('euca_ec2cert').value;
+  } else if (fileFormat == 'ami') {
+    ami_home = '_ami.ec2Home_=' + document.getElementById('ami_home').value;
+    ami_arch += document.getElementById('ami_arch').value;
+    ami_user += document.getElementById('ami_user').value;
+    ami_privateKey += document.getElementById('ami_privateKey').value;
+    ami_cert += document.getElementById('ami_cert').value;
+    ami_ec2cert += document.getElementById('ami_ec2cert').value;
+  } 
+  
+  var cleanup = '_cleanup_='+ 'false';
+  var specA = document.getElementById("stackletHome").value + '/specs/storage/vbd_partition.xml';
+  var specB = document.getElementById("stackletHome").value + '/specs/storage/bundle_ami.xml';
+
+  args.push(specA);
+  args.push(specB);
+  args.push(imgFile);
+  args.push(imgSize);    
+  args.push(tmpImgFile);
+  args.push(sourceRoot);
+  args.push(destFormat);
+  args.push(destDir);
+  if (ami_home != '') {
+    args.push(ami_home);
+  }  
+  args.push(ami_arch);
+  args.push(ami_user);
+  args.push(ami_privateKey);
+  args.push(ami_cert);
+  args.push(ami_ec2cert);
+  args.push(cleanup);
+  return args;
+    
+}
+
+/*
+   Run a process asynchronously using nsIProcess.  Process object
+   is stored in window.stkprocess
+     -appPath is the full path to the executable
+     -args is the arguments to the process
+     -useSudo (boolean) when true causes the executable to run as root using sudo 
+   
+*/  
+function runProc(appPath, args, useSudo) {
+  
+  try {
+    var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);  
+      
+    if (useSudo) {
+      file.initWithPath(document.getElementById("sudoPath").value);
+      args.unshift(appPath);
+      args.unshift('-n');    //tell sudo to not prompt for password, in other words the user must be able to run sudo w/out a password
+    } else {
+      file.initWithPath(appPath);
+    }
+      
+    window.stkprocess = Components.classes["@mozilla.org/process/util;1"].createInstance(Components.interfaces.nsIProcess);
+    window.stkprocess.init(file);
+    window.stkprocess.run(false, args, args.length);
+    
+    
+  } catch (err) {
+    alert(err);
+  }
+}
+
+
+/* This runs periodically as the underlying bundler is running.
+   Parse stacklet.out and update the status text.
+   Also turn off the progress bar when process is done.
+*/
+function updateStatus() {
+  try {
+
+    if (window.firstUpdateStatus && !window.stkprocess.isRunning && window.stkprocess.exitValue != 0) {
+      alert('There was a problem launching stackfactory.  Please check the paths section in the Prefs:General tab.  Also make sure sudo is configured');
+      document.getElementById('progress').hidden = true;
+      return;
+    } 
+    window.firstUpdateStatus = false;
+
+    var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
+    file.initWithPath('/var/log/stacklet.log');
+  
+    // open an input stream from file  
+    var istream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);  
+    istream.init(file, 0x01, 0444, 0);  
+    istream.QueryInterface(Components.interfaces.nsILineInputStream);  
+   
+    var line = {}, lines = [], hasmore;  
+    do {  
+      hasmore = istream.readLine(line);
+      if (line.value.indexOf('STATUS:') != -1) {
+        lines.unshift(line.value.substring(line.value.indexOf('STATUS:') + 7) + '\n');
+      }      
+    } while(hasmore);  
+    var doc = document.getElementById('status_box');
+    doc.value = lines;
+    doc.value = doc.value.replace(/,/g, '');
+    istream.close();  
+    
+    if (window.stkprocess.isRunning) {
+      setTimeout(function(){updateStatus();}, 2000);
+    } else {
+      document.getElementById('progress').hidden = true;
+    }
+  } catch (err) {
+    alert(err);
+  }
+}
+
+/*
+   Reloads the output tab's browser window 
+   and forces the scrollbar to the end.
+*/    
+function reloadOutput() {
+  try {  
+    var outFile = '/var/log/stacklet.log';
+    var doc = document.getElementById('output-frame');
+    doc.loadURI(outFile);
+    
+    var caption = document.getElementById('output-caption');
+    caption.label = outFile;
+    
+    setTimeout(function(){scrollBottom();}, 2000);
+  } catch (err) {
+    //do nothing
+  }  
+}  
+
+/*
+   Forces the scrollbar to the end so we can see the latest messages.
+*/    
+function scrollBottom() {
+  try {  
+    var doc = document.getElementById('output-frame');
+    doc.contentWindow.scrollBy(0, 10000000);
+  } catch (err) {
+    ;
+    //do nothing
+  }  
+}  
+
+/*
+    Allow the user to pick a file or directory using a chooser;
+    set the selected path into a textbox.
+*/
+function filePicker(textboxId, defaultDirectory, mode, title) {
+    
+  try {
+    var nsIFilePicker = Components.interfaces.nsIFilePicker;
+    var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+    
+    if (mode == 0) {
+      mode = nsIFilePicker.modeGetFolder;
+    } else {
+      mode = nsIFilePicker.modeOpen
+    }
+    
+    fp.init(window, title, mode);
+    
+    if (defaultDirectory != null) {
+      var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);  
+      file.initWithPath(defaultDirectory);
+      fp.displayDirectory = file;
+    }
+    
+    var res = fp.show();
+    if (res == nsIFilePicker.returnOK) {
+      var thefile = fp.file;
+      var textbox = document.getElementById(textboxId);
+      textbox.value = thefile.path;
+    }
+  } catch (err) {
+    alert(err.description)
+  }
+}
+
+function validatePaths() {
+
+    if (!validateField('stackfactoryPath', 'Stackfactory Path is Invalid', 'validateFile', true, true, 'Please correct this field in the Prefs:General tab')) {
+      return false;
+    }
+
+    if (!validateField('sudoPath', 'Sudo Path is Invalid', 'validateFile', true, true, 'Please correct this field in the Prefs:General tab')) {
+      return false;
+    }
+
+    if (!validateField('stackletHome', 'Stacklet Home is Invalid', 'validateDirectory', true, true, 'Please correct this field in the Prefs:General tab')) {
+      return false;
+    }
+    
+    return true;
+}
+
+function validateAmiEuca(tabName, user, privateKey, userCert, cloudCert) {
+
+    if (!validateField(user, 'User is Invalid', 'validateRegex', true, true, 'Please correct this field in the Prefs:' + tabName + ' tab', /^[0-9]+$/)) {
+      return false;
+    }
+
+    if (!validateField(privateKey, 'Private Key is Invalid', 'validateFile', true, true, 'Please correct this field in the Prefs:' + tabName + ' tab')) {
+      return false;
+    }
+
+    if (!validateField(userCert, 'Certificate is Invalid', 'validateFile', true, true, 'Please correct this field in the Prefs:' + tabName + ' tab')) {
+      return false;
+    }
+
+    if (!validateField(cloudCert, 'EC2 Certificate is Invalid', 'validateFile', true, true, 'Please correct this field in the Prefs:' + tabName + ' tab')) {
+      return false;
+    }
+    
+    return true;
+}
+

File src/stacklet/stackbundler/chrome/content/main.xul

+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window id="main" title="Stacklet Bundler" width="700" height="520" onload="initApp();" onclose="return closeEvent();" onbeforeunload="return closeApp();"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/x-javascript" src="main.js"/>
+  <script type="application/x-javascript" src="validate.js"/>
+  
+  <tabbox id="tablist" orient='vertical' flex='1'>
+    <tabs id='tabs' orient='horizontal' onselect='selectTab();'>
+      <tab id='prefsTab' accesskey="P">
+        Prefs
+      </tab>
+      <tab id='bundlerTab' accesskey="B">
+        Bundler
+      </tab>
+      <tab id='outputTab' accesskey="O">
+        Output
+      </tab>
+    </tabs>
+    <tabpanels flex='1'>
+      <tabpanel id='prefs'>
+        <tabbox id="tabprefs">
+          <tabs>
+            <tab accesskey="G">
+              General
+            </tab>
+            <tab accesskey="X">
+              Xen
+            </tab>
+            <tab accesskey="K">
+              KVM/QEMU
+            </tab>
+            <tab accesskey="V">
+              VMWare
+            </tab>
+            <tab accesskey="A">
+              Amazon AMI
+            </tab>
+            <tab accesskey="E">
+              Eucalyptus
+            </tab>
+          </tabs>
+          <tabpanels>
+            <tabpanel id='general'>
+              <groupbox orient="vertical">
+                <groupbox orient="vertical" style='border:1px inset white;'>
+                  <groupbox orient="horizontal" align='center'>
+                    <label value='Output Settings' style="font-weight : bold;"/>
+                  </groupbox>  
+                  <groupbox orient="horizontal" align='center'>
+                    <label control="defaultOutputDirectory" value="Output Folder"  width="150" accesskey="u" tooltiptext="Images will be put here unless overridden in the Bundler tab"/>
+                    <textbox id="defaultOutputDirectory" value="/tmp/stacklet/" width="350"/>
+                    <button id="open-button" label="..." oncommand="filePicker('defaultOutputDirectory', null, 0, 'Please Choose a Directory');"/>
+                  </groupbox>      
+                  <groupbox orient="horizontal" align='center'>
+                    <label control="defaultFormat" value="Default Format"  width="150" accesskey="F" tooltiptext="Images will be built in this format unless overridden in the Bundler tab"/>
+                    <menulist id="defaultFormat">
+                      <menupopup>
+                        <menuitem label="Xen 3" value='xen3'/>
+                        <menuitem label="KVM/QEMU" value='kvm'/>
+                        <menuitem label="VMWare" value='vmware'/>
+                        <menuitem label="Amazon AMI" value='ami'/>
+                        <menuitem label="Eucalyptus" value='euca'/>
+                      </menupopup>
+                    </menulist>
+                  </groupbox>
+                  <groupbox orient="horizontal" align='center'>
+                    <label control="defaultSize" value="Default Size (MB)" width="150"  accesskey="S" tooltiptext="Images will be this size unless overridden in the Bundler tab"/>
+                    <textbox id="defaultSize" type="number" min="1000" max="100000"/>
+                  </groupbox>
+                  <groupbox orient="horizontal" align='center'>
+                    <label control="defaultFsType" value="Default FS Type"  width="150"  accesskey="T"/>
+                    <menulist id="defaultFsType">
+                      <menupopup>
+                        <menuitem label="ext3" value='ext3'/>
+                      </menupopup>
+                    </menulist>
+                  </groupbox>
+                  <groupbox orient="horizontal" align='center'>
+                    <label control="defaultRepo" value="Default Repo" width="150" accesskey="R" tooltiptext="Directory containing 'prebuilt' filesystems that can be bundled into images"/>
+                    <textbox id="defaultRepo" value="" width="350"/>
+                    <button id="open-button" label="..." oncommand="filePicker('defaultRepo', null, 0, 'Please Choose a Directory');"/>                    
+                  </groupbox>
+                </groupbox>
+                <groupbox orient="vertical" style='border:1px inset white;'>
+                  <groupbox orient="horizontal" align='center'>
+                    <label value='Paths' style="font-weight : bold;"/>
+                  </groupbox>  
+                  <groupbox orient="horizontal" align='center'>
+                    <label control="stackfactoryPath" value="Stackfactory Path" width="150" tooltiptext="The script that does the actual build process"/>
+                    <textbox id="stackfactoryPath" value="" width="350"/>
+                    <button id="open-button" label="..." oncommand="filePicker('stackfactoryPath', null, 1, 'Please Choose a File');"/>
+                  </groupbox>
+                  <groupbox orient="horizontal" align='center'>
+                    <label control="sudoPath" value="Sudo Path" width="150" tooltiptext="stackfactory needs to run as root (using sudo if necessary).  You may need to update /etc/sudoers to enable this for users."/>
+                    <textbox id="sudoPath" value="" width="350"/>
+                    <button id="open-button" label="..." oncommand="filePicker('sudoPath', null, 1, 'Please Choose a File');"/>
+                  </groupbox>
+                  <groupbox orient="horizontal" align='center'>
+                    <label control="stackletHome" value="Stacklet Home" width="150" tooltiptext="Directory containing stacklet specs"/>
+                    <textbox id="stackletHome" value="" width="350"/>
+                    <button id="open-button" label="..." oncommand="filePicker('stackletHome', null, 0, 'Please Choose a Directory');"/>
+                  </groupbox>
+                </groupbox>
+              </groupbox>
+            </tabpanel>
+            <tabpanel id='xen'>
+              <groupbox orient="vertical">
+                <caption label="Xen Settings"/>
+                <groupbox orient="horizontal" align='center'>
+                  <label control="xenFormat" value="Format"  width="100" accesskey="F"/>
+                  <menulist id="xenFormat">
+                    <menupopup>
+                      <menuitem label="Xen 3" value='xen3'/>
+                    </menupopup>
+                  </menulist>
+                </groupbox>
+              </groupbox>
+            </tabpanel>
+            <tabpanel id='kvm'>
+              <groupbox orient="vertical">
+                <caption label="KVM/QEMU Settings"/>
+                <groupbox orient="horizontal" align='center'>
+                  <label control="kvmFormat" value="Format"  width="100" accesskey="F"/>
+                  <menulist id="kvmFormat">
+                    <menupopup>
+                      <menuitem label="QCOW 2" value='qcow2'/>
+                    </menupopup>
+                  </menulist>
+                </groupbox>
+              </groupbox>
+            </tabpanel>
+            <tabpanel id='vmware'>
+              <groupbox orient="vertical">
+                <caption label="VMWare Settings"/>                
+                <groupbox orient="horizontal" align='center'>
+                  <label control="vmwareFormat" value="Format"  width="100" accesskey="F"/>
+                  <menulist id="vmwareFormat">
+                    <menupopup>
+                      <menuitem label="VMDK" value='vmdk'/>
+                    </menupopup>
+                  </menulist>
+                </groupbox>                
+              </groupbox>
+            </tabpanel>
+            <tabpanel id='ami'>
+              <groupbox orient="vertical">
+                <caption label="AMI Settings"/>
+                <groupbox orient="horizontal" align='center'>
+                  <label control="ami_home" value="AMI Home"  width="150"  accesskey="H" tooltiptext="Exported as EC2_AMITOOL_HOME during build process"/>
+                  <textbox id="ami_home" value=""/>
+                </groupbox>
+
+                <groupbox orient="horizontal" align='center'>
+                  <label control="ami_arch" value="Default Arch"  width="150"  accesskey="r" tooltiptext='Target architecture for the image'/>
+                  <menulist id="ami_arch">
+                    <menupopup>
+                      <menuitem label="i386" value='i386'/>
+                      <menuitem label="x86_64" value='x86_64'/>
+                    </menupopup>
+                  </menulist>
+                </groupbox>
+                <groupbox orient="horizontal" align='center'>
+                  <label control="ami_user" value="AMI User" width="150" accesskey="U" tooltiptext="The user's EC2 user ID"/>
+                  <textbox id="ami_user" value=""/>
+                </groupbox>
+                <groupbox orient="horizontal" align='center'>
+                  <label control="ami_privateKey" value="Private Key"  width="150"  accesskey="y" tooltiptext="The path to the user's PEM encoded RSA private key file"/>
+                  <textbox id="ami_privateKey" value="" width="250"/>
+                  <button id="open-button" label="..." oncommand="filePicker('ami_privateKey', null, 1, 'Please Choose a File');"/>
+                </groupbox>
+                <groupbox orient="horizontal" align='center'>
+                  <label control="ami_cert" value="Certificate"  width="150"  accesskey="C" tooltiptext="The path to the user's PEM encoded RSA public key certificate file"/>
+                  <textbox id="ami_cert" value="" width="250"/>
+                  <button id="open-button" label="..." oncommand="filePicker('ami_cert', null, 1, 'Please Choose a File');"/>
+                </groupbox>
+                <groupbox orient="horizontal" align='center'>
+                  <label control="ami_ec2cert" value="EC2 Certificate"  width="150"  accesskey="2" tooltiptext="The path to the EC2 X509 public key certificate bundled into the AMI"/>
+                  <textbox id="ami_ec2cert" value="" width="250"/>
+                  <button id="open-button" label="..." oncommand="filePicker('ami_ec2cert', null, 1, 'Please Choose a File');"/>
+                </groupbox>
+              </groupbox>
+            </tabpanel>
+            <tabpanel>
+              <groupbox orient="vertical">
+                <caption label="Euca Settings"/>
+                <groupbox orient="horizontal" align='center'>
+                <label control="euca_arch" value="Default Arch"  width="150"  accesskey="r" tooltiptext='Target architecture for the image'/>
+                <menulist id="euca_arch">
+                  <menupopup>
+                    <menuitem label="i386" value='i386'/>
+                    <menuitem label="x86_64" value='x86_64'/>
+                  </menupopup>
+                </menulist>
+                </groupbox>
+                <groupbox orient="horizontal" align='center'>
+                  <label control="euca_user" value="Euca User" width="150" accesskey="U" tooltiptext="The user's Eucalyptus user ID"/>
+                  <textbox id="euca_user" value=""/>
+                </groupbox>
+                <groupbox orient="horizontal" align='center'>
+                  <label control="euca_privateKey" value="Private Key"  width="150"  accesskey="y" tooltiptext="The path to the user's PEM encoded RSA private key file"/>
+                  <textbox id="euca_privateKey" value="" width="250"/>
+                  <button id="open-button" label="..." oncommand="filePicker('euca_privateKey', null, 1, 'Please Choose a File');"/>
+                </groupbox>
+                <groupbox orient="horizontal" align='center'>
+                  <label control="euca_cert" value="Certificate"  width="150"  accesskey="C" tooltiptext="The path to the user's PEM encoded RSA public key certificate file"/>
+                  <textbox id="euca_cert" value="" width="250"/>
+                  <button id="open-button" label="..." oncommand="filePicker('euca_cert', null, 1, 'Please Choose a File');"/>
+                </groupbox>
+                <groupbox orient="horizontal" align='center'>
+                  <label control="euca_ec2cert" value="EC2 Certificate"  width="150"  accesskey="2" tooltiptext="The path to the Cloud's X509 public key certificate"/>
+                  <textbox id="euca_ec2cert" value="" width="250"/>
+                  <button id="open-button" label="..." oncommand="filePicker('euca_ec2cert', null, 1, 'Please Choose a File');"/>
+                </groupbox>
+              </groupbox>             
+            </tabpanel>
+          </tabpanels>
+        </tabbox>
+      </tabpanel>
+      <tabpanel id='bundler' style='border:1px outset white;'>
+        <groupbox orient="vertical" >
+          <caption label="Bundler"/>
+          <groupbox orient="horizontal" align='center'>
+            <label control="destFormat" value="Format"  width="100" accesskey="F" tooltiptext="Images will be built in this format"/>
+            <menulist id="destFormat">
+              <menupopup>
+                <menuitem label="Xen 3" value='xen3'/>
+                <menuitem label="KVM/QEMU" value='kvm'/>
+                <menuitem label="VMWare" value='vmware'/>
+                <menuitem label="Amazon AMI" value='ami'/>
+                <menuitem label="Eucalyptus" value='euca'/>
+              </menupopup>
+            </menulist>
+          </groupbox>
+          <groupbox orient="horizontal" align='center'>
+            <label control="source_root" value="Source Root"  width="100"  accesskey="R" tooltiptext="Directory containing a 'prebuilt' filesystem that will be bundled into the image"/>
+            <textbox id="source_root" value="" width="350"/>
+            <button id="open-button" label="..." oncommand="filePicker('source_root', document.getElementById('defaultRepo').value, 0, 'Please Choose a Directory');"/>
+          </groupbox>
+          <groupbox orient="horizontal" align='center'>
+            <label control="dest_directory" value="Output Folder"  width="100" accesskey="u" tooltiptext="The image will be put here.  For AMI and Eucalyptus builds, the manifest and part files will also be stored here"/>
+            <textbox id="dest_directory" value="" width="350"/>
+            <button id="open-button" label="..." oncommand="filePicker('dest_directory', null, 0, 'Please Choose a Directory');"/>
+          </groupbox>      
+          <groupbox orient="horizontal" align='center'>
+            <label control="dest_imgFile" value="Image File"  width="100"  accesskey="I" tooltiptext="The image file to be built -- will be stored in the Output Folder"/>
+            <textbox id="dest_imgFile" value="" width="350"/>
+          </groupbox>
+          <groupbox orient="horizontal" align='center'>
+            <label control="image_size" value="Size (MB)" width="100" accesskey="S" tooltiptext="Images will be this size"/>
+            <textbox id="image_size" type="number" min="1000" max="100000"/>
+          </groupbox>      
+          <groupbox orient="horizontal" align='center'>
+            <button id="bundle-button" label="Create Image" oncommand="bundle();" accesskey="C" tooltiptext="Start the image creation process, which can take several minutes"/>
+            <progressmeter id='progress' mode="undetermined" hidden='true'/>
+          </groupbox>
+          <groupbox orient="horizontal" align='center' style='border-top:1px inset white;'>
+            &#160;
+          </groupbox>
+          <groupbox orient="horizontal" align='center'>
+            <label control="status_box" value="Status"  width="50"/>
+            <textbox id="status_box" value="" height='100' width="550" multiline='true'/>
+          </groupbox>      
+        </groupbox>
+      </tabpanel>
+      <tabpanel id='output'>
+        <groupbox orient="vertical" flex='1'>
+          <caption id='output-caption' label="Output"/>
+          <browser id="output-frame" flex="1" width='600' style='background-color: white; border:1px inset gray;'/>
+          <groupbox orient='horizontal'>
+            <button id="reload-button" label="Reload" oncommand="reloadOutput();" maxWidth='100' accesskey='R' tooltiptext='Refresh the output -- "Exiting build" will appear when process is done. '/>
+          </groupbox>
+        </groupbox>
+      </tabpanel>
+    </tabpanels>
+  </tabbox>
+
+  <keyset>
+    <key id="close_cmd" modifiers="control" key="W" oncommand="window.close();"/>
+  </keyset>
+</window>
+

File src/stacklet/stackbundler/chrome/content/validate.js

+function validateField(fieldName, userMessage, validator, focus, append, suggestion, pattern) {
+
+  try {
+    var val = document.getElementById(fieldName).value;
+    var returnVal = true;
+    if (validator == 'validateFile') {
+      returnVal = validateFile(val);
+    } else if (validator == 'validateDirectory') {
+      returnVal = validateDirectory(val);
+    } else if (validator == 'validateRegex') {
+      returnVal = validateRegex(val, pattern);
+    }
+    if (!returnVal) {
+      if (append) {
+        userMessage += ': ' + val;
+      }
+      if (suggestion) {
+        userMessage += '\n ' + suggestion;
+      }
+      alert(userMessage);
+      if (focus) {
+        document.getElementById(fieldName).focus();
+      }
+    }
+    return returnVal;    
+  } catch (err) {
+    return false;
+  }
+}
+
+function validateRegex(val, pattern) {
+
+  return pattern.test(val);
+}
+
+function validateFile(filePath) {
+
+  try {
+    if (filePath == null || filePath.replace(/ /gi, "") == "") {
+      return false;
+    }
+    var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);  
+    file.initWithPath(filePath);
+    if (!file.exists()) {
+      return false;
+    }
+    if (!file.isFile()) {
+      return false;
+    }
+    return true;
+  } catch (err) {
+    return false;
+  }
+}
+
+function validateDirectory(filePath) {
+
+  try {
+    if (filePath == null || filePath.replace(/ /gi, "") == "") {
+      return false;
+    }
+    var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);  
+    file.initWithPath(filePath);
+    if (!file.exists()) {
+      return false;
+    }
+    if (!file.isDirectory()) {
+      return false;
+    }
+    return true;
+  } catch (err) {
+    return false;
+  }
+}
+
+

File src/stacklet/stackbundler/defaults/preferences/prefs.js

+pref("toolkit.defaultChromeURI", "chrome://stackbundler/content/main.xul");
+
+pref("stacklet.ami_arch", 0);
+pref("stacklet.ami_cert", "");
+pref("stacklet.ami_ec2cert", "");
+pref("stacklet.ami_home", "/usr/local/ec2/amitools/");
+pref("stacklet.ami_privateKey", "");
+pref("stacklet.ami_user", "");
+pref("stacklet.defaultFormat", 0);
+pref("stacklet.defaultFsType", 0);
+pref("stacklet.defaultOutputDirectory", "/tmp/stacklet");
+pref("stacklet.defaultRepo", "/mnt/stacklet");
+pref("stacklet.defaultSize", 3000);
+pref("stacklet.euca_arch", 0);
+pref("stacklet.euca_cert", "");
+pref("stacklet.euca_ec2cert", "");
+pref("stacklet.euca_privateKey", "");
+pref("stacklet.euca_user", "");
+pref("stacklet.stackfactoryPath", "/usr/bin/stackfactory");
+pref("stacklet.stackletHome", "/usr/local/stacklet");
+pref("stacklet.sudoPath", "/usr/bin/sudo");