Persist assemblies as base64 and decode for execution

This commit is contained in:
2025-10-03 21:16:43 -06:00
parent 304c1e9728
commit 211e37c64c
7 changed files with 370 additions and 21 deletions

View File

@@ -7,6 +7,7 @@ import time
import json import json
import socket import socket
import subprocess import subprocess
import base64
from typing import Optional from typing import Optional
try: try:
@@ -39,6 +40,39 @@ def _project_root():
return os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) return os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
def _decode_base64_text(value):
if not isinstance(value, str):
return None
stripped = value.strip()
if not stripped:
return ""
cleaned = ''.join(stripped.split())
if not cleaned:
return ""
try:
decoded = base64.b64decode(cleaned, validate=True)
except Exception:
return None
try:
return decoded.decode('utf-8')
except Exception:
return decoded.decode('utf-8', errors='replace')
def _decode_playbook_content(raw_content, encoding_hint):
if isinstance(raw_content, str):
encoding = str(encoding_hint or '').strip().lower()
if encoding in ('base64', 'b64', 'base-64'):
decoded = _decode_base64_text(raw_content)
if decoded is not None:
return decoded
decoded = _decode_base64_text(raw_content)
if decoded is not None:
return decoded
return raw_content
return ''
def _agent_root(): def _agent_root():
# Resolve Agent root at runtime. # Resolve Agent root at runtime.
# Typical runtime: <ProjectRoot>/Agent/Borealis/Roles/<this_file> # Typical runtime: <ProjectRoot>/Agent/Borealis/Roles/<this_file>
@@ -801,7 +835,7 @@ try {{
return return
# Accept provided run_id or generate one # Accept provided run_id or generate one
run_id = (payload.get('run_id') or '').strip() or uuid.uuid4().hex run_id = (payload.get('run_id') or '').strip() or uuid.uuid4().hex
content = payload.get('playbook_content') or '' content = _decode_playbook_content(payload.get('playbook_content'), payload.get('playbook_encoding'))
p_name = payload.get('playbook_name') or '' p_name = payload.get('playbook_name') or ''
act_id = payload.get('activity_job_id') act_id = payload.get('activity_job_id')
sched_job_id = payload.get('scheduled_job_id') sched_job_id = payload.get('scheduled_job_id')
@@ -874,7 +908,7 @@ try {{
if target and target != hostname.lower(): if target and target != hostname.lower():
return return
run_id = uuid.uuid4().hex run_id = uuid.uuid4().hex
content = payload.get('script_content') or '' content = _decode_playbook_content(payload.get('script_content'), payload.get('script_encoding'))
p_name = payload.get('script_name') or '' p_name = payload.get('script_name') or ''
self._runs[run_id] = {'cancel': False, 'proc': None} self._runs[run_id] = {'cancel': False, 'proc': None}
asyncio.create_task(self._run_playbook(run_id, content, playbook_name=p_name, activity_job_id=payload.get('job_id'), connection='local')) asyncio.create_task(self._run_playbook(run_id, content, playbook_name=p_name, activity_job_id=payload.get('job_id'), connection='local'))

View File

@@ -4,7 +4,8 @@ import re
import asyncio import asyncio
import tempfile import tempfile
import uuid import uuid
from typing import Dict, List import base64
from typing import Dict, List, Optional
from PyQt5 import QtWidgets, QtGui from PyQt5 import QtWidgets, QtGui
@@ -65,6 +66,40 @@ def _apply_variable_aliases(env_map: Dict[str, str], variables: List[Dict[str, s
return env_map return env_map
def _decode_base64_text(value: str) -> Optional[str]:
if not isinstance(value, str):
return None
stripped = value.strip()
if not stripped:
return ""
try:
cleaned = re.sub(r"\s+", "", stripped)
except Exception:
cleaned = stripped
try:
decoded = base64.b64decode(cleaned, validate=True)
except Exception:
return None
try:
return decoded.decode("utf-8")
except Exception:
return decoded.decode("utf-8", errors="replace")
def _decode_script_content(raw_content, encoding_hint) -> str:
if isinstance(raw_content, str):
encoding = str(encoding_hint or "").strip().lower()
if encoding in ("base64", "b64", "base-64"):
decoded = _decode_base64_text(raw_content)
if decoded is not None:
return decoded
decoded = _decode_base64_text(raw_content)
if decoded is not None:
return decoded
return raw_content
return ""
def _ps_literal(value: str) -> str: def _ps_literal(value: str) -> str:
return "'" + value.replace("'", "''") + "'" return "'" + value.replace("'", "''") + "'"
@@ -242,7 +277,7 @@ class Role:
job_id = payload.get('job_id') job_id = payload.get('job_id')
script_type = (payload.get('script_type') or '').lower() script_type = (payload.get('script_type') or '').lower()
run_mode = (payload.get('run_mode') or 'current_user').lower() run_mode = (payload.get('run_mode') or 'current_user').lower()
content = payload.get('script_content') or '' content = _decode_script_content(payload.get('script_content'), payload.get('script_encoding'))
raw_env = payload.get('environment') raw_env = payload.get('environment')
env_map = _sanitize_env_map(raw_env) env_map = _sanitize_env_map(raw_env)
variables = payload.get('variables') if isinstance(payload.get('variables'), list) else [] variables = payload.get('variables') if isinstance(payload.get('variables'), list) else []

View File

@@ -5,7 +5,8 @@ import tempfile
import uuid import uuid
import time import time
import subprocess import subprocess
from typing import Dict, List import base64
from typing import Dict, List, Optional
ROLE_NAME = 'script_exec_system' ROLE_NAME = 'script_exec_system'
@@ -67,6 +68,40 @@ def _apply_variable_aliases(env_map: Dict[str, str], variables: List[Dict[str, s
return env_map return env_map
def _decode_base64_text(value: str) -> Optional[str]:
if not isinstance(value, str):
return None
stripped = value.strip()
if not stripped:
return ""
try:
cleaned = re.sub(r"\s+", "", stripped)
except Exception:
cleaned = stripped
try:
decoded = base64.b64decode(cleaned, validate=True)
except Exception:
return None
try:
return decoded.decode("utf-8")
except Exception:
return decoded.decode("utf-8", errors="replace")
def _decode_script_content(raw_content, encoding_hint) -> str:
if isinstance(raw_content, str):
encoding = str(encoding_hint or "").strip().lower()
if encoding in ("base64", "b64", "base-64"):
decoded = _decode_base64_text(raw_content)
if decoded is not None:
return decoded
decoded = _decode_base64_text(raw_content)
if decoded is not None:
return decoded
return raw_content
return ""
def _ps_literal(value: str) -> str: def _ps_literal(value: str) -> str:
return "'" + value.replace("'", "''") + "'" return "'" + value.replace("'", "''") + "'"
@@ -236,7 +271,7 @@ class Role:
return return
job_id = payload.get('job_id') job_id = payload.get('job_id')
script_type = (payload.get('script_type') or '').lower() script_type = (payload.get('script_type') or '').lower()
content = payload.get('script_content') or '' content = _decode_script_content(payload.get('script_content'), payload.get('script_encoding'))
raw_env = payload.get('environment') raw_env = payload.get('environment')
env_map = _sanitize_env_map(raw_env) env_map = _sanitize_env_map(raw_env)
variables = payload.get('variables') if isinstance(payload.get('variables'), list) else [] variables = payload.get('variables') if isinstance(payload.get('variables'), list) else []

View File

@@ -156,6 +156,39 @@ def _log_agent(message: str, fname: str = 'agent.log'):
except Exception: except Exception:
pass pass
def _decode_base64_text(value):
if not isinstance(value, str):
return None
stripped = value.strip()
if not stripped:
return ""
cleaned = ''.join(stripped.split())
if not cleaned:
return ""
try:
decoded = base64.b64decode(cleaned, validate=True)
except Exception:
return None
try:
return decoded.decode('utf-8')
except Exception:
return decoded.decode('utf-8', errors='replace')
def _decode_script_payload(content, encoding_hint):
if isinstance(content, str):
encoding = str(encoding_hint or '').strip().lower()
if encoding in ('base64', 'b64', 'base-64'):
decoded = _decode_base64_text(content)
if decoded is not None:
return decoded
decoded = _decode_base64_text(content)
if decoded is not None:
return decoded
return content
return ''
def _resolve_config_path(): def _resolve_config_path():
""" """
Resolve the path for agent settings json in the centralized location: Resolve the path for agent settings json in the centralized location:
@@ -1520,7 +1553,7 @@ if __name__=='__main__':
return return
job_id = payload.get('job_id') job_id = payload.get('job_id')
script_type = (payload.get('script_type') or '').lower() script_type = (payload.get('script_type') or '').lower()
content = payload.get('script_content') or '' content = _decode_script_payload(payload.get('script_content'), payload.get('script_encoding'))
run_mode = (payload.get('run_mode') or 'current_user').lower() run_mode = (payload.get('run_mode') or 'current_user').lower()
if script_type != 'powershell': if script_type != 'powershell':
await sio.emit('quick_job_result', { 'job_id': job_id, 'status': 'Failed', 'stdout': '', 'stderr': f"Unsupported type: {script_type}" }) await sio.emit('quick_job_result', { 'job_id': job_id, 'status': 'Failed', 'stdout': '', 'stderr': f"Unsupported type: {script_type}" })

View File

@@ -198,6 +198,61 @@ function normalizeVariablesFromServer(vars = []) {
})); }));
} }
function decodeBase64String(data = "") {
if (typeof data !== "string") {
return { success: false, value: "" };
}
if (!data.trim()) {
return { success: true, value: "" };
}
try {
if (typeof window !== "undefined" && typeof window.atob === "function") {
const binary = window.atob(data);
if (typeof TextDecoder !== "undefined") {
const decoder = new TextDecoder("utf-8", { fatal: false });
return { success: true, value: decoder.decode(Uint8Array.from(binary, (c) => c.charCodeAt(0))) };
}
return { success: true, value: binary };
}
} catch (err) {
// fall through to Buffer fallback
}
try {
if (typeof Buffer !== "undefined") {
return { success: true, value: Buffer.from(data, "base64").toString("utf-8") };
}
} catch (err) {
// ignore
}
return { success: false, value: "" };
}
function encodeBase64String(text = "") {
if (typeof text !== "string") {
text = text == null ? "" : String(text);
}
if (!text) return "";
try {
if (typeof TextEncoder !== "undefined" && typeof window !== "undefined" && typeof window.btoa === "function") {
const encoder = new TextEncoder();
const bytes = encoder.encode(text);
let binary = "";
bytes.forEach((b) => { binary += String.fromCharCode(b); });
return window.btoa(binary);
}
} catch (err) {
// fall through to Buffer fallback
}
try {
if (typeof Buffer !== "undefined") {
return Buffer.from(text, "utf-8").toString("base64");
}
} catch (err) {
// ignore
}
return "";
}
function normalizeFilesFromServer(files = []) { function normalizeFilesFromServer(files = []) {
return (Array.isArray(files) ? files : []).map((f, idx) => ({ return (Array.isArray(files) ? files : []).map((f, idx) => ({
id: `${Date.now()}_${idx}_${Math.random().toString(36).slice(2, 8)}`, id: `${Date.now()}_${idx}_${Math.random().toString(36).slice(2, 8)}`,
@@ -219,7 +274,20 @@ function fromServerDocument(doc = {}, defaultType = "powershell") {
? doc.script_lines.map((line) => (line == null ? "" : String(line))).join("\n") ? doc.script_lines.map((line) => (line == null ? "" : String(line))).join("\n")
: ""; : "";
const script = doc.script ?? doc.content ?? legacyScript; const script = doc.script ?? doc.content ?? legacyScript;
assembly.script = typeof script === "string" ? script : legacyScript; if (typeof script === "string") {
const encoding = (doc.script_encoding || doc.scriptEncoding || "").toLowerCase();
if (["base64", "b64", "base-64"].includes(encoding)) {
const decoded = decodeBase64String(script);
assembly.script = decoded.success ? decoded.value : "";
} else if (!encoding) {
const decoded = decodeBase64String(script);
assembly.script = decoded.success ? decoded.value : script;
} else {
assembly.script = script;
}
} else {
assembly.script = legacyScript;
}
const timeout = doc.timeout_seconds ?? doc.timeout ?? assembly.timeoutSeconds; const timeout = doc.timeout_seconds ?? doc.timeout ?? assembly.timeoutSeconds;
assembly.timeoutSeconds = Number.isFinite(Number(timeout)) assembly.timeoutSeconds = Number.isFinite(Number(timeout))
? Number(timeout) ? Number(timeout)
@@ -241,13 +309,15 @@ function toServerDocument(assembly) {
: ""; : "";
const timeoutNumeric = Number(assembly.timeoutSeconds); const timeoutNumeric = Number(assembly.timeoutSeconds);
const timeoutSeconds = Number.isFinite(timeoutNumeric) ? Math.max(0, Math.round(timeoutNumeric)) : 3600; const timeoutSeconds = Number.isFinite(timeoutNumeric) ? Math.max(0, Math.round(timeoutNumeric)) : 3600;
const encodedScript = encodeBase64String(normalizedScript);
return { return {
version: 1, version: 1,
name: assembly.name?.trim() || "", name: assembly.name?.trim() || "",
description: assembly.description || "", description: assembly.description || "",
category: assembly.category || "script", category: assembly.category || "script",
type: assembly.type || "powershell", type: assembly.type || "powershell",
script: normalizedScript, script: encodedScript,
script_encoding: "base64",
timeout_seconds: timeoutSeconds, timeout_seconds: timeoutSeconds,
sites: { sites: {
mode: assembly.sites?.mode === "specific" ? "specific" : "all", mode: assembly.sites?.mode === "specific" ? "specific" : "all",

View File

@@ -2,6 +2,7 @@ import os
import time import time
import json import json
import os import os
import base64
import re import re
import sqlite3 import sqlite3
from typing import Any, Dict, List, Optional, Tuple, Callable from typing import Any, Dict, List, Optional, Tuple, Callable
@@ -33,6 +34,53 @@ def _env_string(value: Any) -> str:
return str(value) return str(value)
def _decode_base64_text(value: Any) -> Optional[str]:
if not isinstance(value, str):
return None
stripped = value.strip()
if not stripped:
return ""
try:
cleaned = re.sub(r"\s+", "", stripped)
except Exception:
cleaned = stripped
try:
decoded = base64.b64decode(cleaned, validate=True)
except Exception:
return None
try:
return decoded.decode("utf-8")
except Exception:
return decoded.decode("utf-8", errors="replace")
def _decode_script_content(value: Any, encoding_hint: str = "") -> str:
encoding = (encoding_hint or "").strip().lower()
if isinstance(value, str):
if encoding in ("base64", "b64", "base-64"):
decoded = _decode_base64_text(value)
if decoded is not None:
return decoded.replace("\r\n", "\n")
decoded = _decode_base64_text(value)
if decoded is not None:
return decoded.replace("\r\n", "\n")
return value.replace("\r\n", "\n")
return ""
def _encode_script_content(script_text: Any) -> str:
if not isinstance(script_text, str):
if script_text is None:
script_text = ""
else:
script_text = str(script_text)
normalized = script_text.replace("\r\n", "\n")
if not normalized:
return ""
encoded = base64.b64encode(normalized.encode("utf-8"))
return encoded.decode("ascii")
def _canonical_env_key(name: Any) -> str: def _canonical_env_key(name: Any) -> str:
try: try:
return re.sub(r"[^A-Za-z0-9_]", "_", str(name or "").strip()).upper() return re.sub(r"[^A-Za-z0-9_]", "_", str(name or "").strip()).upper()
@@ -338,6 +386,7 @@ class JobScheduler:
if typ in ("powershell", "batch", "bash", "ansible"): if typ in ("powershell", "batch", "bash", "ansible"):
doc["type"] = typ doc["type"] = typ
script_val = data.get("script") script_val = data.get("script")
content_val = data.get("content")
script_lines = data.get("script_lines") script_lines = data.get("script_lines")
if isinstance(script_lines, list): if isinstance(script_lines, list):
try: try:
@@ -347,11 +396,24 @@ class JobScheduler:
elif isinstance(script_val, str): elif isinstance(script_val, str):
doc["script"] = script_val doc["script"] = script_val
else: else:
content_val = data.get("content")
if isinstance(content_val, str): if isinstance(content_val, str):
doc["script"] = content_val doc["script"] = content_val
normalized_script = (doc["script"] or "").replace("\r\n", "\n") encoding_hint = str(data.get("script_encoding") or data.get("scriptEncoding") or "").strip().lower()
doc["script"] = normalized_script doc["script"] = _decode_script_content(doc.get("script"), encoding_hint)
if encoding_hint in ("base64", "b64", "base-64"):
doc["script_encoding"] = "base64"
else:
probe_source = ""
if isinstance(script_val, str) and script_val:
probe_source = script_val
elif isinstance(content_val, str) and content_val:
probe_source = content_val
decoded_probe = _decode_base64_text(probe_source) if probe_source else None
if decoded_probe is not None:
doc["script_encoding"] = "base64"
doc["script"] = decoded_probe.replace("\r\n", "\n")
else:
doc["script_encoding"] = "plain"
try: try:
timeout_raw = data.get("timeout_seconds", data.get("timeout")) timeout_raw = data.get("timeout_seconds", data.get("timeout"))
if timeout_raw is None: if timeout_raw is None:
@@ -423,6 +485,7 @@ class JobScheduler:
return return
doc = self._load_assembly_document(abs_path, "ansible") doc = self._load_assembly_document(abs_path, "ansible")
content = doc.get("script") or "" content = doc.get("script") or ""
encoded_content = _encode_script_content(content)
variables = doc.get("variables") or [] variables = doc.get("variables") or []
files = doc.get("files") or [] files = doc.get("files") or []
@@ -457,7 +520,8 @@ class JobScheduler:
"run_id": uuid.uuid4().hex, "run_id": uuid.uuid4().hex,
"target_hostname": str(hostname), "target_hostname": str(hostname),
"playbook_name": os.path.basename(abs_path), "playbook_name": os.path.basename(abs_path),
"playbook_content": content, "playbook_content": encoded_content,
"playbook_encoding": "base64",
"activity_job_id": act_id, "activity_job_id": act_id,
"scheduled_job_id": int(scheduled_job_id), "scheduled_job_id": int(scheduled_job_id),
"scheduled_run_id": int(scheduled_run_id), "scheduled_run_id": int(scheduled_run_id),
@@ -517,6 +581,7 @@ class JobScheduler:
env_map, variables, literal_lookup = _prepare_variable_context(doc_variables, overrides) env_map, variables, literal_lookup = _prepare_variable_context(doc_variables, overrides)
content = _rewrite_powershell_script(content, literal_lookup) content = _rewrite_powershell_script(content, literal_lookup)
encoded_content = _encode_script_content(content)
timeout_seconds = 0 timeout_seconds = 0
try: try:
timeout_seconds = max(0, int(doc.get("timeout_seconds") or 0)) timeout_seconds = max(0, int(doc.get("timeout_seconds") or 0))
@@ -557,7 +622,8 @@ class JobScheduler:
"script_type": stype, "script_type": stype,
"script_name": os.path.basename(abs_path), "script_name": os.path.basename(abs_path),
"script_path": path_norm, "script_path": path_norm,
"script_content": content, "script_content": encoded_content,
"script_encoding": "base64",
"environment": env_map, "environment": env_map,
"variables": variables, "variables": variables,
"timeout_seconds": timeout_seconds, "timeout_seconds": timeout_seconds,

View File

@@ -689,6 +689,64 @@ def _empty_assembly_document(default_type: str = "powershell") -> Dict[str, Any]
} }
def _decode_base64_text(value: Any) -> Optional[str]:
if not isinstance(value, str):
return None
stripped = value.strip()
if not stripped:
return ""
try:
cleaned = re.sub(r"\s+", "", stripped)
except Exception:
cleaned = stripped
try:
decoded = base64.b64decode(cleaned, validate=True)
except Exception:
return None
try:
return decoded.decode("utf-8")
except Exception:
return decoded.decode("utf-8", errors="replace")
def _decode_script_content(value: Any, encoding_hint: str = "") -> str:
encoding = (encoding_hint or "").strip().lower()
if isinstance(value, str):
if encoding in ("base64", "b64", "base-64"):
decoded = _decode_base64_text(value)
if decoded is not None:
return decoded.replace("\r\n", "\n")
decoded = _decode_base64_text(value)
if decoded is not None:
return decoded.replace("\r\n", "\n")
return value.replace("\r\n", "\n")
return ""
def _encode_script_content(script_text: Any) -> str:
if not isinstance(script_text, str):
if script_text is None:
script_text = ""
else:
script_text = str(script_text)
normalized = script_text.replace("\r\n", "\n")
if not normalized:
return ""
encoded = base64.b64encode(normalized.encode("utf-8"))
return encoded.decode("ascii")
def _prepare_assembly_storage(doc: Dict[str, Any]) -> Dict[str, Any]:
stored: Dict[str, Any] = {}
for key, value in (doc or {}).items():
if key == "script":
stored[key] = _encode_script_content(value)
else:
stored[key] = value
stored["script_encoding"] = "base64"
return stored
def _normalize_assembly_document(obj: Any, default_type: str, base_name: str) -> Dict[str, Any]: def _normalize_assembly_document(obj: Any, default_type: str, base_name: str) -> Dict[str, Any]:
doc = _empty_assembly_document(default_type) doc = _empty_assembly_document(default_type)
if not isinstance(obj, dict): if not isinstance(obj, dict):
@@ -703,6 +761,7 @@ def _normalize_assembly_document(obj: Any, default_type: str, base_name: str) ->
if typ in ("powershell", "batch", "bash", "ansible"): if typ in ("powershell", "batch", "bash", "ansible"):
doc["type"] = typ doc["type"] = typ
script_val = obj.get("script") script_val = obj.get("script")
content_val = obj.get("content")
script_lines = obj.get("script_lines") script_lines = obj.get("script_lines")
if isinstance(script_lines, list): if isinstance(script_lines, list):
try: try:
@@ -712,11 +771,24 @@ def _normalize_assembly_document(obj: Any, default_type: str, base_name: str) ->
elif isinstance(script_val, str): elif isinstance(script_val, str):
doc["script"] = script_val doc["script"] = script_val
else: else:
content_val = obj.get("content")
if isinstance(content_val, str): if isinstance(content_val, str):
doc["script"] = content_val doc["script"] = content_val
normalized_script = (doc["script"] or "").replace("\r\n", "\n") encoding_hint = str(obj.get("script_encoding") or obj.get("scriptEncoding") or "").strip().lower()
doc["script"] = normalized_script doc["script"] = _decode_script_content(doc.get("script"), encoding_hint)
if encoding_hint in ("base64", "b64", "base-64"):
doc["script_encoding"] = "base64"
else:
probe_source = ""
if isinstance(script_val, str) and script_val:
probe_source = script_val
elif isinstance(content_val, str) and content_val:
probe_source = content_val
decoded_probe = _decode_base64_text(probe_source) if probe_source else None
if decoded_probe is not None:
doc["script_encoding"] = "base64"
doc["script"] = decoded_probe.replace("\r\n", "\n")
else:
doc["script_encoding"] = "plain"
timeout_val = obj.get("timeout_seconds", obj.get("timeout")) timeout_val = obj.get("timeout_seconds", obj.get("timeout"))
if timeout_val is not None: if timeout_val is not None:
try: try:
@@ -853,7 +925,7 @@ def assembly_create():
base_name, base_name,
) )
with open(abs_path, "w", encoding="utf-8") as fh: with open(abs_path, "w", encoding="utf-8") as fh:
json.dump(normalized, fh, indent=2) json.dump(_prepare_assembly_storage(normalized), fh, indent=2)
rel_new = os.path.relpath(abs_path, root).replace(os.sep, "/") rel_new = os.path.relpath(abs_path, root).replace(os.sep, "/")
return jsonify({"status": "ok", "rel_path": rel_new}) return jsonify({"status": "ok", "rel_path": rel_new})
else: else:
@@ -902,7 +974,7 @@ def assembly_edit():
base_name, base_name,
) )
with open(target_abs, "w", encoding="utf-8") as fh: with open(target_abs, "w", encoding="utf-8") as fh:
json.dump(normalized, fh, indent=2) json.dump(_prepare_assembly_storage(normalized), fh, indent=2)
if target_abs != abs_path: if target_abs != abs_path:
try: try:
os.remove(abs_path) os.remove(abs_path)
@@ -2993,6 +3065,7 @@ def scripts_quick_run():
env_map, variables, literal_lookup = _prepare_variable_context(doc_variables, overrides) env_map, variables, literal_lookup = _prepare_variable_context(doc_variables, overrides)
content = _rewrite_powershell_script(content, literal_lookup) content = _rewrite_powershell_script(content, literal_lookup)
encoded_content = _encode_script_content(content)
timeout_seconds = 0 timeout_seconds = 0
try: try:
timeout_seconds = max(0, int(doc.get("timeout_seconds") or 0)) timeout_seconds = max(0, int(doc.get("timeout_seconds") or 0))
@@ -3034,7 +3107,8 @@ def scripts_quick_run():
"script_type": script_type, "script_type": script_type,
"script_name": _safe_filename(rel_path), "script_name": _safe_filename(rel_path),
"script_path": rel_path.replace(os.sep, "/"), "script_path": rel_path.replace(os.sep, "/"),
"script_content": content, "script_content": encoded_content,
"script_encoding": "base64",
"environment": env_map, "environment": env_map,
"variables": variables, "variables": variables,
"timeout_seconds": timeout_seconds, "timeout_seconds": timeout_seconds,
@@ -3070,6 +3144,7 @@ def ansible_quick_run():
return jsonify({"error": "Playbook not found"}), 404 return jsonify({"error": "Playbook not found"}), 404
doc = _load_assembly_document(abs_path, 'ansible') doc = _load_assembly_document(abs_path, 'ansible')
content = doc.get('script') or '' content = doc.get('script') or ''
encoded_content = _encode_script_content(content)
variables = doc.get('variables') if isinstance(doc.get('variables'), list) else [] variables = doc.get('variables') if isinstance(doc.get('variables'), list) else []
files = doc.get('files') if isinstance(doc.get('files'), list) else [] files = doc.get('files') if isinstance(doc.get('files'), list) else []
@@ -3112,7 +3187,8 @@ def ansible_quick_run():
"run_id": run_id, "run_id": run_id,
"target_hostname": str(host), "target_hostname": str(host),
"playbook_name": os.path.basename(abs_path), "playbook_name": os.path.basename(abs_path),
"playbook_content": content, "playbook_content": encoded_content,
"playbook_encoding": "base64",
"connection": "winrm", "connection": "winrm",
"variables": variables, "variables": variables,
"files": files, "files": files,