diff --git a/.gitignore b/.gitignore index e6df8e1..42b3153 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ cabal.sandbox.config .stack-work/ cabal.project.local .HTF/ +ptl-pthread diff --git a/app/Main.hs b/app/Main.hs index 5975c68..d5cace7 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -6,6 +6,7 @@ import Bartlett.Types import qualified Bartlett.Configuration as C import qualified Bartlett.Actions.Info as AI import qualified Bartlett.Actions.Build as AB +import qualified Bartlett.Actions.Config as AC import qualified Bartlett.Parsers as P import Control.Exception @@ -74,6 +75,12 @@ executeCommand cmd usr jenkinsInstance = AI.getInfo usr jenkinsInstance jobPaths Build jobPath jobParameters -> AB.postBuild usr jenkinsInstance jobPath jobParameters + Config jobPath configFilePath -> + case configFilePath of + Just cp -> + AC.updateConfig usr jenkinsInstance jobPath cp + Nothing -> + AC.getConfig usr jenkinsInstance jobPath -- | Execute the appropriate sub-command given parsed cli options. run :: Options -> IO () diff --git a/bartlett.cabal b/bartlett.cabal index 3cc96d9..7816aad 100644 --- a/bartlett.cabal +++ b/bartlett.cabal @@ -1,5 +1,5 @@ name: bartlett -version: 1.0.1 +version: 1.1.0 synopsis: The Jenkins command-line tool to serve your needs. description: Please see README.md homepage: https://github.com/Nike-inc/bartlett @@ -20,7 +20,8 @@ library Bartlett.Network, Bartlett.Configuration, Bartlett.Actions.Info, - Bartlett.Actions.Build + Bartlett.Actions.Build, + Bartlett.Actions.Config ghc-options: -fwarn-tabs -fwarn-unused-imports diff --git a/src/Bartlett/Actions/Build.hs b/src/Bartlett/Actions/Build.hs index 0763a18..aafbd90 100644 --- a/src/Bartlett/Actions/Build.hs +++ b/src/Bartlett/Actions/Build.hs @@ -40,7 +40,7 @@ postBuild :: -> Maybe JobParameters -- ^ Optional set of job parameters to trigger with. -> IO () postBuild user base path parameters = do - resp <- execRequest "post" reqOpts reqUri + resp <- execRequest "post" reqOpts reqUri Nothing BL.putStrLn . encodePretty . BU.toResponseStatus $ resp ^. responseStatus where (suffix, buildOpts) = consBuildType parameters diff --git a/src/Bartlett/Actions/Config.hs b/src/Bartlett/Actions/Config.hs new file mode 100644 index 0000000..b2e9dde --- /dev/null +++ b/src/Bartlett/Actions/Config.hs @@ -0,0 +1,49 @@ +{-| +Module : Config +Description : Methods for executing config requests against Jenkins +Copyright : (c) Nike, Inc., 2016 +License : BSD3 +Maintainer : fernando.freire@nike.com +Stability : stable + +Methods for executing config requests against Jenkins. +-} +module Bartlett.Actions.Config where + +import Bartlett.Network (execRequest) +import Bartlett.Types +import Bartlett.Util (toResponseStatus, mkUrl) + +import Control.Lens ((^.), (?~), (&)) +import Data.Aeson.Encode.Pretty (encodePretty) +import qualified Data.ByteString.Lazy.Char8 as BL +import Network.Wreq (responseStatus, responseBody, defaults, auth) + +-- | Construct a URL to interact with Job configurations. +configUri :: JenkinsInstance -> JobPath -> BL.ByteString +configUri base path = + mkUrl base path "/config.xml" + +-- | Retrieve the XML configuration for the given job. +getConfig :: BasicAuthUser a => + a -- The user to authenticate with. + -> JenkinsInstance -- The Jenkins instance to interact with. + -> JobPath -- The Job for the given Jenkins instance to interact with. + -> IO () -- The XML configuration for the given job. +getConfig user base path = do + resp <- execRequest "get" reqOpts (configUri base path) Nothing + BL.putStrLn $ resp ^. responseBody + where reqOpts = defaults & auth ?~ getBasicAuth user + +-- | Update the XML configuration for the given job. +updateConfig :: BasicAuthUser a => + a -- The user to authenticate with. + -> JenkinsInstance -- The Jenkins instance to interact with. + -> JobPath -- The Job for the given Jenkins instance to interact with. + -> ConfigPath -- Path to the XML configuration to upload to Jenkins. + -> IO () +updateConfig user base path configPath = do + configFile <- BL.readFile configPath + resp <- execRequest "post" reqOpts (configUri base path) (Just configFile) + BL.putStrLn . encodePretty . toResponseStatus $ resp ^. responseStatus + where reqOpts = defaults & auth ?~ getBasicAuth user diff --git a/src/Bartlett/Actions/Info.hs b/src/Bartlett/Actions/Info.hs index 51201df..f509dc0 100644 --- a/src/Bartlett/Actions/Info.hs +++ b/src/Bartlett/Actions/Info.hs @@ -33,7 +33,7 @@ getInfo :: -> IO () getInfo user base [] = return () getInfo user base (path:paths) = do - resp <- execRequest "get" reqOpts reqUri + resp <- execRequest "get" reqOpts reqUri Nothing BL.putStrLn . toPrettyJson $ resp ^. responseBody getInfo user base paths where reqOpts = defaults & auth ?~ getBasicAuth user diff --git a/src/Bartlett/Network.hs b/src/Bartlett/Network.hs index e0aaf09..7843aa0 100644 --- a/src/Bartlett/Network.hs +++ b/src/Bartlett/Network.hs @@ -22,28 +22,36 @@ import Bartlett.Util (toResponseStatus, withForcedSSL) import qualified Control.Exception as E import Data.Aeson.Encode.Pretty (encodePretty) import Data.ByteString.Lazy.Char8 (ByteString, unpack) +import Data.Maybe (fromMaybe) import qualified Network.HTTP.Client as NHC import System.Exit (die) -import Network.Wreq (Options, Response, customMethodWith) +import Network.Wreq (Options, Response) +import qualified Network.Wreq.Session as S -- | General request handler that provides basic error handling. execRequest :: ByteString -- ^ The type of request to make (e.g. "get") -> Options -- ^ Request params to pass along with the request. -> ByteString -- ^ The uri to make the request to + -> Maybe ByteString -- ^ The file to upload to the Jenkins instance. -> IO (Response ByteString) -execRequest requestType opts reqUrl = - case requestType of - "post" -> - mkRequest "post" reqUrl - `E.catch` - recoverableErrorHandler (mkRequest "post" $ withForcedSSL reqUrl) - "get" -> - mkRequest "get" reqUrl - `E.catch` - recoverableErrorHandler (mkRequest "get" $ withForcedSSL reqUrl) - - where mkRequest method url = customMethodWith method opts (unpack url) +execRequest requestType opts reqUrl postBody = + S.withAPISession $ \session -> + case requestType of + -- TODO Need to get a CSRF crumb + -- JENKINS_URL/crumbIssuer/api/json?xpath=?xpath=concat(//crumbRequestField,":",//crumb)') + -- TODO create a proper sum type for requestType + "post" -> + postSession reqUrl + `E.catch` + recoverableErrorHandler (postSession $ withForcedSSL reqUrl) + where fileToUpload = fromMaybe "" postBody :: ByteString + postSession url = S.postWith opts session (unpack url) fileToUpload + "get" -> + getSession reqUrl + `E.catch` + recoverableErrorHandler (getSession $ withForcedSSL reqUrl) + where getSession url = S.getWith opts session (unpack url) -- | Handler that returns a JSON representation of the error status. diff --git a/src/Bartlett/Parsers.hs b/src/Bartlett/Parsers.hs index 304f528..823fadd 100644 --- a/src/Bartlett/Parsers.hs +++ b/src/Bartlett/Parsers.hs @@ -54,6 +54,12 @@ parseJobParameters = option readerByteString $ short 'o' <> long "options" <> metavar "OPTIONS" <> help "Comma separated list of key=value pairs to pass to the job" +-- | Parse a path to the config file to update. +parseConfigFilePath :: Parser ConfigPath +parseConfigFilePath = option str $ + short 'f' <> long "filepath" <> metavar "CONFIG_FILE_PATH" <> + help "Path to the job configuration to upload" + -- | Parse an Info sub-command. parseInfo :: Parser Command parseInfo = Info <$> some (argument readerByteString (metavar "JOB_PATHS...")) @@ -64,11 +70,18 @@ parseBuild = Build <$> argument readerByteString (metavar "JOB_PATH") <*> optional parseJobParameters +-- | Parse a Config sub-command. +parseConfig :: Parser Command +parseConfig = Config + <$> argument readerByteString (metavar "JOB_PATH") + <*> optional parseConfigFilePath + -- | Parse a Command. parseCommand :: Parser Command parseCommand = subparser $ command "info" (parseInfo `withInfo` "Get information on the given job") <> command "build" (parseBuild `withInfo` "Trigger a build for the given job") + <> command "config" (parseConfig `withInfo` "Manage XML configurations for jobs") -- | Combinator for all command line options. parseOptions :: Parser Options diff --git a/src/Bartlett/Types.hs b/src/Bartlett/Types.hs index 2a3ee2a..fbd0e24 100644 --- a/src/Bartlett/Types.hs +++ b/src/Bartlett/Types.hs @@ -30,6 +30,8 @@ type JobParameters = ByteString -- ^ Comma-separated list of key=value pairs to pass along to the triggered job. type Profile = ByteString -- ^ The profile to use when authenticating against Jenkins. +type ConfigPath = FilePath +-- ^ The path to the job configuration to upload. -- | Defines methods for basic authentication class BasicAuthUser a where @@ -49,6 +51,7 @@ instance BasicAuthUser User where data Command = Info [JobPath] -- ^ Retrieve information for the given job. | Build JobPath (Maybe JobParameters) -- ^ Build the given job with the given options. + | Config JobPath (Maybe ConfigPath) -- ^ Retrieve and upload job configurations. -- | Represents all available CLI options for 'Bartlett'. data Options =