Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add storage cleanup #148

Merged
merged 9 commits into from
Nov 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 4 additions & 22 deletions cmd/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import (
)

func Test_Auth_Login_WithToken(t *testing.T) {
t.Cleanup(sessionCleanup)

ctrl := gomock.NewController(t)
defer ctrl.Finish()

Expand All @@ -32,12 +30,7 @@ func Test_Auth_Login_WithToken(t *testing.T) {
r.WriteString("token\n")
printer := view.NewPrinter(r, w, w)
ctx := createDefaultContext("")
_storage := storage.NewLocalStorage(".test_globalping-cli")
defer _storage.Remove()
err := _storage.Init()
if err != nil {
t.Fatal(err)
}
_storage := createDefaultTestStorage(t, utilsMock)
_storage.GetProfile().Token = &globalping.Token{
AccessToken: "oldToken",
RefreshToken: "oldRefreshToken",
Expand All @@ -52,7 +45,7 @@ func Test_Auth_Login_WithToken(t *testing.T) {
gbMock.EXPECT().RevokeToken("oldRefreshToken").Return(nil)

os.Args = []string{"globalping", "auth", "login", "--with-token"}
err = root.Cmd.ExecuteContext(context.TODO())
err := root.Cmd.ExecuteContext(context.TODO())
assert.NoError(t, err)

assert.Equal(t, `Please enter your token:
Expand All @@ -69,8 +62,6 @@ Logged in as test.
}

func Test_Auth_Login(t *testing.T) {
t.Cleanup(sessionCleanup)

ctrl := gomock.NewController(t)
defer ctrl.Finish()

Expand All @@ -80,12 +71,7 @@ func Test_Auth_Login(t *testing.T) {
w := new(bytes.Buffer)
printer := view.NewPrinter(nil, w, w)
ctx := createDefaultContext("")
_storage := storage.NewLocalStorage(".test_globalping-cli")
defer _storage.Remove()
err := _storage.Init()
if err != nil {
t.Fatal(err)
}
_storage := createDefaultTestStorage(t, utilsMock)
_storage.GetProfile().Token = &globalping.Token{
AccessToken: "oldToken",
RefreshToken: "oldRefreshToken",
Expand All @@ -101,7 +87,7 @@ func Test_Auth_Login(t *testing.T) {
utilsMock.EXPECT().OpenBrowser("http://localhost").Return(nil)

os.Args = []string{"globalping", "auth", "login"}
err = root.Cmd.ExecuteContext(context.TODO())
err := root.Cmd.ExecuteContext(context.TODO())
assert.NoError(t, err)

assert.Equal(t, `Please visit the following URL to authenticate:
Expand All @@ -112,8 +98,6 @@ Can't use the browser-based flow? Use "globalping auth login --with-token" to re
}

func Test_AuthStatus(t *testing.T) {
t.Cleanup(sessionCleanup)

ctrl := gomock.NewController(t)
defer ctrl.Finish()

Expand All @@ -139,8 +123,6 @@ func Test_AuthStatus(t *testing.T) {
}

func Test_Logout(t *testing.T) {
t.Cleanup(sessionCleanup)

ctrl := gomock.NewController(t)
defer ctrl.Finish()

Expand Down
142 changes: 7 additions & 135 deletions cmd/common.go
Original file line number Diff line number Diff line change
@@ -1,40 +1,25 @@
package cmd

import (
"bufio"
"errors"
"fmt"
"io"
"io/fs"
"net"
"net/http"
"os"
"path/filepath"
"runtime"
"slices"
"strconv"
"strings"

"github.com/icza/backscanner"
"github.com/jsdelivr/globalping-cli/globalping"
"github.com/jsdelivr/globalping-cli/storage"
"github.com/jsdelivr/globalping-cli/version"
"github.com/jsdelivr/globalping-cli/view"
"github.com/shirou/gopsutil/process"
)

var (
ErrNoPreviousMeasurements = errors.New("no previous measurements found")
ErrInvalidIndex = errors.New("invalid index")
ErrIndexOutOfRange = errors.New("index out of range")
ErrTargetIPVersionNotAllowed = errors.New("ipVersion is not allowed when target is not a domain")
ErrResolverIPVersionNotAllowed = errors.New("ipVersion is not allowed when resolver is not a domain")
)
var (
saveIdToSessionErr = "failed to save measurement ID: %s"
readMeasuremetsErr = "failed to read previous measurements: %s"
)

var SESSION_PATH string

func (r *Root) updateContext(cmd string, args []string) error {
r.ctx.Cmd = cmd // Get the command name
Expand Down Expand Up @@ -94,7 +79,7 @@ func (r *Root) updateContext(cmd string, args []string) error {
func (r *Root) getLocations() ([]globalping.Locations, error) {
fromArr := strings.Split(r.ctx.From, ",")
if len(fromArr) == 1 {
mId, err := mapFromSession(fromArr[0])
mId, err := r.mapFromSession(fromArr[0])
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -198,139 +183,26 @@ func findAndRemoveResolver(args []string) (string, []string) {
}

// Maps a location to a measurement ID from history, if possible.
func mapFromSession(location string) (string, error) {
func (r *Root) mapFromSession(location string) (string, error) {
if location == "" {
return "", nil
}
if location[0] == '@' {
index, err := strconv.Atoi(location[1:])
if err != nil {
return "", ErrInvalidIndex
return "", storage.ErrInvalidIndex
}
return getIdFromSession(index)
return r.storage.GetIdFromSession(index)
}
if location == "first" {
return getIdFromSession(1)
return r.storage.GetIdFromSession(1)
}
if location == "last" || location == "previous" {
return getIdFromSession(-1)
return r.storage.GetIdFromSession(-1)
}
return "", nil
}

// Returns the measurement ID at the given index from the session history
func getIdFromSession(index int) (string, error) {
if index == 0 {
return "", ErrInvalidIndex
}
f, err := os.Open(getMeasurementsPath())
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return "", ErrNoPreviousMeasurements
}
return "", fmt.Errorf(readMeasuremetsErr, err)
}
defer f.Close()
// Read ids from the end of the file
if index < 0 {
fStats, err := f.Stat()
if err != nil {
return "", fmt.Errorf(readMeasuremetsErr, err)
}
if fStats.Size() == 0 {
return "", ErrNoPreviousMeasurements
}
scanner := backscanner.New(f, int(fStats.Size()-1)) // -1 to skip last newline
for {
index++
b, _, err := scanner.LineBytes()
if err != nil {
if err == io.EOF {
return "", ErrIndexOutOfRange
}
return "", fmt.Errorf(readMeasuremetsErr, err)
}
if index == 0 {
return string(b), nil
}
}
}
// Read ids from the beginning of the file
scanner := bufio.NewScanner(f)
for scanner.Scan() {
index--
if index == 0 {
return scanner.Text(), nil
}
}
if err := scanner.Err(); err != nil {
return "", fmt.Errorf("failed to read previous measurements: %s", err)
}
return "", ErrIndexOutOfRange
}

// Saves the measurement ID to the session history
func saveIdToSession(id string) error {
_, err := os.Stat(getSessionPath())
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
err := os.Mkdir(getSessionPath(), 0755)
if err != nil {
return fmt.Errorf(saveIdToSessionErr, err)
}
} else {
return fmt.Errorf(saveIdToSessionErr, err)
}
}
f, err := os.OpenFile(getMeasurementsPath(), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf(saveIdToSessionErr, err)
}
defer f.Close()
_, err = f.WriteString(id + "\n")
if err != nil {
return fmt.Errorf(saveIdToSessionErr, err)
}
return nil
}

func getSessionPath() string {
if SESSION_PATH != "" {
return SESSION_PATH
}
SESSION_PATH = filepath.Join(os.TempDir(), getSessionId())
return SESSION_PATH
}

func getSessionId() string {
p, err := process.NewProcess(int32(os.Getppid()))
if err != nil {
return "globalping"
}
// Workaround for bash.exe on Windows
// PPID is different on each run.
// https://cygwin.com/git/gitweb.cgi?p=newlib-cygwin.git;a=commit;h=448cf5aa4b429d5a9cebf92a0da4ab4b5b6d23fe
if runtime.GOOS == "windows" {
name, _ := p.Name()
if name == "bash.exe" {
p, err = p.Parent()
if err != nil {
return "globalping"
}
}
}
createTime, _ := p.CreateTime()
return fmt.Sprintf("globalping_%d_%d", p.Pid, createTime)
}

func getMeasurementsPath() string {
return filepath.Join(getSessionPath(), "measurements")
}

func getHistoryPath() string {
return filepath.Join(getSessionPath(), "history")
}

func silenceUsageOnCreateMeasurementError(err error) bool {
e, ok := err.(*globalping.MeasurementError)
if ok {
Expand Down
2 changes: 1 addition & 1 deletion cmd/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func (r *Root) RunDNS(cmd *cobra.Command, args []string) error {
r.ctx.History.Push(hm)
if r.ctx.RecordToSession {
r.ctx.RecordToSession = false
err := saveIdToSession(res.ID)
err := r.storage.SaveIdToSession(res.ID)
if err != nil {
r.printer.Printf("Warning: %s\n", err)
}
Expand Down
27 changes: 12 additions & 15 deletions cmd/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import (
)

func Test_Execute_DNS_Default(t *testing.T) {
t.Cleanup(sessionCleanup)

ctrl := gomock.NewController(t)
defer ctrl.Finish()

Expand Down Expand Up @@ -43,7 +41,8 @@ func Test_Execute_DNS_Default(t *testing.T) {
w := new(bytes.Buffer)
printer := view.NewPrinter(nil, w, w)
ctx := createDefaultContext("dns")
root := NewRoot(printer, ctx, viewerMock, utilsMock, gbMock, nil, nil)
_storage := createDefaultTestStorage(t, utilsMock)
root := NewRoot(printer, ctx, viewerMock, utilsMock, gbMock, nil, _storage)

os.Args = []string{"globalping", "dns", "jsdelivr.com",
"from", "Berlin",
Expand All @@ -68,24 +67,22 @@ func Test_Execute_DNS_Default(t *testing.T) {

assert.Equal(t, expectedCtx, ctx)

b, err := os.ReadFile(getMeasurementsPath())
b, err := _storage.GetMeasurements()
assert.NoError(t, err)
expectedHistory := measurementID1 + "\n"
assert.Equal(t, expectedHistory, string(b))

b, err = os.ReadFile(getHistoryPath())
items, err := _storage.GetHistory(0)
assert.NoError(t, err)
expectedHistory = createDefaultExpectedHistoryLogItem(
expectedHistoryItems := []string{createDefaultExpectedHistoryItem(
"1",
measurementID1,
"dns jsdelivr.com from Berlin --limit 2 --type MX --resolver 1.1.1.1 --port 99 --protocol tcp --trace",
)
assert.Equal(t, expectedHistory, string(b))
measurementID1,
)}
assert.Equal(t, expectedHistoryItems, items)
}

func Test_Execute_DNS_IPv4(t *testing.T) {
t.Cleanup(sessionCleanup)

ctrl := gomock.NewController(t)
defer ctrl.Finish()

Expand All @@ -107,7 +104,8 @@ func Test_Execute_DNS_IPv4(t *testing.T) {
w := new(bytes.Buffer)
printer := view.NewPrinter(nil, w, w)
ctx := createDefaultContext("dns")
root := NewRoot(printer, ctx, viewerMock, utilsMock, gbMock, nil, nil)
_storage := createDefaultTestStorage(t, utilsMock)
root := NewRoot(printer, ctx, viewerMock, utilsMock, gbMock, nil, _storage)

os.Args = []string{"globalping", "dns", "jsdelivr.com",
"from", "Berlin",
Expand All @@ -124,8 +122,6 @@ func Test_Execute_DNS_IPv4(t *testing.T) {
}

func Test_Execute_DNS_IPv6(t *testing.T) {
t.Cleanup(sessionCleanup)

ctrl := gomock.NewController(t)
defer ctrl.Finish()

Expand All @@ -147,7 +143,8 @@ func Test_Execute_DNS_IPv6(t *testing.T) {
w := new(bytes.Buffer)
printer := view.NewPrinter(nil, w, w)
ctx := createDefaultContext("dns")
root := NewRoot(printer, ctx, viewerMock, utilsMock, gbMock, nil, nil)
_storage := createDefaultTestStorage(t, utilsMock)
root := NewRoot(printer, ctx, viewerMock, utilsMock, gbMock, nil, _storage)

os.Args = []string{"globalping", "dns", "jsdelivr.com",
"from", "Berlin",
Expand Down
Loading