├── .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 | 
100 |
101 | Or on the command line using `dotnet test`
102 |
103 | 
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 |
--------------------------------------------------------------------------------