refactor: make init logic more lazy and less verbose
All checks were successful
continuous-integration/drone/push Build is passing

- implement most services that need further initialization as lazy.Lazy
- make config structure less dynamic and more explicit - it's always .buildr for the configs
This commit is contained in:
Peter 2023-09-21 18:30:53 +02:00
parent c118822469
commit 3de7016a50
No known key found for this signature in database
25 changed files with 534 additions and 382 deletions

View file

@ -1,8 +1,3 @@
locals {
linux_archs = toset(["amd64", "arm64"])
macos_archs = toset(["arm64"])
}
build "go_build" "linux" {
for_each = local.linux_archs
@ -20,7 +15,7 @@ build "go_build" "linux" {
ldflags = [
"-w -s",
"-X 'code.icb4dc0.de/buildr/buildr/cmd.CurrentVersion=${vcs.tag == "" ? "v0.1.0" : vcs.tag}'"
"-X 'code.icb4dc0.de/buildr/buildr/cmd.CurrentVersion=${vcs.tag == "" ? local.default_version : vcs.tag}'"
]
environment = {
@ -49,7 +44,7 @@ build "go_build" "darwin" {
ldflags = [
"-w -s",
"-X 'code.icb4dc0.de/buildr/buildr/cmd.CurrentVersion=${vcs.tag == "" ? vcs.branch : vcs.tag}'"
"-X 'code.icb4dc0.de/buildr/buildr/cmd.CurrentVersion=${vcs.tag == "" ? local.default_version : vcs.tag}'"
]
environment = {

View file

@ -7,4 +7,11 @@ buildr {
url = "https://code.icb4dc0.de/api/packages/buildr/generic/golang_plugin/0.0.2/golang.wasm"
checksum = "d82de8973447e036d4cc26a76f7b6ac8bc78afc3abdf49fcc31553ba5e119e46"
}
}
locals {
linux_archs = toset(["amd64", "arm64"])
macos_archs = toset(["arm64"])
default_version = "v0.1.0"
}

View file

@ -15,23 +15,10 @@ import (
"github.com/spf13/cobra"
)
type InitLevel int
const (
InitLevelNone InitLevel = iota
InitLevelBasic
InitLevelBuildRConfig
InitLevelParseConfig
)
const (
defaultPasswordLength = 32
)
type LevelInitializer interface {
InitAt(ctx context.Context, lvl InitLevel) error
}
type VaultInitConfig struct {
PassphraseLength uint `mapstructure:"vault-pw-length"`
}
@ -50,7 +37,7 @@ func (c *VaultInitConfig) Flags() *flag.FlagSet {
}
type BuildrConfigAccessor interface {
BuildrConfig() config.Buildr
BuildrConfig() (*config.Buildr, error)
}
type AppConfigAccessor interface {
@ -62,15 +49,15 @@ type TypeRegistryAccessor interface {
}
type PluginsRepoAccessor interface {
PluginsRepo() state.Plugins
PluginsRepo() (state.Plugins, error)
}
type ModuleRepositoryAccessor interface {
Repository() *modules.Repository
Repository() (*modules.Repository, error)
}
type VaultAccessor interface {
Vault() *vault.Vault
Vault() (*vault.Vault, error)
}
type VaultModifier interface {
@ -115,6 +102,10 @@ type ManCommander interface {
DisplayModulesManual(pager Pager) error
}
type RunToolCommander interface {
RunTool(ctx context.Context, name string, args []string) error
}
type KnownTasksArgProviderFunc func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective)
func (f KnownTasksArgProviderFunc) ValidTasksArgs(
@ -146,5 +137,5 @@ func (f AppInitializerFunc) Init(ctx context.Context) error {
}
type EnvCommander interface {
PrintPath(ctx context.Context, writer io.Writer) error
PrintPath(writer io.Writer) error
}

View file

@ -7,6 +7,8 @@ import (
"fmt"
"log/slog"
"os"
"os/exec"
"strings"
"time"
"github.com/docker/docker/client"
@ -53,14 +55,20 @@ func init() {
}
}
func NewApp() *App {
func NewApp(ctx context.Context) *App {
app := &App{
rootCmd: &cobra.Command{
Use: "buildr",
SilenceUsage: true,
SilenceErrors: true,
},
buildrCfg: new(config.Buildr),
services: appServices{
registry: modules.NewRegistry(
task.Registration,
packaging.Registration,
),
dockerClient: lazy.NewLazyCtx(ctx, prepareDockerAPIClient),
},
pluginMgr: &plugins.Manager{
Downloader: plugins.GenericDownloader{
"http": plugins.HTTPDownloader{},
@ -71,20 +79,21 @@ func NewApp() *App {
loggingCfg: logging.NewConfig(),
}
app.buildrCfg = lazy.NewLazyCtx(ctx, app.prepareBuildrConfig)
app.services.vault = lazy.NewLazy(app.appCfg.InitVault)
app.services.stateDB = lazy.NewLazyCtx(ctx, app.appCfg.InitState)
app.services.stateStore = lazy.NewLazy(app.prepareStateStore)
app.services.cache = lazy.NewLazy(app.prepareCache)
app.services.repo = lazy.NewLazyCtx(ctx, app.prepareModulesRepo)
app.rootCmd.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error {
return app.Init(cmd.Context())
return app.Init()
}
app.rootCmd.PersistentPostRunE = func(*cobra.Command, []string) error {
return app.Shutdown()
}
app.initializers = map[InitLevel]AppInitializer{
InitLevelBasic: AppInitializerFunc(app.initBasic),
InitLevelBuildRConfig: AppInitializerFunc(app.initBuildRConfig),
InitLevelParseConfig: AppInitializerFunc(app.initParseConfigs),
}
manApp, err := NewManApp(assets.Manual, app)
if err != nil {
panic(err)
@ -95,17 +104,20 @@ func NewApp() *App {
return ExecuteModuleCommand(
c,
app,
TasksArgsProviderFor(app, app, c),
TasksArgsProviderFor(app, c),
WithShort(fmt.Sprintf("Run a %s by its name", modules.CategoryName(c))),
)
})...,
)
//nolint:contextcheck // there's no context to pass here
app.rootCmd.AddCommand(
ModulesCommand(app, app, manApp, app),
PluginsCommand(NewPluginApp(app.buildrCfg, app.pluginMgr, app), app),
VaultCommand(NewVaultApp(app, app, app)),
ServerCommand(NewServerApp(app, app)),
EnvCommand(NewEnvApp(app, app)),
PluginsCommand(NewPluginApp(app, app.pluginMgr, app)),
VaultCommand(NewVaultApp(app, app)),
ServerCommand(NewServerApp(app)),
EnvCommand(NewEnvApp(app)),
RunCommand(app, app, app),
VersionCommand(),
)
@ -115,48 +127,51 @@ func NewApp() *App {
return app
}
var (
_ LevelInitializer = (*App)(nil)
_ ModuleCommander = (*App)(nil)
)
var _ ModuleCommander = (*App)(nil)
type appServices struct {
registry *modules.TypeRegistry
vault *lazy.Lazy[*vault.Vault]
dockerClient *lazy.Lazy[*client.Client]
ignorer *ignore.Ignorer
stateStore *lazy.Lazy[state.Store]
cache *lazy.Lazy[state.Cache]
stateDB *lazy.Lazy[*state.DB]
diagsWriter hcl2.DiagnosticWriter
repo *lazy.Lazy[*modules.Repository]
}
type App struct {
parsingState struct {
parsingRemainder hcl2.Body
currentEvalCtx *hcl2.EvalContext
}
services struct {
registry *modules.TypeRegistry
vault *vault.Vault
dockerClient *lazy.Lazy[*client.Client]
ignorer *ignore.Ignorer
stateStore *lazy.Lazy[state.Store]
cache *lazy.Lazy[state.Cache]
stateDB *state.DB
diagsWriter hcl2.DiagnosticWriter
}
vcs *lazy.Lazy[any]
repoDetails *lazy.Lazy[repo.Repo]
buildrCfg *config.Buildr
rootCmd *cobra.Command
recorder *profiling.Recorder
initializers map[InitLevel]AppInitializer
repo *modules.Repository
pluginMgr *plugins.Manager
loggingCfg logging.Config
appCfg AppConfig
}
func (a *App) Vault() *vault.Vault {
return a.services.vault
services appServices
vcs *lazy.Lazy[any]
repoDetails *lazy.Lazy[repo.Repo]
buildrCfg *lazy.Lazy[*config.Buildr]
rootCmd *cobra.Command
recorder *profiling.Recorder
pluginMgr *plugins.Manager
loggingCfg logging.Config
appCfg AppConfig
}
func (a *App) SetVault(v *vault.Vault) {
a.services.vault = v
a.services.vault.Set(v)
}
func (a *App) PluginsRepo() state.Plugins {
return a.services.stateDB.Plugins
func (a *App) Vault() (*vault.Vault, error) {
return a.services.vault.Get()
}
func (a *App) PluginsRepo() (state.Plugins, error) {
s, err := a.services.stateDB.Get()
if err != nil {
return nil, err
}
return s.Plugins, nil
}
func (a *App) TypeRegistry() *modules.TypeRegistry {
@ -172,72 +187,24 @@ func (a *App) RunWithArgs(ctx context.Context, args ...string) error {
return a.rootCmd.ExecuteContext(ctx)
}
func (a *App) InitAt(ctx context.Context, lvl InitLevel) error {
for cl := InitLevelNone; cl <= lvl; cl++ {
if init, ok := a.initializers[cl]; ok {
if err := init.Init(ctx); err != nil {
return err
}
}
}
return nil
}
func (a *App) SetInitializer(lvl InitLevel, init AppInitializer) {
a.initializers[lvl] = init
}
func (a *App) Init(ctx context.Context) (err error) {
func (a *App) Init() (err error) {
slog.SetDefault(a.loggingCfg.Logger())
if a.recorder, err = a.appCfg.Profiling.Setup(); err != nil {
return err
}
a.services.registry = modules.NewRegistry(
task.Registration,
packaging.Registration,
)
if err := a.appCfg.InitPaths(cwd); err != nil {
return err
}
// initializing this in the background improves startup performance
a.vcs = lazy.NewFuture[any](a.appCfg.ParseVCSInfo)
a.repoDetails = lazy.NewFuture[repo.Repo](func() (repo.Repo, error) {
var vi any
if vi, err = a.vcs.Get(); err != nil {
return repo.Repo{}, err
} else {
return a.appCfg.CollectRepoDetails(vi)
}
})
if a.services.ignorer, err = a.appCfg.Ignorer(); err != nil {
return err
}
a.services.dockerClient = lazy.NewLazy(func() (*client.Client, error) {
const dockerClientCloseTimeout = 200 * time.Millisecond
cli, err := client.NewClientWithOpts(
client.WithHostFromEnv(),
client.WithVersionFromEnv(),
client.WithTLSClientConfigFromEnv(),
)
if err != nil {
return nil, err
}
dockerCtx, dockerCancel := context.WithTimeout(ctx, dockerClientCloseTimeout)
defer dockerCancel()
cli.NegotiateAPIVersion(dockerCtx)
return cli, nil
})
// init these in the background for better startup performance
a.vcs = lazy.NewFuture[any](a.appCfg.ParseVCSInfo)
a.repoDetails = lazy.NewFuture[repo.Repo](a.prepareRepoMetaInfo)
return nil
}
@ -250,19 +217,15 @@ func (a *App) AppConfig() AppConfig {
return a.appCfg
}
func (a *App) BuildrConfig() config.Buildr {
return *a.buildrCfg
func (a *App) BuildrConfig() (*config.Buildr, error) {
return a.buildrCfg.Get()
}
func (a *App) Repository() *modules.Repository {
return a.repo
func (a *App) Repository() (*modules.Repository, error) {
return a.services.repo.Get()
}
func (a *App) BootstrapModule(ctx context.Context, cat modules.Category, typeName, moduleName string) error {
if err := a.InitAt(ctx, InitLevelBuildRConfig); err != nil {
return err
}
func (a *App) BootstrapModule(_ context.Context, cat modules.Category, typeName, moduleName string) error {
mod, err := a.services.registry.Create(cat, typeName)
if err != nil {
return err
@ -276,13 +239,6 @@ func (a *App) BootstrapModule(ctx context.Context, cat modules.Category, typeNam
}
func (a *App) RunModule(ctx context.Context, cat modules.Category, name string) error {
if err := a.InitAt(ctx, InitLevelParseConfig); err != nil {
if errors.Is(err, modules.ErrNoSuchModule) {
return fmt.Errorf("%w, did you run 'buildr plugins update'", err)
}
return err
}
binPathProvider, err := containers.NewDefaultServerBinPathProvider(nil, CurrentVersion)
if err != nil {
return fmt.Errorf("failed to create server bin path provider: %w", err)
@ -296,127 +252,111 @@ func (a *App) RunModule(ctx context.Context, cat modules.Category, name string)
return containers.NewOrchestrator(ctx, cli, a.services.ignorer, binPathProvider)
})
modulesRepo, err := a.services.repo.Get()
if err != nil {
return err
}
factory := execution.NewTaskFactory(
execution.WithProvider(local.Provider(a.services.stateStore.MustGet())),
execution.WithProvider(container.Provider(orchestratorLazy, a.repo, a.services.stateStore.MustGet())),
execution.WithProvider(container.Provider(orchestratorLazy, modulesRepo, a.services.stateStore.MustGet())),
)
plan, err := execution.NewPlanFor(cat, name, a.repo, factory)
plan, err := execution.NewPlanFor(cat, name, modulesRepo, factory)
if err != nil {
return err
}
buildrCfg, err := a.buildrCfg.Get()
if err != nil {
return err
}
return plan.Execute(ctx, execution.Spec{
RepoRoot: a.appCfg.RepoRoot,
BinariesDirectory: a.buildrCfg.BinariesDirectory,
CacheDirectory: a.buildrCfg.CacheDirectory,
OutDirectory: a.buildrCfg.OutDirectory,
LogsDirectory: a.buildrCfg.LogsDirectory,
LogToStdErr: a.buildrCfg.LogToStderr,
ProjectRoot: a.appCfg.ProjectRoot,
BinariesDirectory: buildrCfg.BinariesDirectory,
CacheDirectory: buildrCfg.CacheDirectory,
OutDirectory: buildrCfg.OutDirectory,
LogsDirectory: buildrCfg.LogsDirectory,
LogToStdErr: buildrCfg.LogToStderr,
})
}
func (a *App) initBasic(ctx context.Context) (err error) {
var v *vault.Vault
if v, err = a.appCfg.InitVault(); err != nil {
func (a *App) RunTool(ctx context.Context, name string, args []string) error {
buildrCfg, err := a.buildrCfg.Get()
if err != nil {
return err
}
var pathBuilder strings.Builder
_, _ = pathBuilder.WriteString(buildrCfg.BinariesDirectory)
currentPath, ok := os.LookupEnv("PATH")
if ok {
_, _ = pathBuilder.WriteRune(os.PathListSeparator)
_, _ = pathBuilder.WriteString(currentPath)
}
if err := os.Setenv("PATH", pathBuilder.String()); err != nil {
return err
}
var db *state.DB
if db, err = a.appCfg.InitState(ctx); err != nil {
modulesRepo, err := a.services.repo.Get()
if err != nil {
return err
}
a.services.vault = v
a.services.stateDB = db
a.services.stateStore = lazy.NewLazy(func() (state.Store, error) {
return db.State, nil
})
a.services.cache = lazy.NewLazy(func() (state.Cache, error) {
return state.NewStateCache(a.appCfg.Cache.TTL, db.State), nil
})
return nil
}
func (a *App) initBuildRConfig(ctx context.Context) error {
parser := hcl.NewParser(slog.Default(), a.appCfg.BuildRFS())
slog.Debug("Reading files", "buildr_directory", a.appCfg.BuildRDirectory)
if err := parser.ReadFiles(ctx); err != nil {
slog.Error("Failed to read files", err)
return err
mod := modulesRepo.Module(modules.CategoryTool, name)
if mod == nil {
return fmt.Errorf("no tool with name %s found", name)
}
a.buildrCfg.LogToStderr = a.appCfg.Execution.LogToStderr
buildrCfg := struct {
Remainder hcl2.Body `hcl:",remain"`
*config.Buildr `hcl:"buildr,block"`
}{
Buildr: a.buildrCfg,
if bn, ok := mod.Unwrap().(modules.BinaryNamer); ok {
if binaryName, err := bn.BinaryName(ctx); err != nil {
return err
} else {
name = binaryName
}
}
a.parsingState.currentEvalCtx = hcl.BasicContext(a.services.vault)
if err := parser.Parse(a.parsingState.currentEvalCtx, &buildrCfg); err != nil {
return err
cmd := exec.CommandContext(ctx, name, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
err = cmd.Run()
if err == nil {
return nil
}
a.services.diagsWriter = parser.DiagsWriter()
if err := buildrCfg.SetupDirectories(a.appCfg.BuildRDirectory, a.appCfg.RepoRoot, a.appCfg.Execution.CleanDirectories); err != nil {
return err
}
a.pluginMgr.Init(a.services.stateDB.Plugins, buildrCfg.CacheDirectory)
if err := a.pluginMgr.Register(ctx, a.services.registry); err != nil {
return err
}
a.parsingState.parsingRemainder = buildrCfg.Remainder
return nil
}
func (a *App) initParseConfigs(ctx context.Context) (err error) {
evalCtx := hcl.FullContext(ctx, a.parsingState.currentEvalCtx, a.services.vault, a.services.cache.MustGet())
var rawSpec hcl.ModulesSpec
if diags := gohcl.DecodeBody(a.parsingState.parsingRemainder, evalCtx, &rawSpec); diags.HasErrors() {
_ = a.services.diagsWriter.WriteDiagnostics(diags)
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
slog.Default().Error(
"Failed to run tool",
slog.Bool("exited", exitErr.Exited()),
slog.Int("exit_code", exitErr.ExitCode()),
slog.Duration("duration", exitErr.UserTime()),
)
return errs.ErrAlreadyLogged
}
vcsInfo, err := a.vcs.Get()
if err != nil {
return err
}
return err
}
repoDetails, err := a.repoDetails.Get()
if err != nil {
return err
func (a *App) prepareRepoMetaInfo() (repo.Repo, error) {
if vi, err := a.vcs.Get(); err != nil {
if errors.Is(err, ErrNoVCSRootDetected) {
return repo.Repo{}, nil
}
return repo.Repo{}, err
} else {
return a.appCfg.CollectRepoDetails(vi)
}
a.repo, err = rawSpec.Repository(
evalCtx,
a.services.diagsWriter,
a.services.registry,
a.buildrCfg.OutDirectory,
hcl.WithGlobalVariable("vcs", vcsInfo),
hcl.WithGlobalVariable("repo", repoDetails),
)
if err != nil {
return err
}
return nil
}
func (a *App) persistVaultState() error {
if a.services.vault == nil {
instance, err := a.services.vault.Get()
if err != nil || instance == nil {
return nil
}
@ -431,5 +371,134 @@ func (a *App) persistVaultState() error {
encoder := json.NewEncoder(outFile)
return encoder.Encode(a.services.vault)
return encoder.Encode(instance)
}
func (a *App) prepareStateStore() (state.Store, error) {
db, err := a.services.stateDB.Get()
if err != nil {
return nil, err
}
return db.State, nil
}
func (a *App) prepareCache() (state.Cache, error) {
db, err := a.services.stateDB.Get()
if err != nil {
return nil, err
}
return state.NewStateCache(a.appCfg.Cache.TTL, db.State), nil
}
func (a *App) prepareBuildrConfig(ctx context.Context) (*config.Buildr, error) {
parser := hcl.NewParser(slog.Default(), a.appCfg.BuildRFS())
slog.Debug("Reading files", "buildr_directory", a.appCfg.BuildRDirectory)
if err := parser.ReadFiles(ctx); err != nil {
slog.Error("Failed to read files", err)
return nil, err
}
buildrCfg := struct {
Remainder hcl2.Body `hcl:",remain"`
*config.Buildr `hcl:"buildr,block"`
}{
Buildr: &config.Buildr{
LogToStderr: a.appCfg.Execution.LogToStderr,
},
}
vaultInstance, err := a.services.vault.Get()
if err != nil {
return nil, err
}
a.parsingState.currentEvalCtx = hcl.BasicContext(vaultInstance)
if err := parser.Parse(a.parsingState.currentEvalCtx, &buildrCfg); err != nil {
return nil, err
}
a.services.diagsWriter = parser.DiagsWriter()
if err := buildrCfg.SetupDirectories(a.appCfg.BuildRDirectory(), a.appCfg.ProjectRoot, a.appCfg.Execution.CleanDirectories); err != nil {
return nil, err
}
stateDB, err := a.services.stateDB.Get()
if err != nil {
return nil, err
}
a.pluginMgr.Init(stateDB.Plugins, buildrCfg.CacheDirectory)
if err := a.pluginMgr.Register(ctx, a.services.registry); err != nil {
return nil, err
}
a.parsingState.parsingRemainder = buildrCfg.Remainder
return buildrCfg.Buildr, nil
}
func (a *App) prepareModulesRepo(ctx context.Context) (*modules.Repository, error) {
vaultInstance, err := a.services.vault.Get()
if err != nil {
return nil, err
}
// call this early to ensure the parsing state is initialized
buildrCfg, err := a.buildrCfg.Get()
if err != nil {
return nil, err
}
evalCtx := hcl.FullContext(ctx, a.parsingState.currentEvalCtx, vaultInstance, a.services.cache.MustGet())
var rawSpec hcl.ModulesSpec
if diags := gohcl.DecodeBody(a.parsingState.parsingRemainder, evalCtx, &rawSpec); diags.HasErrors() {
_ = a.services.diagsWriter.WriteDiagnostics(diags)
return nil, errs.ErrAlreadyLogged
}
var repoOptions []hcl.RepositoryOption
vcsInfo, err := a.vcs.Get()
if err == nil {
repoOptions = append(repoOptions, hcl.WithGlobalVariable("vcs", vcsInfo))
}
repoDetails, err := a.repoDetails.Get()
if err != nil {
return nil, err
} else {
repoOptions = append(repoOptions, hcl.WithGlobalVariable("repo", repoDetails))
}
return rawSpec.Repository(
evalCtx,
a.services.diagsWriter,
a.services.registry,
buildrCfg.OutDirectory,
repoOptions...,
)
}
func prepareDockerAPIClient(ctx context.Context) (*client.Client, error) {
const dockerClientCloseTimeout = 200 * time.Millisecond
cli, err := client.NewClientWithOpts(
client.WithHostFromEnv(),
client.WithVersionFromEnv(),
client.WithTLSClientConfigFromEnv(),
)
if err != nil {
return nil, err
}
dockerCtx, dockerCancel := context.WithTimeout(ctx, dockerClientCloseTimeout)
defer dockerCancel()
cli.NegotiateAPIVersion(dockerCtx)
return cli, nil
}

View file

@ -1,7 +1,6 @@
package cmd
import (
"context"
"fmt"
"io"
"os"
@ -10,24 +9,21 @@ import (
var _ EnvCommander = (*EnvApp)(nil)
func NewEnvApp(initializer LevelInitializer, accessor BuildrConfigAccessor) *EnvApp {
func NewEnvApp(accessor BuildrConfigAccessor) *EnvApp {
return &EnvApp{
initializer: initializer,
executionSpecAccessor: accessor,
}
}
type EnvApp struct {
initializer LevelInitializer
executionSpecAccessor BuildrConfigAccessor
}
func (e EnvApp) PrintPath(ctx context.Context, writer io.Writer) error {
if err := e.initializer.InitAt(ctx, InitLevelBuildRConfig); err != nil {
func (e EnvApp) PrintPath(writer io.Writer) error {
execSpec, err := e.executionSpecAccessor.BuildrConfig()
if err != nil {
return err
}
execSpec := e.executionSpecAccessor.BuildrConfig()
currentPath, ok := os.LookupEnv("PATH")
if !ok {
_, err := fmt.Fprintf(writer, "PATH=%s", execSpec.BinariesDirectory)
@ -35,6 +31,6 @@ func (e EnvApp) PrintPath(ctx context.Context, writer io.Writer) error {
}
joinedPath := strings.Join([]string{execSpec.BinariesDirectory, currentPath}, string(os.PathListSeparator))
_, err := fmt.Fprintf(writer, "PATH=%s", joinedPath)
_, err = fmt.Fprintf(writer, "PATH=%s", joinedPath)
return err
}

View file

@ -4,7 +4,6 @@ import (
"context"
"io"
"code.icb4dc0.de/buildr/buildr/internal/config"
"code.icb4dc0.de/buildr/buildr/internal/plugins"
"github.com/olekukonko/tablewriter"
@ -12,22 +11,26 @@ import (
var _ PluginCommander = (*PluginApp)(nil)
func NewPluginApp(buildrCfg *config.Buildr, pluginMgr *plugins.Manager, svcAcc PluginsRepoAccessor) *PluginApp {
func NewPluginApp(buildrCfgAccessor BuildrConfigAccessor, pluginMgr *plugins.Manager, svcAcc PluginsRepoAccessor) *PluginApp {
return &PluginApp{
buildrCfg: buildrCfg,
pluginMgr: pluginMgr,
svcAcc: svcAcc,
buildrCfgAccessor: buildrCfgAccessor,
pluginMgr: pluginMgr,
svcAcc: svcAcc,
}
}
type PluginApp struct {
buildrCfg *config.Buildr
pluginMgr *plugins.Manager
svcAcc PluginsRepoAccessor
buildrCfgAccessor BuildrConfigAccessor
pluginMgr *plugins.Manager
svcAcc PluginsRepoAccessor
}
func (p PluginApp) ListPlugins(ctx context.Context, writer io.Writer) error {
knownPlugins, err := p.svcAcc.PluginsRepo().List(ctx)
repo, err := p.svcAcc.PluginsRepo()
if err != nil {
return err
}
knownPlugins, err := repo.List(ctx)
if err != nil {
return err
}
@ -42,5 +45,9 @@ func (p PluginApp) ListPlugins(ctx context.Context, writer io.Writer) error {
}
func (p PluginApp) UpdatePlugins(ctx context.Context) error {
return p.pluginMgr.UpdatePlugins(ctx, p.buildrCfg.Plugins...)
cfg, err := p.buildrCfgAccessor.BuildrConfig()
if err != nil {
return err
}
return p.pluginMgr.UpdatePlugins(ctx, cfg.Plugins...)
}

View file

@ -9,23 +9,17 @@ import (
var _ ServerCommander = (*ServerApp)(nil)
func NewServerApp(initializer LevelInitializer, accessor TypeRegistryAccessor) *ServerApp {
func NewServerApp(accessor TypeRegistryAccessor) *ServerApp {
return &ServerApp{
initializer: initializer,
accessor: accessor,
accessor: accessor,
}
}
type ServerApp struct {
initializer LevelInitializer
accessor TypeRegistryAccessor
accessor TypeRegistryAccessor
}
func (s *ServerApp) ServeAPI(ctx context.Context, cfg *rpc.GrpcConfig) error {
if err := s.initializer.InitAt(ctx, InitLevelBasic); err != nil {
return err
}
logger := slog.Default()
logger.Info("Starting gRPC server", slog.Group("grpc", slog.String("addr", cfg.Host.Address)))

View file

@ -21,29 +21,26 @@ type VaultAppServiceAccess interface {
}
func NewVaultApp(
initializer LevelInitializer,
serviceAccess VaultAppServiceAccess,
configAccessor AppConfigAccessor,
) *VaultApp {
return &VaultApp{
initializer: initializer,
serviceAccess: serviceAccess,
configAccessor: configAccessor,
}
}
type VaultApp struct {
initializer LevelInitializer
serviceAccess VaultAppServiceAccess
configAccessor AppConfigAccessor
}
func (v VaultApp) Init(ctx context.Context, initCfg VaultInitConfig) error {
if err := v.initializer.InitAt(ctx, InitLevelBasic); err != nil {
func (v VaultApp) Init(_ context.Context, initCfg VaultInitConfig) error {
vault, err := v.serviceAccess.Vault()
if err != nil {
return err
}
vault := v.serviceAccess.Vault()
if vault != nil {
slog.Default().Info("vault already initialized")
return nil
@ -73,12 +70,12 @@ func (v VaultApp) Init(ctx context.Context, initCfg VaultInitConfig) error {
return nil
}
func (v VaultApp) List(ctx context.Context, writer io.Writer) error {
if err := v.initializer.InitAt(ctx, InitLevelBasic); err != nil {
func (v VaultApp) List(_ context.Context, writer io.Writer) error {
vaultInstance, err := v.serviceAccess.Vault()
if err != nil {
return err
}
vaultInstance := v.serviceAccess.Vault()
if vaultInstance == nil {
return ErrVaultNotInitiated
}
@ -94,12 +91,11 @@ func (v VaultApp) List(ctx context.Context, writer io.Writer) error {
return nil
}
func (v VaultApp) Get(ctx context.Context, key string, writer io.Writer) error {
if err := v.initializer.InitAt(ctx, InitLevelBasic); err != nil {
func (v VaultApp) Get(_ context.Context, key string, writer io.Writer) error {
vaultInstance, err := v.serviceAccess.Vault()
if err != nil {
return err
}
vaultInstance := v.serviceAccess.Vault()
if vaultInstance == nil {
return ErrVaultNotInitiated
}
@ -117,12 +113,11 @@ func (v VaultApp) Get(ctx context.Context, key string, writer io.Writer) error {
return nil
}
func (v VaultApp) Set(ctx context.Context, key string, value []byte) error {
if err := v.initializer.InitAt(ctx, InitLevelBasic); err != nil {
func (v VaultApp) Set(_ context.Context, key string, value []byte) error {
vaultInstance, err := v.serviceAccess.Vault()
if err != nil {
return err
}
vaultInstance := v.serviceAccess.Vault()
if vaultInstance == nil {
return ErrVaultNotInitiated
}
@ -130,12 +125,11 @@ func (v VaultApp) Set(ctx context.Context, key string, value []byte) error {
return vaultInstance.SetValue(key, value)
}
func (v VaultApp) Remove(ctx context.Context, key string) error {
if err := v.initializer.InitAt(ctx, InitLevelBasic); err != nil {
func (v VaultApp) Remove(_ context.Context, key string) error {
vaultInstance, err := v.serviceAccess.Vault()
if err != nil {
return err
}
vaultInstance := v.serviceAccess.Vault()
if vaultInstance == nil {
return ErrVaultNotInitiated
}

View file

@ -1,7 +1,6 @@
package cmd
import (
"log/slog"
"strings"
"github.com/spf13/cobra"
@ -11,18 +10,17 @@ import (
)
func ModulesArgsProviderFor(
initializer LevelInitializer,
registryAcc TypeRegistryAccessor,
buildrConfigAccessor BuildrConfigAccessor,
cat modules.Category,
) KnownModulesArgProvider {
return KnownModulesArgProviderFunc(func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if err := initializer.InitAt(cmd.Context(), InitLevelBasic); err != nil {
slog.Warn("Failed to parse config", slog.String("err", err.Error()))
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
if _, err := buildrConfigAccessor.BuildrConfig(); err != nil {
return nil, cobra.ShellCompDirectiveError
}
knownModules := registryAcc.TypeRegistry().Inventory()[cat]
@ -39,18 +37,18 @@ func ModulesArgsProviderFor(
})
}
func TasksArgsProviderFor(initializer LevelInitializer, repoAcc ModuleRepositoryAccessor, cat modules.Category) KnownTasksArgProvider {
func TasksArgsProviderFor(repoAcc ModuleRepositoryAccessor, cat modules.Category) KnownTasksArgProvider {
return KnownTasksArgProviderFunc(func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if err := initializer.InitAt(cmd.Context(), InitLevelBasic); err != nil {
slog.Warn("Failed to parse config", slog.String("err", err.Error()))
return nil, cobra.ShellCompDirectiveNoFileComp
}
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
tasks := maps.Keys(repoAcc.Repository().ModulesByCategory(cat))
repo, err := repoAcc.Repository()
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
tasks := maps.Keys(repo.ModulesByCategory(cat))
filtered := make([]string, 0, len(tasks))

View file

@ -23,9 +23,16 @@ import (
"code.icb4dc0.de/buildr/buildr/modules/vcs"
)
const defaultCacheTTL = 15 * time.Minute
const (
defaultCacheTTL = 15 * time.Minute
buildrDirectoryName = ".buildr"
buildrProjectRootEnv = "BUILDR_PROJECT_ROOT"
)
var ErrRepoRootNotFound = errors.New("failed to detect repo root")
var (
ErrFailedToFindDirectory = errors.New("failed to find directory")
ErrNoVCSRootDetected = errors.New("no VCS root detected")
)
type AppConfig struct {
Vault struct {
@ -33,20 +40,24 @@ type AppConfig struct {
Passphrase string
PassphraseFile string
}
VCSType vcs.Type
BuildRDirectory string
RepoRoot string
State struct{ FilePath string }
Profiling profiling.Config
Cache struct{ TTL time.Duration }
Execution struct {
ProjectRoot string
VCSRoot string
VCSType vcs.Type
State struct{ FilePath string }
Profiling profiling.Config
Cache struct{ TTL time.Duration }
Execution struct {
LogToStderr bool
CleanDirectories bool
}
}
func (c *AppConfig) CollectRepoDetails(vcsInfo any) (repoDetails repo.Repo, err error) {
repoDetails.Root = c.RepoRoot
if c.VCSRoot == "" || vcsInfo == nil {
return repoDetails, nil
}
repoDetails.Root = c.VCSRoot
if git, ok := vcsInfo.(*vcs.Git); ok && git.Tag != "" && semver.IsValid(git.Tag) {
if repoDetails.Version, err = semver.ParseVersion(git.Tag); err != nil {
@ -60,9 +71,13 @@ func (c *AppConfig) CollectRepoDetails(vcsInfo any) (repoDetails repo.Repo, err
}
func (c *AppConfig) ParseVCSInfo() (vcsDetails any, err error) {
if c.VCSRoot == "" {
return nil, ErrNoVCSRootDetected
}
switch c.VCSType {
case vcs.TypeGit:
gitInfo, err := vcs.ParseGitInfo(c.RepoRoot)
gitInfo, err := vcs.ParseGitInfo(c.VCSRoot)
if err != nil {
return nil, err
}
@ -73,31 +88,39 @@ func (c *AppConfig) ParseVCSInfo() (vcsDetails any, err error) {
}
}
func (c *AppConfig) RepoFS() fs.FS {
return os.DirFS(c.RepoRoot)
func (c *AppConfig) BuildRFS() fs.FS {
return os.DirFS(c.BuildRDirectory())
}
func (c *AppConfig) BuildRFS() fs.FS {
return os.DirFS(c.BuildRDirectory)
func (c *AppConfig) BuildRDirectory() string {
return filepath.Join(c.ProjectRoot, buildrDirectoryName)
}
func (c *AppConfig) Ignorer() (*ignore.Ignorer, error) {
return ignore.NewIgnorer(c.RepoRoot, c.Vault.FilePath)
return ignore.NewIgnorer(c.ProjectRoot, c.Vault.FilePath)
}
func (c *AppConfig) InitPaths(from string) (err error) {
if c.RepoRoot == "" {
if c.RepoRoot, err = c.findRepoRoot(from); err != nil {
return err
}
var projectRoot string
if root, ok := os.LookupEnv(buildrProjectRootEnv); ok {
projectRoot = root
} else if root, err = c.findDirectory(from, buildrDirectoryName); err != nil {
return err
} else {
projectRoot = root
}
if !filepath.IsAbs(c.BuildRDirectory) {
c.BuildRDirectory = filepath.Join(c.RepoRoot, c.BuildRDirectory)
c.ProjectRoot = projectRoot
if d, err := c.findDirectory(c.ProjectRoot, c.VCSType.Directory()); err != nil && !errors.Is(err, ErrFailedToFindDirectory) {
return err
} else {
c.VCSRoot = d
}
if stateFilePath := c.State.FilePath; stateFilePath != "" && !filepath.IsAbs(stateFilePath) {
c.State.FilePath = filepath.Join(c.RepoRoot, stateFilePath)
c.State.FilePath = filepath.Join(c.ProjectRoot, stateFilePath)
}
return nil
@ -123,7 +146,7 @@ func (c *AppConfig) InitVault() (*vault.Vault, error) {
if filePath := c.Vault.FilePath; filePath != "" {
if !filepath.IsAbs(c.Vault.FilePath) {
filePath = filepath.Join(c.RepoRoot, filePath)
filePath = filepath.Join(c.ProjectRoot, filePath)
}
vaultOpts = append(vaultOpts, vault.WithLoadFromPath(filePath))
@ -136,19 +159,6 @@ func (c *AppConfig) Flags() *flag.FlagSet {
flags := flag.NewFlagSet("buildr", flag.ExitOnError)
c.VCSType = config.EnvOr("BUILDR_VCS_TYPE", vcs.ParseType, vcs.TypeGit)
flags.StringVar(
&c.BuildRDirectory,
"buildr.directory",
config.StringEnvOr("BUILDR_DIRECTORY", ".buildr"),
"Directory where to look for BuildR files - if relative path will be relative to repository root (depending on the VCS directory)",
)
flags.StringVar(
&c.RepoRoot,
"buildr.repo-root",
config.StringEnvOr("BUILDR_REPO_ROOT", ""),
"Repository root directory - normally auto-detected based on VCS directory",
)
flags.StringVar(
&c.Vault.FilePath,
@ -210,19 +220,19 @@ func (c *AppConfig) Flags() *flag.FlagSet {
return flags
}
func (c *AppConfig) findRepoRoot(dir string) (string, error) {
if dir == "." || dir == "" || dir == "/" {
return "", ErrRepoRootNotFound
func (c *AppConfig) findDirectory(from, dirToSearch string) (string, error) {
if from == "." || from == "" || from == "/" {
return "", fmt.Errorf("%w: %s", ErrFailedToFindDirectory, dirToSearch)
}
info, err := os.Stat(filepath.Join(dir, c.VCSType.Directory()))
info, err := os.Stat(filepath.Join(from, dirToSearch))
if err != nil {
return c.findRepoRoot(filepath.Dir(dir))
return c.findDirectory(filepath.Dir(from), dirToSearch)
}
if info.IsDir() {
return dir, nil
return from, nil
}
return c.findRepoRoot(filepath.Dir(dir))
return c.findDirectory(filepath.Dir(from), dirToSearch)
}

View file

@ -18,7 +18,7 @@ func EnvCommand(cmder EnvCommander) *cobra.Command {
Short: "Helper to configure your shell's $PATH to include local tools",
Example: `export $(buildr env path)`,
RunE: func(cmd *cobra.Command, _ []string) error {
return cmder.PrintPath(cmd.Context(), os.Stdout)
return cmder.PrintPath(os.Stdout)
},
}

View file

@ -74,7 +74,7 @@ func (m *ManConfig) Pager(ctx context.Context, title string) (Pager, error) {
func ModuleManCommand(
category modules.Category,
cmder ManCommander,
initializer LevelInitializer,
buildrConfigAccessor BuildrConfigAccessor,
argsProvider KnownModulesArgProvider,
manCfg *ManConfig,
) *cobra.Command {
@ -86,10 +86,9 @@ func ModuleManCommand(
Args: cobra.ExactArgs(1),
ValidArgsFunction: argsProvider.ValidModulesArgs,
RunE: func(cmd *cobra.Command, args []string) (err error) {
if err := initializer.InitAt(cmd.Context(), InitLevelBuildRConfig); err != nil {
if _, err = buildrConfigAccessor.BuildrConfig(); err != nil {
return err
}
p, err := manCfg.Pager(cmd.Context(), fmt.Sprintf("Manual - %s/%s", modules.CategoryName(category), args[0]))
if err != nil {
return err
@ -102,8 +101,8 @@ func ModuleManCommand(
func ManCmd(
cmder ManCommander,
initializer LevelInitializer,
registryAcc TypeRegistryAccessor,
buildrConfigAccessor BuildrConfigAccessor,
) *cobra.Command {
var manCfg ManConfig
manCmd := &cobra.Command{
@ -112,10 +111,6 @@ func ManCmd(
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := initializer.InitAt(cmd.Context(), InitLevelBuildRConfig); err != nil {
return err
}
p, err := manCfg.Pager(cmd.Context(), "Manual - modules")
if err != nil {
return err
@ -128,8 +123,8 @@ func ManCmd(
return ModuleManCommand(
c,
cmder,
initializer,
ModulesArgsProviderFor(initializer, registryAcc, c),
buildrConfigAccessor,
ModulesArgsProviderFor(registryAcc, buildrConfigAccessor, c),
&manCfg,
)
})...)

View file

@ -11,8 +11,8 @@ import (
)
func ModulesCommand(
initializer LevelInitializer,
registryAcc TypeRegistryAccessor,
buildrConfigAccessor BuildrConfigAccessor,
manCmder ManCommander,
moduleCmder BootstrapModuleCommander,
) *cobra.Command {
@ -25,14 +25,15 @@ func ModulesCommand(
}
cmd.AddCommand(
ModulesListCommand(initializer, registryAcc, os.Stdout),
ManCmd(manCmder, initializer, registryAcc),
ModulesListCommand(registryAcc, buildrConfigAccessor, os.Stdout),
ManCmd(manCmder, registryAcc, buildrConfigAccessor),
NewCmd(
slices.Map(modules.Categories(), func(c modules.Category) *cobra.Command {
return BootstrapModuleCmd(
c,
moduleCmder,
ModulesArgsProviderFor(initializer, registryAcc, c),
buildrConfigAccessor,
ModulesArgsProviderFor(registryAcc, buildrConfigAccessor, c),
WithShort(fmt.Sprintf("Bootstrap %s module", modules.CategoryName(c))),
)
})...,

View file

@ -8,8 +8,8 @@ import (
)
func ModulesListCommand(
initializer LevelInitializer,
registryAcc TypeRegistryAccessor,
buildrConfigaccessor BuildrConfigAccessor,
out io.Writer,
) *cobra.Command {
return &cobra.Command{
@ -20,7 +20,7 @@ func ModulesListCommand(
SilenceErrors: true,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if err := initializer.InitAt(cmd.Context(), InitLevelBuildRConfig); err != nil {
if _, err := buildrConfigaccessor.BuildrConfig(); err != nil {
return err
}

View file

@ -20,6 +20,7 @@ func NewCmd(subCommands ...*cobra.Command) *cobra.Command {
func BootstrapModuleCmd(
category modules.Category,
cmder BootstrapModuleCommander,
buildrConfigAccessor BuildrConfigAccessor,
argsProvider KnownModulesArgProvider,
opts ...ModuleCommandOption,
) *cobra.Command {
@ -31,6 +32,9 @@ func BootstrapModuleCmd(
ValidArgsFunction: argsProvider.ValidModulesArgs,
Args: cobra.RangeArgs(1, argsWithModuleName),
RunE: func(cmd *cobra.Command, args []string) error {
if _, err := buildrConfigAccessor.BuildrConfig(); err != nil {
return err
}
var (
typeName = args[0]
moduleName string

View file

@ -6,15 +6,12 @@ import (
"github.com/spf13/cobra"
)
func PluginListCommand(cmder PluginCommander, initializer LevelInitializer) *cobra.Command {
func PluginListCommand(cmder PluginCommander) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "List plugins",
Aliases: []string{"ls", "dir"},
RunE: func(cmd *cobra.Command, args []string) error {
if err := initializer.InitAt(cmd.Context(), InitLevelBasic); err != nil {
return err
}
return cmder.ListPlugins(cmd.Context(), os.Stdout)
},
}
@ -22,14 +19,11 @@ func PluginListCommand(cmder PluginCommander, initializer LevelInitializer) *cob
return cmd
}
func PluginUpdateCommand(cmder PluginCommander, initializer LevelInitializer) *cobra.Command {
func PluginUpdateCommand(cmder PluginCommander) *cobra.Command {
cmd := &cobra.Command{
Use: "update",
Short: "Update plugins",
RunE: func(cmd *cobra.Command, args []string) error {
if err := initializer.InitAt(cmd.Context(), InitLevelBuildRConfig); err != nil {
return err
}
return cmder.UpdatePlugins(cmd.Context())
},
}
@ -37,7 +31,7 @@ func PluginUpdateCommand(cmder PluginCommander, initializer LevelInitializer) *c
return cmd
}
func PluginsCommand(cmder PluginCommander, initializer LevelInitializer) *cobra.Command {
func PluginsCommand(cmder PluginCommander) *cobra.Command {
cmd := &cobra.Command{
Use: "plugins",
Short: "Manage plugins",
@ -45,7 +39,7 @@ func PluginsCommand(cmder PluginCommander, initializer LevelInitializer) *cobra.
SilenceErrors: true,
}
cmd.AddCommand(PluginListCommand(cmder, initializer), PluginUpdateCommand(cmder, initializer))
cmd.AddCommand(PluginListCommand(cmder), PluginUpdateCommand(cmder))
return cmd
}

68
internal/cmd/run.go Normal file
View file

@ -0,0 +1,68 @@
package cmd
import (
"strings"
"github.com/spf13/cobra"
"code.icb4dc0.de/buildr/buildr/modules"
)
func RunCommand(
mra ModuleRepositoryAccessor,
moduleExecCmder ModuleCommander,
toolExecCmder RunToolCommander,
) *cobra.Command {
return &cobra.Command{
Use: "run",
Aliases: []string{"exec"},
Short: "Run a tool - tool will be installed on demand",
SilenceUsage: true,
SilenceErrors: true,
Args: cobra.MinimumNArgs(1),
ValidArgsFunction: completeTools(mra),
RunE: func(cmd *cobra.Command, args []string) error {
if err := moduleExecCmder.RunModule(cmd.Context(), modules.CategoryTool, args[0]); err != nil {
return err
}
return toolExecCmder.RunTool(cmd.Context(), args[0], args[1:])
},
}
}
//nolint:lll // return type is what it is
func completeTools(mra ModuleRepositoryAccessor) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) > 0 {
return nil, cobra.ShellCompDirectiveDefault
}
toComplete = strings.ToLower(toComplete)
repo, err := mra.Repository()
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
tools := repo.ModulesByCategory(modules.CategoryTool)
completions := make([]string, 0, len(tools))
for _, t := range tools {
toolName := t.Name()
if bn, ok := t.Unwrap().(modules.BinaryNamer); ok {
if name, err := bn.BinaryName(cmd.Context()); err != nil {
continue
} else {
toolName = name
}
}
if strings.HasPrefix(toolName, toComplete) {
completions = append(completions, toolName)
}
}
return completions, cobra.ShellCompDirectiveNoFileComp
}
}

View file

@ -36,7 +36,7 @@ func (c Buildr) PluginURLs() (pluginUrls []*url.URL, err error) {
return pluginUrls, nil
}
func (c *Buildr) SetupDirectories(buildRDir, repoRoot string, cleanDirectories bool) error {
func (c *Buildr) SetupDirectories(buildRDir, projectRoot string, cleanDirectories bool) error {
if ucd, err := os.UserCacheDir(); err != nil {
return err
} else {
@ -50,7 +50,7 @@ func (c *Buildr) SetupDirectories(buildRDir, repoRoot string, cleanDirectories b
if c.BinariesDirectory == "" {
c.BinariesDirectory = filepath.Join(buildRDir, "bin")
} else if !filepath.IsAbs(c.BinariesDirectory) {
c.BinariesDirectory = filepath.Join(repoRoot, c.BinariesDirectory)
c.BinariesDirectory = filepath.Join(projectRoot, c.BinariesDirectory)
}
if err := createCleanDir(c.BinariesDirectory, false); err != nil {
@ -60,7 +60,7 @@ func (c *Buildr) SetupDirectories(buildRDir, repoRoot string, cleanDirectories b
if c.OutDirectory == "" {
c.OutDirectory = filepath.Join(buildRDir, "out")
} else if !filepath.IsAbs(c.OutDirectory) {
c.OutDirectory = filepath.Join(repoRoot, c.OutDirectory)
c.OutDirectory = filepath.Join(projectRoot, c.OutDirectory)
}
if err := createCleanDir(c.OutDirectory, cleanDirectories); err != nil {
@ -70,7 +70,7 @@ func (c *Buildr) SetupDirectories(buildRDir, repoRoot string, cleanDirectories b
if c.LogsDirectory == "" {
c.LogsDirectory = filepath.Join(buildRDir, "logs")
} else if !filepath.IsAbs(c.LogsDirectory) {
c.LogsDirectory = filepath.Join(repoRoot, c.LogsDirectory)
c.LogsDirectory = filepath.Join(projectRoot, c.LogsDirectory)
}
if err := createCleanDir(c.LogsDirectory, cleanDirectories); err != nil {

View file

@ -1,7 +1,6 @@
package containers
import (
"compress/bzip2"
"context"
"errors"
"fmt"
@ -10,6 +9,8 @@ import (
"path/filepath"
"runtime"
"github.com/klauspost/pgzip"
"code.icb4dc0.de/buildr/buildr/internal/ioutils"
)
@ -70,7 +71,7 @@ func (p DefaultServerBinPathProvider) ServerBinPath(ctx context.Context, platfor
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
fmt.Sprintf("https://code.icb4dc0.de/buildr/buildr/releases/download/%s/buildr_%s_%s.bz2", p.currentVersion, platform.OS, platform.Arch),
fmt.Sprintf("https://code.icb4dc0.de/buildr/buildr/releases/download/%s/buildr_%s_%s.gz", p.currentVersion, platform.OS, platform.Arch),
nil,
)
if err != nil {
@ -99,7 +100,12 @@ func (p DefaultServerBinPathProvider) ServerBinPath(ctx context.Context, platfor
err = errors.Join(err, outFile.Close())
}()
_, err = ioutils.CopyWithPooledBuffer(outFile, bzip2.NewReader(resp.Body))
gzipReader, err := pgzip.NewReader(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to create gzip reader: %w", err)
}
_, err = ioutils.CopyWithPooledBuffer(outFile, gzipReader)
return binaryPath, err
}

View file

@ -35,7 +35,7 @@ func (s *BuildRContainerSpec) containerSpec(buildrExecutableName string) (contai
ExposedPorts: []string{"3000/tcp"},
Env: map[string]string{
"BUILDR_GRPC_SERVE_ADDRESS": "0.0.0.0:3000",
"BUILDR_REPO_ROOT": containerRepoRoot,
"BUILDR_PROJECT_ROOT": containerRepoRoot,
"BUILDR_STATE_FILE_PATH": "/tmp/buildr.state",
},
Entrypoint: []string{

View file

@ -7,7 +7,7 @@ import (
)
type Spec struct {
RepoRoot string
ProjectRoot string
BinariesDirectory string
CacheDirectory string
OutDirectory string

View file

@ -114,7 +114,7 @@ func (c *containerTask) doExecute(ctx context.Context, spec execution.Spec) (err
inputMappings := c.moduleWithMeta.InputMappings()
if len(inputMappings) == 0 {
inputMappings[spec.RepoRoot] = "."
inputMappings[spec.ProjectRoot] = "."
}
containerSpec := c.moduleWithMeta.ContainerSpec()
@ -124,7 +124,7 @@ func (c *containerTask) doExecute(ctx context.Context, spec execution.Spec) (err
Image: containerSpec.Image,
User: containerSpec.User,
Privileged: containerSpec.Privileged,
RepoRoot: spec.RepoRoot,
RepoRoot: spec.ProjectRoot,
Content: inputMappings,
BinariesDir: spec.BinariesDirectory,
ExtraBinaries: extraBinaries,

View file

@ -62,7 +62,7 @@ func (t *localTask) doExecute(ctx context.Context, spec execution.Spec) error {
ctx,
t.module,
spec,
spec.RepoRoot,
spec.ProjectRoot,
t.module.OutDir(),
)
if err != nil {
@ -111,7 +111,7 @@ func (t *localTask) executeIsolated(ctx context.Context, spec execution.Spec) er
}
if !filepath.IsAbs(outDir) {
outDir = filepath.Join(spec.RepoRoot, outDir)
outDir = filepath.Join(spec.ProjectRoot, outDir)
}
ufs := storage.NewUnionFS(outDir)

View file

@ -1,7 +1,9 @@
package lazy
import (
"context"
"errors"
"fmt"
"io"
"reflect"
"sync"
@ -19,30 +21,51 @@ func NewFuture[T any](provider func() (T, error)) *Lazy[T] {
return l
}
func NewLazyCtx[T any](ctx context.Context, provider func(ctx context.Context) (T, error)) *Lazy[T] {
return &Lazy[T]{
baseContext: ctx,
provider: provider,
}
}
func NewLazy[T any](provider func() (T, error)) *Lazy[T] {
return &Lazy[T]{
provider: provider,
baseContext: context.Background(),
provider: func(ctx context.Context) (T, error) {
return provider()
},
}
}
type Lazy[T any] struct {
out T
err error
provider func() (T, error)
once sync.Once
out T
err error
baseContext context.Context
provider func(ctx context.Context) (T, error)
once sync.Once
}
func (l *Lazy[T]) Get() (v T, e error) {
if l == nil {
return v, ErrEmptyLazy
var t T
return v, fmt.Errorf("%w type: %T", ErrEmptyLazy, t)
}
l.once.Do(func() {
l.out, l.err = l.provider()
ctx, cancel := context.WithCancel(l.baseContext)
defer cancel()
l.out, l.err = l.provider(ctx)
})
return l.out, l.err
}
func (l *Lazy[T]) Set(t T) {
l.provider = func(ctx context.Context) (T, error) {
return t, nil
}
l.out = t
}
func (l *Lazy[T]) MustGet() (v T) {
var err error
v, err = l.Get()

View file

@ -21,7 +21,7 @@ func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
app := cmd.NewApp()
app := cmd.NewApp(ctx)
if err := app.Run(ctx); err != nil {
if !errors.Is(err, errs.ErrAlreadyLogged) {