aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--_wui/doc/awwan.adoc25
-rw-r--r--awwan_local_test.go6
-rw-r--r--awwan_play_test.go6
-rw-r--r--session.go33
-rw-r--r--statement.go80
-rw-r--r--statement_test.go35
-rw-r--r--testdata/local/put.aww2
-rw-r--r--testdata/local/put.data4
-rw-r--r--testdata/play/awwanssh.test/put.aww2
-rw-r--r--testdata/play/awwanssh.test/put_test.data4
10 files changed, 152 insertions, 45 deletions
diff --git a/_wui/doc/awwan.adoc b/_wui/doc/awwan.adoc
index 07dfbcb..f8cdfa6 100644
--- a/_wui/doc/awwan.adoc
+++ b/_wui/doc/awwan.adoc
@@ -359,7 +359,7 @@ The magic word "#put" copy file from your local to remote server.
Syntax,
----
- PUT = "#put" (":"/"!") [OWNER] ["+" PERM] SP LOCAL_PATH SP REMOTE_PATH
+ PUT = "#put" (":"/"!") [OWNER] ["+" PERM] *(OPT) SP LOCAL_PATH SP REMOTE_PATH
OWNER = [ USER ] [ ":" GROUP ]
@@ -368,6 +368,8 @@ OWNER = [ USER ] [ ":" GROUP ]
OCTAL = "0" ... "7"
SP = " " / "\t" ; Space characters.
+
+ OPT = "$noparse"
----
For example,
@@ -415,6 +417,27 @@ $ sudo chmod 0600 remote/dst
$ sudo chown root:bin remote/dst
----
+The "#put" command have the following options,
+
+`$noparse`:: Skip parsing the input file.
++
+--
+The input file will be copied as is without parsing and replacing session
+variables inside the file.
+This is useful if the input file contains "{{}}" string.
+
+For example,
+----
+#put:$noparse src dst
+----
+If the content of "src" is
+
+ Hello {{.world}}
+
+then the "dst" file also has the same content.
+--
+
+
=== Magic word "#local"
The magic word "#local" define the command to be executed in the local
diff --git a/awwan_local_test.go b/awwan_local_test.go
index a36be2e..2dd590a 100644
--- a/awwan_local_test.go
+++ b/awwan_local_test.go
@@ -313,6 +313,12 @@ func TestAwwanLocal_Put(t *testing.T) {
lineRange: `12`,
fileDest: filepath.Join(baseDir, `tmp`, `put_with_owner.txt`),
expError: `Local: Copy: chown audio:audio: exit status 1`,
+ }, {
+ desc: `With $noparse`,
+ lineRange: `18`,
+ fileDest: filepath.Join(baseDir, `tmp`, `plain_noparse.txt`),
+ expContent: string(tdata.Output[`tmp/plain_noparse.txt`]),
+ expMode: 0600,
}}
var (
diff --git a/awwan_play_test.go b/awwan_play_test.go
index 44a9ba1..00d407d 100644
--- a/awwan_play_test.go
+++ b/awwan_play_test.go
@@ -221,6 +221,12 @@ func TestAwwan_Play_Put(t *testing.T) {
expContent: string(tdata.Output[`plain.txt`]),
expMode: 0666,
expError: string(tdata.Output[`WithOwner:error`]),
+ }, {
+ desc: `With $noparse`,
+ lineRange: `20`,
+ fileDest: `/home/awwanssh/put_noparse.txt`,
+ expContent: string(tdata.Output[`plain_noparse.txt`]),
+ expMode: 0666,
}}
var (
diff --git a/session.go b/session.go
index aa5c141..42fd0a4 100644
--- a/session.go
+++ b/session.go
@@ -125,7 +125,7 @@ func (ses *Session) Copy(req *ExecRequest, stmt *Statement) (err error) {
case statementKindGet, statementKindSudoGet:
// NO-OP.
case statementKindPut, statementKindSudoPut:
- src, isVault, err = ses.generateFileInput(src)
+ src, isVault, err = ses.generateFileInput(stmt, src)
if err != nil {
return fmt.Errorf(`%s: %w`, logp, err)
}
@@ -197,7 +197,7 @@ func (ses *Session) Put(ctx context.Context, req *ExecRequest, stmt *Statement)
isVault bool
)
- src, isVault, err = ses.generateFileInput(src)
+ src, isVault, err = ses.generateFileInput(stmt, src)
if err != nil {
return fmt.Errorf("%s: %w", logp, err)
}
@@ -241,7 +241,7 @@ func (ses *Session) SudoCopy(ctx context.Context, req *ExecRequest, stmt *Statem
case statementKindGet, statementKindSudoGet:
// NO-OP.
case statementKindPut, statementKindSudoPut:
- src, isVault, err = ses.generateFileInput(src)
+ src, isVault, err = ses.generateFileInput(stmt, src)
if err != nil {
return fmt.Errorf(`%s: %w`, logp, err)
}
@@ -338,7 +338,7 @@ func (ses *Session) SudoPut(ctx context.Context, req *ExecRequest, stmt *Stateme
isVault bool
)
- src, isVault, err = ses.generateFileInput(src)
+ src, isVault, err = ses.generateFileInput(stmt, src)
if err != nil {
return fmt.Errorf("%s: %w", logp, err)
}
@@ -544,30 +544,27 @@ func (ses *Session) executeScriptOnRemote(ctx context.Context, req *ExecRequest,
//
// For example, if the input file path is "{{.BaseDir}}/a/b/script" then the
// output file path would be "{{.BaseDir}}/.cache/a/b/script".
-func (ses *Session) generateFileInput(in string) (out string, isVault bool, err error) {
+func (ses *Session) generateFileInput(stmt *Statement, in string) (out string, isVault bool, err error) {
// Check if the file is binary first, since binary file will not get
// encrypted.
if !strings.HasSuffix(in, defEncryptExt) && libos.IsBinary(in) {
return in, false, nil
}
- var (
- logp = `generateFileInput`
- relPathInput = relativePath(ses.BaseDir, in)
-
- contentInput []byte
- )
+ var logp = `generateFileInput`
+ var newContent []byte
- contentInput, isVault, err = ses.loadFileInput(in)
+ newContent, isVault, err = ses.loadFileInput(in)
if err != nil {
+ var relPathInput = relativePath(ses.BaseDir, in)
return ``, false, fmt.Errorf(`%s %q: %w`, logp, relPathInput, err)
}
- var contentOut []byte
-
- contentOut, err = ses.render(in, contentInput)
- if err != nil {
- return ``, false, fmt.Errorf(`%s: %w`, logp, err)
+ if !stmt.optNoparse {
+ newContent, err = ses.render(in, newContent)
+ if err != nil {
+ return ``, false, fmt.Errorf(`%s: %w`, logp, err)
+ }
}
var (
@@ -582,7 +579,7 @@ func (ses *Session) generateFileInput(in string) (out string, isVault bool, err
out = filepath.Join(outDir, base)
- err = os.WriteFile(out, contentOut, 0600)
+ err = os.WriteFile(out, newContent, 0600)
if err != nil {
return ``, false, fmt.Errorf(`%s: %s: %w`, logp, out, err)
}
diff --git a/statement.go b/statement.go
index 1d33e8e..69cb60b 100644
--- a/statement.go
+++ b/statement.go
@@ -11,6 +11,7 @@ import (
"strconv"
"strings"
+ libascii "git.sr.ht/~shulhan/pakakeh.go/lib/ascii"
libexec "git.sr.ht/~shulhan/pakakeh.go/lib/os/exec"
)
@@ -53,6 +54,10 @@ type Statement struct {
raw []byte
mode fs.FileMode
kind int
+
+ // Option "$noparse" copy the file directly without parsing for
+ // session variables inside the file.
+ optNoparse bool
}
// ParseStatement create and initialize new Statement from raw line.
@@ -180,7 +185,7 @@ func parseStatementGetPut(kind int, raw []byte) (stmt *Statement, err error) {
}
if raw[0] != ' ' && raw[0] != '\t' {
- raw, err = stmt.parseOwnerMode(raw)
+ raw, err = stmt.parseGetPutOptions(raw)
if err != nil {
return nil, err
}
@@ -203,49 +208,72 @@ func parseStatementGetPut(kind int, raw []byte) (stmt *Statement, err error) {
return stmt, nil
}
-// parseOwnerMode parse the owner and optionally the file mode for
+// parseGetPutOptions parse the owner and optionally the file mode for
// destination file.
// The owner and mode has the following syntax,
//
-// [ USER [ ":" GROUP ]][ "+" MODE ]
+// [ USER [ ":" GROUP ]][ "+" MODE ][ "$" OPT ]
//
// The USER and/or GROUP is optional, its accept the value as in "chown".
// The MODE also optional, its value must be an octal.
-func (stmt *Statement) parseOwnerMode(in []byte) (out []byte, err error) {
- var (
- sepSpace = " \t"
- sepMode = "+"
-
- tmp []byte
- idx int
- )
-
- idx = bytes.IndexAny(in, sepSpace)
+// The OPT also optional, its affect how the file processed before copying.
+func (stmt *Statement) parseGetPutOptions(in []byte) (out []byte, err error) {
+ var idx = bytes.IndexAny(in, " \t")
if idx < 0 {
return nil, nil
}
- tmp = in[:idx]
+ var tmp = in[:idx]
out = in[idx+1:]
- idx = bytes.IndexAny(tmp, sepMode)
- if idx < 0 {
- stmt.owner = string(tmp)
- } else {
- stmt.owner = string(tmp[:idx])
-
- var (
- modeString = string(tmp[idx+1:])
- mode uint64
- )
-
+ if libascii.IsAlpha(tmp[0]) {
+ idx = bytes.IndexAny(tmp, "+$")
+ if idx < 0 {
+ stmt.owner = string(tmp)
+ tmp = nil
+ } else {
+ stmt.owner = string(tmp[:idx])
+ tmp = tmp[idx:]
+ }
+ }
+ if len(tmp) == 0 {
+ return out, nil
+ }
+ if tmp[0] == '+' {
+ tmp = tmp[1:]
+ idx = bytes.IndexAny(tmp, "$")
+ var modeString string
+ if idx < 0 {
+ modeString = string(tmp)
+ tmp = nil
+ } else {
+ modeString = string(tmp[:idx])
+ tmp = tmp[idx:]
+ }
+ var mode uint64
mode, err = strconv.ParseUint(modeString, 8, 32)
if err != nil {
return nil, err
}
-
stmt.mode = fs.FileMode(mode)
}
+ var opt string
+ for len(tmp) > 0 {
+ tmp = tmp[1:]
+ idx = bytes.IndexAny(tmp, "$")
+ if idx < 0 {
+ opt = string(tmp)
+ tmp = nil
+ } else {
+ opt = string(tmp[:idx])
+ tmp = tmp[idx:]
+ }
+ opt = strings.ToLower(opt)
+ switch opt {
+ case "noparse":
+ stmt.optNoparse = true
+ }
+ }
return out, nil
}
diff --git a/statement_test.go b/statement_test.go
index 88981fa..760788e 100644
--- a/statement_test.go
+++ b/statement_test.go
@@ -145,6 +145,41 @@ func TestParseStatement(t *testing.T) {
args: []string{`a`},
raw: []byte(` echo "a"`),
},
+ }, {
+ raw: []byte(`#put:user:group+0700$noparse src dst`),
+ exp: &Statement{
+ kind: statementKindPut,
+ owner: `user:group`,
+ mode: 0700,
+ args: []string{`src`, `dst`},
+ raw: []byte(`src dst`),
+ optNoparse: true,
+ },
+ }, {
+ raw: []byte(`#put:+0700$noparse src dst`),
+ exp: &Statement{
+ kind: statementKindPut,
+ mode: 0700,
+ args: []string{`src`, `dst`},
+ raw: []byte(`src dst`),
+ optNoparse: true,
+ },
+ }, {
+ raw: []byte(`#put:$noparse src dst`),
+ exp: &Statement{
+ kind: statementKindPut,
+ args: []string{`src`, `dst`},
+ raw: []byte(`src dst`),
+ optNoparse: true,
+ },
+ }, {
+ raw: []byte(`#put:$unknown$noparse src dst`),
+ exp: &Statement{
+ kind: statementKindPut,
+ args: []string{`src`, `dst`},
+ raw: []byte(`src dst`),
+ optNoparse: true,
+ },
}}
var (
diff --git a/testdata/local/put.aww b/testdata/local/put.aww
index c076bd7..82297e5 100644
--- a/testdata/local/put.aww
+++ b/testdata/local/put.aww
@@ -14,3 +14,5 @@ sudo chmod 0644 /etc/plain.txt
#put!+0516 {{.ScriptDir}}/plain.txt /etc/sudoput_with_mode.txt
#put!awwan:bin+0644 {{.ScriptDir}}/plain.txt /etc/sudoput_with_owner.txt
+
+#put:$noparse {{.ScriptDir}}/plain.txt {{.ScriptDir}}/tmp/plain_noparse.txt
diff --git a/testdata/local/put.data b/testdata/local/put.data
index e457391..db50a6d 100644
--- a/testdata/local/put.data
+++ b/testdata/local/put.data
@@ -2,6 +2,10 @@
The host name is encrypt.
+<<< tmp/plain_noparse.txt
+The host name is {{.Val "host::name"}}.
+
+
<<< missing_val_encrypted
Local: Copy: generateFileInput: template: missing_val_encrypted.txt:2:25: executing "missing_val_encrypted.txt" at <.Val>: error calling Val: "secret::pass" is empty
diff --git a/testdata/play/awwanssh.test/put.aww b/testdata/play/awwanssh.test/put.aww
index e419a4f..0f6aa8c 100644
--- a/testdata/play/awwanssh.test/put.aww
+++ b/testdata/play/awwanssh.test/put.aww
@@ -16,3 +16,5 @@
#put!awwan:bin {{.ScriptDir}}/plain.txt sudoput_with_owner.txt
#put!awwan:bin+602 {{.ScriptDir}}/plain.txt sudoput_with_owner_mode.txt
+
+#put:+666$noparse {{.ScriptDir}}/plain.txt put_noparse.txt
diff --git a/testdata/play/awwanssh.test/put_test.data b/testdata/play/awwanssh.test/put_test.data
index 61ac09c..0dccc05 100644
--- a/testdata/play/awwanssh.test/put_test.data
+++ b/testdata/play/awwanssh.test/put_test.data
@@ -4,6 +4,10 @@ Test input and output for "#put".
The host name is awwanssh.test.
+<<< plain_noparse.txt
+The host name is {{.Val "host::name"}}.
+
+
<<< WithoutPermission:error
Play: Put: Put: permission denied