Skip to content

Commit

Permalink
inputs: refactor build input resolver
Browse files Browse the repository at this point in the history
- move glob path, gitfile, gosource file resolvers to an own resolve/
  package, the resolvers return absolute paths
- the buildinput interface is removed, it is not needed, we can use the
  File struct instead, all resolved build inputs are files
- buildinputs are not converted from the config struct and intermediate
  struct anymore, the App struct references the cfg.BuildInput struct
  directly
  • Loading branch information
fho committed Nov 4, 2018
1 parent 5bddeca commit c68fe5b
Show file tree
Hide file tree
Showing 19 changed files with 485 additions and 503 deletions.
207 changes: 144 additions & 63 deletions app.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package baur

import (
"fmt"
"path"
"path/filepath"
"sort"
Expand All @@ -12,6 +13,10 @@ import (
"github.com/simplesurance/baur/cfg"
"github.com/simplesurance/baur/digest"
"github.com/simplesurance/baur/digest/sha384"
"github.com/simplesurance/baur/log"
"github.com/simplesurance/baur/resolve/gitpath"
"github.com/simplesurance/baur/resolve/glob"
"github.com/simplesurance/baur/resolve/gosource"
)

// App represents an application
Expand All @@ -22,9 +27,10 @@ type App struct {
BuildCmd string
Repository *Repository
Outputs []BuildOutput
BuildInputPaths []BuildInputPathResolver
buildInputs []BuildInput
totalInputDigest *digest.Digest

UnresolvedInputs cfg.BuildInput
buildInputs []*File
}

func replaceUUIDvar(in string) string {
Expand All @@ -44,42 +50,6 @@ func replaceGitCommitVar(in string, r *Repository) (string, error) {
return strings.Replace(in, "$GITCOMMIT", commitID, -1), nil
}

func (a *App) setInputsFromCfg(r *Repository, cfg *cfg.App) error {
sliceLen := len(cfg.Build.Input.Files.Paths)

if len(cfg.Build.Input.GitFiles.Paths) > 0 {
sliceLen++
}

if len(cfg.Build.Input.GolangSources.Paths) > 0 {
sliceLen++
}

a.BuildInputPaths = make([]BuildInputPathResolver, 0, sliceLen)

for _, p := range cfg.Build.Input.Files.Paths {
a.BuildInputPaths = append(a.BuildInputPaths, NewFileGlobPath(r.Path, a.RelPath, p))
}

if len(cfg.Build.Input.GitFiles.Paths) > 0 {
a.BuildInputPaths = append(a.BuildInputPaths,
NewGitPaths(r.Path, a.RelPath, cfg.Build.Input.GitFiles.Paths))
}

if len(cfg.Build.Input.GolangSources.Paths) > 0 {
var gopath string

if len(cfg.Build.Input.GolangSources.GoPath) > 0 {
gopath = filepath.Join(a.Path, cfg.Build.Input.GolangSources.GoPath)
}

a.BuildInputPaths = append(a.BuildInputPaths,
NewGoSrcDirs(r.Path, a.RelPath, gopath, cfg.Build.Input.GolangSources.Paths))
}

return nil
}

func (a *App) setDockerOutputsFromCfg(cfg *cfg.App) error {
for _, di := range cfg.Build.Output.DockerImage {
tag, err := replaceGitCommitVar(di.RegistryUpload.Tag, a.Repository)
Expand Down Expand Up @@ -157,9 +127,7 @@ func NewApp(repository *Repository, cfgPath string) (*App, error) {
return nil, errors.Wrap(err, "processing S3 output declarations failed")
}

if err := app.setInputsFromCfg(repository, cfg); err != nil {
return nil, errors.Wrap(err, "processing input declarations failed")
}
app.UnresolvedInputs = cfg.Build.Input

return &app, nil
}
Expand All @@ -169,38 +137,151 @@ func (a *App) String() string {
return a.Name
}

// BuildInputs returns all deduplicated BuildInputs.
// If the function is called the first time, the BuildInputPaths are resolved
// and stored. On following calls the stored BuildInputs are returned.
func (a *App) BuildInputs() ([]BuildInput, error) {
if a.buildInputs != nil {
return a.buildInputs, nil
}
func (a *App) pathsToUniqFiles(paths []string) ([]*File, error) {
dedupMap := map[string]struct{}{}
res := make([]*File, 0, len(paths))

if len(a.BuildInputPaths) == 0 {
a.buildInputs = []BuildInput{}
return a.buildInputs, nil
for _, path := range paths {
if _, exist := dedupMap[path]; exist {
log.Debugf("%s: removed duplicate Build Input '%s'", a.Name, path)
continue
}
dedupMap[path] = struct{}{}

relPath, err := filepath.Rel(a.Repository.Path, path)
if err != nil {
return nil, errors.Wrapf(err, "resolving relative path to '%s' from '%s' failed", path, a.Repository.Path)
}

// TODO: should resolving the relative path be done in
// Newfile() instead?
res = append(res, NewFile(a.Repository.Path, relPath))
}

dedupBuildInputs := map[string]BuildInput{}
return res, nil
}

func (a *App) resolveGlobFileInputs() ([]string, error) {
var res []string

for _, inputPath := range a.BuildInputPaths {
buildInputs, err := inputPath.Resolve()
for _, globPath := range a.UnresolvedInputs.Files.Paths {
resolver := glob.NewResolver(a.Path, globPath)
paths, err := resolver.Resolve()
if err != nil {
return nil, errors.Wrapf(err, "resolving %q failed", inputPath)
return nil, errors.Wrap(err, globPath)
}

for _, bi := range buildInputs {
if _, exist := dedupBuildInputs[bi.URI()]; exist {
continue
}

dedupBuildInputs[bi.URI()] = bi
if len(paths) == 0 {
return nil, fmt.Errorf("'%s' matched 0 files", globPath)
}

res = append(res, paths...)
}

return res, nil
}

func (a *App) resolveGitFileInputs() ([]string, error) {
if len(a.UnresolvedInputs.GitFiles.Paths) == 0 {
return []string{}, nil
}

resolver := gitpath.NewResolver(a.Path, a.UnresolvedInputs.GitFiles.Paths...)
paths, err := resolver.Resolve()
if err != nil {
return nil, err
}

if len(paths) == 0 {
return nil, fmt.Errorf("'%s' matched 0 files", strings.Join(a.UnresolvedInputs.GitFiles.Paths, ", "))
}

for _, bi := range dedupBuildInputs {
a.buildInputs = append(a.buildInputs, bi)
return paths, nil
}

func (a *App) resolveGoSrcInputs() ([]string, error) {
if len(a.UnresolvedInputs.GolangSources.Paths) == 0 {
return []string{}, nil
}

var gopath string
if a.UnresolvedInputs.GolangSources.GoPath != "" {
gopath = filepath.Join(a.Path, a.UnresolvedInputs.GolangSources.GoPath)
}

resolver := gosource.NewResolver(a.Path, gopath, a.UnresolvedInputs.GolangSources.Paths...)
paths, err := resolver.Resolve()
if err != nil {
return nil, err
}

if len(paths) == 0 {
return nil, fmt.Errorf("'%s' matched 0 files", strings.Join(a.UnresolvedInputs.GitFiles.Paths, ", "))
}

return paths, nil
}

func (a *App) resolveBuildInputPaths() ([]string, error) {
globPaths, err := a.resolveGlobFileInputs()
if err != nil {
return nil, errors.Wrapf(err, "resolving File BuildInputs failed")
}

gitPaths, err := a.resolveGitFileInputs()
if err != nil {
return nil, errors.Wrapf(err, "resolving GitFile BuildInputs failed")
}

goSrcPaths, err := a.resolveGoSrcInputs()
if err != nil {
return nil, errors.Wrapf(err, "resolving GoLangSources BuildInputs failed")
}

paths := make([]string, 0, len(globPaths)+len(gitPaths)+len(goSrcPaths))
paths = append(paths, globPaths...)
paths = append(paths, gitPaths...)
paths = append(paths, goSrcPaths...)

return paths, nil
}

// HasBuildInputs returns true if BuildInputs are defined for the app
func (a *App) HasBuildInputs() bool {
if len(a.UnresolvedInputs.Files.Paths) != 0 {
return true
}

if len(a.UnresolvedInputs.GitFiles.Paths) != 0 {
return true
}

if len(a.UnresolvedInputs.GolangSources.Paths) != 0 {
return true
}

return false
}

// BuildInputs resolves all build inputs of the app.
// The BuildInputs are deduplicates before they are returned.
// If one more resolved path does not match a file an error is generated.
// If not build inputs are defined, an empty slice and no error is returned.
// If the function is called the first time, the BuildInputPaths are resolved
// and stored. On following calls the stored BuildInputs are returned.
func (a *App) BuildInputs() ([]*File, error) {
if a.buildInputs != nil {
return a.buildInputs, nil
}

paths, err := a.resolveBuildInputPaths()
if err != nil {
return nil, err
}

a.buildInputs, err = a.pathsToUniqFiles(paths)
if err != nil {
return nil, err
}

return a.buildInputs, nil
Expand Down
2 changes: 1 addition & 1 deletion build.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (b BuildStatus) String() string {
// If the function returns BuildStatusExist the returned build pointer is valid
// otherwise it is nil.
func GetBuildStatus(storer storage.Storer, app *App) (BuildStatus, *storage.BuildWithDuration, error) {
if len(app.BuildInputPaths) == 0 {
if !app.HasBuildInputs() {
return BuildStatusInputsUndefined, nil, nil
}

Expand Down
12 changes: 0 additions & 12 deletions buildinput.go

This file was deleted.

10 changes: 0 additions & 10 deletions buildinputpathresolver.go

This file was deleted.

2 changes: 1 addition & 1 deletion command/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ func calcDigests(app *baur.App) ([]*storage.Input, string) {

storageInputs = append(storageInputs, &storage.Input{
Digest: d.String(),
URI: s.URI(),
URI: s.RepoRelPath(),
})

inputDigests = append(inputDigests, &d)
Expand Down
4 changes: 2 additions & 2 deletions command/ls_inputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func lsInputs(cmd *cobra.Command, args []string) {
app := mustArgToApp(rep, args[0])
writeHeaders := !lsInputsConfig.quiet && !lsInputsConfig.csv

if len(app.BuildInputPaths) == 0 {
if !app.HasBuildInputs() {
log.Fatalf("No build inputs are configured in %s of %s", baur.AppCfgFile, app.Name)
}

Expand All @@ -73,7 +73,7 @@ func lsInputs(cmd *cobra.Command, args []string) {
}

sort.Slice(inputs, func(i, j int) bool {
return inputs[i].URI() < inputs[j].URI()
return inputs[i].RepoRelPath() < inputs[j].RepoRelPath()
})

for _, input := range inputs {
Expand Down
35 changes: 30 additions & 5 deletions command/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,44 @@ func showApp(arg string) {
}
}

if len(app.BuildInputPaths) != 0 {
if app.HasBuildInputs() {
var printNewLine bool

mustWriteRow(formatter, []interface{}{})
mustWriteRow(formatter, []interface{}{underline("Inputs:")})

for i, bi := range app.BuildInputPaths {
mustWriteRow(formatter, []interface{}{"", "Type:", highlight(bi.Type())})
mustWriteRow(formatter, []interface{}{"", "Configuration:", highlight(bi.String())})
if len(app.UnresolvedInputs.Files.Paths) > 0 {
mustWriteRow(formatter, []interface{}{"", "Type:", highlight("File")})
mustWriteRow(formatter, []interface{}{"",
"Paths:", highlight(strings.Join(app.UnresolvedInputs.Files.Paths, ", ")),
})

printNewLine = true

if i+1 < len(app.BuildInputPaths) {
}

if len(app.UnresolvedInputs.GitFiles.Paths) > 0 {
if printNewLine {
mustWriteRow(formatter, []interface{}{})
}

mustWriteRow(formatter, []interface{}{"", "Type:", highlight("GitFile")})
mustWriteRow(formatter, []interface{}{"",
"Paths:", highlight(strings.Join(app.UnresolvedInputs.GitFiles.Paths, ", "))})

printNewLine = true
}

if len(app.UnresolvedInputs.GolangSources.Paths) > 0 {
if printNewLine {
mustWriteRow(formatter, []interface{}{})
}

mustWriteRow(formatter, []interface{}{"", "Type:", highlight("GolangSources")})
mustWriteRow(formatter, []interface{}{"",
"Paths:", highlight(strings.Join(app.UnresolvedInputs.GolangSources.Paths, ", "))})
mustWriteRow(formatter, []interface{}{"", "GoPath:", highlight(app.UnresolvedInputs.GolangSources.GoPath)})
}
}

if err := formatter.Flush(); err != nil {
Expand Down
5 changes: 0 additions & 5 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,6 @@ func (f *File) RepoRelPath() string {
return f.relPath
}

// URI calls RepoRelPath()
func (f *File) URI() string {
return f.RepoRelPath()
}

// String returns it's string representation
func (f *File) String() string {
return f.RepoRelPath()
Expand Down
Loading

0 comments on commit c68fe5b

Please sign in to comment.