Skip to content

Commit

Permalink
Implement Report processed marking
Browse files Browse the repository at this point in the history
  • Loading branch information
hk21702 committed Jan 30, 2025
1 parent c735381 commit c26443d
Show file tree
Hide file tree
Showing 15 changed files with 419 additions and 107 deletions.
30 changes: 18 additions & 12 deletions internal/fileutils/fileutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package fileutils_test
import (
"os"
"path/filepath"
"runtime"
"testing"

"github.com/stretchr/testify/require"
Expand All @@ -13,21 +14,25 @@ func TestAtomicWrite(t *testing.T) {
t.Parallel()

tests := map[string]struct {
data []byte
fileExists bool
invalidDir bool
data []byte
fileExists bool
fileExistsPerms os.FileMode
invalidDir bool

wantError bool
wantErrWin bool
wantError bool
}{
"Empty file": {data: []byte{}},
"Non-empty file": {data: []byte("data")},
"Override file": {data: []byte("data"), fileExists: true},
"Override empty file": {data: []byte{}, fileExists: true},
"Override file": {data: []byte("data"), fileExistsPerms: 0600, fileExists: true},
"Override empty file": {data: []byte{}, fileExistsPerms: 0600, fileExists: true},

"Existing empty file": {data: []byte{}, fileExists: true},
"Existing non-empty file": {data: []byte("data"), fileExists: true},
"Existing empty file": {data: []byte{}, fileExistsPerms: 0600, fileExists: true},
"Existing non-empty file": {data: []byte("data"), fileExistsPerms: 0600, fileExists: true},

"Invalid Dir": {data: []byte("data"), invalidDir: true, wantError: true},
"Override read-only file": {data: []byte("data"), fileExistsPerms: 0400, fileExists: true, wantErrWin: true},
"Override No Perms file": {data: []byte("data"), fileExistsPerms: 0000, fileExists: true, wantErrWin: true},
"Invalid Dir": {data: []byte("data"), invalidDir: true, wantError: true},
}

for name, tc := range tests {
Expand All @@ -42,12 +47,13 @@ func TestAtomicWrite(t *testing.T) {
}

if tc.fileExists {
err := fileutils.AtomicWrite(path, oldFile)
require.NoError(t, err, "Setup: AtomicWrite should not return an error")
err := os.WriteFile(path, oldFile, tc.fileExistsPerms)
require.NoError(t, err, "Setup: WriteFile should not return an error")
t.Cleanup(func() { _ = os.Chmod(path, 0600) })
}

err := fileutils.AtomicWrite(path, tc.data)
if tc.wantError {
if tc.wantError || (tc.wantErrWin && runtime.GOOS == "windows") {
require.Error(t, err, "AtomicWrite should return an error")

// Check that the file was not overwritten
Expand Down
65 changes: 52 additions & 13 deletions internal/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"time"

"github.com/ubuntu/ubuntu-insights/internal/constants"
"github.com/ubuntu/ubuntu-insights/internal/fileutils"
)

var (
Expand All @@ -30,7 +31,7 @@ var (

// Report represents a report file.
type Report struct {
Path string // Path is the path to the report.
Path string // Path is the path to the report file.
Name string // Name is the name of the report file, including extension.
TimeStamp int64 // TimeStamp is the timestamp of the report.

Expand Down Expand Up @@ -73,6 +74,50 @@ func (r Report) ReadJSON() ([]byte, error) {
return data, nil
}

// MarkAsProcessed moves the report to a destination directory, and writes the data to the report.
// The original report is removed.
//
// The new report is returned, and the original data is stashed for use with UndoProcessed.
// Note that calling MarkAsProcessed multiple times on the same report will overwrite the stashed data.
func (r Report) MarkAsProcessed(dest string, data []byte) (Report, error) {
origData, err := r.ReadJSON()
if err != nil {
return Report{}, fmt.Errorf("failed to read original report: %v", err)
}

newReport := Report{Path: filepath.Join(dest, r.Name), Name: r.Name, TimeStamp: r.TimeStamp,
reportStash: reportStash{Path: r.Path, Data: origData}}

if err := fileutils.AtomicWrite(newReport.Path, data); err != nil {
return Report{}, fmt.Errorf("failed to write report: %v", err)
}

if err := os.Remove(r.Path); err != nil {
return Report{}, fmt.Errorf("failed to remove report: %v", err)
}

return newReport, nil
}

// UndoProcessed moves the report back to the original directory, and writes the original data to the report.
// The new report is returned, and the original data is removed.
func (r Report) UndoProcessed() (Report, error) {
if r.reportStash.Path == "" {
return Report{}, errors.New("no stashed data to restore")
}

if err := fileutils.AtomicWrite(r.reportStash.Path, r.reportStash.Data); err != nil {
return Report{}, fmt.Errorf("failed to write report: %v", err)
}

if err := os.Remove(r.Path); err != nil {
return Report{}, fmt.Errorf("failed to remove report: %v", err)
}

newReport := Report{Path: r.reportStash.Path, Name: r.Name, TimeStamp: r.TimeStamp}
return newReport, nil
}

// getReportTime returns a int64 representation of the report time from the report path.
func getReportTime(path string) (int64, error) {
fileName := filepath.Base(path)
Expand Down Expand Up @@ -108,8 +153,7 @@ func GetForPeriod(dir string, time time.Time, period int) (Report, error) {
var report Report
err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
if err != nil {
slog.Error("Failed to access path", "path", path, "error", err)
return err
return fmt.Errorf("failed to access path: %v", err)
}

// Skip subdirectories.
Expand All @@ -122,8 +166,7 @@ func GetForPeriod(dir string, time time.Time, period int) (Report, error) {
slog.Info("Skipping non-report file", "file", d.Name(), "error", err)
return nil
} else if err != nil {
slog.Error("Failed to create report object", "error", err)
return err
return fmt.Errorf("failed to create report object: %v", err)
}

if r.TimeStamp < periodStart {
Expand Down Expand Up @@ -156,8 +199,7 @@ func GetPerPeriod(dir string, period int) (map[int64]Report, error) {
reports := make(map[int64]Report)
err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
if err != nil {
slog.Error("Failed to access path", "path", path, "error", err)
return err
return fmt.Errorf("failed to access path: %v", err)
}

if d.IsDir() && path != dir {
Expand All @@ -169,8 +211,7 @@ func GetPerPeriod(dir string, period int) (map[int64]Report, error) {
slog.Info("Skipping non-report file", "file", d.Name(), "error", err)
return nil
} else if err != nil {
slog.Error("Failed to create report object", "error", err)
return err
return fmt.Errorf("failed to create report object: %v", err)
}

periodStart := r.TimeStamp - (r.TimeStamp % int64(period))
Expand All @@ -195,8 +236,7 @@ func GetAll(dir string) ([]Report, error) {
reports := make([]Report, 0)
err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
if err != nil {
slog.Error("Failed to access path", "path", path, "error", err)
return err
return fmt.Errorf("failed to access path: %v", err)
}

if d.IsDir() && path != dir {
Expand All @@ -208,8 +248,7 @@ func GetAll(dir string) ([]Report, error) {
slog.Info("Skipping non-report file", "file", d.Name(), "error", err)
return nil
} else if err != nil {
slog.Error("Failed to create report object", "error", err)
return err
return fmt.Errorf("failed to create report object: %v", err)
}

reports = append(reports, r)
Expand Down
Loading

0 comments on commit c26443d

Please sign in to comment.