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

Range-diff rd-101

title
chore: rsync test
description
Patch removed
old #1
0306c3f
new #0
(none)
title
chore: add `rsync --delete` test
description
Patch changed
old #2
971b354
new #1
745b513
Back to top
1: 0306c3f < -: ------- chore: rsync test
2: 971b354 ! 1: 745b513 chore: add `rsync --delete` test
pgs/ssh_test.go pgs/ssh_test.go
 import (
 	"crypto/ed25519"
 	"crypto/rand"
+	"crypto/x509"
+	"encoding/pem"
+	"fmt"
 	"io"
 	"log/slog"
 	"os"
+	"os/exec"
+	"path/filepath"
 	"strings"
 	"testing"
 	"time"
 	time.Sleep(time.Millisecond * 100)
 
 	user := GenerateUser()
+	key := utils.KeyForKeyText(user.signer.PublicKey())
 	// add user's pubkey to the default test account
 	dbpool.Pubkeys = append(dbpool.Pubkeys, &db.PublicKey{
 		ID:     "nice-pubkey",
 		UserID: dbpool.Users[0].ID,
-		Key:    utils.KeyForKeyText(user.signer.PublicKey()),
+		Key:    key,
 	})
 
-	/* client, err := user.NewClient()
+	conn, err := user.NewClient()
 	if err != nil {
 		t.Error(err)
 		return
 	}
+	defer conn.Close()
+
+	// open an SFTP session over an existing ssh connection.
+	client, err := sftp.NewClient(conn)
+	if err != nil {
+		cfg.Logger.Error("could not create sftp client", "err", err)
+		panic(err)
+	}
 	defer client.Close()
 
-	_, err = WriteFilesWithRsync(cfg, client, []string{"index.html", "about.html", "favicon.png", "profile.jpg"})
+	name, err := os.MkdirTemp("", "rsync-")
 	if err != nil {
+		panic(err)
+	}
+
+	// remove the temporary directory at the end of the program
+	defer os.RemoveAll(name)
+
+	block := &pem.Block{
+		Type:  "PRIVATE KEY",
+		Bytes: user.privateKey,
+	}
+	keyFile := filepath.Join(name, "id_ed25519")
+	err = os.WriteFile(
+		keyFile,
+		pem.EncodeToMemory(block), 0600,
+	)
+
+	index := "<!doctype html><html><body>index</body></html>"
+	err = os.WriteFile(
+		filepath.Join(name, "index.html"),
+		[]byte(index), 0666,
+	)
+
+	about := "<!doctype html><html><body>about</body></html>"
+	aboutFile := filepath.Join(name, "about.html")
+	err = os.WriteFile(
+		aboutFile,
+		[]byte(about), 0666,
+	)
+
+	contact := "<!doctype html><html><body>contact</body></html>"
+	err = os.WriteFile(
+		filepath.Join(name, "contact.html"),
+		[]byte(contact), 0666,
+	)
+
+	eCmd := fmt.Sprintf(
+		`"ssh -p 2222 -o IdentitiesOnly=yes -i %s -o StrictHostKeyChecking=no"`,
+		keyFile,
+	)
+
+	// copy files
+	cmd := exec.Command("rsync", "-rv", "-e", eCmd, name+"/", "localhost:/test")
+	fmt.Println(cmd.Args)
+	result, err := cmd.CombinedOutput()
+	if err != nil {
+		fmt.Println(string(result), err)
 		t.Error(err)
 		return
 	}
 
-	// test `--delete` functionality
-	_, err = WriteFilesWithRsync(cfg, client, []string{"index.html", "profile.jpg"})
+	// check it's there
+	fi, err := client.Lstat("about.html")
+	if err != nil {
+		cfg.Logger.Error("could not get stat for file", "err", err)
+		t.Error("about.html not found")
+		return
+	}
+	if fi.Size() != 0 {
+		cfg.Logger.Error("about.html wrong size", "size", fi.Size())
+		t.Error("about.html wrong size")
+		return
+	}
+
+	// remove about file
+	os.Remove(aboutFile)
+
+	// copy files with delete
+	delCmd := exec.Command("rsync", "-rv", "--delete", "-e", eCmd, name+"/", "localhost:/test")
+	err = delCmd.Run()
 	if err != nil {
 		t.Error(err)
 		return
-	} */
+	}
 
 	done <- nil
 }
 
+func createTmpFile(name, contents, ext string) *os.File {
+	file, err := os.CreateTemp("tmp", fmt.Sprintf("%s-*.%s", name, ext))
+	if err != nil {
+		panic(err)
+	}
+
+	data := []byte(contents)
+	if _, err := file.Write(data); err != nil {
+		panic(err)
+	}
+
+	return file
+}
+
 type UserSSH struct {
-	username string
-	signer   ssh.Signer
+	username   string
+	signer     ssh.Signer
+	privateKey []byte
 }
 
 func NewUserSSH(username string, signer ssh.Signer) *UserSSH {
 		panic(err)
 	}
 
+	b, err := x509.MarshalPKCS8PrivateKey(userKey)
+	if err != nil {
+		panic(err)
+	}
+
 	userSigner, err := ssh.NewSignerFromKey(userKey)
 	if err != nil {
 		panic(err)
 	}
 
 	return UserSSH{
-		username: "testuser",
-		signer:   userSigner,
+		username:   "testuser",
+		signer:     userSigner,
+		privateKey: b,
 	}
 }
 
pgs/ssh_test.go pgs/ssh_test.go
 import (
 	"crypto/ed25519"
 	"crypto/rand"
+	"crypto/x509"
+	"encoding/pem"
+	"fmt"
 	"io"
 	"log/slog"
 	"os"
+	"os/exec"
+	"path/filepath"
 	"strings"
 	"testing"
 	"time"
 	"golang.org/x/crypto/ssh"
 )
 
-func TestSshServer(t *testing.T) {
+func TestSshServerSftp(t *testing.T) {
 	logger := slog.Default()
 	dbpool := pgsdb.NewDBMemory(logger)
 	// setup test data
 	done <- nil
 }
 
+func TestSshServerRsync(t *testing.T) {
+	logger := slog.Default()
+	dbpool := pgsdb.NewDBMemory(logger)
+	// setup test data
+	dbpool.SetupTestData()
+	st, err := storage.NewStorageMemory(map[string]map[string]string{})
+	if err != nil {
+		panic(err)
+	}
+	cfg := NewPgsConfig(logger, dbpool, st)
+	done := make(chan error)
+	go StartSshServer(cfg, done)
+	// Hack to wait for startup
+	time.Sleep(time.Millisecond * 100)
+
+	user := GenerateUser()
+	key := utils.KeyForKeyText(user.signer.PublicKey())
+	// add user's pubkey to the default test account
+	dbpool.Pubkeys = append(dbpool.Pubkeys, &db.PublicKey{
+		ID:     "nice-pubkey",
+		UserID: dbpool.Users[0].ID,
+		Key:    key,
+	})
+
+	conn, err := user.NewClient()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	defer conn.Close()
+
+	// open an SFTP session over an existing ssh connection.
+	client, err := sftp.NewClient(conn)
+	if err != nil {
+		cfg.Logger.Error("could not create sftp client", "err", err)
+		panic(err)
+	}
+	defer client.Close()
+
+	name, err := os.MkdirTemp("", "rsync-")
+	if err != nil {
+		panic(err)
+	}
+
+	// remove the temporary directory at the end of the program
+	defer os.RemoveAll(name)
+
+	block := &pem.Block{
+		Type:  "PRIVATE KEY",
+		Bytes: user.privateKey,
+	}
+	keyFile := filepath.Join(name, "id_ed25519")
+	err = os.WriteFile(
+		keyFile,
+		pem.EncodeToMemory(block), 0600,
+	)
+
+	index := "<!doctype html><html><body>index</body></html>"
+	err = os.WriteFile(
+		filepath.Join(name, "index.html"),
+		[]byte(index), 0666,
+	)
+
+	about := "<!doctype html><html><body>about</body></html>"
+	aboutFile := filepath.Join(name, "about.html")
+	err = os.WriteFile(
+		aboutFile,
+		[]byte(about), 0666,
+	)
+
+	contact := "<!doctype html><html><body>contact</body></html>"
+	err = os.WriteFile(
+		filepath.Join(name, "contact.html"),
+		[]byte(contact), 0666,
+	)
+
+	eCmd := fmt.Sprintf(
+		`"ssh -p 2222 -o IdentitiesOnly=yes -i %s -o StrictHostKeyChecking=no"`,
+		keyFile,
+	)
+
+	// copy files
+	cmd := exec.Command("rsync", "-rv", "-e", eCmd, name+"/", "localhost:/test")
+	fmt.Println(cmd.Args)
+	result, err := cmd.CombinedOutput()
+	if err != nil {
+		fmt.Println(string(result), err)
+		t.Error(err)
+		return
+	}
+
+	// check it's there
+	fi, err := client.Lstat("about.html")
+	if err != nil {
+		cfg.Logger.Error("could not get stat for file", "err", err)
+		t.Error("about.html not found")
+		return
+	}
+	if fi.Size() != 0 {
+		cfg.Logger.Error("about.html wrong size", "size", fi.Size())
+		t.Error("about.html wrong size")
+		return
+	}
+
+	// remove about file
+	os.Remove(aboutFile)
+
+	// copy files with delete
+	delCmd := exec.Command("rsync", "-rv", "--delete", "-e", eCmd, name+"/", "localhost:/test")
+	err = delCmd.Run()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	done <- nil
+}
+
+func createTmpFile(name, contents, ext string) *os.File {
+	file, err := os.CreateTemp("tmp", fmt.Sprintf("%s-*.%s", name, ext))
+	if err != nil {
+		panic(err)
+	}
+
+	data := []byte(contents)
+	if _, err := file.Write(data); err != nil {
+		panic(err)
+	}
+
+	return file
+}
+
 type UserSSH struct {
-	username string
-	signer   ssh.Signer
+	username   string
+	signer     ssh.Signer
+	privateKey []byte
 }
 
 func NewUserSSH(username string, signer ssh.Signer) *UserSSH {
 		panic(err)
 	}
 
+	b, err := x509.MarshalPKCS8PrivateKey(userKey)
+	if err != nil {
+		panic(err)
+	}
+
 	userSigner, err := ssh.NewSignerFromKey(userKey)
 	if err != nil {
 		panic(err)
 	}
 
 	return UserSSH{
-		username: "testuser",
-		signer:   userSigner,
+		username:   "testuser",
+		signer:     userSigner,
+		privateKey: b,
 	}
 }
 
+func WriteFilesWithRsync(cfg *PgsConfig, conn *ssh.Client, files []string) (*os.FileInfo, error) {
+	return nil, nil
+}
+
 func WriteFileWithSftp(cfg *PgsConfig, conn *ssh.Client) (*os.FileInfo, error) {
 	// open an SFTP session over an existing ssh connection.
 	client, err := sftp.NewClient(conn)