2module Control.Distributed.Process.Backend.Azure.GenericMain
7import Prelude hiding (catch)
8import System.Exit (exitSuccess, exitFailure)
18import Data.Foldable (forM_)
19import Data.Binary (decode)
20import qualified Data.ByteString.Lazy as BSL (ByteString, hGet, toChunks, length)
21import qualified Data.ByteString as BSS (hGet, length)
22import Control.Monad (unless, forM, void)
23import Control.Monad.Reader (ask)
24import Control.Exception (SomeException)
25import Control.Applicative ((<$>), (<*>), optional)
26import Control.Monad.IO.Class (liftIO)
27import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, readMVar)
30import qualified System.Posix.Process as Posix (forkProcess, createSession)
33import qualified Network.SSH.Client.LibSSH2.Foreign as SSH
38-- Command line options
39import Options.Applicative
58import Control.Distributed.Process
64import Control.Distributed.Process.Node
71import Control.Distributed.Process.Internal.Types
72 ( LocalProcess(processQueue)
76import Control.Distributed.Process.Internal.CQueue (CQueue, enqueue)
79import Control.Distributed.Process.Backend.Azure
80 ( AzureParameters(azureSshUserName, azureSetup)
81 , defaultAzureParameters
84 , VirtualMachine(vmName)
85 , Backend(findVMs, copyToVM, checkMD5, callOnVM, spawnOnVM)
92import Network.Transport.Internal (decodeInt32)
93import Network.Transport.TCP (createTransport, defaultTCPParameters)
95--------------------------------------------------------------------------------
97--------------------------------------------------------------------------------
99genericMain :: (RemoteTable -> RemoteTable) -- ^ Standard CH remote table
100 -> (String -> IO (ProcessPair ())) -- ^ Closures to support in 'run'
101 -> (String -> IO (RemoteProcess ())) -- ^ Closures to support in @run --background@
103genericMain remoteTable callable spawnable = do
104 _ <- SSH.initialize True
105 cmd <- execParser opts
108 params <- azureParameters (azureOptions cmd) Nothing
109 css <- cloudServices (azureSetup params)
112 params <- azureParameters (azureOptions cmd) (Just (sshOptions cmd))
113 backend <- initializeBackend params (targetService (target cmd))
114 vms <- findMatchingVMs backend (targetVM (target cmd))
115 forM_ vms $ \vm -> do
116 putStr (vmName vm ++ ": ") >> hFlush stdout
120 params <- azureParameters (azureOptions cmd) (Just (sshOptions cmd))
121 backend <- initializeBackend params (targetService (target cmd))
122 vms <- findMatchingVMs backend (targetVM (target cmd))
123 matches <- forM vms $ \vm -> do
124 unless (status cmd) $ putStr (vmName vm ++ ": ") >> hFlush stdout
125 match <- checkMD5 backend vm
126 unless (status cmd) $ putStrLn $ if match then "OK" else "FAILED"
131 RunOn {} | background cmd -> do
132 params <- azureParameters (azureOptions cmd) (Just (sshOptions cmd))
133 backend <- initializeBackend params (targetService (target cmd))
134 vms <- findMatchingVMs backend (targetVM (target cmd))
135 rProc <- spawnable (closureId cmd)
136 forM_ vms $ \vm -> do
137 putStr (vmName vm ++ ": ") >> hFlush stdout
138 spawnOnVM backend vm (remotePort cmd) rProc
140 RunOn {} {- not (background cmd) -} -> do
141 params <- azureParameters (azureOptions cmd) (Just (sshOptions cmd))
142 backend <- initializeBackend params (targetService (target cmd))
143 vms <- findMatchingVMs backend (targetVM (target cmd))
144 procPair <- callable (closureId cmd)
145 forM_ vms $ \vm -> do
146 putStr (vmName vm ++ ": ") >> hFlush stdout
147 callOnVM backend vm (remotePort cmd) procPair
148 OnVmCommand (vmCmd@OnVmRun {}) ->
149 onVmRun (remoteTable initRemoteTable)
153 (onVmBackground vmCmd)
156 opts = info (helper <*> commandParser)
158 & header "Cloud Haskell backend for Azure"
161findMatchingVMs :: Backend -> Maybe String -> IO [VirtualMachine]
162findMatchingVMs backend Nothing = findVMs backend
163findMatchingVMs backend (Just vm) = filter ((== vm) . vmName) `fmap` findVMs backend
165azureParameters :: AzureOptions -> Maybe SshOptions -> IO AzureParameters
166azureParameters opts Nothing =
167 defaultAzureParameters (subscriptionId opts)
170azureParameters opts (Just sshOpts) = do
171 params <- defaultAzureParameters (subscriptionId opts)
175 azureSshUserName = remoteUser sshOpts
178--------------------------------------------------------------------------------
179-- Executing a closure on the VM --
180--------------------------------------------------------------------------------
182onVmRun :: RemoteTable -> String -> String -> String -> Bool -> IO ()
183onVmRun rtable host port cloudService bg = do
184 hSetBinaryMode stdin True
185 hSetBinaryMode stdout True
186 Just procEnc <- getWithLength stdin
187 Just paramsEnc <- getWithLength stdin
188 backend <- initializeBackend (decode paramsEnc) cloudService
189 let proc = decode procEnc
190 lprocMVar <- newEmptyMVar :: IO (MVar LocalProcess)
192 then detach $ startCH proc lprocMVar backend runProcess (\_ -> return ())
194 startCH proc lprocMVar backend forkProcess (liftIO . remoteThrow)
195 lproc <- readMVar lprocMVar
196 queueFromHandle stdin (processQueue lproc)
198 startCH :: RemoteProcess ()
201 -> (LocalNode -> Process () -> IO a)
202 -> (SomeException -> Process ())
204 startCH rproc lprocMVar backend go exceptionHandler = do
205 mTransport <- createTransport host port defaultTCPParameters
207 Left err -> remoteThrow err
208 Right transport -> do
209 node <- newLocalNode transport rtable
211 ask >>= liftIO . putMVar lprocMVar
212 proc <- unClosure rproc :: Process (Backend -> Process ())
213 catch (proc backend) exceptionHandler
215-- | Read a 4-byte length @l@ and then an @l@-byte payload
217-- Returns Nothing on EOF
218getWithLength :: Handle -> IO (Maybe BSL.ByteString)
220 lenEnc <- BSS.hGet h 4
221 if BSS.length lenEnc < 4
224 let len = decodeInt32 lenEnc
226 if BSL.length bs < fromIntegral len
228 else return (Just bs)
230queueFromHandle :: Handle -> CQueue Message -> IO ()
231queueFromHandle h q = do
232 mPayload <- getWithLength stdin
233 forM_ mPayload $ \payload -> do
234 enqueue q $ payloadToMessage (BSL.toChunks payload)
237detach :: IO () -> IO ()
239 mapM_ hClose [stdin, stdout, stderr]
240 void . Posix.forkProcess $ void Posix.createSession >> io
242--------------------------------------------------------------------------------
243-- Command line options --
244--------------------------------------------------------------------------------
246data AzureOptions = AzureOptions {
247 subscriptionId :: String
248 , pathToCert :: FilePath
249 , pathToKey :: FilePath
253data SshOptions = SshOptions {
258data Target = Target {
259 targetService :: String
260 , targetVM :: Maybe String
266 azureOptions :: AzureOptions
269 azureOptions :: AzureOptions
270 , sshOptions :: SshOptions
274 azureOptions :: AzureOptions
275 , sshOptions :: SshOptions
280 azureOptions :: AzureOptions
281 , sshOptions :: SshOptions
283 , remotePort :: String
284 , closureId :: String
288 _onVmCommand :: OnVmCommand
296 , onVmService :: String
297 , onVmBackground :: Bool
301azureOptionsParser :: Parser AzureOptions
302azureOptionsParser = AzureOptions
303 <$> strOption ( long "subscription-id"
305 & help "Azure subscription ID"
307 <*> strOption ( long "certificate"
309 & help "X509 certificate"
311 <*> strOption ( long "private"
313 & help "Private key in PKCS#1 format"
316sshOptionsParser :: Parser SshOptions
317sshOptionsParser = SshOptions
318 <$> strOption ( long "user"
320 & help "Remove SSH username"
323listParser :: Parser Command
324listParser = List <$> azureOptionsParser
326copyToParser :: Parser Command
328 <$> azureOptionsParser
332targetParser :: Parser Target
334 <$> strOption ( long "cloud-service"
336 & help "Cloud service name"
338 <*> optional (strOption ( long "virtual-machine"
340 & help "Virtual machine name (all VMs if unspecified)"
343checkMD5Parser :: Parser Command
344checkMD5Parser = CheckMD5
345 <$> azureOptionsParser
348 <*> switch ( long "status"
349 & help "Don't output anything, status code shows success"
352commandParser :: Parser Command
353commandParser = subparser
354 ( command "list" (info listParser
355 (progDesc "List Azure cloud services"))
356 & command "install" (info copyToParser
357 (progDesc "Install the executable"))
358 & command "md5" (info checkMD5Parser
359 (progDesc "Check if the remote and local MD5 hash match"))
360 & command "run" (info runOnParser
361 (progDesc "Run the executable"))
362 & command "onvm" (info onVmCommandParser
363 (progDesc "Commands used when running ON the vm (usually used internally only)"))
366runOnParser :: Parser Command
368 <$> azureOptionsParser
371 <*> strOption ( long "port"
373 & help "Port number of the CH instance"
375 <*> strOption ( long "closure"
377 & help "Process to run on the CH instance"
379 <*> switch ( long "background"
380 & help "Run the process in the background"
383onVmRunParser :: Parser OnVmCommand
384onVmRunParser = OnVmRun
385 <$> strOption ( long "host"
389 <*> strOption ( long "port"
393 <*> strOption ( long "cloud-service"
395 & help "Cloud service name"
397 <*> switch ( long "background"
398 & help "Run the process in the background"
401onVmCommandParser :: Parser Command
402onVmCommandParser = OnVmCommand <$> subparser
403 ( command "run" (info onVmRunParser
404 (progDesc "Run the executable"))
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list