Provision reusable Ansible execution environment

This commit is contained in:
2025-10-12 14:13:06 -06:00
parent 8cae44539c
commit 1e9912efd2
7 changed files with 367 additions and 41 deletions

View File

@@ -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'

View File

@@ -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():

View File

@@ -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:

View 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

View 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

View File

@@ -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
) )

View File

@@ -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
} }