diff --git a/internal/cmd/fingerprint/fingerprint.go b/internal/cmd/fingerprint/fingerprint.go index 5cee3090..4da9e15c 100644 --- a/internal/cmd/fingerprint/fingerprint.go +++ b/internal/cmd/fingerprint/fingerprint.go @@ -1,9 +1,9 @@ package fingerprint import ( + "errors" "fmt" "path/filepath" - "time" "github.com/debricked/cli/internal/fingerprint" "github.com/spf13/cobra" @@ -63,7 +63,7 @@ $ debricked scan . --include '**/node_modules/**'`) cmd.Flags().BoolVar(&shouldFingerprintCompressedContent, FingerprintCompressedContent, false, `Fingerprint the contents of compressed files by unpacking them in memory, Supported files: `+fmt.Sprintf("%v", fingerprint.ZIP_FILE_ENDINGS)) cmd.Flags().StringVar(&outputDir, OutputDirFlag, ".", "The directory to write the output file to") cmd.Flags().IntVar(&minFingerprintContentLength, MinFingerprintContentLengthFlag, 45, "Set minimum content length (in bytes) for files to fingerprint. Defaults to 45 bytes.") - cmd.Flags().BoolVar(&shouldRegenerateFingerprintFile, RegenerateFingerprintFile, true, `If generated fingerprint file should be overwritten on subequent scans. Defaults to true`) + cmd.Flags().BoolVar(&shouldRegenerateFingerprintFile, RegenerateFingerprintFile, true, `Toggle if generated fingerprint file should be overwritten on subequent scans. Defaults to true`) viper.MustBindEnv(ExclusionFlag) @@ -76,7 +76,10 @@ func RunE(f fingerprint.IFingerprint) func(_ *cobra.Command, args []string) erro if len(args) > 0 { path = args[0] } + var outputFilePath = filepath.Join(outputDir, fingerprint.OutputFileNameFingerprints) options := fingerprint.DebrickedOptions{ + OutputPath: outputFilePath, + Regenerate: shouldRegenerateFingerprintFile, Path: path, Exclusions: exclusions, Inclusions: inclusions, @@ -84,22 +87,17 @@ func RunE(f fingerprint.IFingerprint) func(_ *cobra.Command, args []string) erro MinFingerprintContentLength: minFingerprintContentLength, } output, err := f.FingerprintFiles(options) - if err != nil { + if errors.Is(err, &fingerprint.FingerprintFileExistsError{}) { + fmt.Println( + "Fingerprint file exists and command is configured to not overwrite. ", + "To generate new fingerprint file either remove/rename old file or ", + "change flag '--regenerate' to 'true'", + ) + return nil + } return err } - var outputFilePath string - // TODO: decide if we should only create a date-suffix file if a file without date-suffix already exists. - // Depending on decision, check for existence of debricked.fingerprints.txt here. - if shouldRegenerateFingerprintFile { - outputFilePath = filepath.Join(outputDir, fingerprint.OutputFileNameFingerprints) - } else { - // TODO: decide on if date suffix is what we want or if an incrementing integer should be used. - timestamp := time.Now().Format("2006-01-02_15-04-05") // reference date, do not change the date, only format - ext := filepath.Ext(fingerprint.OutputFileNameFingerprints) - ouputFileName := fmt.Sprintf("%s_%s%s", fingerprint.OutputFileNameFingerprints, timestamp, ext) - outputFilePath = filepath.Join(outputDir, ouputFileName) - } err = output.ToFile(outputFilePath) if err != nil { diff --git a/internal/cmd/fingerprint/fingerprint_test.go b/internal/cmd/fingerprint/fingerprint_test.go index 9664be7e..2bf8a7ce 100644 --- a/internal/cmd/fingerprint/fingerprint_test.go +++ b/internal/cmd/fingerprint/fingerprint_test.go @@ -1,6 +1,7 @@ package fingerprint import ( + "io" "os" "testing" @@ -44,7 +45,7 @@ func TestNewFingerprintCmd(t *testing.T) { func TestRunE(t *testing.T) { defer func() { - os.Remove(fingerprint.OutputFileNameFingerprints) // TODO: make sure it will remove all generated fingerprint files (e.g. with date suffix) + os.Remove(fingerprint.OutputFileNameFingerprints) }() fingerprintMock := testdata.NewFingerprintMock() runE := RunE(fingerprintMock) @@ -52,8 +53,25 @@ func TestRunE(t *testing.T) { err := runE(nil, []string{"."}) assert.NoError(t, err) - // TODO: Run command again, first with regenerate=true (default) to ensure no extra file is generated (check only one exists) - // Run command a third time, without regenerate, - // asserting that a date-stamped debricked fingerprint file is created when regenerate=false +} + +func TestRunEFileExistsError(t *testing.T) { + defer func() { + os.Remove(fingerprint.OutputFileNameFingerprints) + }() + fingerprintMock := testdata.NewFingerprintMockFileExistsError() + runE := RunE(fingerprintMock) + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + err := runE(nil, []string{"."}) + + _ = w.Close() + output, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + assert.NoError(t, err) + assert.Contains(t, string(output), "change flag '--regenerate' to 'true'") } diff --git a/internal/fingerprint/fingerprint.go b/internal/fingerprint/fingerprint.go index f33da152..270e1f81 100644 --- a/internal/fingerprint/fingerprint.go +++ b/internal/fingerprint/fingerprint.go @@ -24,6 +24,8 @@ type DebrickedOptions struct { Inclusions []string FingerprintCompressedContent bool MinFingerprintContentLength int + OutputPath string + Regenerate bool } var ZIP_FILE_ENDINGS = []string{".jar", ".nupkg", ".war", ".zip", ".ear", ".whl"} @@ -59,6 +61,12 @@ func NewFingerprinter() *Fingerprinter { } } +type FingerprintFileExistsError struct{} + +func (_ *FingerprintFileExistsError) Error() string { + return "Fingerprint file already exists and command is configured to not overwrite." +} + type FileFingerprint struct { path string contentLength int64 @@ -78,6 +86,17 @@ func (f *Fingerprinter) FingerprintFiles(options DebrickedOptions) (Fingerprints fingerprints := Fingerprints{} + _, err := os.Open(options.OutputPath) + var fingerprintFileExists = false + if !errors.Is(err, os.ErrNotExist) { + fingerprintFileExists = true + } + fmt.Printf("output path=%s", options.OutputPath) + fmt.Printf("regen=%t, file=%t\n", options.Regenerate, fingerprintFileExists) + if !options.Regenerate && fingerprintFileExists { + return fingerprints, &FingerprintFileExistsError{} + } + f.spinnerManager.Start() spinnerMessage := "files processed" spinner := f.spinnerManager.AddSpinner(spinnerMessage) @@ -85,7 +104,7 @@ func (f *Fingerprinter) FingerprintFiles(options DebrickedOptions) (Fingerprints nbFiles := 0 lastLogNb := 0 - err := filepath.Walk(options.Path, func(path string, fileInfo os.FileInfo, err error) error { + err = filepath.Walk(options.Path, func(path string, fileInfo os.FileInfo, err error) error { if err != nil { return err } diff --git a/internal/fingerprint/fingerprint_test.go b/internal/fingerprint/fingerprint_test.go index 1a687f22..08c17c5e 100644 --- a/internal/fingerprint/fingerprint_test.go +++ b/internal/fingerprint/fingerprint_test.go @@ -183,6 +183,25 @@ func TestFingerprintFiles(t *testing.T) { } +func TestFingerprintFilesAlreadyExists(t *testing.T) { + temp, err := os.CreateTemp("testdata/fingerprinter", "temp-fingerprint-*.txt") + fingerprinter := NewFingerprinter() + _, err = fingerprinter.FingerprintFiles( + DebrickedOptions{ + OutputPath: temp.Name(), + Path: "testdata/fingerprinter", + Exclusions: []string{}, + Inclusions: []string{}, + FingerprintCompressedContent: false, + MinFingerprintContentLength: 0, + Regenerate: false, + }, + ) + os.Remove(temp.Name()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Fingerprint file already exists") +} + func TestFingerprintFilesBackslash(t *testing.T) { tempDir, err := os.MkdirTemp("", "slash-test") diff --git a/internal/fingerprint/testdata/fingerprinter_mock.go b/internal/fingerprint/testdata/fingerprinter_mock.go index cf9df001..0e012da3 100644 --- a/internal/fingerprint/testdata/fingerprinter_mock.go +++ b/internal/fingerprint/testdata/fingerprinter_mock.go @@ -14,6 +14,12 @@ func NewFingerprintMock() *FingerprintMock { } } +func NewFingerprintMockFileExistsError() *FingerprintMock { + return &FingerprintMock{ + error: &fingerprint.FingerprintFileExistsError{}, + } +} + func (f *FingerprintMock) FingerprintFiles( options fingerprint.DebrickedOptions, ) (fingerprint.Fingerprints, error) {