dashboard / pico / chore: rsync test #47 rss

open · opened on 2025-02-21T21:07:49Z by erock
Help
# add changes to patch request
git format-patch main --stdout | ssh pr.pico.sh pr add 47
# add review to patch request
git format-patch main --stdout | ssh pr.pico.sh pr add --review 47
# remove patchset
ssh pr.pico.sh ps rm ps-x
# checkout all patches
ssh pr.pico.sh pr print 47 | git am -3
# print a diff between the last two patches in a patch request
ssh pr.pico.sh pr diff 47
# accept PR
ssh pr.pico.sh pr accept 47
# close PR
ssh pr.pico.sh pr close 47

Logs

erock created pr with ps-100 on 2025-02-21T21:07:49Z
erock added ps-101 on 2025-02-21T21:21:15Z
erock added ps-102 on 2025-02-21T21:21:56Z
erock added ps-103 on 2025-02-21T21:22:20Z
erock added ps-104 on 2025-02-22T02:12:03Z

Patchsets

ps-100 by erock on 2025-02-21T21:07:49Z
Range Diff ↕ rd-101
1: 0306c3f < -: ------- chore: rsync test
2: 971b354 ! 1: 745b513 chore: add `rsync --delete` test
ps-101 by erock on 2025-02-21T21:21:15Z
Range Diff ↕ rd-102
1: 745b513 ! 1: 76a6734 chore: add `rsync --delete` test
ps-102 by erock on 2025-02-21T21:21:56Z
Range Diff ↕ rd-103
1: 76a6734 ! 1: 57bb60c chore: add `rsync --delete` test
ps-103 by erock on 2025-02-21T21:22:20Z
Range Diff ↕ rd-104
1: 57bb60c = 1: 57bb60c chore: add `rsync --delete` test
-: ------- > 2: 5c8d929 chore: test is failing on `--delete`
ps-104 by erock on 2025-02-22T02:12:03Z

Patchset ps-104

chore: add `rsync --delete` test

Eric Bower
2025-02-17T15:36:03Z
pgs/ssh_test.go
+149 -5

chore: test is failing on `--delete`

Eric Bower
2025-02-22T02:10:34Z
go.mod
+1 -0
go.sum
+2 -0
pgs/ssh_test.go
+23 -14
Back to top

chore: add `rsync --delete` test

I can't figure out how to format the `exec.Command` for rsync with `-e`
flag

Current error:

```
[rsync -rv -e "ssh -p 2222 -o IdentitiesOnly=yes -i /tmp/rsync-3246995037/id_ed25519 -o StrictHostKeyChecking=no" /tmp/rsync-3246995037/ localhost:/test]
rsync: [sender] Failed to exec ssh -p 2222 -o IdentitiesOnly=yes -i /tmp/rsync-3246995037/id_ed25519 -o StrictHostKeyChecking=no: No such file or directory (2)
rsync error: error in IPC code (code 14) at pipe.c(85) [sender=3.4.0]
rsync: connection unexpectedly closed (0 bytes received so far) [sender]
rsync error: error in IPC code (code 14) at io.c(232) [sender=3.4.0]
 exit status 14
--- FAIL: TestSshServerRsync (0.10s)
    ssh_test.go:152: exit status 14
```
pgs/ssh_test.go link
+149 -5
  1diff --git a/pgs/ssh_test.go b/pgs/ssh_test.go
  2index d30fa31..fe97468 100644
  3--- a/pgs/ssh_test.go
  4+++ b/pgs/ssh_test.go
  5@@ -3,9 +3,14 @@ package pgs
  6 import (
  7 	"crypto/ed25519"
  8 	"crypto/rand"
  9+	"crypto/x509"
 10+	"encoding/pem"
 11+	"fmt"
 12 	"io"
 13 	"log/slog"
 14 	"os"
 15+	"os/exec"
 16+	"path/filepath"
 17 	"strings"
 18 	"testing"
 19 	"time"
 20@@ -18,7 +23,7 @@ import (
 21 	"golang.org/x/crypto/ssh"
 22 )
 23 
 24-func TestSshServer(t *testing.T) {
 25+func TestSshServerSftp(t *testing.T) {
 26 	logger := slog.Default()
 27 	dbpool := pgsdb.NewDBMemory(logger)
 28 	// setup test data
 29@@ -57,9 +62,142 @@ func TestSshServer(t *testing.T) {
 30 	done <- nil
 31 }
 32 
 33+func TestSshServerRsync(t *testing.T) {
 34+	logger := slog.Default()
 35+	dbpool := pgsdb.NewDBMemory(logger)
 36+	// setup test data
 37+	dbpool.SetupTestData()
 38+	st, err := storage.NewStorageMemory(map[string]map[string]string{})
 39+	if err != nil {
 40+		panic(err)
 41+	}
 42+	cfg := NewPgsConfig(logger, dbpool, st)
 43+	done := make(chan error)
 44+	go StartSshServer(cfg, done)
 45+	// Hack to wait for startup
 46+	time.Sleep(time.Millisecond * 100)
 47+
 48+	user := GenerateUser()
 49+	key := utils.KeyForKeyText(user.signer.PublicKey())
 50+	// add user's pubkey to the default test account
 51+	dbpool.Pubkeys = append(dbpool.Pubkeys, &db.PublicKey{
 52+		ID:     "nice-pubkey",
 53+		UserID: dbpool.Users[0].ID,
 54+		Key:    key,
 55+	})
 56+
 57+	conn, err := user.NewClient()
 58+	if err != nil {
 59+		t.Error(err)
 60+		return
 61+	}
 62+	defer conn.Close()
 63+
 64+	// open an SFTP session over an existing ssh connection.
 65+	client, err := sftp.NewClient(conn)
 66+	if err != nil {
 67+		cfg.Logger.Error("could not create sftp client", "err", err)
 68+		panic(err)
 69+	}
 70+	defer client.Close()
 71+
 72+	name, err := os.MkdirTemp("", "rsync-")
 73+	if err != nil {
 74+		panic(err)
 75+	}
 76+
 77+	// remove the temporary directory at the end of the program
 78+	defer os.RemoveAll(name)
 79+
 80+	block := &pem.Block{
 81+		Type:  "PRIVATE KEY",
 82+		Bytes: user.privateKey,
 83+	}
 84+	keyFile := filepath.Join(name, "id_ed25519")
 85+	err = os.WriteFile(
 86+		keyFile,
 87+		pem.EncodeToMemory(block), 0600,
 88+	)
 89+
 90+	index := "<!doctype html><html><body>index</body></html>"
 91+	err = os.WriteFile(
 92+		filepath.Join(name, "index.html"),
 93+		[]byte(index), 0666,
 94+	)
 95+
 96+	about := "<!doctype html><html><body>about</body></html>"
 97+	aboutFile := filepath.Join(name, "about.html")
 98+	err = os.WriteFile(
 99+		aboutFile,
100+		[]byte(about), 0666,
101+	)
102+
103+	contact := "<!doctype html><html><body>contact</body></html>"
104+	err = os.WriteFile(
105+		filepath.Join(name, "contact.html"),
106+		[]byte(contact), 0666,
107+	)
108+
109+	eCmd := fmt.Sprintf(
110+		`"ssh -p 2222 -o IdentitiesOnly=yes -i %s -o StrictHostKeyChecking=no"`,
111+		keyFile,
112+	)
113+
114+	// copy files
115+	cmd := exec.Command("rsync", "-rv", "-e", eCmd, name+"/", "localhost:/test")
116+	fmt.Println(cmd.Args)
117+	result, err := cmd.CombinedOutput()
118+	if err != nil {
119+		fmt.Println(string(result), err)
120+		t.Error(err)
121+		return
122+	}
123+
124+	// check it's there
125+	fi, err := client.Lstat("about.html")
126+	if err != nil {
127+		cfg.Logger.Error("could not get stat for file", "err", err)
128+		t.Error("about.html not found")
129+		return
130+	}
131+	if fi.Size() != 0 {
132+		cfg.Logger.Error("about.html wrong size", "size", fi.Size())
133+		t.Error("about.html wrong size")
134+		return
135+	}
136+
137+	// remove about file
138+	os.Remove(aboutFile)
139+
140+	// copy files with delete
141+	delCmd := exec.Command("rsync", "-rv", "--delete", "-e", eCmd, name+"/", "localhost:/test")
142+	err = delCmd.Run()
143+	if err != nil {
144+		t.Error(err)
145+		return
146+	}
147+
148+	done <- nil
149+}
150+
151+func createTmpFile(name, contents, ext string) *os.File {
152+	file, err := os.CreateTemp("tmp", fmt.Sprintf("%s-*.%s", name, ext))
153+	if err != nil {
154+		panic(err)
155+	}
156+
157+	data := []byte(contents)
158+	if _, err := file.Write(data); err != nil {
159+		panic(err)
160+	}
161+
162+	return file
163+}
164+
165 type UserSSH struct {
166-	username string
167-	signer   ssh.Signer
168+	username   string
169+	signer     ssh.Signer
170+	privateKey []byte
171 }
172 
173 func NewUserSSH(username string, signer ssh.Signer) *UserSSH {
174@@ -146,14 +284,20 @@ func GenerateUser() UserSSH {
175 		panic(err)
176 	}
177 
178+	b, err := x509.MarshalPKCS8PrivateKey(userKey)
179+	if err != nil {
180+		panic(err)
181+	}
182+
183 	userSigner, err := ssh.NewSignerFromKey(userKey)
184 	if err != nil {
185 		panic(err)
186 	}
187 
188 	return UserSSH{
189-		username: "testuser",
190-		signer:   userSigner,
191+		username:   "testuser",
192+		signer:     userSigner,
193+		privateKey: b,
194 	}
195 }
196 

chore: test is failing on `--delete`

Alright, the test is failing because of the new `--delete` flag.

```
time=2025-02-21T21:10:11.225-05:00 level=ERROR source=/home/erock/go/pkg/mod/github.com/picosh/send@v0.0.0-20250213162645-ec2027b68462/protocols/rsync/rsync.go:157 msg="error running rsync middleware" service=pgs err="runtime error: index out of range [7876097] with length 0"
```
go.mod link
+1 -0
 1diff --git a/go.mod b/go.mod
 2index ced7737..d93cede 100644
 3--- a/go.mod
 4+++ b/go.mod
 5@@ -48,6 +48,7 @@ require (
 6 	github.com/jmoiron/sqlx v1.4.0
 7 	github.com/lib/pq v1.10.9
 8 	github.com/microcosm-cc/bluemonday v1.0.27
 9+	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
10 	github.com/minio/minio-go/v7 v7.0.80
11 	github.com/mmcdole/gofeed v1.3.0
12 	github.com/muesli/reflow v0.3.0
go.sum link
+2 -0
 1diff --git a/go.sum b/go.sum
 2index 89d665e..5ce2828 100644
 3--- a/go.sum
 4+++ b/go.sum
 5@@ -634,6 +634,8 @@ github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKju
 6 github.com/miekg/dns v1.1.45/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
 7 github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
 8 github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
 9+github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
10+github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
11 github.com/minio/madmin-go/v3 v3.0.77 h1:cqp5kVeT5anDyocvoN81puwpy+GN7t+Xdj6xCLpnONE=
12 github.com/minio/madmin-go/v3 v3.0.77/go.mod h1:ku/RUc2xeo4Uui/GHUURMoNEVXk4Ih40xA3KHLyMF1o=
13 github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
pgs/ssh_test.go link
+23 -14
  1diff --git a/pgs/ssh_test.go b/pgs/ssh_test.go
  2index fe97468..bad40a5 100644
  3--- a/pgs/ssh_test.go
  4+++ b/pgs/ssh_test.go
  5@@ -3,7 +3,6 @@ package pgs
  6 import (
  7 	"crypto/ed25519"
  8 	"crypto/rand"
  9-	"crypto/x509"
 10 	"encoding/pem"
 11 	"fmt"
 12 	"io"
 13@@ -15,6 +14,7 @@ import (
 14 	"testing"
 15 	"time"
 16 
 17+	"github.com/mikesmitty/edkey"
 18 	"github.com/picosh/pico/db"
 19 	pgsdb "github.com/picosh/pico/pgs/db"
 20 	"github.com/picosh/pico/shared/storage"
 21@@ -24,7 +24,13 @@ import (
 22 )
 23 
 24 func TestSshServerSftp(t *testing.T) {
 25-	logger := slog.Default()
 26+	opts := &slog.HandlerOptions{
 27+		AddSource: true,
 28+		Level:     slog.LevelInfo,
 29+	}
 30+	logger := slog.New(
 31+		slog.NewTextHandler(os.Stdout, opts),
 32+	)
 33 	dbpool := pgsdb.NewDBMemory(logger)
 34 	// setup test data
 35 	dbpool.SetupTestData()
 36@@ -63,7 +69,13 @@ func TestSshServerSftp(t *testing.T) {
 37 }
 38 
 39 func TestSshServerRsync(t *testing.T) {
 40-	logger := slog.Default()
 41+	opts := &slog.HandlerOptions{
 42+		AddSource: true,
 43+		Level:     slog.LevelInfo,
 44+	}
 45+	logger := slog.New(
 46+		slog.NewTextHandler(os.Stdout, opts),
 47+	)
 48 	dbpool := pgsdb.NewDBMemory(logger)
 49 	// setup test data
 50 	dbpool.SetupTestData()
 51@@ -107,10 +119,10 @@ func TestSshServerRsync(t *testing.T) {
 52 	}
 53 
 54 	// remove the temporary directory at the end of the program
 55-	defer os.RemoveAll(name)
 56+	// defer os.RemoveAll(name)
 57 
 58 	block := &pem.Block{
 59-		Type:  "PRIVATE KEY",
 60+		Type:  "OPENSSH PRIVATE KEY",
 61 		Bytes: user.privateKey,
 62 	}
 63 	keyFile := filepath.Join(name, "id_ed25519")
 64@@ -139,7 +151,7 @@ func TestSshServerRsync(t *testing.T) {
 65 	)
 66 
 67 	eCmd := fmt.Sprintf(
 68-		`"ssh -p 2222 -o IdentitiesOnly=yes -i %s -o StrictHostKeyChecking=no"`,
 69+		"ssh -p 2222 -o IdentitiesOnly=yes -i %s -o StrictHostKeyChecking=no",
 70 		keyFile,
 71 	)
 72 
 73@@ -154,13 +166,13 @@ func TestSshServerRsync(t *testing.T) {
 74 	}
 75 
 76 	// check it's there
 77-	fi, err := client.Lstat("about.html")
 78+	fi, err := client.Lstat("/test/about.html")
 79 	if err != nil {
 80 		cfg.Logger.Error("could not get stat for file", "err", err)
 81 		t.Error("about.html not found")
 82 		return
 83 	}
 84-	if fi.Size() != 0 {
 85+	if fi.Size() != 46 {
 86 		cfg.Logger.Error("about.html wrong size", "size", fi.Size())
 87 		t.Error("about.html wrong size")
 88 		return
 89@@ -171,8 +183,9 @@ func TestSshServerRsync(t *testing.T) {
 90 
 91 	// copy files with delete
 92 	delCmd := exec.Command("rsync", "-rv", "--delete", "-e", eCmd, name+"/", "localhost:/test")
 93-	err = delCmd.Run()
 94+	result, err = delCmd.CombinedOutput()
 95 	if err != nil {
 96+		fmt.Println(string(result), err)
 97 		t.Error(err)
 98 		return
 99 	}
100@@ -284,11 +297,7 @@ func GenerateUser() UserSSH {
101 		panic(err)
102 	}
103 
104-	b, err := x509.MarshalPKCS8PrivateKey(userKey)
105-	if err != nil {
106-		panic(err)
107-	}
108-
109+	b := edkey.MarshalED25519PrivateKey(userKey)
110 	userSigner, err := ssh.NewSignerFromKey(userKey)
111 	if err != nil {
112 		panic(err)