mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2026-02-06 05:40:31 -07:00
Persist assemblies as base64 and decode for execution
This commit is contained in:
@@ -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'))
|
||||||
|
|||||||
@@ -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 []
|
||||||
|
|||||||
@@ -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 []
|
||||||
|
|||||||
@@ -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}" })
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user