diff options
| -rw-r--r-- | _wui/doc/awwan.adoc | 25 | ||||
| -rw-r--r-- | awwan_local_test.go | 6 | ||||
| -rw-r--r-- | awwan_play_test.go | 6 | ||||
| -rw-r--r-- | session.go | 33 | ||||
| -rw-r--r-- | statement.go | 80 | ||||
| -rw-r--r-- | statement_test.go | 35 | ||||
| -rw-r--r-- | testdata/local/put.aww | 2 | ||||
| -rw-r--r-- | testdata/local/put.data | 4 | ||||
| -rw-r--r-- | testdata/play/awwanssh.test/put.aww | 2 | ||||
| -rw-r--r-- | testdata/play/awwanssh.test/put_test.data | 4 |
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 ( @@ -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 |
