mirror of
https://github.com/kubesphere/kubekey.git
synced 2025-12-26 01:22:51 +00:00
feat: feat no root ssh (#2858)
feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh Signed-off-by: xuesongzuo@yunify.com <xuesongzuo@yunify.com>
This commit is contained in:
parent
c7b0b113c6
commit
d9c699f80a
|
|
@ -130,7 +130,14 @@
|
||||||
loop: "{{ .cloud_config.runcmd | toJson }}"
|
loop: "{{ .cloud_config.runcmd | toJson }}"
|
||||||
command: "{{ .item }}"
|
command: "{{ .item }}"
|
||||||
|
|
||||||
- name: Sync kubeconfig
|
- name: Sync kubeconfig to current user
|
||||||
|
copy:
|
||||||
|
src: >-
|
||||||
|
{{ .cloud_config_dir }}/kubeconfig/value
|
||||||
|
dest: ~/.kube/config
|
||||||
|
mode: 0600
|
||||||
|
|
||||||
|
- name: Sync kubeconfig to root
|
||||||
copy:
|
copy:
|
||||||
src: >-
|
src: >-
|
||||||
{{ .cloud_config_dir }}/kubeconfig/value
|
{{ .cloud_config_dir }}/kubeconfig/value
|
||||||
|
|
|
||||||
|
|
@ -29,5 +29,6 @@
|
||||||
rm -rf /usr/local/bin/kubeadm && rm -rf /usr/local/bin/kubelet && rm -rf /usr/local/bin/kubectl
|
rm -rf /usr/local/bin/kubeadm && rm -rf /usr/local/bin/kubelet && rm -rf /usr/local/bin/kubectl
|
||||||
rm -rf /var/lib/kubelet/
|
rm -rf /var/lib/kubelet/
|
||||||
rm -rf /etc/kubernetes/
|
rm -rf /etc/kubernetes/
|
||||||
rm -rf .kube/config
|
rm -rf ~/.kube/config
|
||||||
|
rm -rf /root/.kube/config
|
||||||
rm -rf /var/lib/etcd
|
rm -rf /var/lib/etcd
|
||||||
|
|
@ -42,8 +42,14 @@
|
||||||
dest: >-
|
dest: >-
|
||||||
{{ .binary_dir }}/kubeconfig
|
{{ .binary_dir }}/kubeconfig
|
||||||
|
|
||||||
- name: Kubernetes | Distribute kubeconfig to remote host
|
- name: Kubernetes | Distribute kubeconfig to remote host of current user
|
||||||
copy:
|
copy:
|
||||||
src: >-
|
src: >-
|
||||||
{{ .binary_dir }}/kubeconfig
|
{{ .binary_dir }}/kubeconfig
|
||||||
dest: /root/.kube/config
|
dest: ~/.kube/config
|
||||||
|
|
||||||
|
- name: Kubernetes | Distribute kubeconfig to remote host of root user
|
||||||
|
copy:
|
||||||
|
src: >-
|
||||||
|
{{ .binary_dir }}/kubeconfig
|
||||||
|
dest: /root/.kube/config
|
||||||
|
|
@ -45,9 +45,13 @@
|
||||||
|
|
||||||
- name: Init | Copy kubeconfig to default directory
|
- name: Init | Copy kubeconfig to default directory
|
||||||
command: |
|
command: |
|
||||||
|
if [ ! -d ~/.kube ]; then
|
||||||
|
mkdir -p ~/.kube
|
||||||
|
fi
|
||||||
if [ ! -d /root/.kube ]; then
|
if [ ! -d /root/.kube ]; then
|
||||||
mkdir -p /root/.kube
|
mkdir -p /root/.kube
|
||||||
fi
|
fi
|
||||||
|
cp -f /etc/kubernetes/admin.conf ~/.kube/config
|
||||||
cp -f /etc/kubernetes/admin.conf /root/.kube/config
|
cp -f /etc/kubernetes/admin.conf /root/.kube/config
|
||||||
when: .kubernetes_install_LoadState.stdout | eq "not-found"
|
when: .kubernetes_install_LoadState.stdout | eq "not-found"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,13 @@
|
||||||
command: |
|
command: |
|
||||||
/usr/local/bin/kubeadm join --config=/etc/kubernetes/kubeadm-config.yaml --ignore-preflight-errors=FileExisting-crictl,ImagePull
|
/usr/local/bin/kubeadm join --config=/etc/kubernetes/kubeadm-config.yaml --ignore-preflight-errors=FileExisting-crictl,ImagePull
|
||||||
|
|
||||||
- name: Join | Synchronize kubeconfig to remote node
|
- name: Join | Synchronize kubeconfig to remote node for current user
|
||||||
|
copy:
|
||||||
|
src: >-
|
||||||
|
{{ .work_dir }}/kubekey/kubeconfig
|
||||||
|
dest: ~/.kube/config
|
||||||
|
|
||||||
|
- name: Join | Synchronize kubeconfig to remote node for root
|
||||||
copy:
|
copy:
|
||||||
src: >-
|
src: >-
|
||||||
{{ .work_dir }}/kubekey/kubeconfig
|
{{ .work_dir }}/kubekey/kubeconfig
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,6 @@
|
||||||
# If /var/log/pods/ is not cleaned up, static pods may accumulate unexpected restarts due to lingering log files interfering with their lifecycle.
|
# If /var/log/pods/ is not cleaned up, static pods may accumulate unexpected restarts due to lingering log files interfering with their lifecycle.
|
||||||
rm -rf /var/log/pods/
|
rm -rf /var/log/pods/
|
||||||
rm -rf /etc/kubernetes/
|
rm -rf /etc/kubernetes/
|
||||||
rm -rf .kube/config
|
rm -rf ~/.kube/config
|
||||||
|
rm -rf /root/.kube/config
|
||||||
rm -rf /var/lib/etcd
|
rm -rf /var/lib/etcd
|
||||||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
package connector
|
package connector
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -26,6 +27,7 @@ import (
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
"github.com/cockroachdb/errors"
|
||||||
|
|
@ -120,6 +122,8 @@ type sshConnector struct {
|
||||||
shell string
|
shell string
|
||||||
|
|
||||||
gatherFacts *cacheGatherFact
|
gatherFacts *cacheGatherFact
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init connector, get ssh.Client
|
// Init connector, get ssh.Client
|
||||||
|
|
@ -188,6 +192,33 @@ func (c *sshConnector) Close(context.Context) error {
|
||||||
return c.client.Close()
|
return c.client.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *sshConnector) session() (*ssh.Session, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
if c.client == nil {
|
||||||
|
return nil, errors.New("connection closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
sess, err := c.client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
modes := ssh.TerminalModes{
|
||||||
|
ssh.ECHO: 0, // disable echoing
|
||||||
|
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
|
||||||
|
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sess.RequestPty("xterm", 100, 50, modes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess, nil
|
||||||
|
}
|
||||||
|
|
||||||
// PutFile to remote node. src is the file bytes. dst is the remote filename
|
// PutFile to remote node. src is the file bytes. dst is the remote filename
|
||||||
func (c *sshConnector) PutFile(_ context.Context, src []byte, dst string, mode fs.FileMode) error {
|
func (c *sshConnector) PutFile(_ context.Context, src []byte, dst string, mode fs.FileMode) error {
|
||||||
// create sftp client
|
// create sftp client
|
||||||
|
|
@ -243,72 +274,78 @@ func (c *sshConnector) FetchFile(_ context.Context, src string, dst io.Writer) e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecuteCommand in remote host
|
// ExecuteCommand exec cmd with sudo
|
||||||
func (c *sshConnector) ExecuteCommand(_ context.Context, cmd string) ([]byte, []byte, error) {
|
func (c *sshConnector) ExecuteCommand(_ context.Context, cmd string) ([]byte, []byte, error) {
|
||||||
cmd = fmt.Sprintf("sudo -SE %s << 'KUBEKEY_EOF'\n%s\nKUBEKEY_EOF\n", c.shell, cmd)
|
session, err := c.session()
|
||||||
klog.V(5).InfoS("exec ssh command", "cmd", cmd, "host", c.Host)
|
|
||||||
// create ssh session
|
|
||||||
session, err := c.client.NewSession()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "failed to create ssh session")
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
|
|
||||||
// get pipe from session
|
cmd = SudoPrefix(c.shell, cmd)
|
||||||
stdin, err := session.StdinPipe()
|
|
||||||
|
in, err := session.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "failed to get stdin pipe")
|
return nil, nil, errors.Wrap(err, "failed to get stdin pipe")
|
||||||
}
|
}
|
||||||
stdout, err := session.StdoutPipe()
|
|
||||||
|
out, err := session.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "failed to get stdout pipe")
|
return nil, nil, errors.Wrap(err, "failed to get stdout pipe")
|
||||||
}
|
}
|
||||||
|
|
||||||
stderr, err := session.StderrPipe()
|
stderr, err := session.StderrPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "failed to get stderr pipe")
|
return nil, nil, errors.Wrap(err, "failed to get stderr pipe")
|
||||||
}
|
}
|
||||||
// Start the remote command
|
|
||||||
if err := session.Start(cmd); err != nil {
|
if err = session.Start(cmd); err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "failed to start session")
|
return nil, nil, errors.Wrap(err, "failed to start session")
|
||||||
}
|
}
|
||||||
if c.Password != "" {
|
var (
|
||||||
if _, err := stdin.Write([]byte(c.Password + "\n")); err != nil {
|
output []byte
|
||||||
return nil, nil, errors.Wrap(err, "failed to write password")
|
line = ""
|
||||||
|
r = bufio.NewReader(out)
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
b, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
output = append(output, b)
|
||||||
|
|
||||||
|
if b == byte('\n') {
|
||||||
|
line = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
line += string(b)
|
||||||
|
|
||||||
|
if (strings.HasPrefix(line, "[sudo] password for ") || strings.HasPrefix(line, "Password")) && strings.HasSuffix(line, ": ") {
|
||||||
|
_, err = in.Write([]byte(c.Password + "\n"))
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := stdin.Close(); err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "failed to close stdin pipe")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create buffers to store stdout and stderr output
|
outStr := strings.TrimPrefix(string(output), fmt.Sprintf("[sudo] password for %s:", c.User))
|
||||||
var stdoutBuf, stderrBuf bytes.Buffer
|
|
||||||
|
|
||||||
// When reading large amounts of data from stdout/stderr, the pipe buffer can fill up
|
|
||||||
// and block the remote command from completing if we don't read from it continuously.
|
|
||||||
// To prevent this deadlock scenario, we need to read stdout/stderr asynchronously
|
|
||||||
// in separate goroutines while the command is running.
|
|
||||||
// Create channels to signal when copying is complete
|
|
||||||
stdoutDone := make(chan error, 1)
|
|
||||||
stderrDone := make(chan error, 1)
|
|
||||||
|
|
||||||
// Copy stdout and stderr concurrently to prevent pipe buffer from filling
|
|
||||||
go func() {
|
|
||||||
_, err := io.Copy(&stdoutBuf, stdout)
|
|
||||||
stdoutDone <- err
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
_, err := io.Copy(&stderrBuf, stderr)
|
|
||||||
stderrDone <- err
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Wait for command to complete
|
|
||||||
err = session.Wait()
|
err = session.Wait()
|
||||||
|
var stderrBuffer bytes.Buffer
|
||||||
|
_, _ = io.Copy(&stderrBuffer, stderr)
|
||||||
|
outStr = strings.TrimSpace(outStr)
|
||||||
|
stderrData := stderrBuffer.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
return []byte(outStr), nil, errors.Wrap(err, strings.TrimSpace(string(stderrData)))
|
||||||
|
}
|
||||||
|
return []byte(outStr), stderrData, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for stdout and stderr copying to finish to ensure we've captured all output
|
// SudoPrefix returns the prefix for sudo commands.
|
||||||
<-stdoutDone
|
func SudoPrefix(shell, cmd string) string {
|
||||||
<-stderrDone
|
return fmt.Sprintf("TERM=dumb; export LANG=C.UTF-8;sudo -E %s << 'KUBEKEY_EOF'\n%s\nKUBEKEY_EOF", shell, cmd)
|
||||||
|
|
||||||
return stdoutBuf.Bytes(), stderrBuf.Bytes(), errors.Wrap(err, "failed to execute ssh command")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HostInfo from gatherFacts cache
|
// HostInfo from gatherFacts cache
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package modules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -251,7 +252,15 @@ func (ca copyArgs) copyAbsoluteDir(ctx context.Context, conn connector.Connector
|
||||||
dest = filepath.Join(ca.dest, rel)
|
dest = filepath.Join(ca.dest, rel)
|
||||||
}
|
}
|
||||||
|
|
||||||
return conn.PutFile(ctx, data, dest, mode)
|
tmpDest := filepath.Join("/tmp", ca.dest)
|
||||||
|
|
||||||
|
if err = conn.PutFile(ctx, data, tmpDest, mode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = conn.ExecuteCommand(ctx, fmt.Sprintf("mkdir -p %s\nmv %s %s", filepath.Dir(dest), tmpDest, dest))
|
||||||
|
|
||||||
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -289,8 +298,16 @@ func (ca copyArgs) copyRelativeDir(ctx context.Context, pj project.Project, relP
|
||||||
}
|
}
|
||||||
dest = filepath.Join(ca.dest, rel)
|
dest = filepath.Join(ca.dest, rel)
|
||||||
}
|
}
|
||||||
|
tmpDest := filepath.Join("/tmp", ca.dest)
|
||||||
|
|
||||||
return conn.PutFile(ctx, data, dest, mode)
|
err = conn.PutFile(ctx, data, tmpDest, mode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = conn.ExecuteCommand(ctx, fmt.Sprintf("mkdir -p %s\nmv %s %s", filepath.Dir(dest), tmpDest, dest))
|
||||||
|
|
||||||
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -305,7 +322,15 @@ func (ca copyArgs) copyContent(ctx context.Context, mode fs.FileMode, conn conne
|
||||||
mode = os.FileMode(*ca.mode)
|
mode = os.FileMode(*ca.mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := conn.PutFile(ctx, []byte(ca.content), ca.dest, mode); err != nil {
|
tmpDest := filepath.Join("/tmp", ca.dest)
|
||||||
|
|
||||||
|
if err := conn.PutFile(ctx, []byte(ca.content), tmpDest, mode); err != nil {
|
||||||
|
return StdoutFailed, "failed to copy file", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := conn.ExecuteCommand(ctx, fmt.Sprintf("mkdir -p %s\nmv %s %s", filepath.Dir(ca.dest), tmpDest, ca.dest))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
return StdoutFailed, "failed to copy file", err
|
return StdoutFailed, "failed to copy file", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -323,8 +348,15 @@ func (ca copyArgs) copyFile(ctx context.Context, data []byte, mode fs.FileMode,
|
||||||
if ca.mode != nil {
|
if ca.mode != nil {
|
||||||
mode = os.FileMode(*ca.mode)
|
mode = os.FileMode(*ca.mode)
|
||||||
}
|
}
|
||||||
|
tmpDest := filepath.Join("/tmp", dest)
|
||||||
|
|
||||||
return conn.PutFile(ctx, data, dest, mode)
|
if err := conn.PutFile(ctx, data, tmpDest, mode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := conn.ExecuteCommand(ctx, fmt.Sprintf("mkdir -p %s\nmv %s %s", filepath.Dir(dest), tmpDest, dest))
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the "copy" module at init.
|
// Register the "copy" module at init.
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,11 @@ package modules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/rand"
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
|
||||||
"github.com/kubesphere/kubekey/v4/pkg/variable"
|
"github.com/kubesphere/kubekey/v4/pkg/variable"
|
||||||
|
|
@ -78,6 +80,10 @@ func ModuleFetch(ctx context.Context, options ExecOptions) (string, string, erro
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return StdoutFailed, "\"dest\" in args should be string", err
|
return StdoutFailed, "\"dest\" in args should be string", err
|
||||||
}
|
}
|
||||||
|
tmpDir, err := variable.StringVar(ha, ha, "tmp_dir")
|
||||||
|
if err != nil || tmpDir == "" {
|
||||||
|
tmpDir = "/tmp/kubekey/"
|
||||||
|
}
|
||||||
|
|
||||||
// get connector
|
// get connector
|
||||||
conn, err := options.getConnector(ctx)
|
conn, err := options.getConnector(ctx)
|
||||||
|
|
@ -99,7 +105,15 @@ func ModuleFetch(ctx context.Context, options ExecOptions) (string, string, erro
|
||||||
}
|
}
|
||||||
defer destFile.Close()
|
defer destFile.Close()
|
||||||
|
|
||||||
if err := conn.FetchFile(ctx, srcParam, destFile); err != nil {
|
tmpFetchFileName := filepath.Join(tmpDir, fmt.Sprintf("fetch-%s-%s", options.Task.GetUID(), rand.String(5)))
|
||||||
|
|
||||||
|
_, _, err = conn.ExecuteCommand(ctx, fmt.Sprintf("cp %s %s\nchmod 755 %s", srcParam, tmpFetchFileName, tmpFetchFileName))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return StdoutFailed, "failed to fetch file", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = conn.FetchFile(ctx, tmpFetchFileName, destFile); err != nil {
|
||||||
return StdoutFailed, "failed to fetch file", err
|
return StdoutFailed, "failed to fetch file", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package modules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -226,8 +227,15 @@ func handleRelativeDir(ctx context.Context, pj project.Project, relPath string,
|
||||||
}
|
}
|
||||||
dest = filepath.Join(ta.dest, rel)
|
dest = filepath.Join(ta.dest, rel)
|
||||||
}
|
}
|
||||||
|
tmpDest := filepath.Join("/tmp", dest)
|
||||||
|
|
||||||
return conn.PutFile(ctx, result, dest, mode)
|
if err = conn.PutFile(ctx, result, tmpDest, mode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = conn.ExecuteCommand(ctx, fmt.Sprintf("mkdir -p %s\nmv %s %s", filepath.Dir(dest), tmpDest, dest))
|
||||||
|
|
||||||
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,8 +254,15 @@ func (ta templateArgs) readFile(ctx context.Context, data string, mode fs.FileMo
|
||||||
if ta.mode != nil {
|
if ta.mode != nil {
|
||||||
mode = os.FileMode(*ta.mode)
|
mode = os.FileMode(*ta.mode)
|
||||||
}
|
}
|
||||||
|
tmpDest := filepath.Join("/tmp", dest)
|
||||||
|
|
||||||
return conn.PutFile(ctx, result, dest, mode)
|
if err = conn.PutFile(ctx, result, tmpDest, mode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = conn.ExecuteCommand(ctx, fmt.Sprintf("mkdir -p %s\nmv %s %s", filepath.Dir(dest), tmpDest, dest))
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// absDir when template.src is absolute dir, get all files by os, parse it, and copy to remote.
|
// absDir when template.src is absolute dir, get all files by os, parse it, and copy to remote.
|
||||||
|
|
@ -288,11 +303,15 @@ func (ta templateArgs) absDir(ctx context.Context, conn connector.Connector, var
|
||||||
dest = filepath.Join(ta.dest, rel)
|
dest = filepath.Join(ta.dest, rel)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := conn.PutFile(ctx, result, dest, mode); err != nil {
|
tmpDest := filepath.Join("/tmp", dest)
|
||||||
return errors.Wrap(err, "failed to put file")
|
|
||||||
|
if err = conn.PutFile(ctx, result, tmpDest, mode); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
_, _, err = conn.ExecuteCommand(ctx, fmt.Sprintf("mkdir -p %s\nmv %s %s", filepath.Dir(dest), tmpDest, dest))
|
||||||
|
|
||||||
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return errors.Wrapf(err, "failed to walk dir %q", ta.src)
|
return errors.Wrapf(err, "failed to walk dir %q", ta.src)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue