build/scripts/Packaging.fs (187 lines of code) (raw):

// Licensed to Elasticsearch B.V under one or more agreements. // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information module Packaging open System open System.IO open System.IO.Compression open System.Net.Http open Argu open BuildInformation open CommandLine open Octokit open Proc.Fs /// /// This module is hard to read, with so many file manipulations it's kinda hard to avoid. /// /// Packaging ultimately ensures artifacts are available under .artifacts/elastic-distribution/{version} /// /// - Download release assets from: github.com/open-telemetry/opentelemetry-dotnet-instrumentation/{version} /// - cached under .artifacts/otel-distribution/{version}, only downloaded again if an asset is missing /// - Staging copies are created in the `otel-distribution`, renaming `opentelemetry-` to `staging-` /// - these copies are used to do mutations and removed afterward /// - Zip mutations /// - Ensure we package our plugin dll and related files under net/netfx inside the staging zips. /// - Ensure we rename `instrument.sh` to `_instrument.sh` inside the scripts /// - Include our wrapping `instrument.sh` as the new main entry script. /// - Move mutated staging zips `.artifacts/elastic-distribution/{version}` renaming `stage-` to `elastic-`. /// let private otelAutoVersion = Software.OpenTelemetryAutoInstrumentationVersion; let private downloadFolder = Path.Combine(".artifacts", "otel-distribution", otelAutoVersion.AsString) |> Directory.CreateDirectory let private distroFolder = Path.Combine(".artifacts", "elastic-distribution") |> Directory.CreateDirectory let private fileInfo (directory: DirectoryInfo) file = Path.Combine(directory.FullName, file) |> FileInfo let private downloadFileInfo (file: string) = fileInfo downloadFolder file let private downloadAsset (asset: ReleaseAsset) = fileInfo downloadFolder asset.Name let private _stage (s: string) = s.Replace("opentelemetry", "stage").Replace("otel", "stage").Replace("OpenTelemetry", "stage") let private stageFile (file: FileInfo) = fileInfo downloadFolder (file.Name |> _stage) let private stageAsset (asset: ReleaseAsset) = fileInfo downloadFolder (asset.Name |> _stage) let private _distro (s: string) = (s |> _stage).Replace("stage", "elastic") let private distroFile (file: FileInfo) = fileInfo distroFolder (file.Name |> _distro) let private distroAsset (asset: ReleaseAsset) = fileInfo distroFolder (asset.Name |> _distro) let pluginFiles tfm = ["dll"; "pdb"; "xml"] |> List.map(fun e -> $"Elastic.OpenTelemetry.AutoInstrumentation.%s{e}") |> List.map(fun f -> Path.Combine(".artifacts", "bin", "Elastic.OpenTelemetry.AutoInstrumentation", $"release_%s{tfm}", "", f)) |> List.map(fun f -> FileInfo(f)) /// downloads the artifacts if they don't already exist locally let downloadArtifacts (_:ParseResults<Build>) = let client = GitHubClient(ProductHeaderValue "Elastic.OpenTelemetry") let token = Environment.GetEnvironmentVariable "GITHUB_TOKEN" if not(String.IsNullOrWhiteSpace(token)) then printfn "using GITHUB_TOKEN"; let tokenAuth = Credentials(token); client.Credentials <- tokenAuth let assets = async { let! release = client.Repository.Release.Get("open-telemetry", "opentelemetry-dotnet-instrumentation", $"v{otelAutoVersion.AsString}") |> Async.AwaitTask; Console.WriteLine $"Release %s{release.Name} has %i{release.Assets.Count} assets"; return release.Assets |> Seq.map (fun asset -> asset, downloadAsset asset) |> Seq.toList } |> Async.RunSynchronously async { use httpClient = new HttpClient() assets |> Seq.filter (fun (_, f) -> not f.Exists) |> Seq.iter (fun (asset, f) -> async { Console.WriteLine $"Retrieving {asset.Name}"; let! fileData = httpClient.GetByteArrayAsync asset.BrowserDownloadUrl |> Async.AwaitTask Console.WriteLine $"Saving %i{fileData.Length} bytes to {f.FullName}" File.WriteAllBytes(f.FullName, fileData) f.Refresh() } |> Async.RunSynchronously ) } |> Async.RunSynchronously assets let injectPluginFiles (asset: ReleaseAsset) (stagedZip: FileInfo) tfm target = use zipArchive = ZipFile.Open(stagedZip.FullName, ZipArchiveMode.Update) pluginFiles tfm |> List.iter(fun f -> printfn $"Staging zip: %s{stagedZip.Name}, Adding: %s{f.Name} (%s{tfm}) to %s{target}" zipArchive.CreateEntryFromFile(f.FullName, Path.Combine(target, f.Name)) |> ignore ) let injectPluginScripts (stagedZip: FileInfo) (otelScript: FileInfo) (script: FileInfo) = use zipArchive = ZipFile.Open(stagedZip.FullName, ZipArchiveMode.Update) printfn $"Staging : %s{stagedZip.Name}, Adding: %s{otelScript.Name}" zipArchive.CreateEntryFromFile(otelScript.FullName, otelScript.Name) |> ignore printfn $"Staging : %s{stagedZip.Name}, Adding: %s{script.FullName}" let entry = zipArchive.Entries |> Seq.find(fun e -> e.Name = "instrument.sh") entry.Delete() zipArchive.CreateEntryFromFile(script.FullName, script.Name) |> ignore let stageInstrumentationScript (stagedZips:List<ReleaseAsset * FileInfo>) = let openTelemetryVersion = downloadFileInfo "opentelemetry-instrument.sh" let stageVersion = downloadFileInfo "_instrument.sh" let stageScript = match stageVersion.Exists with | true -> stageVersion | _ -> let instrumentShZip = stagedZips |> List.map(fun (_, p) -> p) |> List.find (fun p -> not <| p.Name.EndsWith "-windows.zip") use zipArchive = ZipFile.Open(instrumentShZip.FullName, ZipArchiveMode.Read) let shArchive = zipArchive.Entries |> Seq.find(fun e -> e.Name = "instrument.sh") shArchive.ExtractToFile openTelemetryVersion.FullName openTelemetryVersion.Refresh() openTelemetryVersion.MoveTo stageVersion.FullName stageVersion.Refresh() stageVersion let wrapperScript = downloadFileInfo "instrument.sh" let copyScript = Path.Combine("src", "Elastic.OpenTelemetry.AutoInstrumentation", "instrument.sh") |> FileInfo let script = copyScript.CopyTo(wrapperScript.FullName, true) (stageScript, script) let stageInstallationBashScript () = let installScript = downloadFileInfo "otel-dotnet-auto-install.sh" let staged = installScript.CopyTo ((stageFile installScript).FullName, true) let contents = (File.ReadAllText staged.FullName) .Replace("/open-telemetry/opentelemetry-dotnet-instrumentation/", "/elastic/elastic-otel-dotnet/") .Replace("opentelemetry-dotnet-instrumentation", "elastic-dotnet-instrumentation") .Replace("v" + Software.OpenTelemetryAutoInstrumentationVersion.AsString, Software.Version.NormalizeToShorter()) let elasticInstall = distroFile installScript File.WriteAllText(elasticInstall.FullName, contents) if not (OperatingSystem.IsWindows()) then let permissions = UnixFileMode.UserRead ||| UnixFileMode.UserWrite ||| UnixFileMode.UserExecute ||| UnixFileMode.GroupRead ||| UnixFileMode.GroupWrite ||| UnixFileMode.GroupExecute ||| UnixFileMode.OtherRead ||| UnixFileMode.OtherWrite ||| UnixFileMode.OtherExecute File.SetUnixFileMode(elasticInstall.FullName, permissions); let stageInstallationPsScript () = let installScript = downloadFileInfo "OpenTelemetry.DotNet.Auto.psm1" let staged = installScript.CopyTo ((stageFile installScript).FullName, true) let envMarker = "\"OTEL_DOTNET_AUTO_HOME\" = $OTEL_DOTNET_AUTO_HOME;" let contents = (File.ReadAllText staged.FullName) .Replace("/open-telemetry/opentelemetry-dotnet-instrumentation/", "/elastic/elastic-otel-dotnet/") .Replace("opentelemetry-dotnet-instrumentation", "elastic-dotnet-instrumentation") .Replace("OpenTelemetry .NET Automatic Instrumentation", "Elastic Distribution for OpenTelemetry .NET") .Replace("OpenTelemetry.Dotnet.Auto", "Elastic.OpenTelemetry.DotNet") .Replace(envMarker, [ envMarker "#Elastic Distribution" "\"OTEL_DOTNET_AUTO_PLUGINS\" = \"Elastic.OpenTelemetry.AutoInstrumentationPlugin, Elastic.OpenTelemetry.AutoInstrumentation\"" ] |> String.concat "\r\n " ) .Replace("v" + Software.OpenTelemetryAutoInstrumentationVersion.AsString, Software.Version.NormalizeToShorter()) let elasticInstall = distroFile installScript //ensure we write our new module name File.WriteAllText(elasticInstall.FullName.Replace("elastic.DotNet.Auto", "Elastic.OpenTelemetry.DotNet"), contents); /// moves artifacts from open-distribution to elastic-distribution and renames them to `staged-dotnet-instrumentation*`. /// staged meaning we haven't injected our opentelemetry dll into the zip yet, let stageArtifacts (assets:List<ReleaseAsset * FileInfo>) = let stagedZips = assets |> List.filter(fun (a, _) -> a.Name.EndsWith ".zip") |> List.filter(fun (a, _) -> not <| a.Name.EndsWith "nuget-packages.zip") |> List.map(fun (z, f) -> let stage = stageAsset z z, f.CopyTo(stage.FullName, true) ) let otelScript, wrapperScript = stageInstrumentationScript stagedZips printfn $"Staged (%s{wrapperScript.Name}) calling into (%s{otelScript.Name} for repackaging)" stagedZips |> List.iter (fun (asset, path) -> injectPluginFiles asset path "netstandard2.1" "net" if asset.Name.EndsWith "-windows.zip" then injectPluginFiles asset path "net462" "netfx" injectPluginScripts path otelScript wrapperScript let distro = distroAsset asset path.MoveTo(distro.FullName, true) distro.Refresh() printfn $"Moved staging to: %s{distro.FullName}" ) stagedZips let redistribute (arguments:ParseResults<Build>) = exec { run "dotnet" "build" "src/Elastic.OpenTelemetry.AutoInstrumentation/Elastic.OpenTelemetry.AutoInstrumentation.csproj" "-f" "netstandard2.1" "-c" "release" } exec { run "dotnet" "build" "src/Elastic.OpenTelemetry.AutoInstrumentation/Elastic.OpenTelemetry.AutoInstrumentation.csproj" "-f" "net462" "-c" "release" } let assets = downloadArtifacts arguments printfn "" assets |> List.iter (fun (asset, path) -> printfn "Asset: %s" asset.Name ) stageInstallationBashScript() stageInstallationPsScript() let staged = stageArtifacts assets ignore()