├── .editorconfig ├── .github ├── add-license-headers.sh ├── check-license-headers.sh ├── license-header.txt └── workflows │ ├── ci.yml │ └── license.yml ├── .gitignore ├── Directory.Build.props ├── Elastic.Abstractions.sln ├── License.txt ├── README.md ├── build.bat ├── build.sh ├── build ├── keys │ ├── keypair.snk │ └── public.snk └── scripts │ ├── CommandLine.fs │ ├── Paths.fs │ ├── Program.fs │ ├── Targets.fs │ └── scripts.fsproj ├── dotnet-tools.json ├── examples ├── Elastic.Ephemeral.Example │ ├── Elastic.Ephemeral.Example.csproj │ └── Program.cs ├── Elastic.Managed.Example │ ├── Elastic.Managed.Example.csproj │ └── Program.cs ├── Elastic.Xunit.ExampleComplex │ ├── ClusterTestClassBase.cs │ ├── Clusters.cs │ ├── Elastic.Xunit.ExampleComplex.csproj │ ├── Setup.cs │ ├── TestWithoutClusterFixture.cs │ └── Tests.cs ├── Elastic.Xunit.ExampleMinimal │ ├── Elastic.Xunit.ExampleMinimal.csproj │ └── ExampleTest.cs └── ScratchPad │ ├── Program.cs │ ├── ScratchPad.csproj │ └── ValidateCombinations.cs ├── global.json ├── nuget-icon.png └── src ├── Directory.Build.props ├── Elastic.Elasticsearch.Ephemeral ├── ClusterAuthentication.cs ├── ClusterFeatures.cs ├── Elastic.Elasticsearch.Ephemeral.csproj ├── EphemeralCluster.cs ├── EphemeralClusterComposer.cs ├── EphemeralClusterConfiguration.cs ├── EphemeralFileSystem.cs ├── IEphemeralCluster.cs ├── Plugins │ └── ElasticsearchPlugins.cs ├── README.md ├── SecurityRealms.cs └── Tasks │ ├── AfterNodeStoppedTasks │ └── CleanUpDirectoriesAfterNodeStopped.cs │ ├── BeforeStartNodeTasks │ ├── CacheElasticsearchInstallation.cs │ ├── CreateEphemeralDirectory.cs │ ├── PrintYamlContents.cs │ └── XPack │ │ ├── EnsureSecurityRealms.cs │ │ ├── EnsureSecurityRolesFileExists.cs │ │ ├── EnsureSecurityUsersInDefaultRealmAreAdded.cs │ │ └── GenerateCertificatesTask.cs │ ├── IClusterComposeTask.cs │ ├── InstallationTasks │ ├── CopyCachedEsInstallation.cs │ ├── CreateLocalApplicationDirectory.cs │ ├── DownloadElasticsearchVersion.cs │ ├── EnsureElasticsearchBatWorksAcrossDrives.cs │ ├── EnsureJavaHomeEnvironmentVariableIsSet.cs │ ├── InstallPlugins.cs │ ├── PrintConfiguration.cs │ ├── SetElasticsearchBundledJdkJavaHome.cs │ ├── UnzipElasticsearch.cs │ └── XPack │ │ ├── EnsureXPackEnvBinaryExists.cs │ │ └── PathXPackInBatFile.cs │ └── ValidationTasks │ ├── PostLicenseTask.cs │ ├── ValidateClusterStateTask.cs │ ├── ValidateLicenseTask.cs │ ├── ValidatePluginsTask.cs │ └── ValidateRunningVersion.cs ├── Elastic.Elasticsearch.Managed ├── ClusterBase.cs ├── Configuration │ ├── ClusterConfiguration.cs │ ├── NodeConfiguration.cs │ ├── NodeSetting.cs │ └── NodeSettings.cs ├── ConsoleWriters │ ├── ConsoleLineWriter.cs │ ├── ExceptionLineParser.cs │ ├── IConsoleLineWriter.cs │ ├── LineHighlightWriter.cs │ ├── LineOutParser.cs │ └── NoopConsoleLineWriter.cs ├── DetectedProxySoftware.cs ├── Elastic.Elasticsearch.Managed.csproj ├── ElasticsearchCleanExitException.cs ├── ElasticsearchCluster.cs ├── ElasticsearchNode.cs ├── FileSystem │ ├── INodeFileSystem.cs │ └── NodeFileSystem.cs └── README.md ├── Elastic.Elasticsearch.Xunit ├── Elastic.Elasticsearch.Xunit.csproj ├── ElasticXunitConfigurationAttribute.cs ├── ElasticXunitRunOptions.cs ├── ElasticXunitRunner.cs ├── PrintXunitAfterStartedTask.cs ├── README.md ├── Sdk │ ├── ElasticTestFramework.cs │ └── TestAssemblyRunner.cs ├── XunitClusterBase.cs ├── XunitClusterConfiguration.cs ├── XunitClusterExtensions.cs ├── XunitPlumbing │ ├── ElasticTestCaseDiscoverer.cs │ ├── IClusterFixture.cs │ ├── IntegrationTestDiscoverer.cs │ ├── SkipVersionAttribute.cs │ ├── SkippingTestCase.cs │ └── UnitTestDiscoverer.cs ├── ide-integration.png └── output.gif └── Elastic.Stack.ArtifactsApi ├── Artifact.cs ├── ArtifactBuildState.cs ├── Elastic.Stack.ArtifactsApi.csproj ├── ElasticVersion.cs ├── Platform └── OsMonikers.cs ├── Products ├── ElasticsearchPlugin.cs ├── Product.cs └── SubProduct.cs ├── README.md └── Resolvers ├── ApiResolver.cs ├── ReleasedVersionResolver.cs ├── SnapshotApiResolver.cs └── StagingVersionResolver.cs /.github/add-license-headers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | script_path=$(dirname $(realpath -s $0))/../ 3 | 4 | function add_license () { 5 | (find "$script_path" -name $1 | grep -v "/bin/" | grep -v "/obj/" )|while read fname; do 6 | line=$(sed -n '2p;3q' "$fname") 7 | if ! [[ "$line" == " * Licensed to Elasticsearch B.V. under one or more contributor" ]] ; then 8 | # awk joins the header with the existing file, inserting a newline between them 9 | awk '(NR>1 && FNR==1){print ""}1' "${script_path}.github/license-header.txt" "$fname" > "${fname}.new" 10 | mv "${fname}.new" "$fname" 11 | fi 12 | done 13 | } 14 | 15 | add_license "*.cs" -------------------------------------------------------------------------------- /.github/check-license-headers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Check that source code files in this repo have the appropriate license 4 | # header. 5 | 6 | if [ "$TRACE" != "" ]; then 7 | export PS4='${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 8 | set -o xtrace 9 | fi 10 | set -o errexit 11 | set -o pipefail 12 | 13 | TOP=$(cd "$(dirname "$0")/.." >/dev/null && pwd) 14 | NLINES=$(wc -l .github/license-header.txt | awk '{print $1}') 15 | 16 | function check_license_header { 17 | local f 18 | f=$1 19 | if ! diff -a --strip-trailing-cr .github/license-header.txt <(head -$NLINES "$f") >/dev/null; then 20 | echo "check-license-headers: error: '$f' does not have required license header, see 'diff -u .github/license-header.txt <(head -$NLINES $f)'" 21 | return 1 22 | else 23 | return 0 24 | fi 25 | } 26 | 27 | cd "$TOP" 28 | nErrors=0 29 | for f in $(git ls-files | grep '\.cs$'); do 30 | if ! check_license_header $f; then 31 | nErrors=$((nErrors+1)) 32 | fi 33 | done 34 | 35 | for f in $(git ls-files | grep '\.fs$'); do 36 | if ! check_license_header $f; then 37 | nErrors=$((nErrors+1)) 38 | fi 39 | done 40 | 41 | if [[ $nErrors -eq 0 ]]; then 42 | exit 0 43 | else 44 | exit 1 45 | fi -------------------------------------------------------------------------------- /.github/license-header.txt: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Always be deploying 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - 'README.md' 7 | - '.editorconfig' 8 | push: 9 | paths-ignore: 10 | - 'README.md' 11 | - '.editorconfig' 12 | branches: 13 | - master 14 | tags: 15 | - "*.*.*" 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | with: 23 | fetch-depth: 1 24 | - run: | 25 | git fetch --prune --unshallow --tags 26 | echo exit code $? 27 | git tag --list 28 | 29 | # Install .NET version as mandated by global.json 30 | - uses: actions/setup-dotnet@v4.1.0 31 | with: 32 | global-json-file: global.json 33 | dotnet-version: | 34 | 6.x 35 | env: 36 | NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 37 | 38 | - run: ./build.sh test 39 | name: Build 40 | - run: ./build.sh generatepackages -s true 41 | name: Generate local nuget packages 42 | - run: ./build.sh validatepackages -s true 43 | name: "validate *.npkg files that were created" 44 | - run: ./build.sh generateapichanges -s true 45 | name: "Inspect public API changes" 46 | 47 | - name: publish canary packages github package repository 48 | if: github.event_name == 'push' && startswith(github.ref, 'refs/heads') 49 | shell: bash 50 | timeout-minutes: 10 51 | continue-on-error: true 52 | run: | 53 | until dotnet nuget push 'build/output/*.nupkg' -k ${{secrets.GITHUB_TOKEN}} --skip-duplicate --no-symbols -s https://nuget.pkg.github.com/elastic/index.json; do echo "Retrying"; sleep 1; done; 54 | 55 | # Github packages requires authentication, this is likely going away in the future so for now we publish to feedz.io 56 | - run: dotnet nuget push 'build/output/*.nupkg' -k ${{secrets.FEEDZ_IO_API_KEY}} -s https://f.feedz.io/elastic/all/nuget/index.json --skip-duplicate --no-symbols 57 | name: publish canary packages to feedz.io 58 | if: github.event_name == 'push' && startswith(github.ref, 'refs/heads') 59 | 60 | - run: ./build.sh generatereleasenotes -s true 61 | name: Generate release notes for tag 62 | if: github.event_name == 'push' && startswith(github.ref, 'refs/tags') 63 | - run: ./build.sh createreleaseongithub -s true --token ${{secrets.GITHUB_TOKEN}} 64 | if: github.event_name == 'push' && startswith(github.ref, 'refs/tags') 65 | name: Create or update release for tag on github 66 | 67 | - run: dotnet nuget push 'build/output/*.nupkg' -k ${{secrets.NUGET_ORG_API_KEY}} -s https://api.nuget.org/v3/index.json --skip-duplicate --no-symbols 68 | name: release to nuget.org 69 | if: github.event_name == 'push' && startswith(github.ref, 'refs/tags') 70 | -------------------------------------------------------------------------------- /.github/workflows/license.yml: -------------------------------------------------------------------------------- 1 | name: License headers 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Check license headers 14 | run: | 15 | ./.github/check-license-headers.sh -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.userprefs 2 | *.local.xml 3 | *.sln.docstates 4 | *.obj 5 | *.swp 6 | *.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.tss 12 | *.vspscc 13 | *_i.c 14 | *_p.c 15 | *.ncb 16 | *.suo 17 | *.tlb 18 | *.tlh 19 | *.bak 20 | *.cache 21 | *.ilk 22 | *.log 23 | *.nupkg 24 | *.ncrunchsolution 25 | [Bb]in 26 | [Dd]ebug/ 27 | test-results 28 | test-results/* 29 | *.lib 30 | *.sbr 31 | *.DotSettings 32 | obj/ 33 | [Rr]elease*/ 34 | _ReSharper*/ 35 | _NCrunch*/ 36 | [Tt]est[Rr]esult* 37 | 38 | .fake/* 39 | .fake 40 | packages/* 41 | !.paket/paket.bootstrapper.exe 42 | paket.exe 43 | paket-files/*.cached 44 | 45 | build/* 46 | !build/tools 47 | !build/keys 48 | build/tools/* 49 | !build/tools/sn 50 | !build/tools/sn/* 51 | !build/tools/ilmerge 52 | !build/*.fsx 53 | !build/*.fsx 54 | !build/*.ps1 55 | !build/*.nuspec 56 | !build/*.png 57 | !build/*.targets 58 | !build/scripts 59 | 60 | 61 | /dep/Newtonsoft.Json.4.0.2 62 | !docs/build 63 | docs/node_modules 64 | doc/Help 65 | 66 | /src/Nest.Tests.Unit/*.ncrunchproject 67 | *.ncrunchproject 68 | Cache 69 | YamlCache 70 | tests.yaml 71 | 72 | *.DS_Store 73 | *.sln.ide 74 | 75 | launchSettings.json 76 | # https://github.com/elastic/elasticsearch-net/pull/1822#issuecomment-183722698 77 | *.project.lock.json 78 | project.lock.json 79 | .vs 80 | .vs/* 81 | 82 | .idea/ 83 | *.sln.iml 84 | /src/.vs/restore.dg 85 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | canary 5 | 0.1 6 | 7 | latest 8 | true 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elasticsearch .NET abstractions 2 | 3 | You've reached the home repository for several auxiliary projects from the .NET team within Elastic. 4 | 5 | Current projects: 6 | 7 | ### [Elastic.Elasticsearch.Managed](src/Elastic.Elasticsearch.Managed/README.md) 8 | 9 | Provides an easy to start/stop one or more Elasticsearch instances that exists on disk already 10 | 11 | ### [Elastic.Elasticsearch.Ephemeral](src/Elastic.Elasticsearch.Ephemeral/README.md) 12 | 13 | Bootstrap (download, install, configure) and run Elasticsearch `2.x`, `5.x`, `6.x` and `7.x` clusters with ease. 14 | Started nodes are run in a new ephemeral location each time they are started and will clean up after they 15 | are disposed. 16 | 17 | ### [Elastic.Elasticsearch.Xunit](src/Elastic.Elasticsearch.Xunit/README.md) 18 | 19 | Write integration tests against Elasticsearch `2.x`, `5.x`, `6.x` and `7.x`. 20 | Works with `.NET Core` and `.NET 4.6` and up. 21 | 22 | Supports `dotnet xunit`, `dotnet test`, `xunit.console.runner` and tests will be runnable in your IDE through VSTest and jetBrains Rider. 23 | 24 | ### [Elastic.Stack.ArtifactsApi](src/Elastic.Stack.ArtifactsApi/README.md) 25 | 26 | Library to fetch the url and metadata for released artifacts. 27 | 28 | Supports: 29 | 30 | 1. Snapshots builds 31 | * `7.4.0-SNAPSHOT` 32 | * `latest-MAJOR` where `MAJOR` is a single integer representing the major you want a snapshot for 33 | * `latest` latest greatest 34 | 35 | 2. BuildCandidates 36 | * `commit_hash:version` a build candidate for a version 37 | 38 | 3. Released versions 39 | * `MAJOR.MINOR.PATH` where `MAJOR` is still supported as defined by the EOL policy of Elastic. 40 | * Note if the version exists but is not yet released it will resolve as a build candidate 41 | 42 | 43 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | dotnet run --project build/scripts -- %* 3 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | dotnet run --project build/scripts -- "$@" 4 | -------------------------------------------------------------------------------- /build/keys/keypair.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/elasticsearch-net-abstractions/f4ececfb69d25714a2bd0d8c50a7bbac3767d1ec/build/keys/keypair.snk -------------------------------------------------------------------------------- /build/keys/public.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/elasticsearch-net-abstractions/f4ececfb69d25714a2bd0d8c50a7bbac3767d1ec/build/keys/public.snk -------------------------------------------------------------------------------- /build/scripts/CommandLine.fs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | module CommandLine 6 | 7 | open Argu 8 | open Microsoft.FSharp.Reflection 9 | 10 | type Arguments = 11 | | [] Clean 12 | | [] Build 13 | | [] Test 14 | 15 | | [] PristineCheck 16 | | [] GeneratePackages 17 | | [] ValidatePackages 18 | | [] GenerateReleaseNotes 19 | | [] GenerateApiChanges 20 | | [] Release 21 | 22 | | [] CreateReleaseOnGithub 23 | | [] Publish 24 | 25 | | [] SingleTarget of bool 26 | | [] Token of string 27 | with 28 | interface IArgParserTemplate with 29 | member this.Usage = 30 | match this with 31 | | Clean -> "clean known output locations" 32 | | Build -> "Run build" 33 | | Test -> "Run build and tests" 34 | | Release -> "runs build, and create an validates the packages shy of publishing them" 35 | | Publish -> "Runs the full release" 36 | 37 | | SingleTarget _ -> "Runs the provided sub command without running their dependencies" 38 | | Token _ -> "Token to be used to authenticate with github" 39 | 40 | | PristineCheck 41 | | GeneratePackages 42 | | ValidatePackages 43 | | GenerateReleaseNotes 44 | | GenerateApiChanges 45 | | CreateReleaseOnGithub 46 | -> "Undocumented, dependent target" 47 | member this.Name = 48 | match FSharpValue.GetUnionFields(this, typeof) with 49 | | case, _ -> case.Name.ToLowerInvariant() 50 | -------------------------------------------------------------------------------- /build/scripts/Paths.fs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | module Paths 6 | 7 | open System 8 | open System.IO 9 | 10 | let ToolName = "elasticsearch-net-abstractions" 11 | let Repository = sprintf "elastic/%s" ToolName 12 | let MainTFM = "netstandard2.0" 13 | 14 | let ValidateAssemblyName = false 15 | let IncludeGitHashInInformational = true 16 | let GenerateApiChanges = false 17 | 18 | let Root = 19 | let mutable dir = DirectoryInfo(".") 20 | while dir.GetFiles("*.sln").Length = 0 do dir <- dir.Parent 21 | Environment.CurrentDirectory <- dir.FullName 22 | dir 23 | 24 | let RootRelative path = Path.GetRelativePath(Root.FullName, path) 25 | 26 | let Output = DirectoryInfo(Path.Combine(Root.FullName, "build", "output")) 27 | 28 | let ToolProject = DirectoryInfo(Path.Combine(Root.FullName, "src", ToolName)) 29 | -------------------------------------------------------------------------------- /build/scripts/Program.fs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | module Program 6 | 7 | open Argu 8 | open Bullseye 9 | open ProcNet 10 | open CommandLine 11 | 12 | [] 13 | let main argv = 14 | let parser = ArgumentParser.Create(programName = "./build.sh") 15 | let parsed = 16 | try 17 | let parsed = parser.ParseCommandLine(inputs = argv, raiseOnUsage = true) 18 | let arguments = parsed.GetSubCommand() 19 | Some (parsed, arguments) 20 | with e -> 21 | printfn "%s" e.Message 22 | None 23 | 24 | match parsed with 25 | | None -> 2 26 | | Some (parsed, arguments) -> 27 | 28 | let target = arguments.Name 29 | 30 | Targets.Setup parsed arguments 31 | let swallowTypes = [typeof; typeof] 32 | 33 | Targets.RunTargetsAndExit 34 | ([target], (fun e -> swallowTypes |> List.contains (e.GetType()) ), ":") 35 | 0 36 | 37 | -------------------------------------------------------------------------------- /build/scripts/scripts.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "minver-cli": { 6 | "version": "4.3.0", 7 | "commands": [ 8 | "minver" 9 | ], 10 | "rollForward": false 11 | }, 12 | "assembly-differ": { 13 | "version": "0.14.0", 14 | "commands": [ 15 | "assembly-differ" 16 | ], 17 | "rollForward": false 18 | }, 19 | "release-notes": { 20 | "version": "0.5.2", 21 | "commands": [ 22 | "release-notes" 23 | ], 24 | "rollForward": false 25 | }, 26 | "nupkg-validator": { 27 | "version": "0.7.0", 28 | "commands": [ 29 | "nupkg-validator" 30 | ], 31 | "rollForward": false 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /examples/Elastic.Ephemeral.Example/Elastic.Ephemeral.Example.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | False 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/Elastic.Ephemeral.Example/Program.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Elasticsearch.Ephemeral; 6 | using Elastic.Elasticsearch.Managed; 7 | using Elastic.Transport; 8 | using Elastic.Transport.Products.Elasticsearch; 9 | using static Elastic.Elasticsearch.Ephemeral.ClusterAuthentication; 10 | using static Elastic.Elasticsearch.Ephemeral.ClusterFeatures; 11 | using HttpMethod = Elastic.Transport.HttpMethod; 12 | 13 | 14 | var config = new EphemeralClusterConfiguration("8.15.0"); 15 | using var cluster = new EphemeralCluster(config); 16 | 17 | var exitEvent = new ManualResetEvent(false); 18 | Console.CancelKeyPress += (sender, eventArgs) => { 19 | cluster.Dispose(); 20 | eventArgs.Cancel = true; 21 | exitEvent.Set(); 22 | }; 23 | using var started = cluster.Start(); 24 | 25 | var pool = new StaticNodePool(cluster.NodesUris()); 26 | var transportConfig = new TransportConfiguration(pool, productRegistration: ElasticsearchProductRegistration.Default) 27 | { 28 | Authentication = new BasicAuthentication(Admin.Username, Admin.Password), 29 | ServerCertificateValidationCallback = CertificateValidations.AllowAll, 30 | ProxyAddress = cluster.DetectedProxy == DetectedProxySoftware.None 31 | ? null 32 | : "http://localhost:8080" 33 | }; 34 | 35 | var transport = new DistributedTransport(transportConfig); 36 | 37 | var response = await transport.RequestAsync(HttpMethod.GET, "/"); 38 | Console.WriteLine(response); 39 | 40 | 41 | exitEvent.WaitOne(); 42 | -------------------------------------------------------------------------------- /examples/Elastic.Managed.Example/Elastic.Managed.Example.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | False 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/Elastic.Managed.Example/Program.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.IO; 7 | using Elastic.Elasticsearch.Managed; 8 | using Elastic.Elasticsearch.Managed.Configuration; 9 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 10 | using Elastic.Stack.ArtifactsApi; 11 | using Elastic.Stack.ArtifactsApi.Products; 12 | 13 | namespace Elastic.Managed.Example 14 | { 15 | public static class Program 16 | { 17 | public static void Main(string[] args) 18 | { 19 | ElasticVersion version = "latest-8"; 20 | var folderName = version.Artifact(Product.Elasticsearch).LocalFolderName; 21 | 22 | var temp = Path.Combine(Path.GetTempPath(), "elastic", folderName, "my-cluster"); 23 | var home = Path.Combine(temp, "home"); 24 | 25 | var clusterConfiguration = new ClusterConfiguration(version, home, 2); 26 | using (var cluster = new ElasticsearchCluster(clusterConfiguration)) 27 | cluster.Start(new ConsoleLineWriter(), TimeSpan.FromMinutes(2)); 28 | 29 | Console.WriteLine("Program ended"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/Elastic.Xunit.ExampleComplex/ClusterTestClassBase.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Clients.Elasticsearch; 6 | using Elastic.Elasticsearch.Ephemeral; 7 | using Elastic.Elasticsearch.Xunit.XunitPlumbing; 8 | 9 | namespace Elastic.Xunit.ExampleComplex 10 | { 11 | public abstract class ClusterTestClassBase(TCluster cluster) 12 | : IClusterFixture 13 | where TCluster : IEphemeralCluster, IMyCluster, new() 14 | { 15 | public TCluster Cluster { get; } = cluster; 16 | public ElasticsearchClient Client => Cluster.Client; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/Elastic.Xunit.ExampleComplex/Clusters.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Collections.Concurrent; 6 | using Elastic.Clients.Elasticsearch; 7 | using Elastic.Elasticsearch.Ephemeral; 8 | using Elastic.Elasticsearch.Xunit; 9 | using Elastic.Transport; 10 | 11 | namespace Elastic.Xunit.ExampleComplex 12 | { 13 | internal static class EphemeralClusterExtensions 14 | { 15 | private static readonly ConcurrentDictionary Clients = new(); 16 | 17 | public static ElasticsearchClient GetOrAddClient(this IEphemeralCluster cluster) => 18 | Clients.GetOrAdd(cluster, c => 19 | { 20 | var connectionPool = new StaticNodePool(c.NodesUris()); 21 | var settings = new ElasticsearchClientSettings(connectionPool); 22 | var client = new ElasticsearchClient(settings); 23 | return client; 24 | }); 25 | } 26 | 27 | public interface IMyCluster 28 | { 29 | ElasticsearchClient Client { get; } 30 | } 31 | 32 | public abstract class MyClusterBase() : XunitClusterBase(new XunitClusterConfiguration(MyRunOptions.TestVersion) 33 | { 34 | ShowElasticsearchOutputAfterStarted = false, 35 | }), IMyCluster 36 | { 37 | public ElasticsearchClient Client => this.GetOrAddClient(); 38 | } 39 | 40 | public class TestCluster : MyClusterBase 41 | { 42 | protected override void SeedCluster() 43 | { 44 | var response = Client.Info(); 45 | } 46 | } 47 | 48 | public class TestGenericCluster : XunitClusterBase, IMyCluster 49 | { 50 | public TestGenericCluster() : base(new XunitClusterConfiguration(MyRunOptions.TestVersion)) 51 | { 52 | } 53 | 54 | public ElasticsearchClient Client => this.GetOrAddClient(); 55 | 56 | protected override void SeedCluster() 57 | { 58 | var response = Client.Info(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /examples/Elastic.Xunit.ExampleComplex/Elastic.Xunit.ExampleComplex.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0;net462 4 | False 5 | 6 | 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/Elastic.Xunit.ExampleComplex/Setup.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Elasticsearch.Xunit; 6 | using Elastic.Stack.ArtifactsApi; 7 | using Elastic.Xunit.ExampleComplex; 8 | using Xunit; 9 | 10 | [assembly: TestFramework("Elastic.Elasticsearch.Xunit.Sdk.ElasticTestFramework", "Elastic.Elasticsearch.Xunit")] 11 | [assembly: ElasticXunitConfiguration(typeof(MyRunOptions))] 12 | 13 | namespace Elastic.Xunit.ExampleComplex 14 | { 15 | /// 16 | /// Allows us to control the custom xunit test pipeline 17 | /// 18 | public class MyRunOptions : ElasticXunitRunOptions 19 | { 20 | public MyRunOptions() 21 | { 22 | RunUnitTests = true; 23 | RunIntegrationTests = true; 24 | IntegrationTestsMayUseAlreadyRunningNode = true; 25 | Version = TestVersion; 26 | } 27 | 28 | public static ElasticVersion TestVersion { get; } = "latest-8"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/Elastic.Xunit.ExampleComplex/TestWithoutClusterFixture.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Elasticsearch.Xunit; 6 | using Elastic.Elasticsearch.Xunit.XunitPlumbing; 7 | using FluentAssertions; 8 | 9 | namespace Elastic.Xunit.ExampleComplex 10 | { 11 | [SkipVersion("<6.3.0", "")] 12 | public class TestWithoutClusterFixture 13 | { 14 | [I] 15 | public void Test() 16 | { 17 | (1 + 1).Should().Be(2); 18 | var info = ElasticXunitRunner.CurrentCluster.GetOrAddClient().Info(); 19 | info.Name.Should().NotBeNullOrWhiteSpace(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/Elastic.Xunit.ExampleComplex/Tests.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Elasticsearch.Xunit.XunitPlumbing; 6 | using FluentAssertions; 7 | 8 | namespace Elastic.Xunit.ExampleComplex 9 | { 10 | public class MyTestClass(TestCluster cluster) 11 | : ClusterTestClassBase(cluster) 12 | { 13 | [I] 14 | public void SomeTest() 15 | { 16 | var info = Client.Info(); 17 | 18 | info.IsValidResponse.Should().BeTrue(); 19 | } 20 | } 21 | 22 | public class Tests1(TestCluster cluster) 23 | : ClusterTestClassBase(cluster) 24 | { 25 | [U] public void Unit1Test() => (1 + 1).Should().Be(2); 26 | [U] public void Unit1Test1() => (1 + 1).Should().Be(2); 27 | [U] public void Unit1Test2() => (1 + 1).Should().Be(2); 28 | [U] public void Unit1Test3() => (1 + 1).Should().Be(2); 29 | [U] public void Unit1Test4() => (1 + 1).Should().Be(2); 30 | [U] public void Unit1Test5() => (1 + 1).Should().Be(2); 31 | [U] public void Unit1Test6() => (1 + 1).Should().Be(2); 32 | } 33 | 34 | public class Tests3 35 | { 36 | [U] public void Unit3Test() => (1 + 1).Should().Be(2); 37 | [U] public void Unit3Test1() => (1 + 1).Should().Be(2); 38 | [U] public void Unit3Test2() => (1 + 1).Should().Be(2); 39 | [U] public void Unit3Test3() => (1 + 1).Should().Be(2); 40 | [U] public void Unit3Test4() => (1 + 1).Should().Be(2); 41 | [U] public void Unit3Test5() => (1 + 1).Should().Be(2); 42 | [U] public void Unit3Test6() => (1 + 1).Should().Be(2); 43 | } 44 | 45 | public class Tests2 : ClusterTestClassBase 46 | { 47 | public Tests2(TestCluster cluster) : base(cluster) { } 48 | 49 | [U] public void Unit2Test() => (1 + 1).Should().Be(2); 50 | [U] public void Unit2Test1() => (1 + 1).Should().Be(2); 51 | [U] public void Unit2Test2() => (1 + 1).Should().Be(2); 52 | [U] public void Unit2Test3() => (1 + 1).Should().Be(2); 53 | [U] public void Unit2Test4() => (1 + 1).Should().Be(2); 54 | [U] public void Unit2Test5() => (1 + 1).Should().Be(2); 55 | [U] public void Unit2Test6() => (1 + 1).Should().Be(2); 56 | } 57 | 58 | public class MyGenericTestClass : ClusterTestClassBase 59 | { 60 | public MyGenericTestClass(TestGenericCluster cluster) : base(cluster) { } 61 | 62 | [I] public void SomeTest() 63 | { 64 | var info = Client.Info(); 65 | 66 | info.IsValidResponse.Should().BeTrue(); 67 | } 68 | [U] public void MyGenericUnitTest() => (1 + 1).Should().Be(2); 69 | [U] public void MyGenericUnitTest1() => (1 + 1).Should().Be(2); 70 | [U] public void MyGenericUnitTest2() => (1 + 1).Should().Be(2); 71 | [U] public void MyGenericUnitTest3() => (1 + 1).Should().Be(2); 72 | [U] public void MyGenericUnitTest4() => (1 + 1).Should().Be(2); 73 | [U] public void MyGenericUnitTest5() => (1 + 1).Should().Be(2); 74 | [U] public void MyGenericUnitTest6() => (1 + 1).Should().Be(2); 75 | } 76 | 77 | [SkipVersion("<6.2.0", "")] 78 | public class SkipTestClass(TestGenericCluster cluster) 79 | : ClusterTestClassBase(cluster) 80 | { 81 | [I] 82 | public void SomeTest() 83 | { 84 | var info = Client.Info(); 85 | 86 | info.IsValidResponse.Should().BeTrue(); 87 | } 88 | 89 | [U] 90 | public void UnitTest() => (1 + 1).Should().Be(2); 91 | } 92 | 93 | public class DirectInterfaceTests : IClusterFixture 94 | { 95 | public DirectInterfaceTests(TestGenericCluster _) { } 96 | 97 | [U] 98 | public void DirectUnitTest() => (1 + 1).Should().Be(2); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /examples/Elastic.Xunit.ExampleMinimal/Elastic.Xunit.ExampleMinimal.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0;net462 4 | False 5 | 6 | 7 | 8 | 9 | 10 | all 11 | runtime; build; native; contentfiles; analyzers; buildtransitive 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/Elastic.Xunit.ExampleMinimal/ExampleTest.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Clients.Elasticsearch; 6 | using Elastic.Elasticsearch.Xunit; 7 | using Elastic.Elasticsearch.Xunit.XunitPlumbing; 8 | using Elastic.Transport; 9 | using FluentAssertions; 10 | using Xunit; 11 | 12 | // we need to put this assembly attribute in place for xunit to use our custom test execution pipeline 13 | [assembly: TestFramework("Elastic.Elasticsearch.Xunit.Sdk.ElasticTestFramework", "Elastic.Elasticsearch.Xunit")] 14 | 15 | namespace Elastic.Xunit.ExampleMinimal 16 | { 17 | /// Declare our cluster that we want to inject into our test classes 18 | public class MyTestCluster : XunitClusterBase 19 | { 20 | /// 21 | /// We pass our configuration instance to the base class. 22 | /// We only configure it to run version 6.2.3 here but lots of additional options are available. 23 | /// 24 | public MyTestCluster() : base(new XunitClusterConfiguration("latest-9")) 25 | { 26 | } 27 | } 28 | 29 | public class ExampleTest : IClusterFixture 30 | { 31 | public ExampleTest(MyTestCluster cluster) => 32 | // This registers a single client for the cluster's lifetime to be reused and shared. 33 | // A client is not directly exposed on a cluster for two reasons 34 | // 35 | // 1) We do not want to prescribe how to create an instance of the client 36 | // 37 | // 2) We do not want Elastic.Elasticsearch.Xunit to depend on NEST. Elastic.Elasticsearch.Xunit can start 2.x, 5.x and 6.x clusters 38 | // and NEST Major.x is only tested and supported against Elasticsearch Major.x. 39 | // 40 | Client = cluster.GetOrAddClient(c => 41 | { 42 | var nodes = cluster.NodesUris(); 43 | var connectionPool = new StaticNodePool(nodes); 44 | var settings = new ElasticsearchClientSettings(connectionPool) 45 | .EnableDebugMode(); 46 | return new ElasticsearchClient(settings); 47 | }); 48 | 49 | private ElasticsearchClient Client { get; } 50 | 51 | /// [I] marks an integration test (like [Fact] would for plain Xunit) 52 | [I] 53 | public void SomeTest() 54 | { 55 | var rootNodeInfo = Client.Info(); 56 | 57 | rootNodeInfo.Name.Should().NotBeNullOrEmpty(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/ScratchPad/Program.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Net; 8 | using System.Net.Http; 9 | using Elastic.Clients.Elasticsearch; 10 | using Elastic.Elasticsearch.Ephemeral; 11 | using Elastic.Elasticsearch.Ephemeral.Plugins; 12 | using Elastic.Stack.ArtifactsApi; 13 | using Elastic.Stack.ArtifactsApi.Products; 14 | using Elastic.Stack.ArtifactsApi.Resolvers; 15 | using Elastic.Transport; 16 | using static Elastic.Elasticsearch.Ephemeral.ClusterFeatures; 17 | using HttpMethod = System.Net.Http.HttpMethod; 18 | 19 | namespace ScratchPad 20 | { 21 | public static class Program 22 | { 23 | private static HttpClient HttpClient { get; } = new HttpClient() { }; 24 | 25 | public static int Main() 26 | { 27 | ResolveVersions(); 28 | //ManualConfigRun(); 29 | //ValidateCombinations.Run(); 30 | return 0; 31 | } 32 | 33 | private static void ManualConfigRun() 34 | { 35 | ElasticVersion version = "latest"; 36 | 37 | var plugins = 38 | new ElasticsearchPlugins(ElasticsearchPlugin.IngestGeoIp, ElasticsearchPlugin.IngestAttachment); 39 | var features = Security | XPack | SSL; 40 | var config = new EphemeralClusterConfiguration(version, features, plugins, 1) 41 | { 42 | AutoWireKnownProxies = true, 43 | ShowElasticsearchOutputAfterStarted = true, 44 | CacheEsHomeInstallation = false, 45 | TrialMode = XPackTrialMode.Trial, 46 | NoCleanupAfterNodeStopped = false, 47 | }; 48 | 49 | using (var cluster = new EphemeralCluster(config)) 50 | { 51 | cluster.Start(); 52 | 53 | var nodes = cluster.NodesUris(); 54 | var connectionPool = new StaticNodePool(nodes); 55 | var settings = new ElasticsearchClientSettings(connectionPool).EnableDebugMode(); 56 | if (config.EnableSecurity) 57 | { 58 | settings = settings.Authentication( 59 | new BasicAuthentication(ClusterAuthentication.Admin.Username, ClusterAuthentication.Admin.Password) 60 | ); 61 | } 62 | 63 | if (config.EnableSsl) 64 | settings = settings.ServerCertificateValidationCallback(CertificateValidations.AllowAll); 65 | 66 | var client = new ElasticsearchClient(settings); 67 | 68 | var clusterConfiguration = new Dictionary() 69 | { 70 | {"cluster.routing.use_adaptive_replica_selection", true}, 71 | {"cluster.remote.remote-cluster.seeds", "127.0.0.1:9300"} 72 | }; 73 | 74 | 75 | Console.Write(client.Xpack.Info().DebugInformation); 76 | Console.WriteLine("Press any key to exit"); 77 | Console.ReadKey(); 78 | Console.WriteLine("Exitting.."); 79 | } 80 | 81 | Console.WriteLine("Done!"); 82 | } 83 | 84 | private static void ResolveVersions() 85 | { 86 | var versions = new[] 87 | { 88 | "latest-8", "7.0.0-beta1", "6.6.1", "latest-7", "latest", "7.0.0", "7.4.0-SNAPSHOT", 89 | "957e3089:7.2.0" 90 | }; 91 | //versions = new[] {"latest-7"}; 92 | var products = new Product[] 93 | { 94 | Product.Elasticsearch, Product.Kibana, Product.ElasticsearchPlugin(ElasticsearchPlugin.AnalysisIcu) 95 | }; 96 | var x = SnapshotApiResolver.AvailableVersions.Value; 97 | 98 | foreach (var v in versions) 99 | foreach (var p in products) 100 | { 101 | var r = ElasticVersion.From(v); 102 | var a = r.Artifact(p); 103 | Console.ForegroundColor = ConsoleColor.Green; 104 | Console.Write(v); 105 | Console.ForegroundColor = ConsoleColor.Yellow; 106 | Console.Write($"\t{p.Moniker}"); 107 | Console.ForegroundColor = ConsoleColor.Cyan; 108 | Console.Write($"\t\t{r.ArtifactBuildState}"); 109 | Console.ForegroundColor = ConsoleColor.White; 110 | Console.WriteLine($"\t{a?.BuildHash}"); 111 | Console.ForegroundColor = ConsoleColor.Blue; 112 | // Console.WriteLine($"\t{a.Archive}"); 113 | // Console.WriteLine($"\t{r.ArtifactBuildState}"); 114 | // Console.WriteLine($"\t{a.FolderInZip}"); 115 | // Console.WriteLine($"\tfolder: {a.LocalFolderName}"); 116 | if (a == null) 117 | { 118 | Console.ForegroundColor = ConsoleColor.Red; 119 | Console.WriteLine("\tArtifact not resolved"); 120 | continue; 121 | } 122 | 123 | Console.WriteLine($"\t{a.DownloadUrl}"); 124 | var found = false; 125 | try 126 | { 127 | found = HeadReturns200OnDownloadUrl(a.DownloadUrl); 128 | } 129 | catch 130 | { 131 | // ignored, best effort but does not take into account proxies or other bits that might prevent the check 132 | } 133 | 134 | if (found) continue; 135 | Console.ForegroundColor = ConsoleColor.Red; 136 | Console.WriteLine("\tArtifact not found"); 137 | } 138 | } 139 | 140 | public static bool HeadReturns200OnDownloadUrl(string url) 141 | { 142 | var message = new HttpRequestMessage {Method = HttpMethod.Head, RequestUri = new Uri(url)}; 143 | 144 | using (var response = HttpClient.SendAsync(message).GetAwaiter().GetResult()) 145 | return response.StatusCode == HttpStatusCode.OK; 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /examples/ScratchPad/ScratchPad.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net6.0 5 | False 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/ScratchPad/ValidateCombinations.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using Elastic.Clients.Elasticsearch; 7 | using Elastic.Elasticsearch.Ephemeral; 8 | using Elastic.Elasticsearch.Ephemeral.Plugins; 9 | using Elastic.Stack.ArtifactsApi.Products; 10 | using Elastic.Transport; 11 | 12 | namespace ScratchPad 13 | { 14 | public static class ValidateCombinations 15 | { 16 | public static void Run() 17 | { 18 | var plugins = 19 | new ElasticsearchPlugins(ElasticsearchPlugin.IngestGeoIp, ElasticsearchPlugin.AnalysisKuromoji); 20 | var versions = new string[] 21 | { 22 | "7.0.0-beta1", "latest", "latest-7", "latest-6", "957e3089:7.2.0", "6.6.1", "5.6.15" 23 | }; 24 | var features = new[] 25 | { 26 | ClusterFeatures.None, 27 | // ClusterFeatures.XPack, 28 | // ClusterFeatures.XPack | ClusterFeatures.Security, 29 | ClusterFeatures.XPack | ClusterFeatures.SSL | ClusterFeatures.Security 30 | }; 31 | 32 | foreach (var v in versions) 33 | foreach (var f in features) 34 | { 35 | Console.Clear(); 36 | var reset = Console.ForegroundColor; 37 | Console.ForegroundColor = ConsoleColor.Cyan; 38 | Console.WriteLine($"{v} {f}"); 39 | 40 | Console.ForegroundColor = reset; 41 | var config = new EphemeralClusterConfiguration(v, f, plugins, 1) {AutoWireKnownProxies = true,}; 42 | 43 | using var cluster = new EphemeralCluster(config); 44 | try 45 | { 46 | cluster.Start(); 47 | 48 | var nodes = cluster.NodesUris(); 49 | var connectionPool = new StaticNodePool(nodes); 50 | var settings = new ElasticsearchClientSettings(connectionPool).EnableDebugMode(); 51 | if (config.EnableSecurity) 52 | { 53 | settings = settings.Authentication( 54 | new BasicAuthentication(ClusterAuthentication.Admin.Username, ClusterAuthentication.Admin.Password) 55 | ); 56 | } 57 | if (config.EnableSsl) 58 | settings = settings.ServerCertificateValidationCallback(CertificateValidations.AllowAll); 59 | 60 | var client = new ElasticsearchClient(settings); 61 | Console.WriteLine(client.Info().Version.Number); 62 | cluster.Dispose(); 63 | cluster.WaitForExit(TimeSpan.FromMinutes(1)); 64 | } 65 | catch (Exception e) 66 | { 67 | Console.WriteLine(e); 68 | Console.ForegroundColor = ConsoleColor.Cyan; 69 | Console.WriteLine($"{v} {f}"); 70 | 71 | Console.ForegroundColor = reset; 72 | 73 | throw; 74 | } 75 | } 76 | 77 | Console.WriteLine("Done!"); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.100", 4 | "rollForward": "latestFeature", 5 | "allowPrerelease": false 6 | } 7 | } -------------------------------------------------------------------------------- /nuget-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/elasticsearch-net-abstractions/f4ececfb69d25714a2bd0d8c50a7bbac3767d1ec/nuget-icon.png -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Elastic and contributors 6 | Elasticsearch BV 7 | Apache-2.0 8 | https://github.com/elastic/elasticsearch-net-abstractions 9 | https://github.com/elastic/elasticsearch-net-abstractions 10 | https://github.com/elastic/elasticsearch-net-abstractions/releases 11 | 12 | 13 | true 14 | ..\..\build\keys\keypair.snk 15 | 16 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 17 | true 18 | nuget-icon.png 19 | 20 | 21 | 22 | 23 | 24 | nuget-icon.png 25 | True 26 | nuget-icon.png 27 | 28 | 29 | True 30 | License.txt 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/ClusterAuthentication.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Elasticsearch.Ephemeral 6 | { 7 | /// 8 | /// Authentication credentials for the cluster 9 | /// 10 | public class ClusterAuthentication 11 | { 12 | /// 13 | /// Administrator credentials 14 | /// 15 | public static XPackCredentials Admin => new XPackCredentials {Username = "es_admin", Role = "admin"}; 16 | 17 | /// 18 | /// User credentials 19 | /// 20 | public static XPackCredentials User => new XPackCredentials {Username = "es_user", Role = "user"}; 21 | 22 | /// 23 | /// Credentials for all users 24 | /// 25 | public static XPackCredentials[] AllUsers { get; } = {Admin, User}; 26 | 27 | /// 28 | /// Authentication credentials for X-Pack 29 | /// 30 | public class XPackCredentials 31 | { 32 | public string Username { get; set; } 33 | public string Role { get; set; } 34 | public string Password => Username; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/ClusterFeatures.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | namespace Elastic.Elasticsearch.Ephemeral 8 | { 9 | /// 10 | /// Hints to what features the cluster to be started should have. 11 | /// It's up to the to actually bootstrap these features. 12 | /// 13 | [Flags] 14 | public enum ClusterFeatures 15 | { 16 | /// 17 | /// No features, note that as of Elasticsearch 6.3.0 x-pack ships OOTB 18 | /// 19 | None = 1 << 0, 20 | 21 | /// 22 | /// X-Pack features 23 | /// 24 | XPack = 1 << 1, 25 | 26 | /// 27 | /// X-Pack security 28 | /// 29 | Security = 1 << 2, 30 | 31 | /// 32 | /// SSL/TLS for HTTP and Transport layers 33 | /// 34 | SSL = 1 << 3, 35 | } 36 | 37 | /// 38 | /// As of 6.3.0 x-pack is included by default, this enum allows you to optionally make ephemeral cluster accept the 39 | /// basic license or start a trial 40 | /// 41 | public enum XPackTrialMode 42 | { 43 | None, 44 | Basic, 45 | Trial 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Elastic.Elasticsearch.Ephemeral.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;netstandard2.1;net462;net8.0 5 | Provides an EphemeralCluster implementation that can download/bootstrap/run a throwaway customizable Elasticsearch cluster 6 | elastic,elasticsearch,cluster,ephemeral 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/EphemeralCluster.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Security.Cryptography; 11 | using System.Text; 12 | using Elastic.Elasticsearch.Managed; 13 | using Elastic.Stack.ArtifactsApi; 14 | 15 | namespace Elastic.Elasticsearch.Ephemeral 16 | { 17 | public class EphemeralCluster : EphemeralCluster 18 | { 19 | public EphemeralCluster(ElasticVersion version, int numberOfNodes = 1) 20 | : base(new EphemeralClusterConfiguration(version, ClusterFeatures.None, numberOfNodes: numberOfNodes)) 21 | { 22 | } 23 | 24 | public EphemeralCluster(EphemeralClusterConfiguration clusterConfiguration) : base(clusterConfiguration) 25 | { 26 | } 27 | } 28 | 29 | public abstract class EphemeralCluster : ClusterBase, 30 | IEphemeralCluster 31 | where TConfiguration : EphemeralClusterConfiguration 32 | { 33 | protected EphemeralCluster(TConfiguration clusterConfiguration) : base(clusterConfiguration) => 34 | Composer = new EphemeralClusterComposer(this); 35 | 36 | protected EphemeralClusterComposer Composer { get; } 37 | 38 | public virtual ICollection NodesUris(string hostName = null) 39 | { 40 | hostName ??= "localhost"; 41 | if (hostName == "localhost" && ClusterConfiguration.AutoWireKnownProxies) 42 | { 43 | if (DetectedProxy == DetectedProxySoftware.Fiddler) 44 | hostName = "ipv4.fiddler"; //magic reverse proxy address for fiddler 45 | } 46 | 47 | var ssl = ClusterConfiguration.EnableSsl ? "s" : ""; 48 | return Nodes 49 | .Select(n => $"http{ssl}://{hostName}:{n.Port ?? 9200}") 50 | .Distinct() 51 | .Select(n => new Uri(n)) 52 | .ToList(); 53 | } 54 | 55 | public bool CachingAndCachedHomeExists() 56 | { 57 | if (!ClusterConfiguration.CacheEsHomeInstallation) return false; 58 | var cachedEsHomeFolder = Path.Combine(FileSystem.LocalFolder, GetCacheFolderName()); 59 | return Directory.Exists(cachedEsHomeFolder); 60 | } 61 | 62 | public virtual string GetCacheFolderName() 63 | { 64 | var config = ClusterConfiguration; 65 | 66 | var sb = new StringBuilder(); 67 | sb.Append(EphemeralClusterComposerBase.InstallationTasks.Count()); 68 | sb.Append("-"); 69 | if (config.XPackInstalled) sb.Append("x"); 70 | if (config.EnableSecurity) sb.Append("sec"); 71 | if (config.EnableSsl) sb.Append("ssl"); 72 | if (config.Plugins != null && config.Plugins.Count > 0) 73 | { 74 | sb.Append("-"); 75 | foreach (var p in config.Plugins.OrderBy(p => p.SubProductName)) 76 | sb.Append(p.SubProductName.ToLowerInvariant()); 77 | } 78 | 79 | var name = sb.ToString(); 80 | 81 | return CalculateSha1(name, Encoding.UTF8); 82 | } 83 | 84 | protected override void OnBeforeStart() 85 | { 86 | Composer.Install(); 87 | Composer.OnBeforeStart(); 88 | } 89 | 90 | protected override void OnDispose() => Composer.OnStop(); 91 | 92 | protected override void OnAfterStarted() => Composer.OnAfterStart(); 93 | 94 | protected override string SeeLogsMessage(string message) 95 | { 96 | var log = Path.Combine(FileSystem.LogsPath, $"{ClusterConfiguration.ClusterName}.log"); 97 | if (!File.Exists(log) || ClusterConfiguration.ShowElasticsearchOutputAfterStarted) return message; 98 | if (!Started) return message; 99 | using (var fileStream = new FileStream(log, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 100 | using (var textReader = new StreamReader(fileStream)) 101 | { 102 | var logContents = textReader.ReadToEnd(); 103 | return message + $" contents of {log}:{Environment.NewLine}" + logContents; 104 | } 105 | } 106 | 107 | public static string CalculateSha1(string text, Encoding enc) 108 | { 109 | var buffer = enc.GetBytes(text); 110 | #if NET8_0_OR_GREATER 111 | var cryptoTransformSha1 = SHA1.Create(); 112 | #else 113 | var cryptoTransformSha1 = new SHA1CryptoServiceProvider(); 114 | #endif 115 | return BitConverter.ToString(cryptoTransformSha1.ComputeHash(buffer)) 116 | .Replace("-", "").ToLowerInvariant().Substring(0, 12); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/EphemeralClusterComposer.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using Elastic.Elasticsearch.Ephemeral.Tasks; 8 | using Elastic.Elasticsearch.Ephemeral.Tasks.AfterNodeStoppedTasks; 9 | using Elastic.Elasticsearch.Ephemeral.Tasks.BeforeStartNodeTasks; 10 | using Elastic.Elasticsearch.Ephemeral.Tasks.BeforeStartNodeTasks.XPack; 11 | using Elastic.Elasticsearch.Ephemeral.Tasks.InstallationTasks; 12 | using Elastic.Elasticsearch.Ephemeral.Tasks.InstallationTasks.XPack; 13 | using Elastic.Elasticsearch.Ephemeral.Tasks.ValidationTasks; 14 | using Elastic.Elasticsearch.Managed.FileSystem; 15 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 16 | 17 | namespace Elastic.Elasticsearch.Ephemeral 18 | { 19 | public class EphemeralClusterComposerBase 20 | { 21 | protected EphemeralClusterComposerBase() 22 | { 23 | } 24 | 25 | internal static IEnumerable InstallationTasks { get; } = new List 26 | { 27 | new PrintConfiguration(), 28 | new CreateLocalApplicationDirectory(), 29 | new CopyCachedEsInstallation(), 30 | new EnsureJavaHomeEnvironmentVariableIsSet(), 31 | new DownloadElasticsearchVersion(), 32 | new UnzipElasticsearch(), 33 | new SetElasticsearchBundledJdkJavaHome(), 34 | new InstallPlugins(), 35 | new EnsureElasticsearchBatWorksAcrossDrives(), 36 | new EnsureXPackEnvBinaryExists(), 37 | new PathXPackInBatFile() 38 | }; 39 | 40 | protected static IEnumerable BeforeStart { get; } = new List 41 | { 42 | new CreateEphemeralDirectory(), 43 | new EnsureSecurityRealms(), 44 | new EnsureSecurityRolesFileExists(), 45 | new EnsureSecurityUsersInDefaultRealmAreAdded(), 46 | new GenerateCertificatesTask(), 47 | new AddClientCertificateRoleMappingTask(), 48 | new CacheElasticsearchInstallation() 49 | }; 50 | 51 | protected static IEnumerable NodeStoppedTasks { get; } = new List 52 | { 53 | new CleanUpDirectoriesAfterNodeStopped() 54 | }; 55 | 56 | protected static IEnumerable AfterStartedTasks { get; } = new List 57 | { 58 | new ValidateRunningVersion(), 59 | new ValidateClusterStateTask(), 60 | new PostLicenseTask(), 61 | new ValidateLicenseTask(), 62 | new ValidatePluginsTask(), 63 | }; 64 | } 65 | 66 | 67 | public class EphemeralClusterComposer : EphemeralClusterComposerBase 68 | where TConfiguration : EphemeralClusterConfiguration 69 | { 70 | private readonly object _lock = new object(); 71 | public EphemeralClusterComposer(IEphemeralCluster cluster) => Cluster = cluster; 72 | 73 | private IEphemeralCluster Cluster { get; } 74 | 75 | private bool NodeStarted { get; set; } 76 | 77 | public void OnStop() => Iterate(NodeStoppedTasks, (t, c, fs) => t.Run(c, NodeStarted), false); 78 | 79 | public void Install() => Iterate(InstallationTasks, (t, c, fs) => t.Run(c)); 80 | 81 | public void OnBeforeStart() 82 | { 83 | var tasks = new List(BeforeStart); 84 | if (Cluster.ClusterConfiguration.AdditionalBeforeNodeStartedTasks != null) 85 | tasks.AddRange(Cluster.ClusterConfiguration.AdditionalBeforeNodeStartedTasks); 86 | 87 | if (Cluster.ClusterConfiguration.PrintYamlFilesInConfigFolder) 88 | tasks.Add(new PrintYamlContents()); 89 | 90 | Iterate(tasks, (t, c, fs) => t.Run(c)); 91 | 92 | NodeStarted = true; 93 | } 94 | 95 | public void OnAfterStart() 96 | { 97 | if (Cluster.ClusterConfiguration.SkipBuiltInAfterStartTasks) return; 98 | var tasks = new List(AfterStartedTasks); 99 | if (Cluster.ClusterConfiguration.AdditionalAfterStartedTasks != null) 100 | tasks.AddRange(Cluster.ClusterConfiguration.AdditionalAfterStartedTasks); 101 | Iterate(tasks, (t, c, fs) => t.Run(c), false); 102 | } 103 | 104 | private void Iterate(IEnumerable collection, 105 | Action, INodeFileSystem> act, bool callOnStop = true) 106 | { 107 | lock (_lock) 108 | { 109 | var cluster = Cluster; 110 | foreach (var task in collection) 111 | try 112 | { 113 | act(task, cluster, cluster.FileSystem); 114 | } 115 | catch (Exception ex) 116 | { 117 | if (callOnStop) OnStop(); 118 | cluster.Writer.WriteError($"{ex.Message}{Environment.NewLine}{ex.StackTrace}"); 119 | throw; 120 | } 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/EphemeralFileSystem.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using Elastic.Elasticsearch.Managed.FileSystem; 7 | using Elastic.Stack.ArtifactsApi; 8 | using Elastic.Stack.ArtifactsApi.Products; 9 | 10 | namespace Elastic.Elasticsearch.Ephemeral 11 | { 12 | public class EphemeralFileSystem : NodeFileSystem 13 | { 14 | public EphemeralFileSystem(ElasticVersion version, string clusterName) : base(version, 15 | EphemeralHome(version, clusterName)) => ClusterName = clusterName; 16 | 17 | private string ClusterName { get; } 18 | 19 | public string TempFolder => Path.Combine(Path.GetTempPath(), SubFolder, Artifact.LocalFolderName, ClusterName); 20 | 21 | public override string ConfigPath => Path.Combine(TempFolder, "config"); 22 | public override string LogsPath => Path.Combine(TempFolder, "logs"); 23 | public override string RepositoryPath => Path.Combine(TempFolder, "repositories"); 24 | public override string DataPath => Path.Combine(TempFolder, "data"); 25 | 26 | //certificates 27 | public string CertificateFolderName => "node-certificates"; 28 | public string CertificateNodeName => "node01"; 29 | public string ClientCertificateName => "cn=John Doe,ou=example,o=com"; 30 | public string ClientCertificateFilename => "john_doe"; 31 | 32 | public string CertificatesPath => Path.Combine(ConfigPath, CertificateFolderName); 33 | 34 | public string CaCertificate => Path.Combine(CertificatesPath, "ca", "ca") + ".crt"; 35 | public string CaPrivateKey => Path.Combine(CertificatesPath, "ca", "ca") + ".key"; 36 | 37 | public string NodePrivateKey => 38 | Path.Combine(CertificatesPath, CertificateNodeName, CertificateNodeName) + ".key"; 39 | 40 | public string NodeCertificate => 41 | Path.Combine(CertificatesPath, CertificateNodeName, CertificateNodeName) + ".crt"; 42 | 43 | public string ClientCertificate => 44 | Path.Combine(CertificatesPath, ClientCertificateFilename, ClientCertificateFilename) + ".crt"; 45 | 46 | public string ClientPrivateKey => 47 | Path.Combine(CertificatesPath, ClientCertificateFilename, ClientCertificateFilename) + ".key"; 48 | 49 | public string UnusedCertificateFolderName => $"unused-{CertificateFolderName}"; 50 | public string UnusedCertificatesPath => Path.Combine(ConfigPath, UnusedCertificateFolderName); 51 | public string UnusedCaCertificate => Path.Combine(UnusedCertificatesPath, "ca", "ca") + ".crt"; 52 | 53 | public string UnusedClientCertificate => 54 | Path.Combine(UnusedCertificatesPath, ClientCertificateFilename, ClientCertificateFilename) + ".crt"; 55 | 56 | 57 | protected static string EphemeralHome(ElasticVersion version, string clusterName) 58 | { 59 | var artifact = version.Artifact(Product.Elasticsearch); 60 | var localFolder = artifact.LocalFolderName; 61 | var temp = Path.Combine(Path.GetTempPath(), SubFolder, localFolder, clusterName); 62 | return Path.Combine(temp, "home"); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/IEphemeralCluster.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using Elastic.Elasticsearch.Managed; 8 | 9 | namespace Elastic.Elasticsearch.Ephemeral 10 | { 11 | public interface IEphemeralCluster : ICluster 12 | { 13 | ICollection NodesUris(string hostName = null); 14 | string GetCacheFolderName(); 15 | bool CachingAndCachedHomeExists(); 16 | } 17 | 18 | public interface IEphemeralCluster : IEphemeralCluster, ICluster 19 | where TConfiguration : EphemeralClusterConfiguration 20 | { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Plugins/ElasticsearchPlugins.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Collections.Generic; 6 | using System.Collections.ObjectModel; 7 | using System.Linq; 8 | using Elastic.Stack.ArtifactsApi.Products; 9 | 10 | namespace Elastic.Elasticsearch.Ephemeral.Plugins 11 | { 12 | public class ElasticsearchPlugins : ReadOnlyCollection 13 | { 14 | public ElasticsearchPlugins(IList list) : base(list) 15 | { 16 | } 17 | 18 | public ElasticsearchPlugins(params ElasticsearchPlugin[] list) : base(list) 19 | { 20 | } 21 | 22 | public static ElasticsearchPlugins Supported { get; } = 23 | new ElasticsearchPlugins(new List 24 | { 25 | ElasticsearchPlugin.AnalysisIcu, 26 | ElasticsearchPlugin.AnalysisKuromoji, 27 | ElasticsearchPlugin.AnalysisPhonetic, 28 | ElasticsearchPlugin.AnalysisSmartCn, 29 | ElasticsearchPlugin.AnalysisStempel, 30 | ElasticsearchPlugin.AnalysisUkrainian, 31 | ElasticsearchPlugin.DiscoveryAzureClassic, 32 | ElasticsearchPlugin.DiscoveryEC2, 33 | ElasticsearchPlugin.DiscoveryFile, 34 | ElasticsearchPlugin.DiscoveryGCE, 35 | ElasticsearchPlugin.IngestAttachment, 36 | ElasticsearchPlugin.IngestGeoIp, 37 | ElasticsearchPlugin.IngestUserAgent, 38 | ElasticsearchPlugin.MapperAttachment, 39 | ElasticsearchPlugin.MapperMurmur3, 40 | ElasticsearchPlugin.MapperSize, 41 | ElasticsearchPlugin.RepositoryAzure, 42 | ElasticsearchPlugin.RepositoryGCS, 43 | ElasticsearchPlugin.RepositoryHDFS, 44 | ElasticsearchPlugin.RepositoryS3, 45 | ElasticsearchPlugin.StoreSMB, 46 | ElasticsearchPlugin.DeleteByQuery, 47 | ElasticsearchPlugin.XPack, 48 | }); 49 | 50 | public override string ToString() => string.Join(", ", Items.Select(s => s.SubProductName)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/README.md: -------------------------------------------------------------------------------- 1 | # Elastic.Managed.Ephemeral 2 | 3 | Bootstrap (download, install, configure) and run Elasticsearch `2.x`, `5.x` and `6.x` clusters with ease. 4 | Started nodes are run in a new ephemeral location each time they are started and will clean up after they 5 | are disposed. 6 | 7 | 8 | ## EphemeralCluster 9 | 10 | A `ClusterBase` implementation from `Elastic.Managed` that can: 11 | 12 | * download elasticsearch `2.x`, `5.x` and `6.x` versions (stable releases, snapshots, build candidates) 13 | * download elasticsearch `2.x`, `5.x` and `6.x` plugins (stable releases, snapshots, build candidates) 14 | * install elasticsearch and desired plugins in an ephemeral location. The source downloaded zips are cached 15 | on disk (LocalAppData). 16 | * Ships with builtin knowledge on how to enable XPack, SSL, Security on the running cluster. 17 | * Start elasticsearch using ephemeral locations for ES_HOME and conf/logs/data paths. 18 | 19 | 20 | #### Examples: 21 | 22 | The easiest way to get started is by simply passing the version you want to be bootstrapped to `EphemeralCluster`. 23 | `Start` starts the `ElasticsearchNode`'s and waits for them to be started. The default overload waits `2 minutes`. 24 | 25 | ```csharp 26 | using (var cluster = new EphemeralCluster("6.0.0")) 27 | { 28 | cluster.Start(); 29 | } 30 | ``` 31 | 32 | If you want the full configuration possibilities inject a `EphemeralClusterConfiguration` instead: 33 | 34 | 35 | ```csharp 36 | var plugins = new ElasticsearchPlugins(ElasticsearchPlugin.RepositoryAzure, ElasticsearchPlugin.IngestAttachment); 37 | var config = new EphemeralClusterConfiguration("6.2.3", ClusterFeatures.XPack, plugins, numberOfNodes: 2); 38 | using (var cluster = new EphemeralCluster(config)) 39 | { 40 | cluster.Start(); 41 | 42 | var nodes = cluster.NodesUris(); 43 | var connectionPool = new StaticConnectionPool(nodes); 44 | var settings = new ConnectionSettings(connectionPool).EnableDebugMode(); 45 | var client = new ElasticClient(settings); 46 | 47 | Console.Write(client.CatPlugins().DebugInformation); 48 | } 49 | ``` 50 | Here we first create a `ElasticsearchPlugins` collection of the plugins that we want to bootstrap. 51 | Then we create an instance of `EphemeralClusterConfiguration` that dictates we want a 2 node cluster 52 | running elasticsearch `6.2.3` with XPack enabled using the previous declared `plugins`. 53 | 54 | We then Start the node and after its up create a `NEST` client using the `NodeUris()` that the cluster 55 | started. 56 | 57 | We call `/_cat/plugins` and write `NEST`'s debug information to the console. 58 | 59 | When the cluster exits the using block and disposes the cluster all nodes will be shutdown gracefully. 60 | 61 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/SecurityRealms.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Elasticsearch.Ephemeral 6 | { 7 | public static class SecurityRealms 8 | { 9 | public const string FileRealm = "file1"; 10 | 11 | public const string PkiRealm = "pki1"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/AfterNodeStoppedTasks/CleanUpDirectoriesAfterNodeStopped.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 7 | using ProcNet.Std; 8 | 9 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.AfterNodeStoppedTasks 10 | { 11 | public class CleanUpDirectoriesAfterNodeStopped : IClusterTeardownTask 12 | { 13 | public void Run(IEphemeralCluster cluster, bool nodeStarted) 14 | { 15 | var fs = cluster.FileSystem; 16 | var w = cluster.Writer; 17 | var v = cluster.ClusterConfiguration.Version; 18 | var a = cluster.ClusterConfiguration.Artifact; 19 | if (cluster.ClusterConfiguration.NoCleanupAfterNodeStopped) 20 | { 21 | w.WriteDiagnostic( 22 | $"{{{nameof(CleanUpDirectoriesAfterNodeStopped)}}} skipping cleanup as requested on cluster configuration"); 23 | return; 24 | } 25 | 26 | DeleteDirectory(w, "cluster data", fs.DataPath); 27 | DeleteDirectory(w, "cluster config", fs.ConfigPath); 28 | DeleteDirectory(w, "cluster logs", fs.LogsPath); 29 | DeleteDirectory(w, "repositories", fs.RepositoryPath); 30 | var efs = fs as EphemeralFileSystem; 31 | if (!string.IsNullOrWhiteSpace(efs?.TempFolder)) 32 | DeleteDirectory(w, "cluster temp folder", efs.TempFolder); 33 | 34 | if (efs != null) 35 | { 36 | var extractedFolder = Path.Combine(fs.LocalFolder, a.FolderInZip); 37 | if (extractedFolder != fs.ElasticsearchHome) 38 | DeleteDirectory(w, "ephemeral ES_HOME", fs.ElasticsearchHome); 39 | //if the node was not started delete the cached extractedFolder 40 | if (!nodeStarted) 41 | DeleteDirectory(w, "cached extracted folder - node failed to start", extractedFolder); 42 | } 43 | 44 | //if the node did not start make sure we delete the cached folder as we can not assume its in a good state 45 | var cachedEsHomeFolder = Path.Combine(fs.LocalFolder, cluster.GetCacheFolderName()); 46 | if (cluster.ClusterConfiguration.CacheEsHomeInstallation && !nodeStarted) 47 | DeleteDirectory(w, "cached installation - node failed to start", cachedEsHomeFolder); 48 | else 49 | w.WriteDiagnostic( 50 | $"{{{nameof(CleanUpDirectoriesAfterNodeStopped)}}} Leaving [cached folder] on disk: {{{cachedEsHomeFolder}}}"); 51 | } 52 | 53 | private static void DeleteDirectory(IConsoleLineHandler w, string description, string path) 54 | { 55 | if (!Directory.Exists(path)) return; 56 | w.WriteDiagnostic( 57 | $"{{{nameof(CleanUpDirectoriesAfterNodeStopped)}}} attempting to delete [{description}]: {{{path}}}"); 58 | Directory.Delete(path, true); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/BeforeStartNodeTasks/CacheElasticsearchInstallation.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 7 | 8 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.BeforeStartNodeTasks 9 | { 10 | public class CacheElasticsearchInstallation : ClusterComposeTask 11 | { 12 | public override void Run(IEphemeralCluster cluster) 13 | { 14 | if (!cluster.ClusterConfiguration.CacheEsHomeInstallation) return; 15 | 16 | var fs = cluster.FileSystem; 17 | var cachedEsHomeFolder = Path.Combine(fs.LocalFolder, cluster.GetCacheFolderName()); 18 | var cachedelasticsearchYaml = Path.Combine(cachedEsHomeFolder, "config", "elasticsearch.yml"); 19 | if (File.Exists(cachedelasticsearchYaml)) 20 | { 21 | cluster.Writer?.WriteDiagnostic( 22 | $"{{{nameof(CacheElasticsearchInstallation)}}} cached home already exists [{cachedEsHomeFolder}]"); 23 | return; 24 | } 25 | 26 | var source = fs.ElasticsearchHome; 27 | var target = cachedEsHomeFolder; 28 | cluster.Writer?.WriteDiagnostic( 29 | $"{{{nameof(CacheElasticsearchInstallation)}}} caching {{{source}}} to [{target}]"); 30 | CopyFolder(source, target, false); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/BeforeStartNodeTasks/CreateEphemeralDirectory.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 7 | using Elastic.Elasticsearch.Managed.FileSystem; 8 | 9 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.BeforeStartNodeTasks 10 | { 11 | public class CreateEphemeralDirectory : ClusterComposeTask 12 | { 13 | public override void Run(IEphemeralCluster cluster) 14 | { 15 | var fs = cluster.FileSystem; 16 | if (!(fs is EphemeralFileSystem f)) 17 | { 18 | cluster.Writer?.WriteDiagnostic( 19 | $"{{{nameof(CreateEphemeralDirectory)}}} unexpected IFileSystem implementation {{{fs.GetType()}}}"); 20 | return; 21 | } 22 | 23 | cluster.Writer?.WriteDiagnostic($"{{{nameof(CreateEphemeralDirectory)}}} creating {{{f.TempFolder}}}"); 24 | 25 | Directory.CreateDirectory(f.TempFolder); 26 | 27 | if (!Directory.Exists(f.ConfigPath)) 28 | { 29 | cluster.Writer?.WriteDiagnostic( 30 | $"{{{nameof(CreateEphemeralDirectory)}}} creating config folder {{{f.ConfigPath}}}"); 31 | Directory.CreateDirectory(f.ConfigPath); 32 | } 33 | 34 | CopyHomeConfigToEphemeralConfig(cluster, f, fs); 35 | } 36 | 37 | private static void CopyHomeConfigToEphemeralConfig(IEphemeralCluster cluster, 38 | EphemeralFileSystem ephemeralFileSystem, INodeFileSystem fs) 39 | { 40 | var target = ephemeralFileSystem.ConfigPath; 41 | var cachedEsHomeFolder = Path.Combine(fs.LocalFolder, cluster.GetCacheFolderName()); 42 | var cachedElasticsearchYaml = Path.Combine(cachedEsHomeFolder, "config", "elasticsearch.yaml"); 43 | 44 | var homeSource = 45 | cluster.ClusterConfiguration.CacheEsHomeInstallation && File.Exists(cachedElasticsearchYaml) 46 | ? cachedEsHomeFolder 47 | : fs.ElasticsearchHome; 48 | var source = Path.Combine(homeSource, "config"); 49 | if (!Directory.Exists(source)) 50 | { 51 | cluster.Writer?.WriteDiagnostic( 52 | $"{{{nameof(CreateEphemeralDirectory)}}} source config {{{source}}} does not exist nothing to copy"); 53 | return; 54 | } 55 | 56 | cluster.Writer?.WriteDiagnostic( 57 | $"{{{nameof(CreateEphemeralDirectory)}}} copying cached {{{source}}} as to [{target}]"); 58 | CopyFolder(source, target); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/BeforeStartNodeTasks/PrintYamlContents.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using System.Linq; 7 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 8 | 9 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.BeforeStartNodeTasks 10 | { 11 | public class PrintYamlContents : ClusterComposeTask 12 | { 13 | public override void Run(IEphemeralCluster cluster) 14 | { 15 | var c = cluster.ClusterConfiguration; 16 | var v = c.Version; 17 | var fs = cluster.FileSystem; 18 | 19 | var files = Directory.GetFiles(fs.ConfigPath, "*.yml", SearchOption.AllDirectories); 20 | foreach (var file in files) DumpFile(cluster, file); 21 | } 22 | 23 | private static void DumpFile(IEphemeralCluster cluster, string configFile) 24 | { 25 | if (!File.Exists(configFile)) 26 | { 27 | cluster.Writer.WriteDiagnostic( 28 | $"{{{nameof(PrintYamlContents)}}} skipped printing [{configFile}] as it does not exists"); 29 | return; 30 | } 31 | 32 | var fileName = Path.GetFileName(configFile); 33 | cluster.Writer.WriteDiagnostic($"{{{nameof(PrintYamlContents)}}} printing [{configFile}]"); 34 | var lines = File.ReadAllLines(configFile).ToList(); 35 | foreach (var l in lines.Where(l => !string.IsNullOrWhiteSpace(l) && !l.StartsWith("#"))) 36 | cluster.Writer.WriteDiagnostic($"{{{nameof(PrintYamlContents)}}} [{fileName}] {l}"); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/BeforeStartNodeTasks/XPack/EnsureSecurityRealms.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 9 | 10 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.BeforeStartNodeTasks.XPack 11 | { 12 | public class EnsureSecurityRealms : ClusterComposeTask 13 | { 14 | public override void Run(IEphemeralCluster cluster) 15 | { 16 | if (!cluster.ClusterConfiguration.EnableSecurity) return; 17 | 18 | var configFile = Path.Combine(cluster.FileSystem.ConfigPath, "elasticsearch.yml"); 19 | cluster.Writer.WriteDiagnostic( 20 | $"{{{nameof(EnsureSecurityRealms)}}} attempting to add xpack realms to [{configFile}]"); 21 | var lines = File.ReadAllLines(configFile).ToList(); 22 | var saveFile = false; 23 | 24 | var major = cluster.ClusterConfiguration.Version.Major; 25 | saveFile = major >= 6 26 | ? major >= 7 ? Write7XAndUpRealms(lines, cluster.ClusterConfiguration.EnableSsl) : 27 | Write6XAndUpRealms(lines) 28 | : Write5XAndUpRealms(lines); 29 | 30 | if (saveFile) File.WriteAllLines(configFile, lines); 31 | cluster.Writer.WriteDiagnostic( 32 | $"{{{nameof(EnsureSecurityRealms)}}} {(saveFile ? "saved" : "skipped saving")} xpack realms to [{configFile}]"); 33 | } 34 | 35 | private static bool Write7XAndUpRealms(List lines, bool sslEnabled) 36 | { 37 | if (lines.Any(line => line.Contains("file1"))) return false; 38 | lines.AddRange(new[] 39 | { 40 | string.Empty, "xpack:", " security:", " authc:", " realms:", " file:", 41 | $" {SecurityRealms.FileRealm}:", " order: 0", string.Empty 42 | }); 43 | if (sslEnabled) 44 | lines.AddRange(new[] 45 | { 46 | " pki:", $" {SecurityRealms.PkiRealm}:", " order: 1", string.Empty 47 | }); 48 | return true; 49 | } 50 | 51 | private static bool Write6XAndUpRealms(List lines) 52 | { 53 | if (lines.Any(line => line.Contains("file1"))) return false; 54 | lines.AddRange(new[] 55 | { 56 | string.Empty, "xpack:", " security:", " authc:", " realms:", 57 | $" {SecurityRealms.FileRealm}:", " type: file", " order: 0", 58 | $" {SecurityRealms.PkiRealm}:", " type: pki", " order: 1", string.Empty 59 | }); 60 | 61 | return true; 62 | } 63 | 64 | private static bool Write5XAndUpRealms(List lines) 65 | { 66 | var saveFile = false; 67 | if (!lines.Any(line => line.Contains("file1"))) 68 | { 69 | lines.AddRange(new[] 70 | { 71 | string.Empty, "xpack:", " security:", " authc:", " realms:", 72 | $" {SecurityRealms.FileRealm}:", " type: file", " order: 0", 73 | string.Empty 74 | }); 75 | saveFile = true; 76 | } 77 | 78 | if (!lines.Any(line => line.Contains("pki1"))) 79 | { 80 | lines.AddRange(new[] 81 | { 82 | string.Empty, "xpack:", " security:", " authc:", " realms:", 83 | $" {SecurityRealms.PkiRealm}:", " type: pki", " order: 1", string.Empty 84 | }); 85 | saveFile = true; 86 | } 87 | 88 | return saveFile; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/BeforeStartNodeTasks/XPack/EnsureSecurityRolesFileExists.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using System.Linq; 7 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 8 | 9 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.BeforeStartNodeTasks.XPack 10 | { 11 | public class EnsureSecurityRolesFileExists : ClusterComposeTask 12 | { 13 | public override void Run(IEphemeralCluster cluster) 14 | { 15 | if (!cluster.ClusterConfiguration.EnableSecurity) return; 16 | if (!cluster.ClusterConfiguration.EnableSecurity) return; 17 | 18 | 19 | //2.x tests only use prebaked roles 20 | var v = cluster.ClusterConfiguration.Version; 21 | if (v.Major < 5) return; 22 | var folder = v.Major >= 5 ? "x-pack" : "shield"; 23 | var rolesConfig = v >= "6.3.0" 24 | ? Path.Combine(cluster.FileSystem.ConfigPath, "roles.yml") 25 | : Path.Combine(cluster.FileSystem.ConfigPath, folder, "roles.yml"); 26 | 27 | cluster.Writer.WriteDiagnostic( 28 | $"{{{nameof(EnsureSecurityRolesFileExists)}}} adding roles to {rolesConfig}"); 29 | if (!File.Exists(rolesConfig)) 30 | { 31 | cluster.Writer.WriteDiagnostic( 32 | $"{{{nameof(EnsureSecurityRolesFileExists)}}} {rolesConfig} does not exist"); 33 | Directory.CreateDirectory(Path.Combine(cluster.FileSystem.ConfigPath, folder)); 34 | File.WriteAllText(rolesConfig, string.Empty); 35 | } 36 | 37 | var lines = File.ReadAllLines(rolesConfig).ToList(); 38 | var saveFile = false; 39 | 40 | if (!lines.Any(line => line.StartsWith("user:"))) 41 | { 42 | lines.InsertRange(0, 43 | new[] 44 | { 45 | "# Read-only operations on indices", "user:", " indices:", " - names: '*'", 46 | " privileges:", " - read", string.Empty 47 | }); 48 | 49 | saveFile = true; 50 | } 51 | 52 | if (!lines.Any(line => line.StartsWith("power_user:"))) 53 | { 54 | lines.InsertRange(0, 55 | new[] 56 | { 57 | "# monitoring cluster privileges", "# All operations on all indices", "power_user:", 58 | " cluster:", " - monitor", " indices:", " - names: '*'", " privileges:", 59 | " - all", string.Empty 60 | }); 61 | 62 | saveFile = true; 63 | } 64 | 65 | if (!lines.Any(line => line.StartsWith("admin:"))) 66 | { 67 | lines.InsertRange(0, 68 | new[] 69 | { 70 | "# All cluster rights", "# All operations on all indices", "admin:", " cluster:", 71 | " - all", " indices:", " - names: '*'", " privileges:", " - all", 72 | string.Empty 73 | }); 74 | 75 | saveFile = true; 76 | } 77 | 78 | if (saveFile) File.WriteAllLines(rolesConfig, lines); 79 | cluster.Writer.WriteDiagnostic( 80 | $"{{{nameof(EnsureSecurityRolesFileExists)}}} {(saveFile ? "saved" : "skipped saving")} roles to [{rolesConfig}]"); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/BeforeStartNodeTasks/XPack/EnsureSecurityUsersInDefaultRealmAreAdded.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 7 | 8 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.BeforeStartNodeTasks.XPack 9 | { 10 | public class EnsureSecurityUsersInDefaultRealmAreAdded : ClusterComposeTask 11 | { 12 | public override void Run(IEphemeralCluster cluster) 13 | { 14 | if (!cluster.ClusterConfiguration.EnableSecurity) return; 15 | 16 | var config = cluster.ClusterConfiguration; 17 | var fileSystem = cluster.FileSystem; 18 | var v = config.Version; 19 | 20 | var xpackConfigFolder = 21 | v >= "6.3.0" ? fileSystem.ConfigPath : Path.Combine(fileSystem.ConfigPath, "x-pack"); 22 | ; 23 | var xpackConfigFolderCached = v >= "6.3.0" 24 | ? Path.Combine(fileSystem.LocalFolder, cluster.GetCacheFolderName(), "config") 25 | : Path.Combine(fileSystem.LocalFolder, cluster.GetCacheFolderName(), "config", "x-pack"); 26 | 27 | var usersFile = Path.Combine(xpackConfigFolder, "users"); 28 | var usersFileCached = usersFile.Replace(xpackConfigFolder, xpackConfigFolderCached); 29 | var usersRolesFile = Path.Combine(xpackConfigFolder, "users_roles"); 30 | var usersRolesFileCached = usersRolesFile.Replace(xpackConfigFolder, xpackConfigFolderCached); 31 | var userCachedFileInfo = new FileInfo(usersFileCached); 32 | 33 | if (userCachedFileInfo.Exists && userCachedFileInfo.Length > 0 && 34 | cluster.ClusterConfiguration.CacheEsHomeInstallation) 35 | { 36 | cluster.Writer?.WriteDiagnostic( 37 | $"{{{nameof(EnsureSecurityUsersInDefaultRealmAreAdded)}}} using cached users and users_roles files from {{{xpackConfigFolderCached}}}"); 38 | if (!Directory.Exists(xpackConfigFolder)) Directory.CreateDirectory(xpackConfigFolder); 39 | if (!File.Exists(usersFile)) File.Copy(usersFileCached, usersFile); 40 | if (!File.Exists(usersRolesFile)) File.Copy(usersRolesFileCached, usersRolesFile); 41 | } 42 | else 43 | { 44 | var folder = v.Major >= 5 ? v >= "6.3.0" ? string.Empty : "x-pack" : "shield"; 45 | var binary = v.Major >= 5 ? v >= "6.3.0" ? "elasticsearch-users" : "users" : "esusers"; 46 | 47 | var h = fileSystem.ElasticsearchHome; 48 | var pluginFolder = v >= "6.3.0" ? Path.Combine(h, "bin") : Path.Combine(h, "bin", folder); 49 | var pluginBat = Path.Combine(pluginFolder, binary) + BinarySuffix; 50 | 51 | foreach (var cred in ClusterAuthentication.AllUsers) 52 | { 53 | ExecuteBinary(cluster.ClusterConfiguration, cluster.Writer, pluginBat, 54 | $"adding user {cred.Username}", "useradd", cred.Username, "-p", cred.Password, "-r", cred.Role); 55 | } 56 | 57 | if (!Directory.Exists(xpackConfigFolderCached)) Directory.CreateDirectory(xpackConfigFolderCached); 58 | 59 | if (!File.Exists(usersFileCached)) File.Copy(usersFile, usersFileCached); 60 | if (!File.Exists(usersRolesFileCached)) File.Copy(usersRolesFile, usersRolesFileCached); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/InstallationTasks/CopyCachedEsInstallation.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 7 | 8 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.InstallationTasks 9 | { 10 | public class CopyCachedEsInstallation : ClusterComposeTask 11 | { 12 | public override void Run(IEphemeralCluster cluster) 13 | { 14 | if (!cluster.ClusterConfiguration.CacheEsHomeInstallation) return; 15 | 16 | var fs = cluster.FileSystem; 17 | var cachedEsHomeFolder = Path.Combine(fs.LocalFolder, cluster.GetCacheFolderName()); 18 | if (!Directory.Exists(cachedEsHomeFolder)) return; 19 | 20 | var source = cachedEsHomeFolder; 21 | var target = fs.ElasticsearchHome; 22 | cluster.Writer?.WriteDiagnostic( 23 | $"{{{nameof(CopyCachedEsInstallation)}}} using cached ES_HOME {{{source}}} and copying it to [{target}]"); 24 | CopyFolder(source, target); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/InstallationTasks/CreateLocalApplicationDirectory.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 7 | 8 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.InstallationTasks 9 | { 10 | public class CreateLocalApplicationDirectory : ClusterComposeTask 11 | { 12 | public override void Run(IEphemeralCluster cluster) 13 | { 14 | var fs = cluster.FileSystem; 15 | if (Directory.Exists(fs.LocalFolder)) 16 | { 17 | cluster.Writer?.WriteDiagnostic( 18 | $"{{{nameof(CreateLocalApplicationDirectory)}}} already exists: {{{fs.LocalFolder}}}"); 19 | return; 20 | } 21 | 22 | cluster.Writer?.WriteDiagnostic( 23 | $"{{{nameof(CreateLocalApplicationDirectory)}}} creating {{{fs.LocalFolder}}}"); 24 | 25 | Directory.CreateDirectory(fs.LocalFolder); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/InstallationTasks/DownloadElasticsearchVersion.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 7 | using Elastic.Stack.ArtifactsApi.Products; 8 | 9 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.InstallationTasks 10 | { 11 | public class DownloadElasticsearchVersion : ClusterComposeTask 12 | { 13 | public override void Run(IEphemeralCluster cluster) 14 | { 15 | if (cluster.CachingAndCachedHomeExists()) return; 16 | 17 | var fs = cluster.FileSystem; 18 | var v = cluster.ClusterConfiguration.Version; 19 | var a = cluster.ClusterConfiguration.Artifact; 20 | var from = v.Artifact(Product.Elasticsearch).DownloadUrl; 21 | var to = Path.Combine(fs.LocalFolder, a.Archive); 22 | if (File.Exists(to)) 23 | { 24 | cluster.Writer?.WriteDiagnostic( 25 | $"{{{nameof(DownloadElasticsearchVersion)}}} {v} was already downloaded"); 26 | return; 27 | } 28 | 29 | cluster.Writer?.WriteDiagnostic( 30 | $"{{{nameof(DownloadElasticsearchVersion)}}} downloading Elasticsearch [{v}] from {{{from}}} {{{to}}}"); 31 | DownloadFile(from, to); 32 | cluster.Writer?.WriteDiagnostic( 33 | $"{{{nameof(DownloadElasticsearchVersion)}}} downloaded Elasticsearch [{v}] from {{{from}}} {{{to}}}"); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/InstallationTasks/EnsureElasticsearchBatWorksAcrossDrives.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 7 | 8 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.InstallationTasks 9 | { 10 | /// Fixes https://github.com/elastic/elasticsearch/issues/29057 11 | public class EnsureElasticsearchBatWorksAcrossDrives : ClusterComposeTask 12 | { 13 | public override void Run(IEphemeralCluster cluster) 14 | { 15 | if (cluster.CachingAndCachedHomeExists()) return; 16 | 17 | var config = cluster.ClusterConfiguration; 18 | if (config.Version < "6.2.0" || config.Version >= "6.3.0") 19 | return; 20 | 21 | var batFile = cluster.FileSystem.Binary; 22 | cluster.Writer.WriteDiagnostic( 23 | $"{{{nameof(EnsureElasticsearchBatWorksAcrossDrives)}}} patching {batFile} according to elastic/elasticsearch#29057"); 24 | var contents = File.ReadAllLines(batFile); 25 | for (var i = 0; i < contents.Length; i++) 26 | { 27 | if (contents[i] != "cd \"%ES_HOME%\"") continue; 28 | 29 | contents[i] = "cd /d \"%ES_HOME%\""; 30 | break; 31 | } 32 | 33 | File.WriteAllLines(batFile, contents); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/InstallationTasks/EnsureJavaHomeEnvironmentVariableIsSet.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.IO; 7 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 8 | 9 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.InstallationTasks 10 | { 11 | public class EnsureJavaHomeEnvironmentVariableIsSet : ClusterComposeTask 12 | { 13 | public override void Run(IEphemeralCluster cluster) 14 | { 15 | var fs = cluster.FileSystem; 16 | 17 | var java8Home = Environment.GetEnvironmentVariable("JAVA8_HOME"); 18 | if (cluster.ClusterConfiguration.Version < "6.0.0" && !string.IsNullOrWhiteSpace(java8Home)) 19 | { 20 | //EnvironmentVariableTarget.Process only using this overload 21 | Environment.SetEnvironmentVariable("JAVA_HOME", java8Home); 22 | cluster.Writer?.WriteDiagnostic( 23 | $"{{{nameof(EnsureJavaHomeEnvironmentVariableIsSet)}}} Forcing [JAVA8_HOME] as [JAVA_HOME] since we are on Elasticsearch <6.0.0"); 24 | } 25 | 26 | var envVarName = cluster.ClusterConfiguration.JavaHomeEnvironmentVariable; 27 | var javaHome = Environment.GetEnvironmentVariable(envVarName); 28 | 29 | //7.0.0 ships with its own JDK 30 | if (cluster.ClusterConfiguration.Version < "7.0.0" && string.IsNullOrWhiteSpace(javaHome)) 31 | { 32 | cluster.Writer?.WriteDiagnostic( 33 | $"{{{nameof(EnsureJavaHomeEnvironmentVariableIsSet)}}} [{envVarName}] is not SET exiting.."); 34 | throw new Exception( 35 | "The elasticsearch bat files are resilient to JAVA_HOME not being set, however the shield tooling is not"); 36 | } 37 | 38 | var cachedEsHomeFolder = Path.Combine(fs.LocalFolder, cluster.GetCacheFolderName()); 39 | var jdkFolder = Path.Combine(cachedEsHomeFolder, "jdk"); 40 | if (Directory.Exists(jdkFolder)) 41 | { 42 | //prefer bundled jdk 43 | cluster.Writer?.WriteDiagnostic( 44 | $"{{{nameof(EnsureJavaHomeEnvironmentVariableIsSet)}}} [{envVarName}] is set to bundled jdk: {{{jdkFolder}}} "); 45 | Environment.SetEnvironmentVariable("JAVA_HOME", jdkFolder); 46 | } 47 | else if (cluster.ClusterConfiguration.Version >= "7.0.0" && !string.IsNullOrWhiteSpace(javaHome)) 48 | { 49 | cluster.Writer?.WriteDiagnostic( 50 | $"{{{nameof(EnsureJavaHomeEnvironmentVariableIsSet)}}} [{envVarName}] is set; clearing value for process to prefer bundled jdk..."); 51 | Environment.SetEnvironmentVariable(envVarName, null); 52 | } 53 | else 54 | cluster.Writer?.WriteDiagnostic( 55 | $"{{{nameof(EnsureJavaHomeEnvironmentVariableIsSet)}}} {envVarName} is not set proceeding or using default JDK"); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/InstallationTasks/InstallPlugins.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.IO; 7 | using System.Linq; 8 | using Elastic.Elasticsearch.Managed; 9 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 10 | using Elastic.Elasticsearch.Managed.FileSystem; 11 | using Elastic.Stack.ArtifactsApi; 12 | using Elastic.Stack.ArtifactsApi.Products; 13 | using ProcNet.Std; 14 | 15 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.InstallationTasks 16 | { 17 | public class InstallPlugins : ClusterComposeTask 18 | { 19 | public override void Run(IEphemeralCluster cluster) 20 | { 21 | if (cluster.CachingAndCachedHomeExists()) return; 22 | 23 | var v = cluster.ClusterConfiguration.Version; 24 | 25 | //on 2.x we do not support tests requiring plugins for 2.x since we can not reliably install them 26 | if (v.Major == 2) 27 | { 28 | cluster.Writer?.WriteDiagnostic( 29 | $"{{{nameof(InstallPlugins)}}} skipping install plugins on {{2.x}} version: [{v}]"); 30 | return; 31 | } 32 | 33 | if (v.Major < 7 && v.ArtifactBuildState == ArtifactBuildState.Snapshot) 34 | { 35 | cluster.Writer?.WriteDiagnostic( 36 | $"{{{nameof(InstallPlugins)}}} skipping install SNAPSHOT plugins on < {{7.x}} version: [{v}]"); 37 | return; 38 | } 39 | 40 | var fs = cluster.FileSystem; 41 | var requiredPlugins = cluster.ClusterConfiguration.Plugins; 42 | 43 | if (cluster.ClusterConfiguration.ValidatePluginsToInstall) 44 | { 45 | var invalidPlugins = requiredPlugins 46 | .Where(p => !p.IsValid(v)) 47 | .Select(p => p.SubProductName).ToList(); 48 | if (invalidPlugins.Any()) 49 | throw new ElasticsearchCleanExitException( 50 | $"Can not install the following plugins for version {v}: {string.Join(", ", invalidPlugins)} "); 51 | } 52 | 53 | foreach (var plugin in requiredPlugins) 54 | { 55 | var includedByDefault = plugin.IsIncludedOutOfTheBox(v); 56 | if (includedByDefault) 57 | { 58 | cluster.Writer?.WriteDiagnostic( 59 | $"{{{nameof(Run)}}} SKIP plugin [{plugin.SubProductName}] shipped OOTB as of: {{{plugin.ShippedByDefaultAsOf}}}"); 60 | continue; 61 | } 62 | 63 | var validForCurrentVersion = plugin.IsValid(v); 64 | if (!validForCurrentVersion) 65 | { 66 | cluster.Writer?.WriteDiagnostic( 67 | $"{{{nameof(Run)}}} SKIP plugin [{plugin.SubProductName}] not valid for version: {{{v}}}"); 68 | continue; 69 | } 70 | 71 | var alreadyInstalled = AlreadyInstalled(fs, plugin.SubProductName); 72 | if (alreadyInstalled) 73 | { 74 | cluster.Writer?.WriteDiagnostic( 75 | $"{{{nameof(Run)}}} SKIP plugin [{plugin.SubProductName}] already installed"); 76 | continue; 77 | } 78 | 79 | cluster.Writer?.WriteDiagnostic( 80 | $"{{{nameof(Run)}}} attempting install [{plugin.SubProductName}] as it's not OOTB: {{{plugin.ShippedByDefaultAsOf}}} and valid for {v}: {{{plugin.IsValid(v)}}}"); 81 | //var installParameter = v.ReleaseState == ReleaseState.Released ? plugin.Moniker : UseHttpPluginLocation(cluster.Writer, fs, plugin, v); 82 | var installParameter = UseHttpPluginLocation(cluster.Writer, fs, plugin, v); 83 | if (!Directory.Exists(fs.ConfigPath)) Directory.CreateDirectory(fs.ConfigPath); 84 | ExecuteBinary( 85 | cluster.ClusterConfiguration, 86 | cluster.Writer, 87 | fs.PluginBinary, 88 | $"install elasticsearch plugin: {plugin.SubProductName}", 89 | "install --batch", installParameter); 90 | 91 | CopyConfigDirectoryToHomeCacheConfigDirectory(cluster, plugin); 92 | } 93 | } 94 | 95 | private static void CopyConfigDirectoryToHomeCacheConfigDirectory( 96 | IEphemeralCluster cluster, ElasticsearchPlugin plugin) 97 | { 98 | if (plugin.SubProductName == "x-pack") return; 99 | if (!cluster.ClusterConfiguration.CacheEsHomeInstallation) return; 100 | var fs = cluster.FileSystem; 101 | var cachedEsHomeFolder = Path.Combine(fs.LocalFolder, cluster.GetCacheFolderName()); 102 | var configTarget = Path.Combine(cachedEsHomeFolder, "config"); 103 | 104 | var configPluginPath = Path.Combine(fs.ConfigPath, plugin.SubProductName); 105 | var configPluginPathCached = Path.Combine(configTarget, plugin.SubProductName); 106 | if (!Directory.Exists(configPluginPath) || Directory.Exists(configPluginPathCached)) return; 107 | 108 | Directory.CreateDirectory(configPluginPathCached); 109 | CopyFolder(configPluginPath, configPluginPathCached); 110 | } 111 | 112 | private static bool AlreadyInstalled(INodeFileSystem fileSystem, string folderName) 113 | { 114 | var pluginFolder = Path.Combine(fileSystem.ElasticsearchHome, "plugins", folderName); 115 | return Directory.Exists(pluginFolder); 116 | } 117 | 118 | private static string UseHttpPluginLocation(IConsoleLineHandler writer, INodeFileSystem fileSystem, 119 | ElasticsearchPlugin plugin, ElasticVersion v) 120 | { 121 | var downloadLocation = Path.Combine(fileSystem.LocalFolder, $"{plugin.SubProductName}-{v}.zip"); 122 | DownloadPluginSnapshot(writer, downloadLocation, plugin, v); 123 | //transform downloadLocation to file uri and use that to install from 124 | return new Uri(new Uri("file://"), downloadLocation).AbsoluteUri; 125 | } 126 | 127 | private static void DownloadPluginSnapshot(IConsoleLineHandler writer, string downloadLocation, 128 | ElasticsearchPlugin plugin, ElasticVersion v) 129 | { 130 | if (File.Exists(downloadLocation)) return; 131 | var artifact = v.Artifact(Product.ElasticsearchPlugin(plugin)); 132 | var downloadUrl = artifact.DownloadUrl; 133 | writer?.WriteDiagnostic( 134 | $"{{{nameof(DownloadPluginSnapshot)}}} downloading [{plugin.SubProductName}] from {{{downloadUrl}}}"); 135 | try 136 | { 137 | DownloadFile(downloadUrl, downloadLocation); 138 | writer?.WriteDiagnostic( 139 | $"{{{nameof(DownloadPluginSnapshot)}}} downloaded [{plugin.SubProductName}] to {{{downloadLocation}}}"); 140 | } 141 | catch (Exception) 142 | { 143 | writer?.WriteDiagnostic( 144 | $"{{{nameof(DownloadPluginSnapshot)}}} download failed! [{plugin.SubProductName}] from {{{downloadUrl}}}"); 145 | throw; 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/InstallationTasks/PrintConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Linq; 7 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 8 | using static Elastic.Elasticsearch.Ephemeral.ClusterFeatures; 9 | 10 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.InstallationTasks 11 | { 12 | public class PrintConfiguration : ClusterComposeTask 13 | { 14 | public override void Run(IEphemeralCluster cluster) 15 | { 16 | var c = cluster.ClusterConfiguration; 17 | var version = c.Version; 18 | 19 | string F(ClusterFeatures feature) 20 | { 21 | return c.Features.HasFlag(feature) ? Enum.GetName(typeof(ClusterFeatures), feature) : string.Empty; 22 | } 23 | 24 | var features = string.Join("|", 25 | new[] {F(Security), F(ClusterFeatures.XPack), F(SSL)}.Where(v => !string.IsNullOrWhiteSpace(v))); 26 | features = string.IsNullOrWhiteSpace(features) ? "None" : features; 27 | cluster.Writer?.WriteDiagnostic( 28 | $"{{{nameof(PrintConfiguration)}}} starting {{{version}}} with features [{features}]"); 29 | cluster.Writer?.WriteDiagnostic( 30 | $"{{{nameof(PrintConfiguration)}}} {{{nameof(c.NumberOfNodes)}}} [{c.NumberOfNodes}]"); 31 | cluster.Writer?.WriteDiagnostic( 32 | $"{{{nameof(PrintConfiguration)}}} {{{nameof(c.ClusterName)}}} [{c.ClusterName}]"); 33 | cluster.Writer?.WriteDiagnostic( 34 | $"{{{nameof(PrintConfiguration)}}} {{{nameof(c.EnableSsl)}}} [{c.EnableSsl}]"); 35 | cluster.Writer?.WriteDiagnostic( 36 | $"{{{nameof(PrintConfiguration)}}} {{{nameof(c.EnableSecurity)}}} [{c.EnableSecurity}]"); 37 | cluster.Writer?.WriteDiagnostic( 38 | $"{{{nameof(PrintConfiguration)}}} {{{nameof(c.XPackInstalled)}}} [{c.XPackInstalled}]"); 39 | cluster.Writer?.WriteDiagnostic($"{{{nameof(PrintConfiguration)}}} {{{nameof(c.Plugins)}}} [{c.Plugins}]"); 40 | cluster.Writer?.WriteDiagnostic( 41 | $"{{{nameof(PrintConfiguration)}}} {{{nameof(c.TrialMode)}}} [{c.TrialMode}]"); 42 | cluster.Writer?.WriteDiagnostic( 43 | $"{{{nameof(PrintConfiguration)}}} {{{nameof(c.CacheEsHomeInstallation)}}} [{c.CacheEsHomeInstallation}]"); 44 | cluster.Writer?.WriteDiagnostic( 45 | $"{{{nameof(PrintConfiguration)}}} {{{nameof(c.ShowElasticsearchOutputAfterStarted)}}} [{c.ShowElasticsearchOutputAfterStarted}]"); 46 | cluster.Writer?.WriteDiagnostic( 47 | $"{{{nameof(PrintConfiguration)}}} {{{nameof(c.ValidatePluginsToInstall)}}} [{c.ValidatePluginsToInstall}]"); 48 | cluster.Writer?.WriteDiagnostic( 49 | $"{{{nameof(PrintConfiguration)}}} {{{nameof(c.PrintYamlFilesInConfigFolder)}}} [{c.PrintYamlFilesInConfigFolder}]"); 50 | cluster.Writer?.WriteDiagnostic( 51 | $"{{{nameof(PrintConfiguration)}}} {{{nameof(c.SkipBuiltInAfterStartTasks)}}} [{c.SkipBuiltInAfterStartTasks}]"); 52 | cluster.Writer?.WriteDiagnostic( 53 | $"{{{nameof(PrintConfiguration)}}} {{{nameof(c.AutoWireKnownProxies)}}} [{c.AutoWireKnownProxies}]"); 54 | cluster.Writer?.WriteDiagnostic( 55 | $"{{{nameof(PrintConfiguration)}}} {{{nameof(c.NoCleanupAfterNodeStopped)}}} [{c.NoCleanupAfterNodeStopped}]"); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/InstallationTasks/SetElasticsearchBundledJdkJavaHome.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.IO; 7 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 8 | 9 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.InstallationTasks 10 | { 11 | public class SetElasticsearchBundledJdkJavaHome : ClusterComposeTask 12 | { 13 | public override void Run(IEphemeralCluster cluster) 14 | { 15 | var fs = cluster.FileSystem; 16 | var jdkFolder = Path.Combine(fs.ElasticsearchHome, "jdk"); 17 | if (Directory.Exists(jdkFolder)) 18 | { 19 | var envVarName = cluster.ClusterConfiguration.JavaHomeEnvironmentVariable; 20 | cluster.Writer?.WriteDiagnostic( 21 | $"{{{nameof(SetElasticsearchBundledJdkJavaHome)}}} [{envVarName}] is set to bundled jdk: {{{jdkFolder}}} "); 22 | Environment.SetEnvironmentVariable(envVarName, jdkFolder); 23 | } 24 | else 25 | cluster.Writer?.WriteDiagnostic( 26 | $"{{{nameof(SetElasticsearchBundledJdkJavaHome)}}} [No bundled jdk found] looked in: {{{jdkFolder}}} "); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/InstallationTasks/UnzipElasticsearch.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 7 | 8 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.InstallationTasks 9 | { 10 | public class UnzipElasticsearch : ClusterComposeTask 11 | { 12 | public override void Run(IEphemeralCluster cluster) 13 | { 14 | if (cluster.CachingAndCachedHomeExists()) return; 15 | 16 | var fs = cluster.FileSystem; 17 | var v = cluster.ClusterConfiguration.Version; 18 | var a = cluster.ClusterConfiguration.Artifact; 19 | if (Directory.Exists(fs.ElasticsearchHome)) 20 | { 21 | cluster.Writer?.WriteDiagnostic( 22 | $"{{{nameof(UnzipElasticsearch)}}} skipping [{fs.ElasticsearchHome}] already exists"); 23 | return; 24 | } 25 | 26 | var from = Path.Combine(fs.LocalFolder, a.Archive); 27 | var extractedFolder = Path.Combine(fs.LocalFolder, a.FolderInZip); 28 | if (!Directory.Exists(extractedFolder)) 29 | { 30 | cluster.Writer?.WriteDiagnostic($"{{{nameof(UnzipElasticsearch)}}} unzipping version [{v}] {{{from}}}"); 31 | Extract(from, fs.LocalFolder); 32 | 33 | cluster.Writer?.WriteDiagnostic( 34 | $"{{{nameof(UnzipElasticsearch)}}} extracted version [{v}] to {{{fs.LocalFolder}}}"); 35 | } 36 | 37 | if (extractedFolder == fs.ElasticsearchHome) return; 38 | 39 | cluster.Writer?.WriteDiagnostic( 40 | $"{{{nameof(UnzipElasticsearch)}}} Copying extracted folder {{{extractedFolder}}} => {fs.ElasticsearchHome}"); 41 | CopyFolder(extractedFolder, fs.ElasticsearchHome); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/InstallationTasks/XPack/EnsureXPackEnvBinaryExists.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using Elastic.Elasticsearch.Ephemeral.Tasks.BeforeStartNodeTasks.XPack; 7 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 8 | 9 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.InstallationTasks.XPack 10 | { 11 | public class EnsureXPackEnvBinaryExists : ClusterComposeTask 12 | { 13 | public override void Run(IEphemeralCluster cluster) 14 | { 15 | if (!cluster.ClusterConfiguration.XPackInstalled) return; 16 | if (cluster.CachingAndCachedHomeExists()) return; 17 | 18 | var config = cluster.ClusterConfiguration; 19 | var v = config.Version; 20 | 21 | var envBinary = Path.Combine(config.FileSystem.ElasticsearchHome, "bin", "x-pack", "x-pack-env") + 22 | BinarySuffix; 23 | 24 | if (v.Major != 6 || File.Exists(envBinary)) return; 25 | if (v >= "6.3.0") return; 26 | 27 | cluster.Writer.WriteDiagnostic( 28 | $"{{{nameof(EnsureSecurityUsersInDefaultRealmAreAdded)}}} {envBinary} does not exist, patching now."); 29 | File.WriteAllText(envBinary, "set ES_CLASSPATH=!ES_CLASSPATH!;!ES_HOME!/plugins/x-pack/*"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/InstallationTasks/XPack/PathXPackInBatFile.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 7 | using Elastic.Elasticsearch.Managed.FileSystem; 8 | using Elastic.Stack.ArtifactsApi; 9 | 10 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.InstallationTasks.XPack 11 | { 12 | public class PathXPackInBatFile : ClusterComposeTask 13 | { 14 | public override void Run(IEphemeralCluster cluster) 15 | { 16 | if (!cluster.ClusterConfiguration.XPackInstalled) return; 17 | 18 | var config = cluster.ClusterConfiguration; 19 | var fileSystem = cluster.FileSystem; 20 | var v = config.Version; 21 | 22 | if (v.Major != 5) return; 23 | if (!IsWindows) return; 24 | 25 | cluster.Writer?.WriteDiagnostic( 26 | $"{{{nameof(PathXPackInBatFile)}}} patching x-pack .in.bat to accept CONF_DIR"); 27 | PatchPlugin(v, fileSystem); 28 | } 29 | 30 | private static void PatchPlugin(ElasticVersion v, INodeFileSystem fileSystem) 31 | { 32 | var h = fileSystem.ElasticsearchHome; 33 | var file = Path.Combine(h, "bin", "x-pack", ".in.bat"); 34 | var contents = File.ReadAllText(file); 35 | contents = contents.Replace("set ES_PARAMS=-Des.path.home=\"%ES_HOME%\"", 36 | "set ES_PARAMS=-Des.path.home=\"%ES_HOME%\" -Des.path.conf=\"%CONF_DIR%\""); 37 | File.WriteAllText(file, contents); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/ValidationTasks/PostLicenseTask.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 7 | 8 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.ValidationTasks 9 | { 10 | public class PostLicenseTask : ClusterComposeTask 11 | { 12 | public override void Run(IEphemeralCluster cluster) 13 | { 14 | if (!cluster.ClusterConfiguration.XPackInstalled) return; 15 | if (string.IsNullOrWhiteSpace(cluster.ClusterConfiguration.XPackLicenseJson)) 16 | { 17 | cluster.Writer.WriteDiagnostic($"{{{nameof(PostLicenseTask)}}} no license file available to post"); 18 | StartTrial(cluster); 19 | return; 20 | } 21 | 22 | cluster.Writer.WriteDiagnostic($"{{{nameof(PostLicenseTask)}}} attempting to post license json"); 23 | 24 | var licenseUrl = cluster.ClusterConfiguration.Version.Major < 8 ? "_xpack/license" : "_license"; 25 | var postResponse = Post(cluster, licenseUrl, "", cluster.ClusterConfiguration.XPackLicenseJson); 26 | if (postResponse != null && postResponse.IsSuccessStatusCode) return; 27 | 28 | var details = postResponse != null ? GetResponseException(postResponse) : ""; 29 | throw new Exception($"The license that was posted was not accepted: {details}"); 30 | } 31 | 32 | private void StartTrial(IEphemeralCluster cluster) 33 | { 34 | var w = cluster.Writer; 35 | var c = cluster.ClusterConfiguration; 36 | if (c.Version < "6.3.0" || c.TrialMode == XPackTrialMode.None) 37 | { 38 | cluster.Writer.WriteDiagnostic( 39 | $"{{{nameof(PostLicenseTask)}}} {c.Version} < 6.3.0 or opting out of explicit basic/trial license"); 40 | return; 41 | } 42 | 43 | var licenseUrl = cluster.ClusterConfiguration.Version.Major < 8 ? "_xpack/license" : "_license"; 44 | if (c.TrialMode == XPackTrialMode.Trial) 45 | { 46 | //TODO make this configurable for either trial or basic 47 | cluster.Writer.WriteDiagnostic($"{{{nameof(PostLicenseTask)}}} attempt to start trial license"); 48 | var postResponse = Post(cluster, $"{licenseUrl}/start_trial", "acknowledge=true", string.Empty); 49 | if (postResponse != null && postResponse.IsSuccessStatusCode) return; 50 | } 51 | 52 | if (c.TrialMode == XPackTrialMode.Basic) 53 | { 54 | //TODO make this configurable for either trial or basic 55 | cluster.Writer.WriteDiagnostic($"{{{nameof(PostLicenseTask)}}} attempt to start basic license"); 56 | var postResponse = Post(cluster, $"{licenseUrl}/start_basic", "acknowledge=true", string.Empty); 57 | if (postResponse != null && postResponse.IsSuccessStatusCode) return; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/ValidationTasks/ValidateClusterStateTask.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Net; 7 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 8 | 9 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.ValidationTasks 10 | { 11 | public class ValidateClusterStateTask : ClusterComposeTask 12 | { 13 | public override void Run(IEphemeralCluster cluster) 14 | { 15 | cluster.Writer.WriteDiagnostic( 16 | $"{{{nameof(ValidateClusterStateTask)}}} waiting cluster to go into yellow health state"); 17 | var healthyResponse = Get(cluster, "_cluster/health", "wait_for_status=yellow&timeout=20s"); 18 | if (healthyResponse == null || healthyResponse.StatusCode != HttpStatusCode.OK) 19 | throw new Exception( 20 | $"Cluster health waiting for status yellow failed after 20s {GetResponseException(healthyResponse)}"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/ValidationTasks/ValidateLicenseTask.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Linq; 7 | using System.Threading; 8 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 9 | 10 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.ValidationTasks 11 | { 12 | public class ValidateLicenseTask : ClusterComposeTask 13 | { 14 | public override void Run(IEphemeralCluster cluster) 15 | { 16 | if (!cluster.ClusterConfiguration.XPackInstalled) return; 17 | 18 | cluster.Writer.WriteDiagnostic( 19 | $"{{{nameof(ValidateLicenseTask)}}} validating the x-pack license is active"); 20 | var licenseType = GetLicenseType(cluster); 21 | 22 | var licenseStatus = GetLicenseStatus(cluster); 23 | if (licenseStatus == "active") return; 24 | throw new Exception( 25 | $"x-pack is installed but the {licenseType} license was expected to be active but is {licenseStatus}"); 26 | } 27 | 28 | private string GetLicenseStatus(IEphemeralCluster cluster) => 29 | LicenseInfo(cluster, "license.status"); 30 | 31 | private string GetLicenseType(IEphemeralCluster cluster) => 32 | LicenseInfo(cluster, "license.type"); 33 | 34 | private string LicenseInfo(IEphemeralCluster cluster, string filter, 35 | int retries = 0) 36 | { 37 | var licenseUrl = cluster.ClusterConfiguration.Version.Major < 8 ? "_xpack/license" : "_license"; 38 | var getLicense = Get(cluster, licenseUrl, "filter_path=" + filter); 39 | if ((getLicense == null || !getLicense.IsSuccessStatusCode) && retries >= 5) 40 | throw new Exception( 41 | $"Calling GET _xpack/license did not result in an OK response after trying {retries} {GetResponseException(getLicense)}"); 42 | 43 | if (getLicense == null || !getLicense.IsSuccessStatusCode) 44 | { 45 | Thread.Sleep(TimeSpan.FromSeconds(10)); 46 | return LicenseInfo(cluster, filter, ++retries); 47 | } 48 | 49 | var licenseStatusString = GetResponseString(getLicense) 50 | .Where(c => !new[] {' ', '\n', '{', '"', '}'}.Contains(c)) 51 | .ToArray(); 52 | var status = new string(licenseStatusString).Replace($"{filter.Replace(".", ":")}:", ""); 53 | return status; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/ValidationTasks/ValidatePluginsTask.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Linq; 7 | using Elastic.Elasticsearch.Ephemeral.Tasks.InstallationTasks; 8 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 9 | using Elastic.Stack.ArtifactsApi; 10 | 11 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.ValidationTasks 12 | { 13 | public class ValidatePluginsTask : ClusterComposeTask 14 | { 15 | public override void Run(IEphemeralCluster cluster) 16 | { 17 | var v = cluster.ClusterConfiguration.Version; 18 | if (v.Major == 2) 19 | { 20 | cluster.Writer?.WriteDiagnostic( 21 | $"{{{nameof(ValidatePluginsTask)}}} skipping validate plugins on {{2.x}} version: [{v}]"); 22 | return; 23 | } 24 | 25 | if (v.Major < 7 && v.ArtifactBuildState == ArtifactBuildState.Snapshot) 26 | { 27 | cluster.Writer?.WriteDiagnostic( 28 | $"{{{nameof(InstallPlugins)}}} skipping validate SNAPSHOT plugins on < {{7.x}} version: [{v}]"); 29 | return; 30 | } 31 | 32 | var requestPlugins = cluster.ClusterConfiguration.Plugins 33 | .Where(p => p.IsValid(v)) 34 | .Where(p => !p.IsIncludedOutOfTheBox(v)) 35 | .Select(p => p.GetExistsMoniker(v)) 36 | .ToList(); 37 | if (!requestPlugins.Any()) return; 38 | 39 | cluster.Writer.WriteDiagnostic( 40 | $"{{{nameof(ValidatePluginsTask)}}} validating the cluster is running the requested plugins"); 41 | var catPlugins = Get(cluster, "_cat/plugins", "h=component"); 42 | if (catPlugins == null || !catPlugins.IsSuccessStatusCode) 43 | throw new Exception( 44 | $"Calling _cat/plugins did not result in an OK response {GetResponseException(catPlugins)}"); 45 | 46 | var installedPlugins = GetResponseString(catPlugins) 47 | .Split(new[] {'\n'}, StringSplitOptions.RemoveEmptyEntries).ToList(); 48 | 49 | var missingPlugins = requestPlugins.Except(installedPlugins).ToList(); 50 | if (!missingPlugins.Any()) return; 51 | 52 | var missingString = string.Join(", ", missingPlugins); 53 | var pluginsString = string.Join(", ", installedPlugins); 54 | throw new Exception( 55 | $"Already running elasticsearch missed the following plugin(s): {missingString} currently installed: {pluginsString}."); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Ephemeral/Tasks/ValidationTasks/ValidateRunningVersion.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Linq; 7 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 8 | 9 | namespace Elastic.Elasticsearch.Ephemeral.Tasks.ValidationTasks 10 | { 11 | public class ValidateRunningVersion : ClusterComposeTask 12 | { 13 | public override void Run(IEphemeralCluster cluster) 14 | { 15 | var requestedVersion = cluster.ClusterConfiguration.Version; 16 | 17 | cluster.Writer?.WriteDiagnostic( 18 | $"{{{nameof(ValidateRunningVersion)}}} validating the cluster is running the requested version: {requestedVersion}"); 19 | 20 | var catNodes = Get(cluster, "_cat/nodes", "h=version"); 21 | if (catNodes == null || !catNodes.IsSuccessStatusCode) 22 | throw new Exception( 23 | $"Calling _cat/nodes for version checking did not result in an OK response {GetResponseException(catNodes)}"); 24 | 25 | var nodeVersions = GetResponseString(catNodes).Split(new[] {'\n'}, StringSplitOptions.RemoveEmptyEntries) 26 | .ToList(); 27 | var allOnRequestedVersion = false; 28 | 29 | // fully qualified name not returned anymore, so beta1 rc1 etc. is no longer returned in the version number 30 | if (requestedVersion.Major >= 7) 31 | { 32 | var anchorVersion = $"{requestedVersion.Major}.{requestedVersion.Minor}.{requestedVersion.Patch}"; 33 | allOnRequestedVersion = nodeVersions.All(v => v.Trim() == anchorVersion); 34 | if (!allOnRequestedVersion) 35 | throw new Exception( 36 | $"Not all the running nodes in the cluster are on requested version: {anchorVersion} received: {string.Join(", ", nodeVersions)}"); 37 | } 38 | else 39 | { 40 | var requestedVersionNoSnapShot = 41 | cluster.ClusterConfiguration.Version.ToString().Replace("-SNAPSHOT", ""); 42 | allOnRequestedVersion = nodeVersions.All(v => 43 | v.Trim() == requestedVersion || v.Trim() == requestedVersionNoSnapShot); 44 | 45 | if (!allOnRequestedVersion) 46 | throw new Exception( 47 | $"Not all the running nodes in the cluster are on requested version: {requestedVersion} received: {string.Join(", ", nodeVersions)}"); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/Configuration/ClusterConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Globalization; 7 | using System.IO; 8 | using Elastic.Elasticsearch.Managed.FileSystem; 9 | using Elastic.Stack.ArtifactsApi; 10 | using Elastic.Stack.ArtifactsApi.Products; 11 | 12 | namespace Elastic.Elasticsearch.Managed.Configuration 13 | { 14 | public interface IClusterConfiguration where TFileSystem : INodeFileSystem 15 | { 16 | TFileSystem FileSystem { get; } 17 | 18 | string ClusterName { get; } 19 | NodeSettings DefaultNodeSettings { get; } 20 | ElasticVersion Version { get; } 21 | int NumberOfNodes { get; } 22 | int StartingPortNumber { get; set; } 23 | bool NoCleanupAfterNodeStopped { get; set; } 24 | 25 | bool ShowElasticsearchOutputAfterStarted { get; set; } 26 | bool CacheEsHomeInstallation { get; set; } 27 | 28 | string CreateNodeName(int? node); 29 | } 30 | 31 | public class ClusterConfiguration : ClusterConfiguration 32 | { 33 | public ClusterConfiguration(ElasticVersion version, string esHome, int numberOfNodes = 1) 34 | : base(version, (v, s) => new NodeFileSystem(v, esHome), numberOfNodes, null) 35 | { 36 | } 37 | 38 | public ClusterConfiguration(ElasticVersion version, 39 | Func fileSystem = null, int numberOfNodes = 1, 40 | string clusterName = null) 41 | : base(version, fileSystem ?? ((v, s) => new NodeFileSystem(v, s)), numberOfNodes, clusterName) 42 | { 43 | } 44 | } 45 | 46 | public class ClusterConfiguration : IClusterConfiguration 47 | where TFileSystem : INodeFileSystem 48 | { 49 | /// 50 | /// Creates a new instance of a configuration for an Elasticsearch cluster. 51 | /// 52 | /// The version of Elasticsearch 53 | /// 54 | /// A delegate to create the instance of . 55 | /// Passed the Elasticsearch version and the Cluster name 56 | /// 57 | /// The number of nodes in the cluster 58 | /// The name of the cluster 59 | public ClusterConfiguration(ElasticVersion version, Func fileSystem, 60 | int numberOfNodes = 1, string clusterName = null) 61 | { 62 | if (fileSystem == null) throw new ArgumentException(nameof(fileSystem)); 63 | 64 | ClusterName = clusterName; 65 | Version = version; 66 | Artifact = version.Artifact(Product.Elasticsearch); 67 | FileSystem = fileSystem(Version, ClusterName); 68 | NumberOfNodes = numberOfNodes; 69 | 70 | var fs = FileSystem; 71 | Add("node.max_local_storage_nodes", numberOfNodes.ToString(CultureInfo.InvariantCulture), "<8.0.0"); 72 | 73 | if (version < "7.0.0-beta1") 74 | Add("discovery.zen.minimum_master_nodes", Quorum(numberOfNodes).ToString(CultureInfo.InvariantCulture)); 75 | 76 | Add("cluster.name", clusterName); 77 | Add("path.repo", fs.RepositoryPath); 78 | Add("path.data", fs.DataPath); 79 | var logsPathDefault = Path.Combine(fs.ElasticsearchHome, "logs"); 80 | if (logsPathDefault != fs.LogsPath) Add("path.logs", fs.LogsPath); 81 | 82 | if (version.Major < 6) Add("path.conf", fs.ConfigPath); 83 | 84 | } 85 | 86 | public Artifact Artifact { get; } 87 | 88 | public string JavaHomeEnvironmentVariable => Version.InRange("<7.12.0") ? "JAVA_HOME" : "ES_JAVA_HOME"; 89 | 90 | /// Will print the contents of all the yaml files when starting the cluster up, great for debugging purposes 91 | public bool PrintYamlFilesInConfigFolder { get; set; } 92 | 93 | public string ClusterName { get; } 94 | public ElasticVersion Version { get; } 95 | public TFileSystem FileSystem { get; } 96 | public int NumberOfNodes { get; } 97 | public int StartingPortNumber { get; set; } = 9200; 98 | public bool NoCleanupAfterNodeStopped { get; set; } 99 | 100 | /// 101 | /// Whether should continue to write output to console after it has started. 102 | /// Defaults to true 103 | /// 104 | public bool ShowElasticsearchOutputAfterStarted { get; set; } = true; 105 | 106 | public bool CacheEsHomeInstallation { get; set; } 107 | 108 | /// The node settings to apply to each started node 109 | public NodeSettings DefaultNodeSettings { get; } = new NodeSettings(); 110 | 111 | /// 112 | /// Creates a node name 113 | /// 114 | public virtual string CreateNodeName(int? node) => 115 | node.HasValue ? $"managed-elasticsearch-{node}" : " managed-elasticsearch"; 116 | 117 | /// 118 | /// Calculates the quorum given the number of instances 119 | /// 120 | private static int Quorum(int instanceCount) => Math.Max(1, (int) Math.Floor((double) instanceCount / 2) + 1); 121 | 122 | /// 123 | /// Creates a node attribute for the version of Elasticsearch 124 | /// 125 | public string AttributeKey(string attribute) 126 | { 127 | var attr = Version.Major >= 5 ? "attr." : ""; 128 | return $"node.{attr}{attribute}"; 129 | } 130 | 131 | /// 132 | /// Adds a node setting to the default node settings 133 | /// 134 | protected void Add(string key, string value) 135 | { 136 | if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) return; 137 | DefaultNodeSettings.Add(key, value); 138 | } 139 | 140 | /// 141 | /// Adds a node setting to the default node settings only if the Elasticsearch 142 | /// version is in the range. 143 | /// 144 | protected void Add(string key, string value, string range) 145 | { 146 | if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) return; 147 | if (string.IsNullOrWhiteSpace(range) || Version.InRange(range)) 148 | DefaultNodeSettings.Add(key, value, range); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/Configuration/NodeConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Globalization; 7 | using Elastic.Elasticsearch.Managed.FileSystem; 8 | using Elastic.Stack.ArtifactsApi; 9 | using ProcNet; 10 | 11 | namespace Elastic.Elasticsearch.Managed.Configuration 12 | { 13 | public class NodeConfiguration 14 | { 15 | private Action _defaultStartArgs = s => { }; 16 | 17 | public NodeConfiguration(ElasticVersion version, int? port = null) : this(new ClusterConfiguration(version), 18 | port) 19 | { 20 | } 21 | 22 | public NodeConfiguration(IClusterConfiguration clusterConfiguration, int? port = null, 23 | string nodePrefix = null) 24 | { 25 | ClusterConfiguration = clusterConfiguration; 26 | DesiredPort = port; 27 | DesiredNodeName = CreateNodeName(port, nodePrefix) ?? clusterConfiguration.CreateNodeName(port); 28 | Settings = new NodeSettings(clusterConfiguration.DefaultNodeSettings); 29 | 30 | if (!string.IsNullOrWhiteSpace(DesiredNodeName)) Settings.Add("node.name", DesiredNodeName); 31 | if (DesiredPort.HasValue) 32 | Settings.Add("http.port", DesiredPort.Value.ToString(CultureInfo.InvariantCulture)); 33 | } 34 | 35 | private IClusterConfiguration ClusterConfiguration { get; } 36 | 37 | public int? DesiredPort { get; } 38 | public string DesiredNodeName { get; } 39 | 40 | public Action ModifyStartArguments 41 | { 42 | get => _defaultStartArgs; 43 | set => _defaultStartArgs = value ?? (s => { }); 44 | } 45 | 46 | /// 47 | /// Wheter should continue to write output to console after it has started. 48 | /// Defaults to true but useful to turn of if it proofs to be too noisy 49 | /// 50 | public bool ShowElasticsearchOutputAfterStarted { get; set; } = true; 51 | 52 | /// 53 | /// The expected duration of the shut down sequence for Elasticsearch. After this wait duration a hard kill will occur. 54 | /// 55 | public TimeSpan WaitForShutdown { get; set; } = TimeSpan.FromSeconds(10); 56 | 57 | /// 58 | /// Copy of . Add individual node settings here. 59 | /// 60 | public NodeSettings Settings { get; } 61 | 62 | public INodeFileSystem FileSystem => ClusterConfiguration.FileSystem; 63 | public ElasticVersion Version => ClusterConfiguration.Version; 64 | public string[] CommandLineArguments => Settings.ToCommandLineArguments(Version); 65 | 66 | public void InitialMasterNodes(string initialMasterNodes) => 67 | Settings.Add("cluster.initial_master_nodes", initialMasterNodes, ">=7.0.0.beta1"); 68 | 69 | public string AttributeKey(string attribute) 70 | { 71 | var attr = Version.Major >= 5 ? "attr." : ""; 72 | return $"node.{attr}{attribute}"; 73 | } 74 | 75 | public void Add(string key, string value) => Settings.Add(key, value); 76 | 77 | private string CreateNodeName(int? node, string prefix = null) 78 | { 79 | if (prefix == null) return null; 80 | var suffix = Guid.NewGuid().ToString("N").Substring(0, 6); 81 | return $"{prefix.Replace("Cluster", "").ToLowerInvariant()}-node-{suffix}{node}"; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/Configuration/NodeSetting.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Elasticsearch.Managed.Configuration 6 | { 7 | public struct NodeSetting 8 | { 9 | public string Key { get; } 10 | public string Value { get; } 11 | 12 | /// 13 | /// Stores for which elasticsearch version range this setting is applicable 14 | /// 15 | public string VersionRange { get; } 16 | 17 | public NodeSetting(string key, string value, string range) 18 | { 19 | Key = key; 20 | Value = value; 21 | VersionRange = range; 22 | } 23 | 24 | public override string ToString() => $"{Key}={Value}"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/Configuration/NodeSettings.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using Elastic.Stack.ArtifactsApi; 9 | 10 | namespace Elastic.Elasticsearch.Managed.Configuration 11 | { 12 | public class NodeSettings : List 13 | { 14 | public NodeSettings() 15 | { 16 | } 17 | 18 | public NodeSettings(NodeSettings settings) : base(settings) 19 | { 20 | } 21 | 22 | public void Add(string setting) 23 | { 24 | var s = setting.Split(new[] {'='}, 2, StringSplitOptions.RemoveEmptyEntries); 25 | if (s.Length != 2) 26 | throw new ArgumentException($"Can only add node settings in key=value from but received: {setting}"); 27 | Add(new NodeSetting(s[0], s[1], null)); 28 | } 29 | 30 | public void Add(string key, string value) => Add(new NodeSetting(key, value, null)); 31 | 32 | public void Add(string key, string value, string versionRange) => 33 | Add(new NodeSetting(key, value, versionRange)); 34 | 35 | public string[] ToCommandLineArguments(ElasticVersion version) 36 | { 37 | var settingArgument = "-E"; 38 | return this 39 | //if a node setting is only applicable for a certain version make sure its filtered out 40 | .Where(s => string.IsNullOrEmpty(s.VersionRange) || version.InRange(s.VersionRange)) 41 | //allow additional settings to take precedence over already DefaultNodeSettings 42 | //without relying on elasticsearch to dedup, 5.4.0 no longer allows passing the same setting twice 43 | //on the command with the latter taking precedence 44 | .GroupBy(setting => setting.Key) 45 | .Select(g => g.Last()) 46 | .SelectMany( 47 | s => [settingArgument, $"{s}"] 48 | ) 49 | .ToArray(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/ConsoleWriters/ConsoleLineWriter.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using ProcNet.Std; 7 | 8 | namespace Elastic.Elasticsearch.Managed.ConsoleWriters 9 | { 10 | public class ConsoleLineWriter : IConsoleLineHandler 11 | { 12 | public void Handle(LineOut lineOut) 13 | { 14 | var w = lineOut.Error ? Console.Error : Console.Out; 15 | w.WriteLine(lineOut); 16 | } 17 | 18 | public void Handle(Exception e) => Console.Error.WriteLine(e); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/ConsoleWriters/ExceptionLineParser.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Text.RegularExpressions; 6 | 7 | namespace Elastic.Elasticsearch.Managed.ConsoleWriters 8 | { 9 | public static class ExceptionLineParser 10 | { 11 | private static readonly Regex CauseRegex = new Regex(@"^(?.*?Exception:)(?.*?)$"); 12 | 13 | private static readonly Regex 14 | LocRegex = new Regex(@"^(?\s*?at )(?.*?)\((?.*?)\)(?.*?)$"); 15 | 16 | public static bool TryParseCause(string line, out string cause, out string message) 17 | { 18 | cause = message = null; 19 | if (string.IsNullOrEmpty(line)) return false; 20 | var match = CauseRegex.Match(line); 21 | if (!match.Success) return false; 22 | cause = match.Groups["cause"].Value.Trim(); 23 | message = match.Groups["message"].Value.Trim(); 24 | return true; 25 | } 26 | 27 | public static bool TryParseStackTrace(string line, out string at, out string method, out string file, 28 | out string jar) 29 | { 30 | at = method = file = jar = null; 31 | if (string.IsNullOrEmpty(line)) return false; 32 | var match = LocRegex.Match(line); 33 | if (!match.Success) return false; 34 | at = match.Groups["at"].Value; 35 | method = match.Groups["method"].Value.Trim(); 36 | file = match.Groups["file"].Value.Trim(); 37 | jar = match.Groups["jar"].Value.Trim(); 38 | return true; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/ConsoleWriters/IConsoleLineWriter.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using ProcNet.Std; 7 | 8 | namespace Elastic.Elasticsearch.Managed.ConsoleWriters 9 | { 10 | public static class LineWriterExtensions 11 | { 12 | public static void WriteDiagnostic(this IConsoleLineHandler writer, string message) => 13 | writer.Handle(Info(message)); 14 | 15 | public static void WriteDiagnostic(this IConsoleLineHandler writer, string message, string node) => 16 | writer?.Handle(Info(node != null ? $"[{node}] {message}" : message)); 17 | 18 | public static void WriteError(this IConsoleLineHandler writer, string message) => writer.Handle(Error(message)); 19 | 20 | public static void WriteError(this IConsoleLineHandler writer, string message, string node) => 21 | writer?.Handle(Error(node != null ? $"[{node}] {message}" : message)); 22 | 23 | private static string Format(bool error, string message) => 24 | $"[{DateTime.UtcNow:yyyy-MM-ddThh:mm:ss,fff}][{(error ? "ERROR" : "INFO ")}][Managed Elasticsearch\t] {message}"; 25 | 26 | private static LineOut Info(string message) => ConsoleOut.Out(Format(false, message)); 27 | private static LineOut Error(string message) => ConsoleOut.ErrorOut(Format(true, message)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/ConsoleWriters/LineOutParser.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Text.RegularExpressions; 7 | 8 | namespace Elastic.Elasticsearch.Managed.ConsoleWriters 9 | { 10 | public static class LineOutParser 11 | { 12 | /* 13 | [2016-09-26T11:43:17,314][INFO ][o.e.n.Node ] [readonly-node-a9c5f4] initializing ... 14 | [2016-09-26T11:43:17,470][INFO ][o.e.e.NodeEnvironment ] [readonly-node-a9c5f4] using [1] data paths, mounts [[BOOTCAMP (C:)]], net usable_space [27.7gb], net total_space [129.7gb], spins? [unknown], types [NTFS] 15 | [2016-09-26T11:43:17,471][INFO ][o.e.e.NodeEnvironment ] [readonly-node-a9c5f4] heap size [1.9gb], compressed ordinary object pointers [true] 16 | [2016-09-26T11:43:17,475][INFO ][o.e.n.Node ] [readonly-node-a9c5f4] version[5.0.0-beta1], pid[13172], build[7eb6260/2016-09-20T23:10:37.942Z], OS[Windows 10/10.0/amd64], JVM[Oracle Corporation/Java HotSpot(TM) 64-Bit Server VM/1.8.0_101/25.101-b13] 17 | [2016-09-26T11:43:19,160][INFO ][o.e.p.PluginsService ] [readonly-node-a9c5f4] loaded module [aggs-matrix-stats] 18 | [2016-09-26T11:43:19,160][INFO ][o.e.p.PluginsService ] [readonly-node-a9c5f4] loaded module [ingest-common] 19 | [2016-09-26T11:43:19,161][INFO ][o.e.p.PluginsService ] [readonly-node-a9c5f4] loaded module [lang-expression] 20 | [2016-09-26T11:43:19,161][INFO ][o.e.p.PluginsService ] [readonly-node-a9c5f4] loaded module [lang-groovy] 21 | [2016-09-26T11:43:19,161][INFO ][o.e.p.PluginsService ] [readonly-node-a9c5f4] loaded module [lang-mustache] 22 | [2016-09-26T11:43:19,162][INFO ][o.e.p.PluginsService ] [readonly-node-a9c5f4] loaded module [lang-painless] 23 | [2016-09-26T11:43:19,162][INFO ][o.e.p.PluginsService ] [readonly-node-a9c5f4] loaded module [percolator] 24 | [2016-09-26T11:43:19,162][INFO ][o.e.p.PluginsService ] [readonly-node-a9c5f4] loaded module [reindex] 25 | [2016-09-26T11:43:19,162][INFO ][o.e.p.PluginsService ] [readonly-node-a9c5f4] loaded module [transport-netty3] 26 | [2016-09-26T11:43:19,163][INFO ][o.e.p.PluginsService ] [readonly-node-a9c5f4] loaded module [transport-netty4] 27 | [2016-09-26T11:43:19,163][INFO ][o.e.p.PluginsService ] [readonly-node-a9c5f4] loaded plugin [ingest-attachment] 28 | [2016-09-26T11:43:19,164][INFO ][o.e.p.PluginsService ] [readonly-node-a9c5f4] loaded plugin [ingest-geoip] 29 | [2016-09-26T11:43:19,164][INFO ][o.e.p.PluginsService ] [readonly-node-a9c5f4] loaded plugin [mapper-attachments] 30 | [2016-09-26T11:43:19,164][INFO ][o.e.p.PluginsService ] [readonly-node-a9c5f4] loaded plugin [mapper-murmur3] 31 | [2016-09-26T11:43:19,164][INFO ][o.e.p.PluginsService ] [readonly-node-a9c5f4] loaded plugin [x-pack] 32 | [2016-09-26T11:43:19,374][WARN ][d.m.attachment ] [mapper-attachments] plugin has been deprecated and will be replaced by [ingest-attachment] plugin. 33 | [2016-09-26T11:43:22,179][INFO ][o.e.n.Node ] [readonly-node-a9c5f4] initialized 34 | [2016-09-26T11:43:22,180][INFO ][o.e.n.Node ] [readonly-node-a9c5f4] starting ... 35 | */ 36 | private static readonly Regex ConsoleLineParser = 37 | new Regex(@"\[(?.*?)\]\[(?.*?)\](?:\[(?
.*?)\])(?: \[(?.*?)\])? (?.+)"); 38 | 39 | private static readonly Regex PortParser = new Regex(@"bound_address(es)? {.+\:(?\d+)}"); 40 | 41 | //[2016-09-26T11:43:17,475][INFO ][o.e.n.Node ] [readonly-node-a9c5f4] version[5.0.0-beta1], pid[13172], build[7eb6260/2016-09-20T23:10:37.942Z], OS[Windows 10/10.0/amd64], JVM[Oracle Corporation/Java HotSpot(TM) 64-Bit Server VM/1.8.0_101/25.101-b13] 42 | private static readonly Regex InfoParser = 43 | new Regex(@"version\[(?.*)\], pid\[(?.*)\], build\[(?.+)\]"); 44 | 45 | public static bool TryParse(string line, 46 | out string date, out string level, out string section, out string node, out string message, 47 | out bool started) 48 | { 49 | date = level = section = node = message = null; 50 | started = false; 51 | if (string.IsNullOrEmpty(line)) return false; 52 | 53 | var match = ConsoleLineParser.Match(line); 54 | if (!match.Success) return false; 55 | date = match.Groups["date"].Value.Trim(); 56 | level = match.Groups["level"].Value.Trim(); 57 | section = match.Groups["section"].Value.Trim().Replace("org.elasticsearch.", ""); 58 | node = match.Groups["node"].Value.Trim(); 59 | message = match.Groups["message"].Value.Trim(); 60 | started = TryGetStartedConfirmation(section, message); 61 | return true; 62 | } 63 | 64 | private static bool TryGetStartedConfirmation(string section, string message) 65 | { 66 | var inNodeSection = section == "o.e.n.Node" || section == "node"; 67 | return inNodeSection && (message == "started" || message.StartsWith("started {", StringComparison.Ordinal)); 68 | } 69 | 70 | public static bool TryGetPortNumber(string section, string message, out int port) 71 | { 72 | port = 0; 73 | var inHttpSection = 74 | section == "o.e.h.HttpServer" 75 | || section == "http" 76 | || section == "o.e.h.AbstractHttpServerTransport" 77 | || section == "o.e.h.n.Netty4HttpServerTransport" 78 | || section == "o.e.x.s.t.n.SecurityNetty4HttpServerTransport"; 79 | if (!inHttpSection) return false; 80 | 81 | if (string.IsNullOrWhiteSpace(message)) return false; 82 | 83 | var match = PortParser.Match(message); 84 | if (!match.Success) return false; 85 | 86 | var portString = match.Groups["port"].Value.Trim(); 87 | port = int.Parse(portString); 88 | return true; 89 | } 90 | 91 | public static bool TryParseNodeInfo(string section, string message, out string version, out int? pid) 92 | { 93 | var inNodeSection = section == "o.e.n.Node" || section == "node"; 94 | 95 | version = null; 96 | pid = null; 97 | if (!inNodeSection) return false; 98 | 99 | var match = InfoParser.Match(message.Replace(Environment.NewLine, "")); 100 | if (!match.Success) return false; 101 | 102 | version = match.Groups["version"].Value.Trim(); 103 | pid = int.Parse(match.Groups["pid"].Value.Trim()); 104 | return true; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/ConsoleWriters/NoopConsoleLineWriter.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using ProcNet.Std; 7 | 8 | namespace Elastic.Elasticsearch.Managed.ConsoleWriters 9 | { 10 | internal class NoopConsoleLineWriter : IConsoleLineHandler 11 | { 12 | public static NoopConsoleLineWriter Instance { get; } = new NoopConsoleLineWriter(); 13 | 14 | public void Handle(LineOut lineOut) 15 | { 16 | } 17 | 18 | public void Handle(Exception e) 19 | { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/DetectedProxySoftware.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Elasticsearch.Managed; 6 | 7 | public enum DetectedProxySoftware 8 | { 9 | None, 10 | Fiddler, 11 | MitmProxy 12 | } 13 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/Elastic.Elasticsearch.Managed.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;netstandard2.1;net462;net8.0 5 | 6 | Provides an observable ElasticsearchNode abstraction that can be used to wrap an elasticsearch process. 7 | Also ships with an cluster abstraction that can start one or more ElasticsearchNode's 8 | 9 | elastic,elasticsearch,cluster,observable,rx 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/ElasticsearchCleanExitException.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | namespace Elastic.Elasticsearch.Managed 8 | { 9 | public class ElasticsearchCleanExitException : Exception 10 | { 11 | public ElasticsearchCleanExitException(string message) : base(message) 12 | { 13 | } 14 | 15 | public ElasticsearchCleanExitException(string message, Exception innerException) : base(message, innerException) 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/ElasticsearchCluster.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Elasticsearch.Managed.Configuration; 6 | 7 | namespace Elastic.Elasticsearch.Managed; 8 | 9 | public class ElasticsearchCluster(ClusterConfiguration clusterConfiguration) 10 | : ClusterBase(clusterConfiguration); 11 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/FileSystem/INodeFileSystem.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Elasticsearch.Managed.FileSystem 6 | { 7 | /// 8 | /// The file system for an Elasticsearch node 9 | /// 10 | public interface INodeFileSystem 11 | { 12 | /// 13 | /// The path to the script to start Elasticsearch 14 | /// 15 | string Binary { get; } 16 | 17 | /// 18 | /// The path to the script to manage plugins 19 | /// 20 | string PluginBinary { get; } 21 | 22 | /// 23 | /// The path to the home directory 24 | /// 25 | string ElasticsearchHome { get; } 26 | 27 | /// 28 | /// The path to the config directory 29 | /// 30 | string ConfigPath { get; } 31 | 32 | /// 33 | /// The path to the data directory 34 | /// 35 | string DataPath { get; } 36 | 37 | /// 38 | /// The path to the logs directory 39 | /// 40 | string LogsPath { get; } 41 | 42 | /// 43 | /// The path to the repository directory 44 | /// 45 | string RepositoryPath { get; } 46 | 47 | /// 48 | /// The path to the directory in which this node resides 49 | /// 50 | string LocalFolder { get; } 51 | 52 | /// The config environment variable to use for this version 53 | string ConfigEnvironmentVariableName { get; } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/FileSystem/NodeFileSystem.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.IO; 7 | using System.Runtime.InteropServices; 8 | using Elastic.Stack.ArtifactsApi; 9 | using Elastic.Stack.ArtifactsApi.Products; 10 | 11 | namespace Elastic.Elasticsearch.Managed.FileSystem 12 | { 13 | /// 14 | public class NodeFileSystem : INodeFileSystem 15 | { 16 | protected const string SubFolder = "ElasticManaged"; 17 | 18 | public NodeFileSystem(ElasticVersion version, string elasticsearchHome = null) 19 | { 20 | Version = version; 21 | Artifact = version.Artifact(Product.Elasticsearch); 22 | LocalFolder = AppDataFolder(version); 23 | ElasticsearchHome = elasticsearchHome ?? 24 | GetEsHomeVariable() ?? throw new ArgumentNullException(nameof(elasticsearchHome)); 25 | 26 | ConfigEnvironmentVariableName = version.Major >= 6 ? "ES_PATH_CONF" : "CONF_DIR"; 27 | } 28 | 29 | protected ElasticVersion Version { get; } 30 | protected Artifact Artifact { get; } 31 | 32 | private static bool IsMono { get; } = Type.GetType("Mono.Runtime") != null; 33 | 34 | protected static string BinarySuffix => IsMono || Path.DirectorySeparatorChar == '/' ? "" : ".bat"; 35 | 36 | /// 37 | public string Binary => Path.Combine(ElasticsearchHome, "bin", "elasticsearch") + BinarySuffix; 38 | 39 | /// 40 | public string PluginBinary => 41 | Path.Combine(ElasticsearchHome, "bin", (Version.Major >= 5 ? "elasticsearch-" : "") + "plugin") + 42 | BinarySuffix; 43 | 44 | /// 45 | public string ElasticsearchHome { get; } 46 | 47 | /// 48 | public string LocalFolder { get; } 49 | 50 | /// 51 | public virtual string ConfigPath => null; 52 | 53 | /// 54 | public virtual string DataPath => null; 55 | 56 | /// 57 | public virtual string LogsPath => null; 58 | 59 | /// 60 | public virtual string RepositoryPath => null; 61 | 62 | public string ConfigEnvironmentVariableName { get; } 63 | 64 | protected static string AppDataFolder(ElasticVersion version) 65 | { 66 | var appData = GetApplicationDataDirectory(); 67 | return Path.Combine(appData, SubFolder, version.Artifact(Product.Elasticsearch).LocalFolderName); 68 | } 69 | 70 | protected static string GetEsHomeVariable() => Environment.GetEnvironmentVariable("ES_HOME"); 71 | 72 | protected static string GetApplicationDataDirectory() => 73 | RuntimeInformation.IsOSPlatform(OSPlatform.Windows) 74 | ? Environment.GetEnvironmentVariable("LocalAppData") 75 | : Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData, 76 | Environment.SpecialFolderOption.Create); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Managed/README.md: -------------------------------------------------------------------------------- 1 | # Elastic.Managed 2 | Provides an easy to start/stop one or more Elasticsearch instances that exists on disk already 3 | 4 | 5 | ## ElasticsearchNode 6 | 7 | A `Proc.ObservableProcess` implementation that starts an Elasticsearch instance from the specified 8 | location. It is able to optionally block untill the node goes into started state and it sniffs the output 9 | to expose useful information such as the java process id, port number and others. 10 | 11 | Because its a subclass of `Proc.ObservableProcess` its completely reactive and allows you to seperate the act 12 | of listening to the output and proxying stdout/err. 13 | 14 | #### Examples: 15 | 16 | All the examples assume the following are defined. `esHome` points to a local folder where `Elasticsearch` is installed/unzipped. 17 | 18 | ```csharp 19 | var version = "6.2.0"; 20 | var esHome = Environment.ExpandEnvironmentVariables($@"%LOCALAPPDATA%\ElasticManaged\{version}\elasticsearch-{version}"); 21 | ``` 22 | 23 | The easiest way to get going pass the `version` and `esHome` to `ElasticsearchNode`. 24 | `ElasticsearchNode` implements `IDisposable` and will try to shutdown gracefully when disposed. 25 | Simply new'ing `ElasticsearchNode` won't actually start the node. We need to explicitly call `Start()`. 26 | `Start()` has several overloads but the default waits `2 minutes` for a started confirmation and proxies 27 | the consoleout using `HighlightWriter` which pretty prints the elasticsearch output. 28 | 29 | 30 | ```csharp 31 | using (var node = new ElasticsearchNode(version, esHome)) 32 | { 33 | node.Start(); 34 | } 35 | ``` 36 | 37 | `Start` is simply sugar over 38 | 39 | ```csharp 40 | using (var node = new ElasticsearchNode(version, esHome)) 41 | { 42 | node.SubscribeLines(new HighlightWriter()); 43 | 44 | if (!node.WaitForStarted(TimeSpan.FromMinutes(2))) throw new Exception(); 45 | } 46 | ``` 47 | 48 | As mentioned before `ElasticsearchNode` is really an `IObservable` by virtue of being an 49 | subclass of `Proc.ObservableProcess`. `SubscribeLines` is a specialized 50 | `Subscribe` that buffers `CharactersOut` untill a line is formed and emits a `LineOut`. Overloads exists that 51 | take additional `onNext/onError/onCompleted` handlers. 52 | 53 | A node can also be started using a `NodeConfiguration` 54 | 55 | ```csharp 56 | var clusterConfiguration = new ClusterConfiguration(version, esHome); 57 | var nodeConfiguration = new NodeConfiguration(clusterConfiguration, 9200) 58 | { 59 | ShowElasticsearchOutputAfterStarted = false, 60 | Settings = { "node.attr.x", "y" } 61 | }; 62 | using (var node = new ElasticsearchNode(nodeConfiguration)) 63 | { 64 | node.Start(); 65 | } 66 | ``` 67 | 68 | Which exposes the full range of options e.g here `ShowElasticsearchOutputAfterStarted` will dispose 69 | of the console out subscriptions as soon as we've parsed the started message to minimize the resources we consume. 70 | `Settings` here allows you to pass elasticsearch node settings to use for the node. 71 | 72 | ## ElasticsearchCluster 73 | 74 | A simple abstraction that can can start and stop one or more `ElasticsearchNodes` and wait for all of them to 75 | be started 76 | 77 | ```csharp 78 | var clusterConfiguration = new ClusterConfiguration(version, esHome, numberOfNodes: 2); 79 | using (var cluster = new ElasticsearchCluster(clusterConfiguration)) 80 | { 81 | cluster.Start(); 82 | } 83 | ``` 84 | 85 | `ElasticsearchCluster` is simply a barebones `ClusterBase` implementation, which is more powerful then it seems 86 | and serves as the base for `Elastic.Managed.Ephemeral`. 87 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/Elastic.Elasticsearch.Xunit.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0;netstandard2.1;net462;net8.0 4 | True 5 | False 6 | Provides an Xunit test framework allowing you to run integration tests against local ephemeral Elasticsearch clusters 7 | elastic,elasticsearch,xunit,cluster,integration,test,ephemeral 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/ElasticXunitConfigurationAttribute.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using Nullean.Xunit.Partitions; 7 | 8 | namespace Elastic.Elasticsearch.Xunit; 9 | 10 | /// 11 | /// An assembly attribute that specifies the 12 | /// for Xunit tests within the assembly. 13 | /// 14 | [AttributeUsage(AttributeTargets.Assembly)] 15 | public class ElasticXunitConfigurationAttribute : PartitionOptionsAttribute 16 | { 17 | /// Creates a new instance of . 18 | /// 19 | /// A type deriving from that specifies the run options 20 | /// 21 | public ElasticXunitConfigurationAttribute(Type type) : base(type) { } 22 | } 23 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/ElasticXunitRunOptions.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Linq; 7 | using System.Runtime.Serialization; 8 | using Elastic.Elasticsearch.Xunit.XunitPlumbing; 9 | using Elastic.Stack.ArtifactsApi; 10 | using Nullean.Xunit.Partitions; 11 | using Xunit.Abstractions; 12 | using static System.StringSplitOptions; 13 | 14 | namespace Elastic.Elasticsearch.Xunit 15 | { 16 | /// 17 | /// The Xunit test runner options 18 | /// 19 | public class ElasticXunitRunOptions : PartitionOptions 20 | { 21 | /// 22 | /// Informs the runner whether we expect to run integration tests. Defaults to true 23 | /// 24 | public bool RunIntegrationTests { get; set; } = true; 25 | 26 | /// 27 | /// Setting this to true will assume the cluster that is currently running was started for the purpose of these tests 28 | /// Defaults to false 29 | /// 30 | public bool IntegrationTestsMayUseAlreadyRunningNode { get; set; } = false; 31 | 32 | /// 33 | /// Informs the runner whether unit tests will be run. Defaults to false. 34 | /// If set to true and is false, the runner will run all the 35 | /// tests in parallel with the maximum degree of parallelism 36 | /// 37 | public bool RunUnitTests { get; set; } 38 | 39 | /// 40 | /// A global cluster filter that can be used to only run certain cluster's tests. 41 | /// Accepts a comma separated list of filters 42 | /// 43 | [Obsolete("Use PartitionFilterRegex instead", false)] 44 | [IgnoreDataMember] 45 | public string ClusterFilter 46 | { 47 | get => PartitionFilterRegex; 48 | set 49 | { 50 | if (string.IsNullOrWhiteSpace(value)) PartitionFilterRegex = value; 51 | else 52 | { 53 | //attempt at being backwards compatible with old way of filtering 54 | var re = string.Join("|", value.Split(new[] { ','}, RemoveEmptyEntries).Select(s => s.Trim())); 55 | PartitionFilterRegex = re; 56 | } 57 | } 58 | } 59 | 60 | /// 61 | /// A global test filter that can be used to only run certain cluster's tests. 62 | /// Accepts a comma separated list of filters 63 | /// 64 | [Obsolete("Use ParitionFilterRegex instead", false)] 65 | [IgnoreDataMember] 66 | public string TestFilter 67 | { 68 | get => TestFilterRegex; 69 | set 70 | { 71 | if (string.IsNullOrWhiteSpace(value)) TestFilterRegex = value; 72 | else 73 | { 74 | //attempt at being backwards compatible with old way of filtering 75 | var re = string.Join("|", value.Split(new[] { ','}, RemoveEmptyEntries).Select(s => s.Trim())); 76 | TestFilterRegex = re; 77 | } 78 | } 79 | } 80 | 81 | /// 82 | /// Informs the runner what version of Elasticsearch is under test. Required for 83 | /// to kick in 84 | /// 85 | public ElasticVersion Version { get; set; } 86 | 87 | public override void SetOptions(ITestFrameworkDiscoveryOptions discoveryOptions) 88 | { 89 | base.SetOptions(discoveryOptions); 90 | discoveryOptions.SetValue(nameof(Version), Version); 91 | discoveryOptions.SetValue(nameof(RunIntegrationTests), RunIntegrationTests); 92 | discoveryOptions.SetValue( 93 | nameof(IntegrationTestsMayUseAlreadyRunningNode), 94 | IntegrationTestsMayUseAlreadyRunningNode 95 | ); 96 | discoveryOptions.SetValue(nameof(RunUnitTests), RunUnitTests); 97 | #pragma warning disable CS0618 // Type or member is obsolete 98 | discoveryOptions.SetValue(nameof(TestFilter), TestFilter); 99 | discoveryOptions.SetValue(nameof(ClusterFilter), ClusterFilter); 100 | #pragma warning restore CS0618 // Type or member is obsolete 101 | } 102 | 103 | public override void SetOptions(ITestFrameworkExecutionOptions executionOptions) 104 | { 105 | 106 | base.SetOptions(executionOptions); 107 | executionOptions.SetValue(nameof(Version), Version); 108 | executionOptions.SetValue(nameof(RunIntegrationTests), RunIntegrationTests); 109 | executionOptions.SetValue( 110 | nameof(IntegrationTestsMayUseAlreadyRunningNode), 111 | IntegrationTestsMayUseAlreadyRunningNode 112 | ); 113 | executionOptions.SetValue(nameof(RunUnitTests), RunUnitTests); 114 | #pragma warning disable CS0618 // Type or member is obsolete 115 | executionOptions.SetValue(nameof(TestFilter), TestFilter); 116 | executionOptions.SetValue(nameof(ClusterFilter), ClusterFilter); 117 | #pragma warning restore CS0618 // Type or member is obsolete 118 | 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/ElasticXunitRunner.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Elasticsearch.Ephemeral; 6 | 7 | namespace Elastic.Elasticsearch.Xunit 8 | { 9 | public static class ElasticXunitRunner 10 | { 11 | public static IEphemeralCluster CurrentCluster { get; internal set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/PrintXunitAfterStartedTask.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Elasticsearch.Ephemeral; 6 | using Elastic.Elasticsearch.Ephemeral.Tasks; 7 | using Elastic.Elasticsearch.Managed.ConsoleWriters; 8 | 9 | namespace Elastic.Elasticsearch.Xunit 10 | { 11 | /// 12 | /// A task that writes a diagnostic message to indicate that tests will now run 13 | /// 14 | public class PrintXunitAfterStartedTask : ClusterComposeTask 15 | { 16 | /// 17 | public override void Run(IEphemeralCluster cluster) 18 | { 19 | var name = cluster.GetType().Name; 20 | cluster.Writer.WriteDiagnostic($"All good! kicking off [{name}] tests now"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/README.md: -------------------------------------------------------------------------------- 1 | # Elastic.Elasticsearch.Xunit 2 | 3 | Write integration tests against Elasticsearch `2.x`, `5.x`, `6.x` and `7.x`. 4 | Works with `.NET Core` and `.NET 4.6` and up. 5 | 6 | Supports `dotnet xunit`, `dotnet test`, `xunit.console.runner` and tests will be runnable in your IDE through VSTest and jetBrains Rider. 7 | 8 | 9 | ## Getting started 10 | 11 | **NOTE:** `Elastic.Xunit` supports both .NET core and Full Framework 4.6 and up. The getting started uses the new csproj 12 | from core but you can also use a full framework project. 13 | 14 | ### Create a class library project 15 | 16 | ```xml 17 | 18 | 19 | netcoreapp3.0 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ``` 32 | 33 | ### Use Elastic.Elasticsearch.Xunit's test framework 34 | 35 | Add the following Assembly attribute anywhere in your project. This informs Xunit to use our 36 | test framework to orchestrate and discover the tests. 37 | 38 | ```csharp 39 | [assembly: Xunit.TestFrameworkAttribute("Elastic.Elasticsearch.Xunit.Sdk.ElasticTestFramework", "Elastic.Elasticsearch.Xunit")] 40 | ``` 41 | 42 | ### Create a cluster 43 | 44 | This is the cluster that we'll write our integration test against. You can have multiple cluster. 45 | `Elastic.Elasticsearch.Xunit` will only ever start one cluster at a time and then run all tests belonging to that cluster. 46 | 47 | ```csharp 48 | /// Declare our cluster that we want to inject into our test classes 49 | public class MyTestCluster : XunitClusterBase 50 | { 51 | /// 52 | /// We pass our configuration instance to the base class. 53 | /// We only configure it to run version 6.2.3 here but lots of additional options are available. 54 | /// 55 | public MyTestCluster() : base(new XunitClusterConfiguration("6.2.0")) { } 56 | } 57 | ``` 58 | 59 | ### Create a test class 60 | 61 | ```csharp 62 | public class ExampleTest : IClusterFixture 63 | { 64 | public ExampleTest(MyTestCluster cluster) 65 | { 66 | // This registers a single client for the whole clusters lifetime to be reused and shared. 67 | // we do not expose Client on the passed cluster directly for two reasons 68 | // 69 | // 1) We do not want to prescribe how to new up the client 70 | // 71 | // 2) We do not want Elastic.Xunit to depend on NEST. Elastic.Xunit can start 2.x, 5.x and 6.x clusters 72 | // and NEST Major.x is only tested and supported against Elasticsearch Major.x. 73 | // 74 | this.Client = cluster.GetOrAddClient(c => 75 | { 76 | var nodes = cluster.NodesUris(); 77 | var connectionPool = new StaticConnectionPool(nodes); 78 | var settings = new ConnectionSettings(connectionPool) 79 | .EnableDebugMode(); 80 | return new ElasticClient(settings); 81 | }); 82 | } 83 | 84 | private ElasticClient Client { get; } 85 | 86 | /// [I] marks an integration test (like [Fact] would for plain Xunit) 87 | [I] public void SomeTest() 88 | { 89 | var rootNodeInfo = this.Client.RootNodeInfo(); 90 | 91 | rootNodeInfo.Name.Should().NotBeNullOrEmpty(); 92 | } 93 | } 94 | 95 | ``` 96 | 97 | ### Run your integration tests! 98 | 99 | ![jetBrains rider integration](ide-integration.png) 100 | 101 | Or on the command line using `dotnet test` 102 | 103 | ![sample output](output.gif) 104 | 105 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/Sdk/ElasticTestFramework.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Reflection; 6 | using Elastic.Elasticsearch.Xunit.XunitPlumbing; 7 | using Xunit.Abstractions; 8 | using Xunit.Sdk; 9 | using Nullean.Xunit.Partitions; 10 | using Nullean.Xunit.Partitions.Sdk; 11 | 12 | namespace Elastic.Elasticsearch.Xunit.Sdk; 13 | 14 | // ReSharper disable once UnusedType.Global 15 | public class ElasticTestFramework : PartitionTestFramework 16 | { 17 | public ElasticTestFramework(IMessageSink messageSink) : base(messageSink) { } 18 | } 19 | 20 | public class ElasticTestFrameworkDiscovererFactory : ITestFrameworkDiscovererFactory 21 | { 22 | public XunitTestFrameworkDiscoverer Create( 23 | IAssemblyInfo assemblyInfo, ISourceInformationProvider sourceProvider, IMessageSink diagnosticMessageSink 24 | ) 25 | where TOptions : PartitionOptions, new() => 26 | new PartitionTestFrameworkDiscoverer(assemblyInfo, sourceProvider, diagnosticMessageSink, typeof(IClusterFixture<>)); 27 | } 28 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/Sdk/TestAssemblyRunner.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Concurrent; 7 | using System.Collections.Generic; 8 | using System.Diagnostics; 9 | using System.Linq; 10 | using System.Reflection; 11 | using System.Threading; 12 | using System.Threading.Tasks; 13 | using Elastic.Elasticsearch.Ephemeral; 14 | using Elastic.Elasticsearch.Ephemeral.Tasks.ValidationTasks; 15 | using Elastic.Elasticsearch.Xunit.XunitPlumbing; 16 | using Nullean.Xunit.Partitions.Sdk; 17 | using Xunit.Abstractions; 18 | using Xunit.Sdk; 19 | 20 | namespace Elastic.Elasticsearch.Xunit.Sdk 21 | { 22 | 23 | public class TestAssemblyRunnerFactory : ITestAssemblyRunnerFactory 24 | { 25 | public XunitTestAssemblyRunner Create(ITestAssembly testAssembly, IEnumerable testCases, 26 | IMessageSink diagnosticMessageSink, 27 | IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions) => 28 | new TestAssemblyRunner(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, 29 | executionOptions); 30 | } 31 | internal class TestAssemblyRunner : PartitionTestAssemblyRunner> 32 | { 33 | public TestAssemblyRunner(ITestAssembly testAssembly, 34 | IEnumerable testCases, 35 | IMessageSink diagnosticMessageSink, 36 | IMessageSink executionMessageSink, 37 | ITestFrameworkExecutionOptions executionOptions) 38 | : base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions, typeof(IClusterFixture<>)) 39 | { 40 | RunIntegrationTests = executionOptions.GetValue(nameof(ElasticXunitRunOptions.RunIntegrationTests)); 41 | RunUnitTests = executionOptions.GetValue(nameof(ElasticXunitRunOptions.RunUnitTests)); 42 | IntegrationTestsMayUseAlreadyRunningNode = 43 | executionOptions.GetValue(nameof(ElasticXunitRunOptions 44 | .IntegrationTestsMayUseAlreadyRunningNode)); 45 | } 46 | 47 | private bool RunIntegrationTests { get; } 48 | private bool IntegrationTestsMayUseAlreadyRunningNode { get; } 49 | private bool RunUnitTests { get; } 50 | 51 | protected override async Task RunTestCollectionsAsync(IMessageBus bus, 52 | CancellationTokenSource cancellationTokenSource) 53 | { 54 | if (RunUnitTests && !RunIntegrationTests) 55 | return await RunAllWithoutPartitionFixture(bus, cancellationTokenSource) 56 | .ConfigureAwait(false); 57 | 58 | return await RunAllTests(bus, cancellationTokenSource) 59 | .ConfigureAwait(false); 60 | } 61 | 62 | protected override async Task UseStateAndRun( 63 | IEphemeralCluster cluster, 64 | Func runGroup, 65 | Func failAll 66 | ) 67 | { 68 | using (cluster) 69 | { 70 | ElasticXunitRunner.CurrentCluster = cluster; 71 | var clusterConfiguration = cluster.ClusterConfiguration; 72 | var timeout = clusterConfiguration?.Timeout ?? TimeSpan.FromMinutes(2); 73 | 74 | var started = false; 75 | try 76 | { 77 | if (!IntegrationTestsMayUseAlreadyRunningNode || !ValidateRunningVersion(cluster)) 78 | cluster.Start(timeout); 79 | 80 | started = true; 81 | } 82 | catch (Exception e) 83 | { 84 | await failAll(e, $"Further logs might be available at: {cluster.ClusterConfiguration?.FileSystem?.LogsPath}") 85 | .ConfigureAwait(false); 86 | } 87 | if (started) 88 | await runGroup(clusterConfiguration?.MaxConcurrency).ConfigureAwait(false); 89 | } 90 | } 91 | 92 | private static bool ValidateRunningVersion(IEphemeralCluster cluster) 93 | { 94 | try 95 | { 96 | var t = new ValidateRunningVersion(); 97 | t.Run(cluster); 98 | return true; 99 | } 100 | catch (Exception) 101 | { 102 | return false; 103 | } 104 | } 105 | 106 | public static Type GetClusterFixtureType(ITypeInfo testClass) => 107 | GetPartitionFixtureType(testClass, typeof(IClusterFixture<>)); 108 | 109 | 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/XunitClusterBase.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Elasticsearch.Ephemeral; 6 | 7 | namespace Elastic.Elasticsearch.Xunit 8 | { 9 | /// 10 | /// Base class for a cluster that integrates with Xunit tests 11 | /// 12 | public abstract class XunitClusterBase : XunitClusterBase 13 | { 14 | protected XunitClusterBase(XunitClusterConfiguration configuration) : base(configuration) 15 | { 16 | } 17 | } 18 | 19 | /// 20 | /// Base class for a cluster that integrates with Xunit tests 21 | /// 22 | public abstract class XunitClusterBase : EphemeralCluster 23 | where TConfiguration : XunitClusterConfiguration 24 | { 25 | protected XunitClusterBase(TConfiguration configuration) : base(configuration) 26 | { 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/XunitClusterConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using Elastic.Elasticsearch.Ephemeral; 7 | using Elastic.Elasticsearch.Ephemeral.Plugins; 8 | using Elastic.Stack.ArtifactsApi; 9 | 10 | namespace Elastic.Elasticsearch.Xunit 11 | { 12 | public class XunitClusterConfiguration : EphemeralClusterConfiguration 13 | { 14 | public XunitClusterConfiguration( 15 | ElasticVersion version, 16 | ClusterFeatures features = ClusterFeatures.None, 17 | ElasticsearchPlugins plugins = null, 18 | int numberOfNodes = 1) 19 | : base(version, features, plugins, numberOfNodes) => 20 | AdditionalAfterStartedTasks.Add(new PrintXunitAfterStartedTask()); 21 | 22 | /// 23 | protected override string NodePrefix => "xunit"; 24 | 25 | /// 26 | /// The maximum number of tests that can run concurrently against a cluster using this configuration. 27 | /// 28 | public int MaxConcurrency { get; set; } 29 | 30 | /// 31 | /// The maximum amount of time a cluster can run using this configuration. 32 | /// 33 | public TimeSpan? Timeout { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/XunitClusterExtensions.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Concurrent; 7 | using Elastic.Elasticsearch.Ephemeral; 8 | 9 | namespace Elastic.Elasticsearch.Xunit 10 | { 11 | /// 12 | /// Extension methods for 13 | /// 14 | public static class XunitClusterExtensions 15 | { 16 | private static readonly ConcurrentDictionary Clients = new(); 17 | 18 | /// 19 | /// Gets a client for the cluster if one exists, or creates a new client if one doesn't. 20 | /// 21 | /// the cluster to create a client for 22 | /// A delegate to create a client, given the cluster to create it for 23 | /// the type of the client 24 | /// An instance of a client 25 | public static T GetOrAddClient(this IEphemeralCluster cluster, Func getOrAdd) => 26 | (T) Clients.GetOrAdd(cluster, c => getOrAdd(c)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/XunitPlumbing/ElasticTestCaseDiscoverer.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using Xunit.Abstractions; 9 | using Xunit.Sdk; 10 | 11 | namespace Elastic.Elasticsearch.Xunit.XunitPlumbing 12 | { 13 | /// 14 | /// Base test discoverer used to discover tests cases attached 15 | /// to test methods that are attributed with (or a subclass). 16 | /// 17 | public abstract class ElasticTestCaseDiscoverer : IXunitTestCaseDiscoverer 18 | { 19 | protected readonly IMessageSink DiagnosticMessageSink; 20 | 21 | protected ElasticTestCaseDiscoverer(IMessageSink diagnosticMessageSink) => 22 | DiagnosticMessageSink = diagnosticMessageSink; 23 | 24 | /// 25 | public IEnumerable Discover( 26 | ITestFrameworkDiscoveryOptions discoveryOptions, 27 | ITestMethod testMethod, 28 | IAttributeInfo factAttribute 29 | ) => 30 | SkipMethod(discoveryOptions, testMethod, out var skipReason) 31 | ? string.IsNullOrEmpty(skipReason) 32 | ? new IXunitTestCase[] { } 33 | : new IXunitTestCase[] {new SkippingTestCase(skipReason, testMethod, null)} 34 | : new[] 35 | { 36 | new XunitTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod) 37 | }; 38 | 39 | /// 40 | /// Detemines whether a test method should be skipped, and the reason why 41 | /// 42 | /// The discovery options 43 | /// The test method 44 | /// The reason to skip 45 | /// 46 | protected virtual bool SkipMethod(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, 47 | out string skipReason) 48 | { 49 | skipReason = null; 50 | return false; 51 | } 52 | 53 | protected static IList GetAttributes(ITestMethod testMethod) 54 | where TAttribute : Attribute 55 | { 56 | var classAttributes = testMethod.TestClass.Class.GetCustomAttributes(typeof(TAttribute)) ?? 57 | Enumerable.Empty(); 58 | var methodAttributes = testMethod.Method.GetCustomAttributes(typeof(TAttribute)) ?? 59 | Enumerable.Empty(); 60 | return classAttributes.Concat(methodAttributes).ToList(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/XunitPlumbing/IClusterFixture.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Elasticsearch.Ephemeral; 6 | using Elastic.Elasticsearch.Managed; 7 | 8 | namespace Elastic.Elasticsearch.Xunit.XunitPlumbing 9 | { 10 | // ReSharper disable once UnusedTypeParameter 11 | // used by the runner to new() the proper cluster 12 | public interface IClusterFixture 13 | where TCluster : ICluster, new() 14 | { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/XunitPlumbing/IntegrationTestDiscoverer.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using Elastic.Elasticsearch.Xunit.Sdk; 9 | using Elastic.Stack.ArtifactsApi; 10 | using SemVer; 11 | using Xunit; 12 | using Xunit.Abstractions; 13 | using Xunit.Sdk; 14 | using Enumerable = System.Linq.Enumerable; 15 | 16 | namespace Elastic.Elasticsearch.Xunit.XunitPlumbing 17 | { 18 | /// 19 | /// An Xunit test that should be skipped, and a reason why. 20 | /// 21 | public abstract class SkipTestAttributeBase : Attribute 22 | { 23 | /// 24 | /// Whether the test should be skipped 25 | /// 26 | public abstract bool Skip { get; } 27 | 28 | /// 29 | /// The reason why the test should be skipped 30 | /// 31 | public abstract string Reason { get; } 32 | } 33 | 34 | /// 35 | /// An Xunit integration test 36 | /// 37 | [XunitTestCaseDiscoverer( 38 | "Elastic.Elasticsearch.Xunit.XunitPlumbing.IntegrationTestDiscoverer", 39 | "Elastic.Elasticsearch.Xunit" 40 | )] 41 | public class I : FactAttribute { } 42 | 43 | /// 44 | /// A test discoverer used to discover integration tests cases attached 45 | /// to test methods that are attributed with attribute 46 | /// 47 | public class IntegrationTestDiscoverer : ElasticTestCaseDiscoverer 48 | { 49 | public IntegrationTestDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink) 50 | { 51 | } 52 | 53 | /// 54 | protected override bool SkipMethod( 55 | ITestFrameworkDiscoveryOptions discoveryOptions, 56 | ITestMethod testMethod, 57 | out string skipReason 58 | ) 59 | { 60 | skipReason = null; 61 | var runIntegrationTests = 62 | discoveryOptions.GetValue(nameof(ElasticXunitRunOptions.RunIntegrationTests)); 63 | if (!runIntegrationTests) return true; 64 | 65 | var cluster = TestAssemblyRunner.GetClusterFixtureType(testMethod.TestClass.Class); 66 | if (cluster == null) 67 | { 68 | skipReason += 69 | $"{testMethod.TestClass.Class.Name} does not define a cluster through IClusterFixture"; 70 | return true; 71 | } 72 | 73 | var elasticsearchVersion = 74 | discoveryOptions.GetValue(nameof(ElasticXunitRunOptions.Version)); 75 | 76 | // Skip if the version we are testing against is attributed to be skipped do not run the test nameof(SkipVersionAttribute.Ranges) 77 | var skipVersionAttribute = GetAttributes(testMethod).FirstOrDefault(); 78 | if (skipVersionAttribute != null) 79 | { 80 | var skipVersionRanges = 81 | skipVersionAttribute.GetNamedArgument>(nameof(SkipVersionAttribute.Ranges)) ?? 82 | new List(); 83 | if (elasticsearchVersion == null && skipVersionRanges.Count > 0) 84 | { 85 | skipReason = $"{nameof(SkipVersionAttribute)} has ranges defined for this test but " + 86 | $"no {nameof(ElasticXunitRunOptions.Version)} has been provided to {nameof(ElasticXunitRunOptions)}"; 87 | return true; 88 | } 89 | 90 | if (elasticsearchVersion != null) 91 | { 92 | var reason = skipVersionAttribute.GetNamedArgument(nameof(SkipVersionAttribute.Reason)); 93 | for (var index = 0; index < skipVersionRanges.Count; index++) 94 | { 95 | var range = skipVersionRanges[index]; 96 | // inrange takes prereleases into account 97 | if (!elasticsearchVersion.InRange(range)) continue; 98 | skipReason = 99 | $"{nameof(SkipVersionAttribute)} has range {range} that {elasticsearchVersion} satisfies"; 100 | if (!string.IsNullOrWhiteSpace(reason)) skipReason += $": {reason}"; 101 | return true; 102 | } 103 | } 104 | } 105 | 106 | var skipTests = GetAttributes(testMethod) 107 | .FirstOrDefault(a => a.GetNamedArgument(nameof(SkipTestAttributeBase.Skip))); 108 | 109 | if (skipTests == null) return false; 110 | 111 | skipReason = skipTests.GetNamedArgument(nameof(SkipTestAttributeBase.Reason)); 112 | return true; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/XunitPlumbing/SkipVersionAttribute.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using SemVer; 9 | 10 | namespace Elastic.Elasticsearch.Xunit.XunitPlumbing 11 | { 12 | /// 13 | /// An Xunit test that should be skipped for given Elasticsearch versions, and a reason why. 14 | /// 15 | public class SkipVersionAttribute : Attribute 16 | { 17 | // ReSharper disable once UnusedParameter.Local 18 | // reason is used to allow the test its used on to self document why its been put in place 19 | public SkipVersionAttribute(string skipVersionRangesSeparatedByComma, string reason) 20 | { 21 | Reason = reason; 22 | Ranges = string.IsNullOrEmpty(skipVersionRangesSeparatedByComma) 23 | ? new List() 24 | : skipVersionRangesSeparatedByComma.Split(',') 25 | .Select(r => r.Trim()) 26 | .Where(r => !string.IsNullOrWhiteSpace(r)) 27 | .Select(r => new SemVer.Range(r)) 28 | .ToList(); 29 | } 30 | 31 | /// 32 | /// The reason why the test should be skipped 33 | /// 34 | public string Reason { get; } 35 | 36 | /// 37 | /// The version ranges for which the test should be skipped 38 | /// 39 | public IList Ranges { get; } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/XunitPlumbing/SkippingTestCase.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Xunit.Abstractions; 8 | using Xunit.Sdk; 9 | 10 | namespace Elastic.Elasticsearch.Xunit.XunitPlumbing 11 | { 12 | public class SkippingTestCase : TestMethodTestCase, IXunitTestCase 13 | { 14 | /// Used for de-serialization. 15 | // ReSharper disable once UnusedMember.Global 16 | public SkippingTestCase() { } 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The test method this test case belongs to. 22 | /// The arguments for the test method. 23 | public SkippingTestCase(string skipReason, ITestMethod testMethod, object[] testMethodArguments = null) 24 | : base(TestMethodDisplay.ClassAndMethod, TestMethodDisplayOptions.None, testMethod, testMethodArguments) => 25 | SkipReason = skipReason ?? "skipped"; 26 | 27 | public int Timeout => 0; 28 | 29 | /// 30 | public Task RunAsync( 31 | IMessageSink diagnosticMessageSink, 32 | IMessageBus messageBus, 33 | object[] constructorArguments, 34 | ExceptionAggregator aggregator, 35 | CancellationTokenSource cancellationTokenSource) => 36 | new XunitTestCaseRunner( 37 | this, 38 | DisplayName, 39 | SkipReason, 40 | constructorArguments, 41 | TestMethodArguments, 42 | messageBus, 43 | aggregator, 44 | cancellationTokenSource).RunAsync(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/XunitPlumbing/UnitTestDiscoverer.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Linq; 6 | using Xunit; 7 | using Xunit.Abstractions; 8 | using Xunit.Sdk; 9 | 10 | namespace Elastic.Elasticsearch.Xunit.XunitPlumbing 11 | { 12 | /// 13 | /// An Xunit unit test 14 | /// 15 | [XunitTestCaseDiscoverer( 16 | "Elastic.Elasticsearch.Xunit.XunitPlumbing.UnitTestDiscoverer", 17 | "Elastic.Elasticsearch.Xunit" 18 | )] 19 | public class U : FactAttribute 20 | { 21 | } 22 | 23 | /// 24 | /// A test discoverer used to discover unit tests cases attached 25 | /// to test methods that are attributed with attribute 26 | /// 27 | public class UnitTestDiscoverer : ElasticTestCaseDiscoverer 28 | { 29 | public UnitTestDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink) 30 | { 31 | } 32 | 33 | /// 34 | protected override bool SkipMethod( 35 | ITestFrameworkDiscoveryOptions discoveryOptions, 36 | ITestMethod testMethod, 37 | out string skipReason) 38 | { 39 | skipReason = null; 40 | var runUnitTests = discoveryOptions.GetValue(nameof(ElasticXunitRunOptions.RunUnitTests)); 41 | if (!runUnitTests) return true; 42 | 43 | var skipTests = GetAttributes(testMethod) 44 | .FirstOrDefault(a => a.GetNamedArgument(nameof(SkipTestAttributeBase.Skip))); 45 | 46 | if (skipTests == null) return false; 47 | 48 | skipReason = skipTests.GetNamedArgument(nameof(SkipTestAttributeBase.Reason)); 49 | return true; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/ide-integration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/elasticsearch-net-abstractions/f4ececfb69d25714a2bd0d8c50a7bbac3767d1ec/src/Elastic.Elasticsearch.Xunit/ide-integration.png -------------------------------------------------------------------------------- /src/Elastic.Elasticsearch.Xunit/output.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/elasticsearch-net-abstractions/f4ececfb69d25714a2bd0d8c50a7bbac3767d1ec/src/Elastic.Elasticsearch.Xunit/output.gif -------------------------------------------------------------------------------- /src/Elastic.Stack.ArtifactsApi/Artifact.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.IO; 7 | using Elastic.Stack.ArtifactsApi.Products; 8 | using Elastic.Stack.ArtifactsApi.Resolvers; 9 | using Version = SemVer.Version; 10 | 11 | namespace Elastic.Stack.ArtifactsApi 12 | { 13 | public class Artifact 14 | { 15 | private static readonly Uri BaseUri = new Uri("http://localhost"); 16 | 17 | internal Artifact(Product product, Version version, string downloadUrl, ArtifactBuildState state, 18 | string buildHash) 19 | { 20 | ProductName = product.ProductName; 21 | Version = version; 22 | DownloadUrl = product?.PatchDownloadUrl(downloadUrl); 23 | State = state; 24 | BuildHash = buildHash; 25 | } 26 | 27 | internal Artifact(Product product, Version version, SearchPackage package, string buildHash = null) 28 | { 29 | ProductName = product.ProductName; 30 | Version = version; 31 | State = ArtifactBuildState.Snapshot; 32 | DownloadUrl = product?.PatchDownloadUrl(package.DownloadUrl); 33 | ShaUrl = package.ShaUrl; 34 | AscUrl = package.AscUrl; 35 | BuildHash = buildHash; 36 | } 37 | 38 | public string LocalFolderName 39 | { 40 | get 41 | { 42 | var hashed = string.IsNullOrWhiteSpace(BuildHash) ? string.Empty : $"-build-{BuildHash}"; 43 | switch (State) 44 | { 45 | case ArtifactBuildState.Released: 46 | return $"{ProductName}-{Version}"; 47 | case ArtifactBuildState.Snapshot: 48 | return $"{ProductName}-{Version}{hashed}"; 49 | case ArtifactBuildState.BuildCandidate: 50 | return $"{ProductName}-{Version}{hashed}"; 51 | default: 52 | throw new ArgumentOutOfRangeException(nameof(State), $"{State} not expected here"); 53 | } 54 | } 55 | } 56 | 57 | public string FolderInZip => $"{ProductName}-{Version}"; 58 | 59 | public string Archive 60 | { 61 | get 62 | { 63 | if (!Uri.TryCreate(DownloadUrl, UriKind.Absolute, out var uri)) 64 | uri = new Uri(BaseUri, DownloadUrl); 65 | 66 | return Path.GetFileName(uri.LocalPath); 67 | } 68 | } 69 | 70 | // ReSharper disable UnusedAutoPropertyAccessor.Global 71 | public string ProductName { get; } 72 | public Version Version { get; } 73 | public string DownloadUrl { get; } 74 | 75 | public ArtifactBuildState State { get; } 76 | public string BuildHash { get; } 77 | public string ShaUrl { get; } 78 | 79 | public string AscUrl { get; } 80 | // ReSharper restore UnusedAutoPropertyAccessor.Global 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Elastic.Stack.ArtifactsApi/ArtifactBuildState.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Stack.ArtifactsApi 6 | { 7 | public enum ArtifactBuildState 8 | { 9 | Released, 10 | Snapshot, 11 | BuildCandidate 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Elastic.Stack.ArtifactsApi/Elastic.Stack.ArtifactsApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0;net462;net8.0 4 | Provides a set of classes to resolve the location of Elastic stack products in various stages: released, snapshot and build candidates 5 | elastic,elasticsearch,stack,versioning,artifacts 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Elastic.Stack.ArtifactsApi/Platform/OsMonikers.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace Elastic.Stack.ArtifactsApi.Platform 9 | { 10 | internal static class OsMonikers 11 | { 12 | public static readonly string Windows = "windows"; 13 | public static readonly string Linux = "linux"; 14 | public static readonly string OSX = "darwin"; 15 | 16 | public static string From(OSPlatform platform) 17 | { 18 | if (platform == OSPlatform.Windows) return Windows; 19 | if (platform == OSPlatform.Linux) return Linux; 20 | if (platform == OSPlatform.OSX) return OSX; 21 | return "unknown"; 22 | } 23 | 24 | public static OSPlatform CurrentPlatform() 25 | { 26 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return OSPlatform.Windows; 27 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return OSPlatform.OSX; 28 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return OSPlatform.Linux; 29 | throw new Exception( 30 | $"{RuntimeInformation.OSDescription} is currently not supported please open an issue @elastic/elasticsearch-net-abstractions"); 31 | } 32 | 33 | 34 | public static string CurrentPlatformArchiveExtension() 35 | { 36 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return "zip"; 37 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return "tar.gz"; 38 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return "tar.gz"; 39 | 40 | throw new Exception( 41 | $"{RuntimeInformation.OSDescription} is currently not supported please open an issue @elastic/elasticsearch-net-abstractions"); 42 | } 43 | 44 | public static string CurrentPlatformPackageSuffix() 45 | { 46 | var intelX86Suffix = "x86_64"; 47 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return $"{Windows}-{intelX86Suffix}"; 48 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return $"{OSX}-{intelX86Suffix}"; 49 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return $"{Linux}-{intelX86Suffix}"; 50 | 51 | throw new Exception( 52 | $"{RuntimeInformation.OSDescription} is currently not supported please open an issue @elastic/elasticsearch-net-abstractions"); 53 | } 54 | 55 | internal static string CurrentPlatformSearchFilter() 56 | { 57 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return "zip"; 58 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return "tar"; 59 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return "tar"; 60 | 61 | throw new Exception( 62 | $"{RuntimeInformation.OSDescription} is currently not supported please open an issue @elastic/elasticsearch-net-abstractions"); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Elastic.Stack.ArtifactsApi/Products/ElasticsearchPlugin.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | namespace Elastic.Stack.ArtifactsApi.Products 8 | { 9 | /// An Elasticsearch plugin 10 | public class ElasticsearchPlugin : SubProduct 11 | { 12 | public ElasticsearchPlugin(string plugin, Func isValid = null, 13 | Func listName = null) 14 | : base(plugin, isValid, listName) 15 | { 16 | PlatformDependent = false; 17 | PatchDownloadUrl = s => 18 | { 19 | //Temporary correct plugin download urls as reported by the snapshot API as it currently has a bug 20 | var correct = $"downloads/elasticsearch-plugins/{plugin}"; 21 | return !s.Contains(correct) ? s.Replace("downloads/elasticsearch", correct) : s; 22 | }; 23 | } // ReSharper disable InconsistentNaming 24 | public static ElasticsearchPlugin AnalysisIcu { get; } = new ElasticsearchPlugin("analysis-icu"); 25 | public static ElasticsearchPlugin AnalysisKuromoji { get; } = new ElasticsearchPlugin("analysis-kuromoji"); 26 | public static ElasticsearchPlugin AnalysisPhonetic { get; } = new ElasticsearchPlugin("analysis-phonetic"); 27 | public static ElasticsearchPlugin AnalysisSmartCn { get; } = new ElasticsearchPlugin("analysis-smartcn"); 28 | public static ElasticsearchPlugin AnalysisStempel { get; } = new ElasticsearchPlugin("analysis-stempel"); 29 | public static ElasticsearchPlugin AnalysisUkrainian { get; } = new ElasticsearchPlugin("analysis-ukrainian"); 30 | 31 | public static ElasticsearchPlugin DiscoveryAzureClassic { get; } = 32 | new ElasticsearchPlugin("discovery-azure-classic"); 33 | 34 | public static ElasticsearchPlugin DiscoveryEC2 { get; } = new ElasticsearchPlugin("discovery-ec2"); 35 | public static ElasticsearchPlugin DiscoveryFile { get; } = new ElasticsearchPlugin("discovery-file"); 36 | public static ElasticsearchPlugin DiscoveryGCE { get; } = new ElasticsearchPlugin("discovery-gce"); 37 | 38 | public static ElasticsearchPlugin IngestAttachment { get; } = 39 | new ElasticsearchPlugin("ingest-attachment", version => version >= "5.0.0-alpha3") 40 | { 41 | ShippedByDefaultAsOf = "8.4.0" 42 | }; 43 | 44 | public static ElasticsearchPlugin IngestGeoIp { get; } = 45 | new ElasticsearchPlugin("ingest-geoip", version => version >= "5.0.0-alpha3") 46 | { 47 | ShippedByDefaultAsOf = "6.6.1" 48 | }; 49 | 50 | public static ElasticsearchPlugin IngestUserAgent { get; } = 51 | new ElasticsearchPlugin("ingest-user-agent", version => version >= "5.0.0-alpha3") 52 | { 53 | ShippedByDefaultAsOf = "6.6.1" 54 | }; 55 | 56 | public static ElasticsearchPlugin MapperAttachment { get; } = new ElasticsearchPlugin("mapper-attachments"); 57 | public static ElasticsearchPlugin MapperMurmur3 { get; } = new ElasticsearchPlugin("mapper-murmur3"); 58 | public static ElasticsearchPlugin MapperSize { get; } = new ElasticsearchPlugin("mapper-size"); 59 | 60 | public static ElasticsearchPlugin RepositoryAzure { get; } = new ElasticsearchPlugin("repository-azure"); 61 | public static ElasticsearchPlugin RepositoryGCS { get; } = new ElasticsearchPlugin("repository-gcs"); 62 | public static ElasticsearchPlugin RepositoryHDFS { get; } = new ElasticsearchPlugin("repository-hdfs"); 63 | public static ElasticsearchPlugin RepositoryS3 { get; } = new ElasticsearchPlugin("repository-s3"); 64 | 65 | public static ElasticsearchPlugin StoreSMB { get; } = new ElasticsearchPlugin("store-smb"); 66 | 67 | public static ElasticsearchPlugin DeleteByQuery { get; } = 68 | new ElasticsearchPlugin("delete-by-query", version => version < "5.0.0-alpha3"); 69 | 70 | public static ElasticsearchPlugin XPack { get; } = 71 | new ElasticsearchPlugin("x-pack", listName: v => v >= "6.2.0" && v < "6.3.0" ? "x-pack-core" : "x-pack", 72 | isValid: v => v < "6.3.0") {ShippedByDefaultAsOf = "6.3.0"}; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Elastic.Stack.ArtifactsApi/Products/Product.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Collections.Concurrent; 6 | using Elastic.Stack.ArtifactsApi.Platform; 7 | 8 | namespace Elastic.Stack.ArtifactsApi.Products 9 | { 10 | public class Product 11 | { 12 | private static readonly ConcurrentDictionary CachedProducts = 13 | new ConcurrentDictionary(); 14 | 15 | private static readonly ElasticVersion DefaultIncludePlatformSuffix = ElasticVersion.From("7.0.0-alpha1"); 16 | 17 | private Product(string productName) => ProductName = productName; 18 | 19 | protected Product(string productName, SubProduct relatedProduct, 20 | ElasticVersion platformVersionSuffixAfter = null) : this(productName) 21 | { 22 | SubProduct = relatedProduct; 23 | PlatformSuffixAfter = platformVersionSuffixAfter ?? DefaultIncludePlatformSuffix; 24 | } 25 | 26 | public SubProduct SubProduct { get; } 27 | 28 | public string Moniker => SubProduct?.SubProductName ?? ProductName; 29 | 30 | public string Extension => PlatformDependent ? OsMonikers.CurrentPlatformArchiveExtension() : "zip"; 31 | 32 | public string ProductName { get; } 33 | 34 | public bool PlatformDependent => SubProduct?.PlatformDependent ?? true; 35 | 36 | public ElasticVersion PlatformSuffixAfter { get; } 37 | 38 | public static Product Elasticsearch { get; } = From("elasticsearch"); 39 | 40 | public static Product Kibana { get; } = From("kibana", platformInZipAfter: "1.0.0"); 41 | 42 | public static Product From(string product, SubProduct subProduct = null, 43 | ElasticVersion platformInZipAfter = null) => 44 | CachedProducts.GetOrAdd(subProduct == null ? $"{product}" : $"{product}/{subProduct.SubProductName}", 45 | k => new Product(product, subProduct, platformInZipAfter)); 46 | 47 | public static Product ElasticsearchPlugin(ElasticsearchPlugin plugin) => From("elasticsearch-plugins", plugin); 48 | 49 | public override string ToString() => 50 | SubProduct != null ? $"{ProductName}/{SubProduct.SubProductName}" : ProductName; 51 | 52 | public string PatchDownloadUrl(string downloadUrl) => SubProduct?.PatchDownloadUrl(downloadUrl) ?? downloadUrl; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Elastic.Stack.ArtifactsApi/Products/SubProduct.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | namespace Elastic.Stack.ArtifactsApi.Products 8 | { 9 | public class SubProduct 10 | { 11 | private readonly Func _getExistsMoniker; 12 | 13 | private readonly Func _isValid; 14 | 15 | public SubProduct(string subProject, Func isValid = null, 16 | Func listName = null) 17 | { 18 | SubProductName = subProject; 19 | _isValid = isValid ?? (v => true); 20 | _getExistsMoniker = listName ?? (v => subProject); 21 | } 22 | 23 | public string SubProductName { get; } 24 | 25 | public ElasticVersion ShippedByDefaultAsOf { get; set; } 26 | 27 | /// 28 | /// Temporary, snapshot API reports bad plugin download urls 29 | /// 30 | public Func PatchDownloadUrl { get; set; } = s => s; 31 | 32 | public bool PlatformDependent { get; protected set; } 33 | 34 | /// what moniker to use when asserting the sub product is already present 35 | public string GetExistsMoniker(ElasticVersion version) => _getExistsMoniker(version); 36 | 37 | /// Whether the sub project is included in the distribution out of the box for the given version 38 | public bool IsIncludedOutOfTheBox(ElasticVersion version) 39 | { 40 | if (ShippedByDefaultAsOf is null) 41 | return false; 42 | 43 | // When we are using a snapshot version of Elasticsearch compare on base version. 44 | // This ensures that when testing with a snapshot, we install plugins correctly. 45 | // e.g. When testing with 8.4.0-SNAPSHOT of elasticsearch, we don't expect to ingest-attachment, 46 | // added in-box in 8.4.0 to be installed. 47 | return version.ArtifactBuildState == ArtifactBuildState.Snapshot 48 | ? version.BaseVersion() >= ShippedByDefaultAsOf.BaseVersion() 49 | : version >= ShippedByDefaultAsOf; 50 | } 51 | 52 | /// Whether the subProject is valid for the given version 53 | public bool IsValid(ElasticVersion version) => IsIncludedOutOfTheBox(version) || _isValid(version); 54 | 55 | public override string ToString() => SubProductName; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Elastic.Stack.ArtifactsApi/README.md: -------------------------------------------------------------------------------- 1 | # Elastic.Stack.ArtifactsApi 2 | 3 | Library to fetch the url and metadata for released artifacts. 4 | 5 | Supports: 6 | 7 | 1. Snapshots builds 8 | * `7.4.0-SNAPSHOT` 9 | * `latest-MAJOR` where `MAJOR` is a single integer representing the major you want a snapshot for 10 | * `latest` latest greatest 11 | 12 | 2. BuildCandidates 13 | * `commit_hash:version` a build candidate for a version 14 | 15 | 3. Released versions 16 | * `MAJOR.MINOR.PATH` where `MAJOR` is still supported as defined by the EOL policy of Elastic. 17 | * Note if the version exists but is not yet released it will resolve as a build candidate 18 | 19 | 20 | ## Usage 21 | 22 | First create an elastic version 23 | 24 | ```csharp 25 | var version = ElasticVersion.From(versionString); 26 | ``` 27 | 28 | Where `versionString` is a string in the aforementioned formats. `version.ArtifactBuildState` represents the type of version parsed. 29 | 30 | ```csharp 31 | var version = ElasticVersion.From(versionString); 32 | ``` 33 | 34 | To go from a version to an artifact do the following 35 | 36 | ```csharp 37 | var product = Product.From("elasticsearch"); 38 | var artifact = version.Artifact(product); 39 | ``` 40 | By first creating a `product` we can then pass that `product` to `version.Artifact` to get an artifact to that product's version. 41 | 42 | A product can be a main product such as `elasticsearch` or a related product e.g 43 | 44 | ```csharp 45 | var product = Product.From("elasticsearch-plugins", "analysis-icu"); 46 | var artifact = version.Artifact(product); 47 | ``` 48 | 49 | To aid with discoverability we ship with some statics so you do not need to guess the right monikers. 50 | 51 | ```csharp 52 | Product.Elasticsearch; 53 | Product.Kibana; 54 | Product.ElasticsearchPlugin(ElasticsearchPlugin.AnalysisIcu); 55 | ``` 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/Elastic.Stack.ArtifactsApi/Resolvers/ApiResolver.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Concurrent; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Linq; 10 | 11 | #if NETFRAMEWORK 12 | using System.Net; 13 | #endif 14 | 15 | using System.Net.Http; 16 | 17 | #if !NETFRAMEWORK 18 | 19 | using System.Security.Authentication; 20 | 21 | #endif 22 | 23 | using System.Text.Json; 24 | using System.Text.Json.Serialization; 25 | using System.Text.RegularExpressions; 26 | 27 | namespace Elastic.Stack.ArtifactsApi.Resolvers 28 | { 29 | public static class ApiResolver 30 | { 31 | private const string ArtifactsApiUrl = "https://artifacts-api.elastic.co/v1/"; 32 | 33 | private static readonly ConcurrentDictionary Releases = new ConcurrentDictionary(); 34 | 35 | private static HttpClient HttpClient { get; } = 36 | #if NETFRAMEWORK 37 | new HttpClient 38 | #else 39 | // SslProtocols is only available in .NET Framework 4.7.2 and above 40 | new HttpClient(new HttpClientHandler { SslProtocols = SslProtocols.Tls12 }) 41 | #endif 42 | { 43 | BaseAddress = new Uri(ArtifactsApiUrl) 44 | }; 45 | 46 | private static Regex BuildHashRegex { get; } = 47 | new Regex(@"https://(?:snapshots|staging).elastic.co/(\d+\.\d+\.\d+-([^/]+)?)"); 48 | 49 | public static string FetchJson(string path) 50 | { 51 | using (var stream = HttpClient.GetStreamAsync(path).GetAwaiter().GetResult()) 52 | using (var fileStream = new StreamReader(stream)) 53 | return fileStream.ReadToEnd(); 54 | } 55 | 56 | public static bool IsReleasedVersion(string version) 57 | { 58 | if (Releases.TryGetValue(version, out var released)) 59 | return released; 60 | var versionPath = "https://github.com/elastic/elasticsearch/releases/tag/v" + version; 61 | var message = new HttpRequestMessage { Method = HttpMethod.Head, RequestUri = new Uri(versionPath) }; 62 | 63 | using (var response = HttpClient.SendAsync(message).GetAwaiter().GetResult()) 64 | { 65 | released = response.IsSuccessStatusCode; 66 | Releases.TryAdd(version, released); 67 | return released; 68 | } 69 | } 70 | 71 | public static string LatestBuildHash(string version) 72 | { 73 | var json = FetchJson($"search/{version}/msi"); 74 | try 75 | { 76 | // if packages is empty it turns into an array[] otherwise its a dictionary :/ 77 | var packages = JsonSerializer.Deserialize(json).Packages; 78 | if (packages.Count == 0) 79 | throw new Exception("Can not get build hash for: " + version); 80 | return GetBuildHash(packages.First().Value.DownloadUrl); 81 | } 82 | catch 83 | { 84 | throw new Exception("Can not get build hash for: " + version); 85 | } 86 | } 87 | 88 | public static string GetBuildHash(string url) 89 | { 90 | var tokens = BuildHashRegex.Split(url).Where(s => !string.IsNullOrWhiteSpace(s)).ToArray(); 91 | if (tokens.Length < 2) 92 | throw new Exception("Can not parse build hash from: " + url); 93 | 94 | return tokens[1]; 95 | } 96 | 97 | #if NETFRAMEWORK 98 | static ApiResolver() => 99 | ServicePointManager.SecurityProtocol = ServicePointManager.SecurityProtocol 100 | & ~SecurityProtocolType.Ssl3 101 | & ~SecurityProtocolType.Tls 102 | & ~SecurityProtocolType.Tls11; 103 | #endif 104 | } 105 | 106 | internal class ArtifactsVersionsResponse 107 | { 108 | [JsonPropertyName("versions")] public List Versions { get; set; } 109 | } 110 | 111 | internal class ArtifactsSearchResponse 112 | { 113 | [JsonPropertyName("packages")] public Dictionary Packages { get; set; } 114 | } 115 | 116 | internal class SearchPackage 117 | { 118 | [JsonPropertyName("url")] public string DownloadUrl { get; set; } 119 | [JsonPropertyName("sha_url")] public string ShaUrl { get; set; } 120 | [JsonPropertyName("asc_url")] public string AscUrl { get; set; } 121 | [JsonPropertyName("type")] public string Type { get; set; } 122 | [JsonPropertyName("architecture")] public string Architecture { get; set; } 123 | [JsonPropertyName("os")] public string[] OperatingSystem { get; set; } 124 | [JsonPropertyName("classifier")] public string Classifier { get; set; } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Elastic.Stack.ArtifactsApi/Resolvers/ReleasedVersionResolver.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Runtime.InteropServices; 6 | using Elastic.Stack.ArtifactsApi.Platform; 7 | using Elastic.Stack.ArtifactsApi.Products; 8 | using SemVer; 9 | 10 | namespace Elastic.Stack.ArtifactsApi.Resolvers 11 | { 12 | public static class ReleasedVersionResolver 13 | { 14 | //https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.1.0-linux-x86_64.tar.gz 15 | private const string ArtifactsUrl = "https://artifacts.elastic.co"; 16 | 17 | public static bool TryResolve(Product product, Version version, OSPlatform os, out Artifact artifact) 18 | { 19 | var p = product.Moniker; 20 | var downloadPath = $"{ArtifactsUrl}/downloads/{product}"; 21 | var archive = $"{p}-{version}-{OsMonikers.CurrentPlatformPackageSuffix()}.{product.Extension}"; 22 | if (!product.PlatformDependent || version <= product.PlatformSuffixAfter) 23 | archive = $"{p}-{version}.{product.Extension}"; 24 | 25 | var downloadUrl = $"{downloadPath}/{archive}"; 26 | artifact = new Artifact(product, version, downloadUrl, ArtifactBuildState.Released, null); 27 | return true; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Elastic.Stack.ArtifactsApi/Resolvers/SnapshotApiResolver.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Runtime.InteropServices; 9 | using System.Text.Json; 10 | using System.Text.RegularExpressions; 11 | using System.Threading; 12 | using Elastic.Stack.ArtifactsApi.Platform; 13 | using Elastic.Stack.ArtifactsApi.Products; 14 | using Version = SemVer.Version; 15 | 16 | namespace Elastic.Stack.ArtifactsApi.Resolvers 17 | { 18 | public static class SnapshotApiResolver 19 | { 20 | public static readonly Lazy> AvailableVersions = 21 | new(LoadVersions, LazyThreadSafetyMode.ExecutionAndPublication); 22 | 23 | private static Regex PackageProductRegex { get; } = 24 | new Regex(@"(.*?)-(\d+\.\d+\.\d+(?:-(?:SNAPSHOT|alpha\d+|beta\d+|rc\d+))?)"); 25 | 26 | private static Version IncludeOsMoniker { get; } = new Version("7.0.0"); 27 | 28 | public static Version LatestReleaseOrSnapshot => AvailableVersions.Value.OrderByDescending(v => v).First(); 29 | 30 | public static bool TryResolve(Product product, Version version, OSPlatform os, string filters, 31 | out Artifact artifact) 32 | { 33 | artifact = null; 34 | var p = product.SubProduct?.SubProductName ?? product.ProductName; 35 | var query = p; 36 | if (product.PlatformDependent && version > product.PlatformSuffixAfter) 37 | query += $",{OsMonikers.From(os)}"; 38 | else if (product.PlatformDependent) 39 | query += $",{OsMonikers.CurrentPlatformSearchFilter()}"; 40 | if (!string.IsNullOrWhiteSpace(filters)) 41 | query += $",{filters}"; 42 | 43 | var packages = new Dictionary(); 44 | try 45 | { 46 | var json = ApiResolver.FetchJson($"search/{version}/{query}"); 47 | // if packages is empty it turns into an array[] otherwise its a dictionary :/ 48 | packages = JsonSerializer.Deserialize(json).Packages; 49 | } 50 | catch 51 | { 52 | } 53 | 54 | if (packages == null || packages.Count == 0) return false; 55 | var list = packages 56 | .OrderByDescending(k => k.Value.Classifier == null ? 1 : 0) 57 | .ToArray(); 58 | 59 | var ext = OsMonikers.CurrentPlatformArchiveExtension(); 60 | var shouldEndWith = $"{version}.{ext}"; 61 | if (product.PlatformDependent && version > product.PlatformSuffixAfter) 62 | shouldEndWith = $"{version}-{OsMonikers.CurrentPlatformPackageSuffix()}.{ext}"; 63 | foreach (var kv in list) 64 | { 65 | if (product.PlatformDependent && !kv.Key.EndsWith(shouldEndWith)) continue; 66 | 67 | 68 | var tokens = PackageProductRegex.Split(kv.Key).Where(s => !string.IsNullOrWhiteSpace(s)).ToArray(); 69 | if (tokens.Length < 2) continue; 70 | 71 | if (!tokens[0].Equals(p, StringComparison.CurrentCultureIgnoreCase)) continue; 72 | if (!tokens[1].Equals(version.ToString(), StringComparison.CurrentCultureIgnoreCase)) continue; 73 | // https://snapshots.elastic.co/7.4.0-677857dd/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-7.4.0-SNAPSHOT.zip 74 | var buildHash = ApiResolver.GetBuildHash(kv.Value.DownloadUrl); 75 | artifact = new Artifact(product, version, kv.Value, buildHash); 76 | } 77 | 78 | return false; 79 | } 80 | 81 | 82 | private static IReadOnlyCollection LoadVersions() 83 | { 84 | var json = ApiResolver.FetchJson("versions"); 85 | var versions = JsonSerializer.Deserialize(json).Versions; 86 | 87 | return new List(versions.Select(v => new Version(v))); 88 | } 89 | 90 | public static Version LatestSnapshotForMajor(int major) 91 | { 92 | var range = new SemVer.Range($"~{major}"); 93 | return AvailableVersions.Value 94 | .Reverse() 95 | .FirstOrDefault(v => 96 | v.PreRelease == "SNAPSHOT" && range.IsSatisfied(v.ToString().Replace("-SNAPSHOT", ""))); 97 | } 98 | 99 | public static Version LatestReleaseOrSnapshotForMajor(int major) 100 | { 101 | var range = new SemVer.Range($"~{major}"); 102 | return AvailableVersions.Value 103 | .Reverse() 104 | .FirstOrDefault(v => range.IsSatisfied(v.ToString().Replace("-SNAPSHOT", ""))); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Elastic.Stack.ArtifactsApi/Resolvers/StagingVersionResolver.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Stack.ArtifactsApi.Platform; 6 | using Elastic.Stack.ArtifactsApi.Products; 7 | using SemVer; 8 | 9 | namespace Elastic.Stack.ArtifactsApi.Resolvers 10 | { 11 | public static class StagingVersionResolver 12 | { 13 | private const string StagingUrlFormat = "https://staging.elastic.co/{0}-{1}"; 14 | 15 | // https://staging.elastic.co/7.2.0-957e3089/downloads/elasticsearch/elasticsearch-7.2.0-linux-x86_64.tar.gz 16 | // https://staging.elastic.co/7.2.0-957e3089/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-7.2.0.zip 17 | public static bool TryResolve(Product product, Version version, string buildHash, out Artifact artifact) 18 | { 19 | artifact = null; 20 | if (string.IsNullOrWhiteSpace(buildHash)) return false; 21 | 22 | var p = product.Moniker; 23 | var stagingRoot = string.Format(StagingUrlFormat, version, buildHash); 24 | var archive = $"{p}-{version}-{OsMonikers.CurrentPlatformPackageSuffix()}.{product.Extension}"; 25 | if (!product.PlatformDependent || version <= product.PlatformSuffixAfter) 26 | archive = $"{p}-{version}.{product.Extension}"; 27 | 28 | var downloadUrl = $"{stagingRoot}/downloads/{product}/{archive}"; 29 | artifact = new Artifact(product, version, downloadUrl, ArtifactBuildState.BuildCandidate, buildHash); 30 | return true; 31 | } 32 | } 33 | } 34 | --------------------------------------------------------------------------------