Skip to content

Commit 77a85b6

Browse files
authored
Implement vmcli start --verbose (#5)
1 parent b3ee962 commit 77a85b6

File tree

10 files changed

+81
-38
lines changed

10 files changed

+81
-38
lines changed

package.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ dependencies:
6666
- process
6767
- safe-exceptions
6868
- string-conversions
69+
- strip-ansi-escape
6970
- text
7071
- unix
7172
- xdg-basedir

spec/IntegrationSpec.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Data.String.Interpolate (i)
1313
import Data.Text qualified as T
1414
import Data.Text.IO qualified as T
1515
import NixVms qualified
16+
import Options (VmName (..))
1617
import State (readState)
1718
import StdLib
1819
import System.Directory (doesDirectoryExist, listDirectory)

spec/TestUtils.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Data.String (IsString)
1010
import Data.String.Conversions
1111
import GHC.Exts (IsString (..))
1212
import Network.Socket.Free (getFreePort)
13+
import Options (VmName (..))
1314
import Run (run)
1415
import State
1516
import StdLib
@@ -80,7 +81,7 @@ withMockContext vmNames action = do
8081
NixVms
8182
{ listVms = \_ctx -> pure vmNames,
8283
buildAndRun =
83-
\ctx vmName -> do
84+
\ctx _verbosity vmName -> do
8485
unless (vmName `elem` vmNames) $ do
8586
error $ cs $ "nix vm mock: vm not found: " <> vmNameToText vmName
8687
(_, _, _, ph) <- do

src/Commands.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Data.List.NonEmpty qualified as NonEmpty
1515
import Data.Maybe (fromMaybe)
1616
import Data.Text qualified as T
1717
import Data.Text.IO qualified as T
18-
import Options (StartOptions (..))
18+
import Options (StartOptions (..), Verbosity, VmName (..))
1919
import State
2020
import StdLib
2121
import System.Directory (doesFileExist)
@@ -32,8 +32,8 @@ list ctx = do
3232
[] -> "no vms configured"
3333
vms -> "configured vms: " <> T.intercalate ", " (map vmNameToText vms)
3434

35-
start :: Context -> StartOptions -> IO ()
36-
start ctx startOptions = do
35+
start :: Context -> Verbosity -> StartOptions -> IO ()
36+
start ctx verbosity startOptions = do
3737
vmNames <- case startOptions of
3838
StartAll -> do
3939
vmNames <- listVms (nixVms ctx) ctx
@@ -57,7 +57,7 @@ start ctx startOptions = do
5757
-- todo: make runtime dep
5858
Cradle.cmd "ssh-keygen"
5959
& Cradle.addArgs ["-f", vmKeyPath, "-N", ""]
60-
ph <- buildAndRun (nixVms ctx) ctx vmName
60+
ph <- buildAndRun (nixVms ctx) ctx verbosity vmName
6161
registerProcess ctx ph
6262
pid <- getPid ph <&> fromMaybe (error "no pid")
6363
state <- readState ctx vmName

src/Context.hs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module Context where
22

33
import Cradle qualified
4+
import Options (Verbosity, VmName)
45
import StdLib
56
import System.IO
67
import System.Process
@@ -16,9 +17,6 @@ data Context = Context
1617

1718
data NixVms = NixVms
1819
{ listVms :: Context -> IO [VmName],
19-
buildAndRun :: Context -> VmName -> IO ProcessHandle,
20+
buildAndRun :: Context -> Verbosity -> VmName -> IO ProcessHandle,
2021
sshIntoHost :: forall o. (Cradle.Output o) => Context -> VmName -> [Text] -> IO o
2122
}
22-
23-
newtype VmName = VmName {vmNameToText :: Text}
24-
deriving stock (Eq, Show, Ord)

src/NixVms.hs

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
module NixVms (NixVms (..), production) where
22

33
import Context
4+
import Control.Concurrent (forkIO)
45
import Cradle
56
import Data.Aeson qualified as Aeson
67
import Data.Map.Strict qualified as Map
8+
import Data.String.AnsiEscapeCodes.Strip.Text (stripAnsiEscapeCodes)
79
import Data.String.Conversions (cs)
810
import Data.String.Interpolate (i)
911
import Data.Text qualified as T
1012
import Data.Text.IO qualified as T
1113
import Network.Socket.Free (getFreePort)
14+
import Options (Verbosity (..), VmName (..))
1215
import State
1316
import StdLib
1417
import System.Directory (createDirectoryIfMissing, listDirectory)
1518
import System.Environment (getEnvironment)
1619
import System.FilePath (takeDirectory, (</>))
17-
import System.IO (IOMode (..), openFile)
20+
import System.IO (Handle, IOMode (..), openFile)
1821
import System.IO qualified
1922
import System.Process (CreateProcess (..), ProcessHandle, StdStream (..), createProcess, proc)
2023
import Utils
@@ -47,8 +50,8 @@ listVmsImpl ctx = do
4750
Left err -> error err
4851
Right (parsed :: [Text]) -> pure $ map VmName parsed
4952

50-
buildAndRunImpl :: Context -> VmName -> IO ProcessHandle
51-
buildAndRunImpl ctx vmName = do
53+
buildAndRunImpl :: Context -> Verbosity -> VmName -> IO ProcessHandle
54+
buildAndRunImpl ctx verbosity vmName = do
5255
vmExecutable <- logStep "Building NixOS config..." $ do
5356
moduleExtensions <- getModuleExtensions ctx vmName
5457
(Cradle.StdoutTrimmed drvPathJson) <-
@@ -85,24 +88,7 @@ buildAndRunImpl ctx vmName = do
8588
case files of
8689
[file] -> pure $ cs outPath </> "bin" </> file
8790
files -> error $ "expected one vm script: " <> show files
88-
89-
logStep "Starting VM..." $ do
90-
storageDir <- getStateDir ctx vmName
91-
let nixDiskImage = storageDir </> cs (vmNameToText vmName) </> "image.qcow2"
92-
createDirectoryIfMissing True (takeDirectory nixDiskImage)
93-
stdoutHandle <- openFile (storageDir </> "./stdout.log") WriteMode
94-
stderrHandle <- openFile (storageDir </> "./stderr.log") WriteMode
95-
(_, _, _, ph) <- do
96-
parentEnvironment <-
97-
getEnvironment
98-
<&> Map.fromList
99-
createProcess
100-
(proc vmExecutable [])
101-
{ env = Just $ Map.toList $ Map.insert "NIX_DISK_IMAGE" nixDiskImage parentEnvironment,
102-
std_out = UseHandle stdoutHandle,
103-
std_err = UseHandle stderrHandle
104-
}
105-
pure ph
91+
logStep "Starting VM..." $ runVm ctx verbosity vmName vmExecutable
10692

10793
nixStandardFlags :: [Text]
10894
nixStandardFlags =
@@ -148,6 +134,40 @@ toNixString s = "\"" <> T.concatMap escapeChar (cs s) <> "\""
148134
'\\' -> "\\\\"
149135
c -> T.singleton c
150136

137+
runVm :: Context -> Verbosity -> VmName -> FilePath -> IO ProcessHandle
138+
runVm ctx verbosity vmName vmExecutable = do
139+
storageDir <- getStateDir ctx vmName
140+
let nixDiskImage = storageDir </> cs (vmNameToText vmName) </> "image.qcow2"
141+
createDirectoryIfMissing True (takeDirectory nixDiskImage)
142+
parentEnvironment <- getEnvironment <&> Map.fromList
143+
let mkProc stdout stdin =
144+
(System.Process.proc vmExecutable [])
145+
{ env = Just $ Map.toList $ Map.insert "NIX_DISK_IMAGE" nixDiskImage parentEnvironment,
146+
std_out = stdout,
147+
std_err = stdin
148+
}
149+
proc <- case verbosity of
150+
DefaultVerbosity -> do
151+
stdoutHandle <- openFile (storageDir </> "./stdout.log") WriteMode
152+
stderrHandle <- openFile (storageDir </> "./stderr.log") WriteMode
153+
pure $ mkProc (UseHandle stdoutHandle) (UseHandle stderrHandle)
154+
Verbose -> pure $ mkProc CreatePipe CreatePipe
155+
(_, stdout, stderr, ph) <- createProcess proc
156+
case verbosity of
157+
DefaultVerbosity -> pure ()
158+
Verbose -> do
159+
(Just stdout, Just stderr) <- pure (stdout, stderr)
160+
_ <- forkIO $ streamHandles "qemu" stdout System.IO.stdout
161+
_ <- forkIO $ streamHandles "qemu" stderr System.IO.stderr
162+
pure ()
163+
pure ph
164+
165+
streamHandles :: Text -> Handle -> Handle -> IO ()
166+
streamHandles prefix input output = do
167+
chunk <- T.hGetLine input
168+
T.hPutStrLn output $ prefix <> "> " <> stripAnsiEscapeCodes chunk
169+
streamHandles prefix input output
170+
151171
sshIntoHostImpl :: (Cradle.Output o) => Context -> VmName -> [Text] -> IO o
152172
sshIntoHostImpl ctx vmName command = do
153173
vmKeyPath <- getStateFile ctx vmName "vmkey"

src/Options.hs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
module Options
2-
( Options (..),
2+
( parser,
3+
Options (..),
4+
Verbosity (..),
35
Command (..),
46
StartOptions (..),
5-
parser,
7+
VmName (..),
68
)
79
where
810

9-
import Context
1011
import Data.List.NonEmpty (NonEmpty)
1112
import Data.List.NonEmpty qualified as NonEmpty
1213
import Data.Text
@@ -18,14 +19,26 @@ newtype Options = Options Command
1819

1920
data Command
2021
= List
21-
| Start StartOptions
22+
| Start {verbosity :: Verbosity, options :: StartOptions}
2223
| Ssh {vmName :: VmName, sshCommand :: [Text]}
2324
| Status {vmNames :: [VmName]}
2425
| Stop {vmName :: VmName}
2526
deriving stock (Show, Generic)
2627

27-
parseVmName :: Parser VmName
28-
parseVmName = VmName <$> argument str (metavar "VM_NAME")
28+
data Verbosity
29+
= DefaultVerbosity
30+
| Verbose
31+
deriving stock (Show)
32+
33+
parseVerbosity :: Parser Verbosity
34+
parseVerbosity =
35+
flag
36+
DefaultVerbosity
37+
Verbose
38+
( long "verbose"
39+
<> short 'v'
40+
<> help "increase verbosity"
41+
)
2942

3043
data StartOptions
3144
= StartAll
@@ -53,7 +66,7 @@ parser =
5366
<> command
5467
"start"
5568
( info
56-
(Start <$> parseStartOptions)
69+
(Start <$> parseVerbosity <*> parseStartOptions)
5770
(fullDesc <> progDesc "Start a development vm")
5871
)
5972
<> command
@@ -75,3 +88,9 @@ parser =
7588
(progDesc "Stop a running vm")
7689
)
7790
)
91+
92+
newtype VmName = VmName {vmNameToText :: Text}
93+
deriving stock (Eq, Show, Ord)
94+
95+
parseVmName :: Parser VmName
96+
parseVmName = VmName <$> argument str (metavar "VM_NAME")

src/Run.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ run ctx args =
2626
(Options opts) <- handleParseResult $ execParserPure (prefs showHelpOnError) parser (cs <$> args)
2727
case opts of
2828
List -> list ctx
29-
Start vmNames -> start ctx vmNames
29+
Start verbosity vmNames -> start ctx verbosity vmNames
3030
Stop vmName -> stop ctx vmName
3131
Ssh vmName command -> ssh ctx vmName command
3232
Status vmNames -> status ctx vmNames

src/State.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Control.Monad (filterM)
55
import Data.Aeson
66
import Data.String.Conversions (cs)
77
import Data.Text.IO qualified as T
8+
import Options (VmName (..))
89
import StdLib
910
import System.Directory
1011
( createDirectoryIfMissing,

vmcli.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ executable vmcli
5555
, process
5656
, safe-exceptions
5757
, string-conversions
58+
, strip-ansi-escape
5859
, text
5960
, unix
6061
, xdg-basedir
@@ -120,6 +121,7 @@ test-suite spec
120121
, safe-exceptions
121122
, silently
122123
, string-conversions
124+
, strip-ansi-escape
123125
, temporary
124126
, text
125127
, unix

0 commit comments

Comments
 (0)