Stack.hs 9.84 KB
Newer Older
1
2
3
4
5
6
7
8
{-# LANGUAGE ScopedTypeVariables,
  LambdaCase,
  RecordWildCards,
  OverloadedStrings,
  DataKinds,
  FlexibleInstances,
  TypeOperators,
  ApplicativeDo #-}
9
10
11
12
13

module Argo.Stack where

import           Data.Default
import           Turtle
14
import           Turtle.Shell
15
16
17
18
19
20
import           Prelude                 hiding ( FilePath )

import           System.IO                      ( withFile )
import           Debug.Trace
import           Filesystem.Path                ( (</>) )
import           Control.Concurrent.Async
21
import           System.Console.ANSI
22
import           System.Console.ANSI.Types      ( Color )
23
import           Data.Text                     as T
24
25
26
                                         hiding ( empty )
import           Data.Text.IO                  as Text
import           Argo.Utils
27
28
import           System.Process                as P
                                         hiding ( shell )
29
import           Options.Applicative           as OA
30
import           Control.Monad.Extra           as E
31
32
33
34
35
36
37
38
import           Control.Monad                 as CM
import           Control.Foldl                 as F
import           Data.Conduit
import           Data.Conduit.Process
import           Data.ByteString.Char8         as C8
                                         hiding ( empty )
import           Control.Exception.Base
import           Data.Maybe
39
40

data StackArgs = StackArgs
41
  { app                  :: Text
42
  , args                 :: [Text]
43
  , containerName        :: Text
44
45
46
47
48
49
50
51
  , workingDirectory     :: FilePath
  , manifestDir          :: FilePath
  , manifestName         :: FilePath
  , cmd_out              :: FilePath
  , cmd_err              :: FilePath
  , daemon_out           :: FilePath
  , daemon_err           :: FilePath
  , nrm_log              :: FilePath
52
53
54
55
56
  , messageDaemonOut     :: Maybe Text
  , messageDaemonErr     :: Maybe Text
  , messageCmdOut        :: Maybe Text
  , messageCmdErr        :: Maybe Text
  }
57
58
59

instance Default StackArgs where
  def = StackArgs
60
61
    { app = "echo"
    , args = ["foobar"]
62
    , containerName = "testContainer"
63
64
65
    , workingDirectory = "_output"
    , manifestDir = "manifests"
    , manifestName = "basic.json"
66
67
68
69
70
    , cmd_out = "cmd_out.log"
    , cmd_err = "cmd_err.log"
    , daemon_out = "daemon_out.log"
    , daemon_err = "daemon_err.log"
    , nrm_log = "nrm.log"
71
72
73
74
    , messageDaemonOut  = Nothing
    , messageDaemonErr  = Nothing
    , messageCmdOut = Nothing
    , messageCmdErr = Nothing
75
76
    }

77
78
79
80
parseExtendStackArgs :: StackArgs -> Parser StackArgs
parseExtendStackArgs StackArgs {..} = do
  app <- strOption
    (  long "application"
81
    <> metavar "APP"
82
    <> help "Target application executable name. PATH is inherited."
83
84
85
    <> showDefault
    <> value app
    )
86
87
88
89
90
91
92
  containerName <- strOption
    (  long "container_name"
    <> metavar "ARGO_CONTAINER_UUID"
    <> help "Container name"
    <> showDefault
    <> value containerName
    )
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
  workingDirectory <- strOption
    (  long "output"
    <> metavar "FILE"
    <> help "Working directory."
    <> showDefault
    <> value workingDirectory
    )
  manifestDir <- strOption
    (  long "manifest_directory"
    <> metavar "FILE"
    <> help "Manifest lookup directory"
    <> showDefault
    <> value manifestDir
    )
  manifestName <- strOption
    (  long "manifest_name"
    <> metavar "FILE"
    <> help "Manifest basename"
    <> showDefault
    <> value manifestName
    )
  cmd_out <- strOption
    (  long "cmd_out"
    <> metavar "FILE"
    <> help "Output file, application stdout"
    <> showDefault
    <> value cmd_out
    )
  cmd_err <- strOption
    (  long "cmd_err"
    <> metavar "FILE"
    <> help "Output file, application stderr"
    <> showDefault
    <> value cmd_err
    )
  daemon_out <- strOption
    (  long "daemon_out"
    <> metavar "FILE"
    <> help "Output file, daemon stdout"
    <> showDefault
    <> value daemon_out
    )
  daemon_err <- strOption
    (  long "daemon_err"
    <> metavar "FILE"
    <> help "Output file, daemon stderr"
    <> showDefault
    <> value daemon_err
    )
  nrm_log <- strOption
    (  long "nrm_log"
    <> metavar "FILE"
    <> help "Output file, daemon log"
    <> showDefault
    <> value nrm_log
    )
149
150
151
152
153
154
155
  messageDaemonOut <- optional $ strOption
    (  long "message_daemon_stdout"
    <> metavar "STRING"
    <> help
         "The appearance of this character string in the daemon stdout \
            \ will be monitored during execution and the stack will be \
            \ killed when observing it, returning a successful exit code."
156
    <> showDefault
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
    <> maybe mempty value messageDaemonOut
    )
  messageDaemonErr <- optional $ strOption
    (  long "message_daemon_stdout"
    <> metavar "STRING"
    <> help
         "The appearance of this character string in the daemon stderr \
            \ will be monitored during execution and the stack will be \
            \ killed when observing it, returning a successful exit code."
    <> showDefault
    <> maybe mempty value messageDaemonErr
    )
  messageCmdOut <- optional $ strOption
    (  long "message_daemon_stdout"
    <> metavar "STRING"
    <> help
         "The appearance of this character string in the cmd stdout \
            \ will be monitored during execution and the stack will be \
            \ killed when observing it, returning a successful exit code."
    <> showDefault
    <> maybe mempty value messageCmdOut
    )
  messageCmdErr <- optional $ strOption
    (  long "message_daemon_stdout"
    <> metavar "STRING"
    <> help
         "The appearance of this character string in the cmd stderr \
            \ will be monitored during execution and the stack will be \
            \ killed when observing it, returning a successful exit code."
    <> showDefault
    <> maybe mempty value messageCmdErr
188
189
190
    )
  pure StackArgs {..}

191
192
193
194
195
196
197
198
cleanLeftoverProcesses :: Shell ()
cleanLeftoverProcesses = do
  printInfo "Cleaning leftover processes.\n"
  daemon <- myWhich "daemon"
  verboseShell (format ("pkill " % fp) daemon) empty
  cmd <- myWhich "cmd"
  void $ verboseShell (format ("pkill " % fp) cmd) empty
  daemon_wrapped <- myWhichMaybe ".daemon-wrapped"
199
200
  E.whenJust daemon_wrapped
             (\x -> void $ verboseShell "pkill .daemon-wrapped" empty)
201
  cmd_wrapped <- myWhichMaybe ".cmd-wrapped"
202
203
  void $ E.whenJust cmd_wrapped
                    (\x -> void $ verboseShell "pkill .cmd-wrapped" empty)
204

205
206
cleanLeftovers :: StackArgs -> Shell ()
cleanLeftovers StackArgs {..} = do
207
208
  cleanLeftoverProcesses
  printInfo "Cleaning leftover files.\n"
209
  CM.mapM_
210
    cleanLog
211
212
213
214
215
216
217
    [ workingDirectory </> daemon_out
    , workingDirectory </> daemon_err
    , workingDirectory </> cmd_out
    , workingDirectory </> cmd_err
    , workingDirectory </> nrm_log
    , workingDirectory </> ".argo_nodeos_config_exit_message"
    , workingDirectory </> "argo_nodeos_config"
218
    ]
219
  printInfo "Cleaning leftover sockets.\n"
220
  CM.mapM_ cleanSocket ["/tmp/nrm-downstream-in", "/tmp/nrm-upstream-in"]
221

222
223
224
prepareDaemon
  :: StackArgs -> Shell (IO (Either PatternMatched (ExitCode, (), ())))
prepareDaemon StackArgs {..} = do
225
226
  mktree workingDirectory
  cd workingDirectory
227
  myWhich "daemon"
228
229
  confPath <- myWhich "argo_nodeos_config"
  let confPath' = "./argo_nodeos_config"
230
231
232
  cp confPath confPath'
  printInfo $ format ("Copied the configurator to " % fp % "\n") confPath'
  printInfo $ format "Trying to sudo chown and chmod argo_nodeos_config\n"
233
  verboseShell (format ("sudo chown root:root " % fp) confPath') empty >>= \case
234
235
236
    ExitSuccess -> printInfo "Chowned argo_nodeos_config to root:root.\n"
    ExitFailure n ->
      die ("Failed to set argo_nodeos_config permissions " <> repr n)
237
  verboseShell (format ("sudo chmod u+sw " % fp) confPath') empty >>= \case
238
239
    ExitSuccess   -> printInfo "Set the suid bit.\n"
    ExitFailure n -> die ("Setting suid bit failed with exit code " <> repr n)
240
241
  verboseShell (format (fp % " --clean_config=kill_content:true") confPath')
               empty
242
243
244
245
246
247
248
249
250
251
252
    >>= \case
          ExitSuccess   -> printInfo "Cleaned the argo config.\n"
          ExitFailure n -> do
            printError
              ("argo_nodeos_config failed with exit code :" <> repr n <> "\n")
            testfile ".argo_nodeos_config_exit_message" >>= \case
              True -> do
                printInfo "Contents of .argo_nodeos_config_exit_message: \n"
                view $ input "./argo_nodeos_config_exit_message"
              False -> die ("Clean config failed with exit code " <> repr n)
  export "ARGO_NODEOS_CONFIG" (format fp confPath')
253
254
255
256
257
258
259
  makeInstrumentedProcess $ Instrumentation
    { process    = P.proc "daemon" ["--nrm_log", encodeString nrm_log]
    , stdOutFile = daemon_out
    , stdErrFile = daemon_err
    , messageOut = messageDaemonOut
    , messageErr = messageDaemonErr
    }
260

261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
prepareCmdRun
  :: StackArgs -> Shell (IO (Either PatternMatched (ExitCode, (), ())))
prepareCmdRun StackArgs {..} = makeInstrumentedProcess $ Instrumentation
  { process    = P.proc "cmd"
    $  [ "run"
       , "-u"
       , T.unpack containerName
       , encodeString $ manifestDir </> manifestName
       , T.unpack app
       ]
    ++ fmap T.unpack args
  , stdOutFile = cmd_out
  , stdErrFile = cmd_err
  , messageOut = messageCmdOut
  , messageErr = messageCmdErr
  }
277

278
279
280
data StackOutput = FoundMessage | DaemonDied | CmdDied
runSimpleStack :: StackArgs -> Shell StackOutput
runSimpleStack a@StackArgs {..} = do
281
  cleanLeftovers a
282
283
284
285
  instrumentedDaemon <- prepareDaemon a
  instrumentedCmd    <- prepareCmdRun a
  printInfo "Running the daemon."
  liftIO $ withAsync instrumentedDaemon $ \daemon -> do
286
    kbInstallHandler $ cancel daemon
287
288
    sh $ printInfo "Running cmd run."
    withAsync instrumentedCmd $ \cmd -> do
289
290
      kbInstallHandler $ cancel daemon >> cancel cmd
      waitEitherCancel daemon cmd >>= \case
291
292
293
294
        Left  (Left  PatternMatched) -> return FoundMessage
        Left  (Right _             ) -> return DaemonDied
        Right (Left  PatternMatched) -> return FoundMessage
        Right (Right _             ) -> return CmdDied