b9 0.5.41 → 0.5.42
raw patch · 22 files changed
+1597/−1202 lines, 22 filesdep +lensdep +shakePVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: lens, shake
API changes (from Hackage documentation)
- B9: b9_version :: Version
- B9: configure :: MonadIO m => Maybe SystemPath -> B9Config -> m ConfigParser
- B9.B9Config: [buildDirRoot] :: B9Config -> Maybe FilePath
- B9.B9Config: [envVars] :: B9Config -> BuildVariables
- B9.B9Config: [execEnvType] :: B9Config -> ExecEnvType
- B9.B9Config: [interactive] :: B9Config -> Bool
- B9.B9Config: [keepTempDirs] :: B9Config -> Bool
- B9.B9Config: [logFile] :: B9Config -> Maybe FilePath
- B9.B9Config: [profileFile] :: B9Config -> Maybe FilePath
- B9.B9Config: [repositoryCache] :: B9Config -> Maybe SystemPath
- B9.B9Config: [repository] :: B9Config -> Maybe String
- B9.B9Config: [uniqueBuildDirs] :: B9Config -> Bool
- B9.B9Config: [verbosity] :: B9Config -> Maybe LogLevel
- B9.B9Config: getB9ConfigFile :: MonadIO m => Maybe SystemPath -> m FilePath
- B9.B9Config: writeB9Config :: MonadIO m => Maybe SystemPath -> ConfigParser -> m ()
- B9.B9Config: writeInitialB9Config :: MonadIO m => Maybe SystemPath -> B9Config -> ConfigParser -> m ()
- B9.B9Monad: getConfigParser :: B9 ConfigParser
- B9.ConfigUtils: InB9UserDir :: FilePath -> SystemPath
- B9.ConfigUtils: InHomeDir :: FilePath -> SystemPath
- B9.ConfigUtils: InTempDir :: FilePath -> SystemPath
- B9.ConfigUtils: IniFileException :: FilePath -> CPError -> IniFileException
- B9.ConfigUtils: Path :: FilePath -> SystemPath
- B9.ConfigUtils: UUID :: (Word32, Word16, Word16, Word16, Word32, Word16) -> UUID
- B9.ConfigUtils: allOn :: (a -> Maybe Bool) -> a -> a -> Maybe Bool
- B9.ConfigUtils: consult :: (MonadIO m, Read a) => FilePath -> m a
- B9.ConfigUtils: data IniFileException
- B9.ConfigUtils: data SystemPath
- B9.ConfigUtils: ensureDir :: MonadIO m => FilePath -> m ()
- B9.ConfigUtils: getDirectoryFiles :: MonadIO m => FilePath -> m [FilePath]
- B9.ConfigUtils: getOption :: (Get_C a, Monoid a) => ConfigParser -> SectionSpec -> OptionSpec -> a
- B9.ConfigUtils: getOptionM :: (Read a) => ConfigParser -> SectionSpec -> OptionSpec -> Maybe a
- B9.ConfigUtils: getOptionOr :: (Get_C a) => ConfigParser -> SectionSpec -> OptionSpec -> a -> a
- B9.ConfigUtils: instance Data.Data.Data B9.ConfigUtils.SystemPath
- B9.ConfigUtils: instance GHC.Classes.Eq B9.ConfigUtils.SystemPath
- B9.ConfigUtils: instance GHC.Classes.Eq B9.ConfigUtils.UUID
- B9.ConfigUtils: instance GHC.Classes.Ord B9.ConfigUtils.UUID
- B9.ConfigUtils: instance GHC.Exception.Exception B9.ConfigUtils.IniFileException
- B9.ConfigUtils: instance GHC.Exception.Exception B9.ConfigUtils.ReaderException
- B9.ConfigUtils: instance GHC.Read.Read B9.ConfigUtils.SystemPath
- B9.ConfigUtils: instance GHC.Read.Read B9.ConfigUtils.UUID
- B9.ConfigUtils: instance GHC.Show.Show B9.ConfigUtils.IniFileException
- B9.ConfigUtils: instance GHC.Show.Show B9.ConfigUtils.ReaderException
- B9.ConfigUtils: instance GHC.Show.Show B9.ConfigUtils.SystemPath
- B9.ConfigUtils: instance GHC.Show.Show B9.ConfigUtils.UUID
- B9.ConfigUtils: instance Text.Printf.PrintfArg B9.ConfigUtils.UUID
- B9.ConfigUtils: lastOn :: (a -> Maybe b) -> a -> a -> Maybe b
- B9.ConfigUtils: maybeConsult :: (MonadIO m, Read a) => Maybe FilePath -> a -> m a
- B9.ConfigUtils: maybeConsultSystemPath :: (MonadIO m, Read a) => Maybe SystemPath -> a -> m a
- B9.ConfigUtils: newtype UUID
- B9.ConfigUtils: randomUUID :: MonadIO m => m UUID
- B9.ConfigUtils: readIniFile :: MonadIO m => SystemPath -> m ConfigParser
- B9.ConfigUtils: resolve :: MonadIO m => SystemPath -> m FilePath
- B9.ConfigUtils: tell :: (MonadIO m, Show a) => FilePath -> a -> m ()
- B9.LibVirtLXC: instance GHC.Read.Read B9.LibVirtLXC.LXCGuestCapability
- B9.LibVirtLXC: instance GHC.Read.Read B9.LibVirtLXC.LibVirtLXCConfig
- B9.LibVirtLXC: instance GHC.Show.Show B9.LibVirtLXC.LXCGuestCapability
- B9.LibVirtLXC: instance GHC.Show.Show B9.LibVirtLXC.LibVirtLXCConfig
- B9.LibVirtLXC: setDefaultConfig :: ConfigParser
- B9.Repository: RemoteRepo :: String -> FilePath -> SshPrivKey -> SshRemoteHost -> SshRemoteUser -> RemoteRepo
- B9.Repository: RepoCache :: FilePath -> RepoCache
- B9.Repository: SshPrivKey :: FilePath -> SshPrivKey
- B9.Repository: SshRemoteHost :: (String, Int) -> SshRemoteHost
- B9.Repository: SshRemoteUser :: String -> SshRemoteUser
- B9.Repository: data RemoteRepo
- B9.Repository: getConfiguredRemoteRepos :: ConfigParser -> [RemoteRepo]
- B9.Repository: instance Data.Data.Data B9.Repository.RemoteRepo
- B9.Repository: instance Data.Data.Data B9.Repository.RepoCache
- B9.Repository: instance Data.Data.Data B9.Repository.SshPrivKey
- B9.Repository: instance Data.Data.Data B9.Repository.SshRemoteHost
- B9.Repository: instance Data.Data.Data B9.Repository.SshRemoteUser
- B9.Repository: instance GHC.Read.Read B9.Repository.RemoteRepo
- B9.Repository: instance GHC.Read.Read B9.Repository.RepoCache
- B9.Repository: instance GHC.Read.Read B9.Repository.SshPrivKey
- B9.Repository: instance GHC.Read.Read B9.Repository.SshRemoteHost
- B9.Repository: instance GHC.Read.Read B9.Repository.SshRemoteUser
- B9.Repository: instance GHC.Show.Show B9.Repository.RemoteRepo
- B9.Repository: instance GHC.Show.Show B9.Repository.RepoCache
- B9.Repository: instance GHC.Show.Show B9.Repository.SshPrivKey
- B9.Repository: instance GHC.Show.Show B9.Repository.SshRemoteHost
- B9.Repository: instance GHC.Show.Show B9.Repository.SshRemoteUser
- B9.Repository: newtype RepoCache
- B9.Repository: newtype SshPrivKey
- B9.Repository: newtype SshRemoteHost
- B9.Repository: newtype SshRemoteUser
- B9.Repository: remoteRepoRepoId :: RemoteRepo -> String
- B9.Repository: writeRemoteRepoConfig :: RemoteRepo -> ConfigParser -> Either CPError ConfigParser
+ B9: B9RunParameters :: B9ConfigOverride -> (B9Invokation a) -> BuildVariables -> B9RunParameters a
+ B9: b9Version :: Version
+ B9: b9VersionString :: String
+ B9: data B9RunParameters a
+ B9: defaultB9RunParameters :: B9Invokation a -> B9RunParameters a
+ B9: runAddRepo :: RemoteRepo -> B9Invokation ()
+ B9: runB9 :: B9RunParameters a -> IO (a, Bool)
+ B9: runBuildArtifacts :: [FilePath] -> B9Invokation ()
+ B9: runFormatBuildFiles :: [FilePath] -> B9Invokation ()
+ B9: runGcLocalRepoCache :: B9Invokation ()
+ B9: runGcRemoteRepoCache :: B9Invokation ()
+ B9: runListSharedImages :: B9Invokation ()
+ B9: runLookupLocalSharedImage :: SharedImageName -> B9Invokation (Maybe SharedImageBuildId)
+ B9: runPull :: Maybe SharedImageName -> B9Invokation ()
+ B9: runPush :: SharedImageName -> B9Invokation ()
+ B9: runRun :: SharedImageName -> [String] -> B9Invokation ()
+ B9: runShowVersion :: B9Invokation ()
+ B9.B9Config: B9ConfigOverride :: Maybe SystemPath -> B9Config -> B9ConfigOverride
+ B9.B9Config: [_buildDirRoot] :: B9Config -> Maybe FilePath
+ B9.B9Config: [_customB9ConfigPath] :: B9ConfigOverride -> Maybe SystemPath
+ B9.B9Config: [_customB9Config] :: B9ConfigOverride -> B9Config
+ B9.B9Config: [_envVars] :: B9Config -> BuildVariables
+ B9.B9Config: [_execEnvType] :: B9Config -> ExecEnvType
+ B9.B9Config: [_interactive] :: B9Config -> Bool
+ B9.B9Config: [_keepTempDirs] :: B9Config -> Bool
+ B9.B9Config: [_libVirtLXCConfigs] :: B9Config -> Maybe LibVirtLXCConfig
+ B9.B9Config: [_logFile] :: B9Config -> Maybe FilePath
+ B9.B9Config: [_profileFile] :: B9Config -> Maybe FilePath
+ B9.B9Config: [_remoteRepos] :: B9Config -> [RemoteRepo]
+ B9.B9Config: [_repositoryCache] :: B9Config -> Maybe SystemPath
+ B9.B9Config: [_repository] :: B9Config -> Maybe String
+ B9.B9Config: [_uniqueBuildDirs] :: B9Config -> Bool
+ B9.B9Config: [_verbosity] :: B9Config -> Maybe LogLevel
+ B9.B9Config: appendPositionalArguments :: [String] -> B9Config -> B9Config
+ B9.B9Config: b9ConfigToConfigParser :: B9Config -> ConfigParser -> Either CPError ConfigParser
+ B9.B9Config: buildDirRoot :: Lens' B9Config (Maybe FilePath)
+ B9.B9Config: customB9Config :: Lens' B9ConfigOverride B9Config
+ B9.B9Config: customB9ConfigPath :: Lens' B9ConfigOverride (Maybe SystemPath)
+ B9.B9Config: data B9ConfigOverride
+ B9.B9Config: envVars :: Lens' B9Config BuildVariables
+ B9.B9Config: execEnvType :: Lens' B9Config ExecEnvType
+ B9.B9Config: interactive :: Lens' B9Config Bool
+ B9.B9Config: keepTempDirs :: Lens' B9Config Bool
+ B9.B9Config: libVirtLXCConfigs :: Lens' B9Config (Maybe LibVirtLXCConfig)
+ B9.B9Config: logFile :: Lens' B9Config (Maybe FilePath)
+ B9.B9Config: modifyConfigParser :: (B9Config -> B9Config) -> ConfigParser -> Either CPError ConfigParser
+ B9.B9Config: openOrCreateB9Config :: MonadIO m => Maybe SystemPath -> m ConfigParser
+ B9.B9Config: profileFile :: Lens' B9Config (Maybe FilePath)
+ B9.B9Config: remoteRepos :: Lens' B9Config [RemoteRepo]
+ B9.B9Config: repository :: Lens' B9Config (Maybe String)
+ B9.B9Config: repositoryCache :: Lens' B9Config (Maybe SystemPath)
+ B9.B9Config: uniqueBuildDirs :: Lens' B9Config Bool
+ B9.B9Config: verbosity :: Lens' B9Config (Maybe LogLevel)
+ B9.B9Config: writeB9ConfigParser :: MonadIO m => Maybe SystemPath -> ConfigParser -> m ()
+ B9.B9Config.LibVirtLXC: CAP_AUDIT_CONTROL :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_AUDIT_READ :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_AUDIT_WRITE :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_BLOCK_SUSPEND :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_CHOWN :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_DAC_OVERRIDE :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_DAC_READ_SEARCH :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_FOWNER :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_FSETID :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_IPC_LOCK :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_IPC_OWNER :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_KILL :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_LEASE :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_LINUX_IMMUTABLE :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_MAC_ADMIN :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_MAC_OVERRIDE :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_MKNOD :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_NET_ADMIN :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_NET_BIND_SERVICE :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_NET_BROADCAST :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_NET_RAW :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SETFCAP :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SETGID :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SETPCAP :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SETUID :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SYSLOG :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SYS_ADMIN :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SYS_BOOT :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SYS_CHROOT :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SYS_MODULE :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SYS_NICE :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SYS_PACCT :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SYS_PTRACE :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SYS_RAWIO :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SYS_RESOURCE :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SYS_TIME :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_SYS_TTY_CONFIG :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: CAP_WAKE_ALARM :: LXCGuestCapability
+ B9.B9Config.LibVirtLXC: LibVirtLXCConfig :: Bool -> FilePath -> FilePath -> FilePath -> Maybe String -> [LXCGuestCapability] -> RamSize -> LibVirtLXCConfig
+ B9.B9Config.LibVirtLXC: [emulator] :: LibVirtLXCConfig -> FilePath
+ B9.B9Config.LibVirtLXC: [guestCapabilities] :: LibVirtLXCConfig -> [LXCGuestCapability]
+ B9.B9Config.LibVirtLXC: [guestRamSize] :: LibVirtLXCConfig -> RamSize
+ B9.B9Config.LibVirtLXC: [networkId] :: LibVirtLXCConfig -> Maybe String
+ B9.B9Config.LibVirtLXC: [useSudo] :: LibVirtLXCConfig -> Bool
+ B9.B9Config.LibVirtLXC: [virshPath] :: LibVirtLXCConfig -> FilePath
+ B9.B9Config.LibVirtLXC: [virshURI] :: LibVirtLXCConfig -> FilePath
+ B9.B9Config.LibVirtLXC: data LXCGuestCapability
+ B9.B9Config.LibVirtLXC: data LibVirtLXCConfig
+ B9.B9Config.LibVirtLXC: defaultLibVirtLXCConfig :: LibVirtLXCConfig
+ B9.B9Config.LibVirtLXC: instance GHC.Read.Read B9.B9Config.LibVirtLXC.LXCGuestCapability
+ B9.B9Config.LibVirtLXC: instance GHC.Read.Read B9.B9Config.LibVirtLXC.LibVirtLXCConfig
+ B9.B9Config.LibVirtLXC: instance GHC.Show.Show B9.B9Config.LibVirtLXC.LXCGuestCapability
+ B9.B9Config.LibVirtLXC: instance GHC.Show.Show B9.B9Config.LibVirtLXC.LibVirtLXCConfig
+ B9.B9Config.LibVirtLXC: libVirtLXCConfigToConfigParser :: LibVirtLXCConfig -> ConfigParser -> Either CPError ConfigParser
+ B9.B9Config.LibVirtLXC: parseLibVirtLXCConfig :: ConfigParser -> Either CPError LibVirtLXCConfig
+ B9.B9Config.Repository: RemoteRepo :: String -> FilePath -> SshPrivKey -> SshRemoteHost -> SshRemoteUser -> RemoteRepo
+ B9.B9Config.Repository: RepoCache :: FilePath -> RepoCache
+ B9.B9Config.Repository: SshPrivKey :: FilePath -> SshPrivKey
+ B9.B9Config.Repository: SshRemoteHost :: (String, Int) -> SshRemoteHost
+ B9.B9Config.Repository: SshRemoteUser :: String -> SshRemoteUser
+ B9.B9Config.Repository: data RemoteRepo
+ B9.B9Config.Repository: instance Data.Data.Data B9.B9Config.Repository.RemoteRepo
+ B9.B9Config.Repository: instance Data.Data.Data B9.B9Config.Repository.RepoCache
+ B9.B9Config.Repository: instance Data.Data.Data B9.B9Config.Repository.SshPrivKey
+ B9.B9Config.Repository: instance Data.Data.Data B9.B9Config.Repository.SshRemoteHost
+ B9.B9Config.Repository: instance Data.Data.Data B9.B9Config.Repository.SshRemoteUser
+ B9.B9Config.Repository: instance GHC.Read.Read B9.B9Config.Repository.RemoteRepo
+ B9.B9Config.Repository: instance GHC.Read.Read B9.B9Config.Repository.RepoCache
+ B9.B9Config.Repository: instance GHC.Read.Read B9.B9Config.Repository.SshPrivKey
+ B9.B9Config.Repository: instance GHC.Read.Read B9.B9Config.Repository.SshRemoteHost
+ B9.B9Config.Repository: instance GHC.Read.Read B9.B9Config.Repository.SshRemoteUser
+ B9.B9Config.Repository: instance GHC.Show.Show B9.B9Config.Repository.RemoteRepo
+ B9.B9Config.Repository: instance GHC.Show.Show B9.B9Config.Repository.RepoCache
+ B9.B9Config.Repository: instance GHC.Show.Show B9.B9Config.Repository.SshPrivKey
+ B9.B9Config.Repository: instance GHC.Show.Show B9.B9Config.Repository.SshRemoteHost
+ B9.B9Config.Repository: instance GHC.Show.Show B9.B9Config.Repository.SshRemoteUser
+ B9.B9Config.Repository: newtype RepoCache
+ B9.B9Config.Repository: newtype SshPrivKey
+ B9.B9Config.Repository: newtype SshRemoteHost
+ B9.B9Config.Repository: newtype SshRemoteUser
+ B9.B9Config.Repository: parseRemoteRepos :: ConfigParser -> Either CPError [RemoteRepo]
+ B9.B9Config.Repository: remoteRepoRepoId :: RemoteRepo -> String
+ B9.B9Config.Repository: remoteRepoToConfigParser :: RemoteRepo -> ConfigParser -> Either CPError ConfigParser
+ B9.DiskImages: fromSharedImageBuildId :: SharedImageBuildId -> String
+ B9.DiskImages: fromSharedImageName :: SharedImageName -> String
+ B9.Invokation: data B9Invokation a
+ B9.Invokation: doAfterConfiguration :: ReaderT B9Config IO Bool -> B9Invokation ()
+ B9.Invokation: instance Control.Monad.IO.Class.MonadIO B9.Invokation.B9Invokation
+ B9.Invokation: instance Control.Monad.State.Class.MonadState B9.Invokation.InternalState B9.Invokation.B9Invokation
+ B9.Invokation: instance GHC.Base.Applicative B9.Invokation.B9Invokation
+ B9.Invokation: instance GHC.Base.Functor B9.Invokation.B9Invokation
+ B9.Invokation: instance GHC.Base.Monad B9.Invokation.B9Invokation
+ B9.Invokation: invokeB9 :: B9Invokation a -> IO (a, Bool)
+ B9.Invokation: invokeB9_ :: B9Invokation a -> IO Bool
+ B9.Invokation: modifyInvokationConfig :: (B9Config -> B9Config) -> B9Invokation ()
+ B9.Invokation: modifyPermanentConfig :: (B9Config -> B9Config) -> B9Invokation ()
+ B9.Invokation: overrideB9ConfigPath :: SystemPath -> B9Invokation ()
+ B9.Invokation: overrideWorkingDirectory :: FilePath -> B9Invokation ()
+ B9.LibVirtLXC: logLibVirtLXCConfig :: LibVirtLXCConfig -> B9 ()
+ B9.Shake.Actions: b9InvokationAction :: B9Invokation a -> Action (a, Bool)
+ B9.Shake.Actions: buildB9File :: FilePath -> FilePath -> [String] -> Action ()
+ B9.Shake.SharedImageRules: customSharedImageAction :: SharedImageName -> Action () -> Rules ()
+ B9.Shake.SharedImageRules: enableSharedImageRules :: Rules ()
+ B9.Shake.SharedImageRules: needSharedImage :: SharedImageName -> Action SharedImageBuildId
+ Data.ConfigFile.B9Extras: InB9UserDir :: FilePath -> SystemPath
+ Data.ConfigFile.B9Extras: InHomeDir :: FilePath -> SystemPath
+ Data.ConfigFile.B9Extras: InTempDir :: FilePath -> SystemPath
+ Data.ConfigFile.B9Extras: IniFileException :: FilePath -> CPError -> IniFileException
+ Data.ConfigFile.B9Extras: Path :: FilePath -> SystemPath
+ Data.ConfigFile.B9Extras: UUID :: (Word32, Word16, Word16, Word16, Word32, Word16) -> UUID
+ Data.ConfigFile.B9Extras: allOn :: (a -> Maybe Bool) -> a -> a -> Maybe Bool
+ Data.ConfigFile.B9Extras: consult :: (MonadIO m, Read a) => FilePath -> m a
+ Data.ConfigFile.B9Extras: data IniFileException
+ Data.ConfigFile.B9Extras: data SystemPath
+ Data.ConfigFile.B9Extras: ensureDir :: MonadIO m => FilePath -> m ()
+ Data.ConfigFile.B9Extras: getDirectoryFiles :: MonadIO m => FilePath -> m [FilePath]
+ Data.ConfigFile.B9Extras: getOption :: (Get_C a, Monoid a) => ConfigParser -> SectionSpec -> OptionSpec -> a
+ Data.ConfigFile.B9Extras: getOptionM :: (Read a) => ConfigParser -> SectionSpec -> OptionSpec -> Maybe a
+ Data.ConfigFile.B9Extras: getOptionOr :: (Get_C a) => ConfigParser -> SectionSpec -> OptionSpec -> a -> a
+ Data.ConfigFile.B9Extras: instance Data.Data.Data Data.ConfigFile.B9Extras.SystemPath
+ Data.ConfigFile.B9Extras: instance GHC.Classes.Eq Data.ConfigFile.B9Extras.SystemPath
+ Data.ConfigFile.B9Extras: instance GHC.Classes.Eq Data.ConfigFile.B9Extras.UUID
+ Data.ConfigFile.B9Extras: instance GHC.Classes.Ord Data.ConfigFile.B9Extras.UUID
+ Data.ConfigFile.B9Extras: instance GHC.Exception.Exception Data.ConfigFile.B9Extras.IniFileException
+ Data.ConfigFile.B9Extras: instance GHC.Exception.Exception Data.ConfigFile.B9Extras.ReaderException
+ Data.ConfigFile.B9Extras: instance GHC.Read.Read Data.ConfigFile.B9Extras.SystemPath
+ Data.ConfigFile.B9Extras: instance GHC.Read.Read Data.ConfigFile.B9Extras.UUID
+ Data.ConfigFile.B9Extras: instance GHC.Show.Show Data.ConfigFile.B9Extras.IniFileException
+ Data.ConfigFile.B9Extras: instance GHC.Show.Show Data.ConfigFile.B9Extras.ReaderException
+ Data.ConfigFile.B9Extras: instance GHC.Show.Show Data.ConfigFile.B9Extras.SystemPath
+ Data.ConfigFile.B9Extras: instance GHC.Show.Show Data.ConfigFile.B9Extras.UUID
+ Data.ConfigFile.B9Extras: instance Text.Printf.PrintfArg Data.ConfigFile.B9Extras.UUID
+ Data.ConfigFile.B9Extras: lastOn :: (a -> Maybe b) -> a -> a -> Maybe b
+ Data.ConfigFile.B9Extras: maybeConsult :: (MonadIO m, Read a) => Maybe FilePath -> a -> m a
+ Data.ConfigFile.B9Extras: maybeConsultSystemPath :: (MonadIO m, Read a) => Maybe SystemPath -> a -> m a
+ Data.ConfigFile.B9Extras: newtype UUID
+ Data.ConfigFile.B9Extras: randomUUID :: MonadIO m => m UUID
+ Data.ConfigFile.B9Extras: readIniFile :: MonadIO m => SystemPath -> m ConfigParser
+ Data.ConfigFile.B9Extras: resolve :: MonadIO m => SystemPath -> m FilePath
+ Data.ConfigFile.B9Extras: tell :: (MonadIO m, Show a) => FilePath -> a -> m ()
- B9.B9Config: B9Config :: Maybe LogLevel -> Maybe FilePath -> Maybe FilePath -> Bool -> ExecEnvType -> Maybe FilePath -> BuildVariables -> Bool -> Maybe SystemPath -> Maybe String -> Bool -> B9Config
+ B9.B9Config: B9Config :: Maybe LogLevel -> Maybe FilePath -> Maybe FilePath -> Bool -> ExecEnvType -> Maybe FilePath -> BuildVariables -> Bool -> Maybe SystemPath -> Maybe String -> Bool -> Maybe LibVirtLXCConfig -> [RemoteRepo] -> B9Config
- B9.B9Config: parseB9Config :: ConfigParser -> Either String B9Config
+ B9.B9Config: parseB9Config :: ConfigParser -> Either CPError B9Config
- B9.B9Monad: run :: ConfigParser -> B9Config -> B9 a -> IO a
+ B9.B9Monad: run :: B9 Bool -> B9Invokation ()
- B9.Builder: buildArtifacts :: ArtifactGenerator -> ConfigParser -> B9Config -> IO Bool
+ B9.Builder: buildArtifacts :: ArtifactGenerator -> B9Invokation ()
Files
- README.md +7/−23
- b9.cabal +27/−18
- src/cli/Main.hs +216/−381
- src/lib/B9.hs +209/−14
- src/lib/B9/ArtifactGeneratorImpl.hs +3/−4
- src/lib/B9/B9Config.hs +176/−106
- src/lib/B9/B9Config/LibVirtLXC.hs +128/−0
- src/lib/B9/B9Config/Repository.hs +90/−0
- src/lib/B9/B9Monad.hs +135/−144
- src/lib/B9/Builder.hs +9/−24
- src/lib/B9/ConfigUtils.hs +0/−150
- src/lib/B9/Content/StringTemplate.hs +1/−1
- src/lib/B9/DiskImageBuilder.hs +1/−1
- src/lib/B9/DiskImages.hs +8/−0
- src/lib/B9/Invokation.hs +124/−0
- src/lib/B9/LibVirtLXC.hs +156/−242
- src/lib/B9/Repository.hs +6/−93
- src/lib/B9/RepositoryIO.hs +1/−1
- src/lib/B9/Shake.hs +6/−0
- src/lib/B9/Shake/Actions.hs +23/−0
- src/lib/B9/Shake/SharedImageRules.hs +120/−0
- src/lib/Data/ConfigFile/B9Extras.hs +151/−0
README.md view
@@ -87,35 +87,19 @@ * Configurable remote (ssh with pubkey auth + rsync) image shareing * Local caching of shared images -## Compilation from Source--To build B9 first install:--* `ghc` version 7.6 or higher-* `cabal-install` version 1.16 or higher--B9 uses stackage and cabal sandboxes. The build result can be found in-`.cabal-sandbox/bin/`. To run a complete fresh build, execute:-- ./installDeps.sh- cabal install--To launch b9c run:-- ./build_and_run.sh--To execute a ghci-repl run:+## Installation - cabal repl+### b9 executable and library -To execute unit tests run:+Install via `stack`: - ./build_and_test.sh+ $ stack install b9 -## Installation+### Runtime dependencies -To be able to use B9 install+To be able to use B9 install: +* Linux * lxc * libvirt with lxc support (libvirt-driver-lxc, libvirt-daemon-lxc,...) * virsh
b9.cabal view
@@ -1,5 +1,5 @@ name: b9-version: 0.5.41+version: 0.5.42 synopsis: A tool and library for building virtual machine images. @@ -69,31 +69,37 @@ library exposed-modules: B9- , B9.B9Monad+ , B9.ArtifactGenerator+ , B9.ArtifactGeneratorImpl , B9.B9Config+ , B9.B9Config.LibVirtLXC+ , B9.B9Config.Repository+ , B9.B9Monad , B9.Builder- , B9.ConfigUtils- , B9.ExecEnv- , B9.DiskImages+ , B9.Content.AST+ , B9.Content.ErlangPropList+ , B9.Content.ErlTerms+ , B9.Content.Generator+ , B9.Content.StringTemplate+ , B9.Content.YamlObject , B9.DiskImageBuilder- , B9.ShellScript- , B9.PartitionTable- , B9.MBR+ , B9.DiskImages+ , B9.DSL+ , B9.ExecEnv+ , B9.Invokation , B9.LibVirtLXC+ , B9.MBR+ , B9.PartitionTable+ , B9.QCUtil , B9.Repository , B9.RepositoryIO- , B9.ArtifactGenerator- , B9.ArtifactGeneratorImpl+ , B9.Shake+ , B9.Shake.Actions+ , B9.Shake.SharedImageRules+ , B9.ShellScript , B9.Vm , B9.VmBuilder- , B9.Content.ErlTerms- , B9.Content.ErlangPropList- , B9.Content.YamlObject- , B9.Content.AST- , B9.Content.Generator- , B9.Content.StringTemplate- , B9.QCUtil- , B9.DSL+ , Data.ConfigFile.B9Extras other-modules: Paths_b9 -- other-extensions: build-depends: ConfigFile >= 1.1.4@@ -108,6 +114,7 @@ , directory >= 1.3 , filepath >= 1.4 , hashable >= 1.2+ , lens >= 4 , mtl >= 2.2 , time >= 1.6 , parallel >= 3.2@@ -116,6 +123,7 @@ , pretty >= 1.1 , process >= 1.4 , random >= 1.1+ , shake >= 0.16 && < 0.17 , syb >= 0.6 , template >= 0.2 , text >= 1.2@@ -157,6 +165,7 @@ , directory >= 1.3 , bytestring >= 0.10 , optparse-applicative >= 0.13+ , lens >= 4 hs-source-dirs: src/cli default-language: Haskell2010 default-extensions: TupleSections
src/cli/Main.hs view
@@ -2,281 +2,97 @@ import Options.Applicative hiding (action) import Options.Applicative.Help.Pretty hiding ((</>))--import Control.Exception-import Data.Function (on)-import Data.Maybe-import Data.Version-import Paths_b9-import System.Directory-import System.IO.Error hiding (isDoesNotExistErrorType) import B9+import Control.Lens ((.~), (&)) main :: IO () main = do- b9Opts <- parseCommandLine- result <- runB9 b9Opts- exit result- where- exit success = when (not success) (exitWith (ExitFailure 128))--parseCommandLine :: IO B9Options-parseCommandLine =- execParser- (info- (helper <*> (B9Options <$> globals <*> cmds <*> buildVars))- (fullDesc <>- progDesc- "Build and run VM-Images inside LXC containers. Custom arguments follow after '--' and are accessable in many strings in build files trough shell like variable references, i.e. '${arg_N}' referes to positional argument $N.\n\nRepository names passed to the command line are looked up in the B9 configuration file, which is per default located in: '~/.b9/b9.conf'" <>- headerDoc (Just helpHeader)))- where- helpHeader =- linebreak <>- text ("B9 - a benign VM-Image build tool v. " ++ b9Version)--data B9Options =- B9Options GlobalOpts- BuildAction- BuildVariables--data GlobalOpts = GlobalOpts- { configFile :: Maybe SystemPath- , cliB9Config :: B9Config- }--type BuildAction = Maybe SystemPath -> ConfigParser -> B9Config -> IO Bool--runB9 :: B9Options -> IO Bool-runB9 (B9Options globalOpts action vars) = do- let cfgWithArgs =- cfgCli- { envVars = envVars cfgCli ++ vars- }- cfgCli = cliB9Config globalOpts- cfgFile = configFile globalOpts- cp <- configure cfgFile cfgCli- action cfgFile cp cfgWithArgs--runShowVersion :: BuildAction-runShowVersion _cfgFile _cp _conf = do- putStrLn b9Version- return True--runBuildArtifacts :: [FilePath] -> BuildAction-runBuildArtifacts buildFiles _cfgFile cp conf = do- generators <- mapM consult buildFiles- buildArtifacts (mconcat generators) cp conf--runFormatBuildFiles :: [FilePath] -> BuildAction-runFormatBuildFiles buildFiles _cfgFile _cp _conf = do- generators <- mapM consult buildFiles- let generatorsFormatted = map ppShow (generators :: [ArtifactGenerator])- putStrLn `mapM` (generatorsFormatted)- (uncurry writeFile) `mapM` (buildFiles `zip` generatorsFormatted)- return True--runPush :: SharedImageName -> BuildAction-runPush name _cfgFile cp conf = impl- where- conf' =- conf- { keepTempDirs = False- }- impl =- run- cp- conf'- (if not (isJust (repository conf'))- then do- errorL- "No repository specified! Use '-r' to specify a repo BEFORE 'push'."- return False- else do- pushSharedImageLatestVersion name- return True)--runPull :: Maybe SharedImageName -> BuildAction-runPull mName _cfgFile cp conf =- run cp conf' (pullRemoteRepos >> maybePullImage)- where- conf' =- conf- { keepTempDirs = False- }- maybePullImage = maybe (return True) pullLatestImage mName--runRun :: SharedImageName -> [String] -> BuildAction-runRun (SharedImageName name) cmdAndArgs _cfgFile cp conf = impl- where- conf' =- conf- { keepTempDirs = False- , interactive = True- }- impl = buildArtifacts runCmdAndArgs cp conf'- where- runCmdAndArgs =- Artifact- (IID ("run-" ++ name))- (VmImages- [ ImageTarget- Transient- (From name KeepSize)- (MountPoint "/")]- (VmScript- X86_64- [SharedDirectory "." (MountPoint "/mnt/CWD")]- (Run (head cmdAndArgs') (tail cmdAndArgs'))))- cmdAndArgs' =- if null cmdAndArgs- then ["/usr/bin/zsh"]- else cmdAndArgs---runGcLocalRepoCache :: BuildAction-runGcLocalRepoCache _cfgFile cp conf = impl- where- conf' =- conf- { keepTempDirs = False- }- impl =- run cp conf' $- do toDelete <-- (obsoleteSharedmages . map snd) <$>- lookupSharedImages (== Cache) (const True)- imgDir <- getSharedImagesCacheDir- let filesToDelete = (imgDir </>) <$> (infoFiles ++ imgFiles)- infoFiles = sharedImageFileName <$> toDelete- imgFiles = (imageFileName . sharedImageImage) <$> toDelete- if null filesToDelete- then liftIO $- do putStrLn "\n\nNO IMAGES TO DELETE\n"- return True- else liftIO $- do putStrLn "DELETING FILES:"- putStrLn (unlines filesToDelete)- mapM_ removeIfExists filesToDelete- return True- obsoleteSharedmages :: [SharedImage] -> [SharedImage]- obsoleteSharedmages =- concatMap (tail . reverse) .- filter ((> 1) . length) . groupBy ((==) `on` siName)- removeIfExists :: FilePath -> IO ()- removeIfExists fileName = removeFile fileName `catch` handleExists- where- handleExists e- | isDoesNotExistError e = return ()- | otherwise = throwIO e--runGcRemoteRepoCache :: BuildAction-runGcRemoteRepoCache _cfgFile cp conf = impl- where- conf' =- conf- { keepTempDirs = False- }- impl =- run cp conf' $- do repos <- getSelectedRepos- cache <- getRepoCache- mapM_ (cleanRemoteRepo cache) repos- return True+ b9Opts <- parseCommandLine+ (_, result) <- runB9 b9Opts+ exit result+ where exit success = when (not success) (exitWith (ExitFailure 128)) -runListSharedImages :: BuildAction-runListSharedImages _cfgFile cp conf = impl+parseCommandLine :: IO (B9RunParameters ())+parseCommandLine = execParser+ ( info+ (helper <*> (B9RunParameters <$> globals <*> cmds <*> buildVars))+ ( fullDesc+ <> progDesc+ "Build and run VM-Images inside LXC containers. Custom arguments follow after '--' and are accessable in many strings in build files trough shell like variable references, i.e. '${arg_N}' referes to positional argument $N.\n\nRepository names passed to the command line are looked up in the B9 configuration file, which is per default located in: '~/.b9/b9.conf'"+ <> headerDoc (Just b9HelpHeader)+ )+ ) where- conf' =- conf- { keepTempDirs = False- }- impl =- run cp conf' $- do remoteRepo <- getSelectedRemoteRepo- let repoPred =- maybe (== Cache) ((==) . toRemoteRepository) remoteRepo- allRepos <- getRemoteRepos- if isNothing remoteRepo- then liftIO $- do putStrLn "Showing local shared images only."- putStrLn $- "\nTo view the contents of a remote repo add \n\- \the '-r' switch with one of the remote \n\- \repository ids."- else liftIO $- putStrLn- ("Showing shared images on: " ++- remoteRepoRepoId (fromJust remoteRepo))- when (not (null allRepos)) $- liftIO $- do putStrLn "\nAvailable remote repositories:"- mapM_ (putStrLn . (" * " ++) . remoteRepoRepoId) allRepos- imgs <- lookupSharedImages repoPred (const True)- if null imgs- then liftIO $ putStrLn "\n\nNO SHARED IMAGES\n"- else liftIO $- do putStrLn ""- putStrLn $ prettyPrintSharedImages $ map snd imgs- return True+ b9HelpHeader =+ linebreak <> text ("B9 - a benign VM-Image build tool v. " ++ b9VersionString) -runAddRepo :: RemoteRepo -> BuildAction-runAddRepo repo cfgFile cp _conf = do- repo' <- remoteRepoCheckSshPrivKey repo- case writeRemoteRepoConfig repo' cp of- Left er ->- error- (printf- "Failed to add remote repo '%s' to b9 configuration. The error was: \"%s\"."- (show repo)- (show er))- Right cpWithRepo -> writeB9Config cfgFile cpWithRepo- return True--globals :: Parser GlobalOpts+globals :: Parser B9ConfigOverride globals =- toGlobalOpts <$>- optional- (strOption- (help "Path to user's b9-configuration (default: ~/.b9/b9.conf)" <>- short 'c' <>- long "configuration-file" <>- metavar "FILENAME")) <*>- switch- (help "Log everything that happens to stdout" <> short 'v' <>- long "verbose") <*>- switch (help "Suppress non-error output" <> short 'q' <> long "quiet") <*>- optional- (strOption- (help "Path to a logfile" <> short 'l' <> long "log-file" <>- metavar "FILENAME")) <*>- optional- (strOption- (help "Output file for a command/timing profile" <>- long "profile-file" <>- metavar "FILENAME")) <*>- optional- (strOption- (help "Root directory for build directories" <> short 'b' <>- long "build-root-dir" <>- metavar "DIRECTORY")) <*>- switch- (help "Keep build directories after exit" <> short 'k' <>- long "keep-build-dir") <*>- switch- (help "Predictable build directory names" <> short 'u' <>- long "predictable-build-dir") <*>- optional- (strOption- (help- "Cache directory for shared images, default: '~/.b9/repo-cache'" <>- long "repo-cache" <>- metavar "DIRECTORY")) <*>- optional- (strOption- (help "Remote repository to share image to" <> short 'r' <>- long "repo" <>- metavar "REPOSITORY_ID"))+ toGlobalOpts+ <$> optional+ ( strOption+ ( help+ "Path to user's b9-configuration (default: ~/.b9/b9.conf)"+ <> short 'c'+ <> long "configuration-file"+ <> metavar "FILENAME"+ )+ )+ <*> switch+ ( help "Log everything that happens to stdout"+ <> short 'v'+ <> long "verbose"+ )+ <*> switch+ (help "Suppress non-error output" <> short 'q' <> long "quiet")+ <*> optional+ ( strOption+ ( help "Path to a logfile"+ <> short 'l'+ <> long "log-file"+ <> metavar "FILENAME"+ )+ )+ <*> optional+ ( strOption+ ( help "Output file for a command/timing profile"+ <> long "profile-file"+ <> metavar "FILENAME"+ )+ )+ <*> optional+ ( strOption+ ( help "Root directory for build directories"+ <> short 'b'+ <> long "build-root-dir"+ <> metavar "DIRECTORY"+ )+ )+ <*> switch+ ( help "Keep build directories after exit" <> short 'k' <> long+ "keep-build-dir"+ )+ <*> switch+ ( help "Predictable build directory names" <> short 'u' <> long+ "predictable-build-dir"+ )+ <*> optional+ ( strOption+ ( help+ "Cache directory for shared images, default: '~/.b9/repo-cache'"+ <> long "repo-cache"+ <> metavar "DIRECTORY"+ )+ )+ <*> optional+ ( strOption+ ( help "Remote repository to share image to"+ <> short 'r'+ <> long "repo"+ <> metavar "REPOSITORY_ID"+ )+ ) where toGlobalOpts :: Maybe FilePath@@ -289,101 +105,104 @@ -> Bool -> Maybe FilePath -> Maybe String- -> GlobalOpts- toGlobalOpts cfg verbose quiet logF profF buildRoot keep notUnique mRepoCache repo =- let minLogLevel =- if verbose- then Just LogTrace- else if quiet- then Just LogError- else Nothing- b9cfg' =- let b9cfg =- mempty- { verbosity = minLogLevel- , logFile = logF- , profileFile = profF- , buildDirRoot = buildRoot- , keepTempDirs = keep- , uniqueBuildDirs = not notUnique- , repository = repo- }- in b9cfg- { repositoryCache = Path <$> mRepoCache- }- in GlobalOpts- { configFile = (Path <$> cfg) <|> pure defaultB9ConfigFile- , cliB9Config = b9cfg'- }+ -> B9ConfigOverride+ toGlobalOpts cfg verbose quiet logF profF buildRoot keep notUnique mRepoCache repo+ = let minLogLevel = if verbose+ then Just LogTrace+ else if quiet then Just LogError else Nothing+ b9cfg = mempty & verbosity .~ minLogLevel+ & logFile .~ logF+ & profileFile .~ profF+ & buildDirRoot .~ buildRoot+ & keepTempDirs .~ keep+ & uniqueBuildDirs .~ not notUnique+ & repository .~ repo+ & repositoryCache .~ (Path <$> mRepoCache) -cmds :: Parser BuildAction-cmds =- subparser- (command- "version"- (info- (pure runShowVersion)- (progDesc "Show program version and exit.")) <>- command- "build"- (info- (runBuildArtifacts <$> buildFileParser)- (progDesc "Merge all build file and generate all artifacts.")) <>- command- "run"- (info- (runRun <$> sharedImageNameParser <*> many (strArgument idm))- (progDesc- "Run a command on the lastest version of the specified shared image. All modifications are lost on exit.")) <>- command- "push"- (info- (runPush <$> sharedImageNameParser)- (progDesc- "Push the lastest shared image from cache to the selected remote repository.")) <>- command- "pull"- (info- (runPull <$> optional sharedImageNameParser)- (progDesc- "Either pull shared image meta data from all repositories, or only from just a selected one. If additionally the name of a shared images was specified, pull the newest version from either the selected repo, or from the repo with the most recent version.")) <>- command- "clean-local"- (info- (pure runGcLocalRepoCache)- (progDesc- "Remove old versions of shared images from the local cache.")) <>- command- "clean-remote"- (info- (pure runGcRemoteRepoCache)- (progDesc- "Remove cached meta-data of a remote repository. If no '-r' is given, clean the meta data of ALL remote repositories.")) <>- command- "list"- (info (pure runListSharedImages) (progDesc "List shared images.")) <>- command- "add-repo"- (info- (runAddRepo <$> remoteRepoParser)- (progDesc "Add a remote repo.")) <>- command- "reformat"- (info- (runFormatBuildFiles <$> buildFileParser)- (progDesc "Re-Format all build files.")))+ in B9ConfigOverride+ { _customB9ConfigPath = Path <$> cfg+ , _customB9Config = b9cfg+ } +cmds :: Parser (B9Invokation ())+cmds = subparser+ ( command+ "version"+ ( info (pure runShowVersion)+ (progDesc "Show program version and exit.")+ )+ <> command+ "build"+ ( info+ (runBuildArtifacts <$> buildFileParser)+ (progDesc "Merge all build file and generate all artifacts.")+ )+ <> command+ "run"+ ( info+ (runRun <$> sharedImageNameParser <*> many (strArgument idm))+ ( progDesc+ "Run a command on the lastest version of the specified shared image. All modifications are lost on exit."+ )+ )+ <> command+ "push"+ ( info+ (runPush <$> sharedImageNameParser)+ ( progDesc+ "Push the lastest shared image from cache to the selected remote repository."+ )+ )+ <> command+ "pull"+ ( info+ (runPull <$> optional sharedImageNameParser)+ ( progDesc+ "Either pull shared image meta data from all repositories, or only from just a selected one. If additionally the name of a shared images was specified, pull the newest version from either the selected repo, or from the repo with the most recent version."+ )+ )+ <> command+ "clean-local"+ ( info+ (pure runGcLocalRepoCache)+ ( progDesc+ "Remove old versions of shared images from the local cache."+ )+ )+ <> command+ "clean-remote"+ ( info+ (pure runGcRemoteRepoCache)+ ( progDesc+ "Remove cached meta-data of a remote repository. If no '-r' is given, clean the meta data of ALL remote repositories."+ )+ )+ <> command+ "list"+ (info (pure runListSharedImages) (progDesc "List shared images."))+ <> command+ "add-repo"+ ( info (runAddRepo <$> remoteRepoParser)+ (progDesc "Add a remote repo.")+ )+ <> command+ "reformat"+ ( info (runFormatBuildFiles <$> buildFileParser)+ (progDesc "Re-Format all build files.")+ )+ )+ buildFileParser :: Parser [FilePath]-buildFileParser =- helper <*>- some- (strOption- (help- "Build file to load, specify multiple build files (each witch '-f') to build them all in a single run." <>- short 'f' <>- long "project-file" <>- metavar "FILENAME" <>- noArgError (ErrorMsg "No build file specified!")))+buildFileParser = helper <*> some+ ( strOption+ ( help+ "Build file to load, specify multiple build files (each witch '-f') to build them all in a single run."+ <> short 'f'+ <> long "project-file"+ <> metavar "FILENAME"+ <> noArgError (ErrorMsg "No build file specified!")+ )+ ) buildVars :: Parser BuildVariables buildVars =@@ -391,30 +210,46 @@ remoteRepoParser :: Parser RemoteRepo remoteRepoParser =- helper <*>- (RemoteRepo <$>- strArgument (help "The name of the remmote repository." <> metavar "NAME") <*>- strArgument- (help "The (remote) repository root path." <>- metavar "REMOTE_DIRECTORY") <*>- (SshPrivKey <$>- strArgument- (help- "Path to the SSH private key file used for authorization." <>- metavar "SSH_PRIV_KEY_FILE")) <*>- (SshRemoteHost <$>- ((,) <$> strArgument (help "Repo hostname or IP" <> metavar "HOST") <*>- argument- auto- (help "SSH-Port number" <> value 22 <> showDefault <> metavar "PORT"))) <*>- (SshRemoteUser <$>- strArgument (help "SSH-User to login" <> metavar "USER")))+ helper+ <*> ( RemoteRepo+ <$> strArgument+ ( help "The name of the remmote repository."+ <> metavar "NAME"+ )+ <*> strArgument+ ( help "The (remote) repository root path."+ <> metavar "REMOTE_DIRECTORY"+ )+ <*> ( SshPrivKey <$> strArgument+ ( help+ "Path to the SSH private key file used for authorization."+ <> metavar "SSH_PRIV_KEY_FILE"+ )+ )+ <*> ( SshRemoteHost+ <$> ( (,)+ <$> strArgument+ ( help "Repo hostname or IP"+ <> metavar "HOST"+ )+ <*> argument+ auto+ ( help "SSH-Port number"+ <> value 22+ <> showDefault+ <> metavar "PORT"+ )+ )+ )+ <*> ( SshRemoteUser <$> strArgument+ (help "SSH-User to login" <> metavar "USER")+ )+ ) sharedImageNameParser :: Parser SharedImageName sharedImageNameParser =- helper <*>- (SharedImageName <$>- strArgument (help "Shared image name" <> metavar "NAME"))+ helper+ <*> ( SharedImageName+ <$> strArgument (help "Shared image name" <> metavar "NAME")+ ) -b9Version :: String-b9Version = showVersion version
src/lib/B9.hs view
@@ -11,11 +11,30 @@ -} -module B9 (configure, b9_version, module X) where+module B9 ( b9Version, b9VersionString+ , B9RunParameters(..)+ , runB9+ , defaultB9RunParameters+ , runShowVersion+ , runBuildArtifacts+ , runFormatBuildFiles+ , runPush+ , runPull+ , runRun+ , runGcLocalRepoCache+ , runGcRemoteRepoCache+ , runListSharedImages+ , runAddRepo+ , runLookupLocalSharedImage+ , module X) where import Control.Applicative as X import Control.Monad as X import Control.Monad.IO.Class as X+import Control.Exception(throwIO, catch)+import System.IO.Error (isDoesNotExistError)+import System.Directory (removeFile) import Data.Monoid as X+import Data.IORef import Data.List as X import Data.Maybe as X import Text.Show.Pretty as X (ppShow)@@ -25,19 +44,195 @@ import Text.Printf as X (printf) import Data.Version as X import B9.Builder as X+import B9.Invokation as X import Paths_b9 (version)-import qualified B9.LibVirtLXC as LibVirtLXC---- | Merge 'existingConfig' with the configuration from the main b9 config--- file. If the file does not exists, a new config file with the given--- configuration will be written. The return value is a parser for the config--- file. Returning the raw config file parser allows modules unkown to--- 'B9.B9Config' to add their own values to the shared config file.-configure :: MonadIO m => Maybe SystemPath -> B9Config -> m ConfigParser-configure b9ConfigPath existingConfig = do- writeInitialB9Config b9ConfigPath existingConfig LibVirtLXC.setDefaultConfig- readB9Config b9ConfigPath+import Data.Function (on)+import Control.Lens as Lens ((^.), over, (.~), (%~), set) -- | Return the cabal package version of the B9 library.-b9_version :: Version-b9_version = version+b9Version :: Version+b9Version = version++-- | Return the cabal package version of the B9 library,+-- formatted using `showVersion`.+b9VersionString :: String+b9VersionString = showVersion version++-- | A data structure that contains the `B9Invokation`+-- as well as build parameters.+data B9RunParameters a =+ B9RunParameters+ B9ConfigOverride+ (B9Invokation a)+ BuildVariables++-- | Run a b9 build.+-- Return `True` if the build was successful.+runB9 :: B9RunParameters a -> IO (a, Bool)+runB9 (B9RunParameters overrides action vars) = do+ invokeB9 $ do+ modifyInvokationConfig+ (over envVars (++ vars) . const (overrides ^. customB9Config))+ mapM_ overrideB9ConfigPath (overrides ^. customB9ConfigPath)+ action++defaultB9RunParameters :: B9Invokation a -> B9RunParameters a+defaultB9RunParameters ba =+ B9RunParameters (B9ConfigOverride Nothing mempty) ba mempty++runShowVersion :: B9Invokation ()+runShowVersion = doAfterConfiguration $ do+ liftIO $ putStrLn b9VersionString+ return True++runBuildArtifacts :: [FilePath] -> B9Invokation ()+runBuildArtifacts buildFiles = do+ generators <- mapM consult buildFiles+ buildArtifacts (mconcat generators)++runFormatBuildFiles :: [FilePath] -> B9Invokation ()+runFormatBuildFiles buildFiles = doAfterConfiguration $ liftIO $ do+ generators <- mapM consult buildFiles+ let generatorsFormatted = map ppShow (generators :: [ArtifactGenerator])+ putStrLn `mapM` (generatorsFormatted)+ (uncurry writeFile) `mapM` (buildFiles `zip` generatorsFormatted)+ return True++runPush :: SharedImageName -> B9Invokation ()+runPush name = do+ modifyInvokationConfig (keepTempDirs .~ False)+ run $ do+ conf <- getConfig+ if not (isJust (conf ^. repository))+ then do+ errorL+ "No repository specified! Use '-r' to specify a repo BEFORE 'push'."+ return False+ else do+ pushSharedImageLatestVersion name+ return True+++runPull :: Maybe SharedImageName -> B9Invokation ()+runPull mName = do+ modifyInvokationConfig (keepTempDirs .~ False)+ run (pullRemoteRepos >> maybePullImage)+ where maybePullImage = maybe (return True) pullLatestImage mName++runRun :: SharedImageName -> [String] -> B9Invokation ()+runRun (SharedImageName name) cmdAndArgs = do+ modifyInvokationConfig+ (Lens.set keepTempDirs False . Lens.set interactive True)+ buildArtifacts runCmdAndArgs+ where+ runCmdAndArgs = Artifact+ (IID ("run-" ++ name))+ ( VmImages+ [ImageTarget Transient (From name KeepSize) (MountPoint "/")]+ ( VmScript X86_64+ [SharedDirectory "." (MountPoint "/mnt/CWD")]+ (Run (head cmdAndArgs') (tail cmdAndArgs'))+ )+ )+ where+ cmdAndArgs' = if null cmdAndArgs then ["/usr/bin/zsh"] else cmdAndArgs+++runGcLocalRepoCache :: B9Invokation ()+runGcLocalRepoCache = do+ modifyInvokationConfig (keepTempDirs .~ False)+ impl+ where+ impl = run $ do+ toDelete <- (obsoleteSharedmages . map snd)+ <$> lookupSharedImages (== Cache) (const True)+ imgDir <- getSharedImagesCacheDir+ let filesToDelete = (imgDir </>) <$> (infoFiles ++ imgFiles)+ infoFiles = sharedImageFileName <$> toDelete+ imgFiles = (imageFileName . sharedImageImage) <$> toDelete+ if null filesToDelete+ then liftIO $ do+ putStrLn "\n\nNO IMAGES TO DELETE\n"+ return True+ else liftIO $ do+ putStrLn "DELETING FILES:"+ putStrLn (unlines filesToDelete)+ mapM_ removeIfExists filesToDelete+ return True+ obsoleteSharedmages :: [SharedImage] -> [SharedImage]+ obsoleteSharedmages =+ concatMap (tail . reverse) . filter ((> 1) . length) . groupBy+ ((==) `on` siName)+ removeIfExists :: FilePath -> IO ()+ removeIfExists fileName = removeFile fileName `catch` handleExists+ where+ handleExists e | isDoesNotExistError e = return ()+ | otherwise = throwIO e++runGcRemoteRepoCache :: B9Invokation ()+runGcRemoteRepoCache = do+ modifyInvokationConfig (keepTempDirs .~ False)+ run $ do+ repos <- getSelectedRepos+ cache <- getRepoCache+ mapM_ (cleanRemoteRepo cache) repos+ return True++runListSharedImages :: B9Invokation ()+runListSharedImages = do+ modifyInvokationConfig (keepTempDirs .~ False)+ run $ do+ remoteRepo <- getSelectedRemoteRepo+ let repoPred = maybe (== Cache) ((==) . toRemoteRepository) remoteRepo+ allRepos <- getRemoteRepos+ if isNothing remoteRepo+ then liftIO $ do+ putStrLn "Showing local shared images only."+ putStrLn+ $ "\nTo view the contents of a remote repo add \n\+ \the '-r' switch with one of the remote \n\+ \repository ids."+ else liftIO $ putStrLn+ ("Showing shared images on: " ++ remoteRepoRepoId (fromJust remoteRepo))+ when (not (null allRepos)) $ liftIO $ do+ putStrLn "\nAvailable remote repositories:"+ mapM_ (putStrLn . (" * " ++) . remoteRepoRepoId) allRepos+ imgs <- lookupSharedImages repoPred (const True)+ if null imgs+ then liftIO $ putStrLn "\n\nNO SHARED IMAGES\n"+ else liftIO $ do+ putStrLn ""+ putStrLn $ prettyPrintSharedImages $ map snd imgs+ return True+++runAddRepo :: RemoteRepo -> B9Invokation ()+runAddRepo repo = do+ repo' <- remoteRepoCheckSshPrivKey repo+ modifyPermanentConfig+ ( remoteRepos+ %~ ( mappend [repo']+ . filter ((== remoteRepoRepoId repo') . remoteRepoRepoId)+ )+ )++runLookupLocalSharedImage+ :: SharedImageName -> B9Invokation (Maybe SharedImageBuildId)+runLookupLocalSharedImage n = do+ ref <- liftIO (newIORef Nothing)+ run (go ref)+ liftIO (readIORef ref)+ where+ go ref = do+ res <-+ extractNewestImageFromResults+ <$> lookupSharedImages isAvailableOnLocalHost hasTheDesiredName+ liftIO $ writeIORef ref res+ return True+ where+ extractNewestImageFromResults =+ listToMaybe . map toBuildId . take 1 . reverse . map snd+ where toBuildId (SharedImage _ _ i _ _) = i+ isAvailableOnLocalHost = (Cache ==)+ hasTheDesiredName (SharedImage n' _ _ _ _) = n == n'+
src/lib/B9/ArtifactGeneratorImpl.hs view
@@ -9,7 +9,7 @@ import B9.VmBuilder import B9.Vm import B9.DiskImageBuilder-import B9.ConfigUtils hiding (tell)+import Data.ConfigFile.B9Extras hiding (tell) import B9.Content.StringTemplate import B9.Content.Generator import B9.Content.AST@@ -17,6 +17,7 @@ import qualified Data.ByteString as B import qualified Data.Text as T import qualified Data.Text.Encoding as E+import Control.Lens (view) import Data.Data import Data.Generics.Schemes import Data.Generics.Aliases@@ -50,7 +51,7 @@ -- | Run an artifact generator to produce the artifacts. assemble :: ArtifactGenerator -> B9 [AssembledArtifact] assemble artGen = do- b9cfgEnvVars <- envVars <$> getConfig+ b9cfgEnvVars <- view envVars <$> getConfig buildId <- getBuildId buildDate <- getBuildDate case evalArtifactGenerator buildId buildDate b9cfgEnvVars artGen of@@ -273,7 +274,6 @@ liftIO (B.writeFile toAbs result) sgChangePerm toAbs p - sgReadSourceFile :: [(String, String)] -> SourceFile -> B9 B.ByteString sgReadSourceFile env = withEnvironment env . readTemplateFile @@ -298,7 +298,6 @@ data SGPerm = SGSetPerm (Int, Int, Int) | KeepPerm deriving (Read, Show, Typeable, Data, Eq)- sgGetFroms :: SourceGenerator -> [SourceFile] sgGetFroms (SG _ (SGFiles fs) _ _) = fs
src/lib/B9/B9Config.hs view
@@ -3,30 +3,51 @@ The properties are independent of specific build targets. -} module B9.B9Config ( B9Config(..)+ , verbosity+ , logFile+ , buildDirRoot+ , keepTempDirs+ , execEnvType+ , profileFile+ , envVars+ , uniqueBuildDirs+ , repositoryCache+ , repository+ , interactive+ , libVirtLXCConfigs+ , remoteRepos+ , B9ConfigOverride(..)+ , customB9Config+ , customB9ConfigPath , defaultB9ConfigFile , defaultRepositoryCache , defaultB9Config- , getB9ConfigFile- , writeB9Config- , writeInitialB9Config+ , openOrCreateB9Config+ , writeB9ConfigParser , readB9Config , parseB9Config+ , appendPositionalArguments+ , modifyConfigParser+ , b9ConfigToConfigParser , LogLevel(..) , ExecEnvType (..) , BuildVariables+ , module X ) where import Data.Maybe (fromMaybe)-import Control.Monad import Control.Exception import Data.Function (on) import Control.Monad.IO.Class import System.Directory-import Text.Printf import qualified Data.Semigroup as Sem import Data.Monoid--import B9.ConfigUtils+import Data.List (partition, sortBy)+import Data.ConfigFile.B9Extras+import B9.B9Config.LibVirtLXC as X+import B9.B9Config.Repository as X+import Control.Lens.TH+import Control.Monad ((>=>)) type BuildVariables = [(String,String)] @@ -35,52 +56,96 @@ data LogLevel = LogTrace | LogDebug | LogInfo | LogError | LogNothing deriving (Eq, Show, Ord, Read) -data B9Config = B9Config { verbosity :: Maybe LogLevel- , logFile :: Maybe FilePath- , buildDirRoot :: Maybe FilePath- , keepTempDirs :: Bool- , execEnvType :: ExecEnvType- , profileFile :: Maybe FilePath- , envVars :: BuildVariables- , uniqueBuildDirs :: Bool- , repositoryCache :: Maybe SystemPath- , repository :: Maybe String- , interactive :: Bool+data B9Config = B9Config { _verbosity :: Maybe LogLevel+ , _logFile :: Maybe FilePath+ , _buildDirRoot :: Maybe FilePath+ , _keepTempDirs :: Bool+ , _execEnvType :: ExecEnvType+ , _profileFile :: Maybe FilePath+ , _envVars :: BuildVariables+ , _uniqueBuildDirs :: Bool+ , _repositoryCache :: Maybe SystemPath+ , _repository :: Maybe String+ , _interactive :: Bool+ , _libVirtLXCConfigs :: Maybe LibVirtLXCConfig+ , _remoteRepos :: [RemoteRepo] } deriving (Show) instance Sem.Semigroup B9Config where c <> c' =- B9Config { verbosity = getLast $ on mappend (Last . verbosity) c c'- , logFile = getLast $ on mappend (Last . logFile) c c'- , buildDirRoot = getLast $ on mappend (Last . buildDirRoot) c c'- , keepTempDirs = getAny $ on mappend (Any . keepTempDirs) c c'- , execEnvType = LibVirtLXC- , profileFile = getLast $ on mappend (Last . profileFile) c c'- , envVars = on mappend envVars c c'- , uniqueBuildDirs = getAll ((mappend `on` (All . uniqueBuildDirs)) c c')- , repositoryCache = getLast $ on mappend (Last . repositoryCache) c c'- , repository = getLast ((mappend `on` (Last . repository)) c c')- , interactive = getAny ((mappend `on` (Any . interactive)) c c')+ B9Config { _verbosity = getLast $ on mappend (Last . _verbosity) c c'+ , _logFile = getLast $ on mappend (Last . _logFile) c c'+ , _buildDirRoot = getLast $ on mappend (Last . _buildDirRoot) c c'+ , _keepTempDirs = getAny $ on mappend (Any . _keepTempDirs) c c'+ , _execEnvType = LibVirtLXC+ , _profileFile = getLast $ on mappend (Last . _profileFile) c c'+ , _envVars = on mappend _envVars c c'+ , _uniqueBuildDirs = getAll ((mappend `on` (All . _uniqueBuildDirs)) c c')+ , _repositoryCache = getLast $ on mappend (Last . _repositoryCache) c c'+ , _repository = getLast ((mappend `on` (Last . _repository)) c c')+ , _interactive = getAny ((mappend `on` (Any . _interactive)) c c')+ , _libVirtLXCConfigs = getLast ((mappend `on` (Last . _libVirtLXCConfigs)) c c')+ , _remoteRepos = (mappend `on` _remoteRepos) c c' } instance Monoid B9Config where mappend = (Sem.<>) mempty = B9Config Nothing Nothing Nothing False LibVirtLXC Nothing [] True- Nothing Nothing False+ Nothing Nothing False Nothing [] +-- | Override b9 configuration items and/or the path of the b9 configuration file.+-- This is useful, i.e. when dealing with command line parameters.+data B9ConfigOverride = B9ConfigOverride+ { _customB9ConfigPath :: Maybe SystemPath+ , _customB9Config :: B9Config+ }++makeLenses ''B9Config+makeLenses ''B9ConfigOverride++-- | Open the configuration file that contains the 'B9Config'.+-- If the configuration does not exist, write a default configuration file,+-- and create a all missing directories.+openOrCreateB9Config :: MonadIO m => Maybe SystemPath -> m ConfigParser+openOrCreateB9Config cfgPath = do+ cfgFile <- resolve (fromMaybe defaultB9ConfigFile cfgPath)+ ensureDir cfgFile+ liftIO $ do+ exists <- doesFileExist cfgFile+ if exists+ then readIniFile (Path cfgFile)+ else+ let res = b9ConfigToConfigParser defaultB9Config emptyCP+ in case res of+ Left e -> throwIO (IniFileException cfgFile e)+ Right cp -> writeFile cfgFile (to_string cp) >> return cp++-- | Write the configuration in the 'ConfigParser' to either the user supplied+-- configuration file path or to 'defaultB9ConfigFile'.+-- Create all missing (parent) directories.+writeB9ConfigParser :: MonadIO m => Maybe SystemPath -> ConfigParser -> m ()+writeB9ConfigParser cfgFileIn cp = do+ cfgFile <- resolve (fromMaybe defaultB9ConfigFile cfgFileIn)+ ensureDir cfgFile+ liftIO (writeFile cfgFile (to_string cp))++ defaultB9Config :: B9Config-defaultB9Config = B9Config { verbosity = Just LogInfo- , logFile = Nothing- , buildDirRoot = Nothing- , keepTempDirs = False- , execEnvType = LibVirtLXC- , profileFile = Nothing- , envVars = []- , uniqueBuildDirs = True- , repository = Nothing- , repositoryCache = Just defaultRepositoryCache- , interactive = False- }+defaultB9Config = B9Config+ { _verbosity = Just LogInfo+ , _logFile = Nothing+ , _buildDirRoot = Nothing+ , _keepTempDirs = False+ , _execEnvType = LibVirtLXC+ , _profileFile = Nothing+ , _envVars = []+ , _uniqueBuildDirs = True+ , _repository = Nothing+ , _repositoryCache = Just defaultRepositoryCache+ , _interactive = False+ , _libVirtLXCConfigs = Just defaultLibVirtLXCConfig+ , _remoteRepos = []+ } defaultRepositoryCache :: SystemPath defaultRepositoryCache = InB9UserDir "repo-cache"@@ -109,75 +174,80 @@ cfgFileSection :: String cfgFileSection = "global" -getB9ConfigFile :: MonadIO m => Maybe SystemPath -> m FilePath-getB9ConfigFile mCfgFile = do- cfgFile <- resolve (fromMaybe defaultB9ConfigFile mCfgFile)- ensureDir cfgFile- return cfgFile--writeB9Config :: MonadIO m- => Maybe SystemPath- -> ConfigParser- -> m ()-writeB9Config cfgFileIn cp = do- cfgFile <- getB9ConfigFile cfgFileIn- liftIO (writeFile cfgFile (to_string cp))+-- | Parse a 'B9Config', modify it, and merge it back to the given 'ConfigParser'.+modifyConfigParser+ :: (B9Config -> B9Config) -> ConfigParser -> Either CPError ConfigParser+modifyConfigParser f cp = do+ cfg <- parseB9Config cp+ cp2 <- b9ConfigToConfigParser (f cfg) emptyCP+ return (merge cp cp2) -writeInitialB9Config :: MonadIO m- => Maybe SystemPath- -> B9Config- -> ConfigParser- -> m ()-writeInitialB9Config Nothing cliCfg cpNonGlobal = writeInitialB9Config- (Just defaultB9ConfigFile)- cliCfg- cpNonGlobal-writeInitialB9Config (Just cfgPath) cliCfg cpNonGlobal = do- cfgFile <- resolve cfgPath- ensureDir cfgFile- exists <- liftIO $ doesFileExist cfgFile- unless exists $- let res = do- let cp = emptyCP- c = defaultB9Config <> cliCfg- cp1 <- add_section cp cfgFileSection- cp2 <- setshow cp1 cfgFileSection verbosityK (verbosity c)- cp3 <- setshow cp2 cfgFileSection logFileK (logFile c)- cp4 <- setshow cp3 cfgFileSection buildDirRootK (buildDirRoot c)- cp5 <- setshow cp4 cfgFileSection keepTempDirsK (keepTempDirs c)- cp6 <- setshow cp5 cfgFileSection execEnvTypeK (execEnvType c)- cp7 <- setshow cp6 cfgFileSection profileFileK (profileFile c)- cp8 <- setshow cp7 cfgFileSection envVarsK (envVars c)- cp9 <- setshow cp8 cfgFileSection uniqueBuildDirsK (uniqueBuildDirs c)- cpA <- setshow cp9 cfgFileSection repositoryCacheK (repositoryCache c)- cpB <- setshow cpA cfgFileSection repositoryK (repository c)- return $ merge cpB cpNonGlobal- in case res of- Left e -> liftIO (throwIO (IniFileException cfgFile e))- Right cp -> liftIO (writeFile cfgFile (to_string cp))+-- | Append a config file section for the 'B9Config' to a 'ConfigParser'.+b9ConfigToConfigParser+ :: B9Config -> ConfigParser -> Either CPError ConfigParser+b9ConfigToConfigParser c cp = do+ cp1 <- add_section cp cfgFileSection+ cp2 <- setshow cp1 cfgFileSection verbosityK (_verbosity c)+ cp3 <- setshow cp2 cfgFileSection logFileK (_logFile c)+ cp4 <- setshow cp3 cfgFileSection buildDirRootK (_buildDirRoot c)+ cp5 <- setshow cp4 cfgFileSection keepTempDirsK (_keepTempDirs c)+ cp6 <- setshow cp5 cfgFileSection execEnvTypeK (_execEnvType c)+ cp7 <- setshow cp6 cfgFileSection profileFileK (_profileFile c)+ cp8 <- setshow cp7 cfgFileSection envVarsK (_envVars c)+ cp9 <- setshow cp8 cfgFileSection uniqueBuildDirsK (_uniqueBuildDirs c)+ cpA <- setshow cp9 cfgFileSection repositoryCacheK (_repositoryCache c)+ cpB <-+ ( foldr (>=>)+ return+ (libVirtLXCConfigToConfigParser <$> (_libVirtLXCConfigs c))+ )+ cpA+ cpFinal <- (foldr (>=>) return (remoteRepoToConfigParser <$> _remoteRepos c))+ cpB+ setshow cpFinal cfgFileSection repositoryK (_repository c) readB9Config :: MonadIO m => Maybe SystemPath -> m ConfigParser-readB9Config Nothing = readB9Config (Just defaultB9ConfigFile)-readB9Config (Just cfgFile) = readIniFile cfgFile+readB9Config cfgFile = readIniFile (fromMaybe defaultB9ConfigFile cfgFile) -parseB9Config :: ConfigParser -> Either String B9Config+parseB9Config :: ConfigParser -> Either CPError B9Config parseB9Config cp = let getr :: (Get_C a) => OptionSpec -> Either CPError a getr = get cp cfgFileSection getB9Config =- B9Config <$> getr verbosityK- <*> getr logFileK- <*> getr buildDirRootK- <*> getr keepTempDirsK- <*> getr execEnvTypeK- <*> getr profileFileK- <*> getr envVarsK- <*> getr uniqueBuildDirsK- <*> getr repositoryCacheK- <*> getr repositoryK- <*> pure False- in case getB9Config of- Left err ->- Left (printf "Failed to parse B9 configuration file: '%s'" (show err))- Right x ->- Right x+ B9Config+ <$> getr verbosityK+ <*> getr logFileK+ <*> getr buildDirRootK+ <*> getr keepTempDirsK+ <*> getr execEnvTypeK+ <*> getr profileFileK+ <*> getr envVarsK+ <*> getr uniqueBuildDirsK+ <*> getr repositoryCacheK+ <*> getr repositoryK+ <*> pure False+ <*> Right (either (const Nothing) Just (parseLibVirtLXCConfig cp))+ <*> parseRemoteRepos cp+ in getB9Config+++-- | If environment variables @arg_1 .. arg_n@ are bound+-- and a list of @k@ additional values are passed to this function,+-- store them with keys @arg_(n+1) .. arg_(n+k)@.+appendPositionalArguments :: [String] -> B9Config -> B9Config+appendPositionalArguments extraPositional c = c+ { _envVars = appendVars (_envVars c)+ }+ where+ appendVars argsOld =+ let (oldPositional, oldOther) =+ partition (("arg_" ==) . take 4 . fst) argsOld+ oldPositionalSortedByPosition =+ map snd+ $ sortBy (compare `on` fst)+ $ map (\(x, y) -> ((read :: String -> Int) $ drop 4 $ x, y))+ $ oldPositional+ newPositional =+ let xs = oldPositionalSortedByPosition ++ extraPositional+ in [ ("arg_" ++ show i, a) | (i, a) <- zip [1 :: Int ..] xs ]+ in newPositional ++ oldOther
+ src/lib/B9/B9Config/LibVirtLXC.hs view
@@ -0,0 +1,128 @@+module B9.B9Config.LibVirtLXC (+ libVirtLXCConfigToConfigParser+ , defaultLibVirtLXCConfig+ , parseLibVirtLXCConfig+ , LibVirtLXCConfig(..)+ , LXCGuestCapability(..)+ ) where++import B9.DiskImages+import B9.ExecEnv+import Data.ConfigFile.B9Extras+++data LibVirtLXCConfig = LibVirtLXCConfig { useSudo :: Bool+ , virshPath :: FilePath+ , emulator :: FilePath+ , virshURI :: FilePath+ , networkId :: Maybe String+ , guestCapabilities :: [LXCGuestCapability]+ , guestRamSize :: RamSize+ } deriving (Read, Show)++-- | Available linux capabilities for lxc containers. This maps directly to the+-- capabilities defined in 'man 7 capabilities'.+data LXCGuestCapability = CAP_MKNOD+ | CAP_AUDIT_CONTROL+ | CAP_AUDIT_READ+ | CAP_AUDIT_WRITE+ | CAP_BLOCK_SUSPEND+ | CAP_CHOWN+ | CAP_DAC_OVERRIDE+ | CAP_DAC_READ_SEARCH+ | CAP_FOWNER+ | CAP_FSETID+ | CAP_IPC_LOCK+ | CAP_IPC_OWNER+ | CAP_KILL+ | CAP_LEASE+ | CAP_LINUX_IMMUTABLE+ | CAP_MAC_ADMIN+ | CAP_MAC_OVERRIDE+ | CAP_NET_ADMIN+ | CAP_NET_BIND_SERVICE+ | CAP_NET_BROADCAST+ | CAP_NET_RAW+ | CAP_SETGID+ | CAP_SETFCAP+ | CAP_SETPCAP+ | CAP_SETUID+ | CAP_SYS_ADMIN+ | CAP_SYS_BOOT+ | CAP_SYS_CHROOT+ | CAP_SYS_MODULE+ | CAP_SYS_NICE+ | CAP_SYS_PACCT+ | CAP_SYS_PTRACE+ | CAP_SYS_RAWIO+ | CAP_SYS_RESOURCE+ | CAP_SYS_TIME+ | CAP_SYS_TTY_CONFIG+ | CAP_SYSLOG+ | CAP_WAKE_ALARM+ deriving (Read, Show)++defaultLibVirtLXCConfig :: LibVirtLXCConfig+defaultLibVirtLXCConfig = LibVirtLXCConfig+ True+ "/usr/bin/virsh"+ "/usr/lib/libvirt/libvirt_lxc"+ "lxc:///"+ Nothing+ [ CAP_MKNOD+ , CAP_SYS_ADMIN+ , CAP_SYS_CHROOT+ , CAP_SETGID+ , CAP_SETUID+ , CAP_NET_BIND_SERVICE+ , CAP_SETPCAP+ , CAP_SYS_PTRACE+ , CAP_SYS_MODULE+ ]+ (RamSize 1 GB)++cfgFileSection :: String+cfgFileSection = "libvirt-lxc"+useSudoK :: String+useSudoK = "use_sudo"+virshPathK :: String+virshPathK = "virsh_path"+emulatorK :: String+emulatorK = "emulator_path"+virshURIK :: String+virshURIK = "connection"+networkIdK :: String+networkIdK = "network"+guestCapabilitiesK :: String+guestCapabilitiesK = "guest_capabilities"+guestRamSizeK :: String+guestRamSizeK = "guest_ram_size"++libVirtLXCConfigToConfigParser+ :: LibVirtLXCConfig -> ConfigParser -> Either CPError ConfigParser+libVirtLXCConfigToConfigParser c cp = do+ cp1 <- add_section cp cfgFileSection+ cp2 <- setshow cp1 cfgFileSection useSudoK $ useSudo c+ cp3 <- set cp2 cfgFileSection virshPathK $ virshPath c+ cp4 <- set cp3 cfgFileSection emulatorK $ emulator c+ cp5 <- set cp4 cfgFileSection virshURIK $ virshURI c+ cp6 <- setshow cp5 cfgFileSection networkIdK $ networkId c+ cp7 <- setshow cp6 cfgFileSection guestCapabilitiesK $ guestCapabilities c+ setshow cp7 cfgFileSection guestRamSizeK $ guestRamSize c++parseLibVirtLXCConfig :: ConfigParser -> Either CPError LibVirtLXCConfig+parseLibVirtLXCConfig cp =+ let getr :: (Get_C a) => OptionSpec -> Either CPError a+ getr = get cp cfgFileSection+ in LibVirtLXCConfig+ <$> (getr useSudoK)+ <*> (getr virshPathK)+ <*> (getr emulatorK)+ <*> (getr virshURIK)+ <*> (getr networkIdK)+ <*> (getr guestCapabilitiesK)+ <*> (getr guestRamSizeK)++++
+ src/lib/B9/B9Config/Repository.hs view
@@ -0,0 +1,90 @@+module B9.B9Config.Repository ( RemoteRepo(..)+ , remoteRepoRepoId+ , RepoCache(..)+ , SshPrivKey(..)+ , SshRemoteHost(..)+ , SshRemoteUser(..)+ , remoteRepoToConfigParser+ , parseRemoteRepos+ ) where++import Data.Data+import Data.List (isSuffixOf)+import Data.ConfigFile++newtype RepoCache = RepoCache FilePath+ deriving (Read, Show, Typeable, Data)++data RemoteRepo = RemoteRepo String+ FilePath+ SshPrivKey+ SshRemoteHost+ SshRemoteUser+ deriving (Read, Show, Typeable, Data)++remoteRepoRepoId :: RemoteRepo -> String+remoteRepoRepoId (RemoteRepo repoId _ _ _ _) = repoId++newtype SshPrivKey = SshPrivKey FilePath+ deriving (Read, Show, Typeable, Data)++newtype SshRemoteHost = SshRemoteHost (String,Int)+ deriving (Read, Show, Typeable, Data)++newtype SshRemoteUser = SshRemoteUser String+ deriving (Read, Show, Typeable, Data)+++-- | Persist a repo to a configuration file.+remoteRepoToConfigParser :: RemoteRepo+ -> ConfigParser+ -> Either CPError ConfigParser+remoteRepoToConfigParser repo cpIn = cpWithRepo+ where section = repoId ++ repoSectionSuffix+ (RemoteRepo repoId+ remoteRootDir+ (SshPrivKey keyFile)+ (SshRemoteHost (host,port))+ (SshRemoteUser user)) = repo+ cpWithRepo = do cp1 <- add_section cpIn section+ cp2 <- set cp1 section repoRemotePathK remoteRootDir+ cp3 <- set cp2 section repoRemoteSshKeyK keyFile+ cp4 <- set cp3 section repoRemoteSshHostK host+ cp5 <- setshow cp4 section repoRemoteSshPortK port+ set cp5 section repoRemoteSshUserK user++-- | Load a repository from a configuration file that has been written by+-- 'writeRepositoryToB9Config'.+parseRemoteRepos :: ConfigParser -> Either CPError [RemoteRepo]+parseRemoteRepos cp = traverse parseRepoSection repoSections+ where+ repoSections =+ filter (repoSectionSuffix `isSuffixOf`) (sections cp)+ parseRepoSection section = parseResult+ where+ getsec :: Get_C a => OptionSpec -> Either CPError a+ getsec = get cp section+ parseResult =+ RemoteRepo repoId+ <$> getsec repoRemotePathK+ <*> (SshPrivKey <$> getsec repoRemoteSshKeyK)+ <*> (SshRemoteHost <$> ((,) <$> getsec repoRemoteSshHostK+ <*> getsec repoRemoteSshPortK))+ <*> (SshRemoteUser <$> getsec repoRemoteSshUserK)+ where+ repoId = let prefixLen = length section - suffixLen+ suffixLen = length repoSectionSuffix+ in take prefixLen section++repoSectionSuffix :: String+repoSectionSuffix = "-repo"+repoRemotePathK :: String+repoRemotePathK = "remote_path"+repoRemoteSshKeyK :: String+repoRemoteSshKeyK = "ssh_priv_key_file"+repoRemoteSshHostK :: String+repoRemoteSshHostK = "ssh_remote_host"+repoRemoteSshPortK :: String+repoRemoteSshPortK = "ssh_remote_port"+repoRemoteSshUserK :: String+repoRemoteSshUserK = "ssh_remote_user"
src/lib/B9/B9Monad.hs view
@@ -7,19 +7,21 @@ This module is used by the _effectful_ functions in this library. -} module B9.B9Monad- (B9, run, traceL, dbgL, infoL, errorL, getConfigParser, getConfig,+ (B9, run, traceL, dbgL, infoL, errorL, getConfig, getBuildId, getBuildDate, getBuildDir, getExecEnvType, getSelectedRemoteRepo, getRemoteRepos, getRepoCache, cmd) where import B9.B9Config-import B9.ConfigUtils+import B9.Invokation import B9.Repository import Control.Applicative import Control.Exception (bracket) import Control.Monad import Control.Monad.IO.Class import Control.Monad.State+import Control.Monad.Reader(ask)+import Control.Lens((&), (.~)) import qualified Data.ByteString.Char8 as B import Data.Functor () import Data.Maybe@@ -40,12 +42,10 @@ data BuildState = BuildState {bsBuildId :: String ,bsBuildDate :: String- ,bsCfgParser :: ConfigParser ,bsCfg :: B9Config ,bsBuildDir :: FilePath ,bsLogFileHandle :: Maybe SysIO.Handle ,bsSelectedRemoteRepo :: Maybe RemoteRepo- ,bsRemoteRepos :: [RemoteRepo] ,bsRepoCache :: RepoCache ,bsProf :: [ProfilingEntry] ,bsStartTime :: UTCTime@@ -57,84 +57,82 @@ String deriving (Eq,Show) -run :: ConfigParser -> B9Config -> B9 a -> IO a-run cfgParser cfg action =- do buildId <- generateBuildId- now <- getCurrentTime- withBuildDir buildId- (withLogFile . run' buildId now)- where withLogFile f =- maybe (f Nothing)- (\logf ->- SysIO.withFile logf- SysIO.AppendMode- (f . Just))- (logFile cfg)- withBuildDir buildId = bracket (createBuildDir buildId) removeBuildDir- run' buildId now buildDir logFileHandle =- do maybe (return ())- setCurrentDirectory- (buildDirRoot cfg)- -- Check repositories- repoCache <-- initRepoCache (fromMaybe defaultRepositoryCache (repositoryCache cfg))- let remoteRepos = getConfiguredRemoteRepos cfgParser- buildDate = formatTime undefined "%F-%T" now- remoteRepos' <- mapM (initRemoteRepo repoCache) remoteRepos- let ctx =- BuildState buildId- buildDate- cfgParser- cfg- buildDir- logFileHandle- selectedRemoteRepo- remoteRepos'- repoCache- []- now- (interactive cfg)- selectedRemoteRepo =- do sel <- repository cfg- lookupRemoteRepo remoteRepos sel <|>- error (printf "selected remote repo '%s' not configured, valid remote repos are: '%s'"- sel- (show remoteRepos))- (r,ctxOut) <- runStateT (runB9 wrappedAction) ctx- -- Write a profiling report- when (isJust (profileFile cfg)) $- writeFile (fromJust (profileFile cfg))- (unlines $ show <$> reverse (bsProf ctxOut))- return r- createBuildDir buildId =- if uniqueBuildDirs cfg- then do let subDir = "BUILD-" ++ buildId- buildDir <- resolveBuildDir subDir- createDirectory buildDir- canonicalizePath buildDir- else do let subDir = "BUILD-" ++ buildId- buildDir <- resolveBuildDir subDir- createDirectoryIfMissing True buildDir- canonicalizePath buildDir- where resolveBuildDir f =- case buildDirRoot cfg of- Nothing -> return f- Just root' ->- do createDirectoryIfMissing True root'- root <- canonicalizePath root'- return $ root </> f- removeBuildDir buildDir =- when (uniqueBuildDirs cfg && not (keepTempDirs cfg)) $- removeDirectoryRecursive buildDir- generateBuildId = printf "%08X" <$> (randomIO :: IO Word32)- -- Run the action build action- wrappedAction =- do startTime <- gets bsStartTime- r <- action- now <- liftIO getCurrentTime- let duration = show (now `diffUTCTime` startTime)- infoL (printf "DURATION: %s" duration)- return r+run :: B9 Bool -> B9Invokation ()+run action = doAfterConfiguration $ do+ cfg <- ask+ liftIO $ do+ buildId <- generateBuildId+ now <- getCurrentTime+ withBuildDir cfg buildId (withLogFile cfg . run' cfg buildId now)+ where+ withLogFile cfg f = maybe+ (f Nothing)+ (\logf -> SysIO.withFile logf SysIO.AppendMode (f . Just))+ (_logFile cfg)+ withBuildDir cfg buildId =+ bracket (createBuildDir cfg buildId) (removeBuildDir cfg)+ run' cfg buildId now buildDir logFileHandle = do+ maybe (return ()) setCurrentDirectory (_buildDirRoot cfg)+ -- Check repositories+ repoCache <- initRepoCache+ (fromMaybe defaultRepositoryCache (_repositoryCache cfg))+ let buildDate = formatTime undefined "%F-%T" now+ remoteRepos' <- mapM (initRemoteRepo repoCache) (_remoteRepos cfg)+ let+ ctx = BuildState buildId+ buildDate+ (cfg & remoteRepos .~ remoteRepos')+ buildDir+ logFileHandle+ selectedRemoteRepo+ repoCache+ []+ now+ (_interactive cfg)+ selectedRemoteRepo = do+ sel <- _repository cfg+ lookupRemoteRepo remoteRepos' sel <|> error+ ( printf+ "selected remote repo '%s' not configured, valid remote repos are: '%s'"+ sel+ (show remoteRepos')+ )+ (r, ctxOut) <- runStateT (runB9 wrappedAction) ctx+ -- Write a profiling report+ when (isJust (_profileFile cfg)) $ writeFile+ (fromJust (_profileFile cfg))+ (unlines $ show <$> reverse (bsProf ctxOut))+ return r+ createBuildDir cfg buildId = if _uniqueBuildDirs cfg+ then do+ let subDir = "BUILD-" ++ buildId+ buildDir <- resolveBuildDir subDir+ createDirectory buildDir+ canonicalizePath buildDir+ else do+ let subDir = "BUILD-" ++ buildId+ buildDir <- resolveBuildDir subDir+ createDirectoryIfMissing True buildDir+ canonicalizePath buildDir+ where+ resolveBuildDir f = case _buildDirRoot cfg of+ Nothing -> return f+ Just root' -> do+ createDirectoryIfMissing True root'+ root <- canonicalizePath root'+ return $ root </> f+ removeBuildDir cfg buildDir =+ when (_uniqueBuildDirs cfg && not (_keepTempDirs cfg))+ $ removeDirectoryRecursive buildDir+ generateBuildId = printf "%08X" <$> (randomIO :: IO Word32)+ -- Run the action build action+ wrappedAction = do+ startTime <- gets bsStartTime+ r <- action+ now <- liftIO getCurrentTime+ let duration = show (now `diffUTCTime` startTime)+ infoL (printf "DURATION: %s" duration)+ return r getBuildId :: B9 FilePath getBuildId = gets bsBuildId@@ -145,20 +143,17 @@ getBuildDir :: B9 FilePath getBuildDir = gets bsBuildDir -getConfigParser :: B9 ConfigParser-getConfigParser = gets bsCfgParser- getConfig :: B9 B9Config getConfig = gets bsCfg getExecEnvType :: B9 ExecEnvType-getExecEnvType = gets (execEnvType . bsCfg)+getExecEnvType = gets (_execEnvType . bsCfg) getSelectedRemoteRepo :: B9 (Maybe RemoteRepo) getSelectedRemoteRepo = gets bsSelectedRemoteRepo getRemoteRepos :: B9 [RemoteRepo]-getRemoteRepos = gets bsRemoteRepos+getRemoteRepos = gets (_remoteRepos . bsCfg) getRepoCache :: B9 RepoCache getRepoCache = gets bsRepoCache@@ -180,11 +175,9 @@ -- return expression cmd :: String -> B9 ()-cmd str =- do inheritStdIn <- gets bsInheritStdIn- if inheritStdIn- then interactiveCmd str- else nonInteractiveCmd str+cmd str = do+ inheritStdIn <- gets bsInheritStdIn+ if inheritStdIn then interactiveCmd str else nonInteractiveCmd str interactiveCmd :: String -> B9 () interactiveCmd str = void (cmdWithStdIn True str :: B9 Inherited)@@ -195,32 +188,30 @@ -- nonInteractiveCmd str = void (cmdWithStdIn False str :: B9 ClosedStream) nonInteractiveCmd str = void (cmdWithStdIn False str :: B9 Inherited) -cmdWithStdIn :: (InputSource stdin)- => Bool -> String -> B9 stdin-cmdWithStdIn toStdOut cmdStr =- do traceL $ "COMMAND: " ++ cmdStr- cmdLogger <- getCmdLogger- let outPipe =- if toStdOut- then CL.mapM_ B.putStr- else cmdLogger LogTrace- (cpIn,cpOut,cpErr,cph) <- streamingProcess (shell cmdStr)- e <-- liftIO $- runConcurrently $- Concurrently (cpOut $$ outPipe) *>- Concurrently (cpErr $$ cmdLogger LogInfo) *>- Concurrently (waitForStreamingProcess cph)- checkExitCode e- return cpIn- where getCmdLogger =- do lv <- gets $ verbosity . bsCfg- lfh <- gets bsLogFileHandle- return $ \level -> CL.mapM_ (logImpl lv lfh level . B.unpack)- checkExitCode ExitSuccess = traceL "COMMAND SUCCESS"- checkExitCode ec@(ExitFailure e) =- do errorL $ printf "COMMAND '%s' FAILED: %i!" cmdStr e- liftIO $ exitWith ec+cmdWithStdIn :: (InputSource stdin) => Bool -> String -> B9 stdin+cmdWithStdIn toStdOut cmdStr = do+ traceL $ "COMMAND: " ++ cmdStr+ cmdLogger <- getCmdLogger+ let outPipe = if toStdOut then CL.mapM_ B.putStr else cmdLogger LogTrace+ (cpIn, cpOut, cpErr, cph) <- streamingProcess (shell cmdStr)+ e <-+ liftIO+ $ runConcurrently+ $ Concurrently (cpOut $$ outPipe)+ *> Concurrently (cpErr $$ cmdLogger LogInfo)+ *> Concurrently (waitForStreamingProcess cph)+ checkExitCode e+ return cpIn+ where+ getCmdLogger = do+ lv <- gets $ _verbosity . bsCfg+ lfh <- gets bsLogFileHandle+ return $ \level -> CL.mapM_ (logImpl lv lfh level . B.unpack)+ checkExitCode ExitSuccess =+ traceL $ printf "COMMAND '%s' exited with exit code: 0" cmdStr+ checkExitCode ec@(ExitFailure e) = do+ errorL $ printf "COMMAND '%s' exited with exit code: %i" cmdStr e+ liftIO $ exitWith ec traceL :: String -> B9 () traceL = b9Log LogTrace@@ -235,37 +226,33 @@ errorL = b9Log LogError b9Log :: LogLevel -> String -> B9 ()-b9Log level msg =- do lv <- gets $ verbosity . bsCfg- lfh <- gets bsLogFileHandle- modify $ \ctx -> ctx {bsProf = LogEvent level msg : bsProf ctx}- B9 $ liftIO $ logImpl lv lfh level msg+b9Log level msg = do+ lv <- gets $ _verbosity . bsCfg+ lfh <- gets bsLogFileHandle+ modify $ \ctx -> ctx { bsProf = LogEvent level msg : bsProf ctx }+ B9 $ liftIO $ logImpl lv lfh level msg -logImpl- :: Maybe LogLevel -> Maybe SysIO.Handle -> LogLevel -> String -> IO ()-logImpl minLevel mh level msg =- do lm <- formatLogMsg level msg- when (isJust minLevel && level >= fromJust minLevel)- (putStr lm)- when (isJust mh) $- do SysIO.hPutStr (fromJust mh)- lm- SysIO.hFlush (fromJust mh)+logImpl :: Maybe LogLevel -> Maybe SysIO.Handle -> LogLevel -> String -> IO ()+logImpl minLevel mh level msg = do+ lm <- formatLogMsg level msg+ when (isJust minLevel && level >= fromJust minLevel) (putStr lm)+ when (isJust mh) $ do+ SysIO.hPutStr (fromJust mh) lm+ SysIO.hFlush (fromJust mh) formatLogMsg :: LogLevel -> String -> IO String-formatLogMsg l msg =- do utct <- getCurrentTime- let time = formatTime defaultTimeLocale "%H:%M:%S" utct- return $ unlines $ printf "[%s] %s - %s" (printLevel l) time <$> lines msg+formatLogMsg l msg = do+ utct <- getCurrentTime+ let time = formatTime defaultTimeLocale "%H:%M:%S" utct+ return $ unlines $ printf "[%s] %s - %s" (printLevel l) time <$> lines msg printLevel :: LogLevel -> String-printLevel l =- case l of- LogNothing -> "NOTHING"- LogError -> " ERROR "- LogInfo -> " INFO "- LogDebug -> " DEBUG "- LogTrace -> " TRACE "+printLevel l = case l of+ LogNothing -> "NOTHING"+ LogError -> " ERROR "+ LogInfo -> " INFO "+ LogDebug -> " DEBUG "+ LogTrace -> " TRACE " newtype B9 a = B9 {runB9 :: StateT BuildState IO a}@@ -279,3 +266,7 @@ let durMS = IoActionDuration (stop `diffUTCTime` start) modify $ \ctx -> ctx {bsProf = durMS : bsProf ctx} return res++++
src/lib/B9/Builder.hs view
@@ -4,11 +4,12 @@ -} module B9.Builder (buildArtifacts, module X) where import B9.B9Monad as X-import B9.ConfigUtils as X+import Data.ConfigFile.B9Extras as X import B9.B9Config as X import B9.ExecEnv as X import B9.DiskImages as X import B9.DiskImageBuilder as X+import B9.Invokation as X import B9.ShellScript as X import B9.Repository as X import B9.RepositoryIO as X@@ -24,32 +25,16 @@ import B9.Content.YamlObject as X import B9.Content.Generator as X -import Data.Semigroup as Sem import Text.Printf ( printf ) import Text.Show.Pretty (ppShow) import Control.Monad.IO.Class import System.Directory -buildArtifacts :: ArtifactGenerator -> ConfigParser -> B9Config -> IO Bool-buildArtifacts artifactGenerator cfgParser cliCfg =- withB9Config cfgParser cliCfg $ \cfg ->- run cfgParser cfg $ do- traceL . ("CWD: " ++) =<< liftIO getCurrentDirectory- infoL "BUILDING ARTIFACTS"- getConfig >>= traceL . printf "USING BUILD CONFIGURATION: %v" . ppShow- assemble artifactGenerator- return True+buildArtifacts :: ArtifactGenerator -> B9Invokation ()+buildArtifacts artifactGenerator = run $ do+ traceL . ("CWD: " ++) =<< liftIO getCurrentDirectory+ infoL "BUILDING ARTIFACTS"+ getConfig >>= traceL . printf "USING BUILD CONFIGURATION: %v" . ppShow+ assemble artifactGenerator+ return True -withB9Config :: ConfigParser- -> B9Config- -> (B9Config -> IO Bool)- -> IO Bool-withB9Config cfgParser cliCfg f = do- let parsedCfg' = parseB9Config cfgParser- case parsedCfg' of- Left e -> do- putStrLn (printf "B9 Failed to start: %s" e)- return False- Right parsedCfg ->- let cfg = defaultB9Config Sem.<> parsedCfg Sem.<> cliCfg- in f cfg
− src/lib/B9/ConfigUtils.hs
@@ -1,150 +0,0 @@-{-# Language DeriveDataTypeable #-}-{-| Extensions to 'Data.ConfigFile' and utility functions for dealing with- configuration in general and reading/writing files. -}-module B9.ConfigUtils ( allOn- , lastOn- , SystemPath (..)- , resolve- , ensureDir- , readIniFile- , getOptionM- , getOption- , getOptionOr- , IniFileException(..)- , module Data.ConfigFile- , UUID (..)- , randomUUID- , tell- , consult- , getDirectoryFiles- , maybeConsult- , maybeConsultSystemPath- ) where--import Data.Monoid-import Data.Function ( on )-import Data.Typeable-#if !MIN_VERSION_base(4,8,0)-import Control.Applicative-#endif-import Control.Exception-import Control.Monad.IO.Class-import System.Directory-import Text.Read ( readEither )-import System.Random ( randomIO )-import Data.Word ( Word16, Word32 )-import System.FilePath-import Text.Printf-import Data.ConfigFile-import Data.Data-import Text.Show.Pretty (ppShow)--allOn :: (a -> Maybe Bool) -> a -> a -> Maybe Bool-allOn getter x y = getAll <$> on mappend (fmap All . getter) x y--lastOn :: (a -> Maybe b) -> a -> a -> Maybe b-lastOn getter x y = getLast $ on mappend (Last . getter) x y--data SystemPath = Path FilePath- | InHomeDir FilePath- | InB9UserDir FilePath- | InTempDir FilePath- deriving (Eq, Read, Show, Typeable, Data)--resolve :: MonadIO m => SystemPath -> m FilePath-resolve (Path p) = return p-resolve (InHomeDir p) = liftIO $ do- d <- getHomeDirectory- return $ d </> p-resolve (InB9UserDir p) = liftIO $ do- d <- getAppUserDataDirectory "b9"- return $ d </> p-resolve (InTempDir p) = liftIO $ do- d <- getTemporaryDirectory- return $ d </> p---- | Get all files from 'dir' that is get ONLY files not directories-getDirectoryFiles :: MonadIO m => FilePath -> m [FilePath]-getDirectoryFiles dir = do- entries <- liftIO (getDirectoryContents dir)- fileEntries <- mapM (liftIO . doesFileExist . (dir </>)) entries- return (snd <$> filter fst (fileEntries `zip` entries))--ensureDir :: MonadIO m => FilePath -> m ()-ensureDir p = liftIO (createDirectoryIfMissing True $ takeDirectory p)--data ReaderException = ReaderException FilePath String- deriving (Show, Typeable)-instance Exception ReaderException--tell :: (MonadIO m, Show a) => FilePath -> a -> m ()-tell f x = do- ensureDir f- liftIO (writeFile f (ppShow x))--consult :: (MonadIO m, Read a) => FilePath -> m a-consult f = liftIO $ do- c <- readFile f- case readEither c of- Left e ->- throwIO $ ReaderException f e- Right a ->- return a--maybeConsult :: (MonadIO m, Read a) => Maybe FilePath -> a -> m a-maybeConsult Nothing defaultArg = return defaultArg-maybeConsult (Just f) defaultArg = liftIO $ do- exists <- doesFileExist f- if exists- then consult f- else return defaultArg--maybeConsultSystemPath :: (MonadIO m, Read a) => Maybe SystemPath -> a -> m a-maybeConsultSystemPath Nothing defaultArg = return defaultArg-maybeConsultSystemPath (Just f) defaultArg = liftIO $ do- f' <- resolve f- exists <- doesFileExist f'- if exists- then consult f'- else return defaultArg--data IniFileException = IniFileException FilePath CPError- deriving (Show, Typeable)-instance Exception IniFileException--readIniFile :: MonadIO m => SystemPath -> m ConfigParser-readIniFile cfgFile' = do- cfgFile <- resolve cfgFile'- cp' <- liftIO $ readfile emptyCP cfgFile- case cp' of- Left e -> liftIO $ throwIO (IniFileException cfgFile e)- Right cp -> return cp--getOption :: (Get_C a, Monoid a) => ConfigParser -> SectionSpec -> OptionSpec -> a-getOption cp sec key = either (const mempty) id $ get cp sec key--getOptionM :: (Read a) => ConfigParser -> SectionSpec -> OptionSpec -> Maybe a-getOptionM cp sec key = either (const Nothing) id $ get cp sec key--getOptionOr :: (Get_C a) => ConfigParser -> SectionSpec -> OptionSpec -> a -> a-getOptionOr cp sec key dv = either (const dv) id $ get cp sec key--newtype UUID = UUID (Word32, Word16, Word16, Word16, Word32, Word16)- deriving (Read, Show, Eq, Ord)--instance PrintfArg UUID where- formatArg (UUID (a, b, c, d, e, f)) fmt- | fmtChar (vFmt 'U' fmt) == 'U' =- let str = (printf "%08x-%04x-%04x-%04x-%08x%04x" a b c d e f :: String)- in formatString str (fmt { fmtChar = 's', fmtPrecision = Nothing })- | otherwise = errorBadFormat $ fmtChar fmt---randomUUID :: MonadIO m => m UUID-randomUUID = liftIO- (UUID <$> ((,,,,,) <$> randomIO- <*> randomIO- <*> randomIO- <*> randomIO- <*> randomIO- <*> randomIO))
src/lib/B9/Content/StringTemplate.hs view
@@ -29,7 +29,7 @@ import Text.Printf import Text.Show.Pretty (ppShow) -import B9.ConfigUtils+import Data.ConfigFile.B9Extras import B9.QCUtil -- | A wrapper around a file path and a flag indicating if template variable
src/lib/B9/DiskImageBuilder.hs view
@@ -43,7 +43,7 @@ import B9.RepositoryIO import B9.DiskImages import qualified B9.PartitionTable as P-import B9.ConfigUtils+import Data.ConfigFile.B9Extras import B9.Content.StringTemplate
src/lib/B9/DiskImages.hs view
@@ -181,6 +181,10 @@ -- wrapper around a string that identifies a 'SharedImage' newtype SharedImageName = SharedImageName String deriving (Eq,Ord,Read,Show,Typeable,Data,Hashable,Binary,NFData) +-- | Get the String representation of a 'SharedImageName'.+fromSharedImageName :: SharedImageName -> String+fromSharedImageName (SharedImageName b) = b+ -- | The exact time that build job __started__. -- This is a wrapper around a string contains the build date of a -- 'SharedImage'; this is purely additional convenience and typesafety@@ -193,6 +197,10 @@ -- around a string contains the build id of a 'SharedImage'; this is purely -- additional convenience and typesafety newtype SharedImageBuildId = SharedImageBuildId String deriving (Eq,Ord,Read,Show,Typeable,Data,Hashable,Binary,NFData)++-- | Get the String representation of a 'SharedImageBuildId'.+fromSharedImageBuildId :: SharedImageBuildId -> String+fromSharedImageBuildId (SharedImageBuildId b) = b -- | Shared images are orderd by name, build date and build id instance Ord SharedImage where
+ src/lib/B9/Invokation.hs view
@@ -0,0 +1,124 @@+module B9.Invokation ( B9Invokation()+ , invokeB9+ , invokeB9_+ , overrideWorkingDirectory+ , doAfterConfiguration+ , overrideB9ConfigPath+ , modifyInvokationConfig+ , modifyPermanentConfig) where++import B9.B9Config+import Data.ConfigFile.B9Extras+import Control.Monad.IO.Class+import Control.Monad.State+import Control.Monad.Reader+import Control.Lens+import Control.Exception (bracket)+import System.Directory (getCurrentDirectory, setCurrentDirectory)+import Data.Semigroup as Sem+import Data.Maybe (fromMaybe)+import Text.Printf ( printf )++-- | A monad encapsulating all configuration adaptions happening before any actual+-- build actions are performed. Use 'invokeB9' to execute these things.+-- Inside the 'B9Invokation' monad you can influence and inspect the b9+-- configuration, the permanent, as well as the transient.+newtype B9Invokation a = B9Inv {runB9Invokation :: StateT InternalState IO a}+ deriving (MonadState InternalState, Monad, Applicative, Functor, MonadIO)++-- | Internal state of the 'B9Invokation'+data InternalState = IS { _initialConfigOverride :: B9ConfigOverride+ , _permanentB9ConfigUpdate :: Maybe (ConfigParser -> Either CPError ConfigParser)+ , _changeWorkingDirectory :: Maybe FilePath+ , _buildAction :: ReaderT B9Config IO Bool+ }++makeLenses ''InternalState++initialState :: InternalState+initialState =+ IS (B9ConfigOverride Nothing mempty) Nothing Nothing (return True)++-- | Run a 'B9Invokation'.+-- If the user has no B9 config file yet, a default config file will be created.+invokeB9 :: B9Invokation a -> IO (a, Bool)+invokeB9 act = do+ (a, st) <- runStateT (runB9Invokation act) initialState+ let cfgPath = st ^. initialConfigOverride . customB9ConfigPath+ cp0 <- openOrCreateB9Config cfgPath+ let cpExtErr = fmap ($ cp0) (st ^. permanentB9ConfigUpdate)+ cpExt <- maybe+ (return Nothing)+ ( either+ ( fail+ . printf "Internal configuration error! Please report this: %s\n"+ . show+ )+ (return . Just)+ )+ cpExtErr+ let cp = fromMaybe cp0 cpExt+ mapM_ (writeB9ConfigParser cfgPath) cpExt+ case parseB9Config cp of+ Left e -> fail (printf "Configuration error: %s\n" (show e))+ Right permanentConfig -> do+ let runtimeCfg =+ permanentConfig+ Sem.<> st+ ^. initialConfigOverride+ . customB9Config+ completeBuildAction = bracket+ getCurrentDirectory+ setCurrentDirectory+ ( const+ ( do+ mapM_ setCurrentDirectory+ (st ^. changeWorkingDirectory)+ runReaderT (st ^. buildAction) runtimeCfg+ )+ )+ res <- completeBuildAction+ return (a, res)++-- | Run a 'B9Invokation' and ignore the results of the invokation monad and+-- instead return the results of the '_buildAction'.+-- See: 'invokeB9'+invokeB9_ :: B9Invokation a -> IO Bool+invokeB9_ act = snd <$> invokeB9 act++-- | Add an action to be executed once the configuration has been done.+doAfterConfiguration :: ReaderT B9Config IO Bool -> B9Invokation ()+doAfterConfiguration action = buildAction %= (>> action)++-- | Override the B9 configuration path+overrideB9ConfigPath :: SystemPath -> B9Invokation ()+overrideB9ConfigPath p = initialConfigOverride . customB9ConfigPath .= Just p++-- | Override the current working directory during execution of the actions+-- added with 'doAfterConfiguration'.+overrideWorkingDirectory :: FilePath -> B9Invokation ()+overrideWorkingDirectory p = changeWorkingDirectory .= Just p++-- | Modify the current 'B9Config'. The modifications are not reflected in the+-- config file or the 'ConfigParser' returned by 'askInvokationConfigParser'.+modifyInvokationConfig :: (B9Config -> B9Config) -> B9Invokation ()+modifyInvokationConfig f = initialConfigOverride . customB9Config %= f++-- | Modify the current 'B9Config'. The modifications are not reflected in the+-- config file or the 'ConfigParser' returned by 'askInvokationConfigParser'.+modifyPermanentConfig :: (B9Config -> B9Config) -> B9Invokation ()+modifyPermanentConfig g = permanentB9ConfigUpdate %= go+ where+ go Nothing = go (Just return)+ go (Just f) = Just (\cp -> f cp >>= modifyConfigParser g)+++++++++++
src/lib/B9/LibVirtLXC.hs view
@@ -1,7 +1,8 @@ {-| Implementation of an execution environment that uses "libvirt-lxc". -} module B9.LibVirtLXC ( runInEnvironment , supportedImageTypes- , setDefaultConfig+ , logLibVirtLXCConfig+ , module X ) where import Control.Monad.IO.Class ( liftIO )@@ -9,297 +10,210 @@ import System.FilePath import Text.Printf ( printf ) import Data.Char (toLower)+import Control.Lens (view) import B9.ShellScript import B9.B9Monad+import B9.B9Config (libVirtLXCConfigs) import B9.DiskImages import B9.ExecEnv-import B9.ConfigUtils+import B9.B9Config.LibVirtLXC as X+import Data.ConfigFile.B9Extras ++logLibVirtLXCConfig :: LibVirtLXCConfig -> B9 ()+logLibVirtLXCConfig c = traceL $ printf "USING LibVirtLXCConfig: %s" (show c)+ supportedImageTypes :: [ImageType] supportedImageTypes = [Raw] runInEnvironment :: ExecEnv -> Script -> B9 Bool-runInEnvironment env scriptIn =- if emptyScript scriptIn- then return True- else setUp >>= execute- where- setUp = do- cfg <- configureLibVirtLXC- buildId <- getBuildId- buildBaseDir <- getBuildDir- uuid <- randomUUID- let scriptDirHost = buildDir </> "init-script"- scriptDirGuest = "/" ++ buildId- domainFile = buildBaseDir </> uuid' <.> domainConfig- domain = createDomain cfg env buildId uuid' scriptDirHost scriptDirGuest- uuid' = printf "%U" uuid- setupEnv = Begin [ Run "export" ["HOME=/root"]- , Run "export" ["USER=root"]- , Run "source" ["/etc/profile"]]- script = Begin [setupEnv, scriptIn, successMarkerCmd scriptDirGuest]- buildDir = buildBaseDir </> uuid'- liftIO $ do- createDirectoryIfMissing True scriptDirHost- writeSh (scriptDirHost </> initScript) script- writeFile domainFile domain- return $ Context scriptDirHost uuid domainFile cfg+runInEnvironment env scriptIn = if emptyScript scriptIn+ then return True+ else setUp >>= execute+ where+ setUp = do+ mcfg <- view libVirtLXCConfigs <$> getConfig+ cfg <- maybe (fail "No LibVirtLXC Configuration!") return mcfg+ buildId <- getBuildId+ buildBaseDir <- getBuildDir+ uuid <- randomUUID+ let+ scriptDirHost = buildDir </> "init-script"+ scriptDirGuest = "/" ++ buildId+ domainFile = buildBaseDir </> uuid' <.> domainConfig+ domain = createDomain cfg env buildId uuid' scriptDirHost scriptDirGuest+ uuid' = printf "%U" uuid+ setupEnv = Begin+ [ Run "export" ["HOME=/root"]+ , Run "export" ["USER=root"]+ , Run "source" ["/etc/profile"]+ ]+ script = Begin [setupEnv, scriptIn, successMarkerCmd scriptDirGuest]+ buildDir = buildBaseDir </> uuid'+ liftIO $ do+ createDirectoryIfMissing True scriptDirHost+ writeSh (scriptDirHost </> initScript) script+ writeFile domainFile domain+ return $ Context scriptDirHost uuid domainFile cfg - successMarkerCmd scriptDirGuest =- In scriptDirGuest [Run "touch" [successMarkerFile]]+ successMarkerCmd scriptDirGuest =+ In scriptDirGuest [Run "touch" [successMarkerFile]] - execute (Context scriptDirHost _uuid domainFile cfg) = do- let virsh = virshCommand cfg- cmd $ printf "%s create '%s' --console --autodestroy" virsh domainFile- -- cmd $ printf "%s console %U" virsh uuid- liftIO (doesFileExist $ scriptDirHost </> successMarkerFile)+ execute (Context scriptDirHost _uuid domainFile cfg) = do+ let virsh = virshCommand cfg+ cmd $ printf "%s create '%s' --console --autodestroy" virsh domainFile+ -- cmd $ printf "%s console %U" virsh uuid+ liftIO (doesFileExist $ scriptDirHost </> successMarkerFile) - successMarkerFile = "SUCCESS"+ successMarkerFile = "SUCCESS" - virshCommand :: LibVirtLXCConfig -> String- virshCommand cfg = printf "%s%s -c %s" useSudo' virshPath' virshURI'- where- useSudo' = if useSudo cfg- then "sudo "- else ""- virshPath' = virshPath cfg- virshURI' = virshURI cfg+ virshCommand :: LibVirtLXCConfig -> String+ virshCommand cfg = printf "%s%s -c %s" useSudo' virshPath' virshURI'+ where+ useSudo' = if useSudo cfg then "sudo " else ""+ virshPath' = virshPath cfg+ virshURI' = virshURI cfg data Context = Context FilePath UUID FilePath LibVirtLXCConfig -data LibVirtLXCConfig = LibVirtLXCConfig { useSudo :: Bool- , virshPath :: FilePath- , emulator :: FilePath- , virshURI :: FilePath- , networkId :: Maybe String- , guestCapabilities :: [LXCGuestCapability]- , guestRamSize :: RamSize- } deriving (Read, Show) --- | Available linux capabilities for lxc containers. This maps directly to the--- capabilities defined in 'man 7 capabilities'.-data LXCGuestCapability = CAP_MKNOD- | CAP_AUDIT_CONTROL- | CAP_AUDIT_READ- | CAP_AUDIT_WRITE- | CAP_BLOCK_SUSPEND- | CAP_CHOWN- | CAP_DAC_OVERRIDE- | CAP_DAC_READ_SEARCH- | CAP_FOWNER- | CAP_FSETID- | CAP_IPC_LOCK- | CAP_IPC_OWNER- | CAP_KILL- | CAP_LEASE- | CAP_LINUX_IMMUTABLE- | CAP_MAC_ADMIN- | CAP_MAC_OVERRIDE- | CAP_NET_ADMIN- | CAP_NET_BIND_SERVICE- | CAP_NET_BROADCAST- | CAP_NET_RAW- | CAP_SETGID- | CAP_SETFCAP- | CAP_SETPCAP- | CAP_SETUID- | CAP_SYS_ADMIN- | CAP_SYS_BOOT- | CAP_SYS_CHROOT- | CAP_SYS_MODULE- | CAP_SYS_NICE- | CAP_SYS_PACCT- | CAP_SYS_PTRACE- | CAP_SYS_RAWIO- | CAP_SYS_RESOURCE- | CAP_SYS_TIME- | CAP_SYS_TTY_CONFIG- | CAP_SYSLOG- | CAP_WAKE_ALARM- deriving (Read, Show)--defaultLibVirtLXCConfig :: LibVirtLXCConfig-defaultLibVirtLXCConfig = LibVirtLXCConfig- True- "/usr/bin/virsh"- "/usr/lib/libvirt/libvirt_lxc"- "lxc:///"- Nothing- [ CAP_MKNOD- , CAP_SYS_ADMIN- , CAP_SYS_CHROOT- , CAP_SETGID- , CAP_SETUID- , CAP_NET_BIND_SERVICE- , CAP_SETPCAP- , CAP_SYS_PTRACE- , CAP_SYS_MODULE- ]- (RamSize 1 GB)--cfgFileSection :: String-cfgFileSection = "libvirt-lxc"-useSudoK :: String-useSudoK = "use_sudo"-virshPathK :: String-virshPathK = "virsh_path"-emulatorK :: String-emulatorK = "emulator_path"-virshURIK :: String-virshURIK = "connection"-networkIdK :: String-networkIdK = "network"-guestCapabilitiesK :: String-guestCapabilitiesK = "guest_capabilities"-guestRamSizeK :: String-guestRamSizeK = "guest_ram_size"--configureLibVirtLXC :: B9 LibVirtLXCConfig-configureLibVirtLXC = do- c <- readLibVirtConfig- traceL $ printf "USING LibVirtLXCConfig: %s" (show c)- return c--setDefaultConfig :: ConfigParser-setDefaultConfig = either (error . show) id eitherCp- where- eitherCp = do- let cp = emptyCP- c = defaultLibVirtLXCConfig- cp1 <- add_section cp cfgFileSection- cp2 <- setshow cp1 cfgFileSection useSudoK $ useSudo c- cp3 <- set cp2 cfgFileSection virshPathK $ virshPath c- cp4 <- set cp3 cfgFileSection emulatorK $ emulator c- cp5 <- set cp4 cfgFileSection virshURIK $ virshURI c- cp6 <- setshow cp5 cfgFileSection networkIdK $ networkId c- cp7 <- setshow cp6 cfgFileSection guestCapabilitiesK $ guestCapabilities c- setshow cp7 cfgFileSection guestRamSizeK $ guestRamSize c--readLibVirtConfig :: B9 LibVirtLXCConfig-readLibVirtConfig =- do- cp <- getConfigParser- let geto :: (Get_C a)- => OptionSpec -> a -> a- geto = getOptionOr cp cfgFileSection- return- LibVirtLXCConfig { useSudo = geto useSudoK $- useSudo defaultLibVirtLXCConfig- , virshPath = geto virshPathK $- virshPath defaultLibVirtLXCConfig- , emulator = geto emulatorK $- emulator defaultLibVirtLXCConfig- , virshURI = geto virshURIK $- virshURI defaultLibVirtLXCConfig- , networkId = geto networkIdK $- networkId defaultLibVirtLXCConfig- , guestCapabilities = geto guestCapabilitiesK $- guestCapabilities defaultLibVirtLXCConfig- , guestRamSize = geto guestRamSizeK $- guestRamSize defaultLibVirtLXCConfig- }- initScript :: String initScript = "init.sh" domainConfig :: String domainConfig = "domain.xml" -createDomain :: LibVirtLXCConfig- -> ExecEnv- -> String- -> String- -> FilePath- -> FilePath- -> String+createDomain+ :: LibVirtLXCConfig+ -> ExecEnv+ -> String+ -> String+ -> FilePath+ -> FilePath+ -> String createDomain cfg e buildId uuid scriptDirHost scriptDirGuest =- "<domain type='lxc'>\n <name>" ++ buildId ++ "</name>\n <uuid>" ++ uuid ++ "</uuid>\n <memory unit='" ++ memoryUnit cfg e ++ "'>" ++ memoryAmount cfg e ++ "</memory>\n <currentMemory unit='" ++ memoryUnit cfg e ++ "'>" ++ memoryAmount cfg e ++ "</currentMemory>\n <vcpu placement='static'>" ++ cpuCountStr e ++ "</vcpu>\n <features>\n <capabilities policy='default'>\n "++ renderGuestCapabilityEntries cfg ++"\n </capabilities>\n </features>\n <os>\n <type arch='" ++ osArch e ++ "'>exe</type>\n <init>" ++ scriptDirGuest </> initScript ++ "</init>\n </os>\n <clock offset='utc'/>\n <on_poweroff>destroy</on_poweroff>\n <on_reboot>restart</on_reboot>\n <on_crash>destroy</on_crash>\n <devices>\n <emulator>" ++ emulator cfg ++ "</emulator>\n"- ++ unlines (libVirtNetwork (networkId cfg) ++- (fsImage <$> envImageMounts e) ++- (fsSharedDir <$> envSharedDirectories e)) ++ "\n" ++- " <filesystem type='mount'>\n <source dir='" ++ scriptDirHost ++ "'/>\n <target dir='" ++ scriptDirGuest ++ "'/>\n </filesystem>\n <console>\n <target type='lxc' port='0'/>\n </console>\n </devices>\n</domain>\n"+ "<domain type='lxc'>\n <name>"+ ++ buildId+ ++ "</name>\n <uuid>"+ ++ uuid+ ++ "</uuid>\n <memory unit='"+ ++ memoryUnit cfg e+ ++ "'>"+ ++ memoryAmount cfg e+ ++ "</memory>\n <currentMemory unit='"+ ++ memoryUnit cfg e+ ++ "'>"+ ++ memoryAmount cfg e+ ++ "</currentMemory>\n <vcpu placement='static'>"+ ++ cpuCountStr e+ ++ "</vcpu>\n <features>\n <capabilities policy='default'>\n "+ ++ renderGuestCapabilityEntries cfg+ ++ "\n </capabilities>\n </features>\n <os>\n <type arch='"+ ++ osArch e+ ++ "'>exe</type>\n <init>"+ ++ scriptDirGuest+ </> initScript+ ++ "</init>\n </os>\n <clock offset='utc'/>\n <on_poweroff>destroy</on_poweroff>\n <on_reboot>restart</on_reboot>\n <on_crash>destroy</on_crash>\n <devices>\n <emulator>"+ ++ emulator cfg+ ++ "</emulator>\n"+ ++ unlines+ ( libVirtNetwork (networkId cfg)+ ++ (fsImage <$> envImageMounts e)+ ++ (fsSharedDir <$> envSharedDirectories e)+ )+ ++ "\n"+ ++ " <filesystem type='mount'>\n <source dir='"+ ++ scriptDirHost+ ++ "'/>\n <target dir='"+ ++ scriptDirGuest+ ++ "'/>\n </filesystem>\n <console>\n <target type='lxc' port='0'/>\n </console>\n </devices>\n</domain>\n" renderGuestCapabilityEntries :: LibVirtLXCConfig -> String renderGuestCapabilityEntries = unlines . map render . guestCapabilities- where- render :: LXCGuestCapability -> String- render cap =- let capStr = toLower <$> drop (length "CAP_") (show cap)- in printf "<%s state='on'/>" capStr+ where+ render :: LXCGuestCapability -> String+ render cap =+ let capStr = toLower <$> drop (length "CAP_") (show cap)+ in printf "<%s state='on'/>" capStr osArch :: ExecEnv -> String osArch e = case cpuArch (envResources e) of- X86_64 -> "x86_64"- I386 -> "i686"+ X86_64 -> "x86_64"+ I386 -> "i686" libVirtNetwork :: Maybe String -> [String] libVirtNetwork Nothing = [] libVirtNetwork (Just n) = [ "<interface type='network'>" , " <source network='" ++ n ++ "'/>"- , "</interface>" ]+ , "</interface>"+ ] fsImage :: (Image, MountPoint) -> String-fsImage (img, mnt) =- case fsTarget mnt of- Just mntXml ->- "<filesystem type='file' accessmode='passthrough'>\n " ++- fsImgDriver img ++ "\n " ++ fsImgSource img ++ "\n " ++ mntXml ++- "\n</filesystem>"- Nothing ->- ""- where- fsImgDriver (Image _img fmt _fs) =- printf "<driver %s %s/>" driver fmt'- where- (driver, fmt') =- case fmt of- Raw -> ("type='loop'", "format='raw'")- QCow2 -> ("type='nbd'", "format='qcow2'")- Vmdk -> ("type='nbd'", "format='vmdk'")+fsImage (img, mnt) = case fsTarget mnt of+ Just mntXml ->+ "<filesystem type='file' accessmode='passthrough'>\n "+ ++ fsImgDriver img+ ++ "\n "+ ++ fsImgSource img+ ++ "\n "+ ++ mntXml+ ++ "\n</filesystem>"+ Nothing -> ""+ where+ fsImgDriver (Image _img fmt _fs) = printf "<driver %s %s/>" driver fmt'+ where+ (driver, fmt') = case fmt of+ Raw -> ("type='loop'", "format='raw'")+ QCow2 -> ("type='nbd'", "format='qcow2'")+ Vmdk -> ("type='nbd'", "format='vmdk'") - fsImgSource (Image src _fmt _fs) = "<source file='" ++ src ++ "'/>"+ fsImgSource (Image src _fmt _fs) = "<source file='" ++ src ++ "'/>" fsSharedDir :: SharedDirectory -> String-fsSharedDir (SharedDirectory hostDir mnt) =- case fsTarget mnt of- Just mntXml ->- "<filesystem type='mount'>\n " ++- "<source dir='" ++ hostDir ++ "'/>" ++ "\n " ++ mntXml ++- "\n</filesystem>"- Nothing ->- ""-fsSharedDir (SharedDirectoryRO hostDir mnt) =- case fsTarget mnt of- Just mntXml ->- "<filesystem type='mount'>\n " ++- "<source dir='" ++ hostDir ++ "'/>" ++ "\n " ++ mntXml ++- "\n <readonly />\n</filesystem>"- Nothing ->- ""-fsSharedDir (SharedSources _) =- error "Unreachable code reached!"+fsSharedDir (SharedDirectory hostDir mnt) = case fsTarget mnt of+ Just mntXml ->+ "<filesystem type='mount'>\n "+ ++ "<source dir='"+ ++ hostDir+ ++ "'/>"+ ++ "\n "+ ++ mntXml+ ++ "\n</filesystem>"+ Nothing -> ""+fsSharedDir (SharedDirectoryRO hostDir mnt) = case fsTarget mnt of+ Just mntXml ->+ "<filesystem type='mount'>\n "+ ++ "<source dir='"+ ++ hostDir+ ++ "'/>"+ ++ "\n "+ ++ mntXml+ ++ "\n <readonly />\n</filesystem>"+ Nothing -> ""+fsSharedDir (SharedSources _) = error "Unreachable code reached!" fsTarget :: MountPoint -> Maybe String fsTarget (MountPoint dir) = Just $ "<target dir='" ++ dir ++ "'/>"-fsTarget _ = Nothing+fsTarget _ = Nothing memoryUnit :: LibVirtLXCConfig -> ExecEnv -> String memoryUnit cfg = toUnit . maxMemory . envResources- where- toUnit AutomaticRamSize = toUnit (guestRamSize cfg)- toUnit (RamSize _ u) =- case u of- GB -> "GiB"- MB -> "MiB"- KB -> "KiB"- B -> "B"+ where+ toUnit AutomaticRamSize = toUnit (guestRamSize cfg)+ toUnit (RamSize _ u) = case u of+ GB -> "GiB"+ MB -> "MiB"+ KB -> "KiB"+ B -> "B" memoryAmount :: LibVirtLXCConfig -> ExecEnv -> String memoryAmount cfg = show . toAmount . maxMemory . envResources- where- toAmount AutomaticRamSize = toAmount (guestRamSize cfg)- toAmount (RamSize n _) = n+ where+ toAmount AutomaticRamSize = toAmount (guestRamSize cfg)+ toAmount (RamSize n _) = n cpuCountStr :: ExecEnv -> String cpuCountStr = show . cpuCount . envResources++
src/lib/B9/Repository.hs view
@@ -3,56 +3,25 @@ the whole thing is supposed to be build-server-safe, that means no two builds shall interfere with each other. This is accomplished by refraining from automatic cache updates from/to remote repositories.-}-module B9.Repository (RemoteRepo(..)- ,remoteRepoRepoId- ,RepoCache(..)- ,SshPrivKey(..)- ,SshRemoteHost(..)- ,SshRemoteUser(..)- ,initRepoCache+module B9.Repository (initRepoCache ,initRemoteRepo ,cleanRemoteRepo ,remoteRepoCheckSshPrivKey ,remoteRepoCacheDir ,localRepoDir- ,writeRemoteRepoConfig- ,getConfiguredRemoteRepos- ,lookupRemoteRepo) where+ ,lookupRemoteRepo+ ,module X) where import Control.Monad import Control.Monad.IO.Class #if !MIN_VERSION_base(4,8,0) import Control.Applicative #endif-import Data.Data-import Data.List-import Data.ConfigFile import Text.Printf import System.FilePath import System.Directory-import B9.ConfigUtils--newtype RepoCache = RepoCache FilePath- deriving (Read, Show, Typeable, Data)--data RemoteRepo = RemoteRepo String- FilePath- SshPrivKey- SshRemoteHost- SshRemoteUser- deriving (Read, Show, Typeable, Data)--remoteRepoRepoId :: RemoteRepo -> String-remoteRepoRepoId (RemoteRepo repoId _ _ _ _) = repoId--newtype SshPrivKey = SshPrivKey FilePath- deriving (Read, Show, Typeable, Data)--newtype SshRemoteHost = SshRemoteHost (String,Int)- deriving (Read, Show, Typeable, Data)--newtype SshRemoteUser = SshRemoteUser String- deriving (Read, Show, Typeable, Data)+import B9.B9Config.Repository as X+import Data.ConfigFile.B9Extras -- | Initialize the local repository cache directory. initRepoCache :: MonadIO m => SystemPath -> m RepoCache@@ -118,64 +87,8 @@ localRepoDir (RepoCache cacheDir) = cacheDir </> "local-repo" --- | Persist a repo to a configuration file.-writeRemoteRepoConfig :: RemoteRepo- -> ConfigParser- -> Either CPError ConfigParser-writeRemoteRepoConfig repo cpIn = cpWithRepo- where section = repoId ++ repoSectionSuffix- (RemoteRepo repoId- remoteRootDir- (SshPrivKey keyFile)- (SshRemoteHost (host,port))- (SshRemoteUser user)) = repo- cpWithRepo = do cp1 <- add_section cpIn section- cp2 <- set cp1 section repoRemotePathK remoteRootDir- cp3 <- set cp2 section repoRemoteSshKeyK keyFile- cp4 <- set cp3 section repoRemoteSshHostK host- cp5 <- setshow cp4 section repoRemoteSshPortK port- set cp5 section repoRemoteSshUserK user---- | Load a repository from a configuration file that has been written by--- 'writeRepositoryToB9Config'.+-- | Select the first 'RemoteRepo' with a given @repoId@. lookupRemoteRepo :: [RemoteRepo] -> String -> Maybe RemoteRepo lookupRemoteRepo repos repoId = lookup repoId repoIdRepoPairs where repoIdRepoPairs = map (\r@(RemoteRepo rid _ _ _ _) -> (rid,r)) repos -getConfiguredRemoteRepos :: ConfigParser -> [RemoteRepo]-getConfiguredRemoteRepos cp = map parseRepoSection repoSections- where- repoSections =- filter (repoSectionSuffix `isSuffixOf`) (sections cp)- parseRepoSection section =- case parseResult of- Left e -> error ("Error while parsing repo section \""- ++ section ++ "\": " ++ show e)- Right r -> r- where- getsec :: Get_C a => OptionSpec -> Either CPError a- getsec = get cp section- parseResult =- RemoteRepo repoId- <$> getsec repoRemotePathK- <*> (SshPrivKey <$> getsec repoRemoteSshKeyK)- <*> (SshRemoteHost <$> ((,) <$> getsec repoRemoteSshHostK- <*> getsec repoRemoteSshPortK))- <*> (SshRemoteUser <$> getsec repoRemoteSshUserK)- where- repoId = let prefixLen = length section - suffixLen- suffixLen = length repoSectionSuffix- in take prefixLen section--repoSectionSuffix :: String-repoSectionSuffix = "-repo"-repoRemotePathK :: String-repoRemotePathK = "remote_path"-repoRemoteSshKeyK :: String-repoRemoteSshKeyK = "ssh_priv_key_file"-repoRemoteSshHostK :: String-repoRemoteSshHostK = "ssh_remote_host"-repoRemoteSshPortK :: String-repoRemoteSshPortK = "ssh_remote_port"-repoRemoteSshUserK :: String-repoRemoteSshUserK = "ssh_remote_user"
src/lib/B9/RepositoryIO.hs view
@@ -10,7 +10,7 @@ import B9.Repository import B9.B9Monad-import B9.ConfigUtils+import Data.ConfigFile.B9Extras import Data.List
+ src/lib/B9/Shake.hs view
@@ -0,0 +1,6 @@+-- | A module that re-exports all B9 Shake integration.+-- Which by the way is crude and preliminary...+module B9.Shake (module X) where++import B9.Shake.Actions as X+import B9.Shake.SharedImageRules as X
+ src/lib/B9/Shake/Actions.hs view
@@ -0,0 +1,23 @@+-- | Convenient Shake 'Action's for 'B9' rules.+module B9.Shake.Actions (b9InvokationAction, buildB9File) where++import Development.Shake+import Development.Shake.FilePath+import B9++-- | Convert a 'B9Invokation' action into a Shake 'Action'.+b9InvokationAction :: B9Invokation a -> Action (a, Bool)+b9InvokationAction = liftIO . runB9 . defaultB9RunParameters++-- | An action that does the equivalent of+-- @b9c build -f <b9file> -- (args !! 0) (args !! 1) ... (args !! (length args - 1))@+-- with the current working directory changed to @b9Root@.+buildB9File :: FilePath -> FilePath -> [String] -> Action ()+buildB9File b9Root b9File args = do+ let f = b9Root </> b9File+ need [f]+ (_, success) <- b9InvokationAction $ do+ modifyInvokationConfig (appendPositionalArguments args)+ overrideWorkingDirectory b9Root+ runBuildArtifacts [f]+ if success then return () else fail $ "ERROR: Build failed: " ++ f
@@ -0,0 +1,120 @@+-- | A crude, unsafe and preliminary solution to building B9 'SharedImage's+-- from Shake.+module B9.Shake.SharedImageRules ( customSharedImageAction+ , needSharedImage+ , enableSharedImageRules) where++import Development.Shake+import Development.Shake.Classes+import Development.Shake.Rule+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Lazy as LazyByteString+import qualified Data.Binary as Binary+import B9+import B9.Shake.Actions (b9InvokationAction)++-- | In order to use 'needSharedImage' and 'customSharedImageAction' you need to+-- call this action before using any of the afore mentioned.+enableSharedImageRules :: Rules ()+enableSharedImageRules = addBuiltinRule noLint go+ where+ go :: SharedImageName+ -> Maybe ByteString.ByteString+ -> Bool+ -> Action (RunResult SharedImageBuildId)+ go nameQ mSerlializedBuildId dependenciesChanged = do+ (mCurrentBuildId, success) <- b9InvokationAction (runLookupLocalSharedImage nameQ)+ unless success+ (internalErrorSharedImageNotFound nameQ)++ case (decodeBuildId <$> mSerlializedBuildId, mCurrentBuildId) of++ (Nothing, Nothing) ->+ do newBuildId <- rebuild+ return (RunResult ChangedRecomputeDiff (encodeBuildId newBuildId) newBuildId)++ (Nothing, Just currentBuildId) ->+ if dependenciesChanged then+ do newBuildId <- rebuild+ let changed = if newBuildId == currentBuildId+ then ChangedStore+ else ChangedRecomputeDiff+ return (RunResult changed (encodeBuildId newBuildId) newBuildId)+ else+ return (RunResult ChangedStore (encodeBuildId currentBuildId) currentBuildId)++ (Just oldBuildId, Nothing) ->+ do newBuildId <- rebuild+ let changed = if oldBuildId == newBuildId+ then ChangedRecomputeSame+ else ChangedRecomputeDiff+ return (RunResult changed (encodeBuildId newBuildId) newBuildId)++ (Just oldBuildId, Just currentBuildId) ->+ do newBuildId <- if dependenciesChanged+ then rebuild+ else return currentBuildId+ let changed = if oldBuildId == newBuildId+ then if dependenciesChanged+ then ChangedRecomputeSame+ else ChangedNothing+ else ChangedRecomputeDiff++ return (RunResult changed (encodeBuildId newBuildId) newBuildId)++ where+ decodeBuildId :: ByteString.ByteString -> SharedImageBuildId+ decodeBuildId = Binary.decode . LazyByteString.fromStrict++ encodeBuildId :: SharedImageBuildId -> ByteString.ByteString+ encodeBuildId = LazyByteString.toStrict . Binary.encode++ rebuild :: Action SharedImageBuildId+ rebuild = do+ rules <- getUserRules+ case userRuleMatch rules imgMatch of+ [] -> fail $ "No rules to build B9 shared image " ++ show nameQ ++ " found"+ [act] -> act+ _rs -> fail $ "Multiple rules for the B9 shared image " ++ show nameQ ++ " found"+ where+ imgMatch (SharedImageCustomActionRule name mkImage) =+ if name == nameQ then Just mkImage else Nothing++-- | Add a dependency to the creation of a 'SharedImage'. The build action+-- for the shared image must have been supplied by e.g. 'customSharedImageAction'.+-- NOTE: You must call 'enableSharedImageRules' before this action works.+needSharedImage :: SharedImageName -> Action SharedImageBuildId+needSharedImage = apply1++-- | Specify an arbitrary action that is supposed to build the given shared+-- image identified by a 'SharedImageName'.+-- NOTE: You must call 'enableSharedImageRules' before this action works.+customSharedImageAction :: SharedImageName -> Action () -> Rules ()+customSharedImageAction b9img customAction =+ addUserRule $ SharedImageCustomActionRule b9img $ do+ customAction+ (after, success) <- b9InvokationAction (runLookupLocalSharedImage b9img)+ unless success+ (internalErrorSharedImageNotFound b9img)+ maybe+ (errorSharedImageNotFound b9img)+ return+ after++type instance RuleResult SharedImageName = SharedImageBuildId++data SharedImageCustomActionRule =+ SharedImageCustomActionRule SharedImageName (Action SharedImageBuildId)+ deriving Typeable++internalErrorSharedImageNotFound :: Monad m => SharedImageName -> m a+internalErrorSharedImageNotFound =+ fail+ . printf "Internal Error: SharedImage %s not found. Please report this."+ . show++errorSharedImageNotFound :: Monad m => SharedImageName -> m a+errorSharedImageNotFound =+ fail+ . printf "Error: SharedImage %s not found."+ . show
+ src/lib/Data/ConfigFile/B9Extras.hs view
@@ -0,0 +1,151 @@+{-# Language DeriveDataTypeable #-}+{-| Extensions to 'Data.ConfigFile' and utility functions for dealing with+ configuration in general and reading/writing files. -}+module Data.ConfigFile.B9Extras ( allOn+ , lastOn+ , SystemPath (..)+ , resolve+ , ensureDir+ , readIniFile+ , getOptionM+ , getOption+ , getOptionOr+ , IniFileException(..)+ , module Data.ConfigFile+ , UUID (..)+ , randomUUID+ , tell+ , consult+ , getDirectoryFiles+ , maybeConsult+ , maybeConsultSystemPath+ ) where++import Data.Monoid+import Data.Function ( on )+import Data.Typeable+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif+import Control.Exception+import Control.Monad.IO.Class+import System.Directory+import Text.Read ( readEither )+import System.Random ( randomIO )+import Data.Word ( Word16, Word32 )+import System.FilePath+import Text.Printf+import Data.ConfigFile+import Data.Data+import Text.Show.Pretty (ppShow)++allOn :: (a -> Maybe Bool) -> a -> a -> Maybe Bool+allOn getter x y = getAll <$> on mappend (fmap All . getter) x y++lastOn :: (a -> Maybe b) -> a -> a -> Maybe b+lastOn getter x y = getLast $ on mappend (Last . getter) x y++data SystemPath = Path FilePath+ | InHomeDir FilePath+ | InB9UserDir FilePath+ | InTempDir FilePath+ deriving (Eq, Read, Show, Typeable, Data)++resolve :: MonadIO m => SystemPath -> m FilePath+resolve (Path p) = return p+resolve (InHomeDir p) = liftIO $ do+ d <- getHomeDirectory+ return $ d </> p+resolve (InB9UserDir p) = liftIO $ do+ d <- getAppUserDataDirectory "b9"+ return $ d </> p+resolve (InTempDir p) = liftIO $ do+ d <- getTemporaryDirectory+ return $ d </> p++-- | Get all files from 'dir' that is get ONLY files not directories+getDirectoryFiles :: MonadIO m => FilePath -> m [FilePath]+getDirectoryFiles dir = do+ entries <- liftIO (getDirectoryContents dir)+ fileEntries <- mapM (liftIO . doesFileExist . (dir </>)) entries+ return (snd <$> filter fst (fileEntries `zip` entries))++ensureDir :: MonadIO m => FilePath -> m ()+ensureDir p = liftIO (createDirectoryIfMissing True $ takeDirectory p)++data ReaderException = ReaderException FilePath String+ deriving (Show, Typeable)+instance Exception ReaderException++tell :: (MonadIO m, Show a) => FilePath -> a -> m ()+tell f x = do+ ensureDir f+ liftIO (writeFile f (ppShow x))++consult :: (MonadIO m, Read a) => FilePath -> m a+consult f = liftIO $ do+ c <- readFile f+ case readEither c of+ Left e ->+ throwIO $ ReaderException f e+ Right a ->+ return a++maybeConsult :: (MonadIO m, Read a) => Maybe FilePath -> a -> m a+maybeConsult Nothing defaultArg = return defaultArg+maybeConsult (Just f) defaultArg = liftIO $ do+ exists <- doesFileExist f+ if exists+ then consult f+ else return defaultArg++maybeConsultSystemPath :: (MonadIO m, Read a) => Maybe SystemPath -> a -> m a+maybeConsultSystemPath Nothing defaultArg = return defaultArg+maybeConsultSystemPath (Just f) defaultArg = liftIO $ do+ f' <- resolve f+ exists <- doesFileExist f'+ if exists+ then consult f'+ else return defaultArg++data IniFileException = IniFileException FilePath CPError+ deriving (Show, Typeable)+instance Exception IniFileException++readIniFile :: MonadIO m => SystemPath -> m ConfigParser+readIniFile cfgFile' = do+ cfgFilePath <- resolve cfgFile'+ liftIO $ do+ res <- readfile emptyCP cfgFilePath+ case res of+ Left e -> throwIO (IniFileException cfgFilePath e)+ Right cp -> return cp++getOption :: (Get_C a, Monoid a) => ConfigParser -> SectionSpec -> OptionSpec -> a+getOption cp sec key = either (const mempty) id $ get cp sec key++getOptionM :: (Read a) => ConfigParser -> SectionSpec -> OptionSpec -> Maybe a+getOptionM cp sec key = either (const Nothing) id $ get cp sec key++getOptionOr :: (Get_C a) => ConfigParser -> SectionSpec -> OptionSpec -> a -> a+getOptionOr cp sec key dv = either (const dv) id $ get cp sec key++newtype UUID = UUID (Word32, Word16, Word16, Word16, Word32, Word16)+ deriving (Read, Show, Eq, Ord)++instance PrintfArg UUID where+ formatArg (UUID (a, b, c, d, e, f)) fmt+ | fmtChar (vFmt 'U' fmt) == 'U' =+ let str = (printf "%08x-%04x-%04x-%04x-%08x%04x" a b c d e f :: String)+ in formatString str (fmt { fmtChar = 's', fmtPrecision = Nothing })+ | otherwise = errorBadFormat $ fmtChar fmt+++randomUUID :: MonadIO m => m UUID+randomUUID = liftIO+ (UUID <$> ((,,,,,) <$> randomIO+ <*> randomIO+ <*> randomIO+ <*> randomIO+ <*> randomIO+ <*> randomIO))