Skip to content

Commit 42ad0f0

Browse files
authored
Include telemetry_forwarder.exe in the Windows SSI OCI images (#7342)
## Summary of changes Downloads and verifies the telemetry_forwarder.exe binary and includes it in the OCI image ## Reason for change We want to ship the binary side-by-side with the fleet installer. At some point we'll configure a proper release process for the `telemetry_forwarder.exe` dll, but in the mean time we're simply pulling from a public location for simplicity. ## Implementation details - Download the telemetry forwarder from our Azure blob storage - Sign the binary as part of our normal GitLab build - Include the forwarder in our GitLab build output - Set the `DD_TELEMETRY_FORWARDER_PATH` when installing the tracer ## Test coverage Less than I'd like, but ultimately this is a non-critical component, that is tested independently. It would be nice to have some testing that the telemetry is forwarded correctly, but ultimately I think this should be added in the system-tests/onboarding tests repo. I included the telemetry forwarder in our smoke tests so we should at least get warnings/errors there if there's anything egregious. ## Other details Stacked on - #7341
1 parent 80a9cd2 commit 42ad0f0

File tree

9 files changed

+193
-5
lines changed

9 files changed

+193
-5
lines changed

.azure-pipelines/ultimate-pipeline.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,8 +1025,8 @@ stages:
10251025
artifact: dd-dotnet-win-x64
10261026
path: $(monitoringHome)/win-x64
10271027

1028-
- script: tracer\build.cmd PackageTracerHome PublishFleetInstaller
1029-
displayName: Build MSI and Tracer home
1028+
- script: tracer\build.cmd PackageTracerHome PublishFleetInstaller DownloadWinSsiTelemetryForwarder
1029+
displayName: Build MSI, Tracer home, FleetInstaller, and TelemetryForwarder
10301030
retryCountOnTaskFailure: 1
10311031

10321032
- publish: $(artifacts)/windows-tracer-home.zip
@@ -1045,6 +1045,10 @@ stages:
10451045
displayName: Publish fleet installer
10461046
artifact: fleet-installer
10471047

1048+
- publish: $(artifacts)/telemetry_forwarder.exe
1049+
displayName: Publish telemetry-forwarder
1050+
artifact: fleet-installer-telemetry-forwarder
1051+
10481052
- stage: build_dd_dotnet_windows
10491053
dependsOn: [merge_commit_id]
10501054
variables:
@@ -6048,6 +6052,11 @@ stages:
60486052
artifact: fleet-installer
60496053
path: $(smokeTestAppDir)/artifacts/installer
60506054

6055+
- template: steps/download-artifact.yml
6056+
parameters:
6057+
artifact: fleet-installer-telemetry-forwarder
6058+
path: $(smokeTestAppDir)/artifacts/installer
6059+
60516060
- powershell: |
60526061
ls $(smokeTestAppDir)/artifacts
60536062
ls $(smokeTestAppDir)/artifacts/installer

.gitlab-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ build:
3737
-e SIGN_WINDOWS=true `
3838
-e NUGET_CERT_REVOCATION_MODE=offline `
3939
registry.ddbuild.io/images/mirror/datadog/dd-trace-dotnet-docker-build:latest `
40-
Info Clean BuildTracerHome BuildProfilerHome BuildNativeLoader BuildDdDotnet PublishFleetInstaller PackageTracerHome ZipSymbols SignDlls SignMsi
40+
Info Clean BuildTracerHome BuildProfilerHome BuildNativeLoader BuildDdDotnet PublishFleetInstaller PackageTracerHome ZipSymbols SignDlls SignMsi DownloadWinSsiTelemetryForwarder
4141
- mkdir artifacts-out
4242
- xcopy /e/s build-out\${CI_JOB_ID}\*.* artifacts-out
4343
- remove-item -recurse -force build-out\${CI_JOB_ID}

.gitlab/prepare-oci-package.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ elif [ "$OS" == "windows" ]; then
5656
mkdir -p sources/installer
5757
unzip ../artifacts/windows/fleet-installer.zip -d sources/installer/
5858

59+
# Copy the telemetry forwarder in too
60+
cp ../artifacts/windows/telemetry_forwarder.exe sources/installer/
61+
5962
fi
6063

6164
echo -n $VERSION > sources/version

tracer/build/_build/Build.Gitlab.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5+
using System.Net;
6+
using System.Net.Http;
7+
using System.Security.Cryptography;
58
using System.Security.Cryptography.X509Certificates;
69
using System.Threading.Tasks;
710
using Amazon.SimpleSystemsManagement;
@@ -19,6 +22,32 @@
1922

2023
partial class Build
2124
{
25+
Target DownloadWinSsiTelemetryForwarder => _ => _
26+
.Description("Downloads the telemetry forwarder executable used by SSI ")
27+
.Unlisted()
28+
.Requires(() => IsWin)
29+
.Before(SignDlls)
30+
.Executes(async () =>
31+
{
32+
// Download the forwarder from Azure for now.
33+
// We will likely change this in the future, but it'll do for now.
34+
const string url = "https://apmdotnetci.blob.core.windows.net/apm-datadog-win-ssi-telemetry-forwarder/c83ee9ad2f93c7314779051662e2e00086a213e0/telemetry_forwarder.exe";
35+
const string expectedHash = "0B192C1901C670FC9A55464AFDF39774AB7CD0D667ECFB37BC22C27184B49C37D4658383E021F792A2F0C7024E1091F35C3CAD046EC68871FAEEE3C98A40163A";
36+
37+
var tempFile = await DownloadFile(url);
38+
var actualHash = GetSha512Hash(tempFile);
39+
if (!string.Equals(expectedHash, actualHash, StringComparison.Ordinal))
40+
{
41+
throw new Exception($"Downloaded file did not have expected hash. Expected hash {expectedHash}, actual hash {actualHash}");
42+
}
43+
44+
Logger.Information("Hash verified: '{Hash}'", expectedHash);
45+
46+
// Move to expected location
47+
var output = ArtifactsDirectory / "telemetry_forwarder.exe";
48+
FileSystemTasks.CopyFile(tempFile, output, FileExistsPolicy.Overwrite);
49+
});
50+
2251
Target SignDlls => _ => _
2352
.Description("Sign the dlls produced by building the Tracer, Profiler, and Monitoring home directory, as well as the dd-dotnet exes")
2453
.Unlisted()

tracer/build/_build/Build.Utilities.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Net.Http;
88
using System.Reflection;
99
using System.Runtime.InteropServices;
10+
using System.Security.Cryptography;
1011
using System.Text.Json;
1112
using System.Threading.Tasks;
1213
using Amazon.SimpleSystemsManagement.Model;
@@ -612,6 +613,76 @@ string DiffToString(Diff diff)
612613
var lines = diff.text.TrimEnd(trimChar: '\n').Split(Environment.NewLine);
613614
return string.Join(Environment.NewLine, lines.Select(l => symbol + l));
614615
}
616+
}
617+
618+
/// <summary>
619+
/// Tries to download a file from the provided url, with a retry, and saves it at a temp path
620+
/// </summary>
621+
/// <param name="url">The URL to download from</param>
622+
/// <returns>The temporary path where the file has been saved</returns>
623+
/// <exception cref="Exception"></exception>
624+
private static async Task<string> DownloadFile(string url)
625+
{
626+
using var client = new HttpClient();
627+
var attemptsRemaining = 3;
628+
var defaultDelay = TimeSpan.FromSeconds(2);
629+
630+
while (attemptsRemaining > 0)
631+
{
632+
var retryDelay = defaultDelay;
633+
try
634+
{
635+
Logger.Information("Downloading from {Url}", url);
636+
using var response = await client.GetAsync(url);
637+
var outputPath = Path.GetTempFileName();
638+
639+
if (response.IsSuccessStatusCode)
640+
{
641+
Logger.Information("Saving file to {Path}", outputPath);
642+
await using var fs = new FileStream(outputPath, FileMode.Create, FileAccess.Write);
643+
await response.Content.CopyToAsync(fs);
644+
return outputPath;
645+
}
646+
647+
Logger.Warning("Failed to download file from {Url}, {StatusCode}: {Body}", url, response.StatusCode, await response.Content.ReadAsStringAsync());
648+
649+
if (response.StatusCode == HttpStatusCode.TooManyRequests
650+
&& response.Headers.TryGetValues("Retry-After", out var values)
651+
&& values.FirstOrDefault() is {} retryAfter)
652+
{
653+
if (int.TryParse(retryAfter, out var seconds))
654+
{
655+
retryDelay = TimeSpan.FromSeconds(seconds);
656+
}
657+
else if (DateTimeOffset.TryParse(retryAfter, out var retryDate))
658+
{
659+
var delta = retryDate - DateTimeOffset.UtcNow;
660+
retryDelay = delta > TimeSpan.Zero ? delta : retryDelay;
661+
}
662+
}
663+
}
664+
catch (Exception ex)
665+
{
666+
Logger.Warning(ex, "Error downloading file from {Url}", url);
667+
}
668+
669+
attemptsRemaining--;
670+
if (attemptsRemaining > 0)
671+
{
672+
Logger.Debug("Waiting {RetryDelayTotalSeconds} seconds before retry...", retryDelay.TotalSeconds);
673+
await Task.Delay(retryDelay);
674+
}
675+
}
676+
677+
throw new Exception("Failed to download telemetry forwarder");
678+
}
679+
680+
static string GetSha512Hash(string filePath)
681+
{
682+
using var sha512 = SHA512.Create();
683+
using var stream = File.OpenRead(filePath);
615684

685+
var hashBytes = sha512.ComputeHash(stream);
686+
return Convert.ToHexString(hashBytes);
616687
}
617688
}

tracer/build/_build/docker/smoke.windows.fleet-installer.dockerfile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ COPY --from=builder /src/artifacts /install
2929

3030
RUN mkdir /logs; \
3131
mkdir /monitoring-home; \
32+
mkdir /installer; \
3233
cd /install; \
3334
Expand-Archive 'c:\install\windows-tracer-home.zip' -DestinationPath 'c:\monitoring-home\'; \
34-
c:\install\installer\Datadog.FleetInstaller.exe install-version --home-path c:\monitoring-home; \
35-
c:\install\installer\Datadog.FleetInstaller.exe enable-global-instrumentation --home-path c:\monitoring-home; \
35+
Copy-Item c:\install\installer\*.* -Destination 'c:\installer\'; \
36+
c:\installer\Datadog.FleetInstaller.exe install-version --home-path c:\monitoring-home; \
37+
c:\installer\Datadog.FleetInstaller.exe enable-global-instrumentation --home-path c:\monitoring-home; \
3638
cd /app;
3739

3840
# Set the additional env vars
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// <copyright file="PathHelper.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
using System.IO;
7+
8+
namespace Datadog.FleetInstaller;
9+
10+
internal static class PathHelper
11+
{
12+
private const string ForwarderFileName = "telemetry_forwarder.exe";
13+
14+
public static string GetTelemetryForwarderPath(string homePath)
15+
{
16+
if (string.IsNullOrEmpty(homePath)
17+
|| !Path.IsPathRooted(homePath) // can't use relative paths
18+
|| Path.GetDirectoryName(homePath) is not { } directory)
19+
{
20+
// I guess we failed for some reason, so we can't calculate the path to the telemetry forwarder
21+
return string.Empty;
22+
}
23+
24+
// Ok, now we have the path
25+
// Should we care about long paths?
26+
return Path.Combine(Path.GetFullPath(directory), "installer", ForwarderFileName);
27+
}
28+
}

tracer/src/Datadog.FleetInstaller/TracerValues.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public TracerValues(string tracerHomeDirectory)
1616
TracerHomeDirectory = tracerHomeDirectory;
1717
NativeLoaderX86Path = Path.Combine(tracerHomeDirectory, "win-x86", "Datadog.Trace.ClrProfiler.Native.dll");
1818
NativeLoaderX64Path = Path.Combine(tracerHomeDirectory, "win-x64", "Datadog.Trace.ClrProfiler.Native.dll");
19+
TelemetryForwarderPath = PathHelper.GetTelemetryForwarderPath(tracerHomeDirectory);
1920
IisRequiredEnvVariables = new(new Dictionary<string, string>
2021
{
2122
{ "COR_PROFILER", "{846F5F1C-F9AE-4B07-969E-05C26BC060D8}" },
@@ -28,6 +29,7 @@ public TracerValues(string tracerHomeDirectory)
2829
{ "COR_ENABLE_PROFILING", "1" },
2930
{ "CORECLR_ENABLE_PROFILING", "1" },
3031
{ "DD_INJECTION_ENABLED", "tracer" },
32+
{ "DD_TELEMETRY_FORWARDER_PATH", TelemetryForwarderPath },
3133
{ Defaults.InstrumentationInstallTypeKey, Defaults.InstrumentationInstallTypeValue },
3234
});
3335

@@ -47,6 +49,7 @@ public TracerValues(string tracerHomeDirectory)
4749
// { "COR_ENABLE_PROFILING", "1" },
4850
{ "CORECLR_ENABLE_PROFILING", "1" },
4951
{ "DD_TRACING_ENABLED", "tracing" },
52+
{ "DD_TELEMETRY_FORWARDER_PATH", TelemetryForwarderPath },
5053
{ Defaults.InstrumentationInstallTypeKey, Defaults.InstrumentationInstallTypeValue },
5154
});
5255

@@ -63,6 +66,8 @@ public TracerValues(string tracerHomeDirectory)
6366

6467
public string NativeLoaderX64Path { get; }
6568

69+
public string TelemetryForwarderPath { get; }
70+
6671
public ReadOnlyDictionary<string, string> IisRequiredEnvVariables { get; }
6772

6873
public ReadOnlyDictionary<string, string> GlobalRequiredEnvVariables { get; }
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// <copyright file="PathHelperTests.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#if NETFRAMEWORK
7+
8+
using Datadog.FleetInstaller;
9+
using FluentAssertions;
10+
using Xunit;
11+
12+
namespace Datadog.Trace.IntegrationTests.FleetInstaller;
13+
14+
public class PathHelperTests
15+
{
16+
[Theory]
17+
[InlineData(null)]
18+
[InlineData("")]
19+
[InlineData("./installer")] // relative
20+
[InlineData("somewhere/../installer")] // relative
21+
[InlineData(@"c:\")] // root (no parent)
22+
public void ForInvalidPaths_ReturnsEmptyString(string homePath)
23+
{
24+
var actual = PathHelper.GetTelemetryForwarderPath(homePath);
25+
actual.Should().BeEmpty();
26+
}
27+
28+
[Theory]
29+
[InlineData(@"c:\some\path\to\aplace", @"c:\some\path\to\installer\telemetry_forwarder.exe")]
30+
[InlineData(@"c:\some\path\..\to\aplace", @"c:\some\to\installer\telemetry_forwarder.exe")]
31+
[InlineData(@"D:\some\path\to\aplace.exe", @"D:\some\path\to\installer\telemetry_forwarder.exe")] // a bit questionable, but meh
32+
[InlineData(@"c:\library", @"c:\installer\telemetry_forwarder.exe")]
33+
[InlineData(@"f:\other-library", @"f:\installer\telemetry_forwarder.exe")]
34+
public void ForValidPaths_ReturnsExpectedPath(string homePath, string expected)
35+
{
36+
var actual = PathHelper.GetTelemetryForwarderPath(homePath);
37+
actual.Should().Be(expected);
38+
}
39+
}
40+
41+
#endif

0 commit comments

Comments
 (0)