train-k8s-container-mitre 2.2.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.beads/.gitignore +32 -0
- data/.beads/README.md +81 -0
- data/.beads/config.yaml +62 -0
- data/.beads/issues.jsonl +3 -0
- data/.beads/metadata.json +4 -0
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +2 -1
- data/CHANGELOG.md +7 -0
- data/lib/train-k8s-container/pty_session.rb +105 -19
- data/lib/train-k8s-container/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7401f8f5a6cd3dbab1c4ccf3badcabde98032ddfe39f791eb62aa3fb6d14f17f
|
|
4
|
+
data.tar.gz: b477518b7f03d3f419c0437ce9750af9f4e2496cf2995b0baed4bb41923034db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 69b0c02f9d1d724d6760e341f9f42caec31ccf3a4f96477ae9f09b121067b3c02edbc50cd1b0524296524d770ec87c9022f806b880ca30735c2349e972f108b7
|
|
7
|
+
data.tar.gz: 236996377e83721b771cf5c6d5103509b261c4361231dd313131340a0f52090d8020abf5437373b75aad23852d79db97cdd80c3d74960dcc52b859442035cf6e
|
data/.beads/.gitignore
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# SQLite databases
|
|
2
|
+
*.db
|
|
3
|
+
*.db?*
|
|
4
|
+
*.db-journal
|
|
5
|
+
*.db-wal
|
|
6
|
+
*.db-shm
|
|
7
|
+
|
|
8
|
+
# Daemon runtime files
|
|
9
|
+
daemon.lock
|
|
10
|
+
daemon.log
|
|
11
|
+
daemon.pid
|
|
12
|
+
bd.sock
|
|
13
|
+
|
|
14
|
+
# Local version tracking (prevents upgrade notification spam after git ops)
|
|
15
|
+
.local_version
|
|
16
|
+
|
|
17
|
+
# Legacy database files
|
|
18
|
+
db.sqlite
|
|
19
|
+
bd.db
|
|
20
|
+
|
|
21
|
+
# Merge artifacts (temporary files from 3-way merge)
|
|
22
|
+
beads.base.jsonl
|
|
23
|
+
beads.base.meta.json
|
|
24
|
+
beads.left.jsonl
|
|
25
|
+
beads.left.meta.json
|
|
26
|
+
beads.right.jsonl
|
|
27
|
+
beads.right.meta.json
|
|
28
|
+
|
|
29
|
+
# Keep JSONL exports and config (source of truth for git)
|
|
30
|
+
!issues.jsonl
|
|
31
|
+
!metadata.json
|
|
32
|
+
!config.json
|
data/.beads/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Beads - AI-Native Issue Tracking
|
|
2
|
+
|
|
3
|
+
Welcome to Beads! This repository uses **Beads** for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code.
|
|
4
|
+
|
|
5
|
+
## What is Beads?
|
|
6
|
+
|
|
7
|
+
Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git.
|
|
8
|
+
|
|
9
|
+
**Learn more:** [github.com/steveyegge/beads](https://github.com/steveyegge/beads)
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
### Essential Commands
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Create new issues
|
|
17
|
+
bd create "Add user authentication"
|
|
18
|
+
|
|
19
|
+
# View all issues
|
|
20
|
+
bd list
|
|
21
|
+
|
|
22
|
+
# View issue details
|
|
23
|
+
bd show <issue-id>
|
|
24
|
+
|
|
25
|
+
# Update issue status
|
|
26
|
+
bd update <issue-id> --status in_progress
|
|
27
|
+
bd update <issue-id> --status done
|
|
28
|
+
|
|
29
|
+
# Sync with git remote
|
|
30
|
+
bd sync
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Working with Issues
|
|
34
|
+
|
|
35
|
+
Issues in Beads are:
|
|
36
|
+
- **Git-native**: Stored in `.beads/issues.jsonl` and synced like code
|
|
37
|
+
- **AI-friendly**: CLI-first design works perfectly with AI coding agents
|
|
38
|
+
- **Branch-aware**: Issues can follow your branch workflow
|
|
39
|
+
- **Always in sync**: Auto-syncs with your commits
|
|
40
|
+
|
|
41
|
+
## Why Beads?
|
|
42
|
+
|
|
43
|
+
✨ **AI-Native Design**
|
|
44
|
+
- Built specifically for AI-assisted development workflows
|
|
45
|
+
- CLI-first interface works seamlessly with AI coding agents
|
|
46
|
+
- No context switching to web UIs
|
|
47
|
+
|
|
48
|
+
🚀 **Developer Focused**
|
|
49
|
+
- Issues live in your repo, right next to your code
|
|
50
|
+
- Works offline, syncs when you push
|
|
51
|
+
- Fast, lightweight, and stays out of your way
|
|
52
|
+
|
|
53
|
+
🔧 **Git Integration**
|
|
54
|
+
- Automatic sync with git commits
|
|
55
|
+
- Branch-aware issue tracking
|
|
56
|
+
- Intelligent JSONL merge resolution
|
|
57
|
+
|
|
58
|
+
## Get Started with Beads
|
|
59
|
+
|
|
60
|
+
Try Beads in your own projects:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Install Beads
|
|
64
|
+
curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash
|
|
65
|
+
|
|
66
|
+
# Initialize in your repo
|
|
67
|
+
bd init
|
|
68
|
+
|
|
69
|
+
# Create your first issue
|
|
70
|
+
bd create "Try out Beads"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Learn More
|
|
74
|
+
|
|
75
|
+
- **Documentation**: [github.com/steveyegge/beads/docs](https://github.com/steveyegge/beads/tree/main/docs)
|
|
76
|
+
- **Quick Start Guide**: Run `bd quickstart`
|
|
77
|
+
- **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples)
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
*Beads: Issue tracking that moves at the speed of thought* ⚡
|
data/.beads/config.yaml
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Beads Configuration File
|
|
2
|
+
# This file configures default behavior for all bd commands in this repository
|
|
3
|
+
# All settings can also be set via environment variables (BD_* prefix)
|
|
4
|
+
# or overridden with command-line flags
|
|
5
|
+
|
|
6
|
+
# Issue prefix for this repository (used by bd init)
|
|
7
|
+
# If not set, bd init will auto-detect from directory name
|
|
8
|
+
# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc.
|
|
9
|
+
# issue-prefix: ""
|
|
10
|
+
|
|
11
|
+
# Use no-db mode: load from JSONL, no SQLite, write back after each command
|
|
12
|
+
# When true, bd will use .beads/issues.jsonl as the source of truth
|
|
13
|
+
# instead of SQLite database
|
|
14
|
+
# no-db: false
|
|
15
|
+
|
|
16
|
+
# Disable daemon for RPC communication (forces direct database access)
|
|
17
|
+
# no-daemon: false
|
|
18
|
+
|
|
19
|
+
# Disable auto-flush of database to JSONL after mutations
|
|
20
|
+
# no-auto-flush: false
|
|
21
|
+
|
|
22
|
+
# Disable auto-import from JSONL when it's newer than database
|
|
23
|
+
# no-auto-import: false
|
|
24
|
+
|
|
25
|
+
# Enable JSON output by default
|
|
26
|
+
# json: false
|
|
27
|
+
|
|
28
|
+
# Default actor for audit trails (overridden by BD_ACTOR or --actor)
|
|
29
|
+
# actor: ""
|
|
30
|
+
|
|
31
|
+
# Path to database (overridden by BEADS_DB or --db)
|
|
32
|
+
# db: ""
|
|
33
|
+
|
|
34
|
+
# Auto-start daemon if not running (can also use BEADS_AUTO_START_DAEMON)
|
|
35
|
+
# auto-start-daemon: true
|
|
36
|
+
|
|
37
|
+
# Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE)
|
|
38
|
+
# flush-debounce: "5s"
|
|
39
|
+
|
|
40
|
+
# Git branch for beads commits (bd sync will commit to this branch)
|
|
41
|
+
# IMPORTANT: Set this for team projects so all clones use the same sync branch.
|
|
42
|
+
# This setting persists across clones (unlike database config which is gitignored).
|
|
43
|
+
# Can also use BEADS_SYNC_BRANCH env var for local override.
|
|
44
|
+
# If not set, bd sync will require you to run 'bd config set sync.branch <branch>'.
|
|
45
|
+
sync-branch: "beads-sync"
|
|
46
|
+
|
|
47
|
+
# Multi-repo configuration (experimental - bd-307)
|
|
48
|
+
# Allows hydrating from multiple repositories and routing writes to the correct JSONL
|
|
49
|
+
# repos:
|
|
50
|
+
# primary: "." # Primary repo (where this database lives)
|
|
51
|
+
# additional: # Additional repos to hydrate from (read-only)
|
|
52
|
+
# - ~/beads-planning # Personal planning repo
|
|
53
|
+
# - ~/work-planning # Work planning repo
|
|
54
|
+
|
|
55
|
+
# Integration settings (access with 'bd config get/set')
|
|
56
|
+
# These are stored in the database, not in this file:
|
|
57
|
+
# - jira.url
|
|
58
|
+
# - jira.project
|
|
59
|
+
# - linear.url
|
|
60
|
+
# - linear.api-key
|
|
61
|
+
# - github.org
|
|
62
|
+
# - github.repo
|
data/.beads/issues.jsonl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{"id":"train-k8s-container-681","title":"Submit PR: Auto-discover train-* gems in plugin list","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-12-21T14:29:28.015398-05:00","updated_at":"2025-12-21T14:32:43.790163-05:00","deleted_at":"2025-12-21T14:32:43.790163-05:00","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
|
|
2
|
+
{"id":"train-k8s-container-7ew","title":"Submit PR: Dynamic os.kubernetes? methods","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-12-21T14:30:43.254682-05:00","updated_at":"2025-12-21T14:32:43.5904-05:00","deleted_at":"2025-12-21T14:32:43.5904-05:00","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
|
|
3
|
+
{"id":"train-k8s-container-xlc","title":"Investigate connection pooling differences between v2.0 and v1.3.1","description":"## Root Cause Analysis Complete\n\n### The Bug\nPTY session's `parse_output` method used line-by-line matching to filter command echo-back, which failed for multi-line commands. The echoed command text was included in output, corrupting InSpec's file stat results.\n\n### Evidence\nNick's test data showed v2.2.0 returning command text in output:\n```\ngot: for f in $(find -L /etc/ssl/certs -type f); do\n openssl x509 ...\n 2\u003e\u00261 ; echo __EXIT_CODE__=$?\nfind: '/etc/ssl/certs': No such file or directory\n```\n\n### Fix Implemented\n- Added `remove_command_echo` method in `lib/train-k8s-container/pty_session.rb`\n- Uses marker-based parsing: finds `2\u003e\u00261 ; echo __EXIT_CODE__=$?` and removes everything before it\n- Falls back to line-by-line for edge cases\n\n### Tests Added\n1. `spec/unit/pty_output_parsing_spec.rb` - 9 unit tests\n2. `spec/integration/execution_comparison_spec.rb` - 16 integration tests\n3. `test/scripts/test_pty_fix.rb` - manual validation script\n\n### Local Validation\nAll 6 manual tests pass against local kind cluster:\n- Simple commands ✅\n- Directory existence (matches ground truth) ✅\n- Multi-line command (no echo-back) ✅\n- No internal markers leaked ✅\n- Non-existent file detection ✅\n- Stat command parsing ✅\n\n## Remaining Work (from Peer Review)\n\n### Must Fix (bugs/gaps)\n1. **ANSI test assertion bug** - `spec/unit/pty_output_parsing_spec.rb:163` uses wrong quotes (`'\\e['` should be `\"\\e[\"`)\n2. **Add marker collision test** - What if output contains `__EXIT_CODE__`?\n3. **Add empty output test** - Edge case not covered\n4. **Add cleanup to manual script** - `test/scripts/test_pty_fix.rb` missing `SessionManager.instance.cleanup_all`\n\n### Nice to Have\n- PTY fallback test (when PTY mode fails)\n- Documentation improvements in code comments\n- Consider more unique marker (UUID-based) to prevent collision\n\n## Current State\n- Branch: `feature/fix-pty-output-parsing`\n- Files modified:\n - `lib/train-k8s-container/pty_session.rb` (the fix)\n - `spec/unit/pty_output_parsing_spec.rb` (new)\n - `spec/integration/execution_comparison_spec.rb` (new)\n - `test/scripts/test_pty_fix.rb` (new)\n- All 257 RSpec tests pass\n- Linting passes\n- Test pod `test-ubuntu` running in local cluster\n\n## Next Session Actions\n1. Fix ANSI test assertion\n2. Add marker collision test\n3. Add empty output test\n4. Add cleanup to manual script\n5. Run full test suite\n6. Commit with `fix: resolve PTY output parsing for multi-line commands`\n7. Push and create PR to main\n8. CI will validate, then merge triggers release","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-21T14:34:45.055316-05:00","updated_at":"2026-01-16T11:59:38.678162-05:00","comments":[{"id":1,"issue_id":"train-k8s-container-xlc","author":"alippold","text":"## Test Data\nFiles in data/ directory:\n- train-k8s-container-scan-results v1.31.json\n- train-k8s-container-scan-results v2.json\n\n## Results Comparison\n| Version | Passed | Failed | Skipped | Duration |\n|---------|--------|--------|---------|----------|\n| v1.3.1 | 42 | 45 | 111 | 3.62s |\n| v2.0 | 31 | 54 | 113 | 0.21s |\n\n## Key Observations\n- v2.0 has 11 fewer passes and 9 more failures\n- v2.0 runs 17x faster (0.21s vs 3.62s)\n- Fast execution time suggests commands may not be executing properly\n\n## Investigation Areas\n1. Connection pooling behavior differences\n2. Session management / PTY handling\n3. Command execution timing\n4. Kubectl exec client changes between versions","created_at":"2025-12-21T19:51:04Z"},{"id":2,"issue_id":"train-k8s-container-xlc","author":"alippold","text":"## Test Strategy\n\nNeed to add regression tests for connection pooling to ensure commands execute properly:\n\n1. **Session reuse test**: Multiple commands on same connection should execute\n2. **Command output verification**: Verify actual command output, not just exit codes\n3. **Performance baseline**: Track that execution time is reasonable (not too fast = skipped)\n4. **Comparison test**: Run same commands with v1.3.1 and v2.0, compare results\n\nReference: Other projects with connection pooling (train-ssh, train-winrm) likely have these tests.\n\nTest files to check/update:\n- spec/integration/end_to_end_spec.rb\n- spec/integration/pty_session_integration_spec.rb\n- spec/integration/kubectl_exec_integration_spec.rb","created_at":"2025-12-24T16:01:06Z"},{"id":3,"issue_id":"train-k8s-container-xlc","author":"alippold","text":"## Research: Connection Pooling Test Patterns\n\nNeed to research how other Train transports test connection pooling:\n\n1. **Check train-ssh**: How does it test SSH connection reuse?\n2. **Check train-winrm**: How does it test WinRM session pooling?\n3. **Look for patterns**: Command execution verification, session state validation\n4. **Our implementation**: SessionManager + PTYSession classes\n\nFiles to review:\n- lib/train-k8s-container/session_manager.rb (our pooling logic)\n- lib/train-k8s-container/pty_session.rb (session handling)\n- Compare with train-ssh/train-winrm pooling tests\n- Add similar regression tests to our suite","created_at":"2025-12-24T16:02:25Z"},{"id":4,"issue_id":"train-k8s-container-xlc","author":"alippold","text":"## Alternative: Use connection_pool gem\n\nInstead of custom Singleton pooling, consider using the standard connection_pool gem:\n- https://github.com/mperham/connection_pool\n- v3.0.2 (actively maintained, Dec 2025)\n- Battle-tested in Sidekiq, Puma, production systems\n- Thread-safe, lazy creation, auto-reload on fork\n\nThis would replace our custom SessionManager with proven pooling logic and inherit its test coverage.","created_at":"2025-12-24T16:06:42Z"}]}
|
data/.rubocop.yml
CHANGED
|
@@ -29,10 +29,11 @@ Metrics/PerceivedComplexity:
|
|
|
29
29
|
Metrics/MethodLength:
|
|
30
30
|
Enabled: false
|
|
31
31
|
|
|
32
|
-
# RSpec tests will have longer blocks - acceptable
|
|
32
|
+
# RSpec tests and gemspecs will have longer blocks - acceptable
|
|
33
33
|
Metrics/BlockLength:
|
|
34
34
|
Exclude:
|
|
35
35
|
- spec/**/*_spec.rb
|
|
36
|
+
- '*.gemspec'
|
|
36
37
|
|
|
37
38
|
# Allow reasonable line length (Chef standard is 150)
|
|
38
39
|
Layout/LineLength:
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.2.1](https://github.com/mitre/train-k8s-container/compare/v2.2.0...v2.2.1) (2026-01-17)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* resolve PTY output parsing for multi-line commands ([#12](https://github.com/mitre/train-k8s-container/issues/12)) ([2680c3f](https://github.com/mitre/train-k8s-container/commit/2680c3f497d90d7ddf70dfeb85160e148cf397c6))
|
|
14
|
+
|
|
8
15
|
## [2.2.0](https://github.com/mitre/train-k8s-container/compare/v2.1.1...v2.2.0) (2025-12-24)
|
|
9
16
|
|
|
10
17
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'pty'
|
|
4
4
|
require 'timeout'
|
|
5
|
+
require 'securerandom'
|
|
5
6
|
require_relative 'errors'
|
|
6
7
|
require_relative 'ansi_sanitizer'
|
|
7
8
|
|
|
@@ -14,7 +15,7 @@ module TrainPlugins
|
|
|
14
15
|
class SessionClosedError < PtyError; end
|
|
15
16
|
class CommandTimeoutError < PtyError; end
|
|
16
17
|
|
|
17
|
-
attr_reader :session_key, :reader, :writer, :pid
|
|
18
|
+
attr_reader :session_key, :reader, :writer, :pid, :marker_id
|
|
18
19
|
|
|
19
20
|
DEFAULT_COMMAND_TIMEOUT = 60
|
|
20
21
|
DEFAULT_SESSION_TIMEOUT = 300
|
|
@@ -30,6 +31,9 @@ module TrainPlugins
|
|
|
30
31
|
@reader = nil
|
|
31
32
|
@writer = nil
|
|
32
33
|
@pid = nil
|
|
34
|
+
# Unique marker per session prevents collision with user output
|
|
35
|
+
# Uses 8-char hex (32 bits of entropy) - sufficient for session uniqueness
|
|
36
|
+
@marker_id = SecureRandom.hex(4)
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
def connect
|
|
@@ -70,8 +74,10 @@ module TrainPlugins
|
|
|
70
74
|
|
|
71
75
|
@logger&.debug("Executing in PTY session: #{command}")
|
|
72
76
|
|
|
73
|
-
# Send command with exit code marker
|
|
74
|
-
|
|
77
|
+
# Send command with unique exit code marker (prevents collision with user output)
|
|
78
|
+
# Note: Don't use 2>&1 - PTY already merges streams, and explicit redirect
|
|
79
|
+
# causes stderr content (like SELinux errors) to corrupt structured output
|
|
80
|
+
cmd_with_marker = "#{command}; echo #{exit_marker}=$?"
|
|
75
81
|
@writer.puts(cmd_with_marker)
|
|
76
82
|
@writer.flush
|
|
77
83
|
|
|
@@ -106,13 +112,31 @@ module TrainPlugins
|
|
|
106
112
|
|
|
107
113
|
private
|
|
108
114
|
|
|
115
|
+
# Unique exit code marker for this session
|
|
116
|
+
# Format: __EXIT_CODE_<8-char-hex>__ (e.g., __EXIT_CODE_a1b2c3d4__)
|
|
117
|
+
# This prevents any possible collision with user output
|
|
118
|
+
def exit_marker
|
|
119
|
+
"__EXIT_CODE_#{@marker_id}__"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Regex pattern to match our unique marker
|
|
123
|
+
def exit_marker_pattern
|
|
124
|
+
/#{Regexp.escape(exit_marker)}=(\d+)/
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Wrapper suffix added to commands (for echo removal)
|
|
128
|
+
def wrapper_suffix
|
|
129
|
+
"; echo #{exit_marker}=$?"
|
|
130
|
+
end
|
|
131
|
+
|
|
109
132
|
def read_until_marker
|
|
110
133
|
buffer = +'' # Unfreeze string
|
|
134
|
+
marker_regex = exit_marker_pattern
|
|
111
135
|
|
|
112
136
|
Timeout.timeout(@command_timeout) do
|
|
113
137
|
while (line = @reader.gets)
|
|
114
138
|
buffer << line
|
|
115
|
-
break if line =~
|
|
139
|
+
break if line =~ marker_regex
|
|
116
140
|
end
|
|
117
141
|
end
|
|
118
142
|
|
|
@@ -123,29 +147,91 @@ module TrainPlugins
|
|
|
123
147
|
# Strip ANSI sequences
|
|
124
148
|
cleaned = strip_ansi_sequences(buffer)
|
|
125
149
|
|
|
126
|
-
#
|
|
150
|
+
# IMPORTANT: Order matters here!
|
|
151
|
+
# When shell expands $? in echo-back, the marker appears TWICE:
|
|
152
|
+
# 1. In echoed command: "cmd 2>&1 ; echo __EXIT_CODE_xxx__=0"
|
|
153
|
+
# 2. In actual output: "...\n__EXIT_CODE_xxx__=0"
|
|
154
|
+
# We must remove the command echo FIRST (step 1), leaving only the
|
|
155
|
+
# actual output with its marker (step 2), then extract exit code and
|
|
156
|
+
# remove the marker.
|
|
157
|
+
|
|
158
|
+
# Step 1: Remove command echo-back from PTY output
|
|
159
|
+
# The shell echoes the command before executing, ending with our wrapper marker.
|
|
160
|
+
# For multi-line commands, we can't use simple line matching - we need to find
|
|
161
|
+
# the wrapper marker and remove everything up to and including it.
|
|
162
|
+
output = remove_command_echo(cleaned, command)
|
|
163
|
+
|
|
164
|
+
# Step 2: Extract exit code using our unique session marker
|
|
165
|
+
# Now there's only one marker in the text (the actual output)
|
|
127
166
|
exit_code = 1
|
|
128
|
-
if (match =
|
|
167
|
+
if (match = output.match(exit_marker_pattern))
|
|
129
168
|
exit_code = match[1].to_i
|
|
130
169
|
end
|
|
131
170
|
|
|
132
|
-
# Remove exit code
|
|
133
|
-
|
|
171
|
+
# Step 3: Remove the exit code marker from output
|
|
172
|
+
output = remove_marker_line(output)
|
|
134
173
|
|
|
135
|
-
#
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
lines.reject! { |l| l.strip == cmd_wrapper.strip || l.strip == command.strip }
|
|
140
|
-
|
|
141
|
-
output = lines.join
|
|
174
|
+
# PTY merges stdout/stderr - we cannot separate them
|
|
175
|
+
# Always return output as stdout, let caller use exit_code for success/failure
|
|
176
|
+
Train::Extras::CommandResult.new(output.strip, '', exit_code)
|
|
177
|
+
end
|
|
142
178
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
179
|
+
# Remove echoed command from PTY output
|
|
180
|
+
# PTY shells echo the command before output. Our wrapper adds:
|
|
181
|
+
# "#{command} 2>&1 ; echo #{exit_marker}=$?"
|
|
182
|
+
# The shell echoes this, then outputs the result. We need to find
|
|
183
|
+
# where the echo ends and the actual output begins.
|
|
184
|
+
#
|
|
185
|
+
# IMPORTANT: Some shells expand $? BEFORE echoing, so the echo-back may show:
|
|
186
|
+
# "command; echo __EXIT_CODE_xxx__=0" (expanded)
|
|
187
|
+
# instead of:
|
|
188
|
+
# "command; echo __EXIT_CODE_xxx__=$?" (literal)
|
|
189
|
+
# We use a prefix pattern that matches both cases.
|
|
190
|
+
def remove_command_echo(text, command)
|
|
191
|
+
# Match the wrapper prefix (without $? or the exit code value)
|
|
192
|
+
# This handles both unexpanded ($?) and expanded (0, 127, etc.) cases
|
|
193
|
+
wrapper_prefix = "; echo #{exit_marker}="
|
|
194
|
+
|
|
195
|
+
# Strategy 1: Find the wrapper marker (handles multi-line commands)
|
|
196
|
+
# Everything before and including this line is command echo
|
|
197
|
+
marker_index = text.index(wrapper_prefix)
|
|
198
|
+
if marker_index
|
|
199
|
+
newline_after_marker = text.index("\n", marker_index)
|
|
200
|
+
if newline_after_marker
|
|
201
|
+
# Everything after the marker line is actual output
|
|
202
|
+
output = text[(newline_after_marker + 1)..]
|
|
203
|
+
else
|
|
204
|
+
# Edge case: no newline after the wrapper line
|
|
205
|
+
# Skip to end of line (past the =N or =$? part)
|
|
206
|
+
line_end = marker_index + wrapper_prefix.length
|
|
207
|
+
# Skip any remaining characters until end of string (the exit code value)
|
|
208
|
+
line_end += 1 while line_end < text.length && text[line_end] =~ /[\d$?]/
|
|
209
|
+
output = text[line_end..] || ''
|
|
210
|
+
end
|
|
146
211
|
else
|
|
147
|
-
|
|
212
|
+
# Strategy 2: Fall back to line-by-line removal (handles simple cases)
|
|
213
|
+
# This is used when the marker isn't present (e.g., some test scenarios)
|
|
214
|
+
output = text
|
|
148
215
|
end
|
|
216
|
+
|
|
217
|
+
# Also remove simple command echo if still present
|
|
218
|
+
# (some shells may echo the command on its own line)
|
|
219
|
+
lines = output.lines
|
|
220
|
+
lines.reject! { |l| l.strip == command.strip }
|
|
221
|
+
lines.join
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Remove our unique exit code marker from the output
|
|
225
|
+
# Note: With --printf format (no trailing newline), the marker may be
|
|
226
|
+
# appended to the last line of output rather than on its own line.
|
|
227
|
+
# We must preserve the content before the marker.
|
|
228
|
+
def remove_marker_line(text)
|
|
229
|
+
# Remove the marker pattern itself, preserving any content before it
|
|
230
|
+
# This handles both cases:
|
|
231
|
+
# 1. Marker on its own line: "content\n__EXIT_CODE_xxx__=0\n" -> "content\n"
|
|
232
|
+
# 2. Marker appended to content: "?\n__EXIT_CODE_xxx__=0\n" -> "?\n"
|
|
233
|
+
# or without newline: "?__EXIT_CODE_xxx__=0" -> "?"
|
|
234
|
+
text.sub(/#{Regexp.escape(exit_marker)}=\d+\n?/, '')
|
|
149
235
|
end
|
|
150
236
|
|
|
151
237
|
def strip_ansi_sequences(text)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: train-k8s-container-mitre
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.2.
|
|
4
|
+
version: 2.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- MITRE SAF Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-01-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -40,6 +40,11 @@ executables: []
|
|
|
40
40
|
extensions: []
|
|
41
41
|
extra_rdoc_files: []
|
|
42
42
|
files:
|
|
43
|
+
- ".beads/.gitignore"
|
|
44
|
+
- ".beads/README.md"
|
|
45
|
+
- ".beads/config.yaml"
|
|
46
|
+
- ".beads/issues.jsonl"
|
|
47
|
+
- ".beads/metadata.json"
|
|
43
48
|
- ".expeditor/buildkite/coverage.sh"
|
|
44
49
|
- ".expeditor/buildkite/run_linux_tests.sh"
|
|
45
50
|
- ".expeditor/config.yml"
|