diff --git a/Ganeti/HTools/CLI.hs b/Ganeti/HTools/CLI.hs
index 618eb1ef845bba6693d047bb1b780c97a7c61be9..c597bda8be41722aa5a5f63ffc296beb64820f41 100644
--- a/Ganeti/HTools/CLI.hs
+++ b/Ganeti/HTools/CLI.hs
@@ -8,9 +8,11 @@ and this is more IO oriented.
 
 module Ganeti.HTools.CLI
     ( CLIOptions(..)
+    , EToolOptions(..)
     , parseOpts
     , parseEnv
     , shTemplate
+    , loadExternalData
     ) where
 
 import System.Console.GetOpt
@@ -23,12 +25,26 @@ import Text.Printf (printf)
 import qualified Data.Version
 
 import qualified Ganeti.HTools.Version as Version(version)
+import qualified Ganeti.HTools.Rapi as Rapi
+import qualified Ganeti.HTools.Text as Text
+import qualified Ganeti.HTools.Loader as Loader
+
+import Ganeti.HTools.Types
 
 -- | Class for types which support show help and show version
 class CLIOptions a where
     showHelp    :: a -> Bool
     showVersion :: a -> Bool
 
+-- | Class for types which support the -i/-n/-m options
+class EToolOptions a where
+    nodeFile   :: a -> FilePath
+    nodeSet    :: a -> Bool
+    instFile   :: a -> FilePath
+    instSet    :: a -> Bool
+    masterName :: a -> String
+    silent     :: a -> Bool
+
 -- | Command line parser, using the 'options' structure.
 parseOpts :: (CLIOptions b) =>
              [String]            -- ^ The command line arguments
@@ -78,3 +94,34 @@ shTemplate =
            \    exit 0\n\
            \  fi\n\
            \}\n\n"
+
+-- | External tool data loader from a variety of sources
+loadExternalData :: (EToolOptions a) =>
+                    a
+                 -> IO (NodeList, InstanceList, String, NameList, NameList)
+loadExternalData opts = do
+  (env_node, env_inst) <- parseEnv ()
+  let nodef = if nodeSet opts then nodeFile opts
+              else env_node
+      instf = if instSet opts then instFile opts
+              else env_inst
+  input_data <-
+      case masterName opts of
+        "" -> Text.loadData nodef instf
+        host -> Rapi.loadData host
+
+  let ldresult = input_data >>= Loader.mergeData
+  (loaded_nl, il, csf, ktn, kti) <-
+      (case ldresult of
+         Ok x -> return x
+         Bad s -> do
+           printf "Error: failed to load data. Details:\n%s\n" s
+           exitWith $ ExitFailure 1
+      )
+  let (fix_msgs, fixed_nl) = Loader.checkData loaded_nl il ktn kti
+
+  unless (null fix_msgs || silent opts) $ do
+         putStrLn "Warning: cluster has inconsistent data:"
+         putStrLn . unlines . map (\s -> printf "  - %s" s) $ fix_msgs
+
+  return (fixed_nl, il, csf, ktn, kti)
diff --git a/hbal.hs b/hbal.hs
index 5db69d3ad08b0bc70989e64d073a2115abf897f9..6eda3476033e238d36e40c139bba564447497a23 100644
--- a/hbal.hs
+++ b/hbal.hs
@@ -19,9 +19,6 @@ import qualified Ganeti.HTools.Container as Container
 import qualified Ganeti.HTools.Cluster as Cluster
 import qualified Ganeti.HTools.Node as Node
 import qualified Ganeti.HTools.CLI as CLI
-import qualified Ganeti.HTools.Rapi as Rapi
-import qualified Ganeti.HTools.Text as Text
-import qualified Ganeti.HTools.Loader as Loader
 
 import Ganeti.HTools.Utils
 import Ganeti.HTools.Types
@@ -48,6 +45,14 @@ instance CLI.CLIOptions Options where
     showVersion = optShowVer
     showHelp    = optShowHelp
 
+instance CLI.EToolOptions Options where
+    nodeFile   = optNodef
+    nodeSet    = optNodeSet
+    instFile   = optInstf
+    instSet    = optInstSet
+    masterName = optMaster
+    silent a   = (optVerbose a) == 0
+
 -- | Default values for the command line options.
 defaultOptions :: Options
 defaultOptions  = Options
@@ -175,32 +180,10 @@ main = do
          hPutStrLn stderr "Error: this program doesn't take any arguments."
          exitWith $ ExitFailure 1
 
-  (env_node, env_inst) <- CLI.parseEnv ()
-  let nodef = if optNodeSet opts then optNodef opts
-              else env_node
-      instf = if optInstSet opts then optInstf opts
-              else env_inst
-      oneline = optOneline opts
+  let oneline = optOneline opts
       verbose = optVerbose opts
-  input_data <-
-      case optMaster opts of
-        "" -> Text.loadData nodef instf
-        host -> Rapi.loadData host
-
-  let ldresult = input_data >>= Loader.mergeData
-
-  (loaded_nl, il, csf, ktn, kti) <-
-      (case ldresult of
-         Ok x -> return x
-         Bad s -> do
-           printf "Error: failed to load data. Details:\n%s\n" s
-           exitWith $ ExitFailure 1
-      )
-  let (fix_msgs, fixed_nl) = Loader.checkData loaded_nl il ktn kti
-
-  unless (null fix_msgs || verbose == 0) $ do
-         putStrLn "Warning: cluster has inconsistent data:"
-         putStrLn . unlines . map (\s -> printf "  - %s" s) $ fix_msgs
+
+  (fixed_nl, il, csf, ktn, kti) <- CLI.loadExternalData opts
 
   let offline_names = optOffline opts
       all_names = snd . unzip $ ktn
diff --git a/hn1.hs b/hn1.hs
index 926833859e35f9bdf07175bebe4b1917adf61f03..e7fc122f11d371ae5de25bf5a2ebb94801c8c439 100644
--- a/hn1.hs
+++ b/hn1.hs
@@ -18,9 +18,6 @@ import qualified Ganeti.HTools.Container as Container
 import qualified Ganeti.HTools.Instance as Instance
 import qualified Ganeti.HTools.Cluster as Cluster
 import qualified Ganeti.HTools.CLI as CLI
-import qualified Ganeti.HTools.Rapi as Rapi
-import qualified Ganeti.HTools.Text as Text
-import qualified Ganeti.HTools.Loader as Loader
 import Ganeti.HTools.Types
 
 -- | Command line options structure.
@@ -44,6 +41,14 @@ instance CLI.CLIOptions Options where
     showVersion = optShowVer
     showHelp    = optShowHelp
 
+instance CLI.EToolOptions Options where
+    nodeFile   = optNodef
+    nodeSet    = optNodeSet
+    instFile   = optInstf
+    instSet    = optInstSet
+    masterName = optMaster
+    silent _   = False
+
 -- | Default values for the command line options.
 defaultOptions :: Options
 defaultOptions    = Options
@@ -140,31 +145,7 @@ main = do
          hPutStrLn stderr "Error: this program doesn't take any arguments."
          exitWith $ ExitFailure 1
 
-  (env_node, env_inst) <- CLI.parseEnv ()
-  let nodef = if optNodeSet opts then optNodef opts
-              else env_node
-      instf = if optInstSet opts then optInstf opts
-              else env_inst
-      min_depth = optMinDepth opts
-
-  input_data <-
-      case optMaster opts of
-        "" -> Text.loadData nodef instf
-        host -> Rapi.loadData host
-  let ldresult = input_data >>= Loader.mergeData
-
-  (loaded_nl, il, csf, ktn, kti) <-
-      (case ldresult of
-         Ok x -> return x
-         Bad s -> do
-           printf "Error: failed to load data. Details:\n%s\n" s
-           exitWith $ ExitFailure 1
-      )
-  let (fix_msgs, nl) = Loader.checkData loaded_nl il ktn kti
-
-  unless (null fix_msgs) $ do
-         putStrLn "Warning: cluster has inconsistent data:"
-         putStrLn . unlines . map (\s -> printf "  - %s" s) $ fix_msgs
+  (nl, il, csf, ktn, kti) <- CLI.loadExternalData opts
 
   printf "Loaded %d nodes, %d instances\n"
              (Container.size nl)
@@ -174,6 +155,8 @@ main = do
          printf "Note: Stripping common suffix of '%s' from names\n" csf
 
   let (bad_nodes, bad_instances) = Cluster.computeBadItems nl il
+      min_depth = optMinDepth opts
+
   printf "Initial check done: %d bad nodes, %d bad instances.\n"
              (length bad_nodes) (length bad_instances)