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/*'
|
- '!Dockerfiles/*/windows/*'
|
||||||
- '.github/workflows/images_build.yml'
|
- '.github/workflows/images_build.yml'
|
||||||
- '.github/actions/*'
|
- '.github/actions/*'
|
||||||
- 'templates/entrypoints/**'
|
- 'templates/entrypoints/*.sh'
|
||||||
|
- 'templates/entrypoints/lib/**'
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '50 02 * * *'
|
- cron: '50 02 * * *'
|
||||||
workflow_dispatch:
|
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.md'
|
||||||
- '!**/README.html'
|
- '!**/README.html'
|
||||||
- '.github/workflows/images_build_rhel.yml'
|
- '.github/workflows/images_build_rhel.yml'
|
||||||
|
- 'templates/entrypoints/*.sh'
|
||||||
|
- 'templates/entrypoints/lib/**'
|
||||||
|
- '!templates/entrypoints/web-apache-*.sh'
|
||||||
|
- '!templates/entrypoints/lib/apache.sh'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
publish_images:
|
publish_images:
|
||||||
|
|
|
||||||
10
.github/workflows/images_build_windows.yml
vendored
10
.github/workflows/images_build_windows.yml
vendored
|
|
@ -11,6 +11,10 @@ on:
|
||||||
paths:
|
paths:
|
||||||
- 'build.json'
|
- 'build.json'
|
||||||
- 'Dockerfiles/*/windows/*'
|
- 'Dockerfiles/*/windows/*'
|
||||||
|
- 'templates/config/*_windows/**'
|
||||||
|
- 'templates/entrypoints/**/*.go'
|
||||||
|
- 'templates/entrypoints/go.mod'
|
||||||
|
- 'templates/entrypoints/licenses/go-THIRD-PARTY-NOTICES.txt'
|
||||||
- '!**/README.md'
|
- '!**/README.md'
|
||||||
- '.github/workflows/images_build_windows.yml'
|
- '.github/workflows/images_build_windows.yml'
|
||||||
- '.github/actions/docker-build-push-windows/*'
|
- '.github/actions/docker-build-push-windows/*'
|
||||||
|
|
@ -40,6 +44,7 @@ env:
|
||||||
|
|
||||||
MSFT_BASE_BUILD_IMAGE: "mcr.microsoft.com/windows/servercore"
|
MSFT_BASE_BUILD_IMAGE: "mcr.microsoft.com/windows/servercore"
|
||||||
OS_BASE_IMAGE_NAME: "mcr.microsoft.com/windows/nanoserver"
|
OS_BASE_IMAGE_NAME: "mcr.microsoft.com/windows/nanoserver"
|
||||||
|
ENTRYPOINT_BUILD_IMAGE_NAME: "golang:1.26.5-windowsservercore"
|
||||||
|
|
||||||
BASE_IMAGE_NAME: "build-base"
|
BASE_IMAGE_NAME: "build-base"
|
||||||
BASE_BUILD_IMAGE_NAME: "build-mysql"
|
BASE_BUILD_IMAGE_NAME: "build-mysql"
|
||||||
|
|
@ -698,7 +703,7 @@ jobs:
|
||||||
id: docker_build
|
id: docker_build
|
||||||
uses: ./.github/actions/docker-build-push-windows
|
uses: ./.github/actions/docker-build-push-windows
|
||||||
with:
|
with:
|
||||||
context: ${{ env.DOCKERFILES_DIRECTORY }}\${{ matrix.component }}\windows
|
context: .
|
||||||
dockerfile: ${{ env.DOCKERFILES_DIRECTORY }}\${{ matrix.component }}\windows\Dockerfile
|
dockerfile: ${{ env.DOCKERFILES_DIRECTORY }}\${{ matrix.component }}\windows\Dockerfile
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
label_revision: ${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
|
label_revision: ${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
|
||||||
|
|
@ -706,10 +711,13 @@ jobs:
|
||||||
pull_images: |
|
pull_images: |
|
||||||
${{ steps.base_build.outputs.base_build_image }}
|
${{ steps.base_build.outputs.base_build_image }}
|
||||||
${{ env.OS_BASE_IMAGE_NAME }}:${{ steps.base_os_tag.outputs.os_tag }}
|
${{ 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_args: |
|
||||||
BUILD_BASE_IMAGE=${{ steps.base_build.outputs.base_build_image }}
|
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 }}
|
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 }}
|
push: ${{ env.AUTO_PUSH_IMAGES }}
|
||||||
|
remove_readme: false
|
||||||
|
|
||||||
- name: Sign the images with GitHub OIDC Token
|
- name: Sign the images with GitHub OIDC Token
|
||||||
if: ${{ env.AUTO_PUSH_IMAGES == 'true' }}
|
if: ${{ env.AUTO_PUSH_IMAGES == 'true' }}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,26 @@
|
||||||
# syntax=docker/dockerfile:1.6
|
# syntax=docker/dockerfile:1.6
|
||||||
# escape=`
|
# escape=`
|
||||||
ARG OS_BASE_IMAGE=mcr.microsoft.com/windows/nanoserver:ltsc2022
|
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 MAJOR_VERSION=7.4
|
||||||
ARG ZBX_VERSION=${MAJOR_VERSION}.12
|
ARG ZBX_VERSION=${MAJOR_VERSION}.12
|
||||||
|
|
||||||
ARG BUILD_BASE_IMAGE=zabbix-build-agent:ltsc2022-agent-${ZBX_VERSION}
|
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 ${BUILD_BASE_IMAGE} AS builder
|
||||||
FROM ${OS_BASE_IMAGE}
|
FROM ${OS_BASE_IMAGE}
|
||||||
|
|
||||||
|
|
@ -30,8 +44,10 @@ LABEL org.opencontainers.image.title="Zabbix agent" `
|
||||||
org.opencontainers.image.source="${ZBX_SOURCES}"
|
org.opencontainers.image.source="${ZBX_SOURCES}"
|
||||||
|
|
||||||
COPY --from=builder ["C:\\zabbix-${ZBX_VERSION}-output", "C:\\zabbix"]
|
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 --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\
|
WORKDIR C:\zabbix\
|
||||||
|
|
||||||
|
|
@ -66,6 +82,6 @@ USER zabbix
|
||||||
|
|
||||||
EXPOSE 10050/tcp
|
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"]
|
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
|
# syntax=docker/dockerfile:1.6
|
||||||
# escape=`
|
# escape=`
|
||||||
ARG OS_BASE_IMAGE=mcr.microsoft.com/windows/nanoserver:ltsc2022
|
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 MAJOR_VERSION=7.4
|
||||||
ARG ZBX_VERSION=${MAJOR_VERSION}.12
|
ARG ZBX_VERSION=${MAJOR_VERSION}.12
|
||||||
|
|
||||||
ARG BUILD_BASE_IMAGE=zabbix-build-agent:ltsc2022-agent2-${ZBX_VERSION}
|
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 ${BUILD_BASE_IMAGE} AS builder
|
||||||
FROM ${OS_BASE_IMAGE}
|
FROM ${OS_BASE_IMAGE}
|
||||||
|
|
||||||
|
|
@ -31,8 +45,10 @@ LABEL org.opencontainers.image.title="Zabbix agent 2" `
|
||||||
org.opencontainers.image.source="${ZBX_SOURCES}"
|
org.opencontainers.image.source="${ZBX_SOURCES}"
|
||||||
|
|
||||||
COPY --from=builder ["C:\\zabbix-${ZBX_VERSION}-output", "C:\\zabbix"]
|
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 --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\
|
WORKDIR C:\zabbix\
|
||||||
|
|
||||||
|
|
@ -69,6 +85,6 @@ USER zabbix
|
||||||
|
|
||||||
EXPOSE 10050/tcp 31999/tcp
|
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"]
|
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.version="${ZBX_VERSION}" `
|
||||||
org.opencontainers.image.source="${ZBX_SOURCES}"
|
org.opencontainers.image.source="${ZBX_SOURCES}"
|
||||||
|
|
||||||
ADD config_templates C:\config_templates
|
|
||||||
|
|
||||||
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||||
|
|
||||||
RUN Set-Location -Path $env:SystemDrive\.; `
|
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_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\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 -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 `
|
nmake /S -f Makefile `
|
||||||
PCRE2INCDIR=$env:SystemDrive\build_output\pcre2\include `
|
PCRE2INCDIR=$env:SystemDrive\build_output\pcre2\include `
|
||||||
PCRE2LIBDIR=$env:SystemDrive\build_output\pcre2\lib `
|
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.version="${ZBX_VERSION}" `
|
||||||
org.opencontainers.image.source="${ZBX_SOURCES}"
|
org.opencontainers.image.source="${ZBX_SOURCES}"
|
||||||
|
|
||||||
ADD config_templates C:\config_templates
|
|
||||||
|
|
||||||
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||||
|
|
||||||
RUN Set-Location -Path $env:SystemDrive\.; `
|
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\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 -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: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;
|
mingw32-make -s clean;
|
||||||
|
|
||||||
RUN function Build-Plugin { `
|
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