diff --git a/.buildkite/pipeline.elastic-agent-package.yml b/.buildkite/pipeline.elastic-agent-package.yml index fdea87d1ebc..1c13ce67cdb 100644 --- a/.buildkite/pipeline.elastic-agent-package.yml +++ b/.buildkite/pipeline.elastic-agent-package.yml @@ -120,6 +120,25 @@ steps: - false - true + - label: ":package: :windows: Package Windows Docker Container" + key: package_elastic-agent-windows-docker + depends_on: package_elastic-agent + agents: + provider: "gcp" + image: "family/elastic-workers-windows-2022" + machineType: "n2-standard-8" + diskSizeGb: 200 + env: + PLATFORMS: "windows/amd64" + PACKAGES: "docker" + command: | + powershell -ExecutionPolicy Bypass -File .buildkite/scripts/steps/package-windows-docker.ps1 + artifact_paths: + - "build/distributions/**/*" + plugins: + - elastic/vault-docker-login#v0.5.2: + secret_path: 'kv/ci-shared/platform-ingest/elastic_docker_registry' + - label: ":elastic-stack: Publishing to DRA" if: build.env("BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG") == null || build.env("BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG") != "independent-agent-release-staging" key: dra-publish diff --git a/.buildkite/scripts/steps/package-windows-docker.ps1 b/.buildkite/scripts/steps/package-windows-docker.ps1 new file mode 100644 index 00000000000..eb8116705ff --- /dev/null +++ b/.buildkite/scripts/steps/package-windows-docker.ps1 @@ -0,0 +1,60 @@ +# PowerShell script for packaging Windows Docker containers in Buildkite +# This script runs on a Windows agent with Docker Desktop in Windows container mode + +$ErrorActionPreference = "Stop" + +Write-Host "--- Setting up environment" + +# Check if MANIFEST_URL is set +if (-not $env:MANIFEST_URL) { + $env:MANIFEST_URL = & buildkite-agent meta-data get MANIFEST_URL --default "" + if (-not $env:MANIFEST_URL) { + Write-Host "ERROR: Missing MANIFEST_URL variable or empty string provided" -ForegroundColor Red + exit 1 + } +} + +# Set MAGEFILE_VERBOSE if not set +if (-not $env:MAGEFILE_VERBOSE) { + $env:MAGEFILE_VERBOSE = & buildkite-agent meta-data get MAGEFILE_VERBOSE --default "0" +} + +# Create the agent drop path +$env:AGENT_DROP_PATH = "build/elastic-agent-drop" +New-Item -ItemType Directory -Force -Path $env:AGENT_DROP_PATH | Out-Null + +Write-Host "+++ Downloading Windows binary artifact from previous step" +# Download the pre-built Windows binary from the Linux cross-build step +& buildkite-agent artifact download "build/golang-crossbuild/elastic-agent-windows-amd64.exe" . +if ($LASTEXITCODE -ne 0) { + Write-Host "ERROR: Failed to download Windows binary artifact" -ForegroundColor Red + exit $LASTEXITCODE +} + +Write-Host "+++ Verifying Docker is in Windows container mode" +$dockerInfo = & docker info 2>&1 +if ($dockerInfo -match "OSType: linux") { + Write-Host "ERROR: Docker is in Linux container mode. Please switch to Windows containers." -ForegroundColor Red + Write-Host "Run: & 'C:\Program Files\Docker\Docker\DockerCli.exe' -SwitchWindowsEngine" -ForegroundColor Yellow + exit 1 +} +Write-Host "SUCCESS: Docker is in Windows container mode" -ForegroundColor Green + +Write-Host "+++ Running mage package for Windows Docker" +# Set environment for Windows Docker packaging +$env:PLATFORMS = "windows/amd64" +$env:PACKAGES = "docker" + +# Run mage targets +$mageTargets = @("clean", "downloadManifest", "packageUsingDRA") +& mage $mageTargets + +if ($LASTEXITCODE -ne 0) { + Write-Host "ERROR: Mage packaging failed" -ForegroundColor Red + exit $LASTEXITCODE +} + +Write-Host "+++ Listing built artifacts" +Get-ChildItem -Recurse build/distributions/ | Format-Table -AutoSize + +Write-Host "SUCCESS: Windows Docker packaging completed successfully" -ForegroundColor Green diff --git a/changelog/fragments/1760228817-feature-windows.yaml b/changelog/fragments/1760228817-feature-windows.yaml new file mode 100644 index 00000000000..80c10475784 --- /dev/null +++ b/changelog/fragments/1760228817-feature-windows.yaml @@ -0,0 +1,45 @@ +# REQUIRED +# Kind can be one of: +# - breaking-change: a change to previously-documented behavior +# - deprecation: functionality that is being removed in a later release +# - bug-fix: fixes a problem in a previous version +# - enhancement: extends functionality but does not break or fix existing behavior +# - feature: new functionality +# - known-issue: problems that we are aware of in a given version +# - security: impacts on the security of a product or a user’s deployment. +# - upgrade: important information for someone upgrading from a prior version +# - other: does not fit into any of the other categories +kind: feature + +# REQUIRED for all kinds +# Change summary; a 80ish characters long description of the change. +summary: Add support for Windows containers to enable deployment on Windows hosts + +# REQUIRED for breaking-change, deprecation, known-issue +# Long description; in case the summary is not enough to describe the change +# this field accommodate a description without length limits. +# description: + +# REQUIRED for breaking-change, deprecation, known-issue +# impact: + +# REQUIRED for breaking-change, deprecation, known-issue +# action: + +# REQUIRED for all kinds +# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. +component: elastic-agent + +# AUTOMATED +# OPTIONAL to manually add other PR URLs +# PR URL: A link the PR that added the changeset. +# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. +# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. +# Please provide it if you are adding a fragment for a different PR. +# pr: https://github.com/owner/repo/1234 + +# AUTOMATED +# OPTIONAL to manually add other issue URLs +# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). +# If not present is automatically filled by the tooling with the issue linked to the PR number. +# issue: https://github.com/owner/repo/1234 diff --git a/dev-tools/mage/dockerbuilder.go b/dev-tools/mage/dockerbuilder.go index 466a0aba01d..5433e40b623 100644 --- a/dev-tools/mage/dockerbuilder.go +++ b/dev-tools/mage/dockerbuilder.go @@ -193,20 +193,34 @@ func (b *dockerBuilder) expandDockerfile(templatesDir string, data map[string]in dockerfile = f } + // Use Windows-specific Dockerfile for Windows builds + if b.OS == "windows" { + dockerfile = "Dockerfile.windows.elastic-agent.tmpl" + } + entrypoint := "docker-entrypoint.tmpl" if e, found := b.ExtraVars["docker_entrypoint"]; found { entrypoint = e } + // Use Windows-specific entrypoint for Windows builds + if b.OS == "windows" { + entrypoint = "docker-entrypoint.windows.elastic-agent.tmpl" + } + type fileExpansion struct { source string target string } - for _, file := range []fileExpansion{{dockerfile, "Dockerfile.tmpl"}, {entrypoint, "docker-entrypoint.tmpl"}} { - target := strings.TrimSuffix( - filepath.Join(b.buildDir, file.target), - ".tmpl", - ) + + targetDockerfile := "Dockerfile" + targetEntrypoint := "docker-entrypoint" + if b.OS == "windows" { + targetEntrypoint = "docker-entrypoint.ps1" + } + + for _, file := range []fileExpansion{{dockerfile, targetDockerfile}, {entrypoint, targetEntrypoint}} { + target := filepath.Join(b.buildDir, file.target) path := filepath.Join(templatesDir, file.source) err := b.ExpandFile(path, target, data) if err != nil { diff --git a/dev-tools/mage/pkgtypes.go b/dev-tools/mage/pkgtypes.go index 8da5d7ca9fa..f6fab53678a 100644 --- a/dev-tools/mage/pkgtypes.go +++ b/dev-tools/mage/pkgtypes.go @@ -148,6 +148,9 @@ var OSArchNames = map[string]map[PackageType]map[string]string{ "amd64": "x86_64", "arm64": "arm64", }, + Docker: { + "amd64": "amd64", + }, }, "darwin": { TarGz: { diff --git a/dev-tools/packaging/packages.yml b/dev-tools/packaging/packages.yml index c9abd9c1ddc..eee8015df97 100644 --- a/dev-tools/packaging/packages.yml +++ b/dev-tools/packaging/packages.yml @@ -500,6 +500,11 @@ shared: extra_vars: from: 'docker.elastic.co/wolfi/chainguard-base-fips:latest' + - &docker_windows_spec + docker_variant: 'basic' + extra_vars: + from: '--platform=windows/amd64 mcr.microsoft.com/powershell:lts-nanoserver-ltsc2022' + - &docker_elastic_spec extra_vars: repository: 'docker.elastic.co/elastic-agent' @@ -551,6 +556,23 @@ shared: source: '{{ repo.RootDir }}/deploy/kubernetes/elastic-agent-standalone/templates.d' mode: 0755 + - &agent_windows_docker_spec + <<: *agent_windows_binary_spec + extra_vars: + dockerfile: 'Dockerfile.windows.elastic-agent.tmpl' + docker_entrypoint: 'docker-entrypoint.windows.elastic-agent.tmpl' + user: '{{ .BeatName }}' + beats_install_path: "install" + files: + 'elastic-agent.yml': + source: 'elastic-agent.docker.yml' + mode: 0600 + config: true + '.elastic-agent.active.commit': + content: > + {{ commit }} + mode: 0644 + # cloud build to beats-ci repository - &agent_docker_cloud_spec docker_variant: 'cloud' @@ -1213,6 +1235,20 @@ specs: {{ agent_package_version }} mode: 0644 + ######## Windows Docker images ######### + - os: windows + arch: amd64 + types: [docker] + spec: + <<: *docker_windows_spec + <<: *agent_windows_docker_spec + <<: *docker_elastic_spec + <<: *elastic_license_for_binaries + files: + '{{.BeatName}}{{.BinaryExt}}': + source: ./build/golang-crossbuild/{{.BeatName}}-{{.GOOS}}-{{.Platform.Arch}}{{.BinaryExt}} + ######## End Windows Docker images ######### + - os: darwin types: [tgz] spec: diff --git a/dev-tools/packaging/templates/docker/Dockerfile.windows.elastic-agent.tmpl b/dev-tools/packaging/templates/docker/Dockerfile.windows.elastic-agent.tmpl new file mode 100644 index 00000000000..a5c5e65228f --- /dev/null +++ b/dev-tools/packaging/templates/docker/Dockerfile.windows.elastic-agent.tmpl @@ -0,0 +1,63 @@ +{{- $beatHome := printf "C:/%s" .BeatName }} +{{- $repoInfo := repo }} + +FROM {{ .from }} + +ENV BEAT_SETUID_AS={{ .user }} + +LABEL \ + org.label-schema.build-date="{{ date }}" \ + org.label-schema.schema-version="1.0" \ + org.label-schema.vendor="{{ .BeatVendor }}" \ + org.label-schema.license="{{ .License }}" \ + org.label-schema.name="{{ .BeatName }}" \ + org.label-schema.version="{{ beat_version }}{{if .Snapshot}}-SNAPSHOT{{end}}" \ + org.label-schema.url="{{ .BeatURL }}" \ + org.label-schema.vcs-url="{{ $repoInfo.RootImportPath }}" \ + org.label-schema.vcs-ref="{{ commit }}" \ + io.k8s.description="{{ .BeatDescription }}" \ + io.k8s.display-name="{{ .BeatName | title }} image" \ + org.opencontainers.image.created="{{ date }}" \ + org.opencontainers.image.licenses="{{ .License }}" \ + org.opencontainers.image.title="{{ .BeatName | title }}" \ + org.opencontainers.image.vendor="{{ .BeatVendor }}" \ + org.opencontainers.image.authors="infra@elastic.co" \ + maintainer="infra@elastic.co" \ + name="{{ .BeatName }}" \ + vendor="{{ .BeatVendor }}" \ + version="{{ agent_package_version }}{{if .Snapshot}}-SNAPSHOT{{end}}" \ + release="1" \ + url="{{ .BeatURL }}" \ + summary="{{ .BeatName }}" \ + license="{{ .License }}" \ + description="{{ .BeatDescription }}" + +ENV ELASTIC_CONTAINER="true" +ENV PATH="{{ $beatHome }};C:/Windows/system32;C:/Windows" +ENV GODEBUG="madvdontneed=1" + +# Copy the agent files +COPY beat {{ $beatHome }} + +# Copy the entrypoint script +COPY docker-entrypoint.ps1 C:/docker-entrypoint.ps1 + +# Create necessary directories using cmd (nanoserver has limited PowerShell) +RUN cmd /S /C "mkdir {{ $beatHome }}\data 2>nul & mkdir C:\licenses 2>nul & exit 0" + +# Copy license files +RUN cmd /S /C "if exist {{ $beatHome }}\LICENSE.txt copy {{ $beatHome }}\LICENSE.txt C:\licenses\LICENSE.txt" +RUN cmd /S /C "if exist {{ $beatHome }}\NOTICE.txt copy {{ $beatHome }}\NOTICE.txt C:\licenses\NOTICE.txt" + +{{- range $i, $port := .ExposePorts }} +EXPOSE {{ $port }} +{{- end }} + +# When running under Docker, we must ensure libbeat monitoring pulls cgroup +# metrics from the proper location (not applicable to Windows but kept for consistency) +ENV LIBBEAT_MONITORING_CGROUPS_HIERARCHY_OVERRIDE=/ + +WORKDIR {{ $beatHome }} + +# Use PowerShell Core with full path from PowerShell nanoserver image +ENTRYPOINT ["C:/Program Files/PowerShell/pwsh.exe", "-NoProfile", "-File", "C:/docker-entrypoint.ps1"] diff --git a/dev-tools/packaging/templates/docker/docker-entrypoint.windows.elastic-agent.tmpl b/dev-tools/packaging/templates/docker/docker-entrypoint.windows.elastic-agent.tmpl new file mode 100644 index 00000000000..0d50458280f --- /dev/null +++ b/dev-tools/packaging/templates/docker/docker-entrypoint.windows.elastic-agent.tmpl @@ -0,0 +1,15 @@ +# PowerShell entrypoint for Elastic Agent Windows containers +# Equivalent to docker-entrypoint.elastic-agent.tmpl for Linux + +# For information on the possible environment variables that can be passed into the container, run: +# .\{{ .BeatName }}.exe container --help + +$ErrorActionPreference = "Stop" + +if ($env:ELASTIC_AGENT_OTEL -eq "true") { + & "{{ .BeatName }}.exe" otel $args +} else { + & "{{ .BeatName }}.exe" container $args +} + +exit $LASTEXITCODE