-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
juvix dev anoma {start, stop, status}
to manage an Anoma client (…
…#3183) This PR adds the `juvix dev anoma {start, stop, status}` commands to manage a running Anoma client. The motivation for this is that we can add additional commands (e.g `indexer`, `prove`, `(mempool)-submit`) which interact with the persistent Anoma client. `juvix dev anoma start` now writes a configuration file in `<juvix_config>/anoma-client/config.yaml` which contains the host URL and port of the started Anoma client and the pid of the Anoma client process. For example: config.yaml ``` host: port: 58922 url: localhost pid: 75299 ``` The `anoma stop` command kills the Anoma client and the `anoma status` command shows the config of the currently running client. There can be at most one Anoma client running when using this mechanism. ## Dependency This PR adds a new dependency on the `unix` package. This is used for APIs to send signals to processes. ## CLI docs ### `juvix dev anoma` ``` Usage: juvix dev anoma COMMAND Subcommands related to the Anoma client Available options: -h,--help Show this help text Available commands: start Start an Anoma client status Show the status of the Anoma client stop Stop the Anoma client ``` ### `juvix dev anoma start` ``` Usage: juvix dev anoma start --anoma-dir ANOMA_DIR [-g|--foreground] [-f|--force] Start an Anoma client Available options: --anoma-dir ANOMA_DIR Path to anoma repository -g,--foreground Start the client in the foreground -f,--force Forcefully start a client, terminating any currently running client if necessary -h,--help Show this help text ``` ### `juvix dev anoma status` ``` Usage: juvix dev anoma status Show the status of the Anoma client Available options: -h,--help Show this help text ``` ### `juvix dev anoma stop` ``` Usage: juvix dev anoma stop Stop the Anoma client Available options: -h,--help Show this help text ```
- Loading branch information
1 parent
c100812
commit 669474f
Showing
19 changed files
with
377 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
module Commands.Dev.Anoma.Client where | ||
|
||
import Anoma.Client.Config | ||
import Anoma.Effect.Base | ||
import Commands.Base | ||
import Data.Foldable.Extra qualified as E | ||
import Juvix.Prelude.Posix | ||
|
||
isClientRunning :: (Members '[Files, EmbedIO, Error SimpleError, Logger] r) => ClientConfig -> Sem r Bool | ||
isClientRunning c = | ||
runAnomaWithClient | ||
(c ^. clientConfigHost) | ||
(catchError @SimpleError (anomaListMethods >> return True) (\_ _ -> return False)) | ||
|
||
checkClientRunning :: (Members '[Logger, Files, EmbedIO, Error SimpleError] r) => Sem r (Maybe ClientConfig) | ||
checkClientRunning = do | ||
mconfig <- readConfig | ||
E.findM isClientRunning mconfig | ||
|
||
stopClient :: (Members '[Files, EmbedIO] r) => ClientConfig -> Sem r () | ||
stopClient = terminateProcessPid . (^. clientConfigPid) |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,47 @@ | ||
module Commands.Dev.Anoma.Options where | ||
|
||
import Commands.Dev.Anoma.Node.Options | ||
import Commands.Dev.Anoma.Start.Options | ||
import CommonOptions | ||
|
||
newtype AnomaCommand | ||
= AnomaCommandNode NodeOptions | ||
data AnomaCommand | ||
= AnomaCommandStart StartOptions | ||
| AnomaCommandStatus | ||
| AnomaCommandStop | ||
deriving stock (Data) | ||
|
||
parseAnomaCommand :: Parser AnomaCommand | ||
parseAnomaCommand = | ||
hsubparser | ||
( mconcat | ||
[commandNode] | ||
[ commandStart, | ||
commandStatus, | ||
commandStop | ||
] | ||
) | ||
where | ||
commandNode :: Mod CommandFields AnomaCommand | ||
commandNode = command "node" runInfo | ||
commandStart :: Mod CommandFields AnomaCommand | ||
commandStart = command "start" runInfo | ||
where | ||
runInfo :: ParserInfo AnomaCommand | ||
runInfo = | ||
info | ||
(AnomaCommandNode <$> parseNodeOptions) | ||
(progDesc "Run an Anoma node and client.") | ||
(AnomaCommandStart <$> parseStartOptions) | ||
(progDesc "Start an Anoma client") | ||
|
||
commandStatus :: Mod CommandFields AnomaCommand | ||
commandStatus = command "status" runInfo | ||
where | ||
runInfo :: ParserInfo AnomaCommand | ||
runInfo = | ||
info | ||
(pure AnomaCommandStatus) | ||
(progDesc "Show the status of the Anoma client") | ||
|
||
commandStop :: Mod CommandFields AnomaCommand | ||
commandStop = command "stop" runInfo | ||
where | ||
runInfo :: ParserInfo AnomaCommand | ||
runInfo = | ||
info | ||
(pure AnomaCommandStop) | ||
(progDesc "Stop the Anoma client") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
module Commands.Dev.Anoma.Start where | ||
|
||
import Anoma.Client.Config | ||
import Anoma.Effect | ||
import Commands.Base | ||
import Commands.Dev.Anoma.Client | ||
import Commands.Dev.Anoma.Start.Options | ||
import Juvix.Data.CodeAnn | ||
|
||
runCommand :: forall r. (Members AppEffects r) => StartOptions -> Sem r () | ||
runCommand opts = runAppError @SimpleError | ||
. runProcess | ||
$ case opts ^. startLaunchMode of | ||
LaunchModeAttached -> go >>= void . waitForProcess >> removeConfig | ||
LaunchModeDetached -> void go | ||
where | ||
go :: forall x. (Members (Process ': Error SimpleError ': AppEffects) x) => Sem x ProcessHandle | ||
go = do | ||
whenJustM checkClientRunning $ \config -> | ||
if | ||
| (opts ^. startForce) -> stopClient config | ||
| otherwise -> | ||
throw | ||
( SimpleError | ||
( mkAnsiText | ||
( "An Anoma client is already running" | ||
<> line | ||
<> line | ||
<> ppCodeAnn config | ||
) | ||
) | ||
) | ||
i <- startClient | ||
let processH = i ^. anomaClientLaunchInfoProcess . anomaProcessHandle | ||
mpid <- getPid processH | ||
case mpid of | ||
Just pid -> updateConfig pid (i ^. anomaClientLaunchInfoInfo) >> return processH | ||
Nothing -> throw (SimpleError "The Anoma client did not start sucessfully") | ||
where | ||
startClient :: Sem x AnomaClientLaunchInfo | ||
startClient = do | ||
let launchMode = opts ^. startLaunchMode | ||
anomaDir :: AnomaPath <- AnomaPath <$> fromAppPathDir (opts ^. startAnomaPath) | ||
launchAnomaClient launchMode anomaDir | ||
|
||
updateConfig :: Pid -> AnomaClientInfo -> Sem x () | ||
updateConfig pid clientInfo = | ||
writeConfig | ||
ClientConfig | ||
{ _clientConfigHost = clientInfo, | ||
_clientConfigPid = fromIntegral pid | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
module Commands.Dev.Anoma.Start.Options where | ||
|
||
import Anoma.Client.Base | ||
import CommonOptions | ||
|
||
data StartOptions = StartOptions | ||
{ _startAnomaPath :: AppPath Dir, | ||
_startLaunchMode :: LaunchMode, | ||
_startForce :: Bool | ||
} | ||
deriving stock (Data) | ||
|
||
makeLenses ''StartOptions | ||
|
||
parseStartOptions :: Parser StartOptions | ||
parseStartOptions = do | ||
_startAnomaPath <- anomaDirOpt | ||
_startLaunchMode <- | ||
flag | ||
LaunchModeDetached | ||
LaunchModeAttached | ||
( long "foreground" | ||
<> short 'g' | ||
<> help "Start the client in the foreground" | ||
) | ||
_startForce <- | ||
switch | ||
( long "force" | ||
<> short 'f' | ||
<> help "Forcefully start a client, terminating any currently running client if necessary" | ||
) | ||
pure StartOptions {..} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
module Commands.Dev.Anoma.Status where | ||
|
||
import Commands.Base | ||
import Commands.Dev.Anoma.Client | ||
import Juvix.Data.CodeAnn | ||
|
||
runCommand :: forall r. (Members AppEffects r) => Sem r () | ||
runCommand = runAppError @SimpleError $ do | ||
mconfig <- checkClientRunning | ||
case mconfig of | ||
Just config -> renderStdOutLn (ppCodeAnn config) | ||
Nothing -> logInfo "The Anoma client is not running" >> exitFailure |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
module Commands.Dev.Anoma.Stop where | ||
|
||
import Anoma.Client.Config | ||
import Commands.Base | ||
import Commands.Dev.Anoma.Client | ||
|
||
runCommand :: forall r. (Members AppEffects r) => Sem r () | ||
runCommand = runAppError @SimpleError $ do | ||
mconfig <- checkClientRunning | ||
case mconfig of | ||
Just config -> stopClient config >> removeConfig | ||
Nothing -> logInfo "The Anoma client is not running" >> exitFailure |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
module Anoma.Client.Base where | ||
|
||
import Data.Text qualified as T | ||
import Juvix.Data.CodeAnn | ||
import Juvix.Extra.Paths (anomaStartExs) | ||
import Juvix.Prelude | ||
import Juvix.Prelude.Aeson as Aeson | ||
|
||
data AnomaClientInfo = AnomaClientInfo | ||
{ _anomaClientInfoPort :: Int, | ||
_anomaClientInfoUrl :: String | ||
} | ||
|
||
$( deriveJSON | ||
Aeson.defaultOptions | ||
{ unwrapUnaryRecords = True, | ||
allowOmittedFields = False, | ||
rejectUnknownFields = True, | ||
fieldLabelModifier = \case | ||
"_anomaClientInfoUrl" -> "url" | ||
"_anomaClientInfoPort" -> "port" | ||
_ -> impossibleError "All fields must be covered" | ||
} | ||
''AnomaClientInfo | ||
) | ||
|
||
newtype AnomaPath = AnomaPath {_anomaPath :: Path Abs Dir} | ||
|
||
newtype AnomaProcess = AnomaProcess | ||
{ _anomaProcessHandle :: ProcessHandle | ||
} | ||
|
||
data AnomaClientLaunchInfo = AnomaClientLaunchInfo | ||
{ _anomaClientLaunchInfoInfo :: AnomaClientInfo, | ||
_anomaClientLaunchInfoProcess :: AnomaProcess | ||
} | ||
|
||
data LaunchMode | ||
= -- | Launch the client process attached to the parent | ||
LaunchModeAttached | ||
| -- | Launch the client process detached from the parent | ||
LaunchModeDetached | ||
deriving stock (Data) | ||
|
||
makeLenses ''AnomaClientInfo | ||
makeLenses ''AnomaPath | ||
makeLenses ''AnomaProcess | ||
makeLenses ''AnomaClientLaunchInfo | ||
|
||
anomaClientCreateProcess :: forall r. (Members '[Reader AnomaPath] r) => LaunchMode -> Sem r CreateProcess | ||
anomaClientCreateProcess launchMode = do | ||
p <- baseProc | ||
return $ case launchMode of | ||
LaunchModeAttached -> p | ||
LaunchModeDetached -> p {new_session = True, std_err = NoStream} | ||
where | ||
baseProc :: Sem r CreateProcess | ||
baseProc = do | ||
anomapath <- asks (^. anomaPath) | ||
return | ||
(proc "mix" ["run", "--no-halt", "-e", unpack (T.strip (decodeUtf8 anomaStartExs))]) | ||
{ std_out = CreatePipe, | ||
cwd = Just (toFilePath anomapath), | ||
std_in = NoStream | ||
} | ||
|
||
setupAnomaClientProcess :: (Members '[EmbedIO, Logger, Error SimpleError] r) => Handle -> Sem r AnomaClientInfo | ||
setupAnomaClientProcess nodeOut = do | ||
ln <- hGetLine nodeOut | ||
let parseError = throw (SimpleError (mkAnsiText ("Failed to parse the client grpc port when starting the anoma node and client.\nExpected a number but got " <> ln))) | ||
grpcPort :: Int <- either (const parseError) return . readEither . unpack $ ln | ||
logInfo "Anoma node and client successfully started" | ||
logInfo (mkAnsiText ("Listening on port " <> annotate AnnImportant (pretty grpcPort))) | ||
return | ||
( AnomaClientInfo | ||
{ _anomaClientInfoPort = grpcPort, | ||
_anomaClientInfoUrl = "localhost" | ||
} | ||
) | ||
|
||
launchAnomaClient :: (Members '[Logger, EmbedIO, Error SimpleError] r) => LaunchMode -> AnomaPath -> Sem r AnomaClientLaunchInfo | ||
launchAnomaClient launchMode anomapath = runReader anomapath . runProcess $ do | ||
cproc <- anomaClientCreateProcess launchMode | ||
(_mstdin, mstdout, _mstderr, procHandle) <- createProcess cproc | ||
let stdoutH = fromJust mstdout | ||
info <- setupAnomaClientProcess stdoutH | ||
hClose stdoutH | ||
return | ||
AnomaClientLaunchInfo | ||
{ _anomaClientLaunchInfoInfo = info, | ||
_anomaClientLaunchInfoProcess = AnomaProcess procHandle | ||
} |
Oops, something went wrong.