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