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-102

chore: add `rsync --delete` test

Eric Bower
2025-02-17T15:36:03Z
pgs/ssh_test.go
+153 -5
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
+153 -5
  1diff --git a/pgs/ssh_test.go b/pgs/ssh_test.go
  2index d30fa31..bccd375 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,17 +284,27 @@ 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 
197+func WriteFilesWithRsync(cfg *PgsConfig, conn *ssh.Client, files []string) (*os.FileInfo, error) {
198+	return nil, nil
199+}
200+
201 func WriteFileWithSftp(cfg *PgsConfig, conn *ssh.Client) (*os.FileInfo, error) {
202 	// open an SFTP session over an existing ssh connection.
203 	client, err := sftp.NewClient(conn)