mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-10-26 19:21:58 -06:00
Provision reusable Ansible execution environment
This commit is contained in:
155
Borealis.ps1
155
Borealis.ps1
@@ -256,6 +256,19 @@ $nodeExe = Join-Path $depsRoot 'NodeJS\node.exe'
|
|||||||
$sevenZipExe = Join-Path $depsRoot "7zip\7z.exe"
|
$sevenZipExe = Join-Path $depsRoot "7zip\7z.exe"
|
||||||
$npmCmd = Join-Path (Split-Path $nodeExe) 'npm.cmd'
|
$npmCmd = Join-Path (Split-Path $nodeExe) 'npm.cmd'
|
||||||
$npxCmd = Join-Path (Split-Path $nodeExe) 'npx.cmd'
|
$npxCmd = Join-Path (Split-Path $nodeExe) 'npx.cmd'
|
||||||
|
$ansibleEeRequirementsPath = Join-Path $scriptDir 'Data\Agent\ansible-ee-requirements.txt'
|
||||||
|
$ansibleEeVersionFile = Join-Path $scriptDir 'Data\Agent\ansible-ee-version.txt'
|
||||||
|
$script:AnsibleExecutionEnvironmentVersion = '1.0.0'
|
||||||
|
if (Test-Path $ansibleEeVersionFile -PathType Leaf) {
|
||||||
|
try {
|
||||||
|
$rawVersion = (Get-Content -Path $ansibleEeVersionFile -Raw -ErrorAction Stop)
|
||||||
|
if ($rawVersion) {
|
||||||
|
$script:AnsibleExecutionEnvironmentVersion = ($rawVersion.Split("`n")[0]).Trim()
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
# Leave default version value
|
||||||
|
}
|
||||||
|
}
|
||||||
$node7zUrl = "https://nodejs.org/dist/v23.11.0/node-v23.11.0-win-x64.7z"
|
$node7zUrl = "https://nodejs.org/dist/v23.11.0/node-v23.11.0-win-x64.7z"
|
||||||
$nodeInstallDir = Join-Path $depsRoot "NodeJS"
|
$nodeInstallDir = Join-Path $depsRoot "NodeJS"
|
||||||
$node7zPath = Join-Path $depsRoot "node-v23.11.0-win-x64.7z"
|
$node7zPath = Join-Path $depsRoot "node-v23.11.0-win-x64.7z"
|
||||||
@@ -449,6 +462,139 @@ function Install_Agent_Dependencies {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Ensure-AnsibleExecutionEnvironment {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$ProjectRoot,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$PythonBootstrapExe,
|
||||||
|
|
||||||
|
[string]$RequirementsPath,
|
||||||
|
[string]$ExpectedVersion = '1.0.0',
|
||||||
|
[string]$LogName = 'Install.log'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not (Test-Path $PythonBootstrapExe -PathType Leaf)) {
|
||||||
|
Write-AgentLog -FileName $LogName -Message "[AnsibleEE] Bundled python executable missing at $PythonBootstrapExe"
|
||||||
|
throw "Bundled python executable not found for Ansible execution environment provisioning."
|
||||||
|
}
|
||||||
|
|
||||||
|
$eeRoot = Join-Path $ProjectRoot 'Agent\Ansible_EE'
|
||||||
|
$metadataPath = Join-Path $eeRoot 'metadata.json'
|
||||||
|
$versionTxtPath = Join-Path $eeRoot 'version.txt'
|
||||||
|
|
||||||
|
$requirementsHash = ''
|
||||||
|
if ($RequirementsPath -and (Test-Path $RequirementsPath -PathType Leaf)) {
|
||||||
|
try {
|
||||||
|
$requirementsHash = (Get-FileHash -Path $RequirementsPath -Algorithm SHA256).Hash
|
||||||
|
} catch {
|
||||||
|
$requirementsHash = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentVersion = ''
|
||||||
|
$currentHash = ''
|
||||||
|
if (Test-Path $metadataPath -PathType Leaf) {
|
||||||
|
try {
|
||||||
|
$metaRaw = Get-Content -Path $metadataPath -Raw -ErrorAction Stop
|
||||||
|
if ($metaRaw) {
|
||||||
|
$meta = $metaRaw | ConvertFrom-Json -ErrorAction Stop
|
||||||
|
if ($meta.version) {
|
||||||
|
$currentVersion = ($meta.version).ToString().Trim()
|
||||||
|
}
|
||||||
|
if ($meta.requirements_hash) {
|
||||||
|
$currentHash = ($meta.requirements_hash).ToString().Trim()
|
||||||
|
} elseif ($meta.requirements_sha256) {
|
||||||
|
$currentHash = ($meta.requirements_sha256).ToString().Trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
$currentVersion = ''
|
||||||
|
$currentHash = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pythonCandidates = @(
|
||||||
|
Join-Path $eeRoot 'Scripts\python.exe',
|
||||||
|
Join-Path $eeRoot 'Scripts\python3.exe',
|
||||||
|
Join-Path $eeRoot 'bin\python3',
|
||||||
|
Join-Path $eeRoot 'bin\python'
|
||||||
|
)
|
||||||
|
|
||||||
|
$existingPython = $pythonCandidates | Where-Object { Test-Path $_ -PathType Leaf } | Select-Object -First 1
|
||||||
|
|
||||||
|
$expectedVersionNorm = ($ExpectedVersion ?? '1.0.0').Trim()
|
||||||
|
$isUpToDate = $false
|
||||||
|
if ($existingPython -and $currentVersion -and ($currentVersion -eq $expectedVersionNorm)) {
|
||||||
|
if (-not $requirementsHash -or ($currentHash -and $currentHash -eq $requirementsHash)) {
|
||||||
|
$isUpToDate = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isUpToDate) {
|
||||||
|
Write-AgentLog -FileName $LogName -Message "[AnsibleEE] Existing execution environment is up-to-date (version $currentVersion)."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-AgentLog -FileName $LogName -Message "[AnsibleEE] Provisioning execution environment version $expectedVersionNorm."
|
||||||
|
|
||||||
|
if (Test-Path $eeRoot) {
|
||||||
|
try { Remove-Item -Path $eeRoot -Recurse -Force -ErrorAction Stop } catch {}
|
||||||
|
}
|
||||||
|
New-Item -ItemType Directory -Force -Path $eeRoot | Out-Null
|
||||||
|
|
||||||
|
& $PythonBootstrapExe -m venv $eeRoot | Out-Null
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-AgentLog -FileName $LogName -Message "[AnsibleEE] python -m venv failed with exit code $LASTEXITCODE"
|
||||||
|
throw "Failed to create Ansible execution environment virtual environment."
|
||||||
|
}
|
||||||
|
|
||||||
|
$pythonExe = $pythonCandidates | Where-Object { Test-Path $_ -PathType Leaf } | Select-Object -First 1
|
||||||
|
if (-not $pythonExe) {
|
||||||
|
Write-AgentLog -FileName $LogName -Message "[AnsibleEE] Unable to locate python executable inside execution environment."
|
||||||
|
throw "Ansible execution environment python executable missing after provisioning."
|
||||||
|
}
|
||||||
|
|
||||||
|
& $pythonExe -m pip install --upgrade pip setuptools wheel --disable-pip-version-check | Out-Null
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-AgentLog -FileName $LogName -Message "[AnsibleEE] pip bootstrap failed with exit code $LASTEXITCODE"
|
||||||
|
throw "Failed to bootstrap pip inside the Ansible execution environment."
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($RequirementsPath -and (Test-Path $RequirementsPath -PathType Leaf)) {
|
||||||
|
& $pythonExe -m pip install --disable-pip-version-check -r $RequirementsPath | Out-Null
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-AgentLog -FileName $LogName -Message "[AnsibleEE] pip install -r requirements failed with exit code $LASTEXITCODE"
|
||||||
|
throw "Failed to install Ansible execution environment requirements."
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-AgentLog -FileName $LogName -Message "[AnsibleEE] Requirements file not found; skipping dependency installation."
|
||||||
|
}
|
||||||
|
|
||||||
|
$metadata = [ordered]@{
|
||||||
|
version = $expectedVersionNorm
|
||||||
|
created_utc = (Get-Date).ToUniversalTime().ToString('o')
|
||||||
|
python = $pythonExe
|
||||||
|
}
|
||||||
|
if ($requirementsHash) {
|
||||||
|
$metadata['requirements_hash'] = $requirementsHash
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$metadata | ConvertTo-Json -Depth 5 | Set-Content -Path $metadataPath -Encoding UTF8
|
||||||
|
} catch {
|
||||||
|
Write-AgentLog -FileName $LogName -Message "[AnsibleEE] Failed to persist metadata.json: $($_.Exception.Message)"
|
||||||
|
throw "Unable to persist Ansible execution environment metadata."
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Set-Content -Path $versionTxtPath -Value $expectedVersionNorm -Encoding UTF8
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
Write-AgentLog -FileName $LogName -Message "[AnsibleEE] Execution environment ready at $eeRoot"
|
||||||
|
}
|
||||||
|
|
||||||
function Ensure-AgentTasks {
|
function Ensure-AgentTasks {
|
||||||
param([string]$ScriptRoot)
|
param([string]$ScriptRoot)
|
||||||
$pyw = Join-Path $ScriptRoot 'Agent\Scripts\pythonw.exe'
|
$pyw = Join-Path $ScriptRoot 'Agent\Scripts\pythonw.exe'
|
||||||
@@ -569,6 +715,15 @@ function InstallOrUpdate-BorealisAgent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Run-Step "Provision Ansible Execution Environment" {
|
||||||
|
Ensure-AnsibleExecutionEnvironment \
|
||||||
|
-ProjectRoot $scriptDir \
|
||||||
|
-PythonBootstrapExe $pythonExe \
|
||||||
|
-RequirementsPath $ansibleEeRequirementsPath \
|
||||||
|
-ExpectedVersion $script:AnsibleExecutionEnvironmentVersion \
|
||||||
|
-LogName 'Install.log'
|
||||||
|
}
|
||||||
|
|
||||||
Run-Step "Configure Agent Settings" {
|
Run-Step "Configure Agent Settings" {
|
||||||
$settingsDir = Join-Path $scriptDir 'Agent\Borealis\Settings'
|
$settingsDir = Join-Path $scriptDir 'Agent\Borealis\Settings'
|
||||||
$oldSettingsDir = Join-Path $scriptDir 'Agent\Settings'
|
$oldSettingsDir = Join-Path $scriptDir 'Agent\Settings'
|
||||||
|
|||||||
@@ -229,18 +229,55 @@ def detect_agent_os():
|
|||||||
return "Unknown"
|
return "Unknown"
|
||||||
|
|
||||||
|
|
||||||
|
def _ansible_ee_version():
|
||||||
|
try:
|
||||||
|
root = _project_root()
|
||||||
|
meta_path = os.path.join(root, 'Ansible_EE', 'metadata.json')
|
||||||
|
if os.path.isfile(meta_path):
|
||||||
|
try:
|
||||||
|
with open(meta_path, 'r', encoding='utf-8') as fh:
|
||||||
|
data = json.load(fh)
|
||||||
|
if isinstance(data, dict):
|
||||||
|
for key in ('version', 'ansible_ee_ver', 'ansible_ee_version'):
|
||||||
|
value = data.get(key)
|
||||||
|
if isinstance(value, (str, int, float)):
|
||||||
|
text = str(value).strip()
|
||||||
|
if text:
|
||||||
|
return text
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
version_txt = os.path.join(root, 'Ansible_EE', 'version.txt')
|
||||||
|
if os.path.isfile(version_txt):
|
||||||
|
try:
|
||||||
|
raw = Path(version_txt).read_text(encoding='utf-8')
|
||||||
|
if raw:
|
||||||
|
text = raw.splitlines()[0].strip()
|
||||||
|
if text:
|
||||||
|
return text
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def collect_summary(CONFIG):
|
def collect_summary(CONFIG):
|
||||||
try:
|
try:
|
||||||
hostname = socket.gethostname()
|
hostname = socket.gethostname()
|
||||||
return {
|
summary = {
|
||||||
'hostname': hostname,
|
'hostname': hostname,
|
||||||
'os': detect_agent_os(),
|
'os': detect_agent_os(),
|
||||||
'username': os.environ.get('USERNAME') or os.environ.get('USER') or '',
|
'username': os.environ.get('USERNAME') or os.environ.get('USER') or '',
|
||||||
'domain': os.environ.get('USERDOMAIN') or '',
|
'domain': os.environ.get('USERDOMAIN') or '',
|
||||||
'uptime_sec': int(time.time() - psutil.boot_time()) if psutil else None,
|
'uptime_sec': int(time.time() - psutil.boot_time()) if psutil else None,
|
||||||
}
|
}
|
||||||
|
summary['ansible_ee_ver'] = _ansible_ee_version()
|
||||||
|
return summary
|
||||||
except Exception:
|
except Exception:
|
||||||
return {'hostname': socket.gethostname()}
|
return {
|
||||||
|
'hostname': socket.gethostname(),
|
||||||
|
'ansible_ee_ver': _ansible_ee_version(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _project_root():
|
def _project_root():
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import json
|
|||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import base64
|
import base64
|
||||||
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -53,6 +54,62 @@ 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 _ansible_ee_root():
|
||||||
|
candidates = []
|
||||||
|
try:
|
||||||
|
candidates.append(os.path.join(_project_root(), 'Agent', 'Ansible_EE'))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
candidates.append(os.path.join(_agent_root(), 'Ansible_EE'))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
for path in candidates:
|
||||||
|
if path and os.path.isdir(path):
|
||||||
|
return path
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _ansible_ee_metadata():
|
||||||
|
root = _ansible_ee_root()
|
||||||
|
if not root:
|
||||||
|
return {}
|
||||||
|
meta_path = os.path.join(root, 'metadata.json')
|
||||||
|
if not os.path.isfile(meta_path):
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
with open(meta_path, 'r', encoding='utf-8') as fh:
|
||||||
|
data = json.load(fh)
|
||||||
|
if isinstance(data, dict):
|
||||||
|
return data
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _ansible_ee_version():
|
||||||
|
meta = _ansible_ee_metadata()
|
||||||
|
for key in ('version', 'ansible_ee_ver', 'ansible_ee_version'):
|
||||||
|
value = meta.get(key) if isinstance(meta, dict) else None
|
||||||
|
if isinstance(value, (str, int, float)):
|
||||||
|
text = str(value).strip()
|
||||||
|
if text:
|
||||||
|
return text
|
||||||
|
root = _ansible_ee_root()
|
||||||
|
if root:
|
||||||
|
txt_path = os.path.join(root, 'version.txt')
|
||||||
|
if os.path.isfile(txt_path):
|
||||||
|
try:
|
||||||
|
raw = Path(txt_path).read_text(encoding='utf-8')
|
||||||
|
if raw:
|
||||||
|
text = raw.splitlines()[0].strip()
|
||||||
|
if text:
|
||||||
|
return text
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def _decode_base64_text(value):
|
def _decode_base64_text(value):
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
return None
|
return None
|
||||||
@@ -99,11 +156,22 @@ def _agent_root():
|
|||||||
|
|
||||||
def _scripts_bin():
|
def _scripts_bin():
|
||||||
# Return the venv Scripts (Windows) or bin (POSIX) path adjacent to Borealis
|
# Return the venv Scripts (Windows) or bin (POSIX) path adjacent to Borealis
|
||||||
|
candidates = []
|
||||||
|
ee_root = _ansible_ee_root()
|
||||||
|
if ee_root:
|
||||||
|
candidates.extend(
|
||||||
|
[
|
||||||
|
os.path.join(ee_root, 'Scripts'),
|
||||||
|
os.path.join(ee_root, 'bin'),
|
||||||
|
]
|
||||||
|
)
|
||||||
agent_root = _agent_root()
|
agent_root = _agent_root()
|
||||||
candidates = [
|
candidates.extend(
|
||||||
os.path.join(agent_root, 'Scripts'), # Windows venv
|
[
|
||||||
os.path.join(agent_root, 'bin'), # POSIX venv
|
os.path.join(agent_root, 'Scripts'), # Windows venv
|
||||||
]
|
os.path.join(agent_root, 'bin'), # POSIX venv
|
||||||
|
]
|
||||||
|
)
|
||||||
for base in candidates:
|
for base in candidates:
|
||||||
if os.path.isdir(base):
|
if os.path.isdir(base):
|
||||||
return base
|
return base
|
||||||
@@ -137,10 +205,19 @@ def _collections_dir():
|
|||||||
return base
|
return base
|
||||||
|
|
||||||
def _venv_python():
|
def _venv_python():
|
||||||
|
ee_root = _ansible_ee_root()
|
||||||
|
if ee_root:
|
||||||
|
ee_candidates = [
|
||||||
|
os.path.join(ee_root, 'Scripts', 'python.exe'),
|
||||||
|
os.path.join(ee_root, 'Scripts', 'python3.exe'),
|
||||||
|
os.path.join(ee_root, 'bin', 'python3'),
|
||||||
|
os.path.join(ee_root, 'bin', 'python'),
|
||||||
|
]
|
||||||
|
for cand in ee_candidates:
|
||||||
|
if os.path.isfile(cand):
|
||||||
|
return cand
|
||||||
try:
|
try:
|
||||||
sdir = _scripts_bin()
|
sdir = os.path.join(_agent_root(), 'Scripts' if os.name == 'nt' else 'bin')
|
||||||
if not sdir:
|
|
||||||
return None
|
|
||||||
cand = os.path.join(sdir, 'python.exe' if os.name == 'nt' else 'python3')
|
cand = os.path.join(sdir, 'python.exe' if os.name == 'nt' else 'python3')
|
||||||
return cand if os.path.isfile(cand) else None
|
return cand if os.path.isfile(cand) else None
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -185,40 +262,55 @@ class Role:
|
|||||||
|
|
||||||
def _bootstrap_ansible_sync(self) -> bool:
|
def _bootstrap_ansible_sync(self) -> bool:
|
||||||
missing = self._detect_missing_modules()
|
missing = self._detect_missing_modules()
|
||||||
if not missing:
|
if missing:
|
||||||
return True
|
self._ansible_log(
|
||||||
specs = sorted({spec for spec in missing.values() if spec})
|
f"[bootstrap] required agent modules missing: {', '.join(sorted(missing.keys()))}",
|
||||||
python_exe = _venv_python() or sys.executable
|
error=True,
|
||||||
if not python_exe:
|
)
|
||||||
self._ansible_log('[bootstrap] python executable not found for pip install', error=True)
|
|
||||||
return False
|
return False
|
||||||
cmd = [python_exe, '-m', 'pip', 'install', '--disable-pip-version-check'] + specs
|
ee_root = _ansible_ee_root()
|
||||||
self._ansible_log(f"[bootstrap] ensuring modules via pip: {', '.join(specs)}")
|
if not ee_root or not os.path.isdir(ee_root):
|
||||||
try:
|
self._ansible_log('[bootstrap] execution environment folder Agent/Ansible_EE not found', error=True)
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=900)
|
|
||||||
except Exception as exc:
|
|
||||||
self._ansible_log(f"[bootstrap] pip install exception: {exc}", error=True)
|
|
||||||
return False
|
return False
|
||||||
if result.returncode != 0:
|
|
||||||
err_tail = (result.stderr or '').strip()
|
scripts_dir = _scripts_bin()
|
||||||
if len(err_tail) > 500:
|
exe_name = 'ansible-playbook.exe' if os.name == 'nt' else 'ansible-playbook'
|
||||||
err_tail = err_tail[-500:]
|
playbook_path = None
|
||||||
self._ansible_log(f"[bootstrap] pip install failed rc={result.returncode} err={err_tail}", error=True)
|
if scripts_dir:
|
||||||
|
candidate = os.path.join(scripts_dir, exe_name)
|
||||||
|
if os.path.isfile(candidate):
|
||||||
|
playbook_path = candidate
|
||||||
|
if not playbook_path:
|
||||||
|
self._ansible_log('[bootstrap] ansible-playbook executable missing in execution environment', error=True)
|
||||||
return False
|
return False
|
||||||
remaining = self._detect_missing_modules()
|
|
||||||
if remaining:
|
python_exe = _venv_python()
|
||||||
self._ansible_log(f"[bootstrap] modules still missing after install: {', '.join(sorted(remaining.keys()))}", error=True)
|
if not python_exe or not os.path.isfile(python_exe):
|
||||||
|
self._ansible_log('[bootstrap] execution environment python not found', error=True)
|
||||||
return False
|
return False
|
||||||
try:
|
|
||||||
marker = self._bootstrap_marker_path()
|
env_path = os.environ.get('PATH') or ''
|
||||||
payload = {
|
bin_dir = os.path.dirname(playbook_path)
|
||||||
'timestamp': int(time.time()),
|
if bin_dir:
|
||||||
'modules': specs,
|
segments = [seg for seg in env_path.split(os.pathsep) if seg]
|
||||||
}
|
if bin_dir not in segments:
|
||||||
with open(marker, 'w', encoding='utf-8') as fh:
|
os.environ['PATH'] = bin_dir + (os.pathsep + env_path if env_path else '')
|
||||||
json.dump(payload, fh)
|
|
||||||
except Exception:
|
collections_dir = os.path.join(ee_root, 'collections')
|
||||||
pass
|
if os.path.isdir(collections_dir):
|
||||||
|
existing = os.environ.get('ANSIBLE_COLLECTIONS_PATHS') or ''
|
||||||
|
paths = [seg for seg in existing.split(os.pathsep) if seg]
|
||||||
|
if collections_dir not in paths:
|
||||||
|
os.environ['ANSIBLE_COLLECTIONS_PATHS'] = (
|
||||||
|
collections_dir if not existing else collections_dir + os.pathsep + existing
|
||||||
|
)
|
||||||
|
|
||||||
|
os.environ['BOREALIS_ANSIBLE_EE_ROOT'] = ee_root
|
||||||
|
os.environ['BOREALIS_ANSIBLE_EE_PYTHON'] = python_exe
|
||||||
|
|
||||||
|
version = _ansible_ee_version()
|
||||||
|
if version:
|
||||||
|
self._ansible_log(f"[bootstrap] using execution environment version {version}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _ensure_ansible_ready(self) -> bool:
|
async def _ensure_ansible_ready(self) -> bool:
|
||||||
|
|||||||
8
Data/Agent/ansible-ee-requirements.txt
Normal file
8
Data/Agent/ansible-ee-requirements.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/Agent/ansible-ee-requirements.txt
|
||||||
|
# Ansible execution environment dependencies
|
||||||
|
ansible-core==2.16.7
|
||||||
|
ansible-runner==2.3.5
|
||||||
|
pywinrm==0.4.3
|
||||||
|
requests-credssp==2.0.0
|
||||||
|
requests-ntlm==1.2.0
|
||||||
|
pypsrp==0.8.1
|
||||||
2
Data/Agent/ansible-ee-version.txt
Normal file
2
Data/Agent/ansible-ee-version.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/Agent/ansible-ee-version.txt
|
||||||
|
1.0.0
|
||||||
@@ -3514,6 +3514,7 @@ _DEVICE_TABLE_COLUMNS = [
|
|||||||
"operating_system",
|
"operating_system",
|
||||||
"uptime",
|
"uptime",
|
||||||
"agent_id",
|
"agent_id",
|
||||||
|
"ansible_ee_ver",
|
||||||
"connection_type",
|
"connection_type",
|
||||||
"connection_endpoint",
|
"connection_endpoint",
|
||||||
]
|
]
|
||||||
@@ -3603,6 +3604,7 @@ def _assemble_device_snapshot(record: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
"created": _ts_to_human(created_ts),
|
"created": _ts_to_human(created_ts),
|
||||||
"connection_type": _clean_device_str(record.get("connection_type")) or "",
|
"connection_type": _clean_device_str(record.get("connection_type")) or "",
|
||||||
"connection_endpoint": _clean_device_str(record.get("connection_endpoint")) or "",
|
"connection_endpoint": _clean_device_str(record.get("connection_endpoint")) or "",
|
||||||
|
"ansible_ee_ver": _clean_device_str(record.get("ansible_ee_ver")) or "",
|
||||||
}
|
}
|
||||||
|
|
||||||
details = {
|
details = {
|
||||||
@@ -3747,6 +3749,7 @@ def _extract_device_columns(details: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
)
|
)
|
||||||
payload["uptime"] = _coerce_int(uptime_value)
|
payload["uptime"] = _coerce_int(uptime_value)
|
||||||
payload["agent_id"] = _clean_device_str(summary.get("agent_id"))
|
payload["agent_id"] = _clean_device_str(summary.get("agent_id"))
|
||||||
|
payload["ansible_ee_ver"] = _clean_device_str(summary.get("ansible_ee_ver"))
|
||||||
payload["connection_type"] = _clean_device_str(
|
payload["connection_type"] = _clean_device_str(
|
||||||
summary.get("connection_type")
|
summary.get("connection_type")
|
||||||
or summary.get("remote_type")
|
or summary.get("remote_type")
|
||||||
@@ -3815,9 +3818,10 @@ def _device_upsert(
|
|||||||
operating_system,
|
operating_system,
|
||||||
uptime,
|
uptime,
|
||||||
agent_id,
|
agent_id,
|
||||||
|
ansible_ee_ver,
|
||||||
connection_type,
|
connection_type,
|
||||||
connection_endpoint
|
connection_endpoint
|
||||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||||
ON CONFLICT(hostname) DO UPDATE SET
|
ON CONFLICT(hostname) DO UPDATE SET
|
||||||
description=excluded.description,
|
description=excluded.description,
|
||||||
created_at=COALESCE({DEVICE_TABLE}.created_at, excluded.created_at),
|
created_at=COALESCE({DEVICE_TABLE}.created_at, excluded.created_at),
|
||||||
@@ -3838,6 +3842,7 @@ def _device_upsert(
|
|||||||
operating_system=COALESCE(NULLIF(excluded.operating_system, ''), {DEVICE_TABLE}.operating_system),
|
operating_system=COALESCE(NULLIF(excluded.operating_system, ''), {DEVICE_TABLE}.operating_system),
|
||||||
uptime=COALESCE(NULLIF(excluded.uptime, 0), {DEVICE_TABLE}.uptime),
|
uptime=COALESCE(NULLIF(excluded.uptime, 0), {DEVICE_TABLE}.uptime),
|
||||||
agent_id=COALESCE(NULLIF(excluded.agent_id, ''), {DEVICE_TABLE}.agent_id),
|
agent_id=COALESCE(NULLIF(excluded.agent_id, ''), {DEVICE_TABLE}.agent_id),
|
||||||
|
ansible_ee_ver=COALESCE(NULLIF(excluded.ansible_ee_ver, ''), {DEVICE_TABLE}.ansible_ee_ver),
|
||||||
connection_type=COALESCE(NULLIF(excluded.connection_type, ''), {DEVICE_TABLE}.connection_type),
|
connection_type=COALESCE(NULLIF(excluded.connection_type, ''), {DEVICE_TABLE}.connection_type),
|
||||||
connection_endpoint=COALESCE(NULLIF(excluded.connection_endpoint, ''), {DEVICE_TABLE}.connection_endpoint)
|
connection_endpoint=COALESCE(NULLIF(excluded.connection_endpoint, ''), {DEVICE_TABLE}.connection_endpoint)
|
||||||
"""
|
"""
|
||||||
@@ -3863,6 +3868,7 @@ def _device_upsert(
|
|||||||
column_values.get("operating_system"),
|
column_values.get("operating_system"),
|
||||||
column_values.get("uptime"),
|
column_values.get("uptime"),
|
||||||
column_values.get("agent_id"),
|
column_values.get("agent_id"),
|
||||||
|
column_values.get("ansible_ee_ver"),
|
||||||
column_values.get("connection_type"),
|
column_values.get("connection_type"),
|
||||||
column_values.get("connection_endpoint"),
|
column_values.get("connection_endpoint"),
|
||||||
]
|
]
|
||||||
@@ -4160,7 +4166,10 @@ def init_db():
|
|||||||
last_user TEXT,
|
last_user TEXT,
|
||||||
operating_system TEXT,
|
operating_system TEXT,
|
||||||
uptime INTEGER,
|
uptime INTEGER,
|
||||||
agent_id TEXT
|
agent_id TEXT,
|
||||||
|
ansible_ee_ver TEXT,
|
||||||
|
connection_type TEXT,
|
||||||
|
connection_endpoint TEXT
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
@@ -4193,6 +4202,7 @@ def init_db():
|
|||||||
_ensure_column("operating_system", "TEXT")
|
_ensure_column("operating_system", "TEXT")
|
||||||
_ensure_column("uptime", "INTEGER")
|
_ensure_column("uptime", "INTEGER")
|
||||||
_ensure_column("agent_id", "TEXT")
|
_ensure_column("agent_id", "TEXT")
|
||||||
|
_ensure_column("ansible_ee_ver", "TEXT")
|
||||||
_ensure_column("connection_type", "TEXT")
|
_ensure_column("connection_type", "TEXT")
|
||||||
_ensure_column("connection_endpoint", "TEXT")
|
_ensure_column("connection_endpoint", "TEXT")
|
||||||
|
|
||||||
@@ -4274,6 +4284,7 @@ def init_db():
|
|||||||
operating_system TEXT,
|
operating_system TEXT,
|
||||||
uptime INTEGER,
|
uptime INTEGER,
|
||||||
agent_id TEXT,
|
agent_id TEXT,
|
||||||
|
ansible_ee_ver TEXT,
|
||||||
connection_type TEXT,
|
connection_type TEXT,
|
||||||
connection_endpoint TEXT
|
connection_endpoint TEXT
|
||||||
)
|
)
|
||||||
|
|||||||
21
Update.ps1
21
Update.ps1
@@ -497,6 +497,8 @@ function Invoke-BorealisUpdate {
|
|||||||
|
|
||||||
$preservePath = Join-Path $scriptDir "Data\Server\Python_API_Endpoints\Tesseract-OCR"
|
$preservePath = Join-Path $scriptDir "Data\Server\Python_API_Endpoints\Tesseract-OCR"
|
||||||
$preserveBackupPath = Join-Path $scriptDir "Update_Staging\Tesseract-OCR"
|
$preserveBackupPath = Join-Path $scriptDir "Update_Staging\Tesseract-OCR"
|
||||||
|
$ansibleEePath = Join-Path $scriptDir "Agent\Ansible_EE"
|
||||||
|
$ansibleEeBackupPath = Join-Path $scriptDir "Update_Staging\Ansible_EE"
|
||||||
|
|
||||||
Run-Step "Updating: Move Tesseract-OCR Folder Somewhere Safe to Restore Later" {
|
Run-Step "Updating: Move Tesseract-OCR Folder Somewhere Safe to Restore Later" {
|
||||||
if (Test-Path $preservePath) {
|
if (Test-Path $preservePath) {
|
||||||
@@ -506,6 +508,17 @@ function Invoke-BorealisUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Run-Step "Updating: Preserve Ansible Execution Environment" {
|
||||||
|
if (Test-Path $ansibleEePath) {
|
||||||
|
$stagingPath = Join-Path $scriptDir "Update_Staging"
|
||||||
|
if (-not (Test-Path $stagingPath)) { New-Item -ItemType Directory -Force -Path $stagingPath | Out-Null }
|
||||||
|
if (Test-Path $ansibleEeBackupPath) {
|
||||||
|
Remove-Item -Path $ansibleEeBackupPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
Move-Item -Path $ansibleEePath -Destination $ansibleEeBackupPath -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Run-Step "Updating: Clean Up Folders to Prepare for Update" {
|
Run-Step "Updating: Clean Up Folders to Prepare for Update" {
|
||||||
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue `
|
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue `
|
||||||
(Join-Path $scriptDir "Data"), `
|
(Join-Path $scriptDir "Data"), `
|
||||||
@@ -575,6 +588,14 @@ function Invoke-BorealisUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Run-Step "Updating: Restore Ansible Execution Environment" {
|
||||||
|
$restorePath = Join-Path $scriptDir "Agent"
|
||||||
|
if (Test-Path $ansibleEeBackupPath) {
|
||||||
|
if (-not (Test-Path $restorePath)) { New-Item -ItemType Directory -Force -Path $restorePath | Out-Null }
|
||||||
|
Move-Item -Path $ansibleEeBackupPath -Destination $restorePath -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Run-Step "Updating: Clean Up Update Staging Folder" {
|
Run-Step "Updating: Clean Up Update Staging Folder" {
|
||||||
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $stagingPath
|
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $stagingPath
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user