@@ -1,27 +1,29 @@
1{-# LANGUAGE Arrows #-}
1{-# LANGUAGE Arrows #-}
2module Network.Azure.ServiceManagement
2module Network.Azure.ServiceManagement
3 ( -- * Data types
3 ( -- * Data types
4− HostedService
4+ HostedService(..)
6 , AzureSetup(..)
8 , AzureSetup(..)
7 , azureSetup
9 , azureSetup
8 -- * High-level API
10 -- * High-level API
9 , hostedServices
11 , hostedServices
10− , hostedServiceProperties
12+ , virtualMachines
16−import Prelude hiding (id)
15+import Prelude hiding (id, (.))
17−import Control.Category (id, (>>>))
16+import Control.Category (id, (>>>), (.))
18import Control.Arrow (arr)
17import Control.Arrow (arr)
19−import Data.ByteString.Lazy (ByteString)
18+import Control.Monad (forM)
20import Data.ByteString.Char8 as BSC (pack)
19import Data.ByteString.Char8 as BSC (pack)
21import Data.ByteString.Lazy.Char8 as BSLC (unpack)
20import Data.ByteString.Lazy.Char8 as BSLC (unpack)
22import Network.TLS (PrivateKey)
21import Network.TLS (PrivateKey)
23import Network.TLS.Extra (fileReadCertificate, fileReadPrivateKey)
22import Network.TLS.Extra (fileReadCertificate, fileReadPrivateKey)
24import Data.Certificate.X509 (X509)
23import Data.Certificate.X509 (X509)
24+import Control.Monad.Trans.Resource (ResourceT)
25+import Control.Monad.IO.Class (liftIO)
26+import Control.Arrow.ArrowList (listA)
25import Network.HTTP.Conduit
27import Network.HTTP.Conduit
26 ( parseUrl
28 ( parseUrl
27 , clientCertificates
29 , clientCertificates
@@ -29,6 +31,7 @@ import Network.HTTP.Conduit
29 , withManager
31 , withManager
30 , Response(Response)
32 , Response(Response)
33import Data.CaseInsensitive as CI (mk)
36import Data.CaseInsensitive as CI (mk)
34import Text.XML.HXT.Core
37import Text.XML.HXT.Core
@@ -39,23 +42,34 @@ import Text.XML.HXT.Core
40 , IOSArrow
43 , IOSArrow
41 , ArrowXml
44 , ArrowXml
45− -- FOR DEBUGGING ONLY
47+import Text.XML.HXT.XPath (getXPathTrees)
51--------------------------------------------------------------------------------
49--------------------------------------------------------------------------------
52-- Data types --
50-- Data types --
53--------------------------------------------------------------------------------
51--------------------------------------------------------------------------------
55−newtype HostedService = HostedService { unHostedService :: String }
53+data HostedService = HostedService {
54+ hostedServiceName :: String
57instance Show HostedService where
57instance Show HostedService where
58− show = unHostedService
58+ show = hostedServiceName
64+instance Show Role where
67+data Endpoint = Endpoint {
68+ endpointName :: String
69+ , endpointPort :: String
70+ , endpointVip :: String
60--------------------------------------------------------------------------------
74--------------------------------------------------------------------------------
61-- Setup --
75-- Setup --
@@ -66,7 +80,7 @@ data AzureSetup = AzureSetup
66 { subscriptionId :: String
80 { subscriptionId :: String
67 , certificate :: X509
81 , certificate :: X509
68 , privateKey :: PrivateKey
82 , privateKey :: PrivateKey
69− , baseURL :: String
83+ , baseUrl :: String
72-- | Initialize Azure
86-- | Initialize Azure
@@ -81,67 +95,73 @@ azureSetup sid certPath pkeyPath = do
81 subscriptionId = sid
95 subscriptionId = sid
82 , certificate = cert
96 , certificate = cert
83 , privateKey = pkey
97 , privateKey = pkey
84− , baseURL = "https://management.core.windows.net"
98+ , baseUrl = "https://management.core.windows.net"
87--------------------------------------------------------------------------------
101--------------------------------------------------------------------------------
88-- High-level API --
102-- High-level API --
89--------------------------------------------------------------------------------
103--------------------------------------------------------------------------------
105+-- | Get a list of virtual machines
106+virtualMachines :: AzureSetup -> IO [(HostedService, [(Role, [Endpoint])])]
107+virtualMachines setup = azureExecute setup $ \exec -> do
108+ services <- exec hostedServicesRequest
109+ forM services $ \service -> do
110+ roles <- exec AzureRequest {
111+ relativeUrl = "/services/hostedservices/" ++ hostedServiceName service
112+ ++ "?embed-detail=true"
113+ , apiVersion = "2012-03-01"
114+ , parser = proc xml -> do
115+ role <- getXPathTrees "//Role[@type='PersistentVMRole']" -< xml
116+ name <- getText . getXPathTrees "/Role/RoleName/text()" -< role
117+ endpoints <- listA (parseEndpoint . getXPathTrees "//InputEndpoint") -< role
118+ id -< (Role name, endpoints)
120+ return (service, roles)
91-- | Get list of available hosted services
122-- | Get list of available hosted services
92hostedServices :: AzureSetup -> IO [HostedService]
123hostedServices :: AzureSetup -> IO [HostedService]
93−hostedServices setup =
124+hostedServices setup = azureExecute setup (\exec -> exec hostedServicesRequest)
94− azureRequest setup subURL "2012-03-01" $ proc xml -> do
95− writeDocument [withIndent yes] "hostedservices.xml" -< xml
96− hostedService <- deep (hasName "HostedService") -< xml
97− name <- getTextNode "ServiceName" -< hostedService
98− arr HostedService -< name
100− subURL = "/services/hostedservices"
102−-- | Get properties of the given hosted service
103−hostedServiceProperties :: AzureSetup -> HostedService -> IO [()]
104−hostedServiceProperties setup (HostedService service) = do
105− azureRequest setup subURL "2012-03-01" $ proc xml -> do
106− writeDocument [withIndent yes] "properties.xml" -< xml
109− subURL = "/services/hostedservices/" ++ service ++ "?embed-detail=true"
111−--------------------------------------------------------------------------------
126+hostedServicesRequest :: AzureRequest HostedService
112−-- Low-level API --
127+hostedServicesRequest = AzureRequest
113−--------------------------------------------------------------------------------
128+ { relativeUrl = "/services/hostedservices"
129+ , apiVersion = "2012-03-01"
130+ , parser = arr HostedService
132+ . getXPathTrees "//ServiceName/text()"
115−-- | Execute an Azure request and parse the XML data
135+parseEndpoint :: ArrowXml t => t XmlTree Endpoint
116−azureRequest :: AzureSetup
136+parseEndpoint = proc endpoint -> do
117− -> String
137+ name <- getText . getXPathTrees "//Name/text()" -< endpoint
118− -> String
138+ port <- getText . getXPathTrees "//Port/text()" -< endpoint
119− -> IOSArrow XmlTree c
139+ vip <- getText . getXPathTrees "//Vip/text()" -< endpoint
120− -> IO [c]
140+ id -< Endpoint name port vip
121−azureRequest setup subURL version f = do
122− raw <- azureRawRequest setup subURL version
123− runX $ readString [withValidate no] (BSLC.unpack raw) >>> f
125−-- | Execute an Azure request and return the raw data
126−azureRawRequest :: AzureSetup
127− -> String -- ^ Relative path
128− -> String -- ^ Version
130−azureRawRequest setup subURL version = do
131− req <- parseUrl $ baseURL setup ++ "/" ++ subscriptionId setup ++ "/" ++ subURL
132− withManager $ \manager -> do
134− clientCertificates = [ (certificate setup, Just $ privateKey setup) ]
135− , requestHeaders = [ (CI.mk $ BSC.pack "x-ms-version", BSC.pack version)
136− , (CI.mk $ BSC.pack "content-type", BSC.pack "application/xml")
139− Response _ _ _ lbs <- httpLbs req' manager
142--------------------------------------------------------------------------------
142--------------------------------------------------------------------------------
143−-- XML convenience functions --
143+-- Low-level API --
144--------------------------------------------------------------------------------
144--------------------------------------------------------------------------------
146−getTextNode :: ArrowXml a => String -> a XmlTree String
146+data AzureRequest c = AzureRequest {
147−getTextNode node = deep (hasName node) >>> deep getText
147+ relativeUrl :: String
148+ , apiVersion :: String
149+ , parser :: IOSArrow XmlTree c
152+azureExecute :: AzureSetup -> ((forall b. AzureRequest b -> ResourceT IO [b]) -> ResourceT IO a) -> IO a
153+azureExecute setup f = withManager (\manager -> f (go manager))
155+ go :: Manager -> forall b. AzureRequest b -> ResourceT IO [b]
156+ go manager request = do
157+ req <- parseUrl $ baseUrl setup
158+ ++ "/" ++ subscriptionId setup
159+ ++ "/" ++ relativeUrl request
161+ clientCertificates = [ (certificate setup, Just $ privateKey setup) ]
162+ , requestHeaders = [ (CI.mk $ BSC.pack "x-ms-version", BSC.pack $ apiVersion request)
163+ , (CI.mk $ BSC.pack "content-type", BSC.pack "application/xml")
166+ Response _ _ _ lbs <- httpLbs req' manager
167+ liftIO . runX $ readString [withValidate no] (BSLC.unpack lbs) >>> parser request