2014-11-19 03:50:38 +00:00
|
|
|
module Propellor.CmdLine (
|
|
|
|
defaultMain,
|
|
|
|
processCmdLine,
|
|
|
|
) where
|
2014-03-30 23:10:32 +00:00
|
|
|
|
2014-04-01 15:59:48 +00:00
|
|
|
import System.Environment (getArgs)
|
2014-03-30 23:10:32 +00:00
|
|
|
import Data.List
|
|
|
|
import System.Exit
|
2014-04-03 17:49:26 +00:00
|
|
|
import System.PosixCompat
|
2014-03-30 23:10:32 +00:00
|
|
|
|
2014-03-31 03:55:59 +00:00
|
|
|
import Propellor
|
2014-11-18 17:29:50 +00:00
|
|
|
import Propellor.Protocol
|
2014-11-11 16:58:53 +00:00
|
|
|
import Propellor.Gpg
|
2014-11-18 22:39:10 +00:00
|
|
|
import Propellor.Git
|
2014-11-18 22:42:36 +00:00
|
|
|
import Propellor.Ssh
|
2014-11-19 02:10:50 +00:00
|
|
|
import Propellor.Server
|
2014-04-01 17:51:58 +00:00
|
|
|
import qualified Propellor.Property.Docker as Docker
|
2014-11-20 19:15:28 +00:00
|
|
|
import qualified Propellor.Property.Chroot as Chroot
|
|
|
|
import qualified Propellor.Shim as Shim
|
2014-03-31 03:55:59 +00:00
|
|
|
import Utility.SafeCommand
|
2014-03-30 23:10:32 +00:00
|
|
|
|
2014-11-19 00:33:25 +00:00
|
|
|
usage :: Handle -> IO ()
|
|
|
|
usage h = hPutStrLn h $ unlines
|
|
|
|
[ "Usage:"
|
|
|
|
, " propellor"
|
|
|
|
, " propellor hostname"
|
2014-11-22 16:57:07 +00:00
|
|
|
, " propellor --spin targethost [--via relayhost]"
|
2014-11-19 00:33:25 +00:00
|
|
|
, " propellor --add-key keyid"
|
|
|
|
, " propellor --set field context"
|
|
|
|
, " propellor --dump field context"
|
|
|
|
, " propellor --edit field context"
|
|
|
|
, " propellor --list-fields"
|
|
|
|
]
|
|
|
|
|
|
|
|
usageError :: [String] -> IO a
|
|
|
|
usageError ps = do
|
|
|
|
usage stderr
|
|
|
|
error ("(Unexpected: " ++ show ps)
|
2014-03-30 23:10:32 +00:00
|
|
|
|
|
|
|
processCmdLine :: IO CmdLine
|
|
|
|
processCmdLine = go =<< getArgs
|
|
|
|
where
|
2014-11-18 05:25:54 +00:00
|
|
|
go ("--run":h:[]) = return $ Run h
|
2014-11-22 16:57:07 +00:00
|
|
|
go ("--spin":h:[]) = return $ Spin h Nothing
|
|
|
|
go ("--spin":h:"--via":r:[]) = return $ Spin h (Just r)
|
2014-03-31 16:06:04 +00:00
|
|
|
go ("--add-key":k:[]) = return $ AddKey k
|
2014-07-06 19:56:56 +00:00
|
|
|
go ("--set":f:c:[]) = withprivfield f c Set
|
|
|
|
go ("--dump":f:c:[]) = withprivfield f c Dump
|
|
|
|
go ("--edit":f:c:[]) = withprivfield f c Edit
|
|
|
|
go ("--list-fields":[]) = return ListFields
|
2014-11-19 00:33:25 +00:00
|
|
|
go ("--help":_) = do
|
|
|
|
usage stdout
|
|
|
|
exitFailure
|
2014-11-22 16:57:07 +00:00
|
|
|
go ("--update":_:[]) = return $ Update Nothing
|
|
|
|
go ("--boot":_:[]) = return $ Update Nothing -- for back-compat
|
2014-11-22 19:54:31 +00:00
|
|
|
go ("--serialized":s:[]) = serialized Serialized s
|
|
|
|
go ("--continue":s:[]) = serialized Continue s
|
2014-11-19 00:38:11 +00:00
|
|
|
go ("--gitpush":fin:fout:_) = return $ GitPush (Prelude.read fin) (Prelude.read fout)
|
2014-04-01 15:59:48 +00:00
|
|
|
go (h:[])
|
2014-11-19 00:33:25 +00:00
|
|
|
| "--" `isPrefixOf` h = usageError [h]
|
2014-04-01 15:59:48 +00:00
|
|
|
| otherwise = return $ Run h
|
2014-03-30 23:10:32 +00:00
|
|
|
go [] = do
|
|
|
|
s <- takeWhile (/= '\n') <$> readProcess "hostname" ["-f"]
|
|
|
|
if null s
|
2014-03-31 22:31:08 +00:00
|
|
|
then errorMessage "Cannot determine hostname! Pass it on the command line."
|
2014-03-30 23:10:32 +00:00
|
|
|
else return $ Run s
|
2014-11-19 00:33:25 +00:00
|
|
|
go v = usageError v
|
2014-03-30 23:10:32 +00:00
|
|
|
|
2014-07-06 19:56:56 +00:00
|
|
|
withprivfield s c f = case readish s of
|
|
|
|
Just pf -> return $ f pf (Context c)
|
2014-06-01 20:58:05 +00:00
|
|
|
Nothing -> errorMessage $ "Unknown privdata field " ++ s
|
|
|
|
|
2014-11-22 19:54:31 +00:00
|
|
|
serialized mk s = case readish s of
|
|
|
|
Just cmdline -> return $ mk cmdline
|
|
|
|
Nothing -> errorMessage $ "serialization failure (" ++ s ++ ")"
|
|
|
|
|
2014-11-19 03:50:38 +00:00
|
|
|
-- | Runs propellor on hosts, as controlled by command-line options.
|
2014-04-11 01:09:20 +00:00
|
|
|
defaultMain :: [Host] -> IO ()
|
|
|
|
defaultMain hostlist = do
|
2014-11-20 19:15:28 +00:00
|
|
|
Shim.cleanEnv
|
2014-04-01 15:59:48 +00:00
|
|
|
checkDebugMode
|
|
|
|
cmdline <- processCmdLine
|
|
|
|
debug ["command line: ", show cmdline]
|
|
|
|
go True cmdline
|
2014-03-30 23:10:32 +00:00
|
|
|
where
|
2014-11-22 19:54:31 +00:00
|
|
|
go _ (Serialized cmdline) = go True cmdline
|
2014-03-31 20:37:19 +00:00
|
|
|
go _ (Continue cmdline) = go False cmdline
|
2014-07-06 19:56:56 +00:00
|
|
|
go _ (Set field context) = setPrivData field context
|
|
|
|
go _ (Dump field context) = dumpPrivData field context
|
|
|
|
go _ (Edit field context) = editPrivData field context
|
2014-07-06 20:44:13 +00:00
|
|
|
go _ ListFields = listPrivDataFields hostlist
|
2014-03-31 20:37:19 +00:00
|
|
|
go _ (AddKey keyid) = addKey keyid
|
2014-11-21 21:11:26 +00:00
|
|
|
go _ c@(ChrootChain _ _ _ _) = Chroot.chain hostlist c
|
2014-11-20 04:21:40 +00:00
|
|
|
go _ (DockerChain hn cid) = Docker.chain hostlist hn cid
|
2014-11-19 05:28:38 +00:00
|
|
|
go _ (DockerInit hn) = Docker.init hn
|
2014-11-19 02:10:50 +00:00
|
|
|
go _ (GitPush fin fout) = gitPushHelper fin fout
|
2014-11-22 16:57:07 +00:00
|
|
|
go _ (Update Nothing) = forceConsole >> fetchFirst (onlyprocess (update Nothing))
|
|
|
|
go _ (Update (Just h)) = forceConsole >> fetchFirst (update (Just h))
|
|
|
|
go True cmdline@(Spin _ _) = buildFirst cmdline $ go False cmdline
|
2014-03-31 21:57:12 +00:00
|
|
|
go True cmdline = updateFirst cmdline $ go False cmdline
|
2014-11-22 16:57:07 +00:00
|
|
|
go False (Spin hn r) = withhost hn $ spin hn r
|
2014-11-18 22:26:15 +00:00
|
|
|
go False cmdline@(SimpleRun hn) = buildFirst cmdline $
|
|
|
|
go False (Run hn)
|
2014-04-11 01:09:20 +00:00
|
|
|
go False (Run hn) = ifM ((==) 0 <$> getRealUserID)
|
2014-11-19 05:28:38 +00:00
|
|
|
( onlyprocess $ withhost hn mainProperties
|
2014-11-22 16:57:07 +00:00
|
|
|
, go True (Spin hn Nothing)
|
2014-04-03 17:58:21 +00:00
|
|
|
)
|
2014-03-31 20:37:19 +00:00
|
|
|
|
2014-05-31 22:02:56 +00:00
|
|
|
withhost :: HostName -> (Host -> IO ()) -> IO ()
|
2014-07-07 05:57:59 +00:00
|
|
|
withhost hn a = maybe (unknownhost hn hostlist) a (findHost hostlist hn)
|
2014-11-19 05:28:38 +00:00
|
|
|
|
|
|
|
onlyprocess = onlyProcess (localdir </> ".lock")
|
2014-04-04 20:20:20 +00:00
|
|
|
|
2014-07-07 05:57:59 +00:00
|
|
|
unknownhost :: HostName -> [Host] -> IO a
|
|
|
|
unknownhost h hosts = errorMessage $ unlines
|
2014-04-08 22:42:54 +00:00
|
|
|
[ "Propellor does not know about host: " ++ h
|
2014-04-03 04:59:26 +00:00
|
|
|
, "(Perhaps you should specify the real hostname on the command line?)"
|
|
|
|
, "(Or, edit propellor's config.hs to configure this host)"
|
2014-07-07 05:57:59 +00:00
|
|
|
, "Known hosts: " ++ unwords (map hostName hosts)
|
2014-03-31 03:02:10 +00:00
|
|
|
]
|
|
|
|
|
2014-03-31 22:53:27 +00:00
|
|
|
buildFirst :: CmdLine -> IO () -> IO ()
|
2014-11-22 04:22:19 +00:00
|
|
|
buildFirst cmdline next = ifM (doesFileExist "Makefile")
|
|
|
|
( do
|
|
|
|
oldtime <- getmtime
|
|
|
|
ifM (actionMessage "Propellor build" $ boolSystem "make" [Param "build"])
|
|
|
|
( do
|
|
|
|
newtime <- getmtime
|
|
|
|
if newtime == oldtime
|
|
|
|
then next
|
|
|
|
else void $ boolSystem "./propellor" [Param "--continue", Param (show cmdline)]
|
|
|
|
, errorMessage "Propellor build failed!"
|
|
|
|
)
|
|
|
|
, next
|
|
|
|
)
|
2014-03-31 22:53:27 +00:00
|
|
|
where
|
|
|
|
getmtime = catchMaybeIO $ getModificationTime "propellor"
|
|
|
|
|
2014-11-20 01:00:14 +00:00
|
|
|
fetchFirst :: IO () -> IO ()
|
|
|
|
fetchFirst next = do
|
|
|
|
whenM hasOrigin $
|
|
|
|
void fetchOrigin
|
|
|
|
next
|
|
|
|
|
2014-03-31 21:57:12 +00:00
|
|
|
updateFirst :: CmdLine -> IO () -> IO ()
|
2014-11-18 19:43:00 +00:00
|
|
|
updateFirst cmdline next = ifM hasOrigin (updateFirst' cmdline next, next)
|
|
|
|
|
|
|
|
updateFirst' :: CmdLine -> IO () -> IO ()
|
2014-11-20 01:00:14 +00:00
|
|
|
updateFirst' cmdline next = ifM fetchOrigin
|
|
|
|
( ifM (actionMessage "Propellor build" $ boolSystem "make" [Param "build"])
|
|
|
|
( void $ boolSystem "./propellor" [Param "--continue", Param (show cmdline)]
|
2014-03-31 22:31:08 +00:00
|
|
|
, errorMessage "Propellor build failed!"
|
2014-11-20 01:00:14 +00:00
|
|
|
)
|
|
|
|
, next
|
|
|
|
)
|
2014-03-31 20:20:38 +00:00
|
|
|
|
2014-11-22 16:57:07 +00:00
|
|
|
spin :: HostName -> Maybe HostName -> Host -> IO ()
|
|
|
|
spin target relay hst = do
|
2014-11-22 19:58:09 +00:00
|
|
|
unless relaying $ do
|
|
|
|
void $ actionMessage "Git commit" $
|
|
|
|
gitCommit [Param "--allow-empty", Param "-a", Param "-m", Param "propellor spin"]
|
|
|
|
-- Push to central origin repo first, if possible.
|
|
|
|
-- The remote propellor will pull from there, which avoids
|
|
|
|
-- us needing to send stuff directly to the remote host.
|
|
|
|
whenM hasOrigin $
|
|
|
|
void $ actionMessage "Push to central git repository" $
|
|
|
|
boolSystem "git" [Param "push"]
|
2014-11-18 21:10:10 +00:00
|
|
|
|
2014-11-22 20:20:02 +00:00
|
|
|
cacheparams <- if viarelay
|
|
|
|
then pure ["-A"]
|
|
|
|
else toCommand <$> sshCachingParams hn
|
2014-11-22 20:06:44 +00:00
|
|
|
when viarelay $
|
2014-11-22 16:57:07 +00:00
|
|
|
void $ boolSystem "ssh-add" []
|
2014-11-19 02:10:50 +00:00
|
|
|
|
|
|
|
-- Install, or update the remote propellor.
|
2014-11-22 16:57:07 +00:00
|
|
|
updateServer target relay hst $ withBothHandles createProcessSuccess
|
2014-11-19 02:10:50 +00:00
|
|
|
(proc "ssh" $ cacheparams ++ [user, updatecmd])
|
|
|
|
|
|
|
|
-- And now we can run it.
|
2014-11-22 20:06:44 +00:00
|
|
|
unlessM (boolSystem "ssh" (map Param $ cacheparams ++ ["-t", user, runcmd])) $
|
2014-11-20 00:48:36 +00:00
|
|
|
error $ "remote propellor failed"
|
2014-03-31 19:40:16 +00:00
|
|
|
where
|
2014-11-22 16:57:07 +00:00
|
|
|
hn = fromMaybe target relay
|
2014-04-11 01:09:20 +00:00
|
|
|
user = "root@"++hn
|
2014-11-22 20:06:44 +00:00
|
|
|
|
2014-11-22 19:58:09 +00:00
|
|
|
relaying = relay == Just target
|
2014-11-22 20:06:44 +00:00
|
|
|
viarelay = isJust relay && not relaying
|
2014-03-31 19:40:16 +00:00
|
|
|
|
2014-11-18 05:01:50 +00:00
|
|
|
mkcmd = shellWrap . intercalate " ; "
|
|
|
|
|
2014-11-19 02:10:50 +00:00
|
|
|
updatecmd = mkcmd
|
2014-03-31 20:45:32 +00:00
|
|
|
[ "if [ ! -d " ++ localdir ++ " ]"
|
2014-11-22 04:22:19 +00:00
|
|
|
, "then (" ++ intercalate " && "
|
2014-11-22 17:48:16 +00:00
|
|
|
[ "if ! git --version || ! make --version; then apt-get update && apt-get --no-install-recommends --no-upgrade -y install git make; fi"
|
2014-03-31 20:45:32 +00:00
|
|
|
, "echo " ++ toMarked statusMarker (show NeedGitClone)
|
2014-11-22 04:22:19 +00:00
|
|
|
] ++ ") || echo " ++ toMarked statusMarker (show NeedPrecompiled)
|
2014-03-31 20:45:32 +00:00
|
|
|
, "else " ++ intercalate " && "
|
|
|
|
[ "cd " ++ localdir
|
2014-04-10 04:40:38 +00:00
|
|
|
, "if ! test -x ./propellor; then make deps build; fi"
|
2014-11-22 20:06:44 +00:00
|
|
|
, if viarelay
|
|
|
|
then "./propellor --continue " ++
|
2014-11-22 16:57:07 +00:00
|
|
|
shellEscape (show (Update (Just target)))
|
2014-11-22 20:06:44 +00:00
|
|
|
-- Still using --boot for back-compat...
|
|
|
|
else "./propellor --boot " ++ target
|
2014-03-30 23:10:32 +00:00
|
|
|
]
|
2014-03-31 20:52:58 +00:00
|
|
|
, "fi"
|
2014-03-30 23:10:32 +00:00
|
|
|
]
|
2014-03-31 19:40:16 +00:00
|
|
|
|
2014-11-22 19:54:31 +00:00
|
|
|
runcmd = mkcmd [ "cd " ++ localdir ++ " && ./propellor " ++ cmd ]
|
2014-11-22 20:06:44 +00:00
|
|
|
cmd = if viarelay
|
|
|
|
then "--serialized " ++ shellEscape (show (Spin target (Just target)))
|
|
|
|
else "--continue " ++ shellEscape (show (SimpleRun target))
|