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
7 changes: 4 additions & 3 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,22 +59,23 @@ jobs:
platforms: linux/amd64,linux/arm64
provenance: false
context: .
push: ${{ github.event.pull_request.head.repo.fork != true }}
# Our repository is itself a fork; publish trusted same-repository PRs.
push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Generate artifact attestation
if: ${{ github.event.pull_request.head.repo.fork != true }}
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true

- name: Job summary
if: ${{ github.event.pull_request.head.repo.fork != true }}
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
env:
DIGEST: ${{ steps.push.outputs.digest }}
REF_NAME: ${{ github.ref_name }}
Expand Down
12 changes: 11 additions & 1 deletion internal/kubernetes/kubernetesClientImpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,17 @@ func (k *kubernetesClientImpl) attemptPodCreate(
wg: &sync.WaitGroup{},
removeLock: &sync.Mutex{},
}
return createdPod.wait(ctx)
readyPod, waitErr := createdPod.wait(ctx)
if waitErr != nil && k.config.Pod.Mode == containerSSHConfig.KubernetesExecutionModeConnection {
// Startup may have exhausted ctx. Roll back this creation before the
// retry loop loses its identity, with a separate, bounded stop budget.
// remove logs failures; preserve the original readiness error.
cleanupCtx, cancel := context.WithTimeout(context.Background(), k.config.Timeouts.PodStop)
_ = createdPod.remove(cleanupCtx)
cancel()
return nil, waitErr
}
return readyPod, waitErr
}
k.backendFailuresMetric.Increment()
logger.Debug(
Expand Down
10 changes: 6 additions & 4 deletions internal/kubernetes/kubernetesExecutionImpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ type stdoutProxyWriter struct {
pidRead bool
lock *sync.Mutex
buf *bytes.Buffer
tty bool
waitEcho bool
}

func (s *stdoutProxyWriter) Write(p []byte) (n int, err error) {
Expand All @@ -224,11 +224,13 @@ func (s *stdoutProxyWriter) Write(p []byte) (n int, err error) {
return s.backend.Write(p)
}
// Read the pid. See https://github.com/containerssh/agent for details.
// We need 6 bytes because the first 2 bytes will be \r\n from the --wait function injected in the reader above.
// Session-mode PTYs also need the two echoed --wait bytes. Connection
// and persistent execs do not use --wait: their four-byte PID must unblock
// initial resizing before a terminal-dependent application emits output.
if n, err := s.buf.Write(p); err != nil {
return n, err
}
if (s.tty && s.buf.Len() >= 6) || (!s.tty && s.buf.Len() >= 4) {
if (s.waitEcho && s.buf.Len() >= 6) || (!s.waitEcho && s.buf.Len() >= 4) {
bufferBytes := s.buf.Bytes()
s.pidRead = true
s.buf = nil
Expand Down Expand Up @@ -264,7 +266,7 @@ func (k *kubernetesExecutionImpl) run(
}
}
stdout = &stdoutProxyWriter{
tty: k.tty,
waitEcho: k.tty && k.pod.config.Pod.Mode == config.KubernetesExecutionModeSession,
backend: stdout,
lock: &sync.Mutex{},
pidChannel: pidChannel,
Expand Down
47 changes: 43 additions & 4 deletions internal/kubernetes/pushSizeQueueImpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,70 @@ package kubernetes

import (
"context"
"errors"
"sync"

"k8s.io/client-go/tools/remotecommand"
)

type pushSizeQueueImpl struct {
resizeChan chan remotecommand.TerminalSize
lock sync.Mutex
stopped bool
stopChan chan struct{}
}

func (s *pushSizeQueueImpl) Push(ctx context.Context, size remotecommand.TerminalSize) error {
s.lock.Lock()
stopChan := s.getStopChanLocked()
stopped := s.stopped
s.lock.Unlock()
if stopped {
return errors.New("terminal size queue is stopped")
}

select {
case <-ctx.Done():
return ctx.Err()
case <-stopChan:
return errors.New("terminal size queue is stopped")
case s.resizeChan <- size:
return nil
}
}

func (s *pushSizeQueueImpl) Next() *remotecommand.TerminalSize {
size, ok := <-s.resizeChan
if !ok {
s.lock.Lock()
stopChan := s.getStopChanLocked()
stopped := s.stopped
s.lock.Unlock()
if stopped {
return nil
}

select {
case <-stopChan:
return nil
case size := <-s.resizeChan:
return &size
}
return &size
}

func (s *pushSizeQueueImpl) Stop() {
close(s.resizeChan)
s.lock.Lock()
defer s.lock.Unlock()
if s.stopped {
return
}
s.stopped = true
// Window requests may still be sending when execution finishes. Signal
// shutdown without closing their data channel or blocking behind a send.
close(s.getStopChanLocked())
}

func (s *pushSizeQueueImpl) getStopChanLocked() chan struct{} {
if s.stopChan == nil {
s.stopChan = make(chan struct{})
}
return s.stopChan
}
Loading
Loading