Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions pkg/runner/deployer/filesystemstats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package deployer

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"golang.org/x/crypto/ssh"
"io"
)

type FileSystemStats struct {
Path string `json:"path"`
BlockSize uint64 `json:"blockSize"`
FreeBlocks uint64 `json:"freeBlocks"`
}

func (fss *FileSystemStats) FreeBytes() uint64 {
return fss.BlockSize * fss.FreeBlocks
}

func GetFileSystemStats(client *ssh.Client, path string) (stats FileSystemStats, err error) {
if client == nil {
return FileSystemStats{}, fmt.Errorf("SSH client cannot be nil")
}

execSession, err := client.NewSession()
if err != nil {
return
}

defer func() {
if closeErr := execSession.Close(); closeErr != io.EOF {
err = errors.Join(err, closeErr)
}
}()

cmd := fmt.Sprintf("stat -f -c '{\"path\": %q, \"blockSize\": %%S, \"freeBlocks\": %%a}' %q", path, path)

out, err := execSession.CombinedOutput(cmd)
if err != nil {
return stats, fmt.Errorf("remote stat failed (output: %q): %w", out, err)
}

if err := json.Unmarshal(bytes.TrimSpace(out), &stats); err != nil {
return stats, fmt.Errorf("invalid JSON from remote stat (got %v): %w", string(out), err)
}
return
}
30 changes: 26 additions & 4 deletions pkg/runner/deployer/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,22 @@ func (p *SSH) Deploy(statusCallback ProgressStatusCallback) (err error) {
path := filepath.Join(p.Payload.RootPath, f.Path)

dir := filepath.Dir(path)
parentDir := filepath.Dir(dir)
Comment thread
dantedelucia marked this conversation as resolved.

if err := sftpClient.MkdirAll(dir); err != nil {
return fmt.Errorf("failed to create remote directory for %s: %w", dir, err)
return errors.Join(
fmt.Errorf("failed to create remote directory for %s: %w", dir, err),
p.checkFSSpace(parentDir),
)
}

remoteFile, err := sftpClient.Create(path)

if err != nil {
return fmt.Errorf("failed to create remote file %s: %w", path, err)
return errors.Join(
fmt.Errorf("failed to create remote file %s: %w", path, err),
p.checkFSSpace(parentDir),
)
}

defer func() {
Expand All @@ -72,7 +79,9 @@ func (p *SSH) Deploy(statusCallback ProgressStatusCallback) (err error) {
}

if _, err := io.Copy(remoteFile, tracker); err != nil {
return fmt.Errorf("failed to write to remote file %s: %w", path, err)
return errors.Join(
fmt.Errorf("failed to write to remote file %s: %w", path, err),
p.checkFSSpace(parentDir))
}
}
return nil
Expand Down Expand Up @@ -102,7 +111,7 @@ func (p *SSH) Run(cmdSegs []string, handler DeployerHandler) (err error) {

defer func() {
if closeErr := execSession.Close(); closeErr != io.EOF {
err = errors.Join(closeErr)
err = errors.Join(err, closeErr)
Comment thread
alexanderguy marked this conversation as resolved.
}
}()

Expand Down Expand Up @@ -135,3 +144,16 @@ func (p *SSH) Run(cmdSegs []string, handler DeployerHandler) (err error) {

return nil
}

func (p *SSH) checkFSSpace(path string) error {
stats, err := GetFileSystemStats(p.Client, path)
if err != nil {
return fmt.Errorf("fsblocks check failed for %s: %w", path, err)
}

if stats.FreeBytes() == 0 {
return fmt.Errorf("no space on device %s", stats.Path)
}

return nil
}
Loading