Skip to content

Commit

Permalink
Agent MSI support (#212)
Browse files Browse the repository at this point in the history
* first draft

* 6

* 6

* Preventing agent upgrade via MSI

* Update src/installer/BeatPackageCompiler/BeatPackageCompiler.cs

Co-authored-by: Craig MacKenzie <[email protected]>

* Update README.md

* Update README.md

* Fixing uninstall flow and install cleanup flow

* rolling back if agent install command fails

* redirecting stdout to MSI log

* Failing the uninstall flow in case agent uninstall command fails

* Don't attempt calling agent install if the file doesn't exist

* adding BK logic (#219)

* Update build.ps1

* Clean up path length

Instead of re-cloning to a checkout with a shorter path,
we rename the existing BK checkout.

* Fix 798ff24

* Typo

* Build from MANIFEST_URL and don't sign

This commit implements the refactoring to allow
a short term integration with the unified release.
It's meant to be triggered by getting passed a $MANIFEST_URL
and $DRA_WORKFLOW env var.

* Update README.md

Co-authored-by: Craig MacKenzie <[email protected]>

* Update README.md

Co-authored-by: Craig MacKenzie <[email protected]>

* Update README.md

Co-authored-by: Craig MacKenzie <[email protected]>

* Update README.md

Co-authored-by: Craig MacKenzie <[email protected]>

* Redirecting stderr and removing PATH manipulation for Agent MSI

* Remove cron schedule

As discussed in https://elasticco.atlassian.net/browse/REL-1004?focusedCommentId=107598

* Trigger 7.17 beats DRA using schedule

As the release manager can't trigger the workflow introduced in
566b9e4 , we maintain the old
way of building snapshot/staging beats artifacts by keeping the
trigger pipeline only for that branch.

Note: this means that this PR **MUST NOT** be backported to the
elastic-stack-installers 7.17 branch.

Details: https://elasticco.atlassian.net/browse/REL-1004?focusedCommentId=107973

* Making sure Agent MSI runs as an administrator

* Add MSI tests for Agent Pipeline (#220)

Add Install, Upgrade, and Uninstall MSI tests using Pester w/ Powershell Core

* Enabling agent tests

* Disable "Default" test case as it doesn't exist anymore. Don't fail build on test failure for now.

* Default mode clean-up

* Update build.ps1

---------

Co-authored-by: Craig MacKenzie <[email protected]>
Co-authored-by: Dimitrios Liappis <[email protected]>
Co-authored-by: William Easton <[email protected]>
Co-authored-by: William Easton <[email protected]>
  • Loading branch information
5 people authored Jan 29, 2024
1 parent 06c284c commit 887274a
Show file tree
Hide file tree
Showing 22 changed files with 1,436 additions and 153 deletions.
22 changes: 11 additions & 11 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@
### The following environment variables can be set for testing purposes via the buildkite UI or CLI
### RUN_SNAPSHOT: "true" - will run the snapshot workflow
### RUN_STAGING: "true" - will run the staging workflow
### DBRANCH: "main" - will override the dra branch param. Default is $BUILDKITE_BRANCH
### ONLY_AGENT: "true" - will build only the elastic-agent msi artifact
###

steps:
- group: ":beats: Stack installers Snapshot"
- group: ":package: Stack installers Snapshot"
key: "dra-snapshot"
steps:
- label: ":hammer: Build stack installers Snapshot"
- label: ":construction_worker: Build stack installers / Snapshot"
command: ".buildkite/scripts/build.ps1"
key: "build-snapshot"
artifact_paths: "bin/out/**/*.msi"
artifact_paths: "c:/users/buildkite/esi/bin/out/**/*.msi"
agents:
provider: gcp
image: family/ci-windows-2022
env:
WORKFLOW: "snapshot"
DRA_WORKFLOW: "snapshot"
- label: ":package: DRA Publish Snapshot"
if: build.branch == 'main' || build.branch =~ /^[0-9]+\.[0-9]+\$/ || build.env("RUN_SNAPSHOT") == "true"
command: ".buildkite/scripts/dra-publish.sh"
Expand All @@ -26,20 +26,20 @@ steps:
agents:
provider: gcp
env:
WORKFLOW: "snapshot"
- group: ":beats: Stack installers Staging :beats:"
DRA_WORKFLOW: "snapshot"
- group: ":package: Stack installers Staging"
key: "dra-staging"
steps:
- label: ":hammer: Build stack installers staging"
- label: ":construction_worker: Build stack installers / Staging"
if: build.branch =~ /^[0-9]+\.[0-9]+\$/ || build.env("RUN_STAGING") == "true"
command: ".buildkite/scripts/build.ps1"
key: "build-staging"
artifact_paths: "bin/out/**/*.msi"
artifact_paths: "c:/users/buildkite/esi/bin/out/**/*.msi"
agents:
provider: gcp
image: family/ci-windows-2022
env:
WORKFLOW: "staging"
DRA_WORKFLOW: "staging"
- label: ":package: DRA Publish staging"
if: build.branch =~ /^[0-9]+\.[0-9]+\$/ || build.env("RUN_STAGING") == "true"
command: ".buildkite/scripts/dra-publish.sh"
Expand All @@ -48,7 +48,7 @@ steps:
agents:
provider: gcp
env:
WORKFLOW: "staging"
DRA_WORKFLOW: "staging"

notify:
- slack: "#ingest-notifications"
Expand Down
151 changes: 94 additions & 57 deletions .buildkite/scripts/build.ps1
Original file line number Diff line number Diff line change
@@ -1,72 +1,93 @@
$ErrorActionPreference = "Stop"
Set-Strictmode -version 3

if (-not (Test-Path env:MANIFEST_URL)) {
$errorMessage = "Error: Required environment variable [MANIFEST_URL] is missing."
Write-Host $errorMessage
throw $errorMessage
}

# workaround path limitation for max 248 characters
# example: https://buildkite.com/elastic/elastic-stack-installers/builds/3104#018c5e1b-23a7-4330-ad5d-4acc69157822/74-180
cd ..
# we can't use Rename-Item because this script runs from within the existing checkout resulting in
# Rename-Item : The process cannot access the file because it is being used by another process.
Copy-Item -Path .\elastic-stack-installers -Destination c:\users\buildkite\esi -Recurse
cd c:\users\buildkite\esi

# Read the stack version from build properties
[xml]$xml = Get-Content -Path "Directory.Build.props"
$ns = New-Object Xml.XmlNamespaceManager($xml.NameTable)
$ns.AddNamespace("ns", "http://schemas.microsoft.com/developer/msbuild/2003")
$stack_version = $xml.SelectSingleNode("//ns:PropertyGroup/ns:StackVersion", $ns).InnerText
Write-Host "Building Stack version: $stack_version"
$workflow = ${env:DRA_WORKFLOW}
if ($workflow -eq "snapshot") {
$version = $stack_version + "-" + $workflow.ToUpper()
} else {
$version = $stack_version
}

echo "~~~ Installing dotnet-sdk"
Write-Host "~~~ Building Stack version: $stack_version"

Write-Output "~~~ Installing dotnet-sdk"
& "./tools/dotnet-install.ps1" -NoPath -JSonFile global.json -Architecture "x64" -InstallDir c:/dotnet-sdk
${env:PATH} = "c:\dotnet-sdk;" + ${env:PATH}
Get-Command dotnet | Select-Object -ExpandProperty Definition

echo "~~~ Reading msi certificate from vault"
$MsiCertificate=& vault read -field=cert secret/ci/elastic-elastic-stack-installers/signing_cert
$MsiPassword=& vault read -field=password secret/ci/elastic-elastic-stack-installers/signing_cert
Remove-Item Env:VAULT_TOKEN

$cert_home="C:/.cert"
New-Item $cert_home -Type Directory -Force
[IO.File]::WriteAllBytes("$cert_home/msi_certificate.p12", [Convert]::FromBase64String($MsiCertificate))
[IO.File]::WriteAllText("$cert_home/msi_password.txt", $MsiPassword)
echo "Certificate successfully written to $cert_home"

$client = new-object System.Net.WebClient
$currentDir = $(Get-Location).Path
$beats = @('auditbeat', 'filebeat', 'heartbeat', 'metricbeat', 'packetbeat', 'winlogbeat')
$ossBeats = $beats | ForEach-Object { $_ + "-oss" }
$workflow = ${env:WORKFLOW}

echo "~~~ downloading beat $workflow dependencies"
# TODO remove (and all references/conditionals below) after testing; this just helps to speed up elastic-agent specific build tests
$onlyAgent = $env:ONLY_AGENT -as [string]

Remove-Item bin/in -Recurse -Force -ErrorAction Ignore
New-Item bin/in -Type Directory -Force
if ($workflow -eq "snapshot") {
$version = $stack_version + "-" + $workflow.ToUpper()
$hostname = "artifacts-snapshot.elastic.co"
$response = Invoke-WebRequest -UseBasicParsing -Uri "https://$hostname/beats/latest/$version.json"
$json = $response.Content | ConvertFrom-Json
$buildId = $json.build_id
$prefix = "$hostname/beats/$buildId"
} else {
$version = $stack_version
$hostname = "artifacts-staging.elastic.co"
$response = Invoke-WebRequest -UseBasicParsing -Uri "https://$hostname/beats/latest/$version.json"
$json = $response.Content | ConvertFrom-Json
$buildId = $json.build_id
$prefix = "$hostname/beats/$buildId"
}
foreach ($beat in ($beats + $ossBeats)) {
try {
$beatName = $beat.Replace("-oss", "")
$url = "https://$prefix/downloads/beats/$beatName/$beat-$version-windows-x86_64.zip"
echo "Downloading from $url"

$manifestUrl = ${env:MANIFEST_URL}
$response = Invoke-WebRequest -UseBasicParsing -Uri $manifestUrl
$json = $response.Content | ConvertFrom-Json

Write-Output "~~~ Downloading $workflow dependencies"

$urls = @()

try {
$packageName = "elastic-agent"
$projectName = "$packageName-package"
$urls += $json.projects.$projectName.packages."$packageName-$version-windows-x86_64.zip".url

if ($onlyAgent -eq "true") {
Write-Output "Skipping beats because env var ONLY_AGENT is set to [$env:ONLY_AGENT]"
}
else {
$projectName = "beats"
foreach ($packageName in ($beats + $ossBeats)) {
$urls += $json.projects.$projectName.packages."$packageName-$version-windows-x86_64.zip".url
}
}
foreach ($url in $urls) {
$destFile = [System.IO.Path]::GetFileName($url)
Write-Output "Downloading from $url to $currentDir/bin/in/$destFile"
$client.DownloadFile(
$url,
"$currentDir/bin/in/$beat-$version-windows-x86_64.zip"
"$currentDir\bin\in\$destFile"
)
}
catch [System.Net.WebException] {
if ($_.Exception.InnerException) {
Write-Error $_.Exception.InnerException.Message
} else {
Write-Error $_.Exception.Message
}
throw "An error was encountered while downloading dependencies, aborting."
} catch [System.Net.WebException] {
if ($_.Exception.InnerException) {
Write-Error $_.Exception.InnerException.Message
} else {
Write-Error $_.Exception.Message
}
throw "An error was encountered while downloading dependencies, aborting."
}

echo "--- Building $workflow msi"
$args = @(
Remove-Item bin/out -Recurse -Force -ErrorAction Ignore

Write-Output "--- Building $workflow msi"
$cliArgs = @(
"run",
"--project",
"src\build\ElastiBuild.csproj",
Expand All @@ -75,25 +96,41 @@ $args = @(
"--",
"build",
"--cid",
$version,
"--cert-file",
"$cert_home/msi_certificate.p12",
"--cert-pass",
"$cert_home/msi_password.txt"
$version
)
$args += ($beats + $ossBeats)
&dotnet $args
$cliArgs += "elastic-agent"
if ($onlyAgent -ne "true") {
$cliArgs += ($beats + $ossBeats)
}

&dotnet $cliArgs
if ($LastExitcode -ne 0) {
Write-Error "Build$workflow failed with exit code $LastExitcode"
Write-Error "Build $workflow failed with exit code $LastExitcode"
exit $LastExitcode
} else {
echo "Build$workflow completed with exit code $LastExitcode"
Write-Output "Build $workflow completed with exit code $LastExitcode"
}

echo "--- Checking that all artefacts are there"
Write-Output "--- Checking that all artifacts are there"
$msiCount = Get-ChildItem bin/out -Include "*.msi" -Recurse | Measure-Object | Select-Object -ExpandProperty Count
$expected = 2 * $beats.Length

$expected = 1
if ($onlyAgent -ne "true") {
$expected += (2 * $beats.Length)
}

if ($msiCount -ne $expected) {
Write-Error "Expected $expected msi executable to be produced, but $msiCount were"
Write-Error "Failed: expected $expected msi executables to be produced, but $msiCount were found."
exit 1
} else {
Write-Output "Success, found $msiCount artifacts in bin/out."
}

try {
& (Join-Path $PSScriptRoot "test.ps1")
write-host "Testing Completed"
} catch {
write-host "Testing Failed"
write-error $_
exit 1
}
45 changes: 45 additions & 0 deletions .buildkite/scripts/test.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
$ErrorActionPreference = "Stop"
Set-Strictmode -version 3

write-host (ConvertTo-Json $PSVersiontable -Compress)
write-host "Running as: $([Environment]::UserName)"

write-host "`$env:AGENT = $($Env:AGENT)"

if ($psversiontable.psversion -lt "7.4.0") {
# Download Powershell Core, and rerun this script using Powershell Core

write-host "Downloading Powershell Core"
invoke-webrequest -uri https://github.com/PowerShell/PowerShell/releases/download/v7.4.0/PowerShell-7.4.0-win-x64.zip -outfile pwsh.zip

write-host "Expanding Powershell Core"
Expand-Archive pwsh.zip -destinationpath (Join-Path $PSScriptRoot "pwsh")

Write-host "Invoking from Powershell Core"
& (Join-Path $PSScriptRoot "pwsh/pwsh.exe") -file (Join-Path $PSScriptRoot "test.ps1")

if ($LASTEXITCODE -eq 0) {
write-host "Child pwsh process exited successfully"
exit 0
} else {
write-host "Child pwsh process returned $LASTEXITCODE, a non zero exit code"
throw "Tests failed."
}
}

$AgentMSI = Get-ChildItem bin/out -Include "elastic-agent*.msi" -Recurse

if ($AgentMSI -eq $null) {
write-error "No agent MSI found to test"
}


$OldAgentMSI = (Join-Path $PSScriptRoot "elastic-agent-8.11.4-windows-x86_64.msi")
if (-not (test-path $OldAgentMSI)) {
Write-Host "Downloading older MSI for upgrade tests"
invoke-webrequest -uri https://storage.googleapis.com/agent-msi-testing/elastic-agent-8.11.4-windows-x86_64.msi -outfile $OldAgentMSI
}

& (Join-Path $PSScriptRoot "../../src/agent-qa/Invoke-Pester.ps1") -PathToLatestMSI $AgentMSI.Fullname -PathToEarlyMSI $OldAgentMSI

write-host "Returned from Pester Test"
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,6 @@ src/*.cmd
# Executable Artifacts
src/*.exe
src/*.pdb
src/*.dll
src/*.dll
pwsh
pwsh.zip
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,28 @@ Update version in `Directory.Build.props` in the branch for the related minor ve
- Add a new daily schedule for the new minor branch
- Remove the daily schedule for the previous minor branch
ex: https://github.com/elastic/elastic-stack-installers/pull/156 and https://github.com/elastic/elastic-stack-installers/pull/172

---
## Agent

In case of problems during install / uninstall of agent, please refer to the [Capturing Logs](https://github.com/elastic/elastic-stack-installers/blob/agent_support/README.md#capturing-logs) section which will enable troubleshooting.

### Install
During the install flow, The MSI installer will unpack the contents of the MSI to a temp folder and then will call the `elastic-agent install` in order to:
1. copy the files to the final destination at `c:\Program Files\Elastic\Agent`
2. register the agent as a windows service
3. enroll the agent into fleet

In order to complete step 3 above, the MSI installer shall receive command line arguments, passed with INSTALLARGS command line switch followed by `"`, for example:
```
elastic-agent.msi INSTALLARGS="--url=<fleet_url_with_port> --enrollment-token=<token>"
```

Note that the MSI will call the `elastic-agent install` command with `-f` (force) to avoid user interaction.

### Uninstall
Similarly to the install flow (described above), the MSI will call the `elastic-agent uninstall` command, and it's possible to pass arguments using `INSTALLARGS`. One common use case is uninstalling an agent which has tamper protection enabled.

### Upgrade
The Agent MSI doesn't support upgrade. Since the agents are fleet managed, upgrades shall be done using fleet (UI / API).

16 changes: 2 additions & 14 deletions catalog-info.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ spec:
publish_commit_status: true
publish_commit_status_per_step: false
repository: elastic/elastic-stack-installers
schedules:
Daily main:
branch: main
cronline: "@daily"
message: Builds daily `main` stack-installers dra
teams:
everyone:
access_level: BUILD_AND_READ
Expand Down Expand Up @@ -93,15 +88,8 @@ spec:
publish_commit_status_per_step: false
repository: elastic/elastic-stack-installers
schedules:
Daily 8_12:
branch: "8.12"
cronline: "*/10 * * * *"
message: Checking for new beats artifacts for `8.12`
Daily 8_11:
branch: "8.11"
cronline: "*/10 * * * *"
message: Checking for new beats artifacts for `8.11`
Weekly main:
# trigger for 7_17 still needed for now, details in https://elasticco.atlassian.net/browse/REL-1004?focusedCommentId=107973
Daily 7_17:
branch: "7.17"
cronline: "*/10 * * * *"
message: Checking for new beats artifacts for `7.17`
Expand Down
11 changes: 11 additions & 0 deletions src/CustomAction.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0.30319"/>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
<supportedRuntime version="v2.0.50727"/>
<supportedRuntime version="v2.0.50215"/>
<supportedRuntime version="v1.1.4322"/>
</startup>
</configuration>
Loading

0 comments on commit 887274a

Please sign in to comment.