Skip to content

Commit

Permalink
Forced Regeneration Flag (#173)
Browse files Browse the repository at this point in the history
* Adds forced regeneration flag

* Refactors resolution arguments into a struct
  • Loading branch information
filip-debricked authored Dec 22, 2023
1 parent 645f2c3 commit 64c2471
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 32 deletions.
23 changes: 20 additions & 3 deletions internal/cmd/resolve/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ var (
exclusions = file.Exclusions()
verbose bool
npmPreferred bool
regenerate int
)

const (
ExclusionFlag = "exclusion"
VerboseFlag = "verbose"
NpmPreferredFlag = "prefer-npm"
RegenerateFlag = "regenerate"
)

func NewResolveCmd(resolver resolution.IResolver) *cobra.Command {
Expand Down Expand Up @@ -53,6 +55,17 @@ Exclude flags could alternatively be set using DEBRICKED_EXCLUSIONS="path1,path2
Example:
$ debricked resolve . `+exampleFlags)
cmd.Flags().BoolVar(&verbose, VerboseFlag, true, "set to false to disable extensive resolution error messages")
regenerateDoc := strings.Join(
[]string{
"Toggles regeneration of already existing lock files between 3 modes:\n",
"Force Regeneration Level | Meaning",
"------------------------ | -------",
"0 (default) | No regeneration",
"1 | Regenerates existing non package manager native Debricked lock files",
"2 | Regenerates all existing lock files",
"\nExample:\n$ debricked resolve . --regenerate=1",
}, "\n")
cmd.Flags().IntVar(&regenerate, RegenerateFlag, 0, regenerateDoc)

npmPreferredDoc := strings.Join(
[]string{
Expand All @@ -73,9 +86,13 @@ func RunE(resolver resolution.IResolver) func(_ *cobra.Command, args []string) e
if len(args) == 0 {
args = append(args, ".")
}

resolver.SetNpmPreferred(viper.GetBool(NpmPreferredFlag))
_, err := resolver.Resolve(args, viper.GetStringSlice(ExclusionFlag), viper.GetBool(VerboseFlag))
options := resolution.DebrickedOptions{
Exclusions: viper.GetStringSlice(ExclusionFlag),
Verbose: viper.GetBool(VerboseFlag),
Regenerate: viper.GetInt(RegenerateFlag),
NpmPreferred: viper.GetBool(NpmPreferredFlag),
}
_, err := resolver.Resolve(args, options)

return err
}
Expand Down
14 changes: 14 additions & 0 deletions internal/cmd/scan/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var repositoryUrl string
var integrationName string
var exclusions = file.Exclusions()
var verbose bool
var regenerate int
var noResolve bool
var noFingerprint bool
var passOnDowntime bool
Expand All @@ -38,6 +39,7 @@ const (
IntegrationFlag = "integration"
ExclusionFlag = "exclusion"
VerboseFlag = "verbose"
RegenerateFlag = "regenerate"
NoResolveFlag = "no-resolve"
FingerprintFlag = "fingerprint"
PassOnTimeOut = "pass-on-timeout"
Expand Down Expand Up @@ -95,6 +97,17 @@ Exclude flags could alternatively be set using DEBRICKED_EXCLUSIONS="path1,path2
Examples:
$ debricked scan . `+exampleFlags)
cmd.Flags().BoolVar(&verbose, VerboseFlag, true, "set to false to disable extensive resolution error messages")
regenerateDoc := strings.Join(
[]string{
"Toggles regeneration of already existing lock files between 3 modes:\n",
"Force Regeneration Level | Meaning",
"------------------------ | -------",
"0 (default) | No regeneration",
"1 | Regenerates existing non package manager native Debricked lock files",
"2 | Regenerates all existing lock files",
"\nExample:\n$ debricked resolve . --regenerate=1",
}, "\n")
cmd.Flags().IntVar(&regenerate, RegenerateFlag, 0, regenerateDoc)
cmd.Flags().BoolVarP(&passOnDowntime, PassOnTimeOut, "p", false, "pass scan if there is a service access timeout")
cmd.Flags().BoolVar(&noResolve, NoResolveFlag, false, `disables resolution of manifest files that lack lock files. Resolving manifest files enables more accurate dependency scanning since the whole dependency tree will be analysed.
For example, if there is a "go.mod" in the target path, its dependencies are going to get resolved onto a lock file, and latter scanned.`)
Expand Down Expand Up @@ -136,6 +149,7 @@ func RunE(s *scan.IScanner) func(_ *cobra.Command, args []string) error {
Fingerprint: viper.GetBool(FingerprintFlag),
Exclusions: viper.GetStringSlice(ExclusionFlag),
Verbose: viper.GetBool(VerboseFlag),
Regenerate: viper.GetInt(RegenerateFlag),
RepositoryName: viper.GetString(RepositoryFlag),
CommitName: viper.GetString(CommitFlag),
BranchName: viper.GetString(BranchFlag),
Expand Down
70 changes: 59 additions & 11 deletions internal/resolution/resolver.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package resolution

import (
"errors"
"os"
"path"
"regexp"

"github.com/debricked/cli/internal/file"
resolutionFile "github.com/debricked/cli/internal/resolution/file"
Expand All @@ -11,9 +13,12 @@ import (
"github.com/debricked/cli/internal/tui"
)

var (
BadOptsErr = errors.New("failed to type case IOptions")
)

type IResolver interface {
Resolve(paths []string, exclusions []string, verbose bool) (IResolution, error)
SetNpmPreferred(npmPreferred bool)
Resolve(paths []string, options IOptions) (IResolution, error)
}

type Resolver struct {
Expand All @@ -24,6 +29,16 @@ type Resolver struct {
npmPreferred bool
}

type IOptions interface{}

type DebrickedOptions struct {
Path string
Exclusions []string
Verbose bool
Regenerate int
NpmPreferred bool
}

func NewResolver(
finder file.IFinder,
batchFactory resolutionFile.IBatchFactory,
Expand All @@ -39,16 +54,20 @@ func NewResolver(
}
}

func (r Resolver) SetNpmPreferred(npmPreferred bool) {
func (r Resolver) setNpmPreferred(npmPreferred bool) {
r.batchFactory.SetNpmPreferred(npmPreferred)
}

func (r Resolver) Resolve(paths []string, exclusions []string, verbose bool) (IResolution, error) {
files, err := r.refinePaths(paths, exclusions)
func (r Resolver) Resolve(paths []string, options IOptions) (IResolution, error) {
dOptions, ok := options.(DebrickedOptions)
if !ok {
return nil, BadOptsErr
}
files, err := r.refinePaths(paths, dOptions.Exclusions, dOptions.Regenerate)
if err != nil {
return nil, err
}

r.setNpmPreferred(dOptions.NpmPreferred)
pmBatches := r.batchFactory.Make(files)

var jobs []job.IJob
Expand All @@ -67,13 +86,13 @@ func (r Resolver) Resolve(paths []string, exclusions []string, verbose bool) (IR

if resolution.HasErr() {
jobErrList := tui.NewJobsErrorList(os.Stdout, resolution.Jobs())
err = jobErrList.Render(verbose)
err = jobErrList.Render(dOptions.Verbose)
}

return resolution, err
}

func (r Resolver) refinePaths(paths []string, exclusions []string) ([]string, error) {
func (r Resolver) refinePaths(paths []string, exclusions []string, regenerate int) ([]string, error) {
var fileSet = map[string]bool{}
var dirs []string
for _, arg := range paths {
Expand All @@ -96,7 +115,7 @@ func (r Resolver) refinePaths(paths []string, exclusions []string) ([]string, er
}
}

err := r.searchDirs(fileSet, dirs, exclusions)
err := r.searchDirs(fileSet, dirs, exclusions, regenerate)
if err != nil {
return nil, err
}
Expand All @@ -109,7 +128,7 @@ func (r Resolver) refinePaths(paths []string, exclusions []string) ([]string, er
return files, nil
}

func (r Resolver) searchDirs(fileSet map[string]bool, dirs []string, exclusions []string) error {
func (r Resolver) searchDirs(fileSet map[string]bool, dirs []string, exclusions []string, regenerate int) error {
for _, dir := range dirs {
fileGroups, err := r.finder.GetGroups(
dir,
Expand All @@ -121,11 +140,40 @@ func (r Resolver) searchDirs(fileSet map[string]bool, dirs []string, exclusions
return err
}
for _, fileGroup := range fileGroups.ToSlice() {
if fileGroup.HasFile() && !fileGroup.HasLockFiles() {
shouldGenerate := shouldGenerateLock(fileGroup, regenerate)
if shouldGenerate {
fileSet[fileGroup.ManifestFile] = true
}
}
}

return nil
}

func shouldGenerateLock(fileGroup file.Group, regenerate int) bool {
if !fileGroup.HasFile() {
return false
}
switch regenerate {
case 0:
return !fileGroup.HasLockFiles()
case 1:
return onlyNonNativeLockFiles(fileGroup.LockFiles)
case 2:
return true
}

return false
}

func onlyNonNativeLockFiles(lockFiles []string) bool {
debrickedLockFilePattern := regexp.MustCompile(`.*\.debricked\.lock`)
for _, lockFile := range lockFiles {
if !debrickedLockFilePattern.MatchString(lockFile) {
return false
}
}

return true

}
74 changes: 61 additions & 13 deletions internal/resolution/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,12 @@ func TestResolve(t *testing.T) {
strategyTestdata.NewStrategyFactoryMock(),
NewScheduler(workers),
)

res, err := r.Resolve([]string{"../../go.mod"}, nil, true)
options := DebrickedOptions{
Exclusions: nil,
Verbose: true,
Regenerate: 0,
}
res, err := r.Resolve([]string{"../../go.mod"}, options)
assert.NotEmpty(t, res.Jobs())
assert.NoError(t, err)
}
Expand All @@ -52,8 +56,12 @@ func TestResolveInvokeError(t *testing.T) {
strategyTestdata.NewStrategyFactoryErrorMock(),
NewScheduler(workers),
)

_, err := r.Resolve([]string{"../../go.mod"}, nil, true)
options := DebrickedOptions{
Exclusions: nil,
Verbose: true,
Regenerate: 0,
}
_, err := r.Resolve([]string{"../../go.mod"}, options)
assert.NotNil(t, err)
}

Expand All @@ -65,7 +73,12 @@ func TestResolveStrategyError(t *testing.T) {
NewScheduler(workers),
)

res, err := r.Resolve([]string{"../../go.mod"}, nil, true)
options := DebrickedOptions{
Exclusions: nil,
Verbose: true,
Regenerate: 0,
}
res, err := r.Resolve([]string{"../../go.mod"}, options)
assert.Empty(t, res.Jobs())
assert.NoError(t, err)
}
Expand All @@ -79,7 +92,12 @@ func TestResolveScheduleError(t *testing.T) {
SchedulerMock{Err: errAssertion},
)

res, err := r.Resolve([]string{"../../go.mod"}, nil, true)
options := DebrickedOptions{
Exclusions: nil,
Verbose: true,
Regenerate: 0,
}
res, err := r.Resolve([]string{"../../go.mod"}, options)
assert.NotEmpty(t, res.Jobs())
assert.ErrorIs(t, err, errAssertion)
}
Expand All @@ -92,7 +110,12 @@ func TestResolveDirWithoutManifestFiles(t *testing.T) {
SchedulerMock{},
)

res, err := r.Resolve([]string{"."}, nil, true)
options := DebrickedOptions{
Exclusions: nil,
Verbose: true,
Regenerate: 0,
}
res, err := r.Resolve([]string{"."}, options)
assert.Empty(t, res.Jobs())
assert.NoError(t, err)
}
Expand All @@ -105,7 +128,12 @@ func TestResolveInvalidDir(t *testing.T) {
SchedulerMock{},
)

_, err := r.Resolve([]string{"invalid-dir"}, nil, true)
options := DebrickedOptions{
Exclusions: nil,
Verbose: true,
Regenerate: 0,
}
_, err := r.Resolve([]string{"invalid-dir"}, options)
assert.Error(t, err)
}

Expand All @@ -121,7 +149,12 @@ func TestResolveGetGroupsErr(t *testing.T) {
SchedulerMock{},
)

_, err := r.Resolve([]string{"."}, nil, true)
options := DebrickedOptions{
Exclusions: nil,
Verbose: true,
Regenerate: 0,
}
_, err := r.Resolve([]string{"."}, options)
assert.ErrorIs(t, testErr, err)
}

Expand All @@ -147,9 +180,14 @@ func TestResolveDirWithManifestFiles(t *testing.T) {
SchedulerMock{},
)

for _, dir := range cases {
for i, dir := range cases {
options := DebrickedOptions{
Exclusions: nil,
Verbose: true,
Regenerate: i % 3, // To test the different regenerate values
}
t.Run(fmt.Sprintf("Case: %s", dir), func(t *testing.T) {
res, err := r.Resolve([]string{dir}, nil, true)
res, err := r.Resolve([]string{dir}, options)
assert.Len(t, res.Jobs(), 1)
j := res.Jobs()[0]
assert.False(t, j.Errors().HasError())
Expand All @@ -172,7 +210,12 @@ func TestResolveDirWithExclusions(t *testing.T) {
SchedulerMock{},
)

res, err := r.Resolve([]string{"."}, []string{"dir"}, true)
options := DebrickedOptions{
Exclusions: []string{"dir"},
Verbose: true,
Regenerate: 0,
}
res, err := r.Resolve([]string{"."}, options)

assert.Len(t, res.Jobs(), 1)
j := res.Jobs()[0]
Expand All @@ -199,7 +242,12 @@ func TestResolveHasResolutionErrs(t *testing.T) {
schedulerMock,
)

res, err := r.Resolve([]string{""}, []string{""}, true)
options := DebrickedOptions{
Exclusions: []string{""},
Verbose: true,
Regenerate: 0,
}
res, err := r.Resolve([]string{""}, options)

assert.NoError(t, err)
assert.Len(t, res.Jobs(), 1)
Expand Down
Loading

0 comments on commit 64c2471

Please sign in to comment.