aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2023-10-21 17:17:13 +0700
committerShulhan <ms@kilabit.info>2023-10-21 17:17:13 +0700
commit761212298e017f58345aa948aebedc2c07b73753 (patch)
tree28c556aa99678979a4162aa0e50d036d287a96ad
parent28f9ab76a83efcfcf777e571fea826e2dfc3e28e (diff)
downloadawwan-761212298e017f58345aa948aebedc2c07b73753.tar.xz
all: implement remote "#get:" and "#put:" with owner and mode
In remote environment, using magic command "#get:" or "#put:" with owner and mode like "#get:$OWNER+$MODE" or "#put:$OWNER+MODE" will changes the file owner to $USER or $GROUP and/or permission to $MODE. The file owner will not works if user does not have permission.
-rw-r--r--.gitignore1
-rw-r--r--awwan.go5
-rw-r--r--awwan_play_test.go192
-rw-r--r--session.go40
-rw-r--r--ssh_client.go25
-rw-r--r--testdata/play/awwanssh.test/awwan.env2
-rw-r--r--testdata/play/awwanssh.test/get.aww12
-rw-r--r--testdata/play/awwanssh.test/get_test.data20
-rw-r--r--testdata/play/awwanssh.test/plain.txt1
-rw-r--r--testdata/play/awwanssh.test/put.aww12
-rw-r--r--testdata/play/awwanssh.test/put_test.data12
11 files changed, 315 insertions, 7 deletions
diff --git a/.gitignore b/.gitignore
index 018f06b..d59cd10 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,4 +25,5 @@
/testdata/encrypt-without-passphrase/.awwan.env.vault
/testdata/local/.cache
/testdata/local/put_with_mode.txt
+/testdata/play/.cache/
/www-awwan
diff --git a/awwan.go b/awwan.go
index d2ed1f5..1f12aa8 100644
--- a/awwan.go
+++ b/awwan.go
@@ -292,7 +292,10 @@ func (aww *Awwan) Play(req *Request) (err error) {
return fmt.Errorf("%s: %w", logp, err)
}
- ses.executeScriptOnRemote(req, pos)
+ err = ses.executeScriptOnRemote(req, pos)
+ if err != nil {
+ return fmt.Errorf(`%s: %w`, logp, err)
+ }
}
return nil
diff --git a/awwan_play_test.go b/awwan_play_test.go
new file mode 100644
index 0000000..7bcc827
--- /dev/null
+++ b/awwan_play_test.go
@@ -0,0 +1,192 @@
+// SPDX-FileCopyrightText: 2023 M. Shulhan <ms@kilabit.info>
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+//go:build integration
+
+package awwan
+
+import (
+ "io/fs"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/shuLhan/share/lib/test"
+)
+
+type testCaseGetPut struct {
+ desc string
+ fileDest string
+ lineRange string
+
+ expContent string
+ expError string
+
+ expMode fs.FileMode
+}
+
+func TestAwwan_Play_Get(t *testing.T) {
+ var (
+ baseDir = `testdata/play`
+ scriptDir = filepath.Join(baseDir, `awwanssh.test`)
+ scriptFile = filepath.Join(scriptDir, `get.aww`)
+
+ tdata *test.Data
+ aww *Awwan
+ err error
+ )
+
+ tdata, err = test.LoadData(filepath.Join(scriptDir, `get_test.data`))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ aww, err = New(baseDir)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var cases = []testCaseGetPut{{
+ desc: `WithoutPermission`,
+ lineRange: `3`,
+ expError: string(tdata.Output[`WithoutPermission:error`]),
+ }, {
+ desc: `WithMode`,
+ lineRange: `7`,
+ fileDest: filepath.Join(scriptDir, `tmp`, `get_with_mode.txt`),
+ expContent: string(tdata.Output[`/etc/os-release`]),
+ expMode: 0624,
+ }, {
+ desc: `WithOwner`,
+ lineRange: `12`,
+ fileDest: filepath.Join(scriptDir, `tmp`, `get_with_owner.txt`),
+ expContent: string(tdata.Output[`/etc/os-release`]),
+ expMode: 0644,
+ expError: string(tdata.Output[`WithOwner:error`]),
+ }}
+
+ var (
+ c testCaseGetPut
+ fi os.FileInfo
+ gotContent []byte
+ )
+
+ for _, c = range cases {
+ t.Log(c.desc)
+
+ if len(c.fileDest) != 0 {
+ _ = os.Remove(c.fileDest)
+ }
+
+ var req = NewRequest(CommandModePlay, scriptFile, c.lineRange)
+
+ err = aww.Play(req)
+ if err != nil {
+ test.Assert(t, `play error`, c.expError, err.Error())
+ }
+
+ if len(c.fileDest) == 0 {
+ continue
+ }
+
+ // File successfully copied but maybe error when setting
+ // owner or permission.
+
+ gotContent, err = os.ReadFile(c.fileDest)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ test.Assert(t, `content`, c.expContent, string(gotContent))
+
+ fi, err = os.Stat(c.fileDest)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ test.Assert(t, `mode`, c.expMode, fi.Mode().Perm())
+ }
+}
+
+func TestAwwan_Play_Put(t *testing.T) {
+ var (
+ baseDir = `testdata/play`
+ scriptDir = filepath.Join(baseDir, `awwanssh.test`)
+ scriptFile = filepath.Join(scriptDir, `put.aww`)
+
+ tdata *test.Data
+ aww *Awwan
+ err error
+ )
+
+ tdata, err = test.LoadData(filepath.Join(scriptDir, `put_test.data`))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ aww, err = New(baseDir)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var cases = []testCaseGetPut{{
+ desc: `WithoutPermission`,
+ lineRange: `3`,
+ expError: string(tdata.Output[`WithoutPermission:error`]),
+ }, {
+ desc: `WithMode`,
+ lineRange: `7`,
+ fileDest: `/home/awwanssh/put_with_mode.txt`,
+ expContent: string(tdata.Output[`plain.txt`]),
+ expMode: 0624,
+ }, {
+ desc: `WithOwner`,
+ lineRange: `12`,
+ fileDest: `/home/awwanssh/put_with_owner.txt`,
+ expContent: string(tdata.Output[`plain.txt`]),
+ expMode: 0666,
+ expError: string(tdata.Output[`WithOwner:error`]),
+ }}
+
+ var (
+ c testCaseGetPut
+ fi os.FileInfo
+ gotContent []byte
+ )
+
+ for _, c = range cases {
+ t.Log(c.desc)
+
+ if len(c.fileDest) != 0 {
+ _ = os.Remove(c.fileDest)
+ }
+
+ var req = NewRequest(CommandModePlay, scriptFile, c.lineRange)
+
+ err = aww.Play(req)
+ if err != nil {
+ test.Assert(t, `play error`, c.expError, err.Error())
+ }
+
+ if len(c.fileDest) == 0 {
+ continue
+ }
+
+ // File successfully copied but maybe error when setting
+ // owner or permission.
+
+ gotContent, err = os.ReadFile(c.fileDest)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ test.Assert(t, `content`, c.expContent, string(gotContent))
+
+ fi, err = os.Stat(c.fileDest)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ test.Assert(t, `mode`, c.expMode, fi.Mode().Perm())
+ }
+}
diff --git a/session.go b/session.go
index a4b43b2..4cd746d 100644
--- a/session.go
+++ b/session.go
@@ -170,13 +170,29 @@ func (ses *Session) Copy(stmt *Statement) (err error) {
// Get copy file from remote to local.
func (ses *Session) Get(stmt *Statement) (err error) {
- var logp = "Get"
+ var (
+ logp = `Get`
+ src = stmt.args[0]
+ dst = stmt.args[1]
+ )
- err = ses.sshc.get(stmt.args[0], stmt.args[1])
+ err = ses.sshc.get(src, dst)
if err != nil {
return fmt.Errorf(`%s: %w`, logp, err)
}
-
+ if stmt.mode != 0 {
+ err = os.Chmod(dst, stmt.mode)
+ if err != nil {
+ return fmt.Errorf(`%s: chmod %o %q: %w`, logp, stmt.mode, dst, err)
+ }
+ }
+ if len(stmt.owner) != 0 {
+ var chownStmt = fmt.Sprintf(`chown %s %q`, stmt.owner, dst)
+ err = libexec.Run(chownStmt, nil, nil)
+ if err != nil {
+ return fmt.Errorf(`%s: %s: %w`, logp, chownStmt, err)
+ }
+ }
return nil
}
@@ -205,6 +221,18 @@ func (ses *Session) Put(stmt *Statement) (err error) {
if err != nil {
return fmt.Errorf("%s: %w", logp, err)
}
+ if stmt.mode != 0 {
+ err = ses.sshc.chmod(dst, stmt.mode)
+ if err != nil {
+ return fmt.Errorf(`%s: %w`, logp, err)
+ }
+ }
+ if len(stmt.owner) != 0 {
+ err = ses.sshc.chown(dst, stmt.owner)
+ if err != nil {
+ return fmt.Errorf(`%s: %w`, logp, err)
+ }
+ }
return nil
}
@@ -416,7 +444,7 @@ func (ses *Session) executeScriptOnLocal(req *Request, pos linePosition) (err er
return nil
}
-func (ses *Session) executeScriptOnRemote(req *Request, pos linePosition) {
+func (ses *Session) executeScriptOnRemote(req *Request, pos linePosition) (err error) {
var max = int64(len(req.script.stmts))
if pos.start > max {
return
@@ -440,7 +468,6 @@ func (ses *Session) executeScriptOnRemote(req *Request, pos linePosition) {
fmt.Fprintf(req.stdout, "\n--> %s: %3d: %s\n",
ses.sshc.conn, x, stmt.String())
- var err error
switch stmt.kind {
case statementKindDefault:
err = ses.sshc.conn.Execute(string(stmt.raw))
@@ -455,9 +482,10 @@ func (ses *Session) executeScriptOnRemote(req *Request, pos linePosition) {
}
if err != nil {
fmt.Fprintf(req.stderr, "!!! %s\n", err)
- break
+ return err
}
}
+ return nil
}
// generateFileInput read the content of file input "in", apply the session
diff --git a/ssh_client.go b/ssh_client.go
index 71f3b3b..692f050 100644
--- a/ssh_client.go
+++ b/ssh_client.go
@@ -6,6 +6,7 @@ package awwan
import (
"fmt"
"io"
+ "io/fs"
"path/filepath"
"github.com/shuLhan/share/lib/ascii"
@@ -81,6 +82,30 @@ func newSshClient(section *config.Section, dirTmp string, stdout, stderr io.Writ
return sshc, nil
}
+// chmod change the remoteFile permission.
+func (sshc *sshClient) chmod(remoteFile string, perm fs.FileMode) (err error) {
+ var chmodStmt = fmt.Sprintf(`chmod %o %q`, perm, remoteFile)
+
+ err = sshc.conn.Execute(chmodStmt)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// chown change the owner of remoteFile.
+// The owner parameter can be set to user only "user", group only
+// ":group", or user and group "user:group".
+func (sshc *sshClient) chown(remoteFile, owner string) (err error) {
+ var chownStmt = fmt.Sprintf(`chown %s %q`, owner, remoteFile)
+
+ err = sshc.conn.Execute(chownStmt)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
// get the remote file and write it to local path.
func (sshc *sshClient) get(remote, local string) (err error) {
if sshc.sftpc == nil {
diff --git a/testdata/play/awwanssh.test/awwan.env b/testdata/play/awwanssh.test/awwan.env
new file mode 100644
index 0000000..642535b
--- /dev/null
+++ b/testdata/play/awwanssh.test/awwan.env
@@ -0,0 +1,2 @@
+[host]
+name = awwanssh.test
diff --git a/testdata/play/awwanssh.test/get.aww b/testdata/play/awwanssh.test/get.aww
new file mode 100644
index 0000000..c10ae27
--- /dev/null
+++ b/testdata/play/awwanssh.test/get.aww
@@ -0,0 +1,12 @@
+## Get without read permission.
+
+#get: /etc/shadow {{.ScriptDir}}/tmp/shadow
+
+## Get and changes file permission.
+
+#get:+624 /etc/os-release {{.ScriptDir}}/tmp/get_with_mode.txt
+
+## Setting group to "bin" should be an error since current user does not have
+## privileged.
+
+#get:awwan:bin /etc/os-release {{.ScriptDir}}/tmp/get_with_owner.txt
diff --git a/testdata/play/awwanssh.test/get_test.data b/testdata/play/awwanssh.test/get_test.data
new file mode 100644
index 0000000..ad4577d
--- /dev/null
+++ b/testdata/play/awwanssh.test/get_test.data
@@ -0,0 +1,20 @@
+Test input and output for "#get".
+
+<<< WithoutPermission:error
+Play: Get: Get: permission denied
+
+<<< /etc/os-release
+NAME="Arch Linux"
+PRETTY_NAME="Arch Linux"
+ID=arch
+BUILD_ID=rolling
+ANSI_COLOR="38;2;23;147;209"
+HOME_URL="https://archlinux.org/"
+DOCUMENTATION_URL="https://wiki.archlinux.org/"
+SUPPORT_URL="https://bbs.archlinux.org/"
+BUG_REPORT_URL="https://bugs.archlinux.org/"
+PRIVACY_POLICY_URL="https://terms.archlinux.org/docs/privacy-policy/"
+LOGO=archlinux-logo
+
+<<< WithOwner:error
+Play: Get: chown awwan:bin "/home/awwan/src/testdata/play/awwanssh.test/tmp/get_with_owner.txt": exit status 1
diff --git a/testdata/play/awwanssh.test/plain.txt b/testdata/play/awwanssh.test/plain.txt
new file mode 100644
index 0000000..5067c47
--- /dev/null
+++ b/testdata/play/awwanssh.test/plain.txt
@@ -0,0 +1 @@
+The host name is {{.Val "host::name"}}.
diff --git a/testdata/play/awwanssh.test/put.aww b/testdata/play/awwanssh.test/put.aww
new file mode 100644
index 0000000..9779ed2
--- /dev/null
+++ b/testdata/play/awwanssh.test/put.aww
@@ -0,0 +1,12 @@
+## Put without write permission.
+
+#put: {{.ScriptDir}}/plain.txt /etc/plain.txt
+
+## Put and changes file permission.
+
+#put:+624 {{.ScriptDir}}/plain.txt put_with_mode.txt
+
+## Setting group to "bin" should be an error since current user does not have
+## privileged.
+
+#put:awwan:bin+666 {{.ScriptDir}}/plain.txt put_with_owner.txt
diff --git a/testdata/play/awwanssh.test/put_test.data b/testdata/play/awwanssh.test/put_test.data
new file mode 100644
index 0000000..61ac09c
--- /dev/null
+++ b/testdata/play/awwanssh.test/put_test.data
@@ -0,0 +1,12 @@
+Test input and output for "#put".
+
+<<< plain.txt
+The host name is awwanssh.test.
+
+
+<<< WithoutPermission:error
+Play: Put: Put: permission denied
+
+<<< WithOwner:error
+Play: Put: ssh: Run "chown awwan:bin \"put_with_owner.txt\"": Process exited with status 1
+