packages feed

b9 0.5.63 → 0.5.65

raw patch · 28 files changed

+2286/−1855 lines, 28 filesdep +monad-controlPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: monad-control

API changes (from Hackage documentation)

- B9: CGEnv :: Environment -> [ArtifactSource] -> CGEnv
- B9: CGError :: String -> CGError
- B9: CGParser :: WriterT [InstanceGenerator CGEnv] (ReaderT CGEnv (Either SomeException)) a -> CGParser a
- B9: KeepPerm :: SGPerm
- B9: MkContentGeneratorT :: EnvironmentReaderT m a -> ContentGeneratorT m a
- B9: SG :: Environment -> SGSource -> SGPerm -> FilePath -> SourceGenerator
- B9: SGContent :: Content -> SGSource
- B9: SGFiles :: [SourceFile] -> SGSource
- B9: SGSetPerm :: (Int, Int, Int) -> SGPerm
- B9: [_buildDirRoot] :: B9Config -> Maybe FilePath
- B9: [_envVars] :: B9Config -> Environment
- B9: [_profileFile] :: B9Config -> Maybe FilePath
- B9: [agEnv] :: CGEnv -> Environment
- B9: [agSources] :: CGEnv -> [ArtifactSource]
- B9: [runCGParser] :: CGParser a -> WriterT [InstanceGenerator CGEnv] (ReaderT CGEnv (Either SomeException)) a
- B9: addBindings :: MonadThrow m => [(String, String)] -> CGEnv -> m CGEnv
- B9: appendPositionalArguments :: [String] -> B9Config -> B9Config
- B9: askRuntimeConfig :: Monad m => B9ConfigAction m B9Config
- B9: buildDirRoot :: Lens' B9Config (Maybe FilePath)
- B9: cgError :: String -> CGParser a
- B9: createAssembledArtifacts :: [InstanceGenerator [SourceGenerator]] -> B9 [AssembledArtifact]
- B9: createTarget :: InstanceId -> FilePath -> ArtifactAssembly -> B9 [ArtifactTarget]
- B9: createTargets :: InstanceGenerator FilePath -> B9 AssembledArtifact
- B9: data B9 a
- B9: data B9ConfigAction m a
- B9: data CGEnv
- B9: data SGPerm
- B9: data SGSource
- B9: data SourceGenerator
- B9: eachBindingSet :: ArtifactGenerator -> [(String, [String])] -> CGParser [[(String, String)]]
- B9: eachBindingSetT :: ArtifactGenerator -> [String] -> [[String]] -> CGParser [[(String, String)]]
- B9: envVars :: Lens' B9Config Environment
- B9: evalArtifactGenerator :: String -> String -> Environment -> ArtifactGenerator -> Either String [InstanceGenerator [SourceGenerator]]
- B9: execB9ConfigAction :: MonadIO m => B9ConfigAction m a -> B9ConfigOverride -> m a
- B9: execCGParser :: CGParser () -> CGEnv -> Either SomeException [InstanceGenerator CGEnv]
- B9: execIGEnv :: InstanceGenerator CGEnv -> Either String (InstanceGenerator [SourceGenerator])
- B9: generateSourceTo :: FilePath -> SourceGenerator -> B9 ()
- B9: generateSources :: FilePath -> InstanceGenerator [SourceGenerator] -> B9 (InstanceGenerator FilePath)
- B9: generateUniqueIID :: InstanceId -> B9 InstanceId
- B9: insertPositionalArguments :: [Text] -> Environment -> Environment
- B9: invokeB9 :: MonadIO m => B9ConfigAction m a -> m a
- B9: localRuntimeConfig :: Monad m => (B9Config -> B9Config) -> B9ConfigAction m a -> B9ConfigAction m a
- B9: newtype CGError
- B9: newtype CGParser a
- B9: newtype ContentGeneratorT m a
- B9: parseArtifactGenerator :: ArtifactGenerator -> CGParser ()
- B9: profileFile :: Lens' B9Config (Maybe FilePath)
- B9: renderContentGenerator :: Environment -> ContentGenerator a -> B9 a
- B9: run :: MonadIO m => B9 a -> B9ConfigAction m a
- B9: runEnvironmentReaderT :: Environment -> EnvironmentReaderT m a -> m a
- B9: setSGFromDirectory :: FilePath -> SourceGenerator -> SourceGenerator
- B9: setSGPerm :: Int -> Int -> Int -> SourceGenerator -> Either String SourceGenerator
- B9: setSGToDirectory :: FilePath -> SourceGenerator -> SourceGenerator
- B9: sgChangePerm :: FilePath -> SGPerm -> B9 ()
- B9: sgGetFroms :: SourceGenerator -> [SourceFile]
- B9: sgReadSourceFile :: Environment -> SourceFile -> B9 ByteString
- B9: sourceGeneratorOutputFile :: SourceGenerator -> FilePath
- B9: substAssembly :: Environment -> ArtifactAssembly -> ArtifactAssembly
- B9: substE :: Environment -> String -> Either String String
- B9: substEB :: Environment -> ByteString -> Either String ByteString
- B9: toSourceGen :: Environment -> ArtifactSource -> Either String [SourceGenerator]
- B9: type ContentGenerator a = ContentGeneratorT B9 a
- B9: type EnvironmentReaderT m a = ReaderT Environment m a
- B9: type MonadEnvironment m = MonadReader Environment m
- B9: withArtifactSources :: [ArtifactSource] -> CGParser () -> CGParser ()
- B9: withBindings :: [(String, String)] -> CGParser () -> CGParser ()
- B9: withXBindings :: [(String, [String])] -> CGParser () -> CGParser ()
- B9: writeInstanceGenerator :: InstanceId -> ArtifactAssembly -> CGParser ()
- B9.Artifact.Content: MkContentGeneratorT :: EnvironmentReaderT m a -> ContentGeneratorT m a
- B9.Artifact.Content: instance (GHC.Base.Monad m, GHC.Base.Monoid a) => GHC.Base.Monoid (B9.Artifact.Content.ContentGeneratorT m a)
- B9.Artifact.Content: instance (GHC.Base.Monad m, GHC.Base.Semigroup a) => GHC.Base.Semigroup (B9.Artifact.Content.ContentGeneratorT m a)
- B9.Artifact.Content: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (B9.Artifact.Content.ContentGeneratorT m)
- B9.Artifact.Content: instance Control.Monad.Trans.Class.MonadTrans B9.Artifact.Content.ContentGeneratorT
- B9.Artifact.Content: instance GHC.Base.Alternative m => GHC.Base.Alternative (B9.Artifact.Content.ContentGeneratorT m)
- B9.Artifact.Content: instance GHC.Base.Applicative m => GHC.Base.Applicative (B9.Artifact.Content.ContentGeneratorT m)
- B9.Artifact.Content: instance GHC.Base.Functor m => GHC.Base.Functor (B9.Artifact.Content.ContentGeneratorT m)
- B9.Artifact.Content: instance GHC.Base.Monad m => Control.Monad.Reader.Class.MonadReader B9.Environment.Environment (B9.Artifact.Content.ContentGeneratorT m)
- B9.Artifact.Content: instance GHC.Base.Monad m => GHC.Base.Monad (B9.Artifact.Content.ContentGeneratorT m)
- B9.Artifact.Content: newtype ContentGeneratorT m a
- B9.Artifact.Content: renderContentGenerator :: Environment -> ContentGenerator a -> B9 a
- B9.Artifact.Content: type ContentGenerator a = ContentGeneratorT B9 a
- B9.Artifact.Content.Readable: instance B9.Artifact.Content.ToContentGenerator B9.Artifact.Content.Readable.Content
- B9.Artifact.Content.StringTemplate: substE :: Environment -> String -> Either String String
- B9.Artifact.Content.StringTemplate: substEB :: Environment -> ByteString -> Either String ByteString
- B9.Artifact.Readable.Interpreter: CGEnv :: Environment -> [ArtifactSource] -> CGEnv
- B9.Artifact.Readable.Interpreter: CGError :: String -> CGError
- B9.Artifact.Readable.Interpreter: CGParser :: WriterT [InstanceGenerator CGEnv] (ReaderT CGEnv (Either SomeException)) a -> CGParser a
- B9.Artifact.Readable.Interpreter: KeepPerm :: SGPerm
- B9.Artifact.Readable.Interpreter: SG :: Environment -> SGSource -> SGPerm -> FilePath -> SourceGenerator
- B9.Artifact.Readable.Interpreter: SGContent :: Content -> SGSource
- B9.Artifact.Readable.Interpreter: SGFiles :: [SourceFile] -> SGSource
- B9.Artifact.Readable.Interpreter: SGSetPerm :: (Int, Int, Int) -> SGPerm
- B9.Artifact.Readable.Interpreter: [agEnv] :: CGEnv -> Environment
- B9.Artifact.Readable.Interpreter: [agSources] :: CGEnv -> [ArtifactSource]
- B9.Artifact.Readable.Interpreter: [runCGParser] :: CGParser a -> WriterT [InstanceGenerator CGEnv] (ReaderT CGEnv (Either SomeException)) a
- B9.Artifact.Readable.Interpreter: addBindings :: MonadThrow m => [(String, String)] -> CGEnv -> m CGEnv
- B9.Artifact.Readable.Interpreter: cgError :: String -> CGParser a
- B9.Artifact.Readable.Interpreter: createAssembledArtifacts :: [InstanceGenerator [SourceGenerator]] -> B9 [AssembledArtifact]
- B9.Artifact.Readable.Interpreter: createTarget :: InstanceId -> FilePath -> ArtifactAssembly -> B9 [ArtifactTarget]
- B9.Artifact.Readable.Interpreter: createTargets :: InstanceGenerator FilePath -> B9 AssembledArtifact
- B9.Artifact.Readable.Interpreter: data CGEnv
- B9.Artifact.Readable.Interpreter: data SGPerm
- B9.Artifact.Readable.Interpreter: data SGSource
- B9.Artifact.Readable.Interpreter: data SourceGenerator
- B9.Artifact.Readable.Interpreter: eachBindingSet :: ArtifactGenerator -> [(String, [String])] -> CGParser [[(String, String)]]
- B9.Artifact.Readable.Interpreter: eachBindingSetT :: ArtifactGenerator -> [String] -> [[String]] -> CGParser [[(String, String)]]
- B9.Artifact.Readable.Interpreter: evalArtifactGenerator :: String -> String -> Environment -> ArtifactGenerator -> Either String [InstanceGenerator [SourceGenerator]]
- B9.Artifact.Readable.Interpreter: execCGParser :: CGParser () -> CGEnv -> Either SomeException [InstanceGenerator CGEnv]
- B9.Artifact.Readable.Interpreter: execIGEnv :: InstanceGenerator CGEnv -> Either String (InstanceGenerator [SourceGenerator])
- B9.Artifact.Readable.Interpreter: generateSourceTo :: FilePath -> SourceGenerator -> B9 ()
- B9.Artifact.Readable.Interpreter: generateSources :: FilePath -> InstanceGenerator [SourceGenerator] -> B9 (InstanceGenerator FilePath)
- B9.Artifact.Readable.Interpreter: generateUniqueIID :: InstanceId -> B9 InstanceId
- B9.Artifact.Readable.Interpreter: instance Control.Monad.Catch.MonadThrow B9.Artifact.Readable.Interpreter.CGParser
- B9.Artifact.Readable.Interpreter: instance Control.Monad.Reader.Class.MonadReader B9.Artifact.Readable.Interpreter.CGEnv B9.Artifact.Readable.Interpreter.CGParser
- B9.Artifact.Readable.Interpreter: instance Control.Monad.Writer.Class.MonadWriter [B9.Artifact.Readable.Interpreter.InstanceGenerator B9.Artifact.Readable.Interpreter.CGEnv] B9.Artifact.Readable.Interpreter.CGParser
- B9.Artifact.Readable.Interpreter: instance Data.Data.Data B9.Artifact.Readable.Interpreter.CGError
- B9.Artifact.Readable.Interpreter: instance Data.Data.Data B9.Artifact.Readable.Interpreter.SGPerm
- B9.Artifact.Readable.Interpreter: instance GHC.Base.Applicative B9.Artifact.Readable.Interpreter.CGParser
- B9.Artifact.Readable.Interpreter: instance GHC.Base.Functor B9.Artifact.Readable.Interpreter.CGParser
- B9.Artifact.Readable.Interpreter: instance GHC.Base.Monad B9.Artifact.Readable.Interpreter.CGParser
- B9.Artifact.Readable.Interpreter: instance GHC.Classes.Eq B9.Artifact.Readable.Interpreter.CGEnv
- B9.Artifact.Readable.Interpreter: instance GHC.Classes.Eq B9.Artifact.Readable.Interpreter.CGError
- B9.Artifact.Readable.Interpreter: instance GHC.Classes.Eq B9.Artifact.Readable.Interpreter.SGPerm
- B9.Artifact.Readable.Interpreter: instance GHC.Classes.Eq B9.Artifact.Readable.Interpreter.SGSource
- B9.Artifact.Readable.Interpreter: instance GHC.Classes.Eq B9.Artifact.Readable.Interpreter.SourceGenerator
- B9.Artifact.Readable.Interpreter: instance GHC.Exception.Type.Exception B9.Artifact.Readable.Interpreter.CGError
- B9.Artifact.Readable.Interpreter: instance GHC.Read.Read B9.Artifact.Readable.Interpreter.CGError
- B9.Artifact.Readable.Interpreter: instance GHC.Read.Read B9.Artifact.Readable.Interpreter.SGPerm
- B9.Artifact.Readable.Interpreter: instance GHC.Read.Read B9.Artifact.Readable.Interpreter.SGSource
- B9.Artifact.Readable.Interpreter: instance GHC.Show.Show B9.Artifact.Readable.Interpreter.CGEnv
- B9.Artifact.Readable.Interpreter: instance GHC.Show.Show B9.Artifact.Readable.Interpreter.CGError
- B9.Artifact.Readable.Interpreter: instance GHC.Show.Show B9.Artifact.Readable.Interpreter.SGPerm
- B9.Artifact.Readable.Interpreter: instance GHC.Show.Show B9.Artifact.Readable.Interpreter.SGSource
- B9.Artifact.Readable.Interpreter: instance GHC.Show.Show B9.Artifact.Readable.Interpreter.SourceGenerator
- B9.Artifact.Readable.Interpreter: newtype CGError
- B9.Artifact.Readable.Interpreter: newtype CGParser a
- B9.Artifact.Readable.Interpreter: parseArtifactGenerator :: ArtifactGenerator -> CGParser ()
- B9.Artifact.Readable.Interpreter: setSGFromDirectory :: FilePath -> SourceGenerator -> SourceGenerator
- B9.Artifact.Readable.Interpreter: setSGPerm :: Int -> Int -> Int -> SourceGenerator -> Either String SourceGenerator
- B9.Artifact.Readable.Interpreter: setSGToDirectory :: FilePath -> SourceGenerator -> SourceGenerator
- B9.Artifact.Readable.Interpreter: sgChangePerm :: FilePath -> SGPerm -> B9 ()
- B9.Artifact.Readable.Interpreter: sgGetFroms :: SourceGenerator -> [SourceFile]
- B9.Artifact.Readable.Interpreter: sgReadSourceFile :: Environment -> SourceFile -> B9 ByteString
- B9.Artifact.Readable.Interpreter: sourceGeneratorOutputFile :: SourceGenerator -> FilePath
- B9.Artifact.Readable.Interpreter: substAssembly :: Environment -> ArtifactAssembly -> ArtifactAssembly
- B9.Artifact.Readable.Interpreter: toSourceGen :: Environment -> ArtifactSource -> Either String [SourceGenerator]
- B9.Artifact.Readable.Interpreter: withArtifactSources :: [ArtifactSource] -> CGParser () -> CGParser ()
- B9.Artifact.Readable.Interpreter: withBindings :: [(String, String)] -> CGParser () -> CGParser ()
- B9.Artifact.Readable.Interpreter: withXBindings :: [(String, [String])] -> CGParser () -> CGParser ()
- B9.Artifact.Readable.Interpreter: writeInstanceGenerator :: InstanceId -> ArtifactAssembly -> CGParser ()
- B9.B9Config: [_buildDirRoot] :: B9Config -> Maybe FilePath
- B9.B9Config: [_envVars] :: B9Config -> Environment
- B9.B9Config: [_profileFile] :: B9Config -> Maybe FilePath
- B9.B9Config: appendPositionalArguments :: [String] -> B9Config -> B9Config
- B9.B9Config: askRuntimeConfig :: Monad m => B9ConfigAction m B9Config
- B9.B9Config: buildDirRoot :: Lens' B9Config (Maybe FilePath)
- B9.B9Config: data B9ConfigAction m a
- B9.B9Config: envVars :: Lens' B9Config Environment
- B9.B9Config: execB9ConfigAction :: MonadIO m => B9ConfigAction m a -> B9ConfigOverride -> m a
- B9.B9Config: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (B9.B9Config.B9ConfigAction m)
- B9.B9Config: instance GHC.Base.Applicative m => GHC.Base.Applicative (B9.B9Config.B9ConfigAction m)
- B9.B9Config: instance GHC.Base.Functor m => GHC.Base.Functor (B9.B9Config.B9ConfigAction m)
- B9.B9Config: instance GHC.Base.Monad m => GHC.Base.Monad (B9.B9Config.B9ConfigAction m)
- B9.B9Config: invokeB9 :: MonadIO m => B9ConfigAction m a -> m a
- B9.B9Config: localRuntimeConfig :: Monad m => (B9Config -> B9Config) -> B9ConfigAction m a -> B9ConfigAction m a
- B9.B9Config: profileFile :: Lens' B9Config (Maybe FilePath)
- B9.B9Monad: cmd :: String -> B9 ()
- B9.B9Monad: data B9 a
- B9.B9Monad: dbgL :: String -> B9 ()
- B9.B9Monad: errorExitL :: String -> B9 a
- B9.B9Monad: errorL :: String -> B9 ()
- B9.B9Monad: getBuildDate :: B9 String
- B9.B9Monad: getBuildDir :: B9 FilePath
- B9.B9Monad: getBuildId :: B9 String
- B9.B9Monad: getConfig :: B9 B9Config
- B9.B9Monad: getExecEnvType :: B9 ExecEnvType
- B9.B9Monad: getRemoteRepos :: B9 [RemoteRepo]
- B9.B9Monad: getRepoCache :: B9 RepoCache
- B9.B9Monad: getSelectedRemoteRepo :: B9 (Maybe RemoteRepo)
- B9.B9Monad: infoL :: String -> B9 ()
- B9.B9Monad: instance Control.Monad.IO.Class.MonadIO B9.B9Monad.B9
- B9.B9Monad: instance Control.Monad.State.Class.MonadState B9.B9Monad.BuildState B9.B9Monad.B9
- B9.B9Monad: instance GHC.Base.Alternative B9.B9Monad.B9
- B9.B9Monad: instance GHC.Base.Applicative B9.B9Monad.B9
- B9.B9Monad: instance GHC.Base.Functor B9.B9Monad.B9
- B9.B9Monad: instance GHC.Base.Monad B9.B9Monad.B9
- B9.B9Monad: instance GHC.Classes.Eq B9.B9Monad.ProfilingEntry
- B9.B9Monad: instance GHC.Show.Show B9.B9Monad.ProfilingEntry
- B9.B9Monad: run :: MonadIO m => B9 a -> B9ConfigAction m a
- B9.B9Monad: traceL :: String -> B9 ()
- B9.Environment: insertPositionalArguments :: [Text] -> Environment -> Environment
- B9.Environment: runEnvironmentReaderT :: Environment -> EnvironmentReaderT m a -> m a
- B9.Environment: type EnvironmentReaderT m a = ReaderT Environment m a
- B9.Environment: type MonadEnvironment m = MonadReader Environment m
+ B9: InstanceSources :: Environment -> [ArtifactSource] -> InstanceSources
+ B9: MkB9Error :: String -> B9Error
+ B9: MkLogger :: Maybe Handle -> Logger
+ B9: MkSelectedRemoteRepo :: Maybe RemoteRepo -> SelectedRemoteRepo
+ B9: [_customEnvironment] :: B9ConfigOverride -> Environment
+ B9: [_projectRoot] :: B9Config -> Maybe FilePath
+ B9: [fromB9Error] :: B9Error -> String
+ B9: [fromSelectedRemoteRepo] :: SelectedRemoteRepo -> Maybe RemoteRepo
+ B9: [isEnv] :: InstanceSources -> Environment
+ B9: [isSources] :: InstanceSources -> [ArtifactSource]
+ B9: [logFileHandle] :: Logger -> Maybe Handle
+ B9: addLocalPositionalArguments :: Member EnvironmentReader e => [String] -> Eff e a -> Eff e a
+ B9: addLocalStringBinding :: (Member EnvironmentReader e, Member ExcB9 e) => (String, String) -> Eff e a -> Eff e a
+ B9: addPositionalArguments :: [Text] -> Environment -> Environment
+ B9: b9Log :: CommandIO e => LogLevel -> String -> Eff e ()
+ B9: catchB9Error :: Member ExcB9 e => Eff e a -> (SomeException -> Eff e a) -> Eff e a
+ B9: catchB9ErrorAsEither :: Member ExcB9 e => Eff e a -> Eff e (Either SomeException a)
+ B9: customEnvironment :: Lens' B9ConfigOverride Environment
+ B9: data InstanceSources
+ B9: errorOnException :: Eff (ExcB9 : e) a -> Eff e a
+ B9: getB9Config :: Member B9ConfigReader e => Eff e B9Config
+ B9: getLogVerbosity :: Member B9ConfigReader e => Eff e (Maybe LogLevel)
+ B9: getProjectRoot :: Member B9ConfigReader e => Eff e FilePath
+ B9: hasKey :: Member EnvironmentReader e => Text -> Eff e Bool
+ B9: isInteractive :: Member B9ConfigReader e => Eff e Bool
+ B9: localB9Config :: Member B9ConfigReader e => (B9Config -> B9Config) -> Eff e a -> Eff e a
+ B9: newtype B9Error
+ B9: newtype Logger
+ B9: newtype SelectedRemoteRepo
+ B9: projectRoot :: Lens' B9Config (Maybe FilePath)
+ B9: runArtifactAssembly :: IsB9 e => InstanceId -> FilePath -> ArtifactAssembly -> Eff e [ArtifactTarget]
+ B9: runArtifactGenerator :: Environment -> String -> String -> ArtifactGenerator -> Either SomeException [InstanceGenerator [TextFileWriter]]
+ B9: runB9 :: B9 a -> B9ConfigAction a
+ B9: runB9ConfigAction :: B9ConfigAction a -> IO a
+ B9: runB9ConfigActionWithOverrides :: B9ConfigAction a -> B9ConfigOverride -> IO a
+ B9: runB9ConfigReader :: B9Config -> Eff (B9ConfigReader : e) a -> Eff e a
+ B9: runEnvironmentReader :: Environment -> Eff (EnvironmentReader : e) a -> Eff e a
+ B9: runExcB9 :: Eff (ExcB9 : e) a -> Eff e (Either SomeException a)
+ B9: runInstanceGenerator :: IsB9 e => InstanceGenerator FilePath -> Eff e AssembledArtifact
+ B9: throwB9Error :: Member ExcB9 e => String -> Eff e a
+ B9: throwB9Error_ :: Member ExcB9 e => String -> Eff e ()
+ B9: throwSomeException :: (Member ExcB9 e, Exception x) => x -> Eff e a
+ B9: throwSomeException_ :: (Member ExcB9 e, Exception x) => x -> Eff e ()
+ B9: type B9 a = Eff B9Eff a
+ B9: type B9ConfigAction a = Eff '[B9ConfigWriter, B9ConfigReader, EnvironmentReader, Lift IO] a
+ B9: type B9ConfigReader = Reader B9Config
+ B9: type B9ConfigWriter = Writer (Endo B9Config)
+ B9: type B9Eff = '[SelectedRemoteRepoReader, RepoCacheReader, BuildInfoReader, LoggerReader, B9ConfigReader, EnvironmentReader, ExcB9, Lift IO]
+ B9: type BuildInfoReader = Reader BuildInfo
+ B9: type CommandIO e = (MonadBaseControl IO (Eff e), MonadIO (Eff e), Member LoggerReader e, Member B9ConfigReader e)
+ B9: type EnvironmentReader = Reader Environment
+ B9: type ExcB9 = Exc SomeException
+ B9: type IsB9 e = (Lifted IO e, CommandIO e, B9Eff <:: e)
+ B9: type LoggerReader = Reader Logger
+ B9: type RepoCacheReader = Reader RepoCache
+ B9: type SelectedRemoteRepoReader = Reader SelectedRemoteRepo
+ B9: withBuildInfo :: (Lifted IO e, MonadBaseControl IO (Eff e), Member B9ConfigReader e, Member ExcB9 e, Member EnvironmentReader e, Member LoggerReader e) => Eff (BuildInfoReader : e) a -> Eff e a
+ B9: withLogger :: (MonadBaseControl IO (Eff e), MonadIO (Eff e), Member B9ConfigReader e) => Eff (LoggerReader : e) a -> Eff e a
+ B9: withRemoteRepos :: (Member B9ConfigReader e, Lifted IO e) => Eff (RepoCacheReader : e) a -> Eff e a
+ B9: withSelectedRemoteRepo :: (Member B9ConfigReader e, Member ExcB9 e) => Eff (SelectedRemoteRepoReader : e) a -> Eff e a
+ B9: withSubstitutedStringBindings :: (Member EnvironmentReader e, Member ExcB9 e) => [(String, String)] -> Eff e s -> Eff e s
+ B9.Artifact.Content.Readable: instance B9.Artifact.Content.ToContentGenerator B9.Artifact.Content.Readable.Content Data.ByteString.Lazy.Internal.ByteString
+ B9.Artifact.Content.StringTemplate: withSubstitutedStringBindings :: (Member EnvironmentReader e, Member ExcB9 e) => [(String, String)] -> Eff e s -> Eff e s
+ B9.Artifact.Readable.Interpreter: InstanceSources :: Environment -> [ArtifactSource] -> InstanceSources
+ B9.Artifact.Readable.Interpreter: [isEnv] :: InstanceSources -> Environment
+ B9.Artifact.Readable.Interpreter: [isSources] :: InstanceSources -> [ArtifactSource]
+ B9.Artifact.Readable.Interpreter: data InstanceSources
+ B9.Artifact.Readable.Interpreter: instance Data.Data.Data B9.Artifact.Readable.Interpreter.FilePermissionAction
+ B9.Artifact.Readable.Interpreter: instance GHC.Classes.Eq B9.Artifact.Readable.Interpreter.FilePermissionAction
+ B9.Artifact.Readable.Interpreter: instance GHC.Classes.Eq B9.Artifact.Readable.Interpreter.InstanceSources
+ B9.Artifact.Readable.Interpreter: instance GHC.Classes.Eq B9.Artifact.Readable.Interpreter.TextFileWriter
+ B9.Artifact.Readable.Interpreter: instance GHC.Classes.Eq B9.Artifact.Readable.Interpreter.TextFileWriterInput
+ B9.Artifact.Readable.Interpreter: instance GHC.Read.Read B9.Artifact.Readable.Interpreter.FilePermissionAction
+ B9.Artifact.Readable.Interpreter: instance GHC.Read.Read B9.Artifact.Readable.Interpreter.TextFileWriterInput
+ B9.Artifact.Readable.Interpreter: instance GHC.Show.Show B9.Artifact.Readable.Interpreter.FilePermissionAction
+ B9.Artifact.Readable.Interpreter: instance GHC.Show.Show B9.Artifact.Readable.Interpreter.InstanceSources
+ B9.Artifact.Readable.Interpreter: instance GHC.Show.Show B9.Artifact.Readable.Interpreter.TextFileWriter
+ B9.Artifact.Readable.Interpreter: instance GHC.Show.Show B9.Artifact.Readable.Interpreter.TextFileWriterInput
+ B9.Artifact.Readable.Interpreter: runArtifactAssembly :: IsB9 e => InstanceId -> FilePath -> ArtifactAssembly -> Eff e [ArtifactTarget]
+ B9.Artifact.Readable.Interpreter: runArtifactGenerator :: Environment -> String -> String -> ArtifactGenerator -> Either SomeException [InstanceGenerator [TextFileWriter]]
+ B9.Artifact.Readable.Interpreter: runInstanceGenerator :: IsB9 e => InstanceGenerator FilePath -> Eff e AssembledArtifact
+ B9.B9Config: [_customEnvironment] :: B9ConfigOverride -> Environment
+ B9.B9Config: [_projectRoot] :: B9Config -> Maybe FilePath
+ B9.B9Config: customEnvironment :: Lens' B9ConfigOverride Environment
+ B9.B9Config: getB9Config :: Member B9ConfigReader e => Eff e B9Config
+ B9.B9Config: getConfig :: Member B9ConfigReader e => Eff e B9Config
+ B9.B9Config: getExecEnvType :: Member B9ConfigReader e => Eff e ExecEnvType
+ B9.B9Config: getLogVerbosity :: Member B9ConfigReader e => Eff e (Maybe LogLevel)
+ B9.B9Config: getProjectRoot :: Member B9ConfigReader e => Eff e FilePath
+ B9.B9Config: getRemoteRepos :: Member B9ConfigReader e => Eff e [RemoteRepo]
+ B9.B9Config: instance GHC.Classes.Eq B9.B9Config.B9Config
+ B9.B9Config: isInteractive :: Member B9ConfigReader e => Eff e Bool
+ B9.B9Config: localB9Config :: Member B9ConfigReader e => (B9Config -> B9Config) -> Eff e a -> Eff e a
+ B9.B9Config: projectRoot :: Lens' B9Config (Maybe FilePath)
+ B9.B9Config: runB9ConfigAction :: B9ConfigAction a -> IO a
+ B9.B9Config: runB9ConfigActionWithOverrides :: B9ConfigAction a -> B9ConfigOverride -> IO a
+ B9.B9Config: runB9ConfigReader :: B9Config -> Eff (B9ConfigReader : e) a -> Eff e a
+ B9.B9Config: type B9ConfigAction a = Eff '[B9ConfigWriter, B9ConfigReader, EnvironmentReader, Lift IO] a
+ B9.B9Config: type B9ConfigReader = Reader B9Config
+ B9.B9Config: type B9ConfigWriter = Writer (Endo B9Config)
+ B9.B9Config.LibVirtLXC: instance GHC.Classes.Eq B9.B9Config.LibVirtLXC.LXCGuestCapability
+ B9.B9Config.LibVirtLXC: instance GHC.Classes.Eq B9.B9Config.LibVirtLXC.LibVirtLXCConfig
+ B9.B9Config.Repository: instance GHC.Classes.Eq B9.B9Config.Repository.RemoteRepo
+ B9.B9Config.Repository: instance GHC.Classes.Eq B9.B9Config.Repository.SshPrivKey
+ B9.B9Config.Repository: instance GHC.Classes.Eq B9.B9Config.Repository.SshRemoteHost
+ B9.B9Config.Repository: instance GHC.Classes.Eq B9.B9Config.Repository.SshRemoteUser
+ B9.B9Error: MkB9Error :: String -> B9Error
+ B9.B9Error: catchB9Error :: Member ExcB9 e => Eff e a -> (SomeException -> Eff e a) -> Eff e a
+ B9.B9Error: catchB9ErrorAsEither :: Member ExcB9 e => Eff e a -> Eff e (Either SomeException a)
+ B9.B9Error: errorOnException :: Eff (ExcB9 : e) a -> Eff e a
+ B9.B9Error: fromB9Error :: B9Error -> String
+ B9.B9Error: instance Data.String.IsString B9.B9Error.B9Error
+ B9.B9Error: instance GHC.Exception.Type.Exception B9.B9Error.B9Error
+ B9.B9Error: instance GHC.Show.Show B9.B9Error.B9Error
+ B9.B9Error: newtype B9Error
+ B9.B9Error: runExcB9 :: Eff (ExcB9 : e) a -> Eff e (Either SomeException a)
+ B9.B9Error: throwB9Error :: Member ExcB9 e => String -> Eff e a
+ B9.B9Error: throwB9Error_ :: Member ExcB9 e => String -> Eff e ()
+ B9.B9Error: throwSomeException :: (Member ExcB9 e, Exception x) => x -> Eff e a
+ B9.B9Error: throwSomeException_ :: (Member ExcB9 e, Exception x) => x -> Eff e ()
+ B9.B9Error: type ExcB9 = Exc SomeException
+ B9.B9Exec: cmd :: CommandIO e => String -> Eff e ()
+ B9.B9Logging: MkLogger :: Maybe Handle -> Logger
+ B9.B9Logging: [logFileHandle] :: Logger -> Maybe Handle
+ B9.B9Logging: b9Log :: CommandIO e => LogLevel -> String -> Eff e ()
+ B9.B9Logging: dbgL :: CommandIO e => String -> Eff e ()
+ B9.B9Logging: errorExitL :: (CommandIO e, Member ExcB9 e) => String -> Eff e a
+ B9.B9Logging: errorL :: CommandIO e => String -> Eff e ()
+ B9.B9Logging: infoL :: CommandIO e => String -> Eff e ()
+ B9.B9Logging: newtype Logger
+ B9.B9Logging: traceL :: CommandIO e => String -> Eff e ()
+ B9.B9Logging: type CommandIO e = (MonadBaseControl IO (Eff e), MonadIO (Eff e), Member LoggerReader e, Member B9ConfigReader e)
+ B9.B9Logging: type LoggerReader = Reader Logger
+ B9.B9Logging: withLogger :: (MonadBaseControl IO (Eff e), MonadIO (Eff e), Member B9ConfigReader e) => Eff (LoggerReader : e) a -> Eff e a
+ B9.B9Monad: runB9 :: B9 a -> B9ConfigAction a
+ B9.B9Monad: type B9 a = Eff B9Eff a
+ B9.B9Monad: type B9Eff = '[SelectedRemoteRepoReader, RepoCacheReader, BuildInfoReader, LoggerReader, B9ConfigReader, EnvironmentReader, ExcB9, Lift IO]
+ B9.B9Monad: type IsB9 e = (Lifted IO e, CommandIO e, B9Eff <:: e)
+ B9.BuildInfo: getBuildDate :: Member BuildInfoReader e => Eff e String
+ B9.BuildInfo: getBuildDir :: Member BuildInfoReader e => Eff e FilePath
+ B9.BuildInfo: getBuildId :: Member BuildInfoReader e => Eff e String
+ B9.BuildInfo: getExecEnvType :: Member B9ConfigReader e => Eff e ExecEnvType
+ B9.BuildInfo: instance GHC.Classes.Eq B9.BuildInfo.BuildInfo
+ B9.BuildInfo: instance GHC.Show.Show B9.BuildInfo.BuildInfo
+ B9.BuildInfo: type BuildInfoReader = Reader BuildInfo
+ B9.BuildInfo: withBuildInfo :: (Lifted IO e, MonadBaseControl IO (Eff e), Member B9ConfigReader e, Member ExcB9 e, Member EnvironmentReader e, Member LoggerReader e) => Eff (BuildInfoReader : e) a -> Eff e a
+ B9.Environment: addLocalPositionalArguments :: Member EnvironmentReader e => [String] -> Eff e a -> Eff e a
+ B9.Environment: addLocalStringBinding :: (Member EnvironmentReader e, Member ExcB9 e) => (String, String) -> Eff e a -> Eff e a
+ B9.Environment: addPositionalArguments :: [Text] -> Environment -> Environment
+ B9.Environment: hasKey :: Member EnvironmentReader e => Text -> Eff e Bool
+ B9.Environment: runEnvironmentReader :: Environment -> Eff (EnvironmentReader : e) a -> Eff e a
+ B9.Environment: type EnvironmentReader = Reader Environment
+ B9.Repository: MkSelectedRemoteRepo :: Maybe RemoteRepo -> SelectedRemoteRepo
+ B9.Repository: [fromSelectedRemoteRepo] :: SelectedRemoteRepo -> Maybe RemoteRepo
+ B9.Repository: getRepoCache :: Member RepoCacheReader e => Eff e RepoCache
+ B9.Repository: getSelectedRemoteRepo :: Member SelectedRemoteRepoReader e => Eff e SelectedRemoteRepo
+ B9.Repository: newtype SelectedRemoteRepo
+ B9.Repository: type RepoCacheReader = Reader RepoCache
+ B9.Repository: type SelectedRemoteRepoReader = Reader SelectedRemoteRepo
+ B9.Repository: withRemoteRepos :: (Member B9ConfigReader e, Lifted IO e) => Eff (RepoCacheReader : e) a -> Eff e a
+ B9.Repository: withSelectedRemoteRepo :: (Member B9ConfigReader e, Member ExcB9 e) => Eff (SelectedRemoteRepoReader : e) a -> Eff e a
- B9: B9Config :: Maybe LogLevel -> Maybe FilePath -> Maybe FilePath -> Bool -> ExecEnvType -> Maybe FilePath -> Environment -> Bool -> Maybe SystemPath -> Maybe String -> Bool -> Maybe Int -> Maybe LibVirtLXCConfig -> [RemoteRepo] -> B9Config
+ B9: B9Config :: Maybe LogLevel -> Maybe FilePath -> Maybe FilePath -> Bool -> ExecEnvType -> Bool -> Maybe SystemPath -> Maybe String -> Bool -> Maybe Int -> Maybe LibVirtLXCConfig -> [RemoteRepo] -> B9Config
- B9: B9ConfigOverride :: Maybe SystemPath -> B9Config -> Maybe (Maybe String) -> B9ConfigOverride
+ B9: B9ConfigOverride :: Maybe SystemPath -> B9Config -> Maybe (Maybe String) -> Environment -> B9ConfigOverride
- B9: addStringBinding :: MonadThrow m => (String, String) -> Environment -> m Environment
+ B9: addStringBinding :: Member ExcB9 e => (String, String) -> Environment -> Eff e Environment
- B9: askEnvironment :: MonadEnvironment m => m Environment
+ B9: askEnvironment :: Member EnvironmentReader e => Eff e Environment
- B9: buildWithVm :: InstanceId -> [ImageTarget] -> FilePath -> VmScript -> B9 Bool
+ B9: buildWithVm :: IsB9 e => InstanceId -> [ImageTarget] -> FilePath -> VmScript -> Eff e Bool
- B9: class ToContentGenerator c
+ B9: class ToContentGenerator c a
- B9: cmd :: String -> B9 ()
+ B9: cmd :: CommandIO e => String -> Eff e ()
- B9: convertImage :: Image -> Image -> B9 ()
+ B9: convertImage :: IsB9 e => Image -> Image -> Eff e ()
- B9: createDestinationImage :: Image -> ImageDestination -> B9 ()
+ B9: createDestinationImage :: IsB9 e => Image -> ImageDestination -> Eff e ()
- B9: dbgL :: String -> B9 ()
+ B9: dbgL :: CommandIO e => String -> Eff e ()
- B9: ensureAbsoluteImageDirExists :: Image -> B9 Image
+ B9: ensureAbsoluteImageDirExists :: IsB9 e => Image -> Eff e Image
- B9: errorExitL :: String -> B9 a
+ B9: errorExitL :: (CommandIO e, Member ExcB9 e) => String -> Eff e a
- B9: errorL :: String -> B9 ()
+ B9: errorL :: CommandIO e => String -> Eff e ()
- B9: exportAndRemoveImage :: Image -> Image -> B9 ()
+ B9: exportAndRemoveImage :: IsB9 e => Image -> Image -> Eff e ()
- B9: exportImage :: Image -> Image -> B9 ()
+ B9: exportImage :: IsB9 e => Image -> Image -> Eff e ()
- B9: fromAST :: (FromAST a, ToContentGenerator c) => AST c a -> ContentGenerator a
+ B9: fromAST :: (FromAST a, IsB9 e, ToContentGenerator c ByteString) => AST c a -> Eff e a
- B9: getArtifactOutputFiles :: ArtifactGenerator -> Either String [FilePath]
+ B9: getArtifactOutputFiles :: ArtifactGenerator -> Either SomeException [FilePath]
- B9: getBuildDate :: B9 String
+ B9: getBuildDate :: Member BuildInfoReader e => Eff e String
- B9: getBuildDir :: B9 FilePath
+ B9: getBuildDir :: Member BuildInfoReader e => Eff e FilePath
- B9: getBuildId :: B9 String
+ B9: getBuildId :: Member BuildInfoReader e => Eff e String
- B9: getConfig :: B9 B9Config
+ B9: getConfig :: Member B9ConfigReader e => Eff e B9Config
- B9: getExecEnvType :: B9 ExecEnvType
+ B9: getExecEnvType :: Member B9ConfigReader e => Eff e ExecEnvType
- B9: getRemoteRepos :: B9 [RemoteRepo]
+ B9: getRemoteRepos :: Member B9ConfigReader e => Eff e [RemoteRepo]
- B9: getRepoCache :: B9 RepoCache
+ B9: getRepoCache :: Member RepoCacheReader e => Eff e RepoCache
- B9: getSelectedRemoteRepo :: B9 (Maybe RemoteRepo)
+ B9: getSelectedRemoteRepo :: Member SelectedRemoteRepoReader e => Eff e SelectedRemoteRepo
- B9: getSelectedRepos :: B9 [RemoteRepo]
+ B9: getSelectedRepos :: IsB9 e => Eff e [RemoteRepo]
- B9: getSharedImages :: B9 [(Repository, [SharedImage])]
+ B9: getSharedImages :: IsB9 e => Eff e [(Repository, [SharedImage])]
- B9: getSharedImagesCacheDir :: B9 FilePath
+ B9: getSharedImagesCacheDir :: IsB9 e => Eff e FilePath
- B9: importImage :: Image -> Image -> B9 ()
+ B9: importImage :: IsB9 e => Image -> Image -> Eff e ()
- B9: infoL :: String -> B9 ()
+ B9: infoL :: CommandIO e => String -> Eff e ()
- B9: localEnvironment :: MonadEnvironment m => (Environment -> Environment) -> m a -> m a
+ B9: localEnvironment :: Member EnvironmentReader e => (Environment -> Environment) -> Eff e a -> Eff e a
- B9: lookupEither :: MonadEnvironment m => Text -> m (Either KeyNotFound Text)
+ B9: lookupEither :: Member EnvironmentReader e => Text -> Eff e (Either KeyNotFound Text)
- B9: lookupOrThrow :: (MonadThrow m, MonadEnvironment m) => Text -> m Text
+ B9: lookupOrThrow :: '[ExcB9, EnvironmentReader] <:: e => Text -> Eff e Text
- B9: lookupSharedImages :: (Repository -> Bool) -> (SharedImage -> Bool) -> B9 [(Repository, SharedImage)]
+ B9: lookupSharedImages :: IsB9 e => (Repository -> Bool) -> (SharedImage -> Bool) -> Eff e [(Repository, SharedImage)]
- B9: materializeImageSource :: ImageSource -> Image -> B9 ()
+ B9: materializeImageSource :: IsB9 e => ImageSource -> Image -> Eff e ()
- B9: modifyPermanentConfig :: Monad m => Endo B9Config -> B9ConfigAction m ()
+ B9: modifyPermanentConfig :: Member B9ConfigWriter e => Endo B9Config -> Eff e ()
- B9: preferredDestImageTypes :: ImageSource -> B9 [ImageType]
+ B9: preferredDestImageTypes :: IsB9 e => ImageSource -> Eff e [ImageType]
- B9: pullFromRepo :: RemoteRepo -> FilePath -> FilePath -> B9 ()
+ B9: pullFromRepo :: CommandIO e => RemoteRepo -> FilePath -> FilePath -> Eff e ()
- B9: pullGlob :: FilePath -> FilePathGlob -> RemoteRepo -> B9 ()
+ B9: pullGlob :: (CommandIO e, Member RepoCacheReader e) => FilePath -> FilePathGlob -> RemoteRepo -> Eff e ()
- B9: pullLatestImage :: SharedImageName -> B9 (Maybe SharedImageBuildId)
+ B9: pullLatestImage :: IsB9 e => SharedImageName -> Eff e (Maybe SharedImageBuildId)
- B9: pullRemoteRepos :: B9 ()
+ B9: pullRemoteRepos :: IsB9 e => Eff e ()
- B9: pushSharedImageLatestVersion :: SharedImageName -> B9 ()
+ B9: pushSharedImageLatestVersion :: IsB9 e => SharedImageName -> Eff e ()
- B9: pushToRepo :: RemoteRepo -> FilePath -> FilePath -> B9 ()
+ B9: pushToRepo :: CommandIO e => RemoteRepo -> FilePath -> FilePath -> Eff e ()
- B9: readTemplateFile :: (MonadIO m, MonadEnvironment m) => SourceFile -> m ByteString
+ B9: readTemplateFile :: (MonadIO (Eff e), '[ExcB9, EnvironmentReader] <:: e) => SourceFile -> Eff e ByteString
- B9: repoSearch :: FilePath -> FilePathGlob -> B9 [(Repository, [FilePath])]
+ B9: repoSearch :: forall e. (CommandIO e, Member RepoCacheReader e) => FilePath -> FilePathGlob -> Eff e [(Repository, [FilePath])]
- B9: resizeImage :: ImageResize -> Image -> B9 ()
+ B9: resizeImage :: IsB9 e => ImageResize -> Image -> Eff e ()
- B9: resolveImageSource :: ImageSource -> B9 Image
+ B9: resolveImageSource :: IsB9 e => ImageSource -> Eff e Image
- B9: runAddRepo :: MonadIO m => RemoteRepo -> B9ConfigAction m ()
+ B9: runAddRepo :: RemoteRepo -> B9ConfigAction ()
- B9: runBuildArtifacts :: MonadIO m => [FilePath] -> B9ConfigAction m String
+ B9: runBuildArtifacts :: [FilePath] -> B9ConfigAction String
- B9: runGcLocalRepoCache :: MonadIO m => B9ConfigAction m ()
+ B9: runGcLocalRepoCache :: B9ConfigAction ()
- B9: runGcRemoteRepoCache :: MonadIO m => B9ConfigAction m ()
+ B9: runGcRemoteRepoCache :: B9ConfigAction ()
- B9: runListSharedImages :: MonadIO m => B9ConfigAction m [SharedImage]
+ B9: runListSharedImages :: B9ConfigAction [SharedImage]
- B9: runLookupLocalSharedImage :: MonadIO m => SharedImageName -> B9ConfigAction m (Maybe SharedImageBuildId)
+ B9: runLookupLocalSharedImage :: SharedImageName -> B9ConfigAction (Maybe SharedImageBuildId)
- B9: runPull :: MonadIO m => Maybe SharedImageName -> B9ConfigAction m ()
+ B9: runPull :: Maybe SharedImageName -> B9ConfigAction ()
- B9: runPush :: MonadIO m => SharedImageName -> B9ConfigAction m ()
+ B9: runPush :: SharedImageName -> B9ConfigAction ()
- B9: runRun :: MonadIO m => SharedImageName -> [String] -> B9ConfigAction m String
+ B9: runRun :: SharedImageName -> [String] -> B9ConfigAction String
- B9: shareImage :: Image -> SharedImageName -> B9 SharedImage
+ B9: shareImage :: IsB9 e => Image -> SharedImageName -> Eff e SharedImage
- B9: subst :: Environment -> String -> String
+ B9: subst :: (Member ExcB9 e, Member EnvironmentReader e) => String -> Eff e String
- B9: substFile :: MonadIO m => Environment -> FilePath -> FilePath -> m ()
+ B9: substFile :: (Member EnvironmentReader e, Member ExcB9 e, MonadIO (Eff e)) => FilePath -> FilePath -> Eff e ()
- B9: substImageTarget :: Environment -> ImageTarget -> ImageTarget
+ B9: substImageTarget :: forall e. (Member EnvironmentReader e, Member ExcB9 e) => ImageTarget -> Eff e ImageTarget
- B9: substPath :: Environment -> SystemPath -> SystemPath
+ B9: substPath :: (Member EnvironmentReader e, Member ExcB9 e) => SystemPath -> Eff e SystemPath
- B9: substVmScript :: Environment -> VmScript -> VmScript
+ B9: substVmScript :: forall e. (Member EnvironmentReader e, Member ExcB9 e) => VmScript -> Eff e VmScript
- B9: toContentGenerator :: ToContentGenerator c => c -> ByteStringGenerator
+ B9: toContentGenerator :: (ToContentGenerator c a, IsB9 e) => c -> Eff e a
- B9: traceL :: String -> B9 ()
+ B9: traceL :: CommandIO e => String -> Eff e ()
- B9: type ByteStringGenerator = ContentGenerator ByteString
+ B9: type ByteStringGenerator = B9 ByteString
- B9.Artifact.Content: class ToContentGenerator c
+ B9.Artifact.Content: class ToContentGenerator c a
- B9.Artifact.Content: toContentGenerator :: ToContentGenerator c => c -> ByteStringGenerator
+ B9.Artifact.Content: toContentGenerator :: (ToContentGenerator c a, IsB9 e) => c -> Eff e a
- B9.Artifact.Content: type ByteStringGenerator = ContentGenerator ByteString
+ B9.Artifact.Content: type ByteStringGenerator = B9 ByteString
- B9.Artifact.Content.AST: fromAST :: (FromAST a, ToContentGenerator c) => AST c a -> ContentGenerator a
+ B9.Artifact.Content.AST: fromAST :: (FromAST a, IsB9 e, ToContentGenerator c ByteString) => AST c a -> Eff e a
- B9.Artifact.Content.StringTemplate: readTemplateFile :: (MonadIO m, MonadEnvironment m) => SourceFile -> m ByteString
+ B9.Artifact.Content.StringTemplate: readTemplateFile :: (MonadIO (Eff e), '[ExcB9, EnvironmentReader] <:: e) => SourceFile -> Eff e ByteString
- B9.Artifact.Content.StringTemplate: subst :: Environment -> String -> String
+ B9.Artifact.Content.StringTemplate: subst :: (Member ExcB9 e, Member EnvironmentReader e) => String -> Eff e String
- B9.Artifact.Content.StringTemplate: substFile :: MonadIO m => Environment -> FilePath -> FilePath -> m ()
+ B9.Artifact.Content.StringTemplate: substFile :: (Member EnvironmentReader e, Member ExcB9 e, MonadIO (Eff e)) => FilePath -> FilePath -> Eff e ()
- B9.Artifact.Content.StringTemplate: substPath :: Environment -> SystemPath -> SystemPath
+ B9.Artifact.Content.StringTemplate: substPath :: (Member EnvironmentReader e, Member ExcB9 e) => SystemPath -> Eff e SystemPath
- B9.Artifact.Readable.Interpreter: getArtifactOutputFiles :: ArtifactGenerator -> Either String [FilePath]
+ B9.Artifact.Readable.Interpreter: getArtifactOutputFiles :: ArtifactGenerator -> Either SomeException [FilePath]
- B9.B9Config: B9Config :: Maybe LogLevel -> Maybe FilePath -> Maybe FilePath -> Bool -> ExecEnvType -> Maybe FilePath -> Environment -> Bool -> Maybe SystemPath -> Maybe String -> Bool -> Maybe Int -> Maybe LibVirtLXCConfig -> [RemoteRepo] -> B9Config
+ B9.B9Config: B9Config :: Maybe LogLevel -> Maybe FilePath -> Maybe FilePath -> Bool -> ExecEnvType -> Bool -> Maybe SystemPath -> Maybe String -> Bool -> Maybe Int -> Maybe LibVirtLXCConfig -> [RemoteRepo] -> B9Config
- B9.B9Config: B9ConfigOverride :: Maybe SystemPath -> B9Config -> Maybe (Maybe String) -> B9ConfigOverride
+ B9.B9Config: B9ConfigOverride :: Maybe SystemPath -> B9Config -> Maybe (Maybe String) -> Environment -> B9ConfigOverride
- B9.B9Config: modifyPermanentConfig :: Monad m => Endo B9Config -> B9ConfigAction m ()
+ B9.B9Config: modifyPermanentConfig :: Member B9ConfigWriter e => Endo B9Config -> Eff e ()
- B9.DiskImageBuilder: convertImage :: Image -> Image -> B9 ()
+ B9.DiskImageBuilder: convertImage :: IsB9 e => Image -> Image -> Eff e ()
- B9.DiskImageBuilder: createDestinationImage :: Image -> ImageDestination -> B9 ()
+ B9.DiskImageBuilder: createDestinationImage :: IsB9 e => Image -> ImageDestination -> Eff e ()
- B9.DiskImageBuilder: ensureAbsoluteImageDirExists :: Image -> B9 Image
+ B9.DiskImageBuilder: ensureAbsoluteImageDirExists :: IsB9 e => Image -> Eff e Image
- B9.DiskImageBuilder: exportAndRemoveImage :: Image -> Image -> B9 ()
+ B9.DiskImageBuilder: exportAndRemoveImage :: IsB9 e => Image -> Image -> Eff e ()
- B9.DiskImageBuilder: exportImage :: Image -> Image -> B9 ()
+ B9.DiskImageBuilder: exportImage :: IsB9 e => Image -> Image -> Eff e ()
- B9.DiskImageBuilder: getSelectedRepos :: B9 [RemoteRepo]
+ B9.DiskImageBuilder: getSelectedRepos :: IsB9 e => Eff e [RemoteRepo]
- B9.DiskImageBuilder: getSharedImages :: B9 [(Repository, [SharedImage])]
+ B9.DiskImageBuilder: getSharedImages :: IsB9 e => Eff e [(Repository, [SharedImage])]
- B9.DiskImageBuilder: getSharedImagesCacheDir :: B9 FilePath
+ B9.DiskImageBuilder: getSharedImagesCacheDir :: IsB9 e => Eff e FilePath
- B9.DiskImageBuilder: importImage :: Image -> Image -> B9 ()
+ B9.DiskImageBuilder: importImage :: IsB9 e => Image -> Image -> Eff e ()
- B9.DiskImageBuilder: lookupSharedImages :: (Repository -> Bool) -> (SharedImage -> Bool) -> B9 [(Repository, SharedImage)]
+ B9.DiskImageBuilder: lookupSharedImages :: IsB9 e => (Repository -> Bool) -> (SharedImage -> Bool) -> Eff e [(Repository, SharedImage)]
- B9.DiskImageBuilder: materializeImageSource :: ImageSource -> Image -> B9 ()
+ B9.DiskImageBuilder: materializeImageSource :: IsB9 e => ImageSource -> Image -> Eff e ()
- B9.DiskImageBuilder: preferredDestImageTypes :: ImageSource -> B9 [ImageType]
+ B9.DiskImageBuilder: preferredDestImageTypes :: IsB9 e => ImageSource -> Eff e [ImageType]
- B9.DiskImageBuilder: pullLatestImage :: SharedImageName -> B9 (Maybe SharedImageBuildId)
+ B9.DiskImageBuilder: pullLatestImage :: IsB9 e => SharedImageName -> Eff e (Maybe SharedImageBuildId)
- B9.DiskImageBuilder: pullRemoteRepos :: B9 ()
+ B9.DiskImageBuilder: pullRemoteRepos :: IsB9 e => Eff e ()
- B9.DiskImageBuilder: pushSharedImageLatestVersion :: SharedImageName -> B9 ()
+ B9.DiskImageBuilder: pushSharedImageLatestVersion :: IsB9 e => SharedImageName -> Eff e ()
- B9.DiskImageBuilder: resizeImage :: ImageResize -> Image -> B9 ()
+ B9.DiskImageBuilder: resizeImage :: IsB9 e => ImageResize -> Image -> Eff e ()
- B9.DiskImageBuilder: resolveImageSource :: ImageSource -> B9 Image
+ B9.DiskImageBuilder: resolveImageSource :: IsB9 e => ImageSource -> Eff e Image
- B9.DiskImageBuilder: shareImage :: Image -> SharedImageName -> B9 SharedImage
+ B9.DiskImageBuilder: shareImage :: IsB9 e => Image -> SharedImageName -> Eff e SharedImage
- B9.DiskImageBuilder: substImageTarget :: Environment -> ImageTarget -> ImageTarget
+ B9.DiskImageBuilder: substImageTarget :: forall e. (Member EnvironmentReader e, Member ExcB9 e) => ImageTarget -> Eff e ImageTarget
- B9.Environment: addStringBinding :: MonadThrow m => (String, String) -> Environment -> m Environment
+ B9.Environment: addStringBinding :: Member ExcB9 e => (String, String) -> Environment -> Eff e Environment
- B9.Environment: askEnvironment :: MonadEnvironment m => m Environment
+ B9.Environment: askEnvironment :: Member EnvironmentReader e => Eff e Environment
- B9.Environment: localEnvironment :: MonadEnvironment m => (Environment -> Environment) -> m a -> m a
+ B9.Environment: localEnvironment :: Member EnvironmentReader e => (Environment -> Environment) -> Eff e a -> Eff e a
- B9.Environment: lookupEither :: MonadEnvironment m => Text -> m (Either KeyNotFound Text)
+ B9.Environment: lookupEither :: Member EnvironmentReader e => Text -> Eff e (Either KeyNotFound Text)
- B9.Environment: lookupOrThrow :: (MonadThrow m, MonadEnvironment m) => Text -> m Text
+ B9.Environment: lookupOrThrow :: '[ExcB9, EnvironmentReader] <:: e => Text -> Eff e Text
- B9.LibVirtLXC: logLibVirtLXCConfig :: LibVirtLXCConfig -> B9 ()
+ B9.LibVirtLXC: logLibVirtLXCConfig :: CommandIO e => LibVirtLXCConfig -> Eff e ()
- B9.LibVirtLXC: runInEnvironment :: ExecEnv -> Script -> B9 Bool
+ B9.LibVirtLXC: runInEnvironment :: forall e. (Member BuildInfoReader e, CommandIO e) => ExecEnv -> Script -> Eff e Bool
- B9.RepositoryIO: pullFromRepo :: RemoteRepo -> FilePath -> FilePath -> B9 ()
+ B9.RepositoryIO: pullFromRepo :: CommandIO e => RemoteRepo -> FilePath -> FilePath -> Eff e ()
- B9.RepositoryIO: pullGlob :: FilePath -> FilePathGlob -> RemoteRepo -> B9 ()
+ B9.RepositoryIO: pullGlob :: (CommandIO e, Member RepoCacheReader e) => FilePath -> FilePathGlob -> RemoteRepo -> Eff e ()
- B9.RepositoryIO: pushToRepo :: RemoteRepo -> FilePath -> FilePath -> B9 ()
+ B9.RepositoryIO: pushToRepo :: CommandIO e => RemoteRepo -> FilePath -> FilePath -> Eff e ()
- B9.RepositoryIO: repoSearch :: FilePath -> FilePathGlob -> B9 [(Repository, [FilePath])]
+ B9.RepositoryIO: repoSearch :: forall e. (CommandIO e, Member RepoCacheReader e) => FilePath -> FilePathGlob -> Eff e [(Repository, [FilePath])]
- B9.Shake.Actions: b9InvocationAction :: B9ConfigAction Action a -> B9ConfigOverride -> Action a
+ B9.Shake.Actions: b9InvocationAction :: B9ConfigAction a -> B9ConfigOverride -> Action a
- B9.Vm: substVmScript :: Environment -> VmScript -> VmScript
+ B9.Vm: substVmScript :: forall e. (Member EnvironmentReader e, Member ExcB9 e) => VmScript -> Eff e VmScript
- B9.VmBuilder: buildWithVm :: InstanceId -> [ImageTarget] -> FilePath -> VmScript -> B9 Bool
+ B9.VmBuilder: buildWithVm :: IsB9 e => InstanceId -> [ImageTarget] -> FilePath -> VmScript -> Eff e Bool

Files

CHANGELOG.md view
@@ -1,44 +1,54 @@ # Changelog for B9 +## 0.5.65++* Refactor the B9 Monad to use `extensible-effects`++## 0.5.64++* Refactor and prepare for more drastic changes++* Fix runtime errors due to `undefined` values in the environment+ ## 0.5.63 -* Depend on `shake-0.17.6` to fix build errors +* Depend on `shake-0.17.6` to fix build errors  ## 0.5.62 -* Rewrite `B9.Shake.SharedImageRules` in line with -  Shake's example for custom rules  +* Rewrite `B9.Shake.SharedImageRules` in line with+  Shake's example for custom rules  * Replace `ConcatableSyntax` by using `Binary` instances, and also      * Remove/Inline `encodeSyntax` by using `Binary.encode`-    +     * Rename `decodeSyntax` to `decodeOrFail'` and delegate-      to `Binary.decodeOrFail`. -    +      to `Binary.decodeOrFail`. + * Add a newtype wrapper around `YamlObject` for **cloud-init** yaml documents   `CloudConfigYaml`-  +   This new type serves the purpose of add the `#cloud-config`-  line to the top of the generated yaml document, +  line to the top of the generated yaml document,   as [required by cloud-init](https://cloudinit.readthedocs.io/en/latest/topics/format.html#cloud-config-data).-  -  The `Binary` instance adds this **header line** and -  delegates the rendering of the yaml document to ++  The `Binary` instance adds this **header line** and+  delegates the rendering of the yaml document to   `YamlObject`.-  -* Remove the rendering of this header line in the `YamlObject` ++* Remove the rendering of this header line in the `YamlObject`   `Binary` instance. -* Rename `RenderYaml` to `RenderYamlObject` In order to prevent unexpected +* Rename `RenderYaml` to `RenderYamlObject` In order to prevent unexpected   runtime behaviour in code that uses this library.-  + * Introduce the type `Environment` that replaces the ubiquotus `[(String, String)]`   by a lazy `Text` based `HashMap`.-  -     * Add `appendPositionalArguments`-  ++     * Add `addLocalPositionalArguments`+ * Rename the previous `B9.Artifact.Content` to `B9.Artifact.Content`  * Introduce `ContentGenerator` as an open, extensible alternative@@ -46,21 +56,19 @@   `B9.Artifact.Content` * Rename-and-Split refactor `B9.ArtifactSource{Impl}` to `B9.Artifact.Generator.{..}` -* Move `CanRender` from `B9.Artifact.Content.AST` to `B9.Artifact.Content`    +* Move `CanRender` from `B9.Artifact.Content.AST` to `B9.Artifact.Content`  * Switch to lazy `Text`s and `ByteString`s where possible, since B9 might-  read/generate large files. -  -* Rename `CanRender` to `ToContentGenerator` -  and change the method signatur to return the new `ContentGenerator` type  +  read/generate large files. +* Rename `CanRender` to `ToContentGenerator`+  and change the method signatur to return the new `ContentGenerator` type+ * Fix spelling: Rename B9Invokation to B9Invocation-  + * Rename `FromAST` to `FromAST`  * Rearrange modules for content generation:-   - Introduce `Content.FromByteString` +   - Introduce `Content.FromByteString`  * Remove deprecated `Concatenation`-   - 
b9.cabal view
@@ -1,5 +1,5 @@ name:                b9-version:             0.5.63+version:             0.5.65  synopsis:            A tool and library for building virtual machine images. @@ -80,9 +80,13 @@                    , B9.Artifact.Readable                    , B9.Artifact.Readable.Interpreter                    , B9.Artifact.Readable.Source+                   , B9.BuildInfo                    , B9.B9Config                    , B9.B9Config.LibVirtLXC                    , B9.B9Config.Repository+                   , B9.B9Error+                   , B9.B9Logging+                   , B9.B9Exec                    , B9.B9Monad                    , B9.DiskImageBuilder                    , B9.DiskImages@@ -120,6 +124,7 @@                    , filepath >= 1.4                    , hashable >= 1.2                    , lens >= 4+                   , monad-control >= 1.0 && < 1.1                    , mtl >= 2.2                    , time >= 1.6                    , parallel >= 3.2@@ -175,6 +180,7 @@                    , base >= 4.8 && < 5                    , directory >= 1.3                    , bytestring >= 0.10+                   , extensible-effects >= 5 && < 6                    , optparse-applicative >= 0.13                    , lens >= 4                    , text >= 1.2@@ -211,6 +217,7 @@   build-depends:     base >= 4.8 && < 5                    , b9                    , binary >= 0.8 && < 0.9+                   , extensible-effects >= 5 && < 6                    , hspec                    , hspec-expectations                    , QuickCheck >= 2.5@@ -220,8 +227,6 @@                    , unordered-containers >= 0.2                    , bytestring >= 0.10                    , text >= 1.2-  if !impl(ghc >= 8.0)-    build-depends: semigroups >= 0.18   default-extensions: TupleSections                     , GeneralizedNewtypeDeriving                     , DeriveDataTypeable
src/cli/Main.hs view
@@ -3,7 +3,7 @@   ) where  import           B9-import           Control.Lens                    (over, (&), (.~), (^.))+import           Control.Lens                    ((&), (.~), (^.)) import qualified Data.Text.Lazy                  as LazyT import           Options.Applicative             hiding (action) import           Options.Applicative.Help.Pretty hiding ((</>))@@ -17,21 +17,20 @@ -- as well as build parameters. data B9RunParameters =   B9RunParameters B9ConfigOverride-                  Cmd+                  (B9ConfigAction ())                   Environment -type Cmd = B9ConfigAction IO ()- hostNetworkMagicValue :: String hostNetworkMagicValue = "host"  applyB9RunParameters :: B9RunParameters -> IO () applyB9RunParameters (B9RunParameters overrides act vars) =   let cfg =-        noB9ConfigOverride & customB9Config %~ (over envVars (`mappend` vars) . const (overrides ^. customB9Config)) &+        noB9ConfigOverride &         customLibVirtNetwork .~ (overrides ^. customLibVirtNetwork) &-        maybe id overrideB9ConfigPath (overrides ^. customB9ConfigPath)-   in execB9ConfigAction act cfg+        maybe id overrideB9ConfigPath (overrides ^. customB9ConfigPath) &+        customEnvironment .~ vars+   in runB9ConfigActionWithOverrides act cfg  parseCommandLine :: IO B9RunParameters parseCommandLine =@@ -40,7 +39,7 @@        (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'. The current working directory is used as ${buildDirRoot} if not otherwise specified in the config file, or via the '-b' option." <>+          "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'. The current working directory is used as ${projectRoot} if not otherwise specified in the config file, or via the '-b' option." <>         headerDoc (Just b9HelpHeader)))   where     b9HelpHeader = linebreak <> text ("B9 - a benign VM-Image build tool v. " ++ b9VersionString)@@ -55,11 +54,10 @@   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. If not specified '.'. The path will be canonicalized and stored in ${buildDirRoot}." <>+          "Root directory for build directories. If not specified '.'. The path will be canonicalized and stored in ${projectRoot}." <>         short 'b' <>         long "build-root-dir" <>         metavar "DIRECTORY")) <*>@@ -85,20 +83,19 @@       -> Bool       -> Maybe FilePath       -> Maybe FilePath-      -> Maybe FilePath       -> Bool       -> Bool       -> Maybe FilePath       -> Maybe String       -> Maybe String       -> B9ConfigOverride-    toGlobalOpts cfg verbose quiet logF profF buildRoot keep predictableBuildDir mRepoCache repo libvirtNet =+    toGlobalOpts cfg verbose quiet logF buildRoot keep predictableBuildDir mRepoCache repo libvirtNet =       let minLogLevel             | verbose = Just LogTrace             | quiet = Just LogError             | otherwise = Nothing           b9cfg =-            mempty & verbosity .~ minLogLevel & logFile .~ logF & profileFile .~ profF & buildDirRoot .~ buildRoot &+            mempty & verbosity .~ minLogLevel & logFile .~ logF & projectRoot .~ buildRoot &             keepTempDirs .~ keep &             uniqueBuildDirs .~ not predictableBuildDir &             repository .~ repo &@@ -112,9 +109,10 @@                      then Nothing                      else Just n) <$>                 libvirtNet+            , _customEnvironment = mempty             } -cmds :: Parser Cmd+cmds :: Parser (B9ConfigAction ()) cmds =   subparser     (command "version" (info (pure runShowVersion) (progDesc "Show program version and exit.")) <>@@ -164,7 +162,7 @@         noArgError (ErrorMsg "No build file specified!")))  buildVars :: Parser Environment-buildVars = flip insertPositionalArguments mempty . fmap LazyT.pack <$> many (strArgument idm)+buildVars = flip addPositionalArguments mempty . fmap LazyT.pack <$> many (strArgument idm)  remoteRepoParser :: Parser RemoteRepo remoteRepoParser =
src/lib/B9.hs view
@@ -10,63 +10,73 @@     used to describe a B9 build.  -}+module B9+  ( b9Version+  , b9VersionString+  , runShowVersion+  , runBuildArtifacts+  , runFormatBuildFiles+  , runPush+  , runPull+  , runRun+  , runGcLocalRepoCache+  , runGcRemoteRepoCache+  , runListSharedImages+  , runAddRepo+  , runLookupLocalSharedImage+  , module X+  ) where -module B9 ( b9Version, b9VersionString-          , runShowVersion-          , runBuildArtifacts-          , runFormatBuildFiles-          , runPush-          , runPull-          , runRun-          , runGcLocalRepoCache-          , runGcRemoteRepoCache-          , runListSharedImages-          , runAddRepo-          , runLookupLocalSharedImage-          , module X) where-import Control.Applicative as X-import Control.Exception(throwIO, catch)-import Control.Monad as X-import Control.Monad.IO.Class as X-import Control.Monad.Reader as X (ReaderT, local, ask)-import Control.Lens as X (Lens, (^.), (.~), (%~), (&))-import System.IO.Error (isDoesNotExistError)-import System.Directory (removeFile)-import Data.Function (on)-import Data.List as X-import Data.Maybe as X-import Data.Monoid as X-import Text.Show.Pretty as X (ppShow)-import System.Exit as X (exitWith, ExitCode(..))-import System.FilePath as X-        (takeDirectory, takeFileName, replaceExtension, (</>), (<.>))-import Text.Printf as X (printf)-import Data.Version as X-import System.IO.B9Extras as X--- import B9.Artifact as X-import B9.Artifact.Readable as X-import B9.Artifact.Content as X-import B9.Artifact.Content.AST as X-import B9.Artifact.Content.CloudConfigYaml as X-import B9.Artifact.Content.ErlTerms as X-import B9.Artifact.Content.ErlangPropList as X-import B9.Artifact.Content.Readable as X-import B9.Artifact.Content.StringTemplate as X-import B9.Artifact.Content.YamlObject as X-import B9.Artifact.Readable.Interpreter as X-import B9.B9Monad as X-import B9.B9Config as X-import B9.ExecEnv as X-import B9.DiskImages as X-import B9.DiskImageBuilder as X-import B9.ShellScript as X-import B9.Repository as X-import B9.RepositoryIO as X-import B9.Vm as X-import B9.VmBuilder as X-import B9.QCUtil as X-import B9.Environment as X-import Paths_b9 (version)+import           B9.Artifact.Content                 as X+import           B9.Artifact.Content.AST             as X+import           B9.Artifact.Content.CloudConfigYaml as X+import           B9.Artifact.Content.ErlangPropList  as X+import           B9.Artifact.Content.ErlTerms        as X+import           B9.Artifact.Content.Readable        as X+import           B9.Artifact.Content.StringTemplate  as X+import           B9.Artifact.Content.YamlObject      as X+import           B9.Artifact.Readable                as X+import           B9.Artifact.Readable.Interpreter    as X+import           B9.B9Config                         as X+import           B9.B9Error                          as X+import           B9.B9Exec                           as X+import           B9.B9Logging                        as X+import           B9.B9Monad                          as X+import           B9.BuildInfo                        as X+import           B9.DiskImageBuilder                 as X+import           B9.DiskImages                       as X+import           B9.Environment                      as X+import           B9.ExecEnv                          as X+import           B9.QCUtil                           as X+import           B9.Repository                       as X+import           B9.RepositoryIO                     as X+import           B9.ShellScript                      as X+import           B9.Vm                               as X+import           B9.VmBuilder                        as X+import           Control.Applicative                 as X+import           Control.Exception                   (catch, throwIO)+import           Control.Lens                        as X (Lens, (%~), (&),+                                                           (.~), (^.))+import           Control.Monad                       as X+import           Control.Monad.IO.Class              as X+import           Control.Monad.Reader                as X (ReaderT, ask, local)+import           Data.Function                       (on)+import           Data.List                           as X+import           Data.Maybe                          as X+import           Data.Monoid                         as X+import           Data.Version                        as X+import           Paths_b9                            (version)+import           System.Directory                    (removeFile)+import           System.Exit                         as X (ExitCode (..),+                                                           exitWith)+import           System.FilePath                     as X (replaceExtension,+                                                           takeDirectory,+                                                           takeFileName, (<.>),+                                                           (</>))+import           System.IO.B9Extras                  as X+import           System.IO.Error                     (isDoesNotExistError)+import           Text.Printf                         as X (printf)+import           Text.Show.Pretty                    as X (ppShow)  -- | Return the cabal package version of the B9 library. b9Version :: Version@@ -84,169 +94,150 @@ -- | Execute the artifact generators defined in a list of text files. -- Read the text files in the list and parse them as 'ArtifactGenerator's -- then 'mappend' them and apply 'buildArtifacts' to them.-runBuildArtifacts :: MonadIO m => [FilePath] -> B9ConfigAction m String+runBuildArtifacts :: [FilePath] -> B9ConfigAction String runBuildArtifacts buildFiles = do   generators <- mapM consult buildFiles-  run (buildArtifacts (mconcat generators))+  runB9 (buildArtifacts (mconcat generators))  -- | Read all text files and parse them as 'ArtifactGenerator's. -- Then overwrite the files with their contents but _pretty printed_ -- (i.e. formatted). runFormatBuildFiles :: MonadIO m => [FilePath] -> m ()-runFormatBuildFiles buildFiles = liftIO $ do-  generators <- mapM consult buildFiles-  let generatorsFormatted = map ppShow (generators :: [ArtifactGenerator])-  putStrLn `mapM` generatorsFormatted-  zipWithM_ writeFile buildFiles generatorsFormatted+runFormatBuildFiles buildFiles =+  liftIO $ do+    generators <- mapM consult buildFiles+    let generatorsFormatted = map ppShow (generators :: [ArtifactGenerator])+    putStrLn `mapM` generatorsFormatted+    zipWithM_ writeFile buildFiles generatorsFormatted  -- | Upload a 'SharedImageName' to the default remote repository. -- Note: The remote repository is specified in the 'B9Config'.-runPush :: MonadIO m => SharedImageName -> B9ConfigAction m ()-runPush name = localRuntimeConfig (keepTempDirs .~ False) $ run $ do-  conf <- getConfig-  if isNothing (conf ^. repository)-    then errorExitL-      "No repository specified! Use '-r' to specify a repo BEFORE 'push'."-    else pushSharedImageLatestVersion name+runPush :: SharedImageName -> B9ConfigAction ()+runPush name =+  localB9Config (keepTempDirs .~ False) $+  runB9 $ do+    conf <- getConfig+    if isNothing (conf ^. repository)+      then errorExitL "No repository specified! Use '-r' to specify a repo BEFORE 'push'."+      else pushSharedImageLatestVersion name  -- | Either pull a list of available 'SharedImageName's from the remote -- repository if 'Nothing' is passed as parameter, or pull the latest version -- of the image from the remote repository. Note: The remote repository is -- specified in the 'B9Config'.-runPull :: MonadIO m => Maybe SharedImageName -> B9ConfigAction m ()-runPull mName = localRuntimeConfig (keepTempDirs .~ False)-                                   (run (pullRemoteRepos >> maybePullImage))- where-  maybePullImage = mapM_-    ( \name ->-      pullLatestImage name >>= maybe (failPull name) (const (return ()))-    )-    mName-  failPull name = errorExitL (printf "failed to pull: %s" (show name))+runPull :: Maybe SharedImageName -> B9ConfigAction ()+runPull mName = localB9Config (keepTempDirs .~ False) (runB9 (pullRemoteRepos >> maybePullImage))+  where+    maybePullImage = mapM_ (\name -> pullLatestImage name >>= maybe (failPull name) (const (return ()))) mName+    failPull name = errorExitL (printf "failed to pull: %s" (show name))  -- | Execute an interactive root shell in a running container from a -- 'SharedImageName'.-runRun :: MonadIO m => SharedImageName -> [String] -> B9ConfigAction m String-runRun (SharedImageName name) cmdAndArgs = localRuntimeConfig-  ((keepTempDirs .~ False) . (interactive .~ True))-  (run (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+runRun :: SharedImageName -> [String] -> B9ConfigAction String+runRun (SharedImageName name) cmdAndArgs =+  localB9Config ((keepTempDirs .~ False) . (interactive .~ True)) (runB9 (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  -- | Delete all obsolete versions of all 'SharedImageName's.-runGcLocalRepoCache :: MonadIO m => B9ConfigAction m ()-runGcLocalRepoCache = localRuntimeConfig (keepTempDirs .~ False) (run impl)- where-  impl = 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 $ putStrLn "\n\nNO IMAGES TO DELETE\n"-      else liftIO $ do-        putStrLn "DELETING FILES:"-        putStrLn (unlines filesToDelete)-        mapM_ removeIfExists filesToDelete-   where-    obsoleteSharedmages :: [SharedImage] -> [SharedImage]-    obsoleteSharedmages =-      concatMap (tail . reverse) . filter ((> 1) . length) . groupBy-        ((==) `on` sharedImageName)-    removeIfExists :: FilePath -> IO ()-    removeIfExists fileName = removeFile fileName `catch` handleExists-     where-      handleExists e | isDoesNotExistError e = return ()-                     | otherwise             = throwIO e+runGcLocalRepoCache :: B9ConfigAction ()+runGcLocalRepoCache = localB9Config (keepTempDirs .~ False) (runB9 impl)+  where+    impl = 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 $ putStrLn "\n\nNO IMAGES TO DELETE\n"+        else liftIO $ do+               putStrLn "DELETING FILES:"+               putStrLn (unlines filesToDelete)+               mapM_ removeIfExists filesToDelete+      where+        obsoleteSharedmages :: [SharedImage] -> [SharedImage]+        obsoleteSharedmages = concatMap (tail . reverse) . filter ((> 1) . length) . groupBy ((==) `on` sharedImageName)+        removeIfExists :: FilePath -> IO ()+        removeIfExists fileName = removeFile fileName `catch` handleExists+          where+            handleExists e+              | isDoesNotExistError e = return ()+              | otherwise = throwIO e  -- | Clear the shared image cache for a remote. Note: The remote repository is -- specified in the 'B9Config'.-runGcRemoteRepoCache :: MonadIO m => B9ConfigAction m ()-runGcRemoteRepoCache = localRuntimeConfig-  (keepTempDirs .~ False)-  ( run-    ( do-      repos <- getSelectedRepos-      cache <- getRepoCache-      mapM_ (cleanRemoteRepo cache) repos-    )-  )+runGcRemoteRepoCache :: B9ConfigAction ()+runGcRemoteRepoCache =+  localB9Config+    (keepTempDirs .~ False)+    (runB9+       (do repos <- getSelectedRepos+           cache <- getRepoCache+           mapM_ (cleanRemoteRepo cache) repos))  -- | Print a list of shared images cached locally or remotely, if a remote -- repository was selected. Note: The remote repository is -- specified in the 'B9Config'.-runListSharedImages :: MonadIO m => B9ConfigAction m [SharedImage]-runListSharedImages = localRuntimeConfig-  (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.\n"-          putStrLn "To view the contents of a remote repo add"-          putStrLn "the '-r' switch with one of the remote"-          putStrLn "repository ids."-        else liftIO $ putStrLn-          (  "Showing shared images on: "-          ++ remoteRepoRepoId (fromJust remoteRepo)-          )-      unless (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 (map snd imgs)-    )-  )+runListSharedImages :: B9ConfigAction [SharedImage]+runListSharedImages =+  localB9Config+    (keepTempDirs .~ False)+    (runB9+       (do MkSelectedRemoteRepo remoteRepo <- getSelectedRemoteRepo+           let repoPred = maybe (== Cache) ((==) . toRemoteRepository) remoteRepo+           allRepos <- getRemoteRepos+           if isNothing remoteRepo+             then liftIO $ do+                    putStrLn "Showing local shared images only.\n"+                    putStrLn "To view the contents of a remote repo add"+                    putStrLn "the '-r' switch with one of the remote"+                    putStrLn "repository ids."+             else liftIO $ putStrLn ("Showing shared images on: " ++ remoteRepoRepoId (fromJust remoteRepo))+           unless (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 (map snd imgs)))  -- | Check the SSH settings for a remote repository and add it to the user wide -- B9 configuration file.-runAddRepo :: MonadIO m => RemoteRepo -> B9ConfigAction m ()+runAddRepo :: RemoteRepo -> B9ConfigAction () runAddRepo repo = do   repo' <- remoteRepoCheckSshPrivKey repo   modifyPermanentConfig-    ( Endo-      (  remoteRepos-      %~ ( mappend [repo']-         . filter ((== remoteRepoRepoId repo') . remoteRepoRepoId)-         )-      )-    )+    (Endo (remoteRepos %~ (mappend [repo'] . filter ((== remoteRepoRepoId repo') . remoteRepoRepoId))))  -- | Find the most recent version of a 'SharedImageName' in the local image cache.-runLookupLocalSharedImage-  :: MonadIO m => SharedImageName -> B9ConfigAction m (Maybe SharedImageBuildId)-runLookupLocalSharedImage n = run $ do-  traceL (printf "Searching for cached image: %s" (show n))-  imgs <- lookupSharedImages isAvailableOnLocalHost hasTheDesiredName-  traceL "Candidate images: "-  traceL (printf "%s\n" (prettyPrintSharedImages (map snd imgs)))-  let res = extractNewestImageFromResults imgs-  traceL (printf "Returning result: %s" (show res))-  return res- where-  extractNewestImageFromResults =-    listToMaybe . map toBuildId . take 1 . reverse . map snd-    where toBuildId (SharedImage _ _ i _ _) = i-  isAvailableOnLocalHost = (Cache ==)-  hasTheDesiredName (SharedImage n' _ _ _ _) = n == n'+runLookupLocalSharedImage :: SharedImageName -> B9ConfigAction (Maybe SharedImageBuildId)+runLookupLocalSharedImage n =+  runB9 $ do+    traceL (printf "Searching for cached image: %s" (show n))+    imgs <- lookupSharedImages isAvailableOnLocalHost hasTheDesiredName+    traceL "Candidate images: "+    traceL (printf "%s\n" (prettyPrintSharedImages (map snd imgs)))+    let res = extractNewestImageFromResults imgs+    traceL (printf "Returning result: %s" (show res))+    return res+  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/Artifact/Content.hs view
@@ -5,56 +5,22 @@ -- -- @since 0.5.62 module B9.Artifact.Content-  ( ContentGeneratorT(MkContentGeneratorT)-  , ContentGenerator-  , ByteStringGenerator-  , renderContentGenerator-  , ToContentGenerator (..)-  ) where--import B9.B9Monad (B9)-import B9.Environment-import Control.Applicative (Alternative)-import Control.Monad.Reader-import Data.ByteString.Lazy as Lazy---- | A monadic action that generates content by using the 'Environment'--- as additional input, e.g. when interpolating string templates.------ Most intersting is the fact the 'Semigroup' and 'Monoid' instances--- are available.------ @since 0.5.62-newtype ContentGeneratorT m a = MkContentGeneratorT-  { getContentGenerator :: EnvironmentReaderT m a-  } deriving (MonadTrans, Alternative, MonadReader Environment, MonadIO, Monad, Applicative, Functor)+  ( ByteStringGenerator+  , ToContentGenerator(..)+  )+where --- | A 'B9' action that generates content by using the 'Environment'--- as additional input, e.g. when interpolating string templates.------ @since 0.5.62-type ContentGenerator a = ContentGeneratorT B9 a+import           B9.B9Monad+import           Control.Eff+import           Data.ByteString.Lazy          as Lazy  -- | A 'B9' action that procuces a 'Lazy.ByteString'. -- -- @since 0.5.62-type ByteStringGenerator = ContentGenerator Lazy.ByteString--instance (Monad m, Semigroup a) => Semigroup (ContentGeneratorT m a) where-  l <> r = (<>) <$> l <*> r--instance (Monad m, Monoid a) => Monoid (ContentGeneratorT m a) where-  mempty = pure mempty+type ByteStringGenerator = B9 Lazy.ByteString  -- | Types whose values can be turned into a 'ContentGenerator' -- -- @since 0.5.62-class ToContentGenerator c where-    toContentGenerator :: c -> ByteStringGenerator---- | Execute a 'ContentGenerator'.--- The 'Environment' contains the bindings of any string template variables.------ @since 0.5.62-renderContentGenerator :: Environment -> ContentGenerator a -> B9 a-renderContentGenerator e = runEnvironmentReaderT e . getContentGenerator+class ToContentGenerator c a where+    toContentGenerator :: IsB9 e => c -> Eff e a
src/lib/B9/Artifact/Content/AST.hs view
@@ -20,20 +20,23 @@ Another example is the OTP/Erlang sys.config for configuring OTP/Erlang releases. -} -module B9.Artifact.Content.AST ( FromAST(..)-                      , AST(..)-                      , decodeOrFail'-                      ) where+module B9.Artifact.Content.AST+  ( FromAST(..)+  , AST(..)+  , decodeOrFail'+  )+where +import           Control.Eff import           Control.Parallel.Strategies-import           Data.Binary (Binary)-import qualified Data.Binary as Binary-import qualified Data.ByteString.Lazy.Char8 as Lazy+import           Data.Binary                    ( Binary )+import qualified Data.Binary                   as Binary+import qualified Data.ByteString.Lazy.Char8    as Lazy import           Data.Data import           Data.Hashable-import           GHC.Generics (Generic)+import           GHC.Generics                   ( Generic ) import           Test.QuickCheck-+import           B9.B9Monad import           B9.Artifact.Content.StringTemplate import           B9.Artifact.Content import           B9.QCUtil@@ -42,14 +45,13 @@ -- This is like 'Binary.decodeOrFail' exception that it allows to add and extra -- error message decodeOrFail'-    :: Binary a-    => String -- ^ An arbitrary string for error messages-    -> Lazy.ByteString-    -> Either String a-decodeOrFail' errorMessage b =-  case Binary.decodeOrFail b of-    Left (_,_,e) -> Left (unwords [errorMessage, e])-    Right (_,_,a) -> Right a+  :: Binary a+  => String -- ^ An arbitrary string for error messages+  -> Lazy.ByteString+  -> Either String a+decodeOrFail' errorMessage b = case Binary.decodeOrFail b of+  Left  (_, _, e) -> Left (unwords [errorMessage, e])+  Right (_, _, a) -> Right a  -- | Describe how to create structured content that has a tree-like syntactic -- structure, e.g. yaml, JSON and erlang-proplists. The first parameter defines@@ -77,14 +79,14 @@     deriving (Read,Show,Typeable,Data,Eq,Generic)  instance Functor (AST c) where-  fmap f (AST a)          = AST (f a)-  fmap f (ASTObj x)       = ASTObj ((fmap . fmap . fmap) f x)-  fmap f (ASTArr x)       = ASTArr ((fmap . fmap) f x)-  fmap f (ASTMerge x)     = ASTMerge ((fmap . fmap) f x)-  fmap _ (ASTEmbed x)     = ASTEmbed x-  fmap _ (ASTString x)    = ASTString x-  fmap _ (ASTInt x)       = ASTInt x-  fmap _ (ASTParse x)     = ASTParse x+  fmap f (AST       a) = AST (f a)+  fmap f (ASTObj    x) = ASTObj ((fmap . fmap . fmap) f x)+  fmap f (ASTArr    x) = ASTArr ((fmap . fmap) f x)+  fmap f (ASTMerge  x) = ASTMerge ((fmap . fmap) f x)+  fmap _ (ASTEmbed  x) = ASTEmbed x+  fmap _ (ASTString x) = ASTString x+  fmap _ (ASTInt    x) = ASTInt x+  fmap _ (ASTParse  x) = ASTParse x  instance (Hashable c, Hashable a) => Hashable (AST c a) instance (Binary c, Binary a) => Binary (AST c a)@@ -93,19 +95,16 @@ -- | Types of values that describe content, that can be created from an 'AST'. class FromAST a  where     fromAST-        :: (ToContentGenerator c)-        => AST c a -> ContentGenerator a+        :: (IsB9 e, ToContentGenerator c Lazy.ByteString)+        => AST c a -> Eff e a  instance (Arbitrary c, Arbitrary a) => Arbitrary (AST c a) where-    arbitrary =-        oneof-            [ ASTObj <$> smaller (listOf ((,) <$> arbitrary <*> arbitrary))-            , ASTArr <$> smaller (listOf arbitrary)-            , ASTMerge <$>-              sized-                  (\s ->-                        resize (max 2 s) (listOf (halfSize arbitrary)))-            , ASTEmbed <$> smaller arbitrary-            , ASTString <$> arbitrary-            , ASTParse <$> smaller arbitrary-            , AST <$> smaller arbitrary]+  arbitrary = oneof+    [ ASTObj <$> smaller (listOf ((,) <$> arbitrary <*> arbitrary))+    , ASTArr <$> smaller (listOf arbitrary)+    , ASTMerge <$> sized (\s -> resize (max 2 s) (listOf (halfSize arbitrary)))+    , ASTEmbed <$> smaller arbitrary+    , ASTString <$> arbitrary+    , ASTParse <$> smaller arbitrary+    , AST <$> smaller arbitrary+    ]
src/lib/B9/Artifact/Content/Readable.hs view
@@ -3,11 +3,18 @@ -} module B9.Artifact.Content.Readable where -import Control.Monad.Trans (lift) import Control.Parallel.Strategies #if !MIN_VERSION_base(4,8,0) import Control.Applicative #endif+import B9.Artifact.Content+import B9.Artifact.Content.AST+import B9.Artifact.Content.CloudConfigYaml+import B9.Artifact.Content.ErlangPropList+import B9.Artifact.Content.StringTemplate+import B9.Artifact.Content.YamlObject+import B9.B9Logging+import B9.QCUtil import Control.Monad.IO.Class import Data.Binary as Binary import qualified Data.ByteString as Strict@@ -19,15 +26,6 @@ import System.Process import Test.QuickCheck -import B9.B9Monad-import B9.Artifact.Content-import B9.Artifact.Content.AST-import B9.Artifact.Content.CloudConfigYaml-import B9.Artifact.Content.ErlangPropList-import B9.Artifact.Content.StringTemplate-import B9.Artifact.Content.YamlObject-import B9.QCUtil- -- | This is content that can be 'read' via the generated 'Read' instance. data Content   = RenderErlang (AST Content ErlangPropList)@@ -67,7 +65,7 @@       , FromURL <$> smaller arbitrary       ] -instance ToContentGenerator Content where+instance ToContentGenerator Content Lazy.ByteString where   toContentGenerator (RenderErlang ast) = Binary.encode <$> fromAST ast   toContentGenerator (RenderYamlObject ast) = Binary.encode <$> fromAST ast   toContentGenerator (RenderCloudConfig ast) = Binary.encode <$> fromAST ast@@ -79,15 +77,14 @@   toContentGenerator (RenderBase64Binary b) = pure (Lazy.fromStrict $ B64.encode $ Lazy.toStrict b)   toContentGenerator (FromString str) = pure (Lazy.pack str)   toContentGenerator (FromByteString str) = pure str-  toContentGenerator (FromURL url) =-    lift $ do-      dbgL $ "Downloading: " ++ url-      (exitCode, out, err) <- liftIO $ readProcessWithExitCode "curl" [url] ""-      if exitCode == ExitSuccess-        then do-          dbgL $ "Download finished. Bytes read: " ++ show (length out)-          traceL $ "Downloaded (truncated to first 4K): \n\n" ++ take 4096 out ++ "\n\n"-          pure $ Lazy.pack out-        else do-          errorL $ "Download failed: " ++ err-          liftIO $ exitWith exitCode+  toContentGenerator (FromURL url) = do+    dbgL ("Downloading: " ++ url)+    (exitCode, out, err) <- liftIO (readProcessWithExitCode "curl" [url] "")+    if exitCode == ExitSuccess+      then do+        dbgL ("Download finished. Bytes read: " ++ show (length out))+        traceL ("Downloaded (truncated to first 4K): \n\n" ++ take 4096 out ++ "\n\n")+        pure (Lazy.pack out)+      else do+        errorL ("Download failed: " ++ err)+        liftIO (exitWith exitCode)
src/lib/B9/Artifact/Content/StringTemplate.hs view
@@ -1,42 +1,47 @@ {-# LANGUAGE FlexibleContexts #-}  {-| Utility functions based on 'Data.Text.Template' to offer @ $var @ variable-    expansion in string throughout a B9 artifact. -}+    expansion in string throughout a B9 artifact.++    @deprecated++    TODO remove this in the move to Dhall+    -} module B9.Artifact.Content.StringTemplate   ( subst-  , substE-  , substEB   , substFile   , substPath   , readTemplateFile+  , withSubstitutedStringBindings   , SourceFile(..)   , SourceFileConversion(..)-  ) where-#if !MIN_VERSION_base(4,8,0)-import Control.Applicative-#endif-import B9.Environment-import B9.QCUtil-import Control.Exception (SomeException)-import Control.Monad.IO.Class (MonadIO(liftIO))-import Control.Monad.Reader (runReaderT)-import Control.Monad.Trans.Identity ()-import Control.Parallel.Strategies-import Data.Bifunctor-import Data.Binary-import qualified Data.ByteString as Strict-import qualified Data.ByteString.Lazy as Lazy-import Data.Data-import Data.Hashable-import qualified Data.Text.Lazy as LazyT-import qualified Data.Text      as StrictT-import qualified Data.Text.Encoding       as StrictE-import qualified Data.Text.Lazy.Encoding  as LazyE-import Data.Text.Template (render, renderA, templateSafe)-import GHC.Generics (Generic)-import System.IO.B9Extras-import Test.QuickCheck-import Text.Printf+  )+where+import           B9.B9Error+import           B9.Environment+import           B9.QCUtil+import           Control.Exception              ( displayException )+import           Control.Monad                  ( foldM )+import           Control.Monad.IO.Class         ( MonadIO(liftIO) )+import           Control.Eff                   as Eff+import           Control.Monad.Trans.Identity   ( )+import           Control.Parallel.Strategies+import           Data.Binary+import qualified Data.ByteString               as Strict+import qualified Data.ByteString.Lazy          as Lazy+import           Data.Data+import           Data.Hashable+import qualified Data.Text.Lazy                as LazyT+import qualified Data.Text                     as StrictT+import qualified Data.Text.Encoding            as StrictE+import qualified Data.Text.Lazy.Encoding       as LazyE+import           Data.Text.Template             ( renderA+                                                , templateSafe+                                                )+import           GHC.Generics                   ( Generic )+import           System.IO.B9Extras+import           Test.QuickCheck+import           Text.Printf  -- | A wrapper around a file path and a flag indicating if template variable -- expansion should be performed when reading the file contents.@@ -62,86 +67,131 @@  instance NFData SourceFileConversion -readTemplateFile :: (MonadIO m, MonadEnvironment m) => SourceFile -> m Lazy.ByteString+readTemplateFile+  :: (MonadIO (Eff e), '[ExcB9, EnvironmentReader] <:: e)+  => SourceFile+  -> Eff e Lazy.ByteString readTemplateFile (Source conv f') = do-  env <- askEnvironment-  case substE env f' of-    Left e ->-      error-        (printf-           "Failed to substitute templates in source \-                    \file name '%s'/\nError: %s\n"-           f'-           e)-    Right f -> do-      c <- liftIO (Lazy.readFile f)-      convert f c-  where-    convert f c =-      case conv of-        NoConversion -> return c-        ExpandVariables -> do-          env <- askEnvironment-          case substEB env c of-            Left e -> error (printf "readTemplateFile '%s' failed: \n%s\n" f e)-            Right c' -> return c'---- String template substitution via dollar-subst :: Environment -> String -> String-subst env templateStr =-  case substE env templateStr of-    Left e -> error e-    Right r -> r+  let+    onErrorFileName e = error+      (printf+        "Failed to substitute templates in source \+                                    \file name '%s'/\nError: %s\n"+        f'+        (displayException e)+      )+  f <- subst f' `catchB9Error` onErrorFileName+  c <- liftIO (Lazy.readFile f)+  case conv of+    NoConversion -> return c+    ExpandVariables ->+      let onErrorFile e =+              error+                (printf "readTemplateFile '%s' failed: \n%s\n"+                        f+                        (displayException e)+                )+      in  substEB c `catchB9Error` onErrorFile  -- String template substitution via dollar-substE :: Environment -> String -> Either String String-substE env templateStr = second (LazyT.unpack . LazyE.decodeUtf8) (substEB env (LazyE.encodeUtf8 (LazyT.pack templateStr)))+subst :: (Member ExcB9 e, Member EnvironmentReader e) => String -> Eff e String+subst templateStr = LazyT.unpack . LazyE.decodeUtf8 <$> substEB+  (LazyE.encodeUtf8 (LazyT.pack templateStr))  -- String template substitution via dollar-substEB :: Environment -> Lazy.ByteString -> Either String Lazy.ByteString-substEB env templateStr = do+substEB+  :: (Member ExcB9 e, Member EnvironmentReader e)+  => Lazy.ByteString+  -> Eff e Lazy.ByteString+substEB templateStr = do   t <- template'-  res <- renderA t env'-  return (LazyE.encodeUtf8 res)-  where-    env' t =-      case lookupEither (LazyT.fromStrict t) env of-        Right v -> Right (LazyT.toStrict v)-        Left e -> Left (show e ++ "\nIn template: \"" ++ show templateStr ++ "\"\n")-    template' =-      case templateSafe (LazyT.toStrict (LazyE.decodeUtf8 templateStr)) of-        Left (row, col) ->-          Left-            ("Invalid template, error at row: " ++ show row ++ ", col: " ++ show col ++ " in: \"" ++ show templateStr)-        Right t -> Right t+  LazyE.encodeUtf8 <$> renderA t templateEnvLookup+ where+  templateEnvLookup+    :: (Member EnvironmentReader e, Member ExcB9 e)+    => StrictT.Text+    -> Eff e StrictT.Text+  templateEnvLookup x = LazyT.toStrict <$> lookupOrThrow (LazyT.fromStrict x) -substFile :: MonadIO m => Environment -> FilePath -> FilePath -> m ()-substFile env src dest = do+  template' =+    case templateSafe (LazyT.toStrict (LazyE.decodeUtf8 templateStr)) of+      Left (row, col) -> throwB9Error+        (  "Invalid template, error at row: "+        ++ show row+        ++ ", col: "+        ++ show col+        ++ " in: \""+        ++ show templateStr+        )+      Right t -> return t++substFile+  :: (Member EnvironmentReader e, Member ExcB9 e, MonadIO (Eff e))+  => FilePath+  -> FilePath+  -> Eff e ()+substFile src dest = do   templateBs <- liftIO (Strict.readFile src)   let t = templateSafe (StrictE.decodeUtf8 templateBs)   case t of     Left (r, c) ->-      let badLine = unlines (take r (lines (StrictT.unpack (StrictE.decodeUtf8 templateBs))))+      let badLine =+              unlines+                (take r (lines (StrictT.unpack (StrictE.decodeUtf8 templateBs))))           colMarker = replicate (c - 1) '-' ++ "^"-       in error (printf "Template error in file '%s' line %i:\n\n%s\n%s\n" src r badLine colMarker)+      in  throwB9Error+            (printf "Template error in file '%s' line %i:\n\n%s\n%s\n"+                    src+                    r+                    badLine+                    colMarker+            )     Right template' -> do-      let out = LazyE.encodeUtf8 (render template' envLookup)+      out <- LazyE.encodeUtf8+        <$> renderA template' (templateEnvLookupSrcFile src)       liftIO (Lazy.writeFile dest out)-      return ()-  where-    envLookup :: StrictT.Text -> StrictT.Text-    envLookup x = either err LazyT.toStrict (runReaderT (lookupOrThrow (LazyT.fromStrict x)) env)-      where-        err :: SomeException -> a-        err e = error (show e ++ "\nIn file: \'" ++ src ++ "\'\n") -substPath :: Environment -> SystemPath -> SystemPath-substPath env src =-  case src of-    Path p -> Path (subst env p)-    InHomeDir p -> InHomeDir (subst env p)-    InB9UserDir p -> InB9UserDir (subst env p)-    InTempDir p -> InTempDir (subst env p)+templateEnvLookupSrcFile+  :: (Member EnvironmentReader e, Member ExcB9 e, MonadIO (Eff e))+  => FilePath+  -> StrictT.Text+  -> Eff e StrictT.Text+templateEnvLookupSrcFile src x = do+  r <- catchB9ErrorAsEither (lookupOrThrow (LazyT.fromStrict x))+  either err (pure . LazyT.toStrict) r+  where err e = throwB9Error (show e ++ "\nIn file: \'" ++ src ++ "\'\n") ++substPath+  :: (Member EnvironmentReader e, Member ExcB9 e)+  => SystemPath+  -> Eff e SystemPath+substPath src = case src of+  Path        p -> Path <$> subst p+  InHomeDir   p -> InHomeDir <$> subst p+  InB9UserDir p -> InB9UserDir <$> subst p+  InTempDir   p -> InTempDir <$> subst p+ instance Arbitrary SourceFile where-  arbitrary = Source <$> elements [NoConversion, ExpandVariables] <*> smaller arbitraryFilePath+  arbitrary =+    Source+      <$> elements [NoConversion, ExpandVariables]+      <*> smaller arbitraryFilePath+++-- | Extend an 'Environment' with new bindings, where each value may contain+-- string templates with like @"Hello $name, how is life on $planet these days?"@.+--+-- @since 0.5.64+withSubstitutedStringBindings+  :: (Member EnvironmentReader e, Member ExcB9 e)+  => [(String, String)]+  -> Eff e s+  -> Eff e s+withSubstitutedStringBindings bs nested = do+  let extend env (k, v) = localEnvironment (const env) $ do+        kv <- (k, ) <$> subst v+        addStringBinding kv env+  env    <- askEnvironment+  envExt <- foldM extend env bs+  localEnvironment (const envExt) nested
src/lib/B9/Artifact/Readable.hs view
@@ -66,6 +66,7 @@ These variables can be used as value in nested 'Let's, in most file names/paths and in source files added with 'B9.Artifact.Content.StringTemplate.SourceFile' +-- @deprecated TODO remove this when switching to Dhall -} data ArtifactGenerator   = Sources [ArtifactSource]@@ -76,6 +77,7 @@         [ArtifactGenerator]       -- ^ Bind variables, variables are available in nested       -- generators.+      -- @deprecated TODO remove this when switching to Dhall   | LetX [(String, [String])]          [ArtifactGenerator]       -- ^ A 'Let' where each variable is assigned to each@@ -97,6 +99,7 @@       --       Let [("x", "3"), ("y", "b")] [..]       --     ]       -- @+      -- @deprecated TODO remove this when switching to Dhall   | Each [(String, [String])]          [ArtifactGenerator]       -- ^ Bind each variable to their first value, then each@@ -105,6 +108,7 @@       -- product of all variables, whereas 'Each' represents a       -- sum of variable bindings - 'Each' is more like a /zip/       -- whereas 'LetX' is more like a list comprehension.+      -- @deprecated TODO remove this when switching to Dhall   | EachT [String]           [[String]]           [ArtifactGenerator]@@ -112,6 +116,7 @@       -- in the first list to each a set of values from the       -- second argument; execute the nested generators for       -- each binding+      -- @deprecated TODO remove this when switching to Dhall   | Artifact InstanceId              ArtifactAssembly       -- ^ Generate an artifact defined by an@@ -181,7 +186,7 @@  instance NFData ArtifactAssembly --- | A type representing the targets assembled by+-- | A symbolic representation of the targets assembled by -- 'B9.Artifact.Readable.Interpreter.assemble' from an 'ArtifactAssembly'. There is a -- list of 'ArtifactTarget's because e.g. a single 'CloudInit' can produce up to -- three output files, a directory, an ISO image and a VFAT image.
src/lib/B9/Artifact/Readable/Interpreter.hs view
@@ -1,39 +1,51 @@ {-| Mostly effectful functions to assemble artifacts. -}-module B9.Artifact.Readable.Interpreter where--import           Control.Arrow-import           Control.Exception         (Exception, SomeException)-import           Control.Lens              (view)-import           Control.Monad.Catch-import           Control.Monad.Except-import           Control.Monad.Reader-import           Control.Monad.Writer-import qualified Data.ByteString.Lazy      as Lazy-import           Data.Data-import           Data.Generics.Aliases-import           Data.Generics.Schemes-import           Data.List-import           Data.Maybe-import qualified Data.Text.Lazy            as LazyT-import qualified Data.Text.Lazy.Encoding   as LazyE-import           System.Directory-import           System.FilePath-import           System.IO.B9Extras        (ensureDir, getDirectoryFiles)-import           Text.Printf-import           Text.Show.Pretty          (ppShow)+module B9.Artifact.Readable.Interpreter+  ( buildArtifacts+  , assemble+  , getArtifactOutputFiles+  , runArtifactGenerator+  , runArtifactAssembly+  , InstanceGenerator(..)+  , runInstanceGenerator+  , InstanceSources(..)+  ) where -import           B9.Artifact.Readable-import           B9.B9Config-import           B9.B9Monad-import           B9.Artifact.Content-import           B9.Artifact.Content.Readable-import           B9.Artifact.Content.StringTemplate-import           B9.DiskImageBuilder-import           B9.Environment-import           B9.Vm-import           B9.VmBuilder+import B9.Artifact.Content+import B9.Artifact.Content.Readable+import B9.Artifact.Content.StringTemplate+import B9.Artifact.Readable+import B9.B9Config+import B9.B9Error+import B9.B9Exec+import B9.B9Logging+import B9.B9Monad+import B9.BuildInfo+import B9.DiskImageBuilder+import B9.Environment+import B9.Vm+import B9.VmBuilder+import Control.Arrow+import Control.Eff as Eff+import Control.Eff.Reader.Lazy as Eff+import Control.Eff.Writer.Lazy as Eff+import Control.Exception (SomeException, displayException)+import Control.Monad+import Control.Monad.IO.Class+import qualified Data.ByteString.Lazy as Lazy+import Data.Data+import Data.Generics.Aliases+import Data.Generics.Schemes+import Data.List+import Data.String+import qualified Data.Text.Lazy as LazyT+import qualified Data.Text.Lazy.Encoding as LazyE+import System.Directory+import System.FilePath+import System.IO.B9Extras (ensureDir, getDirectoryFiles)+import Text.Printf+import Text.Show.Pretty (ppShow)  -- | Execute an 'ArtifactGenerator' and return a 'B9Invocation' that returns -- the build id obtained by 'getBuildId'.@@ -47,120 +59,148 @@  -- | Return a list of relative paths for the /local/ files to be generated -- by the ArtifactGenerator. This excludes 'Shared' and Transient image targets.-getArtifactOutputFiles :: ArtifactGenerator -> Either String [FilePath]-getArtifactOutputFiles g = concatMap getOutputs <$> evalArtifactGenerator undefined undefined mempty g+getArtifactOutputFiles :: ArtifactGenerator -> Either SomeException [FilePath]+getArtifactOutputFiles g = concatMap getOutputs <$> runArtifactGenerator mempty "no build-id" "no build-date" g   where     getOutputs (IG _ sgs a) =       let toOutFile (AssemblyGeneratesOutputFiles fs) = fs           toOutFile (AssemblyCopiesSourcesToDirectory pd) =-            let sourceFiles = sourceGeneratorOutputFile <$> sgs+            let sourceFiles = textFileWriterOutputFile <$> sgs              in (pd </>) <$> sourceFiles        in getAssemblyOutput a >>= toOutFile  -- | Run an artifact generator to produce the artifacts. assemble :: ArtifactGenerator -> B9 [AssembledArtifact] assemble artGen = do-  b9cfgEnvVars <- view envVars <$> getConfig+  b9cfgEnvVars <- askEnvironment   buildId <- getBuildId   buildDate <- getBuildDate-  case evalArtifactGenerator buildId buildDate b9cfgEnvVars artGen of-    Left err -> error err-    Right is -> createAssembledArtifacts is+  runArtifactSourcesReader (InstanceSources b9cfgEnvVars mempty) $ do+    is <- evalArtifactGenerator buildId buildDate artGen+    createAssembledArtifacts is +-- | Interpret an 'ArtifactGenerator' into a list of simple commands, i.e. 'InstanceGenerator's+--+-- @since 0.5.65+runArtifactGenerator ::+     Environment -> String -> String -> ArtifactGenerator -> Either SomeException [InstanceGenerator [TextFileWriter]]+runArtifactGenerator initialEnvironment buildId buildData generator =+  Eff.run+    (runExcB9+       (runEnvironmentReader+          initialEnvironment+          (runArtifactSourcesReader+             (InstanceSources initialEnvironment mempty)+             (evalArtifactGenerator buildId buildData generator))))+ -- | Evaluate an 'ArtifactGenerator' into a list of low-level build instructions -- that can be built with 'createAssembledArtifacts'. evalArtifactGenerator ::-     String -> String -> Environment -> ArtifactGenerator -> Either String [InstanceGenerator [SourceGenerator]]-evalArtifactGenerator buildId buildDate b9cfgEnvVars artGen =-  let ag = parseArtifactGenerator artGen-      ee = addBindings [(buildDateKey, buildDate), (buildIdKey, buildId)] $ CGEnv b9cfgEnvVars []-   in case ee of-        Left err -> Left (printf "error parsing: %s: %s" (ppShow artGen) (show err))-        Right e ->-          case execCGParser ag e of-            Left err -> Left (printf "error parsing: %s: %s" (ppShow artGen) (show err))-            Right igs ->-              case execIGEnv `mapM` igs of-                Left err -> Left (printf "Failed to parse:\n%s\nError: %s" (ppShow artGen) err)-                Right is -> Right is---- | Parse an artifacto generator inside a 'CGParser' monad.-parseArtifactGenerator :: ArtifactGenerator -> CGParser ()-parseArtifactGenerator g =-  case g of-    Sources srcs gs -> withArtifactSources srcs (mapM_ parseArtifactGenerator gs)-    Let bs gs -> withBindings bs (mapM_ parseArtifactGenerator gs)-    LetX bs gs -> withXBindings bs (mapM_ parseArtifactGenerator gs)-    EachT keySet valueSets gs -> do-      allBindings <- eachBindingSetT g keySet valueSets-      sequence_ (flip withBindings (mapM_ parseArtifactGenerator gs) <$> allBindings)-    Each kvs gs -> do-      allBindings <- eachBindingSet g kvs-      sequence_ $ do-        b <- allBindings-        return (withBindings b (mapM_ parseArtifactGenerator gs))-    Artifact iid assembly -> writeInstanceGenerator iid assembly-    EmptyArtifact -> return ()---- | Execute a 'CGParser' action in an environment that contains a list of--- 'ArtifactSource's.-withArtifactSources :: [ArtifactSource] -> CGParser () -> CGParser ()-withArtifactSources srcs = local (\ce -> ce {agSources = agSources ce ++ srcs})+     (Member ExcB9 e, Member EnvironmentReader e)+  => String+  -> String+  -> ArtifactGenerator+  -> Eff (ArtifactSourcesReader ': e) [InstanceGenerator [TextFileWriter]]+evalArtifactGenerator buildId buildDate artGen =+  withSubstitutedStringBindings+    [(buildDateKey, buildDate), (buildIdKey, buildId)]+    (runArtifactInterpreter (interpretGenerator artGen)) `catchB9Error`+  (throwB9Error . printf "Failed to eval:\n%s\nError: %s" (ppShow artGen) . displayException) -withBindings :: [(String, String)] -> CGParser () -> CGParser ()-withBindings bs = local (fromJust . addBindings bs)+type ArtifactSourcesReader = Reader [ArtifactSource] -addBindings :: MonadThrow m => [(String, String)] -> CGEnv -> m CGEnv-addBindings bs ce = do-  let addBinding env (k, v) = addStringBinding (k, subst env v) env-  newEnv <- foldM addBinding (agEnv ce) bs-  return ce {agEnv = newEnv}+runArtifactSourcesReader ::+     Member EnvironmentReader e => InstanceSources -> Eff (ArtifactSourcesReader ': e) a -> Eff e a+runArtifactSourcesReader x y = runReader (isSources x) (localEnvironment (const (isEnv x)) y) -withXBindings :: [(String, [String])] -> CGParser () -> CGParser ()-withXBindings bs cp = (`local` cp) `mapM_` (((.) . (.)) fromJust addBindings <$> allXBindings bs)-  where-    allXBindings ((k, vs):rest) = [(k, v) : c | v <- vs, c <- allXBindings rest]-    allXBindings [] = [[]]+-- | Monad for creating Instance generators.+type ArtifactInterpreter e = Writer [InstanceGenerator InstanceSources] : ArtifactSourcesReader : e -eachBindingSetT :: ArtifactGenerator -> [String] -> [[String]] -> CGParser [[(String, String)]]-eachBindingSetT g vars valueSets =-  if all ((== length vars) . length) valueSets-    then return (zip vars <$> valueSets)-    else cgError-           (printf-              "Error in 'Each' binding during artifact generation in:\n '%s'.\n\nThe variable list\n%s\n has %i entries, but this binding set\n%s\n\nhas a different number of entries!\n"-              (ppShow g)-              (ppShow vars)-              (length vars)-              (ppShow (head (dropWhile ((== length vars) . length) valueSets))))+runArtifactInterpreter ::+     (Member ExcB9 e, Member EnvironmentReader e)+  => Eff (ArtifactInterpreter e) ()+  -> Eff (ArtifactSourcesReader : e) [InstanceGenerator [TextFileWriter]]+runArtifactInterpreter ai = do+  ((), igs) <- runMonoidWriter ai+  traverse toFileInstanceGenerator igs -eachBindingSet :: ArtifactGenerator -> [(String, [String])] -> CGParser [[(String, String)]]-eachBindingSet g kvs = do-  checkInput-  return bindingSets+-- | Parse an 'ArtifactGenerator' inside the 'ArtifactInterpreter' effect.+interpretGenerator ::+     (Member ExcB9 e, Member EnvironmentReader e) => ArtifactGenerator -> Eff (ArtifactInterpreter e) ()+interpretGenerator generatorIn =+  case generatorIn of+    Sources sources generators -> withArtifactSources sources (mapM_ interpretGenerator generators)+    Let bindings generators -> withSubstitutedStringBindings bindings (mapM_ interpretGenerator generators)+    LetX bindings generators -> withXBindings bindings (mapM_ interpretGenerator generators)+    EachT keySet valueSets generators -> do+      allBindings <- eachBindingSetT generatorIn keySet valueSets+      sequence_ (flip withSubstitutedStringBindings (mapM_ interpretGenerator generators) <$> allBindings)+    Each kvs generators -> do+      allBindings <- eachBindingSet generatorIn kvs+      sequence_ $ do+        b <- allBindings+        return (withSubstitutedStringBindings b (mapM_ interpretGenerator generators))+    Artifact iid assembly -> interpretAssembly iid assembly+    EmptyArtifact -> return ()   where-    bindingSets = transpose [repeat k `zip` vs | (k, vs) <- kvs]-    checkInput =-      when-        (1 /= length (nub $ length . snd <$> kvs))-        (cgError (printf "Error in 'Each' binding: \n%s\nAll value lists must have the same length!" (ppShow g)))--writeInstanceGenerator :: InstanceId -> ArtifactAssembly -> CGParser ()-writeInstanceGenerator (IID iidStrT) assembly = do-  env@(CGEnv bindings _) <- ask-  iid <- either (throwM . CGError) (return . IID) (substE bindings iidStrT)-  let IID iidStr = iid-  env' <- addBindings [(instanceIdKey, iidStr)] env-  tell [IG iid env' assembly]+    withArtifactSources ::+         (Member ExcB9 e, Member EnvironmentReader e)+      => [ArtifactSource]+      -> Eff (ArtifactInterpreter e) s+      -> Eff (ArtifactInterpreter e) s+    withArtifactSources sources = local (++ sources)+    withXBindings ::+         (Member ExcB9 e, Member EnvironmentReader e)+      => [(String, [String])]+      -> Eff (ArtifactInterpreter e) ()+      -> Eff (ArtifactInterpreter e) ()+    withXBindings bindings cp = (`withSubstitutedStringBindings` cp) `mapM_` allXBindings bindings+      where+        allXBindings ((k, vs):rest) = [(k, v) : c | v <- vs, c <- allXBindings rest]+        allXBindings [] = [[]]+    eachBindingSetT ::+         (Member ExcB9 e)+      => ArtifactGenerator+      -> [String]+      -> [[String]]+      -> Eff (ArtifactInterpreter e) [[(String, String)]]+    eachBindingSetT g vars valueSets =+      if all ((== length vars) . length) valueSets+        then return (zip vars <$> valueSets)+        else throwB9Error+               (printf+                  "Error in 'Each' binding during artifact generation in:\n '%s'.\n\nThe variable list\n%s\n has %i entries, but this binding set\n%s\n\nhas a different number of entries!\n"+                  (ppShow g)+                  (ppShow vars)+                  (length vars)+                  (ppShow (head (dropWhile ((== length vars) . length) valueSets))))+    eachBindingSet ::+         (Member ExcB9 e)+      => ArtifactGenerator+      -> [(String, [String])]+      -> Eff (ArtifactInterpreter e) [[(String, String)]]+    eachBindingSet g kvs = do+      checkInput+      return bindingSets+      where+        bindingSets = transpose [repeat k `zip` vs | (k, vs) <- kvs]+        checkInput =+          when+            (1 /= length (nub $ length . snd <$> kvs))+            (throwB9Error+               (printf "Error in 'Each' binding: \n%s\nAll value lists must have the same length!" (ppShow g))) --- | Monad for creating Instance generators.-newtype CGParser a = CGParser-  { runCGParser :: WriterT [InstanceGenerator CGEnv] (ReaderT CGEnv (Either SomeException)) a-  } deriving (MonadThrow, Functor, Applicative, Monad, MonadReader CGEnv, MonadWriter [InstanceGenerator CGEnv])+interpretAssembly ::+     (Member ExcB9 e, Member EnvironmentReader e) => InstanceId -> ArtifactAssembly -> Eff (ArtifactInterpreter e) ()+interpretAssembly (IID iidStrTemplate) assembly = do+  iid@(IID iidStr) <- IID <$> subst iidStrTemplate+  env <- InstanceSources <$> askEnvironment <*> ask+  withSubstitutedStringBindings [(fromString instanceIdKey, fromString iidStr)] (tell [IG iid env assembly]) -data CGEnv = CGEnv-  { agEnv     :: Environment-  , agSources :: [ArtifactSource]+-- | Internal data structure. Only exposed for unit testing.+data InstanceSources = InstanceSources+  { isEnv :: Environment+  , isSources :: [ArtifactSource]   } deriving (Show, Eq)  data InstanceGenerator e =@@ -169,63 +209,57 @@      ArtifactAssembly   deriving (Read, Show, Typeable, Data, Eq) -newtype CGError =-  CGError String-  deriving (Read, Show, Typeable, Data, Eq)--instance Exception CGError--cgError :: String -> CGParser a-cgError msg = throwM (CGError msg)--execCGParser :: CGParser () -> CGEnv -> Either SomeException [InstanceGenerator CGEnv]-execCGParser = runReaderT . execWriterT . runCGParser--execIGEnv :: InstanceGenerator CGEnv -> Either String (InstanceGenerator [SourceGenerator])-execIGEnv (IG iid (CGEnv env sources) assembly) = IG iid <$> sourceGens <*> pure (substAssembly env assembly)-  where-    sourceGens = join <$> mapM (toSourceGen env) sources+toFileInstanceGenerator ::+     Member ExcB9 e => InstanceGenerator InstanceSources -> Eff e (InstanceGenerator [TextFileWriter])+toFileInstanceGenerator (IG iid (InstanceSources env sources) assembly) =+  runEnvironmentReader env $ do+    assembly' <- substAssembly assembly+    sourceGenerators <- join <$> traverse toSourceGen sources+    pure (IG iid sourceGenerators assembly') -substAssembly :: Environment -> ArtifactAssembly -> ArtifactAssembly-substAssembly env = everywhere gsubst+substAssembly ::+     forall e. (Member ExcB9 e, Member EnvironmentReader e)+  => ArtifactAssembly+  -> Eff e ArtifactAssembly+substAssembly = everywhereM gsubst   where-    gsubst :: Data a => a -> a-    gsubst = mkT substAssembly_ `extT` substImageTarget env `extT` substVmScript env-    substAssembly_ (CloudInit ts f) = CloudInit ts (sub f)-    substAssembly_ vm               = vm-    sub = subst env+    gsubst :: Data a => a -> Eff e a+    gsubst = mkM substAssembly_ `extM` substImageTarget `extM` substVmScript+    substAssembly_ (CloudInit ts f) = CloudInit ts <$> subst f+    substAssembly_ vm = pure vm -toSourceGen :: Environment -> ArtifactSource -> Either String [SourceGenerator]-toSourceGen env src =+toSourceGen :: (Member ExcB9 e, Member EnvironmentReader e) => ArtifactSource -> Eff e [TextFileWriter]+toSourceGen src = do+  env <- askEnvironment   case src of     FromFile t (Source conv f) -> do-      t' <- substE env t-      f' <- substE env f-      return [SG env (SGFiles [Source conv f']) KeepPerm t']+      t' <- subst t+      f' <- subst f+      return [MkTextFileWriter env (ExternalFiles [Source conv f']) KeepPermissions t']     FromContent t c -> do-      t' <- substE env t-      return [SG env (SGContent c) KeepPerm t']+      t' <- subst t+      return [MkTextFileWriter env (StaticContent c) KeepPermissions t']     SetPermissions o g a src' -> do-      sgs <- join <$> mapM (toSourceGen env) src'-      mapM (setSGPerm o g a) sgs+      sgs <- join <$> mapM toSourceGen src'+      traverse (setFilePermissionAction o g a) sgs     FromDirectory fromDir src' -> do-      sgs <- join <$> mapM (toSourceGen env) src'-      fromDir' <- substE env fromDir-      return (setSGFromDirectory fromDir' <$> sgs)+      sgs <- join <$> mapM toSourceGen src'+      fromDir' <- subst fromDir+      return (prefixExternalSourcesPaths fromDir' <$> sgs)     IntoDirectory toDir src' -> do-      sgs <- join <$> mapM (toSourceGen env) src'-      toDir' <- substE env toDir-      return (setSGToDirectory toDir' <$> sgs)+      sgs <- join <$> mapM toSourceGen src'+      toDir' <- subst toDir+      return (prefixOutputFilePaths toDir' <$> sgs) -createAssembledArtifacts :: [InstanceGenerator [SourceGenerator]] -> B9 [AssembledArtifact]+createAssembledArtifacts :: IsB9 e => [InstanceGenerator [TextFileWriter]] -> Eff e [AssembledArtifact] createAssembledArtifacts igs = do   buildDir <- getBuildDir   let outDir = buildDir </> "artifact-instances"   ensureDir (outDir ++ "/")   generated <- generateSources outDir `mapM` igs-  createTargets `mapM` generated+  runInstanceGenerator `mapM` generated -generateSources :: FilePath -> InstanceGenerator [SourceGenerator] -> B9 (InstanceGenerator FilePath)+generateSources :: IsB9 e => FilePath -> InstanceGenerator [TextFileWriter] -> Eff e (InstanceGenerator FilePath) generateSources outDir (IG iid sgs assembly) = do   uiid@(IID uiidStr) <- generateUniqueIID iid   dbgL (printf "generating sources for %s" uiidStr)@@ -234,91 +268,89 @@   generateSourceTo instanceDir `mapM_` sgs   return (IG uiid instanceDir assembly) -createTargets :: InstanceGenerator FilePath -> B9 AssembledArtifact-createTargets (IG uiid@(IID uiidStr) instanceDir assembly) = do-  targets <- createTarget uiid instanceDir assembly+-- | Run an+runInstanceGenerator :: IsB9 e => InstanceGenerator FilePath -> Eff e AssembledArtifact+runInstanceGenerator (IG uiid@(IID uiidStr) instanceDir assembly) = do+  targets <- runArtifactAssembly uiid instanceDir assembly   dbgL (printf "assembled artifact %s" uiidStr)   return (AssembledArtifact uiid targets) -generateUniqueIID :: InstanceId -> B9 InstanceId+generateUniqueIID :: IsB9 e => InstanceId -> Eff e InstanceId generateUniqueIID (IID iid) = IID . printf "%s-%s" iid <$> getBuildId -generateSourceTo :: FilePath -> SourceGenerator -> B9 ()-generateSourceTo instanceDir (SG env sgSource p to) = do-  let toAbs = instanceDir </> to-  ensureDir toAbs-  result <--    case sgSource of-      SGFiles froms -> do-        sources <- mapM (sgReadSourceFile env) froms-        return (mconcat sources)-      SGContent c -> renderContentGenerator env (toContentGenerator c)-  traceL (printf "rendered: \n%s\n" (LazyT.unpack (LazyE.decodeUtf8 result)))-  liftIO (Lazy.writeFile toAbs result)-  sgChangePerm toAbs p--sgReadSourceFile :: Environment -> SourceFile -> B9 Lazy.ByteString-sgReadSourceFile env = renderContentGenerator env . readTemplateFile+generateSourceTo :: IsB9 e => FilePath -> TextFileWriter -> Eff e ()+generateSourceTo instanceDir (MkTextFileWriter env sgSource p to) =+  localEnvironment (const env) $ do+    let toAbs = instanceDir </> to+    ensureDir toAbs+    result <-+      case sgSource of+        ExternalFiles froms -> do+          sources <- mapM readTemplateFile froms+          return (mconcat sources)+        StaticContent c -> toContentGenerator c+    traceL (printf "rendered: \n%s\n" (LazyT.unpack (LazyE.decodeUtf8 result)))+    liftIO (Lazy.writeFile toAbs result)+    runFilePermissionAction toAbs p -sgChangePerm :: FilePath -> SGPerm -> B9 ()-sgChangePerm _ KeepPerm = return ()-sgChangePerm f (SGSetPerm (o, g, a)) = cmd (printf "chmod 0%i%i%i '%s'" o g a f)+runFilePermissionAction :: IsB9 e => FilePath -> FilePermissionAction -> Eff e ()+runFilePermissionAction _ KeepPermissions = return ()+runFilePermissionAction f (ChangePermissions (o, g, a)) = cmd (printf "chmod 0%i%i%i '%s'" o g a f)  -- | Internal data type simplifying the rather complex source generation by---   bioling down 'ArtifactSource's to a flat list of uniform 'SourceGenerator's.-data SourceGenerator =-  SG Environment-     SGSource-     SGPerm-     FilePath+--   boiling down 'ArtifactSource's to a flat list of uniform 'TextFileWriter's.+data TextFileWriter =+  MkTextFileWriter Environment+                  TextFileWriterInput+                  FilePermissionAction+                  FilePath   deriving (Show, Eq)  -- | Return the (internal-)output file of the source file that is generated.-sourceGeneratorOutputFile :: SourceGenerator -> FilePath-sourceGeneratorOutputFile (SG _ _ _ f) = f+textFileWriterOutputFile :: TextFileWriter -> FilePath+textFileWriterOutputFile (MkTextFileWriter _ _ _ f) = f -data SGSource-  = SGFiles [SourceFile]-  | SGContent Content+data TextFileWriterInput+  = ExternalFiles [SourceFile]+  | StaticContent Content   deriving (Read, Show, Eq) -data SGPerm-  = SGSetPerm (Int, Int, Int)-  | KeepPerm+data FilePermissionAction+  = ChangePermissions (Int, Int, Int)+  | KeepPermissions   deriving (Read, Show, Typeable, Data, Eq) -sgGetFroms :: SourceGenerator -> [SourceFile]-sgGetFroms (SG _ (SGFiles fs) _ _) = fs-sgGetFroms _                       = []--setSGPerm :: Int -> Int -> Int -> SourceGenerator -> Either String SourceGenerator-setSGPerm o g a (SG env from KeepPerm dest) = Right (SG env from (SGSetPerm (o, g, a)) dest)-setSGPerm o g a sg-  | o < 0 || o > 7 = Left (printf "Bad 'owner' permission %i in \n%s" o (ppShow sg))-  | g < 0 || g > 7 = Left (printf "Bad 'group' permission %i in \n%s" g (ppShow sg))-  | a < 0 || a > 7 = Left (printf "Bad 'all' permission %i in \n%s" a (ppShow sg))-  | otherwise = Left (printf "Permission for source already defined:\n %s" (ppShow sg))+setFilePermissionAction :: Member ExcB9 e => Int -> Int -> Int -> TextFileWriter -> Eff e TextFileWriter+setFilePermissionAction o g a (MkTextFileWriter env from KeepPermissions dest) =+  pure (MkTextFileWriter env from (ChangePermissions (o, g, a)) dest)+setFilePermissionAction o g a sg+  | o < 0 || o > 7 = throwB9Error (printf "Bad 'owner' permission %i in \n%s" o (ppShow sg))+  | g < 0 || g > 7 = throwB9Error (printf "Bad 'group' permission %i in \n%s" g (ppShow sg))+  | a < 0 || a > 7 = throwB9Error (printf "Bad 'all' permission %i in \n%s" a (ppShow sg))+  | otherwise = throwB9Error (printf "Permission for source already defined:\n %s" (ppShow sg)) -setSGFromDirectory :: FilePath -> SourceGenerator -> SourceGenerator-setSGFromDirectory fromDir (SG e (SGFiles fs) p d) = SG e (SGFiles (setSGFrom <$> fs)) p d+prefixExternalSourcesPaths :: FilePath -> TextFileWriter -> TextFileWriter+prefixExternalSourcesPaths fromDir (MkTextFileWriter e (ExternalFiles fs) p d) =+  MkTextFileWriter e (ExternalFiles (prefixExternalSourcePaths <$> fs)) p d   where-    setSGFrom (Source t f) = Source t (fromDir </> f)-setSGFromDirectory _fromDir sg = sg+    prefixExternalSourcePaths (Source t f) = Source t (fromDir </> f)+prefixExternalSourcesPaths _fromDir sg = sg -setSGToDirectory :: FilePath -> SourceGenerator -> SourceGenerator-setSGToDirectory toDir (SG e fs p d) = SG e fs p (toDir </> d)+prefixOutputFilePaths :: FilePath -> TextFileWriter -> TextFileWriter+prefixOutputFilePaths toDir (MkTextFileWriter e fs p d) = MkTextFileWriter e fs p (toDir </> d) --- | Create the actual target, either just a mountpoint, or an ISO or VFAT--- image.-createTarget :: InstanceId -> FilePath -> ArtifactAssembly -> B9 [ArtifactTarget]-createTarget iid instanceDir (VmImages imageTargets vmScript) = do+-- | Create the 'ArtifactTarget' from an 'ArtifactAssembly' in the directory @instanceDir@+--+-- @since 0.5.65+runArtifactAssembly :: IsB9 e => InstanceId -> FilePath -> ArtifactAssembly -> Eff e [ArtifactTarget]+runArtifactAssembly iid instanceDir (VmImages imageTargets vmScript) = do   dbgL (printf "Creating VM-Images in '%s'" instanceDir)   success <- buildWithVm iid imageTargets instanceDir vmScript   let err_msg = printf "Error creating 'VmImages' for instance '%s'" iidStr       (IID iidStr) = iid   unless success (errorL err_msg >> error err_msg)   return [VmImagesTarget]-createTarget _ instanceDir (CloudInit types outPath) = mapM create_ types+runArtifactAssembly _ instanceDir (CloudInit types outPath) = mapM create_ types   where     create_ CI_DIR = do       let ciDir = outPath
src/lib/B9/B9Config.hs view
@@ -4,13 +4,21 @@ -} module B9.B9Config   ( B9Config(..)+  , runB9ConfigReader+  , B9ConfigReader+  , getB9Config+  , getExecEnvType+  , getConfig+  , getLogVerbosity+  , getProjectRoot+  , getRemoteRepos+  , isInteractive+  , B9ConfigWriter   , verbosity   , logFile-  , buildDirRoot+  , projectRoot   , keepTempDirs   , execEnvType-  , profileFile-  , envVars   , uniqueBuildDirs   , repositoryCache   , repository@@ -21,14 +29,14 @@   , B9ConfigOverride(..)   , noB9ConfigOverride   , B9ConfigAction()-  , execB9ConfigAction-  , invokeB9-  , askRuntimeConfig-  , localRuntimeConfig+  , runB9ConfigActionWithOverrides+  , runB9ConfigAction+  , localB9Config   , modifyPermanentConfig   , customB9Config   , customB9ConfigPath   , customLibVirtNetwork+  , customEnvironment   , overrideB9ConfigPath   , overrideB9Config   , overrideWorkingDirectory@@ -41,7 +49,6 @@   , writeB9CPDocument   , readB9Config   , parseB9Config-  , appendPositionalArguments   , modifyCPDocument   , b9ConfigToCPDocument   , LogLevel(..)@@ -52,12 +59,13 @@  import B9.B9Config.LibVirtLXC as X import B9.B9Config.Repository as X+import Control.Eff+import Control.Eff.Reader.Lazy+import Control.Eff.Writer.Lazy import Control.Exception import Control.Lens as Lens ((&), (.~), (?~), (^.), _Just, makeLenses, over, set) import Control.Monad ((>=>)) import Control.Monad.IO.Class-import Control.Monad.Reader-import Control.Monad.Writer import Data.ConfigFile.B9Extras   ( CPDocument   , CPError@@ -74,8 +82,8 @@   ) import Data.Function (on) import Data.Maybe (fromMaybe)-import qualified Data.Semigroup as Sem-import qualified Data.Text.Lazy as LazyT+import Data.Monoid+import Data.Semigroup as Semigroup hiding (Last(..)) import System.Directory import System.IO.B9Extras (SystemPath(..), ensureDir, resolve) import Text.Printf (printf)@@ -97,11 +105,9 @@ data B9Config = B9Config   { _verbosity :: Maybe LogLevel   , _logFile :: Maybe FilePath-  , _buildDirRoot :: Maybe FilePath+  , _projectRoot :: Maybe FilePath   , _keepTempDirs :: Bool   , _execEnvType :: ExecEnvType-  , _profileFile :: Maybe FilePath-  , _envVars :: Environment   , _uniqueBuildDirs :: Bool   , _repositoryCache :: Maybe SystemPath   , _repository :: Maybe String@@ -109,18 +115,16 @@   , _maxLocalSharedImageRevisions :: Maybe Int   , _libVirtLXCConfigs :: Maybe LibVirtLXCConfig   , _remoteRepos :: [RemoteRepo]-  } deriving (Show)+  } deriving (Show, Eq) -instance Sem.Semigroup B9Config where+instance 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'+      , _projectRoot = getLast $ on mappend (Last . _projectRoot) 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')@@ -131,22 +135,86 @@       }  instance Monoid B9Config where-  mappend = (Sem.<>)-  mempty =-    B9Config Nothing Nothing Nothing False LibVirtLXC Nothing mempty True Nothing Nothing False Nothing Nothing []+  mappend = (<>)+  mempty = B9Config Nothing Nothing Nothing False LibVirtLXC True Nothing Nothing False Nothing Nothing [] +-- | Reader for 'B9Config'. See 'getB9Config' and 'localB9Config'.+--+-- @since 0.5.65+type B9ConfigReader = Reader B9Config++-- | Run a 'B9ConfigReader'.+--+-- @since 0.5.65+runB9ConfigReader :: B9Config -> Eff (B9ConfigReader ': e) a -> Eff e a+runB9ConfigReader = runReader++-- | Return the runtime configuration, that should be the configuration merged+-- from all configuration sources. This is the configuration to be used during+-- a VM image build.+--+-- @since 0.5.65+getB9Config :: Member B9ConfigReader e => Eff e B9Config+getB9Config = ask++-- | Run an action with an updated runtime configuration.+--+-- @since 0.5.65+localB9Config :: Member B9ConfigReader e => (B9Config -> B9Config) -> Eff e a -> Eff e a+localB9Config = local++-- | An alias for 'getB9Config'.+--+-- @deprecated+--+-- @since 0.5.65+getConfig :: Member B9ConfigReader e => Eff e B9Config+getConfig = getB9Config++-- | Ask whether @stdin@ of the @B9@ process should be redirected to the+-- external commands executed during the build.+--+-- @since 0.5.65+isInteractive :: Member B9ConfigReader e => Eff e Bool+isInteractive = _interactive <$> getB9Config++-- | Ask for the 'ExecEnvType'.+--+-- @since 0.5.65+getExecEnvType :: Member B9ConfigReader e => Eff e ExecEnvType+getExecEnvType = _execEnvType <$> getB9Config++-- | Ask for the 'RemoteRepo's.+--+-- @since 0.5.65+getRemoteRepos :: Member B9ConfigReader e => Eff e [RemoteRepo]+getRemoteRepos = _remoteRepos <$> getB9Config++-- | Ask for the 'LogLevel'.+--+-- @since 0.5.65+getLogVerbosity :: Member B9ConfigReader e => Eff e (Maybe LogLevel)+getLogVerbosity = _verbosity <$> getB9Config++-- | Ask for the project root directory.+--+-- @since 0.5.65+getProjectRoot :: Member B9ConfigReader e => Eff e FilePath+getProjectRoot = fromMaybe "." . _projectRoot <$> ask+ -- | 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   , _customLibVirtNetwork :: Maybe (Maybe String)+  , _customEnvironment :: Environment   } deriving (Show)  -- | An empty default 'B9ConfigOverride' value, that will neither apply any -- additional 'B9Config' nor change the path of the configuration file. noB9ConfigOverride :: B9ConfigOverride-noB9ConfigOverride = B9ConfigOverride Nothing mempty mempty+noB9ConfigOverride = B9ConfigOverride Nothing mempty mempty mempty  makeLenses ''B9Config @@ -162,7 +230,7 @@  -- | Define the current working directory to be used when building. overrideWorkingDirectory :: FilePath -> B9ConfigOverride -> B9ConfigOverride-overrideWorkingDirectory p = customB9Config . buildDirRoot ?~ p+overrideWorkingDirectory p = customB9Config . projectRoot ?~ p  -- | Overwrite the 'verbosity' settings in the configuration with those given. overrideVerbosity :: LogLevel -> B9ConfigOverride -> B9ConfigOverride@@ -173,27 +241,23 @@ overrideKeepBuildDirs = overrideB9Config . Lens.set keepTempDirs  -- | A monad that gives access to the (transient) 'B9Config' to be used at--- _runtime_ with 'askRuntimeConfig' or 'localRuntimeConfig', and that allows+-- _runtime_ with 'getB9Config' or 'localB9Config', and that allows -- to write permanent 'B9Config' changes back to the configuration file using--- 'modifyPermanentConfig'. Execute a 'B9ConfigAction' by invoking--- either 'invokeB9' (which is simple) or 'execB9ConfigAction'.-newtype B9ConfigAction m a = B9ConfigAction-  { runB9ConfigAction :: ReaderT B9Config (WriterT [Endo B9Config] m) a-  } deriving (Functor, Applicative, Monad, MonadIO)---- | Return the runtime configuration, that should be the configuration merged--- from all configuration sources. This is the configuration to be used during--- a VM image build.-askRuntimeConfig :: Monad m => B9ConfigAction m B9Config-askRuntimeConfig = B9ConfigAction ask+-- 'modifyPermanentConfig'. This is the amalgamation of 'B9ConfigWriter'+-- 'B9ConfigReader' and 'IO'.+--+-- @since 0.5.65+type B9ConfigAction a = Eff '[ B9ConfigWriter, B9ConfigReader, EnvironmentReader, Lift IO] a --- | Run an action with an updated runtime configuration.-localRuntimeConfig :: Monad m => (B9Config -> B9Config) -> B9ConfigAction m a -> B9ConfigAction m a-localRuntimeConfig f = B9ConfigAction . local f . runB9ConfigAction+-- | Accumulate 'B9Config' changes that go back to the config file. See+-- 'B9ConfigAction' and 'modifyPermanentConfig'.+--+-- @since 0.5.65+type B9ConfigWriter = Writer (Semigroup.Endo B9Config)  -- | Add a modification to the permanent configuration file.-modifyPermanentConfig :: Monad m => Endo B9Config -> B9ConfigAction m ()-modifyPermanentConfig f = B9ConfigAction (tell [f])+modifyPermanentConfig :: Member B9ConfigWriter e => Endo B9Config -> Eff e ()+modifyPermanentConfig = tell  -- | Execute a 'B9ConfigAction'. -- It will take a 'B9ConfigOverride' as input. The 'B9Config' in that value is@@ -206,25 +270,28 @@ -- and written back to that file, note that these changes are ONLY reflected -- in the configuration file and **not** in the _runtime configuration_. ----- See also 'invokeB9', which does not need the 'B9ConfigOverride' parameter.-execB9ConfigAction :: MonadIO m => B9ConfigAction m a -> B9ConfigOverride -> m a-execB9ConfigAction act cfg = do+-- See also 'runB9ConfigAction', which does not need the 'B9ConfigOverride' parameter.+--+-- @since 0.5.65+runB9ConfigActionWithOverrides :: B9ConfigAction a -> B9ConfigOverride -> IO a+runB9ConfigActionWithOverrides act cfg = do   let cfgPath = cfg ^. customB9ConfigPath   cp <- openOrCreateB9Config cfgPath   case parseB9Config cp of     Left e -> fail (printf "Internal configuration load error, please report this: %s\n" (show e))-    Right permanentConfig -> do+    Right permanentConfigIn -> do       let runtimeCfg =-            let rc = permanentConfig Sem.<> (cfg ^. customB9Config)+            let rc = permanentConfigIn <> (cfg ^. customB9Config)              in case cfg ^. customLibVirtNetwork of                   Just overridenNetwork -> rc & libVirtLXCConfigs . _Just . networkId .~ overridenNetwork                   Nothing -> rc-      (res, permanentB9ConfigUpdates) <- runWriterT (runReaderT (runB9ConfigAction act) runtimeCfg)-      let cpExtErr = modifyCPDocument cp <$> permanentB9ConfigUpdate-          permanentB9ConfigUpdate =-            if null permanentB9ConfigUpdates+      (res, permanentB9ConfigUpdates) <-+        runLift (runEnvironmentReader (cfg ^. customEnvironment) (runReader runtimeCfg (runMonoidWriter act)))+      let cpExtErr = modifyCPDocument cp <$> permanentB9ConfigUpdateMaybe+          permanentB9ConfigUpdateMaybe =+            if appEndo permanentB9ConfigUpdates permanentConfigIn == permanentConfigIn               then Nothing-              else Just (mconcat permanentB9ConfigUpdates)+              else Just permanentB9ConfigUpdates       cpExt <-         maybe           (return Nothing)@@ -234,9 +301,11 @@       return res  -- | Run a 'B9ConfigAction' using 'noB9ConfigOverride'.--- See 'execB9ConfigAction' for more details.-invokeB9 :: MonadIO m => B9ConfigAction m a -> m a-invokeB9 = flip execB9ConfigAction noB9ConfigOverride+-- See 'runB9ConfigActionWithOverrides' for more details.+--+-- @since 0.5.65+runB9ConfigAction :: B9ConfigAction a -> IO a+runB9ConfigAction = flip runB9ConfigActionWithOverrides noB9ConfigOverride  -- | Open the configuration file that contains the 'B9Config'. -- If the configuration does not exist, write a default configuration file,@@ -268,11 +337,9 @@   B9Config     { _verbosity = Just LogInfo     , _logFile = Nothing-    , _buildDirRoot = Nothing+    , _projectRoot = Nothing     , _keepTempDirs = False     , _execEnvType = LibVirtLXC-    , _profileFile = Nothing-    , _envVars = mempty     , _uniqueBuildDirs = True     , _repository = Nothing     , _repositoryCache = Just defaultRepositoryCache@@ -294,8 +361,8 @@ logFileK :: String logFileK = "log_file" -buildDirRootK :: String-buildDirRootK = "build_dir_root"+projectRootK :: String+projectRootK = "build_dir_root"  keepTempDirsK :: String keepTempDirsK = "keep_temp_dirs"@@ -303,12 +370,6 @@ execEnvTypeK :: String execEnvTypeK = "exec_env" -profileFileK :: String-profileFileK = "profile_file"--envVarsK :: String-envVarsK = "environment_vars"- uniqueBuildDirsK :: String uniqueBuildDirsK = "unique_build_dirs" @@ -337,16 +398,14 @@   cp1 <- addSectionCP emptyCP cfgFileSection   cp2 <- setShowCP cp1 cfgFileSection verbosityK (_verbosity c)   cp3 <- setShowCP cp2 cfgFileSection logFileK (_logFile c)-  cp4 <- setShowCP cp3 cfgFileSection buildDirRootK (_buildDirRoot c)+  cp4 <- setShowCP cp3 cfgFileSection projectRootK (_projectRoot c)   cp5 <- setShowCP cp4 cfgFileSection keepTempDirsK (_keepTempDirs c)   cp6 <- setShowCP cp5 cfgFileSection execEnvTypeK (_execEnvType c)-  cp7 <- setShowCP cp6 cfgFileSection profileFileK (_profileFile c)-  cp8 <- setShowCP cp7 cfgFileSection envVarsK (_envVars c)-  cp9 <- setShowCP cp8 cfgFileSection uniqueBuildDirsK (_uniqueBuildDirs c)-  cpA <- setShowCP cp9 cfgFileSection maxLocalSharedImageRevisionsK (_maxLocalSharedImageRevisions c)-  cpB <- setShowCP cpA cfgFileSection repositoryCacheK (_repositoryCache c)-  cpC <- foldr (>=>) return (libVirtLXCConfigToCPDocument <$> _libVirtLXCConfigs c) cpB-  cpFinal <- foldr (>=>) return (remoteRepoToCPDocument <$> _remoteRepos c) cpC+  cp7 <- setShowCP cp6 cfgFileSection uniqueBuildDirsK (_uniqueBuildDirs c)+  cp8 <- setShowCP cp7 cfgFileSection maxLocalSharedImageRevisionsK (_maxLocalSharedImageRevisions c)+  cp9 <- setShowCP cp8 cfgFileSection repositoryCacheK (_repositoryCache c)+  cpA <- foldr (>=>) return (libVirtLXCConfigToCPDocument <$> _libVirtLXCConfigs c) cp9+  cpFinal <- foldr (>=>) return (remoteRepoToCPDocument <$> _remoteRepos c) cpA   setShowCP cpFinal cfgFileSection repositoryK (_repository c)  readB9Config :: MonadIO m => Maybe SystemPath -> m CPDocument@@ -356,25 +415,11 @@ parseB9Config cp =   let getr :: (CPGet a) => CPOptionSpec -> Either CPError a       getr = readCP cp cfgFileSection-      getB9Config =-        B9Config <$> getr verbosityK <*> getr logFileK <*> getr buildDirRootK <*> getr keepTempDirsK <*>-        getr execEnvTypeK <*>-        getr profileFileK <*>-        (fromStringPairs <$> getr envVarsK)-         <*>-        getr uniqueBuildDirsK <*>-        getr repositoryCacheK <*>-        getr repositoryK <*>-        pure False <*>-        pure (either (const Nothing) id (getr maxLocalSharedImageRevisionsK)) <*>-        pure (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 = insertPositionalArguments (LazyT.pack <$> extraPositional)+   in B9Config <$> getr verbosityK <*> getr logFileK <*> getr projectRootK <*> getr keepTempDirsK <*> getr execEnvTypeK <*>+      getr uniqueBuildDirsK <*>+      getr repositoryCacheK <*>+      getr repositoryK <*>+      pure False <*>+      pure (either (const Nothing) id (getr maxLocalSharedImageRevisionsK)) <*>+      pure (either (const Nothing) Just (parseLibVirtLXCConfig cp)) <*>+      parseRemoteRepos cp
src/lib/B9/B9Config/LibVirtLXC.hs view
@@ -20,7 +20,7 @@   , _networkId :: Maybe String   , guestCapabilities :: [LXCGuestCapability]   , guestRamSize :: RamSize-  } deriving (Read, Show)+  } deriving (Read, Show, Eq)  -- | Available linux capabilities for lxc containers. This maps directly to the -- capabilities defined in 'man 7 capabilities'.@@ -63,7 +63,7 @@   | CAP_SYS_TTY_CONFIG   | CAP_SYSLOG   | CAP_WAKE_ALARM-  deriving (Read, Show)+  deriving (Read, Show, Eq)  makeLenses ''LibVirtLXCConfig 
src/lib/B9/B9Config/Repository.hs view
@@ -20,19 +20,19 @@                              SshPrivKey                              SshRemoteHost                              SshRemoteUser-  deriving (Read, Show, Typeable, Data)+  deriving (Read, Show, Typeable, Data, Eq)  remoteRepoRepoId :: RemoteRepo -> String remoteRepoRepoId (RemoteRepo repoId _ _ _ _) = repoId  newtype SshPrivKey = SshPrivKey FilePath-  deriving (Read, Show, Typeable, Data)+  deriving (Read, Show, Typeable, Data, Eq)  newtype SshRemoteHost = SshRemoteHost (String,Int)-  deriving (Read, Show, Typeable, Data)+  deriving (Read, Show, Typeable, Data, Eq)  newtype SshRemoteUser = SshRemoteUser String-  deriving (Read, Show, Typeable, Data)+  deriving (Read, Show, Typeable, Data, Eq)   -- | Persist a repo to a configuration file.
+ src/lib/B9/B9Error.hs view
@@ -0,0 +1,95 @@+-- | Error handling in B9 via extensible effects.+-- B9 wraps errors in `SomeException`.+--+-- @since 0.5.64+module B9.B9Error+  ( throwSomeException+  , throwSomeException_+  , throwB9Error+  , throwB9Error_+  , errorOnException+  , ExcB9+  , runExcB9+  , B9Error(MkB9Error)+  , fromB9Error+  , catchB9Error+  , catchB9ErrorAsEither+  )+where++import           Control.Exception              ( toException+                                                , SomeException+                                                , Exception+                                                , displayException+                                                )+import           Control.Eff                   as Eff+import           Control.Eff.Exception         as Eff+import           Control.Monad+import           Data.String                    ( IsString(..) )++-- | The exception effect used in most places in B9.+--  This is `Exc` specialized with `SomeException`.+--+-- @since 0.5.64+type ExcB9 = Exc SomeException++-- | This is a simple runtime exception to indicate that B9 code encountered+-- some exceptional event.+--+-- @since 0.5.64+newtype B9Error = MkB9Error { fromB9Error :: String }+  deriving (Show, IsString)++instance Exception B9Error++-- | Run an `ExcB9`.+--+-- @since 0.5.64+runExcB9 :: Eff (ExcB9 ': e) a -> Eff e (Either SomeException a)+runExcB9 = runError++-- | Run an `ExcB9` and rethrow the exception with `error`.+--+-- @since 0.5.64+errorOnException :: Eff (ExcB9 ': e) a -> Eff e a+errorOnException = runError >=> either (error . displayException) pure++-- | 'SomeException' wrapped into 'Exc'ecption 'Eff'ects+--+-- @since 0.5.64+throwSomeException :: (Member ExcB9 e, Exception x) => x -> Eff e a+throwSomeException = throwError . toException++-- | 'SomeException' wrapped into 'Exc'ecption 'Eff'ects+--+-- @since 0.5.64+throwSomeException_ :: (Member ExcB9 e, Exception x) => x -> Eff e ()+throwSomeException_ = throwError_ . toException+++-- | 'SomeException' wrapped into 'Exc'ecption 'Eff'ects+--+-- @since 0.5.64+throwB9Error :: Member ExcB9 e => String -> Eff e a+throwB9Error = throwSomeException . MkB9Error++-- | 'SomeException' wrapped into 'Exc'ecption 'Eff'ects+--+-- @since 0.5.64+throwB9Error_ :: Member ExcB9 e => String -> Eff e ()+throwB9Error_ = throwSomeException_ . MkB9Error++-- | Catch exceptions.+--+-- @since 0.5.64+catchB9Error+  :: Member ExcB9 e => Eff e a -> (SomeException -> Eff e a) -> Eff e a+catchB9Error = catchError+++-- | Catch exceptions and return them via 'Either'.+--+-- @since 0.5.64+catchB9ErrorAsEither+  :: Member ExcB9 e => Eff e a -> Eff e (Either SomeException a)+catchB9ErrorAsEither x = catchB9Error (Right <$> x) (pure . Left)
+ src/lib/B9/B9Exec.hs view
@@ -0,0 +1,79 @@+-- | This modules contains support for external command execution.+--+-- @since 0.5.65+module B9.B9Exec+  ( cmd+  ) where++import B9.B9Config+import B9.B9Logging+import Control.Concurrent.Async (Concurrently(..))+import Control.Eff+import Control.Monad+import Control.Monad.IO.Class+import Control.Monad.Trans.Control (embed_)+import qualified Data.ByteString.Char8 as Strict+import Data.Conduit ((.|), runConduit)+import qualified Data.Conduit.List as CL+import Data.Conduit.Process+import Data.Functor ()+import System.Exit+import Text.Printf++-- | Execute the given shell command.+--+-- If 'isInteractive' is true, the standard-in will be passed to the external command,+-- and all output of the program will be directed to standard-out.+--+-- The command and the output is either logged to the logfile with 'traceL' or 'errorL' or+-- written to stdout.+--+-- If the command exists with non-zero exit code, the current process exists with the same+-- exit code.+--+-- @since 0.5.65+cmd :: CommandIO e => String -> Eff e ()+cmd str = do+  inheritStdIn <- isInteractive+  if inheritStdIn+    then interactiveCmd str+    else nonInteractiveCmd str++interactiveCmd ::+     forall e. CommandIO e+  => String+  -> Eff e ()+interactiveCmd str = void (cmdWithStdIn True str :: Eff e Inherited)++nonInteractiveCmd ::+     forall e. CommandIO e+  => String+  -> Eff e ()+-- TODO if we use 'ClosedStream' we get an error from 'virsh console'+-- complaining about a missing controlling tty. Original source line:+-- nonInteractiveCmd str = void (cmdWithStdIn False str :: B9 ClosedStream)+nonInteractiveCmd str = void (cmdWithStdIn False str :: Eff e Inherited)++cmdWithStdIn :: (CommandIO e, InputSource stdin) => Bool -> String -> Eff e stdin+cmdWithStdIn toStdOut cmdStr = do+  traceL $ "COMMAND: " ++ cmdStr+  traceLIO <- embed_ (traceL . Strict.unpack)+  errorLIO <- embed_ (errorL . Strict.unpack)+  let errorLC = CL.mapM_ (liftIO . errorLIO)+  let traceLC =+        if toStdOut+          then CL.mapM_ Strict.putStr+          else CL.mapM_ (liftIO . traceLIO)+  (cpIn, cpOut, cpErr, cph) <- streamingProcess (shell cmdStr)+  e <-+    liftIO $+    runConcurrently $+    Concurrently (runConduit (cpOut .| traceLC)) *> Concurrently (runConduit (cpErr .| errorLC)) *>+    Concurrently (waitForStreamingProcess cph)+  checkExitCode e+  return cpIn+  where+    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
+ src/lib/B9/B9Logging.hs view
@@ -0,0 +1,105 @@+-- | This modules contains support for logging.+--+-- @since 0.5.65+module B9.B9Logging+  ( Logger(..)+  , CommandIO+  , LoggerReader+  , withLogger+  , b9Log+  , traceL+  , dbgL+  , infoL+  , errorL+  , errorExitL+  ) where++import B9.B9Config+import B9.B9Error+import Control.Eff+import Control.Eff.Reader.Lazy+import Control.Monad+import Control.Monad.IO.Class+import Control.Monad.Trans.Control (MonadBaseControl, liftBaseWith, restoreM)+import Data.Maybe+import Data.Time.Clock+import Data.Time.Format+import qualified System.IO as SysIO+import Text.Printf++-- | The logger to write log messages to.+--+-- @since 0.5.65+newtype Logger = MkLogger+  { logFileHandle :: Maybe SysIO.Handle+  }++-- | Effect that reads a 'Logger'.+--+-- @since 0.5.65+type LoggerReader = Reader Logger++-- | Lookup the selected 'getLogVerbosity' and '_logFile' from the 'B9Config'+-- and open it.+--+-- Then run the given action; if the action crashes, the log file will be closed.+--+-- @since 0.5.65+withLogger ::+     (MonadBaseControl IO (Eff e), MonadIO (Eff e), Member B9ConfigReader e) => Eff (LoggerReader ': e) a -> Eff e a+withLogger action = do+  lf <- _logFile <$> getB9Config+  effState <-+    liftBaseWith $ \runInIO ->+      let fInIO = runInIO . flip runReader action . MkLogger+       in maybe (fInIO Nothing) (\logf -> SysIO.withFile logf SysIO.AppendMode (fInIO . Just)) lf+  restoreM effState++-- | Convenience type alias for 'Eff'ects that have a 'B9Config', a 'Logger', 'MonadIO' and 'MonadBaseControl'.+--+-- @since 0.5.65+type CommandIO e = (MonadBaseControl IO (Eff e), MonadIO (Eff e), Member LoggerReader e, Member B9ConfigReader e)++traceL :: CommandIO e => String -> Eff e ()+traceL = b9Log LogTrace++dbgL :: CommandIO e => String -> Eff e ()+dbgL = b9Log LogDebug++infoL :: CommandIO e => String -> Eff e ()+infoL = b9Log LogInfo++errorL :: CommandIO e => String -> Eff e ()+errorL = b9Log LogError++errorExitL :: (CommandIO e, Member ExcB9 e) => String -> Eff e a+errorExitL e = b9Log LogError e >> throwB9Error e++b9Log :: CommandIO e => LogLevel -> String -> Eff e ()+b9Log level msg = do+  lv <- getLogVerbosity+  lfh <- logFileHandle <$> ask+  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)++formatLogMsg :: LogLevel -> String -> IO String+formatLogMsg l msg = do+  u <- getCurrentTime+  let time = formatTime defaultTimeLocale "%H:%M:%S" u+  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 "
src/lib/B9/B9Monad.hs view
@@ -1,286 +1,52 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--{-| Definition of the B9 monad. It encapsulates logging, very basic command-execution profiling, a reader for the "B9.B9Config" and access to the-current build id, the current build directory and the artifact to build.--This module is used by the _effectful_ functions in this library.--} module B9.B9Monad-  ( B9-  , run-  , traceL-  , dbgL-  , infoL-  , errorL-  , errorExitL-  , getConfig-  , getBuildId-  , getBuildDate-  , getBuildDir-  , getExecEnvType-  , getSelectedRemoteRepo-  , getRemoteRepos-  , getRepoCache-  , cmd+  ( runB9+  , B9+  , B9Eff+  , IsB9   ) where -import           B9.B9Config-import           B9.Environment-import           B9.Repository-import           Control.Applicative-import           Control.Concurrent.Async (Concurrently (..))-import           Control.Exception        (bracket)-import           Control.Lens             ((%~), (&), (.~), (?~), (^.))-import           Control.Monad-import           Control.Monad.IO.Class-import           Control.Monad.State-import qualified Data.ByteString.Char8    as Strict-import           Data.Conduit             (runConduit, (.|))-import qualified Data.Conduit.List        as CL-import           Data.Conduit.Process-import           Data.Foldable-import           Data.Functor             ()-import           Data.Hashable-import           Data.Maybe-import           Data.Time.Clock-import           Data.Time.Format-import           Data.Word                (Word32)-import           System.Directory-import           System.Exit-import           System.FilePath-import qualified System.IO                as SysIO-import           System.Random            (randomIO)-import           Text.Printf--data BuildState = BuildState-  { bsBuildId            :: String-  , bsBuildDate          :: String-  , bsCfg                :: B9Config-  , bsBuildDir           :: FilePath-  , bsLogFileHandle      :: Maybe SysIO.Handle-  , bsSelectedRemoteRepo :: Maybe RemoteRepo-  , bsRepoCache          :: RepoCache-  , bsProf               :: [ProfilingEntry]-  , bsStartTime          :: UTCTime-  , bsInheritStdIn       :: Bool-  }--data ProfilingEntry-  = IoActionDuration NominalDiffTime-  | LogEvent LogLevel-             String-  deriving (Eq, Show)--run :: MonadIO m => B9 a -> B9ConfigAction m a-run action = do-  cfg <- askRuntimeConfig-  liftIO $ do-    buildId <- liftIO $ generateBuildId cfg-    now <- liftIO getCurrentTime-    liftIO $ withBuildDir cfg buildId (withLogFile cfg . runImpl cfg buildId now)-  where-    resolveBuildDirRoot cfg =-      case _buildDirRoot cfg of-        Nothing -> getCurrentDirectory >>= canonicalizePath-        Just root' -> do-          createDirectoryIfMissing True root'-          canonicalizePath root'-    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)-    runImpl cfg buildId now buildDir logFileHandle = do-      repoCache <- initRepoCache (fromMaybe defaultRepositoryCache (_repositoryCache cfg))-      let buildDate = formatTime undefined "%F-%T" now-      remoteRepos' <- mapM (initRemoteRepo repoCache) (_remoteRepos cfg)-      buildDirRootAbs <- resolveBuildDirRoot cfg-      let finalCfg =-            cfg & remoteRepos .~ remoteRepos' & (buildDirRoot ?~ buildDirRootAbs) & envVars %~ fromJust .-            addStringBinding ("buildDirRoot", buildDirRootAbs)-          ctx =-            BuildState-              buildId-              buildDate-              finalCfg-              buildDir-              logFileHandle-              selectedRemoteRepo-              repoCache-              []-              now-              (_interactive finalCfg)-          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-      when (isJust (_profileFile cfg)) $-        writeFile (fromJust (_profileFile cfg)) (unlines $ show <$> reverse (bsProf ctxOut))-      return r-            -- Check repositories-    createBuildDir cfg buildId = do-      let subDir = "BUILD-" ++ buildId-      buildDir <- resolveBuildDir subDir-      createDirectoryIfMissing True buildDir-      canonicalizePath buildDir-      where-        resolveBuildDir f = do-          root <- resolveBuildDirRoot cfg-          return $ root </> f-    removeBuildDir cfg buildDir =-      when (_uniqueBuildDirs cfg && not (_keepTempDirs cfg)) $ removeDirectoryRecursive buildDir-    generateBuildId cfg =-      let cfgHash = hash (show cfg)-       in if _uniqueBuildDirs cfg-            then do-              salt <- randomIO :: IO Word32-              return (printf "%08X-%08X" cfgHash salt)-            else return (printf "%08X" cfgHash)-    wrappedAction = do-      b9cfg <- getConfig-      traverse_ (traceL . printf "Root Build Directory: %s") (b9cfg ^. buildDirRoot)-      startTime <- gets bsStartTime-      r <- action-      now <- liftIO getCurrentTime-      let duration = show (now `diffUTCTime` startTime)-      infoL (printf "DURATION: %s" duration)-      return r---- Run the action build action-getBuildId :: B9 String-getBuildId = gets bsBuildId--getBuildDate :: B9 String-getBuildDate = gets bsBuildDate--getBuildDir :: B9 FilePath-getBuildDir = gets bsBuildDir--getConfig :: B9 B9Config-getConfig = gets bsCfg--getExecEnvType :: B9 ExecEnvType-getExecEnvType = gets (_execEnvType . bsCfg)--getSelectedRemoteRepo :: B9 (Maybe RemoteRepo)-getSelectedRemoteRepo = gets bsSelectedRemoteRepo--getRemoteRepos :: B9 [RemoteRepo]-getRemoteRepos = gets (_remoteRepos . bsCfg)--getRepoCache :: B9 RepoCache-getRepoCache = gets bsRepoCache+import B9.B9Config+import B9.B9Error+import B9.B9Logging+import B9.BuildInfo+import B9.Environment+import B9.Repository+import Control.Eff+import Data.Functor () --- getDownloader :: B9 Downloader--- getDownloader = gets bsDownloader+-- | Definition of the B9 monad. See 'B9Eff'. ----- -- | Configuration for a tool that retreives arbitrary URL and returns them to--- -- @stdout@.--- data Downloader =---   Downloader {downloaderCmd :: FilePath---              ,downloaderArgsBeforeUrl :: [String]---              ,downloaderUrlArgPrintfFormatString :: [String]---              ,downloaderArgsAfterUrl :: [String]}---   deriving (Read,Show,Eq,Ord,Typeable,Generic)+-- This module is used by the _effectful_ functions in this library. ----- readContentFromUrl :: String -> B9 Strict.ByteString--- readContentFromUrl url = do---   return expression-cmd :: String -> B9 ()-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)--nonInteractiveCmd :: String -> B9 ()--- TODO if we use 'ClosedStream' we get an error from 'virsh console'--- complaining about a missing controlling tty. Original source line:--- 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_ Strict.putStr-          else cmdLogger LogTrace-  (cpIn, cpOut, cpErr, cph) <- streamingProcess (shell cmdStr)-  e <--    liftIO $ runConcurrently $ Concurrently (runConduit (cpOut .| outPipe)) *>-    Concurrently (runConduit (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 . Strict.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--dbgL :: String -> B9 ()-dbgL = b9Log LogDebug--infoL :: String -> B9 ()-infoL = b9Log LogInfo--errorL :: String -> B9 ()-errorL = b9Log LogError--errorExitL :: String -> B9 a-errorExitL e = b9Log LogError e >> fail e--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--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+-- @since 0.5.65+type B9 a = Eff B9Eff a -printLevel :: LogLevel -> String-printLevel l =-  case l of-    LogNothing -> "NOTHING"-    LogError   -> " ERROR "-    LogInfo    -> " INFO  "-    LogDebug   -> " DEBUG "-    LogTrace   -> " TRACE "+-- | Definition of the B9 effect list. It encapsulates logging,+-- a reader for the "B9.B9Config" and access to the+-- current build id, the current build directory and the artifact to build.+--+-- This monad is used by the _effectful_ functions in this library.+--+-- @since 0.5.65+type B9Eff+   = '[ SelectedRemoteRepoReader, RepoCacheReader, BuildInfoReader, LoggerReader, B9ConfigReader, EnvironmentReader, ExcB9, Lift IO] -newtype B9 a = B9-  { runB9 :: StateT BuildState IO a-  } deriving (Functor, Applicative, Monad, MonadState BuildState, Alternative)+-- | A constraint that contains all effects of 'B9Eff'+--+-- @since 0.5.65+type IsB9 e = (Lifted IO e, CommandIO e, B9Eff <:: e) -instance MonadIO B9 where-  liftIO m = do-    start <- B9 $ liftIO getCurrentTime-    res <- B9 $ liftIO m-    stop <- B9 $ liftIO getCurrentTime-    let durMS = IoActionDuration (stop `diffUTCTime` start)-    modify $ \ctx -> ctx {bsProf = durMS : bsProf ctx}-    return res+-- | Execute a 'B9' effect and return an action that needs+-- the 'B9Config'.+--+-- @since 0.5.65+runB9 :: B9 a -> B9ConfigAction a+runB9 action = do+  cfg <- getB9Config+  env <- askEnvironment+  lift+    (runLift .+     errorOnException .+     runEnvironmentReader env .+     runB9ConfigReader cfg . withLogger . withBuildInfo . withRemoteRepos . withSelectedRemoteRepo $+     action)
+ src/lib/B9/BuildInfo.hs view
@@ -0,0 +1,128 @@+-- | Provide information about the current build.+--+-- This module provides build meta information like+-- build directory, build-id and build-time.+--+-- @since 0.5.65+module B9.BuildInfo+  ( getBuildId+  , getBuildDate+  , getBuildDir+  , getExecEnvType+  , withBuildInfo+  , BuildInfoReader+  ) where++import B9.B9Config+import B9.B9Error+import B9.B9Logging+import B9.Environment+import Control.Eff+import Control.Eff.Reader.Lazy+import Control.Exception (bracket)+import Control.Lens ((?~))+import Control.Monad+import Control.Monad.IO.Class+import Control.Monad.Trans.Control (MonadBaseControl, control)+import Data.Functor ()+import Data.Hashable+import Data.Time.Clock+import Data.Time.Format+import System.Directory+import System.FilePath+import Text.Printf++-- | Build meta information.+--+-- @since 0.5.65+data BuildInfo = BuildInfo+  { bsBuildId :: String+  , bsBuildDate :: String+  , bsBuildDir :: FilePath+  , bsStartTime :: UTCTime+  } deriving (Eq, Show)++-- | Type alias for a 'BuildInfo' 'Reader'+--+-- @since 0.5.65+type BuildInfoReader = Reader BuildInfo++-- | Create the build directories, generate (hash) the build-id and execute the given action.+--+-- Export the @projectRoot@ 'Environment' variable.+--+-- Unless '_keepTempDirs' is @True@ clean up the build directories after the actions+-- returns - even if the action throws a runtime exception.+--+-- @since 0.5.65+withBuildInfo ::+     ( Lifted IO e+     , MonadBaseControl IO (Eff e)+     , Member B9ConfigReader e+     , Member ExcB9 e+     , Member EnvironmentReader e+     , Member LoggerReader e+     )+  => Eff (BuildInfoReader ': e) a+  -> Eff e a+withBuildInfo action =+  withRootDir $ do+    now <- lift getCurrentTime+    let buildDate = formatTime undefined "%F-%T" now+    buildId <- generateBuildId buildDate+    withBuildDir buildId (runImpl buildId buildDate now)+  where+    withRootDir f = do+      mRoot <- _projectRoot <$> getB9Config+      root <-+        lift $+        case mRoot of+          Nothing -> getCurrentDirectory >>= canonicalizePath+          Just rootIn -> do+            createDirectoryIfMissing True rootIn+            canonicalizePath rootIn+      localB9Config (projectRoot ?~ root) (addLocalStringBinding ("projectRoot", root) f)+    generateBuildId buildDate = do+      unqiueBuildDir <- _uniqueBuildDirs <$> getB9Config+      cfgHash <- hash . show <$> getB9Config+      if unqiueBuildDir+        then return (printf "%08X-%08X" cfgHash (hash buildDate))+        else return (printf "%08X" cfgHash)+    withBuildDir buildId f = do+      root <- _projectRoot <$> getB9Config+      cfg <- getB9Config+      control $ \runInIO -> bracket (createBuildDir root) (removeBuildDir cfg) (runInIO . f)+      where+        createBuildDir root = do+          let buildDir =+                case root of+                  Just r -> r </> "BUILD-" ++ buildId+                  Nothing -> "BUILD-" ++ buildId+          createDirectoryIfMissing True buildDir+          canonicalizePath buildDir+        removeBuildDir cfg buildDir =+          when (_uniqueBuildDirs cfg && not (_keepTempDirs cfg)) $ removeDirectoryRecursive buildDir+    runImpl buildId buildDate startTime buildDir =+      let ctx = BuildInfo buildId buildDate buildDir startTime+       in runReader ctx wrappedAction+      where+        wrappedAction = do+          rootD <- getProjectRoot+          traceL (printf "Project Root Directory: %s" rootD)+          buildD <- getBuildDir+          traceL (printf "Build Directory:        %s" buildD)+          r <- action+          tsAfter <- liftIO getCurrentTime+          let duration = show (tsAfter `diffUTCTime` startTime)+          infoL (printf "DURATION: %s" duration)+          return r++-- Run the action build action+getBuildId :: Member BuildInfoReader e => Eff e String+getBuildId = bsBuildId <$> ask++getBuildDate :: Member BuildInfoReader e => Eff e String+getBuildDate = bsBuildDate <$> ask++getBuildDir :: Member BuildInfoReader e => Eff e FilePath+getBuildDir = bsBuildDir <$> ask
src/lib/B9/DiskImageBuilder.hs view
@@ -22,115 +22,135 @@   , getSelectedRepos   , pullRemoteRepos   , pullLatestImage-  ) where+  )+where -import B9.B9Config-import B9.B9Monad-import B9.Artifact.Content.StringTemplate-import B9.DiskImages-import qualified B9.PartitionTable as P-import B9.Repository-import B9.RepositoryIO-import Control.Exception-import Control.Lens ((^.))-import Control.Monad-import Control.Monad.IO.Class-import Data.Data-import Data.Function-import Data.Generics.Aliases-import Data.Generics.Schemes-import Data.List-import Data.Maybe-import System.Directory-import System.FilePath-import System.IO.B9Extras (consult, ensureDir, prettyPrintToFile)-import System.IO.Error (isDoesNotExistError)-import Text.Printf (printf)-import Text.Show.Pretty (ppShow)+import           B9.B9Config+import           B9.BuildInfo+import           B9.B9Exec+import           B9.B9Logging+import           B9.B9Error+import           B9.B9Monad+import           B9.Artifact.Content.StringTemplate+import           B9.DiskImages+import           B9.Environment+import qualified B9.PartitionTable             as P+import           B9.Repository+import           B9.RepositoryIO+import           Control.Eff+import           Control.Exception+import           Control.Lens                   ( (^.) )+import           Control.Monad+import           Control.Monad.IO.Class+import           Data.Function+import           Data.Generics.Aliases+import           Data.Generics.Schemes+import           Data.List+import           Data.Maybe+import           System.Directory+import           System.FilePath+import           System.IO.B9Extras             ( consult+                                                , ensureDir+                                                , prettyPrintToFile+                                                )+import           System.IO.Error                ( isDoesNotExistError )+import           Text.Printf                    ( printf )+import           Text.Show.Pretty               ( ppShow )  -- -- | Convert relative file paths of images, sources and mounted host directories--- -- to absolute paths relative to '_buildDirRoot'.+-- -- to absolute paths relative to '_projectRoot'. -- makeImagePathsAbsoluteToBuildDirRoot :: ImageTarget -> B9 ImageTarget -- makeImagePathsAbsoluteToBuildDirRoot img =---   getConfig >>= maybe (return img) (return . go) . _buildDirRoot+--   getConfig >>= maybe (return img) (return . go) . _projectRoot --   where --     go rootDir = everywhere mkAbs img --       where mkAbs = mkT -- | Replace $... variables inside an 'ImageTarget'-substImageTarget :: Environment -> ImageTarget -> ImageTarget-substImageTarget env = everywhere gsubst-  where-    gsubst :: Data a => a -> a-    gsubst = mkT substMountPoint `extT` substImage `extT` substImageSource `extT` substDiskTarget-    substMountPoint NotMounted = NotMounted-    substMountPoint (MountPoint x) = MountPoint (sub x)-    substImage (Image fp t fs) = Image (sub fp) t fs-    substImageSource (From n s) = From (sub n) s-    substImageSource (EmptyImage l f t s) = EmptyImage (sub l) f t s-    substImageSource s = s-    substDiskTarget (Share n t s) = Share (sub n) t s-    substDiskTarget (LiveInstallerImage name outDir resize) = LiveInstallerImage (sub name) (sub outDir) resize-    substDiskTarget s = s-    sub = subst env+substImageTarget+  :: forall e+   . (Member EnvironmentReader e, Member ExcB9 e)+  => ImageTarget+  -> Eff e ImageTarget+substImageTarget = everywhereM gsubst+ where+  gsubst :: GenericM (Eff e)+  gsubst =+    mkM substMountPoint+      `extM` substImage+      `extM` substImageSource+      `extM` substDiskTarget+  substMountPoint NotMounted     = pure NotMounted+  substMountPoint (MountPoint x) = MountPoint <$> subst x+  substImage (Image fp t fs) = Image <$> subst fp <*> pure t <*> pure fs+  substImageSource (From n s) = From <$> subst n <*> pure s+  substImageSource (EmptyImage l f t s) =+    EmptyImage <$> subst l <*> pure f <*> pure t <*> pure s+  substImageSource s = pure s+  substDiskTarget (Share n t s) = Share <$> subst n <*> pure t <*> pure s+  substDiskTarget (LiveInstallerImage name outDir resize) =+    LiveInstallerImage <$> subst name <*> subst outDir <*> pure resize+  substDiskTarget s = pure s  -- | Resolve an ImageSource to an 'Image'. The ImageSource might -- not exist, as is the case for 'EmptyImage'.-resolveImageSource :: ImageSource -> B9 Image-resolveImageSource src =-  case src of-    (EmptyImage fsLabel fsType imgType _size) ->-      let img = Image fsLabel imgType fsType-       in return (changeImageFormat imgType img)-    (SourceImage srcImg _part _resize) -> ensureAbsoluteImageDirExists srcImg-    (CopyOnWrite backingImg) -> ensureAbsoluteImageDirExists backingImg-    (From name _resize) ->-      getLatestImageByName (SharedImageName name) >>=-      maybe (errorExitL (printf "Nothing found for %s." (show (SharedImageName name)))) ensureAbsoluteImageDirExists+resolveImageSource :: IsB9 e => ImageSource -> Eff e Image+resolveImageSource src = case src of+  (EmptyImage fsLabel fsType imgType _size) ->+    let img = Image fsLabel imgType fsType+    in  return (changeImageFormat imgType img)+  (SourceImage srcImg _part _resize) -> ensureAbsoluteImageDirExists srcImg+  (CopyOnWrite backingImg          ) -> ensureAbsoluteImageDirExists backingImg+  (From name _resize) ->+    getLatestImageByName (SharedImageName name)+      >>= maybe+            (errorExitL+              (printf "Nothing found for %s." (show (SharedImageName name)))+            )+            ensureAbsoluteImageDirExists  -- | Return all valid image types sorted by preference.-preferredDestImageTypes :: ImageSource -> B9 [ImageType]-preferredDestImageTypes src =-  case src of-    (CopyOnWrite (Image _file fmt _fs)) -> return [fmt]-    (EmptyImage _label NoFileSystem fmt _size) -> return (nub [fmt, Raw, QCow2, Vmdk])-    (EmptyImage _label _fs _fmt _size) -> return [Raw]-    (SourceImage _img (Partition _) _resize) -> return [Raw]-    (SourceImage (Image _file fmt _fs) _pt resize) ->-      return (nub [fmt, Raw, QCow2, Vmdk] `intersect` allowedImageTypesForResize resize)-    (From name resize) ->-      getLatestImageByName (SharedImageName name) >>=-      maybe-        (errorExitL (printf "Nothing found for %s." (show (SharedImageName name))))-        (\sharedImg -> preferredDestImageTypes (SourceImage sharedImg NoPT resize))+preferredDestImageTypes :: IsB9 e => ImageSource -> Eff e [ImageType]+preferredDestImageTypes src = case src of+  (CopyOnWrite (Image _file fmt _fs)) -> return [fmt]+  (EmptyImage _label NoFileSystem fmt _size) ->+    return (nub [fmt, Raw, QCow2, Vmdk])+  (EmptyImage _label _fs _fmt _size                       ) -> return [Raw]+  (SourceImage _img                  (Partition _) _resize) -> return [Raw]+  (SourceImage (Image _file fmt _fs) _pt           resize ) -> return+    (nub [fmt, Raw, QCow2, Vmdk] `intersect` allowedImageTypesForResize resize)+  (From name resize) -> getLatestImageByName (SharedImageName name) >>= maybe+    (errorExitL (printf "Nothing found for %s." (show (SharedImageName name))))+    (\sharedImg -> preferredDestImageTypes (SourceImage sharedImg NoPT resize))  -- | Return all supported source 'ImageType's compatible to a 'ImageDestinaion' -- in the preferred order. preferredSourceImageTypes :: ImageDestination -> [ImageType]-preferredSourceImageTypes dest =-  case dest of-    (Share _ fmt resize) -> nub [fmt, Raw, QCow2, Vmdk] `intersect` allowedImageTypesForResize resize-    (LocalFile (Image _ fmt _) resize) -> nub [fmt, Raw, QCow2, Vmdk] `intersect` allowedImageTypesForResize resize-    Transient -> [Raw, QCow2, Vmdk]-    (LiveInstallerImage _name _repo _imgResize) -> [Raw]+preferredSourceImageTypes dest = case dest of+  (Share _ fmt resize) ->+    nub [fmt, Raw, QCow2, Vmdk] `intersect` allowedImageTypesForResize resize+  (LocalFile (Image _ fmt _) resize) ->+    nub [fmt, Raw, QCow2, Vmdk] `intersect` allowedImageTypesForResize resize+  Transient -> [Raw, QCow2, Vmdk]+  (LiveInstallerImage _name _repo _imgResize) -> [Raw]  allowedImageTypesForResize :: ImageResize -> [ImageType]-allowedImageTypesForResize r =-  case r of-    Resize _ -> [Raw]-    ShrinkToMinimum -> [Raw]-    _ -> [Raw, QCow2, Vmdk]+allowedImageTypesForResize r = case r of+  Resize _        -> [Raw]+  ShrinkToMinimum -> [Raw]+  _               -> [Raw, QCow2, Vmdk]  -- | Create the parent directories for the file that contains the 'Image'.--- If the path to the image file is relative, prepend '_buildDirRoot' from+-- If the path to the image file is relative, prepend '_projectRoot' from -- the 'B9Config'.-ensureAbsoluteImageDirExists :: Image -> B9 Image+ensureAbsoluteImageDirExists :: IsB9 e => Image -> Eff e Image ensureAbsoluteImageDirExists img@(Image path _ _) = do   b9cfg <- getConfig   let dir =         let dirRel = takeDirectory path-         in if isRelative dirRel-              then let prefix = fromMaybe "." (b9cfg ^. buildDirRoot)-                    in prefix </> dirRel+        in  if isRelative dirRel+              then+                let prefix = fromMaybe "." (b9cfg ^. projectRoot)+                in  prefix </> dirRel               else dirRel   liftIO $ do     createDirectoryIfMissing True dir@@ -140,95 +160,113 @@ -- | Create an image from an image source. The destination image must have a -- compatible image type and filesystem. The directory of the image MUST be -- present and the image file itself MUST NOT alredy exist.-materializeImageSource :: ImageSource -> Image -> B9 ()-materializeImageSource src dest =-  case src of-    (EmptyImage fsLabel fsType _imgType size) ->-      let (Image _ imgType _) = dest-       in createEmptyImage fsLabel fsType imgType size dest-    (SourceImage srcImg part resize) -> createImageFromImage srcImg part resize dest-    (CopyOnWrite backingImg) -> createCOWImage backingImg dest-    (From name resize) ->-      getLatestImageByName (SharedImageName name) >>=-      maybe-        (errorExitL (printf "Nothing found for %s." (show (SharedImageName name))))-        (\sharedImg -> materializeImageSource (SourceImage sharedImg NoPT resize) dest)+materializeImageSource :: IsB9 e => ImageSource -> Image -> Eff e ()+materializeImageSource src dest = case src of+  (EmptyImage fsLabel fsType _imgType size) ->+    let (Image _ imgType _) = dest+    in  createEmptyImage fsLabel fsType imgType size dest+  (SourceImage srcImg part resize) ->+    createImageFromImage srcImg part resize dest+  (CopyOnWrite backingImg) -> createCOWImage backingImg dest+  (From name resize) -> getLatestImageByName (SharedImageName name) >>= maybe+    (errorExitL (printf "Nothing found for %s." (show (SharedImageName name))))+    (\sharedImg ->+      materializeImageSource (SourceImage sharedImg NoPT resize) dest+    ) -createImageFromImage :: Image -> Partition -> ImageResize -> Image -> B9 ()+createImageFromImage :: IsB9 e => Image -> Partition -> ImageResize -> Image -> Eff e () createImageFromImage src part size out = do   importImage src out   extractPartition part out   resizeImage size out-  where-    extractPartition :: Partition -> Image -> B9 ()-    extractPartition NoPT _ = return ()-    extractPartition (Partition partIndex) (Image outFile Raw _) = do-      (start, len, blockSize) <- liftIO (P.getPartition partIndex outFile)-      let tmpFile = outFile <.> "extracted"-      dbgL (printf "Extracting partition %i from '%s'" partIndex outFile)-      cmd (printf "dd if='%s' of='%s' bs=%i skip=%i count=%i &> /dev/null" outFile tmpFile blockSize start len)-      cmd (printf "mv '%s' '%s'" tmpFile outFile)-    extractPartition (Partition partIndex) (Image outFile fmt _) =-      error-        (printf "Extract partition %i from image '%s': Invalid format %s" partIndex outFile (imageFileExtension fmt))+ where+  extractPartition :: IsB9 e => Partition -> Image -> Eff e ()+  extractPartition NoPT                  _                     = return ()+  extractPartition (Partition partIndex) (Image outFile Raw _) = do+    (start, len, blockSize) <- liftIO (P.getPartition partIndex outFile)+    let tmpFile = outFile <.> "extracted"+    dbgL (printf "Extracting partition %i from '%s'" partIndex outFile)+    cmd+      (printf "dd if='%s' of='%s' bs=%i skip=%i count=%i &> /dev/null"+              outFile+              tmpFile+              blockSize+              start+              len+      )+    cmd (printf "mv '%s' '%s'" tmpFile outFile)+  extractPartition (Partition partIndex) (Image outFile fmt _) = error+    (printf "Extract partition %i from image '%s': Invalid format %s"+            partIndex+            outFile+            (imageFileExtension fmt)+    )  -- | Convert some 'Image', e.g. a temporary image used during the build phase -- to the final destination.-createDestinationImage :: Image -> ImageDestination -> B9 ()-createDestinationImage buildImg dest =-  case dest of-    (Share name imgType imgResize) -> do-      resizeImage imgResize buildImg-      let shareableImg = changeImageFormat imgType buildImg-      exportAndRemoveImage buildImg shareableImg-      void (shareImage shareableImg (SharedImageName name))-    (LocalFile destImg imgResize) -> do-      resizeImage imgResize buildImg-      exportAndRemoveImage buildImg destImg-    (LiveInstallerImage name repo imgResize) -> do-      resizeImage imgResize buildImg-      let destImg = Image destFile Raw buildImgFs-          (Image _ _ buildImgFs) = buildImg-          destFile = repo </> "machines" </> name </> "disks" </> "raw" </> "0.raw"-          sizeFile = repo </> "machines" </> name </> "disks" </> "raw" </> "0.size"-          versFile = repo </> "machines" </> name </> "disks" </> "raw" </> "VERSION"-      exportAndRemoveImage buildImg destImg-      cmd-        (printf-           "echo $(qemu-img info -f raw '%s' | gawk -e '/virtual size/ {print $4}' | tr -d '(') > '%s'"-           destFile-           sizeFile)-      buildDate <- getBuildDate-      buildId <- getBuildId-      liftIO (writeFile versFile (buildId ++ "-" ++ buildDate))-    Transient -> return ()+createDestinationImage :: IsB9 e => Image -> ImageDestination -> Eff e ()+createDestinationImage buildImg dest = case dest of+  (Share name imgType imgResize) -> do+    resizeImage imgResize buildImg+    let shareableImg = changeImageFormat imgType buildImg+    exportAndRemoveImage buildImg shareableImg+    void (shareImage shareableImg (SharedImageName name))+  (LocalFile destImg imgResize) -> do+    resizeImage imgResize buildImg+    exportAndRemoveImage buildImg destImg+  (LiveInstallerImage name repo imgResize) -> do+    resizeImage imgResize buildImg+    let+      destImg = Image destFile Raw buildImgFs+      (Image _ _ buildImgFs) = buildImg+      destFile = repo </> "machines" </> name </> "disks" </> "raw" </> "0.raw"+      sizeFile =+        repo </> "machines" </> name </> "disks" </> "raw" </> "0.size"+      versFile =+        repo </> "machines" </> name </> "disks" </> "raw" </> "VERSION"+    exportAndRemoveImage buildImg destImg+    cmd+      (printf+        "echo $(qemu-img info -f raw '%s' | gawk -e '/virtual size/ {print $4}' | tr -d '(') > '%s'"+        destFile+        sizeFile+      )+    buildDate <- getBuildDate+    buildId   <- getBuildId+    liftIO (writeFile versFile (buildId ++ "-" ++ buildDate))+  Transient -> return () -createEmptyImage :: String -> FileSystem -> ImageType -> ImageSize -> Image -> B9 ()+createEmptyImage+  :: IsB9 e => String -> FileSystem -> ImageType -> ImageSize -> Image -> Eff e () createEmptyImage fsLabel fsType imgType imgSize dest@(Image _ imgType' fsType')-  | fsType /= fsType' =-    error-      (printf-         "Conflicting createEmptyImage parameters. Requested is file system %s but the destination image has %s."-         (show fsType)-         (show fsType'))-  | imgType /= imgType' =-    error-      (printf-         "Conflicting createEmptyImage parameters. Requested is image type %s but the destination image has type %s."-         (show imgType)-         (show imgType'))+  | fsType /= fsType' = error+    (printf+      "Conflicting createEmptyImage parameters. Requested is file system %s but the destination image has %s."+      (show fsType)+      (show fsType')+    )+  | imgType /= imgType' = error+    (printf+      "Conflicting createEmptyImage parameters. Requested is image type %s but the destination image has type %s."+      (show imgType)+      (show imgType')+    )   | otherwise = do     let (Image imgFile imgFmt imgFs) = dest-        qemuImgOpts = conversionOptions imgFmt+        qemuImgOpts                  = conversionOptions imgFmt     dbgL-      (printf "Creating empty raw image '%s' with size %s and options %s" imgFile (toQemuSizeOptVal imgSize) qemuImgOpts)+      (printf "Creating empty raw image '%s' with size %s and options %s"+              imgFile+              (toQemuSizeOptVal imgSize)+              qemuImgOpts+      )     cmd-      (printf-         "qemu-img create -f %s %s '%s' '%s'"-         (imageFileExtension imgFmt)-         qemuImgOpts-         imgFile-         (toQemuSizeOptVal imgSize))+      (printf "qemu-img create -f %s %s '%s' '%s'"+              (imageFileExtension imgFmt)+              qemuImgOpts+              imgFile+              (toQemuSizeOptVal imgSize)+      )     case (imgFmt, imgFs) of       (Raw, Ext4_64) -> do         let fsCmd = "mkfs.ext4"@@ -238,18 +276,27 @@         let fsCmd = "mkfs.ext4"         dbgL (printf "Creating file system %s" (show imgFs))         cmd (printf "%s -F -L '%s' -O ^64bit -q '%s'" fsCmd fsLabel imgFile)-      (it, fs) -> error (printf "Cannot create file system %s in image type %s" (show fs) (show it))+      (it, fs) -> error+        (printf "Cannot create file system %s in image type %s"+                (show fs)+                (show it)+        ) -createCOWImage :: Image -> Image -> B9 ()+createCOWImage :: IsB9 e => Image -> Image -> Eff e () createCOWImage (Image backingFile _ _) (Image imgOut imgFmt _) = do   dbgL (printf "Creating COW image '%s' backed by '%s'" imgOut backingFile)-  cmd (printf "qemu-img create -f %s -o backing_file='%s' '%s'" (imageFileExtension imgFmt) backingFile imgOut)+  cmd+    (printf "qemu-img create -f %s -o backing_file='%s' '%s'"+            (imageFileExtension imgFmt)+            backingFile+            imgOut+    )  -- | Resize an image, including the file system inside the image.-resizeImage :: ImageResize -> Image -> B9 ()+resizeImage :: IsB9 e => ImageResize -> Image -> Eff e () resizeImage KeepSize _ = return ()-resizeImage (Resize newSize) (Image img Raw fs)-  | fs == Ext4 || fs == Ext4_64 = do+resizeImage (Resize newSize) (Image img Raw fs) | fs == Ext4 || fs == Ext4_64 =+  do     let sizeOpt = toQemuSizeOptVal newSize     dbgL (printf "Resizing ext4 filesystem on raw image to %s" sizeOpt)     cmd (printf "e2fsck -p '%s'" img)@@ -258,36 +305,38 @@   let sizeOpt = toQemuSizeOptVal newSize   dbgL (printf "Resizing image to %s" sizeOpt)   cmd (printf "qemu-img resize -q '%s' %s" img sizeOpt)-resizeImage ShrinkToMinimum (Image img Raw fs)-  | fs == Ext4 || fs == Ext4_64 = do+resizeImage ShrinkToMinimum (Image img Raw fs) | fs == Ext4 || fs == Ext4_64 =+  do     dbgL "Shrinking image to minimum size"     cmd (printf "e2fsck -p '%s'" img)     cmd (printf "resize2fs -f -M '%s'" img)-resizeImage _ img = error (printf "Invalid image type or filesystem, cannot resize image: %s" (show img))+resizeImage _ img = error+  (printf "Invalid image type or filesystem, cannot resize image: %s" (show img)+  )  -- | Import a disk image from some external source into the build directory -- if necessary convert the image.-importImage :: Image -> Image -> B9 ()+importImage :: IsB9 e => Image -> Image -> Eff e () importImage imgIn imgOut@(Image imgOutPath _ _) = do   alreadyThere <- liftIO (doesFileExist imgOutPath)   unless alreadyThere (convert False imgIn imgOut)  -- | Export a disk image from the build directory; if necessary convert the image.-exportImage :: Image -> Image -> B9 ()+exportImage :: IsB9 e => Image -> Image -> Eff e () exportImage = convert False  -- | Export a disk image from the build directory; if necessary convert the image.-exportAndRemoveImage :: Image -> Image -> B9 ()+exportAndRemoveImage :: IsB9 e => Image -> Image -> Eff e () exportAndRemoveImage = convert True  -- | Convert an image in the build directory to another format and return the new image.-convertImage :: Image -> Image -> B9 ()+convertImage :: IsB9 e => Image -> Image -> Eff e () convertImage imgIn imgOut@(Image imgOutPath _ _) = do   alreadyThere <- liftIO (doesFileExist imgOutPath)   unless alreadyThere (convert True imgIn imgOut)  -- | Convert/Copy/Move images-convert :: Bool -> Image -> Image -> B9 ()+convert :: IsB9 e => Bool -> Image -> Image -> Eff e () convert doMove (Image imgIn fmtIn _) (Image imgOut fmtOut _)   | imgIn == imgOut = do     ensureDir imgOut@@ -299,36 +348,39 @@   | otherwise = do     ensureDir imgOut     dbgL-      (printf "Converting %s to %s: '%s' to '%s'" (imageFileExtension fmtIn) (imageFileExtension fmtOut) imgIn imgOut)+      (printf "Converting %s to %s: '%s' to '%s'"+              (imageFileExtension fmtIn)+              (imageFileExtension fmtOut)+              imgIn+              imgOut+      )     cmd-      (printf-         "qemu-img convert -q -f %s -O %s %s '%s' '%s'"-         (imageFileExtension fmtIn)-         (imageFileExtension fmtOut)-         (conversionOptions fmtOut)-         imgIn-         imgOut)+      (printf "qemu-img convert -q -f %s -O %s %s '%s' '%s'"+              (imageFileExtension fmtIn)+              (imageFileExtension fmtOut)+              (conversionOptions fmtOut)+              imgIn+              imgOut+      )     when doMove $ do       dbgL (printf "Removing '%s'" imgIn)       liftIO (removeFile imgIn)  conversionOptions :: ImageType -> String-conversionOptions Vmdk = " -o adapter_type=lsilogic "+conversionOptions Vmdk  = " -o adapter_type=lsilogic " conversionOptions QCow2 = " -o compat=1.1,lazy_refcounts=on "-conversionOptions _ = " "+conversionOptions _     = " "  toQemuSizeOptVal :: ImageSize -> String-toQemuSizeOptVal (ImageSize amount u) =-  show amount ++-  case u of-    GB -> "G"-    MB -> "M"-    KB -> "K"-    B -> ""+toQemuSizeOptVal (ImageSize amount u) = show amount ++ case u of+  GB -> "G"+  MB -> "M"+  KB -> "K"+  B  -> ""  -- | Publish an sharedImage made from an image and image meta data to the -- configured repository-shareImage :: Image -> SharedImageName -> B9 SharedImage+shareImage :: IsB9 e => Image -> SharedImageName -> Eff e SharedImage shareImage buildImg sname@(SharedImageName name) = do   sharedImage <- createSharedImageInCache buildImg sname   infoL (printf "SHARED '%s'" name)@@ -337,20 +389,26 @@  -- | Return a 'SharedImage' with the current build data and build id from the -- name and disk image.-getSharedImageFromImageInfo :: SharedImageName -> Image -> B9 SharedImage+getSharedImageFromImageInfo :: IsB9 e =>  SharedImageName -> Image -> Eff e SharedImage getSharedImageFromImageInfo name (Image _ imgType imgFS) = do   buildId <- getBuildId-  date <- getBuildDate-  return (SharedImage name (SharedImageDate date) (SharedImageBuildId buildId) imgType imgFS)+  date    <- getBuildDate+  return+    (SharedImage name+                 (SharedImageDate date)+                 (SharedImageBuildId buildId)+                 imgType+                 imgFS+    )  -- | Convert the disk image and serialize the base image data structure. -- If the 'maxLocalSharedImageRevisions' configuration is set to @Just n@ -- also delete all but the @n - 1@ newest images from the local cache.-createSharedImageInCache :: Image -> SharedImageName -> B9 SharedImage+createSharedImageInCache :: IsB9 e => Image -> SharedImageName -> Eff e SharedImage createSharedImageInCache img sname@(SharedImageName name) = do   dbgL (printf "CREATING SHARED IMAGE: '%s' '%s'" (ppShow img) name)   sharedImg <- getSharedImageFromImageInfo sname img-  dir <- getSharedImagesCacheDir+  dir       <- getSharedImagesCacheDir   convertImage img (changeImageDirectory dir (sharedImageImage sharedImg))   prettyPrintToFile (dir </> sharedImageFileName sharedImg) sharedImg   dbgL (printf "CREATED SHARED IMAGE IN CACHE '%s'" (ppShow sharedImg))@@ -359,44 +417,45 @@  -- | Publish the latest version of a shared image identified by name to the -- selected repository from the cache.-pushSharedImageLatestVersion :: SharedImageName -> B9 ()+pushSharedImageLatestVersion :: IsB9 e => SharedImageName -> Eff e () pushSharedImageLatestVersion name@(SharedImageName imgName) =-  getLatestSharedImageByNameFromCache name >>=-  maybe+  getLatestSharedImageByNameFromCache name >>= maybe     (errorExitL (printf "Nothing found for %s." (show imgName)))     (\sharedImage -> do-       dbgL (printf "PUSHING '%s'" (ppShow sharedImage))-       pushToSelectedRepo sharedImage-       infoL (printf "PUSHED '%s'" imgName))+      dbgL (printf "PUSHING '%s'" (ppShow sharedImage))+      pushToSelectedRepo sharedImage+      infoL (printf "PUSHED '%s'" imgName)+    )  -- | Upload a shared image from the cache to a selected remote repository-pushToSelectedRepo :: SharedImage -> B9 ()+pushToSelectedRepo :: IsB9 e => SharedImage -> Eff e () pushToSelectedRepo i = do   c <- getSharedImagesCacheDir-  r <- getSelectedRemoteRepo+  MkSelectedRemoteRepo r <- getSelectedRemoteRepo   when (isJust r) $ do     let (Image imgFile' _imgType _imgFS) = sharedImageImage i         cachedImgFile = c </> imgFile'         cachedInfoFile = c </> sharedImageFileName i         repoImgFile = sharedImagesRootDirectory </> imgFile'         repoInfoFile = sharedImagesRootDirectory </> sharedImageFileName i-    pushToRepo (fromJust r) cachedImgFile repoImgFile+    pushToRepo (fromJust r) cachedImgFile  repoImgFile     pushToRepo (fromJust r) cachedInfoFile repoInfoFile  -- | Pull metadata files from all remote repositories.-pullRemoteRepos :: B9 ()+pullRemoteRepos :: IsB9 e => Eff e () pullRemoteRepos = do   repos <- getSelectedRepos   mapM_ dl repos-  where-    dl = pullGlob sharedImagesRootDirectory (FileExtension sharedImageFileExtension)+ where+  dl =+    pullGlob sharedImagesRootDirectory (FileExtension sharedImageFileExtension)  -- | Pull the latest version of an image, either from the selected remote -- repo or from the repo that has the latest version.-pullLatestImage :: SharedImageName -> B9 (Maybe SharedImageBuildId)+pullLatestImage :: IsB9 e => SharedImageName -> Eff e (Maybe SharedImageBuildId) pullLatestImage name@(SharedImageName dbgName) = do   repos <- getSelectedRepos-  let repoPredicate Cache = False+  let repoPredicate Cache           = False       repoPredicate (Remote repoId) = repoId `elem` repoIds       repoIds = map remoteRepoRepoId repos       hasName sharedImage = name == sharedImageName sharedImage@@ -404,18 +463,24 @@   let (Remote repoId, image) = last candidates   if null candidates     then do-      errorL (printf "No shared image named '%s' on these remote repositories: '%s'" dbgName (ppShow repoIds))+      errorL+        (printf+          "No shared image named '%s' on these remote repositories: '%s'"+          dbgName+          (ppShow repoIds)+        )       return Nothing     else do       dbgL (printf "PULLING SHARED IMAGE: '%s'" (ppShow image))       cacheDir <- getSharedImagesCacheDir-      let (Image imgFile' _imgType _fs) = sharedImageImage image-          cachedImgFile = cacheDir </> imgFile'-          cachedInfoFile = cacheDir </> sharedImageFileName image-          repoImgFile = sharedImagesRootDirectory </> imgFile'-          repoInfoFile = sharedImagesRootDirectory </> sharedImageFileName image-          repo = fromJust (lookupRemoteRepo repos repoId)-      pullFromRepo repo repoImgFile cachedImgFile+      let+        (Image imgFile' _imgType _fs) = sharedImageImage image+        cachedImgFile                 = cacheDir </> imgFile'+        cachedInfoFile                = cacheDir </> sharedImageFileName image+        repoImgFile                   = sharedImagesRootDirectory </> imgFile'+        repoInfoFile = sharedImagesRootDirectory </> sharedImageFileName image+        repo                          = fromJust (lookupRemoteRepo repos repoId)+      pullFromRepo repo repoImgFile  cachedImgFile       pullFromRepo repo repoInfoFile cachedInfoFile       infoL (printf "PULLED '%s' FROM '%s'" dbgName repoId)       cleanOldSharedImageRevisionsFromCache name@@ -423,64 +488,73 @@  -- | Return the 'Image' of the latest version of a shared image named 'name' -- from the local cache.-getLatestImageByName :: SharedImageName -> B9 (Maybe Image)+getLatestImageByName :: IsB9 e => SharedImageName -> Eff e (Maybe Image) getLatestImageByName name = do   sharedImage <- getLatestSharedImageByNameFromCache name-  cacheDir <- getSharedImagesCacheDir+  cacheDir    <- getSharedImagesCacheDir   let image = changeImageDirectory cacheDir . sharedImageImage <$> sharedImage   case image of-    Just i -> dbgL (printf "USING SHARED SOURCE IMAGE '%s'" (show i))+    Just i  -> dbgL (printf "USING SHARED SOURCE IMAGE '%s'" (show i))     Nothing -> errorL (printf "SOURCE IMAGE '%s' NOT FOUND" (show name))   return image  -- | Return the latest version of a shared image named 'name' from the local cache.-getLatestSharedImageByNameFromCache :: SharedImageName -> B9 (Maybe SharedImage)+getLatestSharedImageByNameFromCache :: IsB9 e => SharedImageName -> Eff e (Maybe SharedImage) getLatestSharedImageByNameFromCache name@(SharedImageName dbgName) = do   imgs <- lookupSharedImages (== Cache) ((== name) . sharedImageName)   case reverse imgs of-    (Cache, sharedImage):_rest -> return (Just sharedImage)-    _ -> do+    (Cache, sharedImage) : _rest -> return (Just sharedImage)+    _                            -> do       errorL (printf "No image(s) named '%s' found." dbgName)       return Nothing  -- | Return a list of all existing sharedImages from cached repositories.-getSharedImages :: B9 [(Repository, [SharedImage])]+getSharedImages :: IsB9 e => Eff e [(Repository, [SharedImage])] getSharedImages = do-  reposAndFiles <- repoSearch sharedImagesRootDirectory (FileExtension sharedImageFileExtension)-  mapM (\(repo, files) -> (repo, ) . catMaybes <$> mapM consult' files) reposAndFiles-  where-    consult' f = do-      r <- liftIO (try (consult f))-      case r of-        Left (e :: SomeException) -> do-          dbgL (printf "Failed to load shared image meta-data from '%s': '%s'" (takeFileName f) (show e))-          dbgL (printf "Removing bad meta-data file '%s'" f)-          liftIO (removeFile f)-          return Nothing-        Right c -> return (Just c)+  reposAndFiles <- repoSearch sharedImagesRootDirectory+                              (FileExtension sharedImageFileExtension)+  mapM (\(repo, files) -> (repo, ) . catMaybes <$> mapM consult' files)+       reposAndFiles+ where+  consult' f = do+    r <- liftIO (try (consult f))+    case r of+      Left (e :: SomeException) -> do+        dbgL+          (printf "Failed to load shared image meta-data from '%s': '%s'"+                  (takeFileName f)+                  (show e)+          )+        dbgL (printf "Removing bad meta-data file '%s'" f)+        liftIO (removeFile f)+        return Nothing+      Right c -> return (Just c)  -- | Find shared images and the associated repos from two predicates. The result -- is the concatenated result of the sorted shared images satisfying 'imgPred'.-lookupSharedImages :: (Repository -> Bool) -> (SharedImage -> Bool) -> B9 [(Repository, SharedImage)]+lookupSharedImages+  ::IsB9 e =>  (Repository -> Bool)+  -> (SharedImage -> Bool)+  -> Eff e [(Repository, SharedImage)] lookupSharedImages repoPred imgPred = do   xs <- getSharedImages-  let rs = [(r, s) | (r, ss) <- xs, s <- ss]+  let rs           = [ (r, s) | (r, ss) <- xs, s <- ss ]       matchingRepo = filter (repoPred . fst) rs-      matchingImg = filter (imgPred . snd) matchingRepo-      sorted = sortBy (compare `on` snd) matchingImg+      matchingImg  = filter (imgPred . snd) matchingRepo+      sorted       = sortBy (compare `on` snd) matchingImg   return (mconcat (pure <$> sorted))  -- | Return either all remote repos or just the single selected repo.-getSelectedRepos :: B9 [RemoteRepo]+getSelectedRepos ::IsB9 e =>  Eff e [RemoteRepo] getSelectedRepos = do-  allRepos <- getRemoteRepos-  selectedRepo <- getSelectedRemoteRepo+  allRepos     <- getRemoteRepos+  MkSelectedRemoteRepo selectedRepo <- getSelectedRemoteRepo   let repos = maybe allRepos return selectedRepo -- 'Maybe' a repo   return repos  -- | Return the path to the sub directory in the cache that contains files of -- shared images.-getSharedImagesCacheDir :: B9 FilePath+getSharedImagesCacheDir :: IsB9 e => Eff e FilePath getSharedImagesCacheDir = do   cacheDir <- localRepoDir <$> getRepoCache   return (cacheDir </> sharedImagesRootDirectory)@@ -488,24 +562,29 @@ -- | Depending on the 'maxLocalSharedImageRevisions' 'B9Config' settings either -- do nothing or delete all but the configured number of most recent shared -- images with the given name from the local cache.-cleanOldSharedImageRevisionsFromCache :: SharedImageName -> B9 ()+cleanOldSharedImageRevisionsFromCache :: IsB9 e => SharedImageName -> Eff e () cleanOldSharedImageRevisionsFromCache sn = do   b9Cfg <- getConfig   forM_ (b9Cfg ^. maxLocalSharedImageRevisions) $ \maxRevisions -> do     toDelete <- take maxRevisions <$> newestSharedImages-    imgDir <- getSharedImagesCacheDir+    imgDir   <- getSharedImagesCacheDir     let filesToDelete = (imgDir </>) <$> (infoFiles ++ imgFiles)-        infoFiles = sharedImageFileName <$> toDelete-        imgFiles = imageFileName . sharedImageImage <$> toDelete+        infoFiles     = sharedImageFileName <$> toDelete+        imgFiles      = imageFileName . sharedImageImage <$> toDelete     unless (null filesToDelete) $ do-      traceL (printf "DELETING %d OBSOLETE REVISIONS OF: %s" (length filesToDelete) (show sn))-      mapM_ traceL filesToDelete+      traceL+        (printf "DELETING %d OBSOLETE REVISIONS OF: %s"+                (length filesToDelete)+                (show sn)+        )+      mapM_ traceL         filesToDelete       mapM_ removeIfExists filesToDelete-  where-    newestSharedImages :: B9 [SharedImage]-    newestSharedImages = reverse . map snd <$> lookupSharedImages (== Cache) ((sn ==) . sharedImageName)-    removeIfExists fileName = liftIO $ removeFile fileName `catch` handleExists-      where-        handleExists e-          | isDoesNotExistError e = return ()-          | otherwise = throwIO e+ where+  newestSharedImages :: IsB9 e => Eff e [SharedImage]+  newestSharedImages = reverse . map snd <$> lookupSharedImages+    (== Cache)+    ((sn ==) . sharedImageName)+  removeIfExists fileName = liftIO $ removeFile fileName `catch` handleExists+   where+    handleExists e | isDoesNotExistError e = return ()+                   | otherwise             = throwIO e
src/lib/B9/Environment.hs view
@@ -10,30 +10,36 @@   ( Environment()   , fromStringPairs   , addStringBinding-  , insertPositionalArguments-  , EnvironmentReaderT-  , MonadEnvironment-  , runEnvironmentReaderT+  , addLocalStringBinding+  , addPositionalArguments+  , addLocalPositionalArguments+  , EnvironmentReader+  , hasKey+  , runEnvironmentReader   , askEnvironment   , localEnvironment   , lookupOrThrow   , lookupEither   , KeyNotFound(..)   , DuplicateKey(..)-  ) where+  )+where -import           Control.Arrow               ((***))-import           Control.Exception           (Exception)-import           Control.Monad.Catch         (MonadThrow, throwM)-import           Control.Monad.Reader+import           B9.B9Error+import           Control.Arrow                  ( (***) )+import           Control.Exception              ( Exception )+import           Control.Eff                   as Eff+import           Control.Eff.Reader.Lazy       as Eff import           Control.Parallel.Strategies import           Data.Data-import           Data.HashMap.Strict         (HashMap)-import qualified Data.HashMap.Strict         as HashMap-import           Data.Maybe                  (maybe)-import           Data.Text.Lazy              (Text)-import qualified Data.Text.Lazy              as Text-import           GHC.Generics                (Generic)+import           Data.HashMap.Strict            ( HashMap )+import qualified Data.HashMap.Strict           as HashMap+import           Data.Maybe                     ( maybe+                                                , isJust+                                                )+import           Data.Text.Lazy                 ( Text )+import qualified Data.Text.Lazy                as Text+import           GHC.Generics                   ( Generic )  -- | A map of textual keys to textual values. --@@ -46,20 +52,38 @@ instance NFData Environment  instance Semigroup Environment where-  e1 <> e2 =-    MkEnvironment-      { nextPosition =-          case (nextPosition e1, nextPosition e2) of-            (0, 0) -> 0-            (0, p2) -> p2-            (p1, 0) -> p1-            _ -> error ("Overlapping positional arguments (<>): (" ++ show e1 ++ ") <> (" ++ show e2 ++ ")")-      , fromEnvironment =-          let i = HashMap.intersection (fromEnvironment e1) (fromEnvironment e2)-           in if HashMap.null i-                then fromEnvironment e1 <> fromEnvironment e2-                else error ("Overlapping entries (<>): (" ++ show e1 ++ ") <> (" ++ show e2 ++ "): (" ++ show i ++ ")")-      }+  e1 <> e2 = MkEnvironment+    { nextPosition    = case (nextPosition e1, nextPosition e2) of+                          (0 , 0 ) -> 0+                          (0 , p2) -> p2+                          (p1, 0 ) -> p1+                          _        -> error+                            (  "Overlapping positional arguments (<>): ("+                            ++ show e1+                            ++ ") <> ("+                            ++ show e2+                            ++ ")"+                            )+    , fromEnvironment = let i  = HashMap.intersection h1 h2+                            h1 = fromEnvironment e1+                            h2 = fromEnvironment e2+                        in  if HashMap.null i || all+                               (\k -> HashMap.lookup k h1 == HashMap.lookup k h2+                               )+                               (HashMap.keys i)+                            then+                              h1 <> h2+                            else+                              error+                                (  "Overlapping entries (<>): ("+                                ++ show e1+                                ++ ") <> ("+                                ++ show e2+                                ++ "): ("+                                ++ show i+                                ++ ")"+                                )+    }  instance Monoid Environment where   mempty = MkEnvironment 0 HashMap.empty@@ -71,52 +95,84 @@ -- Note that the Environment contains an index of the next position. -- -- @since 0.5.62-insertPositionalArguments :: [Text] -> Environment -> Environment-insertPositionalArguments =-  flip (foldr (\arg (MkEnvironment i e) -> MkEnvironment (i + 1) (HashMap.insert (Text.pack ("arg_" ++ show i)) arg e)))+addPositionalArguments :: [Text] -> Environment -> Environment+addPositionalArguments = flip+  (foldr+    (\arg (MkEnvironment i e) -> MkEnvironment+      (i + 1)+      (HashMap.insert (Text.pack ("arg_" ++ show i)) arg e)+    )+  ) --- | Create an 'Environment' from a list of pairs ('String's)++-- | Convenient wrapper around 'addPositionalArguments' and 'localEnvironment'. --+-- @since 0.5.65+addLocalPositionalArguments :: Member EnvironmentReader e => [String] -> Eff e a -> Eff e a+addLocalPositionalArguments extraPositional = localEnvironment appendVars+  where+    appendVars = addPositionalArguments (Text.pack <$> extraPositional)++-- | Create an 'Environment' from a list of pairs ('String's).+-- Duplicated entries are ignored.+-- -- @since 0.5.62 fromStringPairs :: [(String, String)] -> Environment-fromStringPairs = MkEnvironment 0 . HashMap.fromList . fmap (Text.pack *** Text.pack)+fromStringPairs =+  MkEnvironment 0 . HashMap.fromList . fmap (Text.pack *** Text.pack) --- | Insert a value into an 'Environment'.+-- | Insert a value into an 'Environment'. Thrown an exception when the+-- key already exists, and the value is different. -- -- @since 0.5.62-addStringBinding :: MonadThrow m => (String, String) -> Environment -> m Environment-addStringBinding (k, vNew) env =-  case HashMap.lookup (Text.pack k) (fromEnvironment env) of-    Just vOld -> throwM (MkDuplicateKey (Text.pack k) vOld (Text.pack vNew))-    Nothing ->-      pure (MkEnvironment (nextPosition env) (HashMap.insert (Text.pack k) (Text.pack vNew) (fromEnvironment env)))+addStringBinding+  :: Member ExcB9 e => (String, String) -> Environment -> Eff e Environment+addStringBinding (kStr, vNewStr) env =+  let k    = Text.pack kStr+      vNew = Text.pack vNewStr+      h    = fromEnvironment env+  in  case HashMap.lookup k h of+        Just vOld | vOld /= vNew ->+          throwSomeException (MkDuplicateKey k vOld vNew)+        _ -> pure (MkEnvironment (nextPosition env) (HashMap.insert k vNew h)) --- | A monad transformer providing a 'MonadReader' instance for 'Environment'+-- | Insert a value into an 'Environment' like 'addStringBinding',+-- but add it to the environment of the given effect, as in 'localEnvironment'. ----- @since 0.5.62-type EnvironmentReaderT m a = ReaderT Environment m a+-- @since 0.5.65+addLocalStringBinding+  :: (Member EnvironmentReader e, Member ExcB9 e)+  => (String, String) -> Eff e a-> Eff e a+addLocalStringBinding binding action =+  do e <- askEnvironment+     e' <- addStringBinding binding e+     localEnvironment (const e') action --- | A constraint on a monad @m@ ensuring (read-only) access to an 'Environment'+-- | A monad transformer providing a 'MonadReader' instance for 'Environment' -- -- @since 0.5.62-type MonadEnvironment m = MonadReader Environment m+type EnvironmentReader = Reader Environment  -- | Run a 'ReaderT' of 'Environment'. -- -- @since 0.5.62-runEnvironmentReaderT :: Environment -> EnvironmentReaderT m a -> m a-runEnvironmentReaderT = flip runReaderT+runEnvironmentReader :: Environment -> Eff (EnvironmentReader ': e) a -> Eff e a+runEnvironmentReader = runReader  -- | Get the current 'Environment' -- -- @since 0.5.62-askEnvironment :: MonadEnvironment m => m Environment+askEnvironment :: Member EnvironmentReader e => Eff e Environment askEnvironment = ask  -- | Run a computation with a modified 'Environment' -- -- @since 0.5.62-localEnvironment :: MonadEnvironment m => (Environment -> Environment) -> m a -> m a+localEnvironment+  :: Member EnvironmentReader e+  => (Environment -> Environment)+  -> Eff e a+  -> Eff e a localEnvironment = local  -- | Lookup a key for a value.@@ -125,10 +181,12 @@ -- in the 'Environment'. -- -- @Since 0.5.62-lookupOrThrow :: (MonadThrow m, MonadEnvironment m) => Text -> m Text+lookupOrThrow :: ('[ExcB9, EnvironmentReader] <:: e) => Text -> Eff e Text lookupOrThrow key = do   env <- askEnvironment-  maybe (throwM (MkKeyNotFound key env)) return (HashMap.lookup key (fromEnvironment env))+  maybe (throwSomeException (MkKeyNotFound key env))+        return+        (HashMap.lookup key (fromEnvironment env))  -- | Lookup a key for a value. --@@ -136,10 +194,12 @@ -- in the 'Environment', or 'Right' the value. -- -- @Since 0.5.62-lookupEither :: MonadEnvironment m => Text -> m (Either KeyNotFound Text)+lookupEither+  :: Member EnvironmentReader e => Text -> Eff e (Either KeyNotFound Text) lookupEither key = do   env <- askEnvironment-  (return . maybe (Left (MkKeyNotFound key env)) Right) (HashMap.lookup key (fromEnvironment env))+  (return . maybe (Left (MkKeyNotFound key env)) Right)+    (HashMap.lookup key (fromEnvironment env))  -- | An 'Exception' thrown by 'addBinding' indicating that a key already exists. --@@ -165,5 +225,13 @@ instance Show KeyNotFound where   showsPrec _ (MkKeyNotFound key env) =     let keys = unlines (Text.unpack <$> HashMap.keys (fromEnvironment env))-     in showString "Invalid template parameter: \"" .-        showString (Text.unpack key) . showString "\".\nValid variables:\n" . showString keys+    in  showString "Invalid template parameter: \""+          . showString (Text.unpack key)+          . showString "\".\nValid variables:\n"+          . showString keys++-- | A predicate that is satisfied when a key exists in the environment.+--+-- @since 0.5.64+hasKey :: Member EnvironmentReader e => Text -> Eff e Bool+hasKey k = isJust . HashMap.lookup k . fromEnvironment <$> askEnvironment
src/lib/B9/LibVirtLXC.hs view
@@ -4,81 +4,83 @@   , supportedImageTypes   , logLibVirtLXCConfig   , module X-  )-where+  ) where -import           Control.Monad.IO.Class         ( liftIO )-import           System.Directory-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.B9Config.LibVirtLXC        as X-import           System.IO.B9Extras             ( UUID()-                                                , randomUUID-                                                )+import B9.B9Config (getB9Config, libVirtLXCConfigs)+import B9.B9Config.LibVirtLXC as X+import B9.B9Exec+import B9.B9Logging+import B9.BuildInfo+import B9.DiskImages+import B9.ExecEnv+import B9.ShellScript+import Control.Eff+import Control.Lens (view)+import Control.Monad.IO.Class (liftIO)+import Data.Char (toLower)+import System.Directory+import System.FilePath+import System.IO.B9Extras (UUID(), randomUUID)+import Text.Printf (printf) -logLibVirtLXCConfig :: LibVirtLXCConfig -> B9 ()+logLibVirtLXCConfig :: CommandIO e => LibVirtLXCConfig -> Eff e () 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-    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]]--  execute (Context scriptDirHost _uuid domainFile cfg) = do-    let virsh = virshCommand cfg-    cmd $ printf "%s create '%s' --console --autodestroy" virsh domainFile+runInEnvironment ::+     forall e. (Member BuildInfoReader e, CommandIO e)+  => ExecEnv+  -> Script+  -> Eff e Bool+runInEnvironment env scriptIn =+  if emptyScript scriptIn+    then return True+    else setUp >>= execute+  where+    setUp = do+      mcfg <- view libVirtLXCConfigs <$> getB9Config+      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]]+    execute :: Context -> Eff e Bool+    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"--  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+      liftIO (doesFileExist $ scriptDirHost </> successMarkerFile)+    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 +data Context =+  Context FilePath+          UUID+          FilePath+          LibVirtLXCConfig  initScript :: String initScript = "init.sh"@@ -86,134 +88,109 @@ 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"+osArch e =+  case cpuArch (envResources e) of+    X86_64 -> "x86_64"+    I386 -> "i686"  libVirtNetwork :: Maybe String -> [String] libVirtNetwork Nothing = []-libVirtNetwork (Just n) =-  [ "<interface type='network'>"-  , "  <source network='" ++ n ++ "'/>"-  , "</interface>"-  ]+libVirtNetwork (Just n) = ["<interface type='network'>", "  <source network='" ++ n ++ "'/>", "</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'")--  fsImgSource (Image src _fmt _fs) = "<source file='" ++ src ++ "'/>"+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 ++ "'/>"  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 (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
@@ -4,6 +4,13 @@ shall interfere with each other. This is accomplished by refraining from automatic cache updates from/to remote repositories.-} module B9.Repository (initRepoCache+                     ,RepoCacheReader+                     ,getRepoCache+                     ,withRemoteRepos+                     ,withSelectedRemoteRepo+                     ,getSelectedRemoteRepo+                     ,SelectedRemoteRepoReader+                     ,SelectedRemoteRepo(..)                      ,initRemoteRepo                      ,cleanRemoteRepo                      ,remoteRepoCheckSshPrivKey@@ -12,16 +19,91 @@                      ,lookupRemoteRepo                      ,module X) where +import B9.B9Config+import B9.B9Error import Control.Monad+import Control.Eff+import Control.Eff.Reader.Lazy+import Control.Lens import Control.Monad.IO.Class #if !MIN_VERSION_base(4,8,0) import Control.Applicative #endif+import Data.Maybe import Text.Printf import System.FilePath import System.Directory import B9.B9Config.Repository as X import System.IO.B9Extras++-- | Initialize the local repository cache directory and the 'RemoteRepo's.+-- Run the given action with a 'B9Config' that contains the initialized+-- repositories in '_remoteRepos'.+--+-- @since 0.5.65+withRemoteRepos :: (Member B9ConfigReader e, Lifted IO e)+  => Eff (RepoCacheReader ': e) a -> Eff e a+withRemoteRepos f = do+      cfg <- getB9Config+      repoCache <- lift (initRepoCache (fromMaybe defaultRepositoryCache (_repositoryCache cfg)))+      remoteRepos' <- mapM (initRemoteRepo repoCache) (_remoteRepos cfg)+      let setRemoteRepos = remoteRepos .~ remoteRepos'+      localB9Config setRemoteRepos (runReader repoCache f)++-- | Alias for a 'Reader' 'Eff'ect that reads a list of 'RemoteRepo's.+--+-- @since 0.5.65+type RepoCacheReader = Reader RepoCache++-- | Ask for the 'RepoCache' initialized by 'withRemoteRepos'.+--+-- @since 0.5.65+getRepoCache :: Member RepoCacheReader e => Eff e RepoCache+getRepoCache = ask++-- | Run a 'SelectedRemoteRepoReader' with the 'SelectedRemoteRepo' selected+-- in the 'B9Config'.+--+-- If the selected repo does not exist, and exception is thrown.+--+-- @since 0.5.65+withSelectedRemoteRepo+  :: (Member B9ConfigReader e, Member ExcB9 e)+  => Eff (SelectedRemoteRepoReader ': e) a+  -> Eff e a+withSelectedRemoteRepo e = do+  remoteRepos' <- _remoteRepos <$> getB9Config+  mSelectedRepoName <- _repository <$> getB9Config+  case mSelectedRepoName of+    Nothing -> runReader (MkSelectedRemoteRepo Nothing) e+    Just selectedRepoName ->+      case lookupRemoteRepo remoteRepos' selectedRepoName of+        Nothing ->+          throwB9Error+            (printf "selected remote repo '%s' not configured, valid remote repos are: '%s'"+                    (show selectedRepoName)+                    (show remoteRepos'))+        Just r ->+          runReader (MkSelectedRemoteRepo (Just r)) e++-- | Contains the 'Just' the 'RemoteRepo' selected by the 'B9Config' value '_repository',+-- or 'Nothing' of no 'RemoteRepo' was selected in the 'B9Config'.+--+-- @since 0.5.65+newtype SelectedRemoteRepo = MkSelectedRemoteRepo { fromSelectedRemoteRepo :: Maybe RemoteRepo }++-- | Alias for a 'Reader' 'Eff'ect that reads the 'RemoteRepo'+-- selected by the 'B9Config' value '_repository'. See 'withSelectedRemoteRepo'.+--+-- @since 0.5.65+type SelectedRemoteRepoReader = Reader SelectedRemoteRepo++-- | Ask for the 'RemoteRepo'+-- selected by the 'B9Config' value '_repository'. See 'withSelectedRemoteRepo'.+--+-- @since 0.5.65+getSelectedRemoteRepo :: Member SelectedRemoteRepoReader e => Eff e SelectedRemoteRepo+getSelectedRemoteRepo = ask  -- | Initialize the local repository cache directory. initRepoCache :: MonadIO m => SystemPath -> m RepoCache
src/lib/B9/RepositoryIO.hs view
@@ -1,23 +1,30 @@ {-| Effectful functions executing shared image respository operations.     See "B9.Repository" -}-module B9.RepositoryIO (repoSearch-                       ,pushToRepo-                       ,pullFromRepo-                       ,pullGlob-                       ,Repository(..)-                       ,toRemoteRepository-                       ,FilePathGlob(..)) where+module B9.RepositoryIO+  ( repoSearch+  , pushToRepo+  , pullFromRepo+  , pullGlob+  , Repository(..)+  , toRemoteRepository+  , FilePathGlob(..)+  ) where +import B9.B9Config (getRemoteRepos)+import B9.B9Logging+import B9.B9Exec import B9.Repository-import B9.B9Monad-import System.IO.B9Extras (ensureDir)-import Data.List+import Control.Eff import Control.Monad.IO.Class+import Data.List import System.Directory import System.FilePath+import System.IO.B9Extras (ensureDir) import Text.Printf (printf) -data Repository = Cache | Remote String+data Repository+  = Cache+  | Remote String   deriving (Eq, Ord, Read, Show)  -- | Convert a `RemoteRepo` down to a mere `Repository`@@ -27,80 +34,73 @@ -- | Find files which are in 'subDir' and match 'glob' in the repository -- cache. NOTE: This operates on the repository cache, but does not enforce a -- repository cache update.-repoSearch :: FilePath -> FilePathGlob -> B9 [(Repository, [FilePath])]+repoSearch ::+     forall e. (CommandIO e, Member RepoCacheReader e)+  => FilePath+  -> FilePathGlob+  -> Eff e [(Repository, [FilePath])] repoSearch subDir glob = (:) <$> localMatches <*> remoteRepoMatches-  where remoteRepoMatches = do-          remoteRepos <- getRemoteRepos-          mapM remoteRepoSearch remoteRepos--        localMatches :: B9 (Repository, [FilePath])-        localMatches = do-          cache <- getRepoCache-          let dir = localRepoDir cache </> subDir-          files <- findGlob dir-          return (Cache, files)--        remoteRepoSearch :: RemoteRepo -> B9 (Repository, [FilePath])-        remoteRepoSearch repo = do-          cache <- getRepoCache-          let dir = remoteRepoCacheDir cache repoId </> subDir-              (RemoteRepo repoId _ _ _ _) = repo-          files <- findGlob dir-          return (Remote repoId, files)--        findGlob :: FilePath -> B9 [FilePath]-        findGlob dir = do-          traceL (printf "reading contents of directory '%s'" dir)-          ensureDir (dir ++ "/")-          files <- liftIO (getDirectoryContents dir)-          return ((dir </>) <$> filter (matchGlob glob) files)+  where+    remoteRepoMatches = do+      remoteRepos <- getRemoteRepos+      mapM remoteRepoSearch remoteRepos+    localMatches :: Eff e (Repository, [FilePath])+    localMatches = do+      cache <- getRepoCache+      let dir = localRepoDir cache </> subDir+      files <- findGlob dir+      return (Cache, files)+    remoteRepoSearch :: RemoteRepo -> Eff e (Repository, [FilePath])+    remoteRepoSearch repo = do+      cache <- getRepoCache+      let dir = remoteRepoCacheDir cache repoId </> subDir+          (RemoteRepo repoId _ _ _ _) = repo+      files <- findGlob dir+      return (Remote repoId, files)+    findGlob :: FilePath -> Eff e [FilePath]+    findGlob dir = do+      traceL (printf "reading contents of directory '%s'" dir)+      ensureDir (dir ++ "/")+      files <- liftIO (getDirectoryContents dir)+      return ((dir </>) <$> filter (matchGlob glob) files)  -- | Push a file from the cache to a remote repository-pushToRepo :: RemoteRepo -> FilePath -> FilePath -> B9 ()+pushToRepo :: (CommandIO e) => RemoteRepo -> FilePath -> FilePath -> Eff e () pushToRepo repo@(RemoteRepo repoId _ _ _ _) src dest = do   dbgL (printf "PUSHING '%s' TO REPO '%s'" (takeFileName src) repoId)   cmd (repoEnsureDirCmd repo dest)   cmd (pushCmd repo src dest)  -- | Pull a file from a remote repository to cache-pullFromRepo :: RemoteRepo -> FilePath -> FilePath -> B9 ()-pullFromRepo repo@(RemoteRepo repoId-                              rootDir-                              _key-                              (SshRemoteHost (host, _port))-                              (SshRemoteUser user)) src dest = do+pullFromRepo ::  (CommandIO e) => RemoteRepo -> FilePath -> FilePath -> Eff e ()+pullFromRepo repo@(RemoteRepo repoId rootDir _key (SshRemoteHost (host, _port)) (SshRemoteUser user)) src dest = do   dbgL (printf "PULLING '%s' FROM REPO '%s'" (takeFileName src) repoId)-  cmd (printf "rsync -rtv -e 'ssh %s' '%s@%s:%s' '%s'"-              (sshOpts repo)-              user-              host-              (rootDir </> src)-              dest)+  cmd (printf "rsync -rtv -e 'ssh %s' '%s@%s:%s' '%s'" (sshOpts repo) user host (rootDir </> src) dest)  -- | Push a file from the cache to a remote repository-pullGlob :: FilePath -> FilePathGlob -> RemoteRepo -> B9 ()-pullGlob subDir glob repo@(RemoteRepo repoId rootDir _key (SshRemoteHost (host,_port)) (SshRemoteUser user)) = do-    cache <- getRepoCache-    infoL (printf "SYNCING REPO METADATA '%s'" repoId)-    let c =-            printf-                "rsync -rtv --include '%s' --exclude '*.*' -e 'ssh %s' '%s@%s:%s/' '%s/'"-                (globToPattern glob)-                (sshOpts repo)-                user-                host-                (rootDir </> subDir)-                destDir-        destDir = repoCacheDir </> subDir-        repoCacheDir = remoteRepoCacheDir cache repoId-    ensureDir destDir-    cmd c+pullGlob ::  (CommandIO e, Member RepoCacheReader e) => FilePath -> FilePathGlob -> RemoteRepo -> Eff e ()+pullGlob subDir glob repo@(RemoteRepo repoId rootDir _key (SshRemoteHost (host, _port)) (SshRemoteUser user)) = do+  cache <- getRepoCache+  infoL (printf "SYNCING REPO METADATA '%s'" repoId)+  let c =+        printf+          "rsync -rtv --include '%s' --exclude '*.*' -e 'ssh %s' '%s@%s:%s/' '%s/'"+          (globToPattern glob)+          (sshOpts repo)+          user+          host+          (rootDir </> subDir)+          destDir+      destDir = repoCacheDir </> subDir+      repoCacheDir = remoteRepoCacheDir cache repoId+  ensureDir destDir+  cmd c  -- | Express a pattern for file paths, used when searching repositories.-newtype FilePathGlob = FileExtension String+newtype FilePathGlob =+  FileExtension String  -- * Internals- globToPattern :: FilePathGlob -> String globToPattern (FileExtension ext) = "*." ++ ext @@ -111,36 +111,26 @@ -- | A shell command string for invoking rsync to push a path to a remote host -- via ssh. pushCmd :: RemoteRepo -> FilePath -> FilePath -> String-pushCmd repo@(RemoteRepo _repoId-                         rootDir-                         _key-                         (SshRemoteHost (host, _port))-                         (SshRemoteUser user)) src dest =-  printf "rsync -rtv --inplace --ignore-existing -e 'ssh %s' '%s' '%s'"-         (sshOpts repo) src sshDest-  where sshDest = printf "%s@%s:%s/%s" user host rootDir dest :: String+pushCmd repo@(RemoteRepo _repoId rootDir _key (SshRemoteHost (host, _port)) (SshRemoteUser user)) src dest =+  printf "rsync -rtv --inplace --ignore-existing -e 'ssh %s' '%s' '%s'" (sshOpts repo) src sshDest+  where+    sshDest = printf "%s@%s:%s/%s" user host rootDir dest :: String  -- | A shell command string for invoking rsync to create the directories for a -- file push. repoEnsureDirCmd :: RemoteRepo -> FilePath -> String-repoEnsureDirCmd repo@(RemoteRepo _repoId-                                   rootDir-                                   _key-                                   (SshRemoteHost (host, _port))-                                   (SshRemoteUser user)) dest =-  printf "ssh %s %s@%s mkdir -p '%s'"-         (sshOpts repo)-         user-         host-         (rootDir </> takeDirectory dest)+repoEnsureDirCmd repo@(RemoteRepo _repoId rootDir _key (SshRemoteHost (host, _port)) (SshRemoteUser user)) dest =+  printf "ssh %s %s@%s mkdir -p '%s'" (sshOpts repo) user host (rootDir </> takeDirectory dest)  sshOpts :: RemoteRepo -> String-sshOpts (RemoteRepo _repoId-                    _rootDir-                    (SshPrivKey key)-                    (SshRemoteHost (_host, port))-                    _user) =-  unwords ["-o","StrictHostKeyChecking=no"-          ,"-o","UserKnownHostsFile=/dev/null"-          ,"-o",printf "Port=%i" port-          ,"-o","IdentityFile=" ++ key]+sshOpts (RemoteRepo _repoId _rootDir (SshPrivKey key) (SshRemoteHost (_host, port)) _user) =+  unwords+    [ "-o"+    , "StrictHostKeyChecking=no"+    , "-o"+    , "UserKnownHostsFile=/dev/null"+    , "-o"+    , printf "Port=%i" port+    , "-o"+    , "IdentityFile=" ++ key+    ]
src/lib/B9/Shake/Actions.hs view
@@ -1,15 +1,18 @@ -- | Convenient Shake 'Action's for 'B9' rules.-module B9.Shake.Actions (b9InvocationAction, buildB9File) where+module B9.Shake.Actions+  ( b9InvocationAction+  , buildB9File+  ) where -import Development.Shake-import Development.Shake.FilePath-import B9+import           B9+import           Control.Lens               ((?~))+import           Development.Shake  -- | Convert a 'B9Invocation' action into a Shake 'Action'. This is just--- an alias for 'execB9ConfigAction' since 'Action' is an instance of 'MonadIO'--- and 'execB9ConfigAction' work on any .-b9InvocationAction :: B9ConfigAction Action a -> B9ConfigOverride -> Action a-b9InvocationAction = execB9ConfigAction+-- an alias for 'runB9ConfigActionWithOverrides' since 'Action' is an instance of 'MonadIO'+-- and 'runB9ConfigActionWithOverrides' work on any .+b9InvocationAction :: B9ConfigAction a -> B9ConfigOverride -> Action a+b9InvocationAction x y = liftIO (runB9ConfigActionWithOverrides x y)  -- | An action that does the equivalent of -- @b9c build -f <b9file> -- (args !! 0) (args !! 1) ... (args !! (length args - 1))@@@ -19,4 +22,6 @@ buildB9File b9Root b9File args = do   let f = b9Root </> b9File   need [f]-  invokeB9 (localRuntimeConfig (appendPositionalArguments args . (buildDirRoot .~ Just b9Root)) (runBuildArtifacts [f]))+  liftIO+    (runB9ConfigAction+       (addLocalPositionalArguments args (localB9Config (projectRoot ?~ b9Root) (runBuildArtifacts [f]))))
src/lib/B9/Shake/SharedImageRules.hs view
@@ -4,72 +4,61 @@   ( customSharedImageAction   , needSharedImage   , enableSharedImageRules-  )-where+  ) where -import           Development.Shake-import           Development.Shake.Classes-import           Development.Shake.Rule-import qualified Data.ByteString               as ByteString-import qualified Data.ByteString.Builder       as Builder-import           Data.ByteString.Builder        ( stringUtf8 )-import qualified Data.ByteString.Lazy          as LazyByteString-import qualified Data.Binary                   as Binary-import           B9+import B9+import qualified Data.Binary as Binary+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Builder as Builder+import Data.ByteString.Builder (stringUtf8)+import qualified Data.ByteString.Lazy as LazyByteString+import Development.Shake+import Development.Shake.Classes+import Development.Shake.Rule  -- | In order to use 'needSharedImage' and 'customSharedImageAction' you need to -- call this action before using any of the aforementioned 'Rules'. enableSharedImageRules :: B9ConfigOverride -> Rules () enableSharedImageRules b9inv = addBuiltinRule noLint sharedImageIdentity go- where-  sharedImageIdentity :: BuiltinIdentity SharedImageName SharedImageBuildId-  sharedImageIdentity (SharedImageName k) (SharedImageBuildId v) =+  where+    sharedImageIdentity :: BuiltinIdentity SharedImageName SharedImageBuildId+    sharedImageIdentity (SharedImageName k) (SharedImageBuildId v) =       Just (LazyByteString.toStrict (Builder.toLazyByteString (stringUtf8 k <> stringUtf8 v)))--  go :: BuiltinRun SharedImageName SharedImageBuildId-  go nameQ mOldBIdBinary dependenciesChanged = do-    mCurrentBId <- getImgBuildId-    case mCurrentBId of-      Just currentBId ->-        let currentBIdBinary = encodeBuildId currentBId--        in  if dependenciesChanged-                 == RunDependenciesChanged-                 && mOldBIdBinary-                 == Just currentBIdBinary-              then return $ RunResult ChangedNothing currentBIdBinary currentBId-              else rebuild (Just currentBIdBinary)-      _ -> rebuild Nothing-   where-    getImgBuildId = execB9ConfigAction (runLookupLocalSharedImage nameQ) b9inv--    encodeBuildId :: SharedImageBuildId -> ByteString.ByteString-    encodeBuildId = LazyByteString.toStrict . Binary.encode--    rebuild-      :: Maybe ByteString.ByteString -> Action (RunResult SharedImageBuildId)-    rebuild mCurrentBIdBinary = do-      (_, act) <- getUserRuleOne nameQ (const Nothing) imgMatch-      act b9inv-      mNewBId <- getImgBuildId-      newBId  <- maybe-        (error-          (  "failed to get SharedImageBuildId for "-          ++ show nameQ-          ++ " in context of "-          ++ show b9inv-          )-        )-        return-        mNewBId-      let newBIdBinary = encodeBuildId newBId-      let change = if Just newBIdBinary == mCurrentBIdBinary-            then ChangedRecomputeSame-            else ChangedRecomputeDiff-      return $ RunResult change newBIdBinary newBId-     where-      imgMatch (SharedImageCustomActionRule name mkImage) =-        if name == nameQ then Just mkImage else Nothing+    go :: BuiltinRun SharedImageName SharedImageBuildId+    go nameQ mOldBIdBinary dependenciesChanged = do+      mCurrentBId <- getImgBuildId+      case mCurrentBId of+        Just currentBId ->+          let currentBIdBinary = encodeBuildId currentBId+           in if dependenciesChanged == RunDependenciesChanged && mOldBIdBinary == Just currentBIdBinary+                then return $ RunResult ChangedNothing currentBIdBinary currentBId+                else rebuild (Just currentBIdBinary)+        _ -> rebuild Nothing+      where+        getImgBuildId = liftIO (runB9ConfigActionWithOverrides (runLookupLocalSharedImage nameQ) b9inv)+        encodeBuildId :: SharedImageBuildId -> ByteString.ByteString+        encodeBuildId = LazyByteString.toStrict . Binary.encode+        rebuild :: Maybe ByteString.ByteString -> Action (RunResult SharedImageBuildId)+        rebuild mCurrentBIdBinary = do+          (_, act) <- getUserRuleOne nameQ (const Nothing) imgMatch+          act b9inv+          mNewBId <- getImgBuildId+          newBId <-+            maybe+              (error ("failed to get SharedImageBuildId for " ++ show nameQ ++ " in context of " ++ show b9inv))+              return+              mNewBId+          let newBIdBinary = encodeBuildId newBId+          let change =+                if Just newBIdBinary == mCurrentBIdBinary+                  then ChangedRecomputeSame+                  else ChangedRecomputeDiff+          return $ RunResult change newBIdBinary newBId+          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'.@@ -81,25 +70,20 @@ -- image identified by a 'SharedImageName'. -- NOTE: You must call 'enableSharedImageRules' before this action works. customSharedImageAction :: SharedImageName -> Action () -> Rules ()-customSharedImageAction b9img customAction = addUserRule-  (SharedImageCustomActionRule b9img customAction')- where-  customAction' b9inv = do-    customAction-    mCurrentBuildId <- execB9ConfigAction (runLookupLocalSharedImage b9img)-                                          b9inv-    putLoud-      (printf "Finished custom action, for %s, build-id is: %s"-              (show b9img)-              (show mCurrentBuildId)-      )-    maybe (errorSharedImageNotFound b9img) return mCurrentBuildId+customSharedImageAction b9img customAction = addUserRule (SharedImageCustomActionRule b9img customAction')+  where+    customAction' b9inv = do+      customAction+      mCurrentBuildId <- liftIO (runB9ConfigActionWithOverrides (runLookupLocalSharedImage b9img) b9inv)+      putLoud (printf "Finished custom action, for %s, build-id is: %s" (show b9img) (show mCurrentBuildId))+      maybe (errorSharedImageNotFound b9img) return mCurrentBuildId  type instance RuleResult SharedImageName = SharedImageBuildId  data SharedImageCustomActionRule =-  SharedImageCustomActionRule SharedImageName (B9ConfigOverride -> Action SharedImageBuildId)-  deriving Typeable+  SharedImageCustomActionRule SharedImageName+                              (B9ConfigOverride -> Action SharedImageBuildId)+  deriving (Typeable)  errorSharedImageNotFound :: Monad m => SharedImageName -> m a errorSharedImageNotFound = fail . printf "Error: %s not found." . show
src/lib/B9/Vm.hs view
@@ -5,17 +5,20 @@ module B9.Vm   ( VmScript(..)   , substVmScript-  ) where+  )+where  import           Control.Parallel.Strategies+import           Control.Eff import           Data.Binary import           Data.Data-import           Data.Generics.Aliases       hiding (Generic)+import           Data.Generics.Aliases   hiding ( Generic ) import           Data.Generics.Schemes import           Data.Hashable-import           GHC.Generics                (Generic)+import           GHC.Generics                   ( Generic )  import           B9.Environment+import           B9.B9Error import           B9.Artifact.Content.StringTemplate import           B9.DiskImages import           B9.ExecEnv@@ -36,18 +39,26 @@  instance NFData VmScript -substVmScript :: Environment -> VmScript -> VmScript-substVmScript env = everywhere gsubst-  where-    gsubst :: Data a => a -> a-    gsubst = mkT substMountPoint `extT` substSharedDir `extT` substScript-    substMountPoint NotMounted     = NotMounted-    substMountPoint (MountPoint x) = MountPoint (sub x)-    substSharedDir (SharedDirectory fp mp)   = SharedDirectory (sub fp) mp-    substSharedDir (SharedDirectoryRO fp mp) = SharedDirectoryRO (sub fp) mp-    substSharedDir s                         = s-    substScript (In fp s)     = In (sub fp) s-    substScript (Run fp args) = Run (sub fp) (map sub args)-    substScript (As fp s)     = As (sub fp) s-    substScript s             = s-    sub = subst env+substVmScript+  :: forall e+   . (Member EnvironmentReader e, Member ExcB9 e)+  => VmScript+  -> Eff e VmScript+substVmScript = everywhereM gsubst+ where+  gsubst :: GenericM (Eff e)+  gsubst = mkM substMountPoint `extM` substSharedDir `extM` substScript++  substMountPoint NotMounted     = pure NotMounted+  substMountPoint (MountPoint x) = MountPoint <$> subst x++  substSharedDir (SharedDirectory fp mp) =+    SharedDirectory <$> subst fp <*> pure mp+  substSharedDir (SharedDirectoryRO fp mp) =+    SharedDirectoryRO <$> subst fp <*> pure mp+  substSharedDir s = pure s++  substScript (In  fp s   ) = In <$> subst fp <*> pure s+  substScript (Run fp args) = Run <$> subst fp <*> mapM subst args+  substScript (As  fp s   ) = As <$> subst fp <*> pure s+  substScript s             = pure s
src/lib/B9/VmBuilder.hs view
@@ -1,26 +1,30 @@ {-| Effectful functions to execute and build virtual machine images using     an execution environment like e.g. libvirt-lxc. -}-module B9.VmBuilder (buildWithVm) where+module B9.VmBuilder+  ( buildWithVm+  ) where -import Data.List+import Control.Eff import Control.Monad import Control.Monad.IO.Class-import System.Directory (createDirectoryIfMissing, canonicalizePath)-import Text.Printf ( printf )+import Data.List+import System.Directory (canonicalizePath, createDirectoryIfMissing)+import Text.Printf (printf) import Text.Show.Pretty (ppShow) +import B9.Artifact.Readable+import B9.B9Config+import B9.B9Logging import B9.B9Monad-import B9.DiskImages+import B9.BuildInfo import B9.DiskImageBuilder+import B9.DiskImages import B9.ExecEnv-import B9.B9Config-import B9.Vm-import B9.Artifact.Readable-import B9.ShellScript import qualified B9.LibVirtLXC as LXC-+import B9.ShellScript+import B9.Vm -buildWithVm :: InstanceId -> [ImageTarget] -> FilePath -> VmScript -> B9 Bool+buildWithVm :: IsB9 e => InstanceId -> [ImageTarget] -> FilePath -> VmScript -> Eff e Bool buildWithVm iid imageTargets instanceDir vmScript = do   vmBuildSupportedImageTypes <- getVmScriptSupportedImageTypes vmScript   buildImages <- createBuildImages imageTargets vmBuildSupportedImageTypes@@ -28,15 +32,14 @@   when success (createDestinationImages buildImages imageTargets)   return success -getVmScriptSupportedImageTypes :: VmScript -> B9 [ImageType]-getVmScriptSupportedImageTypes NoVmScript =-  return [QCow2, Raw, Vmdk]+getVmScriptSupportedImageTypes :: IsB9 e => VmScript -> Eff e [ImageType]+getVmScriptSupportedImageTypes NoVmScript = return [QCow2, Raw, Vmdk] getVmScriptSupportedImageTypes _ = supportedImageTypes <$> getExecEnvType  supportedImageTypes :: ExecEnvType -> [ImageType] supportedImageTypes LibVirtLXC = LXC.supportedImageTypes -createBuildImages :: [ImageTarget] -> [ImageType] -> B9 [Image]+createBuildImages :: IsB9 e => [ImageTarget] -> [ImageType] -> Eff e [Image] createBuildImages imageTargets vmBuildSupportedImageTypes = do   dbgL "creating build images"   traceL (ppShow imageTargets)@@ -48,24 +51,22 @@     createBuildImage (ImageTarget dest imageSource _mnt) = do       buildDir <- getBuildDir       destTypes <- preferredDestImageTypes imageSource-      let buildImgType = head (destTypes-                               `intersect`-                               preferredSourceImageTypes dest-                               `intersect`-                               vmBuildSupportedImageTypes)+      let buildImgType =+            head (destTypes `intersect` preferredSourceImageTypes dest `intersect` vmBuildSupportedImageTypes)       srcImg <- resolveImageSource imageSource-      let buildImg = changeImageFormat buildImgType-                                       (changeImageDirectory buildDir srcImg)+      let buildImg = changeImageFormat buildImgType (changeImageDirectory buildDir srcImg)       buildImgAbsolutePath <- ensureAbsoluteImageDirExists buildImg       materializeImageSource imageSource buildImg       return buildImgAbsolutePath -runVmScript :: InstanceId-            -> [ImageTarget]-            -> [Image]-            -> FilePath-            -> VmScript-            -> B9 Bool+runVmScript ::+     forall e. IsB9 e+  => InstanceId+  -> [ImageTarget]+  -> [Image]+  -> FilePath+  -> VmScript+  -> Eff e Bool runVmScript _ _ _ _ NoVmScript = return True runVmScript (IID iid) imageTargets buildImages instanceDir vmScript = do   dbgL (printf "starting vm script with instanceDir '%s'" instanceDir)@@ -78,17 +79,14 @@     else errorL "BUILD SCRIPT FAILED"   return success   where-    setUpExecEnv :: B9 ExecEnv+    setUpExecEnv :: IsB9 e => Eff e ExecEnv     setUpExecEnv = do       let (VmScript cpu shares _) = vmScript       let mountedImages = buildImages `zip` (itImageMountPoint <$> imageTargets)       sharesAbs <- createSharedDirs instanceDir shares-      return (ExecEnv iid-                      mountedImages-                      sharesAbs-                      (Resources AutomaticRamSize 8 cpu))+      return (ExecEnv iid mountedImages sharesAbs (Resources AutomaticRamSize 8 cpu)) -createSharedDirs :: FilePath -> [SharedDirectory] -> B9 [SharedDirectory]+createSharedDirs :: IsB9 e => FilePath -> [SharedDirectory] -> Eff e [SharedDirectory] createSharedDirs instanceDir = mapM createSharedDir   where     createSharedDir (SharedDirectoryRO d m) = do@@ -100,11 +98,12 @@     createSharedDir (SharedSources mp) = do       d' <- createAndCanonicalize instanceDir       return $ SharedDirectoryRO d' mp-    createAndCanonicalize d = liftIO $ do-      createDirectoryIfMissing True d-      canonicalizePath d+    createAndCanonicalize d =+      liftIO $ do+        createDirectoryIfMissing True d+        canonicalizePath d -createDestinationImages :: [Image] -> [ImageTarget] -> B9 ()+createDestinationImages :: IsB9 e => [Image] -> [ImageTarget] -> Eff e () createDestinationImages buildImages imageTargets = do   dbgL "converting build- to output images"   let pairsToConvert = buildImages `zip` (itImageDestination `map` imageTargets)@@ -112,8 +111,8 @@   mapM_ (uncurry createDestinationImage) pairsToConvert   infoL "CONVERTED BUILD- TO OUTPUT IMAGES" -runInEnvironment :: ExecEnv -> Script -> B9 Bool+runInEnvironment :: IsB9 e => ExecEnv -> Script -> Eff e Bool runInEnvironment env script = do   t <- getExecEnvType   case t of-   LibVirtLXC -> LXC.runInEnvironment env script+    LibVirtLXC -> LXC.runInEnvironment env script
src/tests/B9/ArtifactGeneratorImplSpec.hs view
@@ -1,135 +1,102 @@-module B9.ArtifactGeneratorImplSpec (spec) where-import Test.Hspec-import Data.Text ()+module B9.ArtifactGeneratorImplSpec+  ( spec+  ) where -import B9.Artifact.Readable-import B9.Artifact.Readable.Interpreter-import B9.Environment as Env-import B9.DiskImages-import B9.ExecEnv-import B9.Vm-import B9.ShellScript+import           B9.Artifact.Readable+import           B9.Artifact.Readable.Interpreter+import           B9.DiskImages+import           B9.ExecEnv+import           B9.ShellScript+import           B9.Vm+import           Data.Text                        ()+import           Test.Hspec  spec :: Spec spec =   describe "assemble" $ do--     it "replaces '$...' variables in SourceImage Image file paths" $-       let e = CGEnv (Env.fromStringPairs [("variable","value")]) []-           src = vmImagesArtifact "" [transientCOW "${variable}" ""] NoVmScript-           expected = transientCOW "value" ""-           (Right [igEnv]) = execCGParser (parseArtifactGenerator src) e-           (Right (IG _ _ (VmImages [actual] _))) = execIGEnv igEnv+    it "replaces '$...' variables in SourceImage Image file paths" $+      let src = Let [("variable", "value")] [vmImagesArtifact "" [transientCOW "${variable}" ""] NoVmScript]+          expected = transientCOW "value" ""+          (Right [IG _ _ (VmImages [actual] _)]) = runArtifactGenerator mempty "" "" src        in actual `shouldBe` expected--     it "replaces '$...' variables in SourceImage 'From' names" $-       let e = CGEnv (Env.fromStringPairs [("variable","value")]) []-           src = vmImagesArtifact "" [transientShared "${variable}" ""] NoVmScript-           expected = transientShared "value" ""-           (Right [igEnv]) = execCGParser (parseArtifactGenerator src) e-           (Right (IG _ _ (VmImages [actual] _))) = execIGEnv igEnv+    it "replaces '$...' variables in SourceImage 'From' names" $+      let src = Let [("variable", "value")] [vmImagesArtifact "" [transientShared "${variable}" ""] NoVmScript]+          expected = transientShared "value" ""+          (Right [IG _ _ (VmImages [actual] _)]) = runArtifactGenerator mempty "" "" src        in actual `shouldBe` expected--     it "replaces '$...' variables in the name of a shared image" $-       let e = CGEnv (Env.fromStringPairs [("variable","value")]) []-           src = vmImagesArtifact "" [shareCOW "${variable}" ""] NoVmScript-           expected = shareCOW "value" ""-           (Right [igEnv]) = execCGParser (parseArtifactGenerator src) e-           (Right (IG _ _ (VmImages [actual] _))) = execIGEnv igEnv+    it "replaces '$...' variables in the name of a shared image" $+      let src = Let [("variable", "value")] [vmImagesArtifact "" [shareCOW "${variable}" ""] NoVmScript]+          expected = shareCOW "value" ""+          (Right [IG _ _ (VmImages [actual] _)]) = runArtifactGenerator mempty "" "" src        in actual `shouldBe` expected--     it "replaces '$...' variables in the name and path of a live installer image" $-       let e = CGEnv (Env.fromStringPairs [("variable","value")]) []-           src = vmImagesArtifact "" [liveInstallerCOWImage "${variable}" ""] NoVmScript-           expected = liveInstallerCOWImage "value" ""-           (Right [igEnv]) = execCGParser (parseArtifactGenerator src) e-           (Right (IG _ _ (VmImages [actual] _))) = execIGEnv igEnv+    it "replaces '$...' variables in the name and path of a live installer image" $+      let src = Let [("variable", "value")] [vmImagesArtifact "" [liveInstallerCOWImage "${variable}" ""] NoVmScript]+          expected = liveInstallerCOWImage "value" ""+          (Right [IG _ _ (VmImages [actual] _)]) = runArtifactGenerator mempty "" "" src        in actual `shouldBe` expected--     it "replaces '$...' variables in the file name of an image exported as LocalFile" $-       let e = CGEnv (Env.fromStringPairs [("variable","value")]) []-           src = vmImagesArtifact "" [localCOWImage "${variable}" ""] NoVmScript-           expected = localCOWImage "value" ""-           (Right [igEnv]) = execCGParser (parseArtifactGenerator src) e-           (Right (IG _ _ (VmImages [actual] _))) = execIGEnv igEnv+    it "replaces '$...' variables in the file name of an image exported as LocalFile" $+      let src = Let [("variable", "value")] [vmImagesArtifact "" [localCOWImage "${variable}" ""] NoVmScript]+          expected = localCOWImage "value" ""+          (Right [IG _ _ (VmImages [actual] _)]) = runArtifactGenerator mempty "" "" src        in actual `shouldBe` expected--     it "replaces '$...' variables in mount point of an image" $-       let e = CGEnv (Env.fromStringPairs [("variable","value")]) []-           src = vmImagesArtifact "" [localCOWImage "" "${variable}"] NoVmScript-           expected = localCOWImage "" "value"-           (Right [igEnv]) = execCGParser (parseArtifactGenerator src) e-           (Right (IG _ _ (VmImages [actual] _))) = execIGEnv igEnv+    it "replaces '$...' variables in mount point of an image" $+      let src = Let [("variable", "value")] [vmImagesArtifact "" [localCOWImage "" "${variable}"] NoVmScript]+          expected = localCOWImage "" "value"+          (Right [IG _ _ (VmImages [actual] _)]) = runArtifactGenerator mempty "" "" src        in actual `shouldBe` expected--     it "replaces '$...' variables in shared directory source and mount point (RO)" $-       let e = CGEnv (Env.fromStringPairs [("variable","value")]) []-           src = vmImagesArtifact "" [] (emptyScriptWithSharedDirRO "${variable}")-           expected = emptyScriptWithSharedDirRO "value"-           (Right [igEnv]) = execCGParser (parseArtifactGenerator src) e-           (Right (IG _ _ (VmImages [] actual))) = execIGEnv igEnv+    it "replaces '$...' variables in shared directory source and mount point (RO)" $+      let src = Let [("variable", "value")] [vmImagesArtifact "" [] (emptyScriptWithSharedDirRO "${variable}")]+          expected = emptyScriptWithSharedDirRO "value"+          (Right [IG _ _ (VmImages [] actual)]) = runArtifactGenerator mempty "" "" src        in actual `shouldBe` expected--     it "replaces '$...' variables in shared directory source and mount point (RW)" $-       let e = CGEnv (Env.fromStringPairs [("variable","value")]) []-           src = vmImagesArtifact "" [] (emptyScriptWithSharedDirRW "${variable}")-           expected = emptyScriptWithSharedDirRW "value"-           (Right [igEnv]) = execCGParser (parseArtifactGenerator src) e-           (Right (IG _ _ (VmImages [] actual))) = execIGEnv igEnv+    it "replaces '$...' variables in shared directory source and mount point (RW)" $+      let src = Let [("variable", "value")] [vmImagesArtifact "" [] (emptyScriptWithSharedDirRW "${variable}")]+          expected = emptyScriptWithSharedDirRW "value"+          (Right [IG _ _ (VmImages [] actual)]) = runArtifactGenerator mempty "" "" src        in actual `shouldBe` expected--     it "replaces '$...' variables in VmImages build script instructions" $-       let e = CGEnv (Env.fromStringPairs [("variable","value")]) []-           src = vmImagesArtifact "" [] (buildScript "${variable}")-           expected = buildScript "value"-           (Right [igEnv]) = execCGParser (parseArtifactGenerator src) e-           (Right (IG _ _ (VmImages [] actual))) = execIGEnv igEnv+    it "replaces '$...' variables in VmImages build script instructions" $+      let src = Let [("variable", "value")] [vmImagesArtifact "" [] (buildScript "${variable}")]+          expected = buildScript "value"+          (Right [IG _ _ (VmImages [] actual)]) = runArtifactGenerator mempty "" "" src        in actual `shouldBe` expected  transientCOW :: FilePath -> FilePath -> ImageTarget transientCOW fileName mountPoint =-  ImageTarget Transient-              (CopyOnWrite (Image fileName QCow2 Ext4))-              (MountPoint mountPoint)+  ImageTarget Transient (CopyOnWrite (Image fileName QCow2 Ext4)) (MountPoint mountPoint)  transientShared :: FilePath -> FilePath -> ImageTarget-transientShared name mountPoint =-  ImageTarget Transient-              (From name KeepSize)-              (MountPoint mountPoint)+transientShared name mountPoint = ImageTarget Transient (From name KeepSize) (MountPoint mountPoint)  shareCOW :: FilePath -> FilePath -> ImageTarget shareCOW destName mountPoint =-  ImageTarget (Share destName QCow2 KeepSize)-              (CopyOnWrite (Image "cowSource" QCow2 Ext4))-              (MountPoint mountPoint)+  ImageTarget (Share destName QCow2 KeepSize) (CopyOnWrite (Image "cowSource" QCow2 Ext4)) (MountPoint mountPoint)  liveInstallerCOWImage :: FilePath -> FilePath -> ImageTarget liveInstallerCOWImage destName mountPoint =-  ImageTarget (LiveInstallerImage destName destName KeepSize)-              (CopyOnWrite (Image "cowSource" QCow2 Ext4))-              (MountPoint mountPoint)+  ImageTarget+    (LiveInstallerImage destName destName KeepSize)+    (CopyOnWrite (Image "cowSource" QCow2 Ext4))+    (MountPoint mountPoint)  localCOWImage :: FilePath -> FilePath -> ImageTarget localCOWImage destName mountPoint =-  ImageTarget (LocalFile (Image destName QCow2 Ext4) KeepSize)-              (CopyOnWrite (Image "cowSource" QCow2 Ext4))-              (MountPoint mountPoint)+  ImageTarget+    (LocalFile (Image destName QCow2 Ext4) KeepSize)+    (CopyOnWrite (Image "cowSource" QCow2 Ext4))+    (MountPoint mountPoint)  vmImagesArtifact :: String -> [ImageTarget] -> VmScript -> ArtifactGenerator-vmImagesArtifact iid imgs script =-  Artifact (IID iid) (VmImages imgs script)+vmImagesArtifact iid imgs script = Artifact (IID iid) (VmImages imgs script)  emptyScriptWithSharedDirRO :: String -> VmScript-emptyScriptWithSharedDirRO arg =-  VmScript X86_64 [SharedDirectoryRO arg (MountPoint arg) ] (Run "" [])+emptyScriptWithSharedDirRO arg = VmScript X86_64 [SharedDirectoryRO arg (MountPoint arg)] (Run "" [])  emptyScriptWithSharedDirRW :: String -> VmScript-emptyScriptWithSharedDirRW arg =-  VmScript X86_64 [SharedDirectory arg (MountPoint arg) ] (Run "" [])+emptyScriptWithSharedDirRW arg = VmScript X86_64 [SharedDirectory arg (MountPoint arg)] (Run "" [])  buildScript :: String -> VmScript buildScript arg =-  VmScript X86_64 [SharedDirectory arg (MountPoint arg),-                   SharedDirectoryRO arg NotMounted]-                  (As arg [In arg [Run arg [arg]]])+  VmScript+    X86_64+    [SharedDirectory arg (MountPoint arg), SharedDirectoryRO arg NotMounted]+    (As arg [In arg [Run arg [arg]]])