A few unittests improvements
[ganeti-local] / htools / Ganeti / HTools / CLI.hs
index ebdc174..69e0806 100644 (file)
@@ -1,7 +1,7 @@
 {-| Implementation of command-line functions.
 
 This module holds the common command-line related functions for the
-binaries, separated into this module since "Ganeti.HTools.Utils" is
+binaries, separated into this module since "Ganeti.Utils" is
 used in many other places and this is more IO oriented.
 
 -}
@@ -30,12 +30,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 module Ganeti.HTools.CLI
   ( Options(..)
   , OptType
-  , parseOpts
+  , defaultOptions
+  , Ganeti.HTools.CLI.parseOpts
   , parseOptsInner
   , parseYesNo
   , parseISpecString
   , shTemplate
-  , defaultLuxiSocket
   , maybePrintNodes
   , maybePrintInsts
   , maybeShowWarnings
@@ -73,42 +73,31 @@ module Ganeti.HTools.CLI
   , oPrintNodes
   , oQuiet
   , oRapiMaster
-  , oReplay
   , oSaveCluster
   , oSelInst
   , oShowHelp
   , oShowVer
+  , oShowComp
   , oStdSpec
-  , oTestCount
   , oTieredSpec
   , oVerbose
+  , genericOpts
   ) where
 
 import Control.Monad
 import Data.Char (toUpper)
 import Data.Maybe (fromMaybe)
-import qualified Data.Version
 import System.Console.GetOpt
 import System.IO
-import System.Info
-import System.Exit
 import Text.Printf (printf)
 
-import qualified Ganeti.HTools.Version as Version(version)
 import qualified Ganeti.HTools.Container as Container
 import qualified Ganeti.HTools.Node as Node
-import qualified Ganeti.Constants as C
+import qualified Ganeti.Path as Path
 import Ganeti.HTools.Types
-import Ganeti.HTools.Utils
 import Ganeti.BasicTypes
-
--- * Constants
-
--- | The default value for the luxi socket.
---
--- This is re-exported from the "Ganeti.Constants" module.
-defaultLuxiSocket :: FilePath
-defaultLuxiSocket = C.masterSocket
+import Ganeti.Common as Common
+import Ganeti.Utils
 
 -- * Data types
 
@@ -144,6 +133,7 @@ data Options = Options
   , optSaveCluster :: Maybe FilePath -- ^ Save cluster state to this file
   , optShowCmds    :: Maybe FilePath -- ^ Whether to show the command list
   , optShowHelp    :: Bool           -- ^ Just show the help
+  , optShowComp    :: Bool           -- ^ Just show the completion info
   , optShowInsts   :: Bool           -- ^ Whether to show the instance map
   , optShowNodes   :: Maybe [String] -- ^ Whether to show node status
   , optShowVer     :: Bool           -- ^ Just show the program version
@@ -187,6 +177,7 @@ defaultOptions  = Options
   , optSaveCluster = Nothing
   , optShowCmds    = Nothing
   , optShowHelp    = False
+  , optShowComp    = False
   , optShowInsts   = False
   , optShowNodes   = Nothing
   , optShowVer     = False
@@ -198,7 +189,15 @@ defaultOptions  = Options
   }
 
 -- | Abrreviation for the option type.
-type OptType = OptDescr (Options -> Result Options)
+type OptType = GenericOptType Options
+
+instance StandardOptions Options where
+  helpRequested = optShowHelp
+  verRequested  = optShowVer
+  compRequested = optShowComp
+  requestHelp o = o { optShowHelp = True }
+  requestVer  o = o { optShowVer  = True }
+  requestComp o = o { optShowComp = True }
 
 -- * Helper functions
 
@@ -217,309 +216,321 @@ parseISpecString descr inp = do
     [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
     _ -> err
 
+-- | Disk template choices.
+optComplDiskTemplate :: OptCompletion
+optComplDiskTemplate = OptComplChoices $
+                       map diskTemplateToRaw [minBound..maxBound]
+
 -- * Command line options
 
 oDataFile :: OptType
-oDataFile = Option "t" ["text-data"]
-            (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
-            "the cluster data FILE"
+oDataFile =
+  (Option "t" ["text-data"]
+   (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
+   "the cluster data FILE",
+   OptComplFile)
 
 oDiskMoves :: OptType
-oDiskMoves = Option "" ["no-disk-moves"]
-             (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
-             "disallow disk moves from the list of allowed instance changes,\
-             \ thus allowing only the 'cheap' failover/migrate operations"
+oDiskMoves =
+  (Option "" ["no-disk-moves"]
+   (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
+   "disallow disk moves from the list of allowed instance changes,\
+   \ thus allowing only the 'cheap' failover/migrate operations",
+   OptComplNone)
 
 oDiskTemplate :: OptType
-oDiskTemplate = Option "" ["disk-template"]
-                (ReqArg (\ t opts -> do
-                           dt <- diskTemplateFromRaw t
-                           return $ opts { optDiskTemplate = Just dt })
-                 "TEMPLATE") "select the desired disk template"
+oDiskTemplate =
+  (Option "" ["disk-template"]
+   (reqWithConversion diskTemplateFromRaw
+    (\dt opts -> Ok opts { optDiskTemplate = Just dt })
+    "TEMPLATE") "select the desired disk template",
+   optComplDiskTemplate)
 
 oSpindleUse :: OptType
-oSpindleUse = Option "" ["spindle-use"]
-              (ReqArg (\ n opts -> do
-                         su <- tryRead "parsing spindle-use" n
-                         when (su < 0) $
-                              fail "Invalid value of the spindle-use\
-                                   \ (expected >= 0)"
-                         return $ opts { optSpindleUse = Just su })
-               "SPINDLES") "select how many virtual spindle instances use\
-                           \ [default read from cluster]"
+oSpindleUse =
+  (Option "" ["spindle-use"]
+   (reqWithConversion (tryRead "parsing spindle-use")
+    (\su opts -> do
+       when (su < 0) $
+            fail "Invalid value of the spindle-use (expected >= 0)"
+       return $ opts { optSpindleUse = Just su })
+    "SPINDLES") "select how many virtual spindle instances use\
+                \ [default read from cluster]",
+   OptComplFloat)
 
 oSelInst :: OptType
-oSelInst = Option "" ["select-instances"]
-          (ReqArg (\ f opts -> Ok opts { optSelInst = sepSplit ',' f }) "INSTS")
-          "only select given instances for any moves"
+oSelInst =
+  (Option "" ["select-instances"]
+   (ReqArg (\ f opts -> Ok opts { optSelInst = sepSplit ',' f }) "INSTS")
+   "only select given instances for any moves",
+   OptComplManyInstances)
 
 oInstMoves :: OptType
-oInstMoves = Option "" ["no-instance-moves"]
-             (NoArg (\ opts -> Ok opts { optInstMoves = False}))
-             "disallow instance (primary node) moves from the list of allowed,\
-             \ instance changes, thus allowing only slower, but sometimes\
-             \ safer, drbd secondary changes"
+oInstMoves =
+  (Option "" ["no-instance-moves"]
+   (NoArg (\ opts -> Ok opts { optInstMoves = False}))
+   "disallow instance (primary node) moves from the list of allowed,\
+   \ instance changes, thus allowing only slower, but sometimes\
+   \ safer, drbd secondary changes",
+   OptComplNone)
 
 oDynuFile :: OptType
-oDynuFile = Option "U" ["dynu-file"]
-            (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
-            "Import dynamic utilisation data from the given FILE"
+oDynuFile =
+  (Option "U" ["dynu-file"]
+   (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
+   "Import dynamic utilisation data from the given FILE",
+   OptComplFile)
 
 oEvacMode :: OptType
-oEvacMode = Option "E" ["evac-mode"]
-            (NoArg (\opts -> Ok opts { optEvacMode = True }))
-            "enable evacuation mode, where the algorithm only moves \
-            \ instances away from offline and drained nodes"
+oEvacMode =
+  (Option "E" ["evac-mode"]
+   (NoArg (\opts -> Ok opts { optEvacMode = True }))
+   "enable evacuation mode, where the algorithm only moves \
+   \ instances away from offline and drained nodes",
+   OptComplNone)
 
 oExInst :: OptType
-oExInst = Option "" ["exclude-instances"]
-          (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
-          "exclude given instances from any moves"
+oExInst =
+  (Option "" ["exclude-instances"]
+   (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
+   "exclude given instances from any moves",
+   OptComplManyInstances)
 
 oExTags :: OptType
-oExTags = Option "" ["exclusion-tags"]
-            (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
-             "TAG,...") "Enable instance exclusion based on given tag prefix"
+oExTags =
+  (Option "" ["exclusion-tags"]
+   (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
+    "TAG,...") "Enable instance exclusion based on given tag prefix",
+   OptComplString)
 
 oExecJobs :: OptType
-oExecJobs = Option "X" ["exec"]
-             (NoArg (\ opts -> Ok opts { optExecJobs = True}))
-             "execute the suggested moves via Luxi (only available when using\
-             \ it for data gathering)"
+oExecJobs =
+  (Option "X" ["exec"]
+   (NoArg (\ opts -> Ok opts { optExecJobs = True}))
+   "execute the suggested moves via Luxi (only available when using\
+   \ it for data gathering)",
+   OptComplNone)
 
 oGroup :: OptType
-oGroup = Option "G" ["group"]
-            (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
-            "the ID of the group to balance"
+oGroup =
+  (Option "G" ["group"]
+   (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
+   "the ID of the group to balance",
+   OptComplOneGroup)
 
 oIAllocSrc :: OptType
-oIAllocSrc = Option "I" ["ialloc-src"]
-             (ReqArg (\ f opts -> Ok opts { optIAllocSrc = Just f }) "FILE")
-             "Specify an iallocator spec as the cluster data source"
+oIAllocSrc =
+  (Option "I" ["ialloc-src"]
+   (ReqArg (\ f opts -> Ok opts { optIAllocSrc = Just f }) "FILE")
+   "Specify an iallocator spec as the cluster data source",
+   OptComplFile)
 
 oLuxiSocket :: OptType
-oLuxiSocket = Option "L" ["luxi"]
-              (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
-                       fromMaybe defaultLuxiSocket) "SOCKET")
-              "collect data via Luxi, optionally using the given SOCKET path"
+oLuxiSocket =
+  (Option "L" ["luxi"]
+   (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
+            fromMaybe Path.defaultLuxiSocket) "SOCKET")
+   "collect data via Luxi, optionally using the given SOCKET path",
+   OptComplFile)
 
 oMachineReadable :: OptType
-oMachineReadable = Option "" ["machine-readable"]
-                   (OptArg (\ f opts -> do
-                     flag <- parseYesNo True f
-                     return $ opts { optMachineReadable = flag }) "CHOICE")
-          "enable machine readable output (pass either 'yes' or 'no' to\
-          \ explicitly control the flag, or without an argument defaults to\
-          \ yes"
+oMachineReadable =
+  (Option "" ["machine-readable"]
+   (OptArg (\ f opts -> do
+              flag <- parseYesNo True f
+              return $ opts { optMachineReadable = flag }) "CHOICE")
+   "enable machine readable output (pass either 'yes' or 'no' to\
+   \ explicitly control the flag, or without an argument defaults to\
+   \ yes",
+   optComplYesNo)
 
 oMaxCpu :: OptType
-oMaxCpu = Option "" ["max-cpu"]
-          (ReqArg (\ n opts -> do
-                     mcpu <- tryRead "parsing max-cpu" n
-                     when (mcpu <= 0) $
-                          fail "Invalid value of the max-cpu ratio,\
-                               \ expected >0"
-                     return $ opts { optMcpu = Just mcpu }) "RATIO")
-          "maximum virtual-to-physical cpu ratio for nodes (from 0\
-          \ upwards) [default read from cluster]"
+oMaxCpu =
+  (Option "" ["max-cpu"]
+   (reqWithConversion (tryRead "parsing max-cpu")
+    (\mcpu opts -> do
+       when (mcpu <= 0) $
+            fail "Invalid value of the max-cpu ratio, expected >0"
+       return $ opts { optMcpu = Just mcpu }) "RATIO")
+   "maximum virtual-to-physical cpu ratio for nodes (from 0\
+   \ upwards) [default read from cluster]",
+   OptComplFloat)
 
 oMaxSolLength :: OptType
-oMaxSolLength = Option "l" ["max-length"]
-                (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
-                "cap the solution at this many balancing or allocation \
-                \ rounds (useful for very unbalanced clusters or empty \
-                \ clusters)"
+oMaxSolLength =
+  (Option "l" ["max-length"]
+   (reqWithConversion (tryRead "max solution length")
+    (\i opts -> Ok opts { optMaxLength = i }) "N")
+   "cap the solution at this many balancing or allocation \
+   \ rounds (useful for very unbalanced clusters or empty \
+   \ clusters)",
+   OptComplInteger)
 
 oMinDisk :: OptType
-oMinDisk = Option "" ["min-disk"]
-           (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
-           "minimum free disk space for nodes (between 0 and 1) [0]"
+oMinDisk =
+  (Option "" ["min-disk"]
+   (reqWithConversion (tryRead "min free disk space")
+    (\n opts -> Ok opts { optMdsk = n }) "RATIO")
+   "minimum free disk space for nodes (between 0 and 1) [0]",
+   OptComplFloat)
 
 oMinGain :: OptType
-oMinGain = Option "g" ["min-gain"]
-            (ReqArg (\ g opts -> Ok opts { optMinGain = read g }) "DELTA")
-            "minimum gain to aim for in a balancing step before giving up"
+oMinGain =
+  (Option "g" ["min-gain"]
+   (reqWithConversion (tryRead "min gain")
+    (\g opts -> Ok opts { optMinGain = g }) "DELTA")
+   "minimum gain to aim for in a balancing step before giving up",
+   OptComplFloat)
 
 oMinGainLim :: OptType
-oMinGainLim = Option "" ["min-gain-limit"]
-            (ReqArg (\ g opts -> Ok opts { optMinGainLim = read g }) "SCORE")
-            "minimum cluster score for which we start checking the min-gain"
+oMinGainLim =
+  (Option "" ["min-gain-limit"]
+   (reqWithConversion (tryRead "min gain limit")
+    (\g opts -> Ok opts { optMinGainLim = g }) "SCORE")
+   "minimum cluster score for which we start checking the min-gain",
+   OptComplFloat)
 
 oMinScore :: OptType
-oMinScore = Option "e" ["min-score"]
-            (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
-            "mininum score to aim for"
+oMinScore =
+  (Option "e" ["min-score"]
+   (reqWithConversion (tryRead "min score")
+    (\e opts -> Ok opts { optMinScore = e }) "EPSILON")
+   "mininum score to aim for",
+   OptComplFloat)
 
 oNoHeaders :: OptType
-oNoHeaders = Option "" ["no-headers"]
-             (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
-             "do not show a header line"
+oNoHeaders =
+  (Option "" ["no-headers"]
+   (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
+   "do not show a header line",
+   OptComplNone)
 
 oNoSimulation :: OptType
-oNoSimulation = Option "" ["no-simulation"]
-                (NoArg (\opts -> Ok opts {optNoSimulation = True}))
-                "do not perform rebalancing simulation"
+oNoSimulation =
+  (Option "" ["no-simulation"]
+   (NoArg (\opts -> Ok opts {optNoSimulation = True}))
+   "do not perform rebalancing simulation",
+   OptComplNone)
 
 oNodeSim :: OptType
-oNodeSim = Option "" ["simulate"]
-            (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
-            "simulate an empty cluster, given as\
-            \ 'alloc_policy,num_nodes,disk,ram,cpu'"
+oNodeSim =
+  (Option "" ["simulate"]
+   (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
+   "simulate an empty cluster, given as\
+   \ 'alloc_policy,num_nodes,disk,ram,cpu'",
+   OptComplString)
 
 oOfflineNode :: OptType
-oOfflineNode = Option "O" ["offline"]
-               (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
-               "set node as offline"
+oOfflineNode =
+  (Option "O" ["offline"]
+   (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
+   "set node as offline",
+   OptComplOneNode)
 
 oOutputDir :: OptType
-oOutputDir = Option "d" ["output-dir"]
-             (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
-             "directory in which to write output files"
+oOutputDir =
+  (Option "d" ["output-dir"]
+   (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
+   "directory in which to write output files",
+   OptComplDir)
 
 oPrintCommands :: OptType
-oPrintCommands = Option "C" ["print-commands"]
-                 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
-                          fromMaybe "-")
-                  "FILE")
-                 "print the ganeti command list for reaching the solution,\
-                 \ if an argument is passed then write the commands to a\
-                 \ file named as such"
+oPrintCommands =
+  (Option "C" ["print-commands"]
+   (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
+            fromMaybe "-")
+    "FILE")
+   "print the ganeti command list for reaching the solution,\
+   \ if an argument is passed then write the commands to a\
+   \ file named as such",
+   OptComplNone)
 
 oPrintInsts :: OptType
-oPrintInsts = Option "" ["print-instances"]
-              (NoArg (\ opts -> Ok opts { optShowInsts = True }))
-              "print the final instance map"
+oPrintInsts =
+  (Option "" ["print-instances"]
+   (NoArg (\ opts -> Ok opts { optShowInsts = True }))
+   "print the final instance map",
+   OptComplNone)
 
 oPrintNodes :: OptType
-oPrintNodes = Option "p" ["print-nodes"]
-              (OptArg ((\ f opts ->
-                          let (prefix, realf) = case f of
-                                                  '+':rest -> (["+"], rest)
-                                                  _ -> ([], f)
-                              splitted = prefix ++ sepSplit ',' realf
-                          in Ok opts { optShowNodes = Just splitted }) .
-                       fromMaybe []) "FIELDS")
-              "print the final node list"
+oPrintNodes =
+  (Option "p" ["print-nodes"]
+   (OptArg ((\ f opts ->
+               let (prefix, realf) = case f of
+                                       '+':rest -> (["+"], rest)
+                                       _ -> ([], f)
+                   splitted = prefix ++ sepSplit ',' realf
+               in Ok opts { optShowNodes = Just splitted }) .
+            fromMaybe []) "FIELDS")
+   "print the final node list",
+   OptComplNone)
 
 oQuiet :: OptType
-oQuiet = Option "q" ["quiet"]
-         (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
-         "decrease the verbosity level"
+oQuiet =
+  (Option "q" ["quiet"]
+   (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
+   "decrease the verbosity level",
+   OptComplNone)
 
 oRapiMaster :: OptType
-oRapiMaster = Option "m" ["master"]
-              (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
-              "collect data via RAPI at the given ADDRESS"
+oRapiMaster =
+  (Option "m" ["master"]
+   (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
+   "collect data via RAPI at the given ADDRESS",
+   OptComplHost)
 
 oSaveCluster :: OptType
-oSaveCluster = Option "S" ["save"]
-            (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
-            "Save cluster state at the end of the processing to FILE"
-
-oShowHelp :: OptType
-oShowHelp = Option "h" ["help"]
-            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
-            "show help"
-
-oShowVer :: OptType
-oShowVer = Option "V" ["version"]
-           (NoArg (\ opts -> Ok opts { optShowVer = True}))
-           "show the version of the program"
+oSaveCluster =
+  (Option "S" ["save"]
+   (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
+   "Save cluster state at the end of the processing to FILE",
+   OptComplNone)
 
 oStdSpec :: OptType
-oStdSpec = Option "" ["standard-alloc"]
-             (ReqArg (\ inp opts -> do
-                        tspec <- parseISpecString "standard" inp
-                        return $ opts { optStdSpec = Just tspec } )
-              "STDSPEC")
-             "enable standard specs allocation, given as 'disk,ram,cpu'"
-
-oTestCount :: OptType
-oTestCount = Option "" ["test-count"]
-             (ReqArg (\ inp opts -> do
-                        tcount <- tryRead "parsing test count" inp
-                        return $ opts { optTestCount = Just tcount } )
-              "COUNT")
-             "override the target test count"
+oStdSpec =
+  (Option "" ["standard-alloc"]
+   (ReqArg (\ inp opts -> do
+              tspec <- parseISpecString "standard" inp
+              return $ opts { optStdSpec = Just tspec } )
+    "STDSPEC")
+   "enable standard specs allocation, given as 'disk,ram,cpu'",
+   OptComplString)
 
 oTieredSpec :: OptType
-oTieredSpec = Option "" ["tiered-alloc"]
-             (ReqArg (\ inp opts -> do
-                        tspec <- parseISpecString "tiered" inp
-                        return $ opts { optTieredSpec = Just tspec } )
-              "TSPEC")
-             "enable tiered specs allocation, given as 'disk,ram,cpu'"
-
-oReplay :: OptType
-oReplay = Option "" ["replay"]
-          (ReqArg (\ stat opts -> Ok opts { optReplay = Just stat } ) "STATE")
-          "Pre-seed the random number generator with STATE"
+oTieredSpec =
+  (Option "" ["tiered-alloc"]
+   (ReqArg (\ inp opts -> do
+              tspec <- parseISpecString "tiered" inp
+              return $ opts { optTieredSpec = Just tspec } )
+    "TSPEC")
+   "enable tiered specs allocation, given as 'disk,ram,cpu'",
+   OptComplString)
 
 oVerbose :: OptType
-oVerbose = Option "v" ["verbose"]
-           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
-           "increase the verbosity level"
+oVerbose =
+  (Option "v" ["verbose"]
+   (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
+   "increase the verbosity level",
+   OptComplNone)
+
+-- | Generic options.
+genericOpts :: [GenericOptType Options]
+genericOpts =  [ oShowVer
+               , oShowHelp
+               , oShowComp
+               ]
 
 -- * Functions
 
--- | Helper for parsing a yes\/no command line flag.
-parseYesNo :: Bool         -- ^ Default value (when we get a @Nothing@)
-           -> Maybe String -- ^ Parameter value
-           -> Result Bool  -- ^ Resulting boolean value
-parseYesNo v Nothing      = return v
-parseYesNo _ (Just "yes") = return True
-parseYesNo _ (Just "no")  = return False
-parseYesNo _ (Just s)     = fail ("Invalid choice '" ++ s ++
-                                  "', pass one of 'yes' or 'no'")
-
--- | Usage info.
-usageHelp :: String -> [OptType] -> String
-usageHelp progname =
-  usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
-             progname Version.version progname)
-
--- | Show the program version info.
-versionInfo :: String -> String
-versionInfo progname =
-  printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
-         progname Version.version compilerName
-         (Data.Version.showVersion compilerVersion)
-         os arch
-
--- | Command line parser, using the 'Options' structure.
+-- | Wrapper over 'Common.parseOpts' with our custom options.
 parseOpts :: [String]               -- ^ The command line arguments
           -> String                 -- ^ The program name
           -> [OptType]              -- ^ The supported command line options
+          -> [ArgCompletion]        -- ^ The supported command line arguments
           -> IO (Options, [String]) -- ^ The resulting options and leftover
                                     -- arguments
-parseOpts argv progname options =
-  case parseOptsInner argv progname options of
-    Left (code, msg) -> do
-      hPutStr (if code == 0 then stdout else stderr) msg
-      exitWith (if code == 0 then ExitSuccess else ExitFailure code)
-    Right result ->
-      return result
-
--- | Inner parse options. The arguments are similar to 'parseOpts',
--- but it returns either a 'Left' composed of exit code and message,
--- or a 'Right' for the success case.
-parseOptsInner :: [String] -> String -> [OptType]
-               -> Either (Int, String) (Options, [String])
-parseOptsInner argv progname options =
-  case getOpt Permute options argv of
-    (o, n, []) ->
-      let (pr, args) = (foldM (flip id) defaultOptions o, n)
-      in case pr of
-           Bad msg -> Left (1, "Error while parsing command\
-                               \line arguments:\n" ++ msg ++ "\n")
-           Ok po ->
-             select (Right (po, args))
-                 [ (optShowHelp po, Left (0, usageHelp progname options))
-                 , (optShowVer po,  Left (0, versionInfo progname))
-                 ]
-    (_, _, errs) ->
-      Left (2, "Command line error: "  ++ concat errs ++ "\n" ++
-            usageHelp progname options)
+parseOpts = Common.parseOpts defaultOptions
+
 
 -- | A shell script template for autogenerated scripts.
 shTemplate :: String
@@ -546,7 +557,6 @@ maybePrintNodes (Just fields) msg fn = do
   hPutStrLn stderr (msg ++ " status:")
   hPutStrLn stderr $ fn fields
 
-
 -- | Optionally print the instance list.
 maybePrintInsts :: Bool   -- ^ Whether to print the instance list
                 -> String -- ^ Type of the instance map (e.g. initial)
@@ -571,13 +581,14 @@ maybeShowWarnings fix_msgs =
 printKeys :: String              -- ^ Prefix to printed variables
           -> [(String, String)]  -- ^ List of (key, value) pairs to be printed
           -> IO ()
-printKeys prefix = mapM_ (\(k, v) ->
-                       printf "%s_%s=%s\n" prefix (map toUpper k) (ensureQuoted v))
+printKeys prefix =
+  mapM_ (\(k, v) ->
+           printf "%s_%s=%s\n" prefix (map toUpper k) (ensureQuoted v))
 
 -- | Prints the final @OK@ marker in machine readable output.
 printFinal :: String    -- ^ Prefix to printed variable
-           -> Bool      -- ^ Whether output should be machine readable
-                        -- Note: if not, there is nothing to print
+           -> Bool      -- ^ Whether output should be machine readable;
+                        -- note: if not, there is nothing to print
            -> IO ()
 printFinal prefix True =
   -- this should be the final entry
@@ -606,7 +617,7 @@ setNodeStatus opts fixed_nl = do
       m_cpu = optMcpu opts
       m_dsk = optMdsk opts
 
-  unless (null offline_wrong) $ do
+  unless (null offline_wrong) .
          exitErr $ printf "wrong node name(s) set as offline: %s\n"
                    (commaJoin (map lrContent offline_wrong))
   let setMCpuFn = case m_cpu of