propellor/src/Propellor/CmdLine.hs

193 lines
6.1 KiB
Haskell
Raw Normal View History

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
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"
, " propellor --spin hostname"
, " 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-18 23:49:34 +00:00
go ("--spin":h:[]) = return $ Spin h
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-18 23:49:34 +00:00
go ("--update":h:[]) = return $ Update h
go ("--boot":h:[]) = return $ Update h -- for back-compat
go ("--continue":s:[]) = case readish s of
2014-03-31 20:37:19 +00:00
Just cmdline -> return $ Continue cmdline
2014-11-19 00:28:16 +00:00
Nothing -> errorMessage $ "--continue serialization failure (" ++ 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)
Nothing -> errorMessage $ "Unknown privdata field " ++ 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-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
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
go _ (DockerChain hn cid) = Docker.chain hostlist hn cid
go _ (DockerInit hn) = Docker.init hn
2014-11-19 02:10:50 +00:00
go _ (GitPush fin fout) = gitPushHelper fin fout
go _ (Update _) = forceConsole >> fetchFirst (onlyprocess update)
2014-03-31 22:53:27 +00:00
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-07-06 21:37:10 +00:00
go False (Spin hn) = withhost hn $ spin hn
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)
( onlyprocess $ withhost hn mainProperties
2014-04-11 01:09:20 +00:00
, go True (Spin hn)
2014-04-03 17:58:21 +00:00
)
2014-03-31 20:37:19 +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)
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
, "(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 ()
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"
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 ()
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!"
)
, next
)
2014-03-31 20:20:38 +00:00
2014-07-06 21:37:10 +00:00
spin :: HostName -> Host -> IO ()
spin hn hst = do
2014-11-20 04:55:28 +00:00
void $ actionMessage "Git commit" $
2014-11-18 22:07:26 +00:00
gitCommit [Param "--allow-empty", Param "-a", Param "-m", Param "propellor spin"]
2014-11-18 21:10:10 +00:00
-- 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 $
2014-11-18 22:13:42 +00:00
void $ actionMessage "Push to central git repository" $
2014-11-18 22:07:26 +00:00
boolSystem "git" [Param "push"]
2014-11-18 21:10:10 +00:00
2014-04-11 01:09:20 +00:00
cacheparams <- toCommand <$> sshCachingParams hn
2014-11-19 02:10:50 +00:00
-- Install, or update the remote propellor.
updateServer hn hst $ withBothHandles createProcessSuccess
(proc "ssh" $ cacheparams ++ [user, updatecmd])
-- And now we can run it.
2014-11-19 00:19:10 +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-04-11 01:09:20 +00:00
user = "root@"++hn
2014-03-31 19:40:16 +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 ++ " ]"
, "then (" ++ intercalate " && "
2014-11-22 04:47:26 +00:00
[ "apt-get update"
, "apt-get --no-install-recommends --no-upgrade -y install git make"
2014-03-31 20:45:32 +00:00
, "echo " ++ toMarked statusMarker (show NeedGitClone)
] ++ ") || 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"
, "./propellor --boot " ++ hn
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-18 05:25:54 +00:00
runcmd = mkcmd
[ "cd " ++ localdir ++ " && ./propellor --continue " ++ shellEscape (show (SimpleRun hn)) ]