Using go entrypoint for Windows agents
This commit is contained in:
parent
9e7e5e633f
commit
0866f929e2
3
.github/workflows/images_build.yml
vendored
3
.github/workflows/images_build.yml
vendored
|
|
@ -16,7 +16,8 @@ on:
|
|||
- '!Dockerfiles/*/windows/*'
|
||||
- '.github/workflows/images_build.yml'
|
||||
- '.github/actions/*'
|
||||
- 'templates/entrypoints/**'
|
||||
- 'templates/entrypoints/*.sh'
|
||||
- 'templates/entrypoints/lib/**'
|
||||
schedule:
|
||||
- cron: '50 02 * * *'
|
||||
workflow_dispatch:
|
||||
|
|
|
|||
4
.github/workflows/images_build_rhel.yml
vendored
4
.github/workflows/images_build_rhel.yml
vendored
|
|
@ -13,6 +13,10 @@ on:
|
|||
- '!**/README.md'
|
||||
- '!**/README.html'
|
||||
- '.github/workflows/images_build_rhel.yml'
|
||||
- 'templates/entrypoints/*.sh'
|
||||
- 'templates/entrypoints/lib/**'
|
||||
- '!templates/entrypoints/web-apache-*.sh'
|
||||
- '!templates/entrypoints/lib/apache.sh'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish_images:
|
||||
|
|
|
|||
10
.github/workflows/images_build_windows.yml
vendored
10
.github/workflows/images_build_windows.yml
vendored
|
|
@ -11,6 +11,10 @@ on:
|
|||
paths:
|
||||
- 'build.json'
|
||||
- 'Dockerfiles/*/windows/*'
|
||||
- 'templates/config/*_windows/**'
|
||||
- 'templates/entrypoints/**/*.go'
|
||||
- 'templates/entrypoints/go.mod'
|
||||
- 'templates/entrypoints/licenses/go-THIRD-PARTY-NOTICES.txt'
|
||||
- '!**/README.md'
|
||||
- '.github/workflows/images_build_windows.yml'
|
||||
- '.github/actions/docker-build-push-windows/*'
|
||||
|
|
@ -40,6 +44,7 @@ env:
|
|||
|
||||
MSFT_BASE_BUILD_IMAGE: "mcr.microsoft.com/windows/servercore"
|
||||
OS_BASE_IMAGE_NAME: "mcr.microsoft.com/windows/nanoserver"
|
||||
ENTRYPOINT_BUILD_IMAGE_NAME: "golang:1.26.5-windowsservercore"
|
||||
|
||||
BASE_IMAGE_NAME: "build-base"
|
||||
BASE_BUILD_IMAGE_NAME: "build-mysql"
|
||||
|
|
@ -698,7 +703,7 @@ jobs:
|
|||
id: docker_build
|
||||
uses: ./.github/actions/docker-build-push-windows
|
||||
with:
|
||||
context: ${{ env.DOCKERFILES_DIRECTORY }}\${{ matrix.component }}\windows
|
||||
context: .
|
||||
dockerfile: ${{ env.DOCKERFILES_DIRECTORY }}\${{ matrix.component }}\windows\Dockerfile
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
label_revision: ${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
|
||||
|
|
@ -706,10 +711,13 @@ jobs:
|
|||
pull_images: |
|
||||
${{ steps.base_build.outputs.base_build_image }}
|
||||
${{ env.OS_BASE_IMAGE_NAME }}:${{ steps.base_os_tag.outputs.os_tag }}
|
||||
${{ env.ENTRYPOINT_BUILD_IMAGE_NAME }}-${{ steps.base_os_tag.outputs.os_tag }}
|
||||
build_args: |
|
||||
BUILD_BASE_IMAGE=${{ steps.base_build.outputs.base_build_image }}
|
||||
OS_BASE_IMAGE=${{ env.OS_BASE_IMAGE_NAME }}:${{ steps.base_os_tag.outputs.os_tag }}
|
||||
ENTRYPOINT_BUILD_IMAGE=${{ env.ENTRYPOINT_BUILD_IMAGE_NAME }}-${{ steps.base_os_tag.outputs.os_tag }}
|
||||
push: ${{ env.AUTO_PUSH_IMAGES }}
|
||||
remove_readme: false
|
||||
|
||||
- name: Sign the images with GitHub OIDC Token
|
||||
if: ${{ env.AUTO_PUSH_IMAGES == 'true' }}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,26 @@
|
|||
# syntax=docker/dockerfile:1.6
|
||||
# escape=`
|
||||
ARG OS_BASE_IMAGE=mcr.microsoft.com/windows/nanoserver:ltsc2022
|
||||
ARG ENTRYPOINT_BUILD_IMAGE=golang:1.26.5-windowsservercore-ltsc2022
|
||||
|
||||
ARG MAJOR_VERSION=7.4
|
||||
ARG ZBX_VERSION=${MAJOR_VERSION}.12
|
||||
|
||||
ARG BUILD_BASE_IMAGE=zabbix-build-agent:ltsc2022-agent-${ZBX_VERSION}
|
||||
|
||||
FROM ${ENTRYPOINT_BUILD_IMAGE} AS entrypoint-builder
|
||||
|
||||
COPY ["templates/entrypoints", "C:\\entrypoints"]
|
||||
|
||||
WORKDIR C:\entrypoints
|
||||
|
||||
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||
|
||||
RUN $env:CGO_ENABLED = '0'; `
|
||||
go build -buildvcs=false -trimpath -ldflags='-s -w -buildid=' `
|
||||
-o C:\docker-entrypoint.exe `
|
||||
./cmd/agent;
|
||||
|
||||
FROM ${BUILD_BASE_IMAGE} AS builder
|
||||
FROM ${OS_BASE_IMAGE}
|
||||
|
||||
|
|
@ -30,8 +44,10 @@ LABEL org.opencontainers.image.title="Zabbix agent" `
|
|||
org.opencontainers.image.source="${ZBX_SOURCES}"
|
||||
|
||||
COPY --from=builder ["C:\\zabbix-${ZBX_VERSION}-output", "C:\\zabbix"]
|
||||
COPY ["templates/config/agent_windows", "C:\\zabbix\\conf"]
|
||||
COPY --from=builder ["C:\\build_deps\\pwsh", "C:\\Program Files\\PowerShell"]
|
||||
COPY [".\\docker-entrypoint.ps1", "C:\\zabbix"]
|
||||
COPY --from=entrypoint-builder ["C:\\docker-entrypoint.exe", "C:\\zabbix\\docker-entrypoint.exe"]
|
||||
COPY ["templates/entrypoints/licenses/go-THIRD-PARTY-NOTICES.txt", "C:\\licenses\\"]
|
||||
|
||||
WORKDIR C:\zabbix\
|
||||
|
||||
|
|
@ -66,6 +82,6 @@ USER zabbix
|
|||
|
||||
EXPOSE 10050/tcp
|
||||
|
||||
ENTRYPOINT ["pwsh", "-File", "C:\\zabbix\\docker-entrypoint.ps1"]
|
||||
ENTRYPOINT ["C:\\zabbix\\docker-entrypoint.exe"]
|
||||
|
||||
CMD ["C:\\zabbix\\sbin\\zabbix_agentd.exe", "-c", "C:\\zabbix\\conf\\zabbix_agentd.conf", "-f"]
|
||||
|
|
|
|||
19
Dockerfiles/agent/windows/Dockerfile.dockerignore
Normal file
19
Dockerfiles/agent/windows/Dockerfile.dockerignore
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
**
|
||||
!templates/
|
||||
!templates/entrypoints/
|
||||
!templates/entrypoints/go.mod
|
||||
!templates/entrypoints/cmd/
|
||||
!templates/entrypoints/cmd/agent/
|
||||
!templates/entrypoints/cmd/agent/**
|
||||
!templates/entrypoints/internal/
|
||||
!templates/entrypoints/internal/agent/
|
||||
!templates/entrypoints/internal/agent/**
|
||||
!templates/entrypoints/internal/bootstrap/
|
||||
!templates/entrypoints/internal/bootstrap/**
|
||||
!templates/entrypoints/internal/hooks/
|
||||
!templates/entrypoints/internal/hooks/**
|
||||
!templates/entrypoints/licenses/
|
||||
!templates/entrypoints/licenses/go-THIRD-PARTY-NOTICES.txt
|
||||
!templates/config/
|
||||
!templates/config/agent_windows/
|
||||
!templates/config/agent_windows/**
|
||||
|
|
@ -1,233 +0,0 @@
|
|||
|
||||
# Script trace mode
|
||||
if ($env:DEBUG_MODE -eq "true") {
|
||||
Set-PSDebug -trace 1
|
||||
}
|
||||
|
||||
# Default Zabbix server host
|
||||
if ([string]::IsNullOrEmpty($env:ZBX_SERVER_HOST)) {
|
||||
$env:ZBX_SERVER_HOST="zabbix-server"
|
||||
}
|
||||
# Default Zabbix server port number
|
||||
if ([string]::IsNullOrEmpty($env:ZBX_SERVER_PORT)) {
|
||||
$env:ZBX_SERVER_PORT="10051"
|
||||
}
|
||||
|
||||
# Default directories
|
||||
# Internal directory for TLS related files, used when TLS*File specified as plain text values
|
||||
$ZabbixInternalEncDir="$env:ZABBIX_USER_HOME_DIR/enc_internal"
|
||||
|
||||
function Update-Config-Var {
|
||||
Param (
|
||||
[Parameter(Mandatory=$true, Position=0)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $ConfigPath,
|
||||
[Parameter(Mandatory=$true, Position=1)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$VarName,
|
||||
[Parameter(Mandatory=$false, Position=2)]
|
||||
[string]$VarValue = $null,
|
||||
[Parameter(Mandatory=$false, Position=3)]
|
||||
[bool]$IsMultiple
|
||||
)
|
||||
|
||||
$MaskList = "TLSPSKIdentity"
|
||||
|
||||
if (-not(Test-Path -Path $ConfigPath -PathType Leaf)) {
|
||||
throw "**** Configuration file '$ConfigPath' does not exist"
|
||||
}
|
||||
|
||||
if ($MaskList.Contains($VarName) -eq $true -And [string]::IsNullOrWhitespace($VarValue) -ne $true) {
|
||||
Write-Host -NoNewline "** Updating '$ConfigPath' parameter ""$VarName"": '****'. Enable DEBUG_MODE to view value ..."
|
||||
}
|
||||
else {
|
||||
Write-Host -NoNewline "** Updating '$ConfigPath' parameter ""$VarName"": '$VarValue'..."
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhitespace($VarValue)) {
|
||||
if ((Get-Content $ConfigPath | %{$_ -match "^$VarName="}) -contains $true) {
|
||||
(Get-Content $ConfigPath) |
|
||||
Where-Object {$_ -notmatch "^$VarName=" } |
|
||||
Set-Content $ConfigPath
|
||||
}
|
||||
|
||||
Write-Host "removed"
|
||||
return
|
||||
}
|
||||
|
||||
if ($VarValue -eq '""') {
|
||||
(Get-Content $ConfigPath) | Foreach-Object { $_ -Replace "^($VarName=)(.*)", '$1' } | Set-Content $ConfigPath
|
||||
Write-Host "undefined"
|
||||
return
|
||||
}
|
||||
|
||||
if ($VarName -match '^TLS.*File$') {
|
||||
$VarValue="$env:ZABBIX_USER_HOME_DIR\enc\$VarValue"
|
||||
}
|
||||
|
||||
if ((Get-Content $ConfigPath | %{$_ -match "^$VarName="}) -contains $true -And $IsMultiple -ne $true) {
|
||||
(Get-Content $ConfigPath) | Foreach-Object { $_ -Replace "^$VarName=.+", "$VarName=$VarValue" } | Set-Content $ConfigPath
|
||||
|
||||
Write-Host updated
|
||||
}
|
||||
elseif ((Get-Content $ConfigPath | select-string -pattern "^[#;] $VarName=").length -gt 1) {
|
||||
(Get-Content $ConfigPath) |
|
||||
Foreach-Object {
|
||||
$_
|
||||
if ($_ -match "^[#;] $VarName=$") {
|
||||
"$VarName=$VarValue"
|
||||
}
|
||||
} | Set-Content $ConfigPath
|
||||
|
||||
Write-Host "added first occurrence"
|
||||
}
|
||||
elseif ((Get-Content $ConfigPath | select-string -pattern "^[#;] $VarName=").length -gt 0) {
|
||||
(Get-Content $ConfigPath) |
|
||||
Foreach-Object {
|
||||
$_
|
||||
if ($_ -match "^[#;] $VarName=") {
|
||||
"$VarName=$VarValue"
|
||||
}
|
||||
} | Set-Content $ConfigPath
|
||||
|
||||
Write-Host "added"
|
||||
}
|
||||
else {
|
||||
Add-Content -Path $ConfigPath -Value "$VarName=$VarValue"
|
||||
Write-Host "added at the end"
|
||||
}
|
||||
}
|
||||
|
||||
function Update-Config-Multiple-Var {
|
||||
Param (
|
||||
[Parameter(Mandatory=$true, Position=0)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $ConfigPath,
|
||||
[Parameter(Mandatory=$true, Position=1)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$VarName,
|
||||
[Parameter(Mandatory=$false, Position=2)]
|
||||
[string]$VarValue = $null
|
||||
)
|
||||
|
||||
foreach ($value in $VarValue.split(',')) {
|
||||
Update-Config-Var $ConfigPath $VarName $value $true
|
||||
}
|
||||
}
|
||||
|
||||
function File-Process-From-Env {
|
||||
Param (
|
||||
[Parameter(Mandatory=$true, Position=0)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $VarName,
|
||||
[Parameter(Mandatory=$false, Position=1)]
|
||||
[string]$FileName = $null,
|
||||
[Parameter(Mandatory=$false, Position=2)]
|
||||
[string]$VarValue = $null
|
||||
)
|
||||
|
||||
if (![string]::IsNullOrEmpty($VarValue)) {
|
||||
$VarValue | Set-Content "$ZabbixInternalEncDir\$VarName"
|
||||
$FileName="$ZabbixInternalEncDir\$VarName"
|
||||
}
|
||||
|
||||
if (![string]::IsNullOrEmpty($FileName)) {
|
||||
Set-Item env:$VarName -Value $FileName
|
||||
}
|
||||
|
||||
$VarName=$VarName -replace 'FILE$'
|
||||
Set-Item env:$VarName -Value $null
|
||||
}
|
||||
|
||||
function Prepare-Zbx-Agent-Config {
|
||||
if ([string]::IsNullOrEmpty($env:ZBX_PASSIVESERVERS)) {
|
||||
$env:ZBX_PASSIVESERVERS=""
|
||||
}
|
||||
if ([string]::IsNullOrEmpty($env:ZBX_ACTIVESERVERS)) {
|
||||
$env:ZBX_ACTIVESERVERS=""
|
||||
}
|
||||
|
||||
if (![string]::IsNullOrEmpty($env:ZBX_SERVER_HOST) -And ![string]::IsNullOrEmpty($env:ZBX_PASSIVESERVERS)) {
|
||||
$env:ZBX_PASSIVESERVERS="$env:ZBX_SERVER_HOST,$env:ZBX_PASSIVESERVERS"
|
||||
}
|
||||
elseif (![string]::IsNullOrEmpty($env:ZBX_SERVER_HOST)) {
|
||||
$env:ZBX_PASSIVESERVERS=$env:ZBX_SERVER_HOST
|
||||
}
|
||||
|
||||
if (![string]::IsNullOrEmpty($env:ZBX_SERVER_HOST)) {
|
||||
if (![string]::IsNullOrEmpty($env:ZBX_SERVER_PORT) -And $env:ZBX_SERVER_PORT -ne "10051") {
|
||||
$env:ZBX_SERVER_HOST="$env:ZBX_SERVER_HOST:$env:ZBX_SERVER_PORT"
|
||||
}
|
||||
if (![string]::IsNullOrEmpty($env:ZBX_ACTIVESERVERS)) {
|
||||
$env:ZBX_ACTIVESERVERS="$env:ZBX_SERVER_HOST,$env:ZBX_ACTIVESERVERS"
|
||||
}
|
||||
else {
|
||||
$env:ZBX_ACTIVESERVERS=$env:ZBX_SERVER_HOST
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhitespace($env:ZBX_PASSIVE_ALLOW)) {
|
||||
$env:ZBX_PASSIVE_ALLOW="true"
|
||||
}
|
||||
|
||||
if ($env:ZBX_PASSIVE_ALLOW -eq "true") {
|
||||
Write-Host "** Using '$env:ZBX_PASSIVESERVERS' servers for passive checks"
|
||||
}
|
||||
else {
|
||||
Set-Item env:ZBX_PASSIVESERVERS -Value $null
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhitespace($env:ZBX_ACTIVE_ALLOW)) {
|
||||
$env:ZBX_ACTIVE_ALLOW="true"
|
||||
}
|
||||
|
||||
if ($env:ZBX_ACTIVE_ALLOW -eq "true") {
|
||||
Write-Host "** Using '$env:ZBX_ACTIVESERVERS' servers for active checks"
|
||||
}
|
||||
else {
|
||||
Set-Item env:ZBX_ACTIVESERVERS -Value $null
|
||||
}
|
||||
|
||||
Update-Config-Multiple-Var "$env:ZABBIX_CONF_DIR\zabbix_agentd_item_keys.conf" "DenyKey" "$env:ZBX_DENYKEY"
|
||||
Update-Config-Multiple-Var "$env:ZABBIX_CONF_DIR\zabbix_agentd_item_keys.conf" "AllowKey" "$env:ZBX_ALLOWKEY"
|
||||
|
||||
File-Process-From-Env "ZBX_TLSCAFILE" "$env:ZBX_TLSCAFILE" "$env:ZBX_TLSCA"
|
||||
File-Process-From-Env "ZBX_TLSCRLFILE" "$env:ZBX_TLSCRLFILE" "$env:ZBX_TLSCRL"
|
||||
File-Process-From-Env "ZBX_TLSCERTFILE" "$env:ZBX_TLSCERTFILE" "$env:ZBX_TLSCERT"
|
||||
File-Process-From-Env "ZBX_TLSKEYFILE" "$env:ZBX_TLSKEYFILE" "$env:ZBX_TLSKEY"
|
||||
File-Process-From-Env "ZBX_TLSPSKFILE" "$env:ZBX_TLSPSKFILE" "$env:ZBX_TLSPSK"
|
||||
}
|
||||
|
||||
function ClearZbxEnv() {
|
||||
if ($env:ZBX_CLEAR_ENV -eq "false") {
|
||||
return
|
||||
}
|
||||
|
||||
$env_vars=Get-ChildItem env:* | Where-Object {$_.Name -match "^ZABBIX_.*" } | foreach { $_.Name }
|
||||
foreach ($env_var in $env_vars) {
|
||||
Set-Item env:$env_var -Value $null
|
||||
}
|
||||
}
|
||||
|
||||
function PrepareAgent {
|
||||
Write-Host "** Preparing Zabbix agent"
|
||||
|
||||
Prepare-Zbx-Agent-Config
|
||||
ClearZbxEnv
|
||||
}
|
||||
|
||||
$commandArgs = $args
|
||||
|
||||
if ($args.length -gt 0 -And $args[0].StartsWith('-')) {
|
||||
$commandArgs = @("C:\zabbix\sbin\zabbix_agentd.exe") + $args
|
||||
}
|
||||
|
||||
if ($commandArgs.length -gt 0 -And $commandArgs[0] -eq "C:\zabbix\sbin\zabbix_agentd.exe") {
|
||||
PrepareAgent
|
||||
}
|
||||
|
||||
if ($commandArgs.length -gt 0) {
|
||||
$exe, $exeArgs = $commandArgs
|
||||
& $exe @exeArgs
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
|
@ -1,12 +1,26 @@
|
|||
# syntax=docker/dockerfile:1.6
|
||||
# escape=`
|
||||
ARG OS_BASE_IMAGE=mcr.microsoft.com/windows/nanoserver:ltsc2022
|
||||
ARG ENTRYPOINT_BUILD_IMAGE=golang:1.26.5-windowsservercore-ltsc2022
|
||||
|
||||
ARG MAJOR_VERSION=7.4
|
||||
ARG ZBX_VERSION=${MAJOR_VERSION}.12
|
||||
|
||||
ARG BUILD_BASE_IMAGE=zabbix-build-agent:ltsc2022-agent2-${ZBX_VERSION}
|
||||
|
||||
FROM ${ENTRYPOINT_BUILD_IMAGE} AS entrypoint-builder
|
||||
|
||||
COPY ["templates/entrypoints", "C:\\entrypoints"]
|
||||
|
||||
WORKDIR C:\entrypoints
|
||||
|
||||
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||
|
||||
RUN $env:CGO_ENABLED = '0'; `
|
||||
go build -buildvcs=false -trimpath -ldflags='-s -w -buildid=' `
|
||||
-o C:\docker-entrypoint.exe `
|
||||
./cmd/agent2;
|
||||
|
||||
FROM ${BUILD_BASE_IMAGE} AS builder
|
||||
FROM ${OS_BASE_IMAGE}
|
||||
|
||||
|
|
@ -31,8 +45,10 @@ LABEL org.opencontainers.image.title="Zabbix agent 2" `
|
|||
org.opencontainers.image.source="${ZBX_SOURCES}"
|
||||
|
||||
COPY --from=builder ["C:\\zabbix-${ZBX_VERSION}-output", "C:\\zabbix"]
|
||||
COPY ["templates/config/agent2_windows", "C:\\zabbix\\conf"]
|
||||
COPY --from=builder ["C:\\build_deps\\pwsh", "C:\\Program Files\\PowerShell"]
|
||||
COPY [".\\docker-entrypoint.ps1", "C:\\zabbix"]
|
||||
COPY --from=entrypoint-builder ["C:\\docker-entrypoint.exe", "C:\\zabbix\\docker-entrypoint.exe"]
|
||||
COPY ["templates/entrypoints/licenses/go-THIRD-PARTY-NOTICES.txt", "C:\\licenses\\"]
|
||||
|
||||
WORKDIR C:\zabbix\
|
||||
|
||||
|
|
@ -69,6 +85,6 @@ USER zabbix
|
|||
|
||||
EXPOSE 10050/tcp 31999/tcp
|
||||
|
||||
ENTRYPOINT ["pwsh", "-File", "C:\\zabbix\\docker-entrypoint.ps1"]
|
||||
ENTRYPOINT ["C:\\zabbix\\docker-entrypoint.exe"]
|
||||
|
||||
CMD ["C:\\zabbix\\sbin\\zabbix_agent2.exe", "-c", "C:\\zabbix\\conf\\zabbix_agent2.conf", "-f"]
|
||||
|
|
|
|||
19
Dockerfiles/agent2/windows/Dockerfile.dockerignore
Normal file
19
Dockerfiles/agent2/windows/Dockerfile.dockerignore
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
**
|
||||
!templates/
|
||||
!templates/entrypoints/
|
||||
!templates/entrypoints/go.mod
|
||||
!templates/entrypoints/cmd/
|
||||
!templates/entrypoints/cmd/agent2/
|
||||
!templates/entrypoints/cmd/agent2/**
|
||||
!templates/entrypoints/internal/
|
||||
!templates/entrypoints/internal/agent/
|
||||
!templates/entrypoints/internal/agent/**
|
||||
!templates/entrypoints/internal/bootstrap/
|
||||
!templates/entrypoints/internal/bootstrap/**
|
||||
!templates/entrypoints/internal/hooks/
|
||||
!templates/entrypoints/internal/hooks/**
|
||||
!templates/entrypoints/licenses/
|
||||
!templates/entrypoints/licenses/go-THIRD-PARTY-NOTICES.txt
|
||||
!templates/config/
|
||||
!templates/config/agent2_windows/
|
||||
!templates/config/agent2_windows/**
|
||||
|
|
@ -1,263 +0,0 @@
|
|||
|
||||
# Script trace mode
|
||||
if ($env:DEBUG_MODE -eq "true") {
|
||||
Set-PSDebug -trace 1
|
||||
}
|
||||
|
||||
# Default Zabbix server host
|
||||
if ([string]::IsNullOrEmpty($env:ZBX_SERVER_HOST)) {
|
||||
$env:ZBX_SERVER_HOST="zabbix-server"
|
||||
}
|
||||
# Default Zabbix server port number
|
||||
if ([string]::IsNullOrEmpty($env:ZBX_SERVER_PORT)) {
|
||||
$env:ZBX_SERVER_PORT="10051"
|
||||
}
|
||||
|
||||
# Default directories
|
||||
# Internal directory for TLS related files, used when TLS*File specified as plain text values
|
||||
$ZabbixInternalEncDir="$env:ZABBIX_USER_HOME_DIR/enc_internal"
|
||||
|
||||
function Update-Config-Var {
|
||||
Param (
|
||||
[Parameter(Mandatory=$true, Position=0)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $ConfigPath,
|
||||
[Parameter(Mandatory=$true, Position=1)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$VarName,
|
||||
[Parameter(Mandatory=$false, Position=2)]
|
||||
[string]$VarValue = $null,
|
||||
[Parameter(Mandatory=$false, Position=3)]
|
||||
[bool]$IsMultiple
|
||||
)
|
||||
|
||||
$MaskList = "TLSPSKIdentity"
|
||||
|
||||
if (-not(Test-Path -Path $ConfigPath -PathType Leaf)) {
|
||||
throw "**** Configuration file '$ConfigPath' does not exist"
|
||||
}
|
||||
|
||||
if ($MaskList.Contains($VarName) -eq $true -And [string]::IsNullOrWhitespace($VarValue) -ne $true) {
|
||||
Write-Host -NoNewline "** Updating '$ConfigPath' parameter ""$VarName"": '****'. Enable DEBUG_MODE to view value ..."
|
||||
}
|
||||
else {
|
||||
Write-Host -NoNewline "** Updating '$ConfigPath' parameter ""$VarName"": '$VarValue'..."
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhitespace($VarValue)) {
|
||||
if ((Get-Content $ConfigPath | %{$_ -match "^$VarName="}) -contains $true) {
|
||||
(Get-Content $ConfigPath) |
|
||||
Where-Object {$_ -notmatch "^$VarName=" } |
|
||||
Set-Content $ConfigPath
|
||||
}
|
||||
|
||||
Write-Host "removed"
|
||||
return
|
||||
}
|
||||
|
||||
if ($VarValue -eq '""') {
|
||||
(Get-Content $ConfigPath) | Foreach-Object { $_ -Replace "^($VarName=)(.*)", '$1' } | Set-Content $ConfigPath
|
||||
Write-Host "undefined"
|
||||
return
|
||||
}
|
||||
|
||||
if ($VarName -match '^TLS.*File$') {
|
||||
$VarValue="$env:ZABBIX_USER_HOME_DIR\enc\$VarValue"
|
||||
}
|
||||
|
||||
if ((Get-Content $ConfigPath | %{$_ -match "^$VarName="}) -contains $true -And $IsMultiple -ne $true) {
|
||||
(Get-Content $ConfigPath) | Foreach-Object { $_ -Replace "^$VarName=.+", "$VarName=$VarValue" } | Set-Content $ConfigPath
|
||||
|
||||
Write-Host updated
|
||||
}
|
||||
elseif ((Get-Content $ConfigPath | select-string -pattern "^[#;] $VarName=").length -gt 1) {
|
||||
(Get-Content $ConfigPath) |
|
||||
Foreach-Object {
|
||||
$_
|
||||
if ($_ -match "^[#;] $VarName=$") {
|
||||
"$VarName=$VarValue"
|
||||
}
|
||||
} | Set-Content $ConfigPath
|
||||
|
||||
Write-Host "added first occurrence"
|
||||
}
|
||||
elseif ((Get-Content $ConfigPath | select-string -pattern "^[#;] $VarName=").length -gt 0) {
|
||||
(Get-Content $ConfigPath) |
|
||||
Foreach-Object {
|
||||
$_
|
||||
if ($_ -match "^[#;] $VarName=") {
|
||||
"$VarName=$VarValue"
|
||||
}
|
||||
} | Set-Content $ConfigPath
|
||||
|
||||
Write-Host "added"
|
||||
}
|
||||
else {
|
||||
Add-Content -Path $ConfigPath -Value "$VarName=$VarValue"
|
||||
Write-Host "added at the end"
|
||||
}
|
||||
}
|
||||
|
||||
function Update-Config-Multiple-Var {
|
||||
Param (
|
||||
[Parameter(Mandatory=$true, Position=0)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $ConfigPath,
|
||||
[Parameter(Mandatory=$true, Position=1)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$VarName,
|
||||
[Parameter(Mandatory=$false, Position=2)]
|
||||
[string]$VarValue = $null
|
||||
)
|
||||
|
||||
foreach ($value in $VarValue.split(',')) {
|
||||
Update-Config-Var $ConfigPath $VarName $value $true
|
||||
}
|
||||
}
|
||||
|
||||
function File-Process-From-Env {
|
||||
Param (
|
||||
[Parameter(Mandatory=$true, Position=0)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $VarName,
|
||||
[Parameter(Mandatory=$false, Position=1)]
|
||||
[string]$FileName = $null,
|
||||
[Parameter(Mandatory=$false, Position=2)]
|
||||
[string]$VarValue = $null
|
||||
)
|
||||
|
||||
if (![string]::IsNullOrEmpty($VarValue)) {
|
||||
$VarValue | Set-Content "$ZabbixInternalEncDir\$VarName"
|
||||
$FileName="$ZabbixInternalEncDir\$VarName"
|
||||
}
|
||||
|
||||
if (![string]::IsNullOrEmpty($FileName)) {
|
||||
Set-Item env:$VarName -Value $FileName
|
||||
}
|
||||
|
||||
$VarName=$VarName -replace 'FILE$'
|
||||
Set-Item env:$VarName -Value $null
|
||||
}
|
||||
|
||||
function Prepare-Zbx-Agent-Config {
|
||||
if ([string]::IsNullOrEmpty($env:ZBX_PASSIVESERVERS)) {
|
||||
$env:ZBX_PASSIVESERVERS=""
|
||||
}
|
||||
if ([string]::IsNullOrEmpty($env:ZBX_ACTIVESERVERS)) {
|
||||
$env:ZBX_ACTIVESERVERS=""
|
||||
}
|
||||
|
||||
if (![string]::IsNullOrEmpty($env:ZBX_SERVER_HOST) -And ![string]::IsNullOrEmpty($env:ZBX_PASSIVESERVERS)) {
|
||||
$env:ZBX_PASSIVESERVERS="$env:ZBX_SERVER_HOST,$env:ZBX_PASSIVESERVERS"
|
||||
}
|
||||
elseif (![string]::IsNullOrEmpty($env:ZBX_SERVER_HOST)) {
|
||||
$env:ZBX_PASSIVESERVERS=$env:ZBX_SERVER_HOST
|
||||
}
|
||||
|
||||
if (![string]::IsNullOrEmpty($env:ZBX_SERVER_HOST)) {
|
||||
if (![string]::IsNullOrEmpty($env:ZBX_SERVER_PORT) -And $env:ZBX_SERVER_PORT -ne "10051") {
|
||||
$env:ZBX_SERVER_HOST="$env:ZBX_SERVER_HOST:$env:ZBX_SERVER_PORT"
|
||||
}
|
||||
if (![string]::IsNullOrEmpty($env:ZBX_ACTIVESERVERS)) {
|
||||
$env:ZBX_ACTIVESERVERS="$env:ZBX_SERVER_HOST,$env:ZBX_ACTIVESERVERS"
|
||||
}
|
||||
else {
|
||||
$env:ZBX_ACTIVESERVERS=$env:ZBX_SERVER_HOST
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhitespace($env:ZBX_PASSIVE_ALLOW)) {
|
||||
$env:ZBX_PASSIVE_ALLOW="true"
|
||||
}
|
||||
|
||||
if ($env:ZBX_PASSIVE_ALLOW -eq "true") {
|
||||
Write-Host "** Using '$env:ZBX_PASSIVESERVERS' servers for passive checks"
|
||||
}
|
||||
else {
|
||||
Set-Item env:ZBX_PASSIVESERVERS -Value $null
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhitespace($env:ZBX_ACTIVE_ALLOW)) {
|
||||
$env:ZBX_ACTIVE_ALLOW="true"
|
||||
}
|
||||
|
||||
if ($env:ZBX_ACTIVE_ALLOW -eq "true") {
|
||||
Write-Host "** Using '$env:ZBX_ACTIVESERVERS' servers for active checks"
|
||||
}
|
||||
else {
|
||||
Set-Item env:ZBX_ACTIVESERVERS -Value $null
|
||||
}
|
||||
Set-Item env:ZBX_SERVER_HOST -Value $null
|
||||
Set-Item env:ZBX_SERVER_PORT -Value $null
|
||||
|
||||
if ($env:ZBX_ENABLEPERSISTENTBUFFER -eq "true") {
|
||||
$env:ZBX_ENABLEPERSISTENTBUFFER="1"
|
||||
}
|
||||
else {
|
||||
Set-Item env:ZBX_ENABLEPERSISTENTBUFFER -Value $null
|
||||
Set-Item env:ZBX_PERSISTENTBUFFERFILE -Value $null
|
||||
}
|
||||
|
||||
if ($env:ZBX_ENABLESTATUSPORT -eq "true") {
|
||||
$env:ZBX_STATUSPORT="31999"
|
||||
}
|
||||
else {
|
||||
Set-Item env:ZBX_STATUSPORT -Value $null
|
||||
}
|
||||
|
||||
Update-Config-Multiple-Var "$env:ZABBIX_CONF_DIR\zabbix_agent2_item_keys.conf" "DenyKey" "$env:ZBX_DENYKEY"
|
||||
Update-Config-Multiple-Var "$env:ZABBIX_CONF_DIR\zabbix_agent2_item_keys.conf" "AllowKey" "$env:ZBX_ALLOWKEY"
|
||||
|
||||
File-Process-From-Env "ZBX_TLSCAFILE" "$env:ZBX_TLSCAFILE" "$env:ZBX_TLSCA"
|
||||
File-Process-From-Env "ZBX_TLSCRLFILE" "$env:ZBX_TLSCRLFILE" "$env:ZBX_TLSCRL"
|
||||
File-Process-From-Env "ZBX_TLSCERTFILE" "$env:ZBX_TLSCERTFILE" "$env:ZBX_TLSCERT"
|
||||
File-Process-From-Env "ZBX_TLSKEYFILE" "$env:ZBX_TLSKEYFILE" "$env:ZBX_TLSKEY"
|
||||
File-Process-From-Env "ZBX_TLSPSKFILE" "$env:ZBX_TLSPSKFILE" "$env:ZBX_TLSPSK"
|
||||
}
|
||||
|
||||
function Prepare-Zbx-Agent-Plugins-Config {
|
||||
Write-Host "** Preparing Zabbix agent 2 (plugins) configuration files"
|
||||
|
||||
Update-Config-Var "$env:ZABBIX_CONF_DIR\zabbix_agent2.d\plugins.d\mongodb.conf" "Plugins.MongoDB.System.Path" "$env:ZABBIX_USER_HOME_DIR\zabbix-agent2-plugin\mongodb.exe"
|
||||
Update-Config-Var "$env:ZABBIX_CONF_DIR\zabbix_agent2.d\plugins.d\postgresql.conf" "Plugins.PostgreSQL.System.Path" "$env:ZABBIX_USER_HOME_DIR\zabbix-agent2-plugin\postgresql.exe"
|
||||
Update-Config-Var "$env:ZABBIX_CONF_DIR\zabbix_agent2.d\plugins.d\mssql.conf" "Plugins.MSSQL.System.Path" "$env:ZABBIX_USER_HOME_DIR\zabbix-agent2-plugin\mssql.exe"
|
||||
Update-Config-Var "$env:ZABBIX_CONF_DIR\zabbix_agent2.d\plugins.d\ember.conf" "Plugins.EmberPlus.System.Path" "$env:ZABBIX_USER_HOME_DIR\zabbix-agent2-plugin\ember-plus.exe"
|
||||
if (Get-Command nvidia-smi.exe -errorAction SilentlyContinue) {
|
||||
Update-Config-Var "$env:ZABBIX_CONF_DIR\zabbix_agent2.d\plugins.d\nvidia.conf" "Plugins.NVIDIA.System.Path" "$env:ZABBIX_USER_HOME_DIR\zabbix-agent2-plugin\nvidia-gpu.exe"
|
||||
}
|
||||
}
|
||||
|
||||
function ClearZbxEnv() {
|
||||
if ($env:ZBX_CLEAR_ENV -eq "false") {
|
||||
return
|
||||
}
|
||||
|
||||
$env_vars=Get-ChildItem env:* | Where-Object {$_.Name -match "^ZABBIX_.*" } | foreach { $_.Name }
|
||||
foreach ($env_var in $env_vars) {
|
||||
Set-Item env:$env_var -Value $null
|
||||
}
|
||||
}
|
||||
|
||||
function PrepareAgent {
|
||||
Write-Host "** Preparing Zabbix agent 2"
|
||||
|
||||
Prepare-Zbx-Agent-Config
|
||||
Prepare-Zbx-Agent-Plugins-Config
|
||||
ClearZbxEnv
|
||||
}
|
||||
|
||||
$commandArgs = $args
|
||||
|
||||
if ($args.length -gt 0 -And $args[0].StartsWith('-')) {
|
||||
$commandArgs = @("C:\zabbix\sbin\zabbix_agent2.exe") + $args
|
||||
}
|
||||
|
||||
if ($commandArgs.length -gt 0 -And $commandArgs[0] -eq "C:\zabbix\sbin\zabbix_agent2.exe") {
|
||||
PrepareAgent
|
||||
}
|
||||
|
||||
if ($commandArgs.length -gt 0) {
|
||||
$exe, $exeArgs = $commandArgs
|
||||
& $exe @exeArgs
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
|
@ -27,8 +27,6 @@ LABEL org.opencontainers.image.title="Zabbix agent build (Windows)" `
|
|||
org.opencontainers.image.version="${ZBX_VERSION}" `
|
||||
org.opencontainers.image.source="${ZBX_SOURCES}"
|
||||
|
||||
ADD config_templates C:\config_templates
|
||||
|
||||
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||
|
||||
RUN Set-Location -Path $env:SystemDrive\.; `
|
||||
|
|
@ -76,7 +74,6 @@ RUN Set-Location -Path $env:SystemDrive\.; `
|
|||
Copy-Item -Path $env:ZBX_SOURCES_DIR\bin\win64\zabbix_get.exe $env:ZBX_OUTPUT_DIR\bin; `
|
||||
Copy-Item -Path $env:ZBX_SOURCES_DIR\bin\win64\zabbix_sender.exe $env:ZBX_OUTPUT_DIR\bin; `
|
||||
Copy-Item -Path $env:ZBX_SOURCES_DIR\conf\zabbix_agentd.win.conf $env:ZBX_OUTPUT_DIR\conf\zabbix_agentd.conf_template; `
|
||||
Copy-Item -Recurse -Path $env:SystemDrive\config_templates\agent\* $env:ZBX_OUTPUT_DIR\conf\; `
|
||||
nmake /S -f Makefile `
|
||||
PCRE2INCDIR=$env:SystemDrive\build_output\pcre2\include `
|
||||
PCRE2LIBDIR=$env:SystemDrive\build_output\pcre2\lib `
|
||||
|
|
|
|||
|
|
@ -39,8 +39,6 @@ LABEL org.opencontainers.image.title="Zabbix agent 2 build (Windows)" `
|
|||
org.opencontainers.image.version="${ZBX_VERSION}" `
|
||||
org.opencontainers.image.source="${ZBX_SOURCES}"
|
||||
|
||||
ADD config_templates C:\config_templates
|
||||
|
||||
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||
|
||||
RUN Set-Location -Path $env:SystemDrive\.; `
|
||||
|
|
@ -78,7 +76,6 @@ RUN Set-Location -Path $env:SystemDrive\.; `
|
|||
Copy-Item -Path $env:ZBX_SOURCES_DIR\bin\win64\zabbix_agent2.exe $env:ZBX_OUTPUT_DIR\sbin; `
|
||||
Copy-Item -Path $env:ZBX_SOURCES_DIR\src\go\conf\zabbix_agent2.win.conf $env:ZBX_OUTPUT_DIR\conf\zabbix_agent2.conf_template; `
|
||||
Copy-Item -Recurse -Path $env:ZBX_SOURCES_DIR\src\go\conf\zabbix_agent2.d $env:ZBX_OUTPUT_DIR\conf\zabbix_agent2.d; `
|
||||
Copy-Item -Recurse -Path $env:SystemDrive\config_templates\agent2\* $env:ZBX_OUTPUT_DIR\conf; `
|
||||
mingw32-make -s clean;
|
||||
|
||||
RUN function Build-Plugin { `
|
||||
|
|
|
|||
40
templates/entrypoints/cmd/agent/main.go
Normal file
40
templates/entrypoints/cmd/agent/main.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
config "github.com/zabbix/zabbix-docker/templates/entrypoints/internal/agent"
|
||||
"github.com/zabbix/zabbix-docker/templates/entrypoints/internal/bootstrap"
|
||||
"github.com/zabbix/zabbix-docker/templates/entrypoints/internal/hooks"
|
||||
)
|
||||
|
||||
func prepareService(env bootstrap.Environment) error {
|
||||
bootstrap.LogInfo("** Preparing Zabbix agent")
|
||||
|
||||
homeDir, configDir, err := bootstrap.RequiredDirectories(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.ConfigureServers(env)
|
||||
|
||||
if err := config.ConfigureAllowDenyKeys(env, configDir, "zabbix_agentd_item_keys.conf"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := config.ProcessTLSFiles(env, homeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := hooks.Run(env); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.ClearPrivateEnv(env)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
bootstrap.ExitOnError(bootstrap.RunService(agentBinary, prepareService))
|
||||
}
|
||||
3
templates/entrypoints/cmd/agent/platform_windows.go
Normal file
3
templates/entrypoints/cmd/agent/platform_windows.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
package main
|
||||
|
||||
const agentBinary = `C:\zabbix\sbin\zabbix_agentd.exe`
|
||||
41
templates/entrypoints/cmd/agent/platform_windows_test.go
Normal file
41
templates/entrypoints/cmd/agent/platform_windows_test.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/zabbix/zabbix-docker/templates/entrypoints/internal/bootstrap"
|
||||
)
|
||||
|
||||
func TestPrepareServiceWindows(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
configDir := filepath.Join(root, "conf")
|
||||
homeDir := filepath.Join(root, "home")
|
||||
if err := os.MkdirAll(filepath.Join(homeDir, "enc_internal"), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(configDir, 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(configDir, "zabbix_agentd_item_keys.conf"), []byte("# DenyKey=system.run[*]\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
env := bootstrap.Environment{
|
||||
"ZABBIX_CONF_DIR": configDir, "ZABBIX_USER_HOME_DIR": homeDir,
|
||||
"UNRELATED_VARIABLE": "value",
|
||||
}
|
||||
if err := prepareService(env); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if env["ZBX_PASSIVESERVERS"] != "zabbix-server" || env["ZBX_ACTIVESERVERS"] != "zabbix-server" {
|
||||
t.Fatalf("unexpected server configuration: %#v", env)
|
||||
}
|
||||
if env["UNRELATED_VARIABLE"] != "value" {
|
||||
t.Fatal("Windows entrypoint removed an unrelated variable")
|
||||
}
|
||||
if _, found := env["ZABBIX_CONF_DIR"]; found {
|
||||
t.Fatal("ZABBIX_CONF_DIR was not removed")
|
||||
}
|
||||
}
|
||||
96
templates/entrypoints/cmd/agent2/main.go
Normal file
96
templates/entrypoints/cmd/agent2/main.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
config "github.com/zabbix/zabbix-docker/templates/entrypoints/internal/agent"
|
||||
"github.com/zabbix/zabbix-docker/templates/entrypoints/internal/bootstrap"
|
||||
"github.com/zabbix/zabbix-docker/templates/entrypoints/internal/hooks"
|
||||
)
|
||||
|
||||
func prepareService(env bootstrap.Environment) error {
|
||||
bootstrap.LogInfo("** Preparing Zabbix agent 2")
|
||||
|
||||
homeDir, configDir, err := bootstrap.RequiredDirectories(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.ConfigureServers(env)
|
||||
configureFeatureSwitches(env)
|
||||
|
||||
if err := config.ConfigureAllowDenyKeys(env, configDir, "zabbix_agent2_item_keys.conf"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := config.ProcessTLSFiles(env, homeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := updatePluginConfig(homeDir, configDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := hooks.Run(env); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.ClearPrivateEnv(env)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureFeatureSwitches(env bootstrap.Environment) {
|
||||
if env["ZBX_ENABLEPERSISTENTBUFFER"] == "true" {
|
||||
env["ZBX_ENABLEPERSISTENTBUFFER"] = "1"
|
||||
} else {
|
||||
delete(env, "ZBX_ENABLEPERSISTENTBUFFER")
|
||||
delete(env, "ZBX_PERSISTENTBUFFERFILE")
|
||||
}
|
||||
|
||||
if env["ZBX_ENABLESTATUSPORT"] == "true" {
|
||||
env["ZBX_STATUSPORT"] = env.ValueOrDefaultNonEmpty("ZBX_STATUSPORT", "31999")
|
||||
} else {
|
||||
delete(env, "ZBX_STATUSPORT")
|
||||
}
|
||||
}
|
||||
|
||||
func updatePluginConfig(homeDir, configDir string) error {
|
||||
bootstrap.LogInfo("** Preparing Zabbix agent 2 plugin configuration files")
|
||||
|
||||
configDir = filepath.Join(configDir, "zabbix_agent2.d", "plugins.d")
|
||||
binDir := pluginBinDir(homeDir)
|
||||
|
||||
plugins := []struct {
|
||||
file, parameter, binary string
|
||||
}{
|
||||
{"mongodb.conf", "Plugins.MongoDB.System.Path", "mongodb"},
|
||||
{"postgresql.conf", "Plugins.PostgreSQL.System.Path", "postgresql"},
|
||||
{"mssql.conf", "Plugins.MSSQL.System.Path", "mssql"},
|
||||
{"ember.conf", "Plugins.EmberPlus.System.Path", "ember-plus"},
|
||||
}
|
||||
|
||||
if _, err := exec.LookPath(nvidiaCommand); err == nil {
|
||||
plugins = append(plugins, struct {
|
||||
file, parameter, binary string
|
||||
}{"nvidia.conf", "Plugins.NVIDIA.System.Path", "nvidia-gpu"})
|
||||
}
|
||||
|
||||
for _, plugin := range plugins {
|
||||
if err := bootstrap.UpdateConfigValue(
|
||||
filepath.Join(configDir, plugin.file),
|
||||
plugin.parameter,
|
||||
filepath.Join(binDir, plugin.binary+pluginExecSuffix),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
bootstrap.ExitOnError(bootstrap.RunService(agent2Binary, prepareService))
|
||||
}
|
||||
15
templates/entrypoints/cmd/agent2/platform_windows.go
Normal file
15
templates/entrypoints/cmd/agent2/platform_windows.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
agent2Binary = `C:\zabbix\sbin\zabbix_agent2.exe`
|
||||
nvidiaCommand = "nvidia-smi.exe"
|
||||
pluginExecSuffix = ".exe"
|
||||
)
|
||||
|
||||
func pluginBinDir(homeDir string) string {
|
||||
return filepath.Join(homeDir, "zabbix-agent2-plugin")
|
||||
}
|
||||
76
templates/entrypoints/cmd/agent2/platform_windows_test.go
Normal file
76
templates/entrypoints/cmd/agent2/platform_windows_test.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/zabbix/zabbix-docker/templates/entrypoints/internal/bootstrap"
|
||||
)
|
||||
|
||||
func TestPrepareServiceWindows(t *testing.T) {
|
||||
t.Setenv("PATH", t.TempDir())
|
||||
|
||||
root := t.TempDir()
|
||||
configDir := filepath.Join(root, "conf")
|
||||
homeDir := filepath.Join(root, "home")
|
||||
pluginDirectory := filepath.Join(configDir, "zabbix_agent2.d", "plugins.d")
|
||||
if err := os.MkdirAll(pluginDirectory, 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(homeDir, "enc_internal"), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(configDir, "zabbix_agent2_item_keys.conf"), []byte("# DenyKey=system.run[*]\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, name := range []string{"mongodb.conf", "postgresql.conf", "mssql.conf", "ember.conf"} {
|
||||
if err := os.WriteFile(filepath.Join(pluginDirectory, name), []byte("# plugin config\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
env := bootstrap.Environment{
|
||||
"ZABBIX_CONF_DIR": configDir, "ZABBIX_USER_HOME_DIR": homeDir,
|
||||
"ZBX_ENABLESTATUSPORT": "true", "ZBX_STATUSPORT": "12345",
|
||||
"UNRELATED_VARIABLE": "value",
|
||||
}
|
||||
if err := prepareService(env); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if env["ZBX_STATUSPORT"] != "12345" {
|
||||
t.Fatalf("unexpected Windows status port: %q", env["ZBX_STATUSPORT"])
|
||||
}
|
||||
if env["UNRELATED_VARIABLE"] != "value" {
|
||||
t.Fatal("Windows entrypoint removed an unrelated variable")
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join(pluginDirectory, "mongodb.conf"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := filepath.Join(homeDir, "zabbix-agent2-plugin", "mongodb.exe")
|
||||
if !strings.Contains(string(data), "Plugins.MongoDB.System.Path="+want) {
|
||||
t.Fatalf("MongoDB plugin path is missing from config: %s", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFeatureSwitchesRequireLowercaseTrue(t *testing.T) {
|
||||
env := bootstrap.Environment{
|
||||
"ZBX_ENABLEPERSISTENTBUFFER": "TRUE",
|
||||
"ZBX_PERSISTENTBUFFERFILE": `C:\zabbix\buffer\agent2.db`,
|
||||
"ZBX_ENABLESTATUSPORT": "false",
|
||||
"ZBX_STATUSPORT": "31999",
|
||||
}
|
||||
configureFeatureSwitches(env)
|
||||
|
||||
if _, found := env["ZBX_PERSISTENTBUFFERFILE"]; found {
|
||||
t.Fatal("persistent buffer file was retained")
|
||||
}
|
||||
if _, found := env["ZBX_ENABLEPERSISTENTBUFFER"]; found {
|
||||
t.Fatal("persistent buffer was enabled by a case-insensitive value")
|
||||
}
|
||||
if _, found := env["ZBX_STATUSPORT"]; found {
|
||||
t.Fatal("status port was retained")
|
||||
}
|
||||
}
|
||||
3
templates/entrypoints/go.mod
Normal file
3
templates/entrypoints/go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/zabbix/zabbix-docker/templates/entrypoints
|
||||
|
||||
go 1.25.0
|
||||
87
templates/entrypoints/internal/agent/config.go
Normal file
87
templates/entrypoints/internal/agent/config.go
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
//go:build windows
|
||||
|
||||
// Package agent prepares the runtime environment for Zabbix agent and
|
||||
// agent 2.
|
||||
package agent
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/zabbix/zabbix-docker/templates/entrypoints/internal/bootstrap"
|
||||
)
|
||||
|
||||
// ConfigureServers merges ZBX_SERVER_HOST and ZBX_SERVER_PORT into the
|
||||
// passive and active server lists, honouring the ZBX_PASSIVE_ALLOW and
|
||||
// ZBX_ACTIVE_ALLOW switches.
|
||||
func ConfigureServers(env bootstrap.Environment) {
|
||||
serverHost := env.ValueOrDefault("ZBX_SERVER_HOST", "zabbix-server")
|
||||
serverPort := env.ValueOrDefault("ZBX_SERVER_PORT", "10051")
|
||||
passiveServers := env["ZBX_PASSIVESERVERS"]
|
||||
activeServers := env["ZBX_ACTIVESERVERS"]
|
||||
|
||||
activeServer := serverHost
|
||||
if serverPort != "" && serverPort != "10051" {
|
||||
activeServer += ":" + serverPort
|
||||
}
|
||||
if serverHost != "" {
|
||||
passiveServers = prependServer(serverHost, passiveServers)
|
||||
activeServers = prependServer(activeServer, activeServers)
|
||||
}
|
||||
|
||||
if v := env["ZBX_PASSIVE_ALLOW"]; (v == "" || strings.EqualFold(v, "true")) && passiveServers != "" {
|
||||
bootstrap.LogInfo("** Using '%s' servers for passive checks", passiveServers)
|
||||
env["ZBX_PASSIVESERVERS"] = passiveServers
|
||||
} else {
|
||||
delete(env, "ZBX_PASSIVESERVERS")
|
||||
}
|
||||
|
||||
if v := env["ZBX_ACTIVE_ALLOW"]; (v == "" || strings.EqualFold(v, "true")) && activeServers != "" {
|
||||
bootstrap.LogInfo("** Using '%s' servers for active checks", activeServers)
|
||||
env["ZBX_ACTIVESERVERS"] = activeServers
|
||||
} else {
|
||||
delete(env, "ZBX_ACTIVESERVERS")
|
||||
}
|
||||
|
||||
delete(env, "ZBX_SERVER_HOST")
|
||||
delete(env, "ZBX_SERVER_PORT")
|
||||
}
|
||||
|
||||
// ConfigureAllowDenyKeys writes ZBX_DENYKEY and ZBX_ALLOWKEY into the item key
|
||||
// configuration file.
|
||||
func ConfigureAllowDenyKeys(env bootstrap.Environment, configDir, fileName string) error {
|
||||
path := filepath.Join(configDir, fileName)
|
||||
|
||||
if err := bootstrap.UpdateConfigMultiple(path, "DenyKey", env["ZBX_DENYKEY"]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bootstrap.UpdateConfigMultiple(path, "AllowKey", env["ZBX_ALLOWKEY"])
|
||||
}
|
||||
|
||||
// ProcessTLSFiles persists the agent TLS material from the
|
||||
// environment into files.
|
||||
func ProcessTLSFiles(env bootstrap.Environment, homeDir string) error {
|
||||
return bootstrap.ProcessTLSFiles(
|
||||
env,
|
||||
homeDir,
|
||||
"ZBX_TLSCA",
|
||||
"ZBX_TLSCRL",
|
||||
"ZBX_TLSCERT",
|
||||
"ZBX_TLSKEY",
|
||||
"ZBX_TLSPSK",
|
||||
)
|
||||
}
|
||||
|
||||
// ClearPrivateEnv drops internal ZABBIX_* variables before the
|
||||
// agent starts.
|
||||
func ClearPrivateEnv(env bootstrap.Environment) {
|
||||
bootstrap.ClearPrivateEnv(env, "ZABBIX_")
|
||||
}
|
||||
|
||||
func prependServer(server, servers string) string {
|
||||
if servers == "" {
|
||||
return server
|
||||
}
|
||||
return server + "," + servers
|
||||
}
|
||||
20
templates/entrypoints/internal/agent/config_test.go
Normal file
20
templates/entrypoints/internal/agent/config_test.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
//go:build windows
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/zabbix/zabbix-docker/templates/entrypoints/internal/bootstrap"
|
||||
)
|
||||
|
||||
func TestConfigureServers(t *testing.T) {
|
||||
env := bootstrap.Environment{
|
||||
"ZBX_SERVER_HOST": "server", "ZBX_SERVER_PORT": "10061",
|
||||
"ZBX_PASSIVESERVERS": "passive", "ZBX_ACTIVESERVERS": "active",
|
||||
}
|
||||
ConfigureServers(env)
|
||||
if env["ZBX_PASSIVESERVERS"] != "server,passive" || env["ZBX_ACTIVESERVERS"] != "server:10061,active" {
|
||||
t.Fatalf("unexpected servers: %#v", env)
|
||||
}
|
||||
}
|
||||
75
templates/entrypoints/internal/bootstrap/command.go
Normal file
75
templates/entrypoints/internal/bootstrap/command.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
//go:build windows
|
||||
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ExitCode maps err to a process exit status, preserving the status of a
|
||||
// finished child process.
|
||||
func ExitCode(err error) int {
|
||||
if err == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
var exitError *exec.ExitError
|
||||
if errors.As(err, &exitError) {
|
||||
return exitError.ExitCode()
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// ExitOnError logs err and terminates the process with a matching exit
|
||||
// status. A nil error is ignored.
|
||||
func ExitOnError(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
LogError("**** %v", err)
|
||||
os.Exit(ExitCode(err))
|
||||
}
|
||||
|
||||
// Command decides what the container should execute: no arguments start the
|
||||
// service binary, arguments beginning with a dash are treated as its flags,
|
||||
// anything else is a user-supplied command.
|
||||
func Command(args []string, binary string) []string {
|
||||
if len(args) == 0 {
|
||||
return []string{binary}
|
||||
}
|
||||
if strings.HasPrefix(args[0], "-") {
|
||||
return append([]string{binary}, args...)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// Execute hands control over to args with the given environment.
|
||||
func Execute(args []string, env Environment) error {
|
||||
if err := Exec(args, env); err != nil {
|
||||
return fmt.Errorf("execute %s: %w", args[0], err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunService implements the common entrypoint flow: when the container is
|
||||
// about to start the service binary (the image default), prepare is called
|
||||
// first; custom user commands are executed untouched.
|
||||
func RunService(binary string, prepare func(Environment) error) error {
|
||||
env := NewEnvironment(os.Environ())
|
||||
args := Command(os.Args[1:], binary)
|
||||
|
||||
if args[0] == binary {
|
||||
if err := prepare(env); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return Execute(args, env)
|
||||
}
|
||||
55
templates/entrypoints/internal/bootstrap/command_test.go
Normal file
55
templates/entrypoints/internal/bootstrap/command_test.go
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
//go:build windows
|
||||
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommand(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want []string
|
||||
}{
|
||||
{name: "default", want: []string{"component"}},
|
||||
{name: "component options", args: []string{"--version"}, want: []string{"component", "--version"}},
|
||||
{name: "custom command", args: []string{"shell", "argument"}, want: []string{"shell", "argument"}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if got := Command(test.args, "component"); !reflect.DeepEqual(got, test.want) {
|
||||
t.Fatalf("Command() = %#v, want %#v", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExitCode(t *testing.T) {
|
||||
if code := ExitCode(nil); code != 0 {
|
||||
t.Fatalf("ExitCode(nil) = %d, want 0", code)
|
||||
}
|
||||
if code := ExitCode(errors.New("failure")); code != 1 {
|
||||
t.Fatalf("ExitCode(regular error) = %d, want 1", code)
|
||||
}
|
||||
|
||||
command := exec.Command(os.Args[0], "-test.run=TestExitCodeHelperProcess")
|
||||
command.Env = append(os.Environ(), "ENTRYPOINT_EXIT_CODE_HELPER=1")
|
||||
err := command.Run()
|
||||
if code := ExitCode(err); code != 23 {
|
||||
t.Fatalf("ExitCode(child error) = %d, want 23: %v", code, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExitCodeHelperProcess(t *testing.T) {
|
||||
if os.Getenv("ENTRYPOINT_EXIT_CODE_HELPER") != "1" {
|
||||
return
|
||||
}
|
||||
|
||||
os.Exit(23)
|
||||
}
|
||||
124
templates/entrypoints/internal/bootstrap/config.go
Normal file
124
templates/entrypoints/internal/bootstrap/config.go
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
//go:build windows
|
||||
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UpdateConfigMultiple replaces the name option in the configuration file
|
||||
// with one line per comma-separated item of rawValue. An empty value removes
|
||||
// the option.
|
||||
func UpdateConfigMultiple(configPath, name, rawValue string) error {
|
||||
value := strings.Trim(strings.TrimSpace(rawValue), `"`)
|
||||
if value == "" {
|
||||
return rewriteConfig(configPath, name, nil, false)
|
||||
}
|
||||
|
||||
items := strings.Split(value, ",")
|
||||
values := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
if item != "" {
|
||||
values = append(values, item)
|
||||
}
|
||||
}
|
||||
return rewriteConfig(configPath, name, values, true)
|
||||
}
|
||||
|
||||
// UpdateConfigValue sets a single-value option in the configuration file.
|
||||
func UpdateConfigValue(configPath, name, value string) error {
|
||||
return rewriteConfig(configPath, name, []string{value}, false)
|
||||
}
|
||||
|
||||
func rewriteConfig(configPath, name string, values []string, preserveExisting bool) error {
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("missing configuration file %s: %w", configPath, err)
|
||||
}
|
||||
|
||||
lines := strings.Split(strings.TrimSuffix(string(data), "\n"), "\n")
|
||||
|
||||
activePrefix := name + "="
|
||||
commentPrefixes := []string{"# " + activePrefix, "; " + activePrefix}
|
||||
|
||||
output := make([]string, 0, len(lines)+len(values)+1)
|
||||
existing := make(map[string]struct{})
|
||||
insertAt := -1
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, activePrefix) {
|
||||
if preserveExisting {
|
||||
output = append(output, line)
|
||||
existing[strings.TrimPrefix(line, activePrefix)] = struct{}{}
|
||||
insertAt = len(output)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
output = append(output, line)
|
||||
|
||||
if insertAt == -1 && hasAnyPrefix(line, commentPrefixes) {
|
||||
insertAt = len(output)
|
||||
}
|
||||
}
|
||||
|
||||
newLines := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, found := existing[value]; !found {
|
||||
newLines = append(newLines, activePrefix+value)
|
||||
}
|
||||
}
|
||||
|
||||
if insertAt >= 0 {
|
||||
updated := make([]string, 0, len(output)+len(newLines))
|
||||
updated = append(updated, output[:insertAt]...)
|
||||
updated = append(updated, newLines...)
|
||||
updated = append(updated, output[insertAt:]...)
|
||||
|
||||
output = updated
|
||||
} else if len(newLines) > 0 {
|
||||
if len(output) > 0 && output[len(output)-1] != "" {
|
||||
output = append(output, "")
|
||||
}
|
||||
|
||||
output = append(output, newLines...)
|
||||
}
|
||||
|
||||
requested := false
|
||||
for _, value := range values {
|
||||
if value != "" {
|
||||
requested = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
updatedData := []byte(strings.Join(output, "\n") + "\n")
|
||||
|
||||
changed := !bytes.Equal(data, updatedData)
|
||||
if changed {
|
||||
if err := WriteFilePreservingMode(configPath, updatedData); err != nil {
|
||||
return fmt.Errorf("update configuration file %s: %w", configPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !requested {
|
||||
if changed {
|
||||
LogInfo("** Removing %s parameter '%s'", configPath, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
loggedValue := strings.Join(values, ",")
|
||||
if changed {
|
||||
LogInfo("** Updating %s parameter '%s': '%s'", configPath, name, loggedValue)
|
||||
} else {
|
||||
LogInfo("** Updating %s parameter '%s': '%s'... exists", configPath, name, loggedValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
101
templates/entrypoints/internal/bootstrap/config_test.go
Normal file
101
templates/entrypoints/internal/bootstrap/config_test.go
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
//go:build windows
|
||||
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestUpdateConfigMultiple(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "agent.conf")
|
||||
if err := os.WriteFile(path, []byte("# DenyKey=system.run[*]\nOther=value\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := UpdateConfigMultiple(path, "DenyKey", `"one,two"`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := "# DenyKey=system.run[*]\nDenyKey=one\nDenyKey=two\nOther=value\n"
|
||||
if string(data) != want {
|
||||
t.Fatalf("config:\n%s\nwant:\n%s", data, want)
|
||||
}
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if info.Mode().Perm() != 0o600 {
|
||||
t.Fatalf("config mode = %o, want 600", info.Mode().Perm())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateConfigMultiplePreservesActiveValues(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "agent.conf")
|
||||
if err := os.WriteFile(path, []byte("DenyKey=existing\n# DenyKey=system.run[*]\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := UpdateConfigMultiple(path, "DenyKey", "existing,new"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
content := string(data)
|
||||
if strings.Count(content, "DenyKey=existing\n") != 1 {
|
||||
t.Fatalf("existing value was removed or duplicated:\n%s", data)
|
||||
}
|
||||
if !strings.Contains(content, "DenyKey=new\n") {
|
||||
t.Fatalf("new value was not added:\n%s", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateConfigMultipleRemovesActiveValuesWhenEmpty(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "agent.conf")
|
||||
if err := os.WriteFile(path, []byte("DenyKey=system.run[*]\nOther=value\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := UpdateConfigMultiple(path, "DenyKey", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(string(data), "DenyKey=") {
|
||||
t.Fatalf("active DenyKey was not removed:\n%s", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateConfigMultipleDoesNotRewriteUnchangedConfig(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "agent.conf")
|
||||
data := []byte("# DenyKey=system.run[*]\nOther=value\n")
|
||||
if err := os.WriteFile(path, data, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
modified := time.Unix(1_700_000_000, 0)
|
||||
if err := os.Chtimes(path, modified, modified); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := UpdateConfigMultiple(path, "DenyKey", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !info.ModTime().Equal(modified) {
|
||||
t.Fatalf("unchanged configuration was rewritten: modification time is %s", info.ModTime())
|
||||
}
|
||||
}
|
||||
159
templates/entrypoints/internal/bootstrap/environment.go
Normal file
159
templates/entrypoints/internal/bootstrap/environment.go
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
//go:build windows
|
||||
|
||||
// Package bootstrap provides the shared building blocks of the container
|
||||
// entrypoints: process environment handling, Zabbix configuration file
|
||||
// updates, logging and the final hand-off to the service binary.
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Environment is a mutable set of environment variables keyed by name.
|
||||
// The entrypoint modifies it while preparing a service and passes the
|
||||
// result to the final process.
|
||||
type Environment map[string]string
|
||||
|
||||
// NewEnvironment parses "NAME=value" pairs as returned by os.Environ.
|
||||
func NewEnvironment(values []string) Environment {
|
||||
env := make(Environment, len(values))
|
||||
for _, item := range values {
|
||||
name, value, found := strings.Cut(item, "=")
|
||||
if found {
|
||||
env[name] = value
|
||||
}
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
// List returns the variables as sorted "NAME=value" pairs suitable for exec.
|
||||
func (env Environment) List() []string {
|
||||
names := make([]string, 0, len(env))
|
||||
for name := range env {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
values := make([]string, 0, len(names))
|
||||
for _, name := range names {
|
||||
values = append(values, name+"="+env[name])
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// ValueOrDefault returns the value of name, or defaultValue when the
|
||||
// variable is not present. An empty value counts as present; use
|
||||
// ValueOrDefaultNonEmpty to treat it as missing.
|
||||
func (env Environment) ValueOrDefault(name, defaultValue string) string {
|
||||
value, found := env[name]
|
||||
if !found {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// ValueOrDefaultNonEmpty returns the value of name, or defaultValue when
|
||||
// the variable is missing or empty.
|
||||
func (env Environment) ValueOrDefaultNonEmpty(name, defaultValue string) string {
|
||||
if value := env[name]; value != "" {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// RequiredHomeDirectory returns the Zabbix home directory,
|
||||
// verifying that it exists.
|
||||
func RequiredHomeDirectory(env Environment) (string, error) {
|
||||
return requiredDirectory(env, "ZABBIX_USER_HOME_DIR")
|
||||
}
|
||||
|
||||
// RequiredDirectories returns the Zabbix home and config directories
|
||||
// (ZABBIX_USER_HOME_DIR and ZABBIX_CONF_DIR), verifying that both exist.
|
||||
func RequiredDirectories(env Environment) (homeDir, configDir string, err error) {
|
||||
homeDir, err = RequiredHomeDirectory(env)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
configDir, err = requiredDirectory(env, "ZABBIX_CONF_DIR")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return homeDir, configDir, nil
|
||||
}
|
||||
|
||||
// requiredDirectory returns directory from env variable.
|
||||
func requiredDirectory(env Environment, name string) (string, error) {
|
||||
directory := env[name]
|
||||
if directory == "" {
|
||||
return "", fmt.Errorf("%s must be set", name)
|
||||
}
|
||||
|
||||
info, err := os.Stat(directory)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("access %s directory %s: %w", name, directory, err)
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return "", fmt.Errorf("%s path %s is not a directory", name, directory)
|
||||
}
|
||||
|
||||
return directory, nil
|
||||
}
|
||||
|
||||
// ProcessFileFromEnvironment persists the value of variable into a file
|
||||
// under directory and points the corresponding "<variable>FILE" variable at
|
||||
// it. The plain variable is always removed so that secrets do not stay in
|
||||
// the service environment.
|
||||
func ProcessFileFromEnvironment(env Environment, directory, variable string) error {
|
||||
fileVariable := variable + "FILE"
|
||||
if value := env[variable]; value != "" {
|
||||
path := filepath.Join(directory, fileVariable)
|
||||
if err := os.WriteFile(path, []byte(value), 0o600); err != nil {
|
||||
return fmt.Errorf("write %s: %w", path, err)
|
||||
}
|
||||
env[fileVariable] = path
|
||||
}
|
||||
delete(env, variable)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcessTLSFiles moves TLS material from the listed variables into
|
||||
// files under <home>/enc_internal, so that Zabbix reads certificates and
|
||||
// keys from disk instead of the environment.
|
||||
func ProcessTLSFiles(env Environment, homeDir string, variables ...string) error {
|
||||
directory := filepath.Join(homeDir, "enc_internal")
|
||||
for _, variable := range variables {
|
||||
if err := ProcessFileFromEnvironment(env, directory, variable); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearPrivateEnv removes variables with the supplied prefixes that the
|
||||
// service should not inherit. ZBX_CLEAR_ENV=false disables the cleanup.
|
||||
func ClearPrivateEnv(env Environment, prefixes ...string) {
|
||||
if env["ZBX_CLEAR_ENV"] == "false" {
|
||||
return
|
||||
}
|
||||
for name := range env {
|
||||
if hasAnyPrefix(name, prefixes) {
|
||||
delete(env, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hasAnyPrefix(value string, prefixes []string) bool {
|
||||
for _, prefix := range prefixes {
|
||||
if strings.HasPrefix(value, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
101
templates/entrypoints/internal/bootstrap/environment_test.go
Normal file
101
templates/entrypoints/internal/bootstrap/environment_test.go
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
//go:build windows
|
||||
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEnvironmentDefaults(t *testing.T) {
|
||||
env := Environment{"EMPTY": "", "VALUE": "configured"}
|
||||
if got := env.ValueOrDefault("EMPTY", "default"); got != "" {
|
||||
t.Fatalf("ValueOrDefault() = %q, want empty value", got)
|
||||
}
|
||||
if got := env.ValueOrDefaultNonEmpty("EMPTY", "default"); got != "default" {
|
||||
t.Fatalf("ValueOrDefaultNonEmpty() = %q, want default", got)
|
||||
}
|
||||
if got := env.ValueOrDefaultNonEmpty("VALUE", "default"); got != "configured" {
|
||||
t.Fatalf("ValueOrDefaultNonEmpty() = %q, want configured", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessFileAndClearEnvironment(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
env := Environment{
|
||||
"ZBX_TLSPSK": "secret", "ZABBIX_CONF_DIR": `C:\zabbix\conf`,
|
||||
"UNRELATED_VARIABLE": "value", "VALUE": "a=b",
|
||||
}
|
||||
if err := ProcessFileFromEnvironment(env, directory, "ZBX_TLSPSK"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(directory, "ZBX_TLSPSKFILE"))
|
||||
if err != nil || string(data) != "secret" {
|
||||
t.Fatalf("TLS file: %q, %v", data, err)
|
||||
}
|
||||
if env["ZBX_TLSPSKFILE"] != filepath.Join(directory, "ZBX_TLSPSKFILE") {
|
||||
t.Fatalf("unexpected TLS file path: %q", env["ZBX_TLSPSKFILE"])
|
||||
}
|
||||
if _, found := env["ZBX_TLSPSK"]; found {
|
||||
t.Fatal("ZBX_TLSPSK was not removed")
|
||||
}
|
||||
|
||||
ClearPrivateEnv(env, "ZABBIX_")
|
||||
if env["UNRELATED_VARIABLE"] != "value" {
|
||||
t.Fatal("unrelated variable was unexpectedly removed")
|
||||
}
|
||||
if !strings.Contains(strings.Join(env.List(), "\n"), "VALUE=a=b") {
|
||||
t.Fatalf("environment list: %q", env.List())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearPrivateEnvWithPrefixes(t *testing.T) {
|
||||
env := Environment{
|
||||
"ZABBIX_CONF_DIR": `C:\zabbix\conf`,
|
||||
"UNRELATED_VARIABLE": "value",
|
||||
}
|
||||
ClearPrivateEnv(env, "ZABBIX_")
|
||||
if _, found := env["ZABBIX_CONF_DIR"]; found {
|
||||
t.Fatal("ZABBIX_CONF_DIR was not removed")
|
||||
}
|
||||
if env["UNRELATED_VARIABLE"] != "value" {
|
||||
t.Fatal("unrelated variable was unexpectedly removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequiredHomeDirectory(t *testing.T) {
|
||||
homeDir := t.TempDir()
|
||||
got, err := RequiredHomeDirectory(Environment{"ZABBIX_USER_HOME_DIR": homeDir})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != homeDir {
|
||||
t.Fatalf("RequiredHomeDirectory() = %q, want %q", got, homeDir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequiredHomeDirectoryRejectsInvalidPaths(t *testing.T) {
|
||||
filePath := filepath.Join(t.TempDir(), "home")
|
||||
if err := os.WriteFile(filePath, nil, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
}{
|
||||
{name: "missing"},
|
||||
{name: "not found", path: filepath.Join(t.TempDir(), "missing")},
|
||||
{name: "regular file", path: filePath},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if _, err := RequiredHomeDirectory(Environment{"ZABBIX_USER_HOME_DIR": test.path}); err == nil {
|
||||
t.Fatal("RequiredHomeDirectory() unexpectedly succeeded")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
18
templates/entrypoints/internal/bootstrap/exec_windows.go
Normal file
18
templates/entrypoints/internal/bootstrap/exec_windows.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package bootstrap
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Exec runs args as a child process wired to the entrypoint standard
|
||||
// streams; Windows has no execve equivalent.
|
||||
func Exec(args []string, env Environment) error {
|
||||
command := exec.Command(args[0], args[1:]...)
|
||||
command.Env = env.List()
|
||||
command.Stdin = os.Stdin
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
|
||||
return command.Run()
|
||||
}
|
||||
44
templates/entrypoints/internal/bootstrap/files.go
Normal file
44
templates/entrypoints/internal/bootstrap/files.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
//go:build windows
|
||||
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// WriteFilePreservingMode atomically replaces a file while preserving its
|
||||
// permission bits.
|
||||
func WriteFilePreservingMode(path string, data []byte) error {
|
||||
resolvedPath, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := os.Stat(resolvedPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
temporary, err := os.CreateTemp(filepath.Dir(resolvedPath), "."+filepath.Base(resolvedPath)+".tmp-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(temporary.Name())
|
||||
defer temporary.Close()
|
||||
|
||||
if err := temporary.Chmod(info.Mode().Perm()); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := temporary.Write(data); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := temporary.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := temporary.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Rename(temporary.Name(), resolvedPath)
|
||||
}
|
||||
38
templates/entrypoints/internal/bootstrap/logging.go
Normal file
38
templates/entrypoints/internal/bootstrap/logging.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
//go:build windows
|
||||
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LogInfo writes a timestamped informational message to stdout.
|
||||
func LogInfo(format string, args ...any) {
|
||||
logMessage(os.Stdout, "info", format, args...)
|
||||
}
|
||||
|
||||
// LogDebug writes a debug message to stdout when DEBUG_MODE=true.
|
||||
func LogDebug(env Environment, format string, args ...any) {
|
||||
if env["DEBUG_MODE"] != "true" {
|
||||
return
|
||||
}
|
||||
|
||||
logMessage(os.Stdout, "debug", format, args...)
|
||||
}
|
||||
|
||||
// LogWarn writes a warning message to stderr.
|
||||
func LogWarn(format string, args ...any) {
|
||||
logMessage(os.Stderr, "warning", format, args...)
|
||||
}
|
||||
|
||||
// LogError writes an error message to stderr.
|
||||
func LogError(format string, args ...any) {
|
||||
logMessage(os.Stderr, "error", format, args...)
|
||||
}
|
||||
|
||||
func logMessage(file *os.File, level, format string, args ...any) {
|
||||
timestamp := time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
||||
fmt.Fprintf(file, "%s [%s]: %s\n", timestamp, level, fmt.Sprintf(format, args...))
|
||||
}
|
||||
18
templates/entrypoints/internal/hooks/command_windows.go
Normal file
18
templates/entrypoints/internal/hooks/command_windows.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package hooks
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func command(path string, _ os.FileMode) ([]string, bool) {
|
||||
switch strings.ToLower(filepath.Ext(path)) {
|
||||
case ".ps1":
|
||||
return []string{"pwsh.exe", "-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", path}, true
|
||||
case ".cmd", ".bat":
|
||||
return []string{"cmd.exe", "/D", "/S", "/C", path}, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
27
templates/entrypoints/internal/hooks/command_windows_test.go
Normal file
27
templates/entrypoints/internal/hooks/command_windows_test.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
//go:build windows
|
||||
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPowerShellCommand(t *testing.T) {
|
||||
path := `C:\zabbix\entrypoint.d\10-custom.ps1`
|
||||
args, supported := command(path, os.FileMode(0))
|
||||
want := []string{"pwsh.exe", "-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", path}
|
||||
if !supported || !reflect.DeepEqual(args, want) {
|
||||
t.Fatalf("command() = %#v, %v; want %#v, true", args, supported, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmdCommand(t *testing.T) {
|
||||
path := `C:\zabbix\entrypoint.d\20-custom.cmd`
|
||||
args, supported := command(path, os.FileMode(0))
|
||||
want := []string{"cmd.exe", "/D", "/S", "/C", path}
|
||||
if !supported || !reflect.DeepEqual(args, want) {
|
||||
t.Fatalf("command() = %#v, %v; want %#v, true", args, supported, want)
|
||||
}
|
||||
}
|
||||
64
templates/entrypoints/internal/hooks/hooks.go
Normal file
64
templates/entrypoints/internal/hooks/hooks.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
//go:build windows
|
||||
|
||||
// Package hooks runs user-provided scripts from the entrypoint.d directory
|
||||
// before the service starts.
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/zabbix/zabbix-docker/templates/entrypoints/internal/bootstrap"
|
||||
)
|
||||
|
||||
const directoryName = "entrypoint.d"
|
||||
|
||||
// Run executes supported PowerShell and cmd scripts from <home>/entrypoint.d
|
||||
// in file name order. Everything else is skipped. The first failing hook
|
||||
// aborts the entrypoint.
|
||||
func Run(env bootstrap.Environment) error {
|
||||
homeDir, err := bootstrap.RequiredHomeDirectory(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
directory := filepath.Join(homeDir, directoryName)
|
||||
entries, err := os.ReadDir(directory)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("read entrypoint hooks directory %s: %w", directory, err)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
path := filepath.Join(directory, entry.Name())
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inspect entrypoint hook %s: %w", path, err)
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
continue
|
||||
}
|
||||
|
||||
args, supported := command(path, info.Mode())
|
||||
if !supported {
|
||||
continue
|
||||
}
|
||||
|
||||
bootstrap.LogInfo("** Running entrypoint hook: %s", path)
|
||||
|
||||
hook := exec.Command(args[0], args[1:]...)
|
||||
hook.Env = env.List()
|
||||
hook.Stdin = os.Stdin
|
||||
hook.Stdout = os.Stdout
|
||||
hook.Stderr = os.Stderr
|
||||
if err := hook.Run(); err != nil {
|
||||
return fmt.Errorf("entrypoint hook %s failed: %w", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
76
templates/entrypoints/internal/hooks/hooks_test.go
Normal file
76
templates/entrypoints/internal/hooks/hooks_test.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
//go:build windows
|
||||
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/zabbix/zabbix-docker/templates/entrypoints/internal/bootstrap"
|
||||
)
|
||||
|
||||
func TestRunExecutesHooksInOrder(t *testing.T) {
|
||||
homeDir := t.TempDir()
|
||||
directory := filepath.Join(homeDir, directoryName)
|
||||
if err := os.Mkdir(directory, 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
output := filepath.Join(homeDir, "output")
|
||||
for name, content := range map[string]string{
|
||||
"20-second.cmd": "@echo second:%ZABBIX_CONF_DIR%>>\"%HOOK_OUTPUT%\"\r\n",
|
||||
"10-first.cmd": "@echo first:%ZABBIX_CONF_DIR%>>\"%HOOK_OUTPUT%\"\r\n",
|
||||
"30-ignored.txt": "@exit /b 1\r\n",
|
||||
} {
|
||||
if err := os.WriteFile(filepath.Join(directory, name), []byte(content), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
env := bootstrap.NewEnvironment(os.Environ())
|
||||
env["ZABBIX_USER_HOME_DIR"] = homeDir
|
||||
env["ZABBIX_CONF_DIR"] = `C:\zabbix\conf`
|
||||
env["HOOK_OUTPUT"] = output
|
||||
if err := Run(env); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(output)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := strings.ReplaceAll(string(data), "\r\n", "\n")
|
||||
want := "first:C:\\zabbix\\conf\nsecond:C:\\zabbix\\conf\n"
|
||||
if got != want {
|
||||
t.Fatalf("hook output = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunReturnsHookFailure(t *testing.T) {
|
||||
homeDir := t.TempDir()
|
||||
directory := filepath.Join(homeDir, directoryName)
|
||||
if err := os.Mkdir(directory, 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
path := filepath.Join(directory, "10-fail.cmd")
|
||||
if err := os.WriteFile(path, []byte("@exit /b 7\r\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
env := bootstrap.NewEnvironment(os.Environ())
|
||||
env["ZABBIX_USER_HOME_DIR"] = homeDir
|
||||
err := Run(env)
|
||||
if err == nil || !strings.Contains(err.Error(), "10-fail.cmd") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunIgnoresMissingDirectory(t *testing.T) {
|
||||
err := Run(bootstrap.Environment{"ZABBIX_USER_HOME_DIR": t.TempDir()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
58
templates/entrypoints/licenses/go-THIRD-PARTY-NOTICES.txt
Normal file
58
templates/entrypoints/licenses/go-THIRD-PARTY-NOTICES.txt
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
Go entrypoint third-party notices
|
||||
=================================
|
||||
|
||||
Component: Go standard library
|
||||
License: BSD-3-Clause
|
||||
Source: https://go.googlesource.com/go
|
||||
|
||||
Copyright 2009 The Go Authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Additional IP Rights Grant (Patents)
|
||||
------------------------------------
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in the
|
||||
future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
||||
Loading…
Reference in a new issue