Skip to content

Commit 8e280fb

Browse files
authored
chore(wls): Add Windows support (#43609)
### What does this PR do? - Supports APM Workload Selection on Windows by adding `dd-compile-policy.exe` to the build - Fixes an issue in the Windows `InstallPath` variable, not matching the actual file hierarchy ### Motivation Making workload Selection work on Windows ### Describe how you validated your changes Manual QA on a VM ### Additional Notes Co-authored-by: baptiste.foy <baptiste.foy@datadoghq.com>
1 parent a9588fc commit 8e280fb

File tree

11 files changed

+211
-57
lines changed

11 files changed

+211
-57
lines changed

comp/workloadselection/impl/workloadselection.go

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@
77
package workloadselectionimpl
88

99
import (
10-
"bytes"
1110
"encoding/json"
1211
"fmt"
1312
"os"
14-
"os/exec"
1513
"path/filepath"
1614
"regexp"
1715
"sort"
@@ -26,8 +24,7 @@ import (
2624
)
2725

2826
var (
29-
configPath = filepath.Join(config.DefaultConfPath, "managed", "rc-orgwide-wls-policy.bin")
30-
ddPolicyCompileRelativePath = filepath.Join("embedded", "bin", "dd-compile-policy")
27+
configPath = filepath.Join(config.DefaultConfPath, "managed", "rc-orgwide-wls-policy.bin")
3128
// Pattern to extract policy ID from config path: datadog/\d+/<product>/<config_id>/<hash>
3229
policyIDPattern = regexp.MustCompile(`^datadog/\d+/[^/]+/([^/]+)/`)
3330
// Pattern to extract numeric prefix from policy ID: N.<name>
@@ -78,36 +75,6 @@ type workloadselectionComponent struct {
7875
config config.Component
7976
}
8077

81-
// isCompilePolicyBinaryAvailable checks if the compile policy binary is available
82-
// and executable
83-
func (c *workloadselectionComponent) isCompilePolicyBinaryAvailable() bool {
84-
compilePath := filepath.Join(getInstallPath(), ddPolicyCompileRelativePath)
85-
info, err := os.Stat(compilePath)
86-
if err != nil {
87-
if !os.IsNotExist(err) {
88-
c.log.Warnf("failed to stat APM workload selection compile policy binary: %v", err)
89-
}
90-
return false
91-
}
92-
return info.Mode().IsRegular() && info.Mode()&0111 != 0
93-
}
94-
95-
// compilePolicyBinary compiles the policy binary into a binary file
96-
// readable by the injector
97-
func (c *workloadselectionComponent) compileAndWriteConfig(rawConfig []byte) error {
98-
if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
99-
return err
100-
}
101-
cmd := exec.Command(filepath.Join(getInstallPath(), ddPolicyCompileRelativePath), "--input-string", string(rawConfig), "--output-file", configPath)
102-
var stdoutBuf, stderrBuf bytes.Buffer
103-
cmd.Stdout = &stdoutBuf
104-
cmd.Stderr = &stderrBuf
105-
if err := cmd.Run(); err != nil {
106-
return fmt.Errorf("error executing dd-policy-compile (%w); out: '%s'; err: '%s'", err, stdoutBuf.String(), stderrBuf.String())
107-
}
108-
return nil
109-
}
110-
11178
// policyConfig represents a config with its ordering information
11279
type policyConfig struct {
11380
path string
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2025-present Datadog, Inc.
5+
6+
//go:build !windows
7+
8+
package workloadselectionimpl
9+
10+
import (
11+
"bytes"
12+
"fmt"
13+
"os"
14+
"os/exec"
15+
"path/filepath"
16+
)
17+
18+
// getCompilePolicyBinaryPath returns the full path to the compile policy binary on Unix
19+
func getCompilePolicyBinaryPath() string {
20+
return filepath.Join(getInstallPath(), "embedded", "bin", "dd-compile-policy")
21+
}
22+
23+
// isCompilePolicyBinaryAvailable checks if the compile policy binary is available
24+
// and executable on Unix systems
25+
func (c *workloadselectionComponent) isCompilePolicyBinaryAvailable() bool {
26+
compilePath := getCompilePolicyBinaryPath()
27+
info, err := os.Stat(compilePath)
28+
if err != nil {
29+
if !os.IsNotExist(err) {
30+
c.log.Warnf("failed to stat APM workload selection compile policy binary: %v", err)
31+
}
32+
return false
33+
}
34+
// On Unix, check for executable bits
35+
return info.Mode().IsRegular() && info.Mode()&0111 != 0
36+
}
37+
38+
// compileAndWriteConfig compiles the policy binary into a binary file readable by the injector
39+
// On Unix systems, uses standard 0755 permissions for the directory
40+
func (c *workloadselectionComponent) compileAndWriteConfig(rawConfig []byte) error {
41+
if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
42+
return err
43+
}
44+
cmd := exec.Command(getCompilePolicyBinaryPath(), "--input-string", string(rawConfig), "--output-file", configPath)
45+
var stdoutBuf, stderrBuf bytes.Buffer
46+
cmd.Stdout = &stdoutBuf
47+
cmd.Stderr = &stderrBuf
48+
if err := cmd.Run(); err != nil {
49+
return fmt.Errorf("error executing dd-policy-compile (%w); out: '%s'; err: '%s'", err, stdoutBuf.String(), stderrBuf.String())
50+
}
51+
return nil
52+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2025-present Datadog, Inc.
5+
6+
//go:build windows
7+
8+
package workloadselectionimpl
9+
10+
import (
11+
"bytes"
12+
"fmt"
13+
"os"
14+
"os/exec"
15+
"path/filepath"
16+
17+
"golang.org/x/sys/windows"
18+
)
19+
20+
// getCompilePolicyBinaryPath returns the full path to the compile policy binary on Windows
21+
func getCompilePolicyBinaryPath() string {
22+
return filepath.Join(getInstallPath(), "bin", "dd-compile-policy.exe")
23+
}
24+
25+
// isCompilePolicyBinaryAvailable checks if the compile policy binary is available
26+
// on Windows systems
27+
func (c *workloadselectionComponent) isCompilePolicyBinaryAvailable() bool {
28+
compilePath := getCompilePolicyBinaryPath()
29+
info, err := os.Stat(compilePath)
30+
if err != nil {
31+
if !os.IsNotExist(err) {
32+
c.log.Warnf("failed to stat APM workload selection compile policy binary: %v", err)
33+
}
34+
return false
35+
}
36+
// On Windows, check that it's a regular file (not a directory)
37+
return info.Mode().IsRegular()
38+
}
39+
40+
// setFileReadableByEveryone sets the DACL on a file to allow:
41+
// - Current owner (ddagentuser): Full Control
42+
// - SYSTEM: Full Control
43+
// - Administrators: Full Control
44+
// - Everyone: Read and Execute
45+
// The owner is NOT changed - it remains as ddagentuser
46+
func setFileReadableByEveryone(path string) error {
47+
// Create an SDDL with only the DACL part (no Owner/Group)
48+
// D:AI - DACL, Auto-Inherit.
49+
// "Inherit permissions from the parent folder (System/Admins usually)."
50+
// (A;;GRGX;;;WD) - Allow Generic Read + Generic Execute to Everyone (World Domain).
51+
sddl := "D:AI(A;;GRGX;;;WD)"
52+
53+
sd, err := windows.SecurityDescriptorFromString(sddl)
54+
if err != nil {
55+
return fmt.Errorf("failed to create security descriptor: %w", err)
56+
}
57+
58+
dacl, _, err := sd.DACL()
59+
if err != nil {
60+
return fmt.Errorf("failed to get DACL: %w", err)
61+
}
62+
63+
// Only set the DACL, don't touch owner or group
64+
return windows.SetNamedSecurityInfo(
65+
path,
66+
windows.SE_FILE_OBJECT,
67+
windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION,
68+
nil, // owner - leave unchanged
69+
nil, // group - leave unchanged
70+
dacl, // DACL - set this
71+
nil, // SACL - leave unchanged
72+
)
73+
}
74+
75+
// compileAndWriteConfig compiles the policy binary into a binary file readable by the injector
76+
// On Windows, sets ACLs to allow Everyone read+execute access while keeping ddagentuser as owner
77+
func (c *workloadselectionComponent) compileAndWriteConfig(rawConfig []byte) error {
78+
dir := filepath.Dir(configPath)
79+
if err := os.MkdirAll(dir, 0755); err != nil {
80+
return err
81+
}
82+
cmd := exec.Command(getCompilePolicyBinaryPath(), "--input-string", string(rawConfig), "--output-file", configPath)
83+
var stdoutBuf, stderrBuf bytes.Buffer
84+
cmd.Stdout = &stdoutBuf
85+
cmd.Stderr = &stderrBuf
86+
if err := cmd.Run(); err != nil {
87+
return fmt.Errorf("error executing dd-policy-compile (%w); out: '%s'; err: '%s'", err, stdoutBuf.String(), stderrBuf.String())
88+
}
89+
// Set permissions on the file to allow Everyone read+execute access
90+
// We keep the current owner (ddagentuser) and only modify the DACL
91+
if err := setFileReadableByEveryone(configPath); err != nil {
92+
return fmt.Errorf("failed to set permissions on file %s: %w", configPath, err)
93+
}
94+
return nil
95+
}

comp/workloadselection/impl/workloadselection_linux_test.go renamed to comp/workloadselection/impl/workloadselection_withbin_test.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
// This product includes software developed at Datadog (https://www.datadoghq.com/).
44
// Copyright 2025-present Datadog, Inc.
55

6-
//go:build linux
6+
//go:build linux || windows
77

88
package workloadselectionimpl
99

1010
import (
1111
"os"
1212
"path/filepath"
13+
"runtime"
1314
"testing"
1415

1516
"github.com/stretchr/testify/assert"
@@ -20,6 +21,14 @@ import (
2021
"github.com/DataDog/datadog-agent/pkg/remoteconfig/state"
2122
)
2223

24+
// getBinaryPath returns the correct binary path based on the platform
25+
func getBinaryPath(tempDir string) string {
26+
if runtime.GOOS == "windows" {
27+
return filepath.Join(tempDir, "bin", "dd-compile-policy.exe")
28+
}
29+
return filepath.Join(tempDir, "embedded", "bin", "dd-compile-policy")
30+
}
31+
2332
// TestNewComponent tests the component initialization
2433
func TestNewComponent(t *testing.T) {
2534
tests := []struct {
@@ -62,7 +71,7 @@ func TestNewComponent(t *testing.T) {
6271

6372
// Create binary if needed
6473
if tt.setupBinary {
65-
binaryPath := filepath.Join(tempDir, ddPolicyCompileRelativePath)
74+
binaryPath := getBinaryPath(tempDir)
6675
require.NoError(t, os.MkdirAll(filepath.Dir(binaryPath), 0755))
6776
// Create executable file
6877
require.NoError(t, os.WriteFile(binaryPath, []byte("#!/bin/sh\necho test"), 0755))
@@ -100,7 +109,7 @@ func TestIsCompilePolicyBinaryAvailable(t *testing.T) {
100109
{
101110
name: "binary exists and is executable",
102111
setupFunc: func(t *testing.T, tempDir string) string {
103-
binaryPath := filepath.Join(tempDir, ddPolicyCompileRelativePath)
112+
binaryPath := getBinaryPath(tempDir)
104113
require.NoError(t, os.MkdirAll(filepath.Dir(binaryPath), 0755))
105114
require.NoError(t, os.WriteFile(binaryPath, []byte("#!/bin/sh\necho test"), 0755))
106115
return tempDir
@@ -117,17 +126,19 @@ func TestIsCompilePolicyBinaryAvailable(t *testing.T) {
117126
{
118127
name: "binary exists but is not executable",
119128
setupFunc: func(t *testing.T, tempDir string) string {
120-
binaryPath := filepath.Join(tempDir, ddPolicyCompileRelativePath)
129+
binaryPath := getBinaryPath(tempDir)
121130
require.NoError(t, os.MkdirAll(filepath.Dir(binaryPath), 0755))
122131
require.NoError(t, os.WriteFile(binaryPath, []byte("#!/bin/sh\necho test"), 0644))
123132
return tempDir
124133
},
125-
expectResult: false,
134+
// On Windows, executability is determined by extension, not permissions
135+
// So this test expects different results on Windows vs Unix
136+
expectResult: runtime.GOOS == "windows",
126137
},
127138
{
128139
name: "binary exists but is a directory",
129140
setupFunc: func(t *testing.T, tempDir string) string {
130-
binaryPath := filepath.Join(tempDir, ddPolicyCompileRelativePath)
141+
binaryPath := getBinaryPath(tempDir)
131142
require.NoError(t, os.MkdirAll(binaryPath, 0755))
132143
return tempDir
133144
},

comp/workloadselection/impl/workloadselection_nolinux_test.go renamed to comp/workloadselection/impl/workloadselection_withoutbin_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This product includes software developed at Datadog (https://www.datadoghq.com/).
44
// Copyright 2025-present Datadog, Inc.
55

6-
//go:build !linux
6+
//go:build !linux && !windows
77

88
package workloadselectionimpl
99

@@ -17,8 +17,8 @@ import (
1717
logmock "github.com/DataDog/datadog-agent/comp/core/log/mock"
1818
)
1919

20-
// TestNewComponent_NonLinux tests that the component is created but RCListener is not enabled on non-Linux platforms
21-
func TestNewComponent_NonLinux(t *testing.T) {
20+
// TestNewComponent_WithoutBin tests that the component is created but RCListener is not enabled on platforms without the compile policy binary
21+
func TestNewComponent_WithoutBin(t *testing.T) {
2222
tests := []struct {
2323
name string
2424
workloadSelectionEnabled bool

deps/compile_policy/BUILD.bazel

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,31 @@ alias(
1111
actual = select({
1212
"//:linux_arm64": "@compile_policy_arm64//:dd-compile-policy",
1313
"//:linux_x86_64": "@compile_policy_x86_64//:dd-compile-policy",
14+
"//:windows_x86_64": "@compile_policy_windows_x86_64//:dd-compile-policy.exe",
15+
}),
16+
target_compatible_with = select({
17+
"//:linux_arm64": [],
18+
"//:linux_x86_64": [],
19+
"//:windows_x86_64": [],
20+
"//conditions:default": ["@platforms//:incompatible"],
1421
}),
15-
target_compatible_with = [
16-
"@platforms//os:linux",
17-
],
1822
)
1923

2024
pkg_files(
2125
name = "bin_files",
2226
srcs = [":dd_compile_policy"],
23-
attributes = pkg_attributes(
24-
group = "root",
25-
mode = "0555",
26-
owner = "root",
27-
),
28-
prefix = "embedded/bin",
27+
attributes = select({
28+
"//:windows_x86_64": None, # Windows doesn't use Unix permissions
29+
"//conditions:default": pkg_attributes(
30+
group = "root",
31+
mode = "0555",
32+
owner = "root",
33+
),
34+
}),
35+
prefix = select({
36+
"//:windows_x86_64": "bin/agent",
37+
"//conditions:default": "embedded/bin",
38+
}),
2939
)
3040

3141
pkg_files(

deps/compile_policy/compile_policy.MODULE.bazel

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
http_archive = use_repo_rule("//third_party/bazel/tools/build_defs/repo:http.bzl", "http_archive")
44

5-
VERSION = "0.1.0"
5+
VERSION = "0.1.2"
66

77
# Define URLs for each architecture. This is used for the Linux build only; Windows and Darwin are not supported yet.
88
# When we do those, we can make this parameterized.
@@ -14,7 +14,7 @@ http_archive(
1414
"LICENSE": "//deps/compile_policy:LICENSE",
1515
"LICENSE-3rdparty.csv": "//deps/compile_policy:LICENSE-3rdparty.csv",
1616
},
17-
sha256 = "ce7ada4e91d3b57849cc602fbb541f6b48988735a6d95ac4044dfab28272dd56",
17+
sha256 = "f32c245da1e0152e981ea6a3531b244bbab42c02666ec9bf37edc52204a96355",
1818
urls = [
1919
"https://github.com/DataDog/dd-policy-engine/releases/download/v{version}/dd-compile-policy-linux-amd64.tar.gz".format(
2020
version = VERSION,
@@ -29,10 +29,25 @@ http_archive(
2929
"LICENSE": "//deps/compile_policy:LICENSE",
3030
"LICENSE-3rdparty.csv": "//deps/compile_policy:LICENSE-3rdparty.csv",
3131
},
32-
sha256 = "fe3c4470ca33030c4aa002dfb283aef672a8eff37b1660f9bcff67917e2fc64f",
32+
sha256 = "1e9447733ac2a623d59b657b689811695738e040a7dd7be22a368c07cbd52842",
3333
urls = [
3434
"https://github.com/DataDog/dd-policy-engine/releases/download/v{version}/dd-compile-policy-linux-arm64.tar.gz".format(
3535
version = VERSION,
3636
),
3737
],
3838
)
39+
40+
http_archive(
41+
name = "compile_policy_windows_x86_64",
42+
files = {
43+
"BUILD.bazel": "//deps/compile_policy:overlay.BUILD.bazel",
44+
"LICENSE": "//deps/compile_policy:LICENSE",
45+
"LICENSE-3rdparty.csv": "//deps/compile_policy:LICENSE-3rdparty.csv",
46+
},
47+
sha256 = "39cabb8dc85e29837be223d389b6c00b715513e7ffc6fa8a0d69db03fdc66f2e",
48+
urls = [
49+
"https://github.com/DataDog/dd-policy-engine/releases/download/v{version}/dd-compile-policy-windows-x64.zip".format(
50+
version = VERSION,
51+
),
52+
],
53+
)

0 commit comments

Comments
 (0)