buildr/internal/cmd/config.go
Peter e60726ef9e
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
feat: implement new and man for plugin modules
- use extracted shared libraries
2023-08-23 22:06:26 +02:00

229 lines
5.5 KiB
Go

package cmd
import (
"context"
"errors"
"flag"
"fmt"
"io/fs"
"os"
"path/filepath"
"strconv"
"time"
"code.icb4dc0.de/buildr/buildr/internal/semver"
"code.icb4dc0.de/buildr/buildr/modules/repo"
"code.icb4dc0.de/buildr/buildr/modules/state"
"code.icb4dc0.de/buildr/buildr/internal/config"
"code.icb4dc0.de/buildr/buildr/internal/ignore"
"code.icb4dc0.de/buildr/buildr/internal/profiling"
"code.icb4dc0.de/buildr/buildr/internal/vault"
"code.icb4dc0.de/buildr/buildr/modules/vcs"
)
const defaultCacheTTL = 15 * time.Minute
var ErrRepoRootNotFound = errors.New("failed to detect repo root")
type AppConfig struct {
Vault struct {
FilePath string
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 {
LogToStderr bool
CleanDirectories bool
}
}
func (c *AppConfig) CollectRepoDetails(vcsInfo any) (repoDetails repo.Repo, err error) {
repoDetails.Root = c.RepoRoot
if git, ok := vcsInfo.(*vcs.Git); ok && git.Tag != "" && semver.IsValid(git.Tag) {
if repoDetails.Version, err = semver.ParseVersion(git.Tag); err != nil {
return repoDetails, err
} else {
return repoDetails, nil
}
}
return repoDetails, nil
}
func (c *AppConfig) ParseVCSInfo() (vcsDetails any, err error) {
switch c.VCSType {
case vcs.TypeGit:
gitInfo, err := vcs.ParseGitInfo(c.RepoRoot)
if err != nil {
return nil, err
}
return gitInfo, nil
default:
return nil, fmt.Errorf("unsupported VCS type: %s", c.VCSType)
}
}
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) Ignorer() (*ignore.Ignorer, error) {
return ignore.NewIgnorer(c.RepoRoot, 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
}
}
if !filepath.IsAbs(c.BuildRDirectory) {
c.BuildRDirectory = filepath.Join(c.RepoRoot, c.BuildRDirectory)
}
if stateFilePath := c.State.FilePath; stateFilePath != "" && !filepath.IsAbs(stateFilePath) {
c.State.FilePath = filepath.Join(c.RepoRoot, stateFilePath)
}
return nil
}
//nolint:nilnil // if state is empty we don't care
func (c *AppConfig) InitState(ctx context.Context) (*state.DB, error) {
if stateFilePath := c.State.FilePath; stateFilePath != "" {
return state.NewDB(ctx, c.State.FilePath)
}
return nil, nil
}
func (c *AppConfig) InitVault() (*vault.Vault, error) {
var vaultOpts []vault.InitOption
if pw := c.Vault.Passphrase; pw != "" {
vaultOpts = append(vaultOpts, vault.WithPassphrase(pw))
} else if path := c.Vault.PassphraseFile; path != "" {
vaultOpts = append(vaultOpts, vault.WithPassphraseFile(path))
}
if filePath := c.Vault.FilePath; filePath != "" {
if !filepath.IsAbs(c.Vault.FilePath) {
filePath = filepath.Join(c.RepoRoot, filePath)
}
vaultOpts = append(vaultOpts, vault.WithLoadFromPath(filePath))
}
return vault.NewVault(vaultOpts...)
}
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,
"vault.file",
config.StringEnvOr("BUILDR_VAULT_FILE", filepath.Join(".buildr", "vault.json")),
"Relative file path to vault file",
)
flags.StringVar(
&c.Vault.Passphrase,
"vault.passphrase",
config.StringEnvOr("BUILDR_VAULT_PASSPHRASE", ""),
"Password for vault - has precedence over vault-passphrase-file",
)
flags.StringVar(
&c.Vault.PassphraseFile,
"vault.passphrase-file",
config.StringEnvOr("BUILDR_VAULT_PASSPHRASE_FILE", filepath.Join(".buildr", ".vaultpw")),
"File containing the vault passphrase",
)
flags.BoolVar(
&c.Execution.LogToStderr,
"execution.log-to-stderr",
config.EnvOr("BUILDR_EXECUTION_LOG_TO_STDERR", strconv.ParseBool, false),
"If output of commands should be piped to stdout",
)
flags.BoolVar(
&c.Execution.CleanDirectories,
"execution.clean-directories",
config.EnvOr("BUILDR_EXECUTION_CLEAN_DIRECTORIES", strconv.ParseBool, false),
"If ephemeral directories (currently out and logs) should be cleaned",
)
flags.StringVar(
&c.State.FilePath,
"state.file-path",
config.StringEnvOr("BUILDR_STATE_FILE_PATH", filepath.Join(".buildr", "state.sqlite")),
"Relative file path to state file",
)
flags.DurationVar(
&c.Cache.TTL,
"cache.ttl",
config.EnvOr("BUILDR_CACHE_TTL", time.ParseDuration, defaultCacheTTL),
"TTL for cache entries",
)
flags.Var(
&c.VCSType,
"vcs.type",
"The type of VCS to use",
)
c.Profiling.AddFlags(flags)
return flags
}
func (c *AppConfig) findRepoRoot(dir string) (string, error) {
if dir == "." || dir == "" || dir == "/" {
return "", ErrRepoRootNotFound
}
info, err := os.Stat(filepath.Join(dir, c.VCSType.Directory()))
if err != nil {
return c.findRepoRoot(filepath.Dir(dir))
}
if info.IsDir() {
return dir, nil
}
return c.findRepoRoot(filepath.Dir(dir))
}