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)
93import Network.Transport.Internal (decodeInt32)
94import Network.Transport.TCP (createTransport, defaultTCPParameters)
96--------------------------------------------------------------------------------
98--------------------------------------------------------------------------------
100genericMain :: (RemoteTable -> RemoteTable) -- ^ Standard CH remote table
101 -> (String -> IO (ProcessPair ())) -- ^ Closures to support in 'run'
102 -> (String -> IO (RemoteProcess ())) -- ^ Closures to support in @run --background@
104genericMain remoteTable callable spawnable = do
105 _ <- SSH.initialize True
106 cmd <- execParser opts
109 params <- azureParameters (azureOptions cmd) Nothing
110 css <- cloudServices (azureSetup params)
113 params <- azureParameters (azureOptions cmd) (Just (sshOptions cmd))
114 backend <- initializeBackend params (targetService (target cmd))
115 vms <- findMatchingVMs backend (targetVM (target cmd))
116 forM_ vms $ \vm -> do
117 putStr (vmName vm ++ ": ") >> hFlush stdout
121 params <- azureParameters (azureOptions cmd) (Just (sshOptions cmd))
122 backend <- initializeBackend params (targetService (target cmd))
123 vms <- findMatchingVMs backend (targetVM (target cmd))
124 matches <- forM vms $ \vm -> do
125 unless (status cmd) $ putStr (vmName vm ++ ": ") >> hFlush stdout
126 match <- checkMD5 backend vm
127 unless (status cmd) $ putStrLn $ if match then "OK" else "FAILED"
132 RunOn {} | background cmd -> do
133 params <- azureParameters (azureOptions cmd) (Just (sshOptions cmd))
134 backend <- initializeBackend params (targetService (target cmd))
135 vms <- findMatchingVMs backend (targetVM (target cmd))
136 rProc <- spawnable (closureId cmd)
137 forM_ vms $ \vm -> do
138 putStr (vmName vm ++ ": ") >> hFlush stdout
139 spawnOnVM backend vm (remotePort cmd) rProc
141 RunOn {} {- not (background cmd) -} -> do
142 params <- azureParameters (azureOptions cmd) (Just (sshOptions cmd))
143 backend <- initializeBackend params (targetService (target cmd))
144 vms <- findMatchingVMs backend (targetVM (target cmd))
145 procPair <- callable (closureId cmd)
146 forM_ vms $ \vm -> do
147 putStr (vmName vm ++ ": ") >> hFlush stdout
148 callOnVM backend vm (remotePort cmd) procPair
149 OnVmCommand (vmCmd@OnVmRun {}) ->
150 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 -> Bool -> IO ()
183onVmRun rtable host port bg = do
184 hSetBinaryMode stdin True
185 hSetBinaryMode stdout True
186 mProcEnc <- getWithLength stdin
187 forM_ mProcEnc $ \procEnc -> do
188 let proc = decode procEnc
189 lprocMVar <- newEmptyMVar :: IO (MVar LocalProcess)
191 then detach $ startCH proc lprocMVar runProcess (\_ -> return ())
193 startCH proc lprocMVar forkProcess remoteThrow
194 lproc <- readMVar lprocMVar
195 queueFromHandle stdin (processQueue lproc)
197 startCH :: RemoteProcess ()
199 -> (LocalNode -> Process () -> IO a)
200 -> (SomeException -> Process ())
202 startCH rproc lprocMVar go exceptionHandler = do
203 mTransport <- createTransport host port defaultTCPParameters
205 Left err -> remoteSend' 1 (show err)
206 Right transport -> do
207 node <- newLocalNode transport rtable
209 ask >>= liftIO . putMVar lprocMVar
210 let backend = error "TODO: backend not initialized in onVmRun"
211 proc <- unClosure rproc :: Process (Backend -> Process ())
212 catch (proc backend) exceptionHandler
214-- | Read a 4-byte length @l@ and then an @l@-byte payload
216-- Returns Nothing on EOF
217getWithLength :: Handle -> IO (Maybe BSL.ByteString)
219 lenEnc <- BSS.hGet h 4
220 if BSS.length lenEnc < 4
223 let len = decodeInt32 lenEnc
225 if BSL.length bs < fromIntegral len
227 else return (Just bs)
229queueFromHandle :: Handle -> CQueue Message -> IO ()
230queueFromHandle h q = do
231 mPayload <- getWithLength stdin
232 forM_ mPayload $ \payload -> do
233 enqueue q $ payloadToMessage (BSL.toChunks payload)
236detach :: IO () -> IO ()
238 mapM_ hClose [stdin, stdout, stderr]
239 void . Posix.forkProcess $ void Posix.createSession >> io
241--------------------------------------------------------------------------------
242-- Command line options --
243--------------------------------------------------------------------------------
245data AzureOptions = AzureOptions {
246 subscriptionId :: String
247 , pathToCert :: FilePath
248 , pathToKey :: FilePath
252data SshOptions = SshOptions {
257data Target = Target {
258 targetService :: String
259 , targetVM :: Maybe String
265 azureOptions :: AzureOptions
268 azureOptions :: AzureOptions
269 , sshOptions :: SshOptions
273 azureOptions :: AzureOptions
274 , sshOptions :: SshOptions
279 azureOptions :: AzureOptions
280 , sshOptions :: SshOptions
282 , remotePort :: String
283 , closureId :: String
287 _onVmCommand :: OnVmCommand
295 , onVmBackground :: Bool
299azureOptionsParser :: Parser AzureOptions
300azureOptionsParser = AzureOptions
301 <$> strOption ( long "subscription-id"
303 & help "Azure subscription ID"
305 <*> strOption ( long "certificate"
307 & help "X509 certificate"
309 <*> strOption ( long "private"
311 & help "Private key in PKCS#1 format"
314sshOptionsParser :: Parser SshOptions
315sshOptionsParser = SshOptions
316 <$> strOption ( long "user"
318 & help "Remove SSH username"
321listParser :: Parser Command
322listParser = List <$> azureOptionsParser
324copyToParser :: Parser Command
326 <$> azureOptionsParser
330targetParser :: Parser Target
332 <$> strOption ( long "cloud-service"
334 & help "Cloud service name"
336 <*> optional (strOption ( long "virtual-machine"
338 & help "Virtual machine name (all VMs if unspecified)"
341checkMD5Parser :: Parser Command
342checkMD5Parser = CheckMD5
343 <$> azureOptionsParser
346 <*> switch ( long "status"
347 & help "Don't output anything, status code shows success"
350commandParser :: Parser Command
351commandParser = subparser
352 ( command "list" (info listParser
353 (progDesc "List Azure cloud services"))
354 & command "install" (info copyToParser
355 (progDesc "Install the executable"))
356 & command "md5" (info checkMD5Parser
357 (progDesc "Check if the remote and local MD5 hash match"))
358 & command "run" (info runOnParser
359 (progDesc "Run the executable"))
360 & command "onvm" (info onVmCommandParser
361 (progDesc "Commands used when running ON the vm (usually used internally only)"))
364runOnParser :: Parser Command
366 <$> azureOptionsParser
369 <*> strOption ( long "port"
371 & help "Port number of the CH instance"
373 <*> strOption ( long "closure"
375 & help "Process to run on the CH instance"
377 <*> switch ( long "background"
378 & help "Run the process in the background"
381onVmRunParser :: Parser OnVmCommand
382onVmRunParser = OnVmRun
383 <$> strOption ( long "host"
387 <*> strOption ( long "port"
391 <*> switch ( long "background"
392 & help "Run the process in the background"
395onVmCommandParser :: Parser Command
396onVmCommandParser = OnVmCommand <$> subparser
397 ( command "run" (info onVmRunParser
398 (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