├── .ci ├── ca.crt ├── ca.key ├── testnode.crt └── testnode.key ├── .gitattributes ├── .gitignore ├── Elastic.Console ├── Elastic.Console.psd1 ├── Elastic.Console.psm1 ├── ElasticVersion.cs ├── ElasticsearchRequestBody.cs ├── README.md └── ServerCertificateValidation.cs ├── LICENSE ├── README.md ├── build.ps1 ├── build └── elastic-cluster-icon.png ├── test.ps1 └── tests ├── Integration.tests.ps1 ├── Unit.tests.ps1 └── elasticsearch.ps1 /.ci/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDSjCCAjKgAwIBAgIVAJQLm8V2LcaCTHUcoIfO+KL63nG3MA0GCSqGSIb3DQEB 3 | CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu 4 | ZXJhdGVkIENBMB4XDTIwMDIyNjA1NTA1N1oXDTIzMDIyNTA1NTA1N1owNDEyMDAG 5 | A1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5lcmF0ZWQgQ0Ew 6 | ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDYyajkPvGtUOE5M1OowQfB 7 | kWVrWjo1+LIxzgCeRHp0YztLtdVJ0sk2xoSrt2uZpxcPepdyOseLTjFJex1D2yCR 8 | AEniIqcFif4G72nDih2LlbhpUe/+/MTryj8ZTkFTzI+eMmbQi5FFMaH+kwufmdt/ 9 | 5/w8YazO18SxxJUlzMqzfNUrhM8vvvVdxgboU7PWhk28wZHCMHQovomHmzclhRpF 10 | N0FMktA98vHHeRjH19P7rNhifSd7hZzoH3H148HVAKoPgqnZ6vW2O2YfAWOP6ulq 11 | cyszr57p8fS9B2wSdlWW7nVHU1JuKcYD67CxbBS23BeGFgCj4tiNrmxO8S5Yf85v 12 | AgMBAAGjUzBRMB0GA1UdDgQWBBSWAlip9eoPmnG4p4OFZeOUBlAbNDAfBgNVHSME 13 | GDAWgBSWAlip9eoPmnG4p4OFZeOUBlAbNDAPBgNVHRMBAf8EBTADAQH/MA0GCSqG 14 | SIb3DQEBCwUAA4IBAQA19qqrMTWl7YyId+LR/QIHDrP4jfxmrEELrAL58q5Epc1k 15 | XxZLzOBSXoBfBrPdv+3XklWqXrZjKWfdkux0Xmjnl4qul+srrZDLJVZG3I7IrITh 16 | AmQUmL9MuPiMnAcxoGZp1xpijtW8Qmd2qnambbljWfkuVaa4hcVRfrAX6TciIQ21 17 | bS5aeLGrPqR14h30YzDp0RMmTujEa1o6ExN0+RSTkE9m89Q6WdM69az8JW7YkWqm 18 | I+UCG3TcLd3TXmN1zNQkq4y2ObDK4Sxy/2p6yFPI1Fds5w/zLfBOvvPQY61vEqs8 19 | SCCcQIe7f6NDpIRIBlty1C9IaEHj7edyHjF6rtYb 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /.ci/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpgIBAAKCAQEA2Mmo5D7xrVDhOTNTqMEHwZFla1o6NfiyMc4AnkR6dGM7S7XV 3 | SdLJNsaEq7drmacXD3qXcjrHi04xSXsdQ9sgkQBJ4iKnBYn+Bu9pw4odi5W4aVHv 4 | /vzE68o/GU5BU8yPnjJm0IuRRTGh/pMLn5nbf+f8PGGsztfEscSVJczKs3zVK4TP 5 | L771XcYG6FOz1oZNvMGRwjB0KL6Jh5s3JYUaRTdBTJLQPfLxx3kYx9fT+6zYYn0n 6 | e4Wc6B9x9ePB1QCqD4Kp2er1tjtmHwFjj+rpanMrM6+e6fH0vQdsEnZVlu51R1NS 7 | binGA+uwsWwUttwXhhYAo+LYja5sTvEuWH/ObwIDAQABAoIBAQC8QDGnMnmPdWJ+ 8 | 13FYY3cmwel+FXXjFDk5QpgK15A2rUz6a8XxO1d7d1wR+U84uH4v9Na6XQyWjaoD 9 | EyPQnuJiyAtgkZLUHoY244PGR5NsePEQlBSCKmGeF5w/j1LvP/2e9EmP4wKdQYJY 10 | nLxFNcgEBCFnFbKIU5n8fKa/klybCrwlBokenyBro02tqH4LL7h1YMRRrl97fv1V 11 | e/y/0WcMN+KnMglfz6haimBRV2yamCCHHmBImC+wzOgT/quqlxPfI+a3ScHxuA65 12 | 3QyCavaqlPh+T3lXnN/Na4UWqFtzMmwgJX2x1zM5qiln46/JoDiXtagvV43L3rNs 13 | LhPRFeIRAoGBAPhEB7nNpEDNjIRUL6WpebWS9brKAVY7gYn7YQrKGhhCyftyaiBZ 14 | zYgxPaJdqYXf+DmkWlANGoYiwEs40QwkR/FZrvO4+Xh3n3dgtl59ZmieuoQvDsG+ 15 | RYIj+TfBaqhewhZNMMl7dxz7DeyQhyRCdsvl3VqJM0RuOsIrzrhCIEItAoGBAN+K 16 | lgWI7swDpOEaLmu+IWMkGImh1LswXoZqIgi/ywZ7htZjPzidOIeUsMi+lrYsKojG 17 | uU3sBxASsf9kYXDnuUuUbGT5M/N2ipXERt7klUAA/f5sg1IKlTrabaN/HGs/uNtf 18 | Efa8v/h2VyTurdPCJ17TNpbOMDwX1qGM62tyt2CLAoGBAIHCnP8iWq18QeuQTO8b 19 | a3/Z9hHRL22w4H4MI6aOB6GSlxuTq6CJD4IVqo9IwSg17fnCy2l3z9s4IqWuZqUf 20 | +XJOW8ELd2jdrT2qEOfGR1Z7UCVyqxXcq1vgDYx0zZh/HpalddB5dcJx/c8do2Ty 21 | UEE2PcHqYB9uNcvzNbLc7RtpAoGBALbuU0yePUTI6qGnajuTcQEPpeDjhRHWSFRZ 22 | ABcG1N8uMS66Mx9iUcNp462zgeP8iqY5caUZtMHreqxT+gWKK7F0+as7386pwElF 23 | QPXgO18QMMqHBIQb0vlBjJ1SRPBjSiSDTVEML1DljvTTOX7kEJHh6HdKrmBO5b54 24 | cqMQUo53AoGBAPVWRPUXCqlBz914xKna0ZUh2aesRBg5BvOoq9ey9c52EIU5PXL5 25 | 0Isk8sWSsvhl3tjDPBH5WuL5piKgnCTqkVbEHmWu9s1T57Mw6NuxlPMLBWvyv4c6 26 | tB9brOxv0ui3qGMuBsBoDKbkNnwXyOXLyFg7O+H4l016A3mLQzJM+NGV 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /.ci/testnode.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDYjCCAkqgAwIBAgIVAIZQH0fe5U+bGQ6m1JUBO/AQkQ/9MA0GCSqGSIb3DQEB 3 | CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu 4 | ZXJhdGVkIENBMB4XDTIwMDMyNzE5MTcxMVoXDTIzMDMyNzE5MTcxMVowEzERMA8G 5 | A1UEAxMIaW5zdGFuY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDB 6 | fco1t1+sE1gTwTVGcXKZqJTP2GjMHM0cfJE5KKfwC5B+pHADRT6FZxvepgKjEBDt 7 | CK+2Rmotyeb15XXMSKguNhyT+2PuKvT5r05L7P91XRYXrwxG2swJPtct7A87xdFa 8 | Ek+YRpqGGmTaux2jOELMiAmqEzoj6w/xFq+LF4SolTW4wOL2eLFkEFHBX2oCwU5T 9 | Q+B+7E9zL45nFWlkeRGJ+ZQTnRNZ/1r4N9A9Gtj4x/H1/y4inWndikdxAb5QiEYJ 10 | T+vbQWzHYWjz13ttHJsz+6T8rvA1jK+buHgVh4K8lV13X9k54soBqHB8va7/KIJP 11 | g8gvd6vusEI7Bmfl1as7AgMBAAGjgYswgYgwHQYDVR0OBBYEFKnnpvuVYwtFSUis 12 | WwN9OHLyExzJMB8GA1UdIwQYMBaAFJYCWKn16g+acbing4Vl45QGUBs0MDsGA1Ud 13 | EQQ0MDKCCWxvY2FsaG9zdIIIaW5zdGFuY2WHBH8AAAGHEAAAAAAAAAAAAAAAAAAA 14 | AAGCA2VzMTAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQAPNsIoD4GBrTgR 15 | jfvBuHS6eU16P95m16O8Mdpr4SMQgWLQUhs8aoVgfwpg2TkbCWxOe6khJOyNm7bf 16 | fW4aFQ/OHcQV4Czz3c7eOHTWSyMlCOv+nRXd4giJZ5TOHw1zKGmKXOIvhvE6RfdF 17 | uBBfrusk164H4iykm0Bbr/wo4d6wuebp3ZYLPw5zV0D08rsaR+3VJ9VxWuFpdm/r 18 | 2onYOohyuX9DRjAczasC+CRRQN4eHJlRfSQB8WfTKw3EloRJJDAg6SJyGiAJ++BF 19 | hnqfNcEyKes2AWagFF9aTbEJMrzMhH+YB5F+S/PWvMUlFzcoocVKqc4pIrjKUNWO 20 | 6nbTxeAB 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /.ci/testnode.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAwX3KNbdfrBNYE8E1RnFymaiUz9hozBzNHHyROSin8AuQfqRw 3 | A0U+hWcb3qYCoxAQ7QivtkZqLcnm9eV1zEioLjYck/tj7ir0+a9OS+z/dV0WF68M 4 | RtrMCT7XLewPO8XRWhJPmEaahhpk2rsdozhCzIgJqhM6I+sP8RavixeEqJU1uMDi 5 | 9nixZBBRwV9qAsFOU0PgfuxPcy+OZxVpZHkRifmUE50TWf9a+DfQPRrY+Mfx9f8u 6 | Ip1p3YpHcQG+UIhGCU/r20Fsx2Fo89d7bRybM/uk/K7wNYyvm7h4FYeCvJVdd1/Z 7 | OeLKAahwfL2u/yiCT4PIL3er7rBCOwZn5dWrOwIDAQABAoIBAFcm4ICnculf4Sks 8 | umFbUiISA81GjZV6V4zAMu1K+bGuk8vnJyjh9JJD6hK0NbXa07TgV7zDJKoxKd2S 9 | GCgGhfIin2asMcuh/6vDIYIjYsErR3stdlsnzAVSD7v4ergSlwR6AO32xz0mAE1h 10 | QK029yeHEstPU72/7/NIo5MD6dXAbut1MzgijZD8RQo1z21D6qmLcPTVTfkn7a3W 11 | MY3y7XUIkA1TOyIRsH3k6F6NBWkvtXbwOUeLCJ14EvS8T9BqhIhPDZv8mQTRLDOD 12 | tQRyC4Cnw+UhYmnMFJhj6N2jpTBv/AdoKcRC56uBJyPW+dxj6i4e7n3pQuxqRvpI 13 | LLJJsskCgYEA4QQxzuJizLKV75rE+Qxg0Ej0Gid1aj3H5eeTZOUhm9KC8KDfPdpk 14 | msKaNzJq/VDcqHPluGS1jYZVgZlal1nk5xKBcbQ4n297VPVd+sLtlf0bj4atlDUO 15 | +iOVo0H7k5yWvj+TzVRlc5zjDLcnQh8i+22o3+65hIrb2zpzg/cCZJ8CgYEA3CJX 16 | bjmWPQ0uZVIa8Wz8cJFtKT9uVl7Z3/f6HjN9I0b/9MmVlNxQVAilVwhDkzR/UawG 17 | QeRFBJ6XWRwX0aoMq+O9VSNu/R2rtEMpIYt3LwbI3yw6GRoCdB5qeL820O+KX5Fl 18 | /z+ZNgrHgA1yKPVf+8ke2ZtLEqPHMN+BMuq8t+UCgYEAy0MfvzQPbbuw55WWcyb0 19 | WZJdNzcHwKX4ajzrj4vP9VOPRtD7eINMt+QsrMnVjei6u0yeahhHTIXZvc2K4Qeq 20 | V/YGinDzaUqqTU+synXFauUOPXO6XxQi6GC2rphPKsOcBFWoLSYc0vgYvgbA5uD7 21 | l8Yyc77RROKuwfWmHcJHHh8CgYBurGFSjGdJWHgr/oSHPqkIG0VLiJV7nQJjBPRd 22 | /Lr8YnTK6BJpHf7Q0Ov3frMirjEYqakXtaExel5TMbmT8q+eN8h3pnHlleY+oclr 23 | EQghv4J8GWs4NYhoQuZ6wH/ZuaTS+XHTS3FG51J3wcrUZtET8ICvHNE4lNjPbH8z 24 | TysENQKBgHER1RtDFdz+O7mlWibrHk8JDgcVdZV/pBF+9cb7r/orkH9RLAHDlsAO 25 | tuSVaQmm5eqgaAxMamBXSyw1lir07byemyuEDg0mJ1rNUGsAY8P+LWr579gvKMme 26 | 5gvrJr99JkBTV3z+TiL7dZa52eW00Ijqg2qcbHGpq3kXWWkbd8Tn 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.psd1 text -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | specs/ 2 | -------------------------------------------------------------------------------- /Elastic.Console/Elastic.Console.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'Elastic.Console' 3 | # 4 | # Generated by: Elastic and Contributors 5 | # 6 | # Generated on: 11/08/2020 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'Elastic.Console.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '7.8.1' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = '8cae79e0-7214-458e-8546-fec8ec9c1d68' 22 | 23 | # Author of this module 24 | Author = 'Elastic and Contributors' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'Elastic' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) 2019 Elasticsearch BV. All rights reserved. Licensed under Apache 2.0' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'Elastic.Console - cmdlets to simplify making requests to Elasticsearch with PowerShell and PowerShell Core' 34 | 35 | # Minimum version of the PowerShell engine required by this module 36 | PowerShellVersion = '3.0' 37 | 38 | # Name of the PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # ClrVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | RequiredAssemblies = 'System.Web.dll', 'System.Net.dll' 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 72 | FunctionsToExport = 'Invoke-Elasticsearch', 'ConvertFrom-KibanaConsole', 73 | 'Get-ElasticsearchVersion', 'Set-ElasticsearchVersion' 74 | 75 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 76 | CmdletsToExport = 'Invoke-Elasticsearch', 'ConvertFrom-KibanaConsole', 77 | 'Get-ElasticsearchVersion', 'Set-ElasticsearchVersion' 78 | 79 | # Variables to export from this module 80 | # VariablesToExport = @() 81 | 82 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 83 | AliasesToExport = 'es', 'ckc', 'hash' 84 | 85 | # DSC resources to export from this module 86 | # DscResourcesToExport = @() 87 | 88 | # List of all modules packaged with this module 89 | # ModuleList = @() 90 | 91 | # List of all files packaged with this module 92 | FileList = 'Elastic.Console.psm1', 'ElasticsearchRequestBody.cs', 93 | 'ElasticVersion.cs', 'ServerCertificateValidation.cs' 94 | 95 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 96 | PrivateData = @{ 97 | 98 | PSData = @{ 99 | 100 | # Tags applied to this module. These help with module discovery in online galleries. 101 | Tags = 'elastic','elasticsearch','search' 102 | 103 | # A URL to the license for this module. 104 | LicenseUri = 'https://github.com/elastic/powershell/blob/master/LICENSE' 105 | 106 | # A URL to the main website for this project. 107 | ProjectUri = 'https://github.com/elastic/powershell/' 108 | 109 | # A URL to an icon representing this module. 110 | IconUri = 'https://raw.githubusercontent.com/elastic/powershell/master/build/elastic-cluster-icon.png' 111 | 112 | # ReleaseNotes of this module 113 | ReleaseNotes = 'Update to 7.8.1 specs' 114 | 115 | # Prerelease string of this module 116 | Prerelease = 'rc1' 117 | 118 | # Flag to indicate whether the module requires explicit user acceptance for install/update/save 119 | # RequireLicenseAcceptance = $false 120 | 121 | # External dependent modules of this module 122 | # ExternalModuleDependencies = @() 123 | 124 | } # End of PSData hashtable 125 | 126 | } # End of PrivateData hashtable 127 | 128 | # HelpInfo URI of this module 129 | # HelpInfoURI = '' 130 | 131 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 132 | # DefaultCommandPrefix = '' 133 | 134 | } 135 | 136 | -------------------------------------------------------------------------------- /Elastic.Console/Elastic.Console.psm1: -------------------------------------------------------------------------------- 1 | # Register-ArgumentCompleter needs minimum v3.0 2 | Set-StrictMode -Version 3.0 3 | 4 | #load types used by the module 5 | if (-not ([System.Management.Automation.PSTypeName]'Elastic.ElasticsearchRequestBody').Type) { 6 | # A type to accept only string or hashtable as input. 7 | # This allows $Body in Invoke-Elasticsearch to *not* bind to ElasticsearchRequest 8 | Add-Type -LiteralPath (Join-Path $PSScriptRoot -ChildPath "ElasticsearchRequestBody.cs") 9 | } 10 | 11 | if (-not ([System.Management.Automation.PSTypeName]'Elastic.ElasticVersion').Type) { 12 | # A type to represent an Elasticsearch version 13 | Add-Type -LiteralPath (Join-Path $PSScriptRoot -ChildPath "ElasticVersion.cs") 14 | } 15 | 16 | if (-not ([System.Management.Automation.PSTypeName]'Elastic.ServerCertificateValidation').Type) { 17 | # A type for skipping Certificate validation. There is a bug in some versions of PowerShell 18 | # where using a script block does not work, but using a class does. 19 | Add-Type -LiteralPath (Join-Path $PSScriptRoot -ChildPath "ServerCertificateValidation.cs") 20 | } 21 | 22 | # module scope variables 23 | $Script:completerComponents = $null 24 | $Script:version = $null 25 | $Script:methods = @("GET", "PUT", "POST", "DELETE", "HEAD") 26 | 27 | $forwardSlashChar = @('/') 28 | 29 | # Rely on the OS default for valid SSL/TLS protocols 30 | [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::SystemDefault 31 | 32 | # Disable nagling and expect 100 continue. Only valid for PowerShell versions 33 | # where ServicePointManager is used i.e. PowerShell not based on .NET Core and NetStandard 34 | [System.Net.ServicePointManager]::Expect100Continue = $false 35 | [System.Net.ServicePointManager]::UseNagleAlgorithm = $false 36 | 37 | <# 38 | .Synopsis 39 | Tests if there is anything in the pipeline 40 | #> 41 | function Test-Any() { 42 | Begin { 43 | $any = $false 44 | } 45 | Process { 46 | $any = $true 47 | } 48 | End { 49 | $any 50 | } 51 | } 52 | 53 | <# 54 | .Synopsis 55 | Gets the version of Elasticsearch with which to work 56 | .Description 57 | Gets the version of Elasticsearch with which to work. Use the -Installed parameter to list 58 | Elasticsearch versions for which specs are installed. 59 | .Parameter ListAvailable 60 | Lists the Elasticsearch versions for which specs have been downloaded, in descending version order. 61 | A version with downloaded specs does not require downloading files when using Set-ElasticsearchVersion 62 | to power tab completion. 63 | .Example 64 | PS> Set-ElasticsearchVersion 6.2.0 65 | PS> Get-ElasticsearchVersion 66 | 67 | Sets the Elasticsearch version to 6.2.0, then retrieves the set version 68 | .Example 69 | PS> Get-ElasticsearchVersion -ListAvailable 70 | 71 | Lists the Elasticsearch versions for which specs have been downloaded 72 | .Example 73 | PS> Get-ElasticsearchVersion -ListAvailable | Select-Object -First 1 | Set-ElasticsearchVersion 74 | 75 | Lists the Elasticsearch versions for which specs have been downloaded, selecting the latest 76 | version downloaded, and setting this as the version of Elasticsearch to work with 77 | #> 78 | function Get-ElasticsearchVersion { 79 | [CmdletBinding()] 80 | param( 81 | [switch] 82 | [Parameter()] 83 | $ListAvailable 84 | ) 85 | 86 | if ($ListAvailable) { 87 | $specsDir = (Join-Path $PSScriptRoot -ChildPath "specs") 88 | Get-ChildItem $specsDir -Directory -Name | ForEach-Object { 89 | $version = $null 90 | [Elastic.ElasticVersion]::TryParse($_, [ref]$version) | Out-Null 91 | $version 92 | } | Where-Object { $null -ne $_ } | Sort-Object -Descending 93 | } else { 94 | $Script:Version 95 | } 96 | } 97 | 98 | <# 99 | .Synopsis 100 | Sets the version of Elasticsearch with which to work. 101 | .Description 102 | Downloads the REST API specs for a specific version of Elasticsearch, using the 103 | API paths in the specification to power tab completion on paths 104 | .Parameter Version 105 | The Elasticsearch version 106 | .Parameter Force 107 | Forces the REST API specs to be downloaded, overwriting any existing 108 | specs for the version, and regenerating the file to power tab completion on paths 109 | .Example 110 | PS> Set-ElasticsearchVersion 6.2.0 111 | 112 | Sets the version of Elasticsearch to 6.2.0 113 | .Example 114 | PS> Set-ElasticsearchVersion -Version 7.0.0-beta1 -Force 115 | 116 | Sets the version of Elasticsearch to 7.0.0-beta1, overriding any existing downloaded 117 | files for the version 118 | .Example 119 | PS> Set-ElasticsearchVersion 6.2 120 | 121 | Sets the version of Elasticsearch to 6.2.0. An omitted patch version part will have 122 | the value 0 by default 123 | .Example 124 | PS> Set-ElasticsearchVersion 6 125 | 126 | Sets the version of Elasticsearch to 6.0.0. Omitted minor and patch version parts will have 127 | the value 0 by default 128 | #> 129 | function Set-ElasticsearchVersion { 130 | [CmdletBinding()] 131 | param( 132 | [Elastic.ElasticVersion] 133 | [Parameter(Mandatory = $true, ValueFromPipeline=$true)] 134 | $Version, 135 | 136 | [Parameter()] 137 | [switch] 138 | $Force 139 | ) 140 | 141 | Begin { 142 | } 143 | 144 | Process { 145 | if ($Version -eq $Script:version -and -not $Force) { 146 | return 147 | } 148 | 149 | $Script:Version = $Version 150 | $Script:completerComponents = $null 151 | 152 | $specsDir = (Join-Path $PSScriptRoot -ChildPath "specs") 153 | if (-not (Test-Path $specsDir)) { 154 | New-Item -Path $specsDir -ItemType Directory | Out-Null 155 | } 156 | 157 | $versionDir = (Join-Path $specsDir -ChildPath ($Version.ToString())) 158 | if (-not (Test-Path $versionDir)) { 159 | New-Item -Path $versionDir -ItemType Directory | Out-Null 160 | } 161 | 162 | $autocompleteFileName = "autocomplete.json" 163 | $autocompleteFile = (Join-Path $versionDir -ChildPath $autocompleteFileName) 164 | 165 | # Get the REST API specs from GitHub for the Elasticsearch version 166 | if (-not (Test-Path $autocompleteFile) -or $Force) { 167 | 168 | $contentsApi = "https://api.github.com/repos/elastic/elasticsearch/contents" 169 | $specUrls = @("$contentsApi/rest-api-spec/src/main/resources/rest-api-spec/api?ref=v$Version") 170 | 171 | # Get the REST API specs for Elastic Stack Feature/X-Pack endpoints too, when available 172 | if ($Version -ge "6.3.0") { 173 | $specUrls += "$contentsApi/x-pack/plugin/src/test/resources/rest-api-spec/api?ref=v$Version" 174 | } 175 | 176 | $downloadurls = $specUrls | Foreach-Object { 177 | Invoke-RestMethod $_ | Where-Object { $_.name.EndsWith(".json") } | Foreach-Object { $_.download_url } 178 | } 179 | 180 | # TODO: optimize by downloading in parallel 181 | for ($i = 0; $i -lt $downloadurls.count; $i++) { 182 | $downloadurl = $downloadurls[$i] 183 | $file = Split-Path $downloadurl -Leaf 184 | $outfile = Join-Path $versionDir -ChildPath $file 185 | 186 | Write-Progress -Activity "Downloading REST API specs for Elasticsearch $Version" -Status "Downloading $file" ` 187 | -PercentComplete ($i / $downloadurls.Count * 100) 188 | 189 | (New-Object System.Net.WebClient).DownloadFile($downloadurl, $outfile) 190 | } 191 | 192 | $excludeSpecs = @($autocompleteFileName, "_common.json") 193 | $specs = Get-ChildItem $versionDir -File -Filter *.json | Where-Object { $excludeSpecs -notcontains $_.Name } | ForEach-Object { $_.FullName } 194 | $apiCompleters = @() 195 | $pathCompleters = New-Object System.Collections.ArrayList 196 | 197 | foreach($spec in $specs) { 198 | $json = Get-Content $spec -Raw | ConvertFrom-Json 199 | $api = $json.PsObject.Properties | Select-Object -First 1 200 | 201 | # skip specs where the first key/value isn't an object 202 | if (-not ($api.Value -is [string])) { 203 | $name = $api.Name 204 | 205 | if ($api.Value.PsObject.Properties.Name -contains "methods") { 206 | # old REST api spec format that lists methods at the top level 207 | $methods = $api.Value.methods 208 | $url = $api.Value.url 209 | 210 | foreach($path in $url.paths) { 211 | $apiCompleter = @{ 212 | name = $name 213 | path = $path 214 | parts = $path.Split($forwardSlashChar, [System.StringSplitOptions]::RemoveEmptyEntries) 215 | methods = $methods 216 | } 217 | 218 | $apiCompleters += $apiCompleter 219 | 220 | for ($i = 0; $i -lt $apiCompleter.parts.Length; $i++) { 221 | if (($pathCompleters.Count - 1) -lt $i) { 222 | [void]$pathCompleters.Add($(New-Object System.Collections.Generic.HashSet[string])) 223 | } 224 | 225 | [void]$pathCompleters[$i].Add($apiCompleter.parts[$i]) 226 | } 227 | } 228 | } else { 229 | # newer REST api spec format that lists methods against paths 230 | $url = $api.Value.url 231 | 232 | foreach($path in $url.paths) { 233 | $apiCompleter = @{ 234 | name = $name 235 | path = $path.path 236 | parts = $path.path.Split($forwardSlashChar, [System.StringSplitOptions]::RemoveEmptyEntries) 237 | methods = $path.methods 238 | } 239 | 240 | $apiCompleters += $apiCompleter 241 | 242 | for ($i = 0; $i -lt $apiCompleter.parts.Length; $i++) { 243 | if (($pathCompleters.Count - 1) -lt $i) { 244 | [void]$pathCompleters.Add($(New-Object System.Collections.Generic.HashSet[string])) 245 | } 246 | 247 | [void]$pathCompleters[$i].Add($apiCompleter.parts[$i]) 248 | } 249 | } 250 | } 251 | } 252 | } 253 | 254 | # $pathCompleters must be an array rather than an ArrayList. With the latter, 255 | # No autocompletion with urlCompleter after downloading REST specs otherwise 256 | $Script:completerComponents = @{ 257 | apiCompleters = $apiCompleters 258 | pathCompleters = $pathCompleters.ToArray() 259 | } 260 | 261 | ConvertTo-Json -InputObject $Script:completerComponents -Compress -Depth 3 | Set-Content $autocompleteFile 262 | } 263 | 264 | if ($null -eq $Script:completerComponents) { 265 | $Script:completerComponents = Get-Content $autocompleteFile -Raw | ConvertFrom-Json 266 | } 267 | 268 | $uriCompleter = { 269 | param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) 270 | 271 | $uriAndQuery = $wordToComplete.Trim('"').Split('?') 272 | $wordToComplete = $uriAndQuery[0] 273 | 274 | if ($uriAndQuery.Length -gt 1) { 275 | $query = $uriAndQuery[1] 276 | } else { 277 | $query = $null 278 | } 279 | 280 | # if the Uri contains an authority, this should be prepended to any autocompletion results. 281 | # if it doesn't, the Uri should be normalized to start with forward slash to match paths in REST API specs 282 | $parsedUri = $null 283 | $authority = $null 284 | $validUri = [Uri]::TryCreate($wordToComplete, [UriKind]::RelativeOrAbsolute, [ref]$parsedUri) 285 | if ($validUri -and $parsedUri.IsAbsoluteUri) { 286 | $authority = $parsedUri.GetLeftPart([UriPartial]::Authority) 287 | $toComplete = [System.Web.HttpUtility]::UrlDecode($parsedUri.AbsolutePath) 288 | } else { 289 | if ($wordToComplete.StartsWith("/")) { 290 | $toComplete = $wordToComplete 291 | } else { 292 | $toComplete = "/" + $wordToComplete 293 | } 294 | } 295 | 296 | # Filter suggested APIs by passed Method 297 | $method = $fakeBoundParameters.ContainsKey("Method") 298 | if ($method) { 299 | $apis = ($Script:completerComponents).apiCompleters | Where-Object { $_.methods -contains "$($fakeBoundParameters.Method)" } 300 | } else { 301 | $apis = ($Script:completerComponents).apiCompleters 302 | } 303 | 304 | $parts = $toComplete.Split($forwardSlashChar, [System.StringSplitOptions]::RemoveEmptyEntries) 305 | 306 | # only filter when a value has been provided i.e. anything but empty string 307 | if ($parts.Length -gt 0) { 308 | 309 | # calculate if there are any paths that would be a like match for the last part of the passed path. 310 | # if there are, any token matches should be excluded later on 311 | $partsLikeness = New-Object bool[] -ArgumentList $parts.Length 312 | $likes = [string[]]($parts | ForEach-Object { "$_*" }) 313 | 314 | for ($i = 0; $i -lt $parts.Length; $i++) { 315 | $part = $parts[$i] 316 | if (($Script:completerComponents).pathCompleters.Length -ge $parts.Length) { 317 | $partsLikeness[$i] = ($Script:completerComponents).pathCompleters[$i] | Where-Object { $_ -like $likes[$i] } | Test-Any 318 | } 319 | } 320 | 321 | $len = $parts.Length - 1 322 | 323 | $apis = $apis | Where-Object { 324 | # Exclude APIs with parts shorter than the one we're gonna match 325 | if ($_.parts.Length -lt $parts.Length) { 326 | return $false 327 | } 328 | 329 | # for all parts of the path except the last, the part either needs 330 | # to be an exact match or a token, like {index} 331 | for ($i = 0; $i -lt $len; $i++) { 332 | $part = $parts[$i] 333 | if ($partsLikeness[$i] -and $part -ne $_.parts[$i]) { 334 | return $false 335 | } elseif ($part -ne $_.parts[$i] -and -not $_.parts[$i].StartsWith("{")) { 336 | return $false 337 | } 338 | } 339 | 340 | # for the last part of the path, the path part needs to be like the passed last part 341 | # and if there aren't any matches, can also be a token 342 | if ($partsLikeness[$len]) { 343 | return $_.parts[$len] -like $likes[$len] 344 | } else { 345 | return $_.parts[$len] -like $likes[$len] -or $_.parts[$len].StartsWith("{") 346 | } 347 | } 348 | } 349 | 350 | # completion suggestions 351 | $apis | ForEach-Object { 352 | 353 | $path = New-Object string[] -ArgumentList $_.parts.Length 354 | 355 | for ($i = 0; $i -lt $_.parts.Length; $i++) { 356 | if ($i -eq ($_.parts.Length - 1)) { 357 | if ($_.parts[$i] -eq "{index}") { 358 | # TODO: Get the indices names and return as multiple values for this API 359 | $path[$i] = $_.parts[$i] 360 | } else { 361 | $path[$i] = $_.parts[$i] 362 | } 363 | } elseif ($i -lt $parts.Length -and $_.parts[$i].StartsWith("{")) { 364 | $path[$i] = $parts[$i] 365 | } else { 366 | $path[$i] = $_.parts[$i] 367 | } 368 | } 369 | 370 | $path = "/" + ($path -join "/") 371 | 372 | if ($authority) { 373 | $builder = New-Object System.UriBuilder -ArgumentList $authority 374 | $builder.Path = $path 375 | 376 | if ($query) { 377 | $builder.Query = $query 378 | } 379 | 380 | # don't include default ports, if present 381 | if (($builder.Scheme -eq "http" -and $builder.Port -eq 80) -or ` 382 | ($builder.Scheme -eq "https" -and $builder.Port -eq 443)) { 383 | $builder.Port = -1 384 | } 385 | 386 | $completionText = [System.Web.HttpUtility]::UrlDecode($builder.ToString()) 387 | } else { 388 | if ($query) { 389 | $completionText = $path + "?" + $query 390 | } else { 391 | $completionText = $path 392 | } 393 | } 394 | 395 | # Always quote completion results as token URL parts like {index} represent script blocks when left unquoted 396 | New-Object System.Management.Automation.CompletionResult -ArgumentList "`"$completionText`"", 397 | $_.path, 398 | "ParameterValue", 399 | $_.name 400 | } 401 | 402 | # pass back the original value as a completion suggestion too, if it has a value 403 | if ($wordToComplete) { 404 | New-Object System.Management.Automation.CompletionResult -ArgumentList "`"$wordToComplete`"", 405 | $wordToComplete, 406 | "ParameterValue", 407 | $wordToComplete 408 | } 409 | } 410 | 411 | Register-ArgumentCompleter -CommandName Invoke-Elasticsearch -ParameterName Uri -ScriptBlock $uriCompleter 412 | } 413 | } 414 | 415 | # auto completion for HTTP methods 416 | Register-ArgumentCompleter -CommandName Invoke-Elasticsearch -ParameterName Method -ScriptBlock { 417 | param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) 418 | 419 | if ($null -eq $Script:completerComponents.apiCompleters -or $fakeBoundParameters.ContainsKey("Uri") -eq $false) { 420 | # If there's no Uri then all methods are valid 421 | $Script:methods | ForEach-Object { 422 | New-Object System.Management.Automation.CompletionResult -ArgumentList $_, $_, "ParameterValue", $_ 423 | } 424 | } else { 425 | 426 | # get just the path out of the Uri 427 | $uri = $fakeBoundParameters.Uri.Trim('"').Split('?')[0] 428 | $parsedUri = $null 429 | if ([Uri]::TryCreate($uri, [UriKind]::RelativeOrAbsolute, [ref]$parsedUri) -and $parsedUri.IsAbsoluteUri) { 430 | $toComplete = [System.Web.HttpUtility]::UrlDecode($parsedUri.AbsolutePath) 431 | } else { 432 | if ($uri.StartsWith("/")) { 433 | $toComplete = $uri 434 | } else { 435 | $toComplete = "/" + $uri 436 | } 437 | } 438 | 439 | $parts = $toComplete.Split($forwardSlashChar, [System.StringSplitOptions]::RemoveEmptyEntries) 440 | $apis = $Script:completerComponents.apiCompleters 441 | 442 | # only filter when a value has been provided i.e. anything but empty string 443 | if ($parts.Length -gt 0) { 444 | 445 | # calculate if there are any paths that would be a like match for the last part of the passed path. 446 | # if there are, any token matches should be excluded later on 447 | $partsLikeness = New-Object bool[] -ArgumentList $parts.Length 448 | $likes = [string[]]($parts | ForEach-Object { "$_*" }) 449 | 450 | for ($i = 0; $i -lt $parts.Length; $i++) { 451 | $part = $parts[$i] 452 | if ($Script:completerComponents.pathCompleters.Length -ge $parts.Length) { 453 | $partsLikeness[$i] = $Script:completerComponents.pathCompleters[$i] | Where-Object { $_ -like $likes[$i] } | Test-Any 454 | } 455 | } 456 | 457 | $len = $parts.Length - 1 458 | 459 | $apis = $apis | Where-Object { 460 | # Exclude APIs with parts shorter than the one we're gonna match 461 | if ($_.parts.Length -lt $parts.Length) { 462 | return $false 463 | } 464 | 465 | # for all parts of the path except the last, the part either needs 466 | # to be an exact match or a token 467 | for ($i = 0; $i -lt $len; $i++) { 468 | $part = $parts[$i] 469 | if ($partsLikeness[$i] -and $part -ne $_.parts[$i]) { 470 | return $false 471 | } elseif ($part -ne $_.parts[$i] -and -not $_.parts[$i].StartsWith("{")) { 472 | return $false 473 | } 474 | } 475 | 476 | # for the last part of the path, the path part needs to be like the passed last part 477 | # and if there aren't any matches, can also be a token 478 | if ($partsLikeness[$len]) { 479 | return $_.parts[$len] -like $likes[$len] 480 | } else { 481 | return $_.parts[$len] -like $likes[$len] -or $_.parts[$len].StartsWith("{") 482 | } 483 | } 484 | } 485 | 486 | if (-not $apis) { 487 | # If there's no match then all methods are valid 488 | $Script:methods | ForEach-Object { 489 | New-Object System.Management.Automation.CompletionResult -ArgumentList $_, $_, "ParameterValue", $_ 490 | } 491 | } else { 492 | $apis | ForEach-Object { -split $_.methods } | Sort-Object -Unique | ForEach-Object { 493 | New-Object System.Management.Automation.CompletionResult -ArgumentList $_, $_, "ParameterValue", $_ 494 | } 495 | } 496 | } 497 | } 498 | 499 | <# 500 | .Synopsis 501 | Converts a Kibana console request to a request that can be piped to Invoke-Elasticsearch 502 | .Description 503 | Converts a Kibana console request to a request that can be piped to Invoke-Elasticsearch. 504 | 505 | The converted request is a custom PowerShell object with request properties that can 506 | be piped to Invoke-Elasticsearch 507 | 508 | .Example 509 | PS> @' 510 | PUT /my_locations 511 | { 512 | "mappings": { 513 | "properties": { 514 | "pin": { 515 | "properties": { 516 | "location": { 517 | "type": "geo_point" 518 | } 519 | } 520 | } 521 | } 522 | } 523 | } 524 | 525 | PUT /my_locations/_doc/1 526 | { 527 | "pin" : { 528 | "location" : { 529 | "lat" : 40.12, 530 | "lon" : -71.34 531 | } 532 | } 533 | } 534 | '@ | ConvertFrom-KibanaConsole | Invoke-Elasticsearch 535 | 536 | Converts two Kibana console requests to requests that can be piped to 537 | Elasticsearch to execute 538 | 539 | .Example 540 | PS> 'GET /_cat/indices' | ckc | es 541 | 542 | Converts a Kibana console GET request to the _cat/indices endpoint and pipes the 543 | resulting request to Elasticsearch to execute 544 | .Link 545 | https://www.elastic.co/guide/en/kibana/current/console-kibana.html 546 | #> 547 | function ConvertFrom-KibanaConsole { 548 | [CmdletBinding()] 549 | param ( 550 | [string] 551 | [Parameter(Mandatory=$true, ValueFromPipeline=$true)] 552 | $Request 553 | ) 554 | 555 | Begin { 556 | $requests = @() 557 | } 558 | 559 | Process { 560 | # A command may be multiple API requests, so split by empty lines or lines starting with verbs 561 | $requests += [Regex]::Split($Request, "\r?\n\r?\n|\r?\n(?=HEAD|GET|PUT|POST|DELETE)") 562 | } 563 | 564 | End { 565 | $emptyChars = @(" ") 566 | $newLines = [string[]]@("`n", "`r`n", [Environment]::NewLine) 567 | 568 | $requests | ForEach-Object { 569 | $consoleParts = $_.Split($newLines, 2, [StringSplitOptions]::RemoveEmptyEntries) 570 | $methodUriParts = $consoleParts[0].Split($emptyChars, 2, [StringSplitOptions]::None) 571 | 572 | if ($methodUriParts.Length -ne 2) { 573 | throw "'$($consoleParts[0])' is not a valid Kibana Console command of the form ' '" 574 | } 575 | 576 | $method = $methodUriParts[0] 577 | $uri = $methodUriParts[1] 578 | 579 | if ($consoleParts.Length -gt 1) { 580 | $body = $consoleParts[1] 581 | } else { 582 | $body = $null 583 | } 584 | 585 | return [PSCustomObject]@{ 586 | PSTypeName = "ElasticsearchRequest" 587 | Method = $method 588 | Uri = $uri 589 | Body = $body 590 | } 591 | } 592 | } 593 | } 594 | 595 | <# 596 | .Synopsis 597 | Executes a REST API request against Elasticsearch 598 | .Description 599 | Provides a simpler experience for executing REST API requests against Elasticsearch. 600 | 601 | Use Set-ElasticsearchVersion to download the REST API specs for a specific version 602 | of Elasticsearch, to power tab completion of available endpoints and methods. 603 | 604 | A request that does not specify a method 605 | 606 | - Without a body will be a GET request 607 | - With a body will be a POST request 608 | 609 | In addition, a GET request with a body will be sent as a POST request, since PowerShell does 610 | not allow sending a GET request with a body. 611 | .Example 612 | es _cat/indices 613 | 614 | Sends a request to Elasticsearch to list the indices in the cluster. 615 | .Example 616 | PS> es twitter/_doc/1 -Pretty -Method PUT -Body @' 617 | { 618 | "user" : "kimchy", 619 | "post_date" : "2009-11-15T14:12:12", 620 | "message" : "trying out Elasticsearch" 621 | } 622 | '@ 623 | 624 | Sends a request to Elasticsearch to create a document with id 1 in the twitter index. 625 | The document is sent as a JSON string literal 626 | 627 | .Example 628 | PS> es posts/_search?pretty -u elastic:changeme -H @{ 'X-Opaque-Id' = 'track_this_call' } -ResponseVariable response -d @{ 629 | query = @{ 630 | match = @{ 631 | user = "kimchy" 632 | } 633 | } 634 | } 635 | 636 | PS> $statusCode = $response.StatusCode 637 | PS> $responseHeaders = $response.Headers 638 | 639 | Sends a request to Elasticsearch to search all indices, passing an X-Opaque-Id header to track the call 640 | using the tasks API. The search query is passed as a Hashtable. The underlying PowerShell response is captured 641 | with the response variable, allowing the status code and response headers to be inspected. 642 | 643 | .Example 644 | PS> es posts/_bulk -ContentType application/x-ndjson -d C:\data.json 645 | 646 | Sends a bulk request to Elasticsearch with the newline delimited application/x-ndjson content type. The 647 | body of the request is read from the file C:\data.json 648 | .Example 649 | PS> gc C:\data.json | es posts/_bulk -ContentType application/x-ndjson 650 | 651 | Sends a bulk request to Elasticsearch with the newline delimited application/x-ndjson content type. The 652 | body of the request is piped to the command. 653 | .Parameter Method 654 | The HTTP method to use. For requests with a body, will default to 'POST' and without, 'GET'. 655 | .Parameter Uri 656 | The URI to make the request against. A relative URI path will make a request using the base URI 'http://localhost:9200' 657 | .Parameter User 658 | The username for Authentication. the password may also be specified here using the format 'username:password' 659 | .Parameter Password 660 | The password for Authentication. if username is specified but password is not, an interactive prompt will be displayed to provide the password. 661 | .Parameter Body 662 | The request body. May be a JSON string literal, a Hashtable, or a path to a file containing JSON 663 | .Parameter ContentType 664 | The Content-Type HTTP header. By default, uses 'application/json' 665 | .Parameter Headers 666 | A Hashtable of the HTTP headers to send 667 | .Parameter Pretty 668 | Pretty print (indent) the JSON response. Alternatively, may be supplied as a query string parameter on the Uri with '?pretty' or '?pretty=true' 669 | .Parameter SkipCertificateCheck 670 | By default, Server certificates are verified when making requests against Elasticsearch secured by SSL/TLS. Verification 671 | can be skipped by specifying SkipCertificateCheck. Can be useful when working with self-signed certificates, for example. 672 | .Parameter ResponseVariable 673 | The name of a variable to which the response will be assigned, with global scope. 674 | The response can be inspected for response headers, status code, etc. 675 | .Parameter Bytes 676 | Return the response as a byte array. 677 | The response will be returned as a string by default, which may not be desired for content types like CBOR. 678 | .Inputs 679 | The request body. May be a JSON string literal, a Hashtable, or a path to a file containing JSON 680 | .Outputs 681 | The response body as a string 682 | #> 683 | function Invoke-Elasticsearch { 684 | [CmdletBinding()] 685 | [OutputType([string])] 686 | param ( 687 | [string] 688 | # Use ValidateScript to work in conjunction with Register-ArgumentCompleter for Method 689 | [ValidateScript({ 690 | if ($_ -in $Script:methods) { 691 | $true 692 | } else { 693 | throw "'$_' must be one of $($Script:methods -join ", ")" 694 | } 695 | })] 696 | [Parameter(ValueFromPipelineByPropertyName=$true)] 697 | [Alias("X")] 698 | $Method, 699 | 700 | [string] 701 | [Parameter(Mandatory=$true,Position=0,ValueFromPipelineByPropertyName=$true)] 702 | $Uri, 703 | 704 | [string] 705 | [Alias("u")] 706 | $User, 707 | 708 | [SecureString] 709 | $Password, 710 | 711 | [Alias("d")] 712 | [Alias("data")] 713 | [Elastic.ElasticsearchRequestBody] 714 | [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 715 | $Body, 716 | 717 | [string] 718 | $ContentType = "application/json", 719 | 720 | [Alias("H")] 721 | $Headers = @{}, 722 | 723 | [switch] 724 | $Pretty, 725 | 726 | [Alias("k")] 727 | [Alias("insecure")] 728 | [switch] 729 | $SkipCertificateCheck, 730 | 731 | [Alias("response")] 732 | [string] 733 | $ResponseVariable, 734 | 735 | [switch] 736 | $Bytes 737 | ) 738 | Begin { 739 | } 740 | 741 | Process { 742 | if (-not $Method) { 743 | if ($Body) { 744 | $Method = "POST" 745 | } else { 746 | $Method = "GET" 747 | } 748 | } elseif ($Body -and $Method -eq "GET") { 749 | # Invoke-WebRequest does not allow sending a body with GET, so force POST 750 | $Method = "POST" 751 | } 752 | 753 | if ($Uri) { 754 | $parsedUri = $null 755 | if ([Uri]::TryCreate($Uri, [UriKind]::RelativeOrAbsolute, [ref]$parsedUri) -and -not $parsedUri.IsAbsoluteUri) { 756 | $parsedUri = New-Object System.Uri "http://localhost:9200/$($parsedUri.OriginalString.TrimStart('/'))" 757 | } 758 | } 759 | else { 760 | $parsedUri = New-Object System.Uri "http://localhost:9200/" 761 | } 762 | 763 | # ParseQueryString does not respect keys without values, so test .Query directly 764 | if ($Pretty -and (-not $parsedUri.Query -or $parsedUri.Query -match "[?|&]pretty(?=\=true)" -eq $false)) { 765 | $queryString = [System.Web.HttpUtility]::ParseQueryString($parsedUri.Query) 766 | if (-not $queryString) { 767 | $queryString = New-Object System.Collections.Specialized.NameValueCollection 768 | } 769 | $queryString.Set("pretty","true") 770 | $uriBuilder = New-Object System.UriBuilder $parsedUri 771 | $uriBuilder.Query = $queryString.ToString(); 772 | $parsedUri = $uriBuilder.Uri 773 | } 774 | 775 | if ($User) { 776 | $userParts = $User.Split(':', 2) 777 | if ($userParts.Length -eq 2) { 778 | $User = $userParts[0] 779 | $Password = $userParts[1] | ConvertTo-SecureString -AsPlainText -Force 780 | } 781 | 782 | while (-not $Password -or $Password.Length -eq 0) { 783 | $Password = Read-Host -AsSecureString "Enter password for $($User):" 784 | } 785 | 786 | $credential = New-Object System.Management.Automation.PSCredential ($User, $Password) 787 | } else { 788 | $credential = $null 789 | } 790 | 791 | $requestParameters = @{ 792 | Uri = $parsedUri 793 | ContentType = $ContentType 794 | Headers = $Headers 795 | Credential = $credential 796 | Method = $Method 797 | UseBasicParsing = $true 798 | } 799 | 800 | # Allow PowerShell Core to send credentials over unencrypted connection. Possibly expose as param? 801 | if ((Get-Command Invoke-WebRequest).Parameters.ContainsKey("AllowUnencryptedAuthentication")) { 802 | $requestParameters.AllowUnencryptedAuthentication = $true 803 | } 804 | 805 | if ($Body) { 806 | if ($Body.Input -is [string]) { 807 | if (Test-Path $Body.Input -PathType Leaf) { 808 | $requestParameters.InFile = $Body.Input 809 | } else { 810 | $requestParameters.Body = $Body.Input 811 | } 812 | } else { 813 | $requestParameters.Body = $Body.Input | ConvertTo-Json 814 | } 815 | } 816 | 817 | if ($SkipCertificateCheck) { 818 | if ((Get-Command Invoke-WebRequest).Parameters.ContainsKey("SkipCertificateCheck")) { 819 | # PowerShell Core 820 | $requestParameters.SkipCertificateCheck = $true 821 | } else { 822 | # PowerShell 823 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = [Elastic.ServerCertificateValidation]::AllowAll() 824 | } 825 | } 826 | 827 | try { 828 | $response = Invoke-WebRequest @requestParameters 829 | if ($ResponseVariable) { 830 | Set-Variable -Name $ResponseVariable -Value $response -Scope Global 831 | } 832 | 833 | if ($response.Content.GetType().FullName -eq "System.Net.Http.HttpContent") { 834 | #PowerShell Core 835 | if ($Bytes) { 836 | return $response.Content.ReadAsByteArrayAsync().Result; 837 | } else { 838 | return $response.Content.ReadAsStringAsync().Result; 839 | } 840 | } else { 841 | #PowerShell 842 | if ($response.Content -is [string]) { 843 | if ($Bytes) { 844 | return [Text.Encoding]::UTF8.GetBytes($response.Content); 845 | } 846 | 847 | return $response.Content; 848 | } else { 849 | if ($Bytes) { 850 | return $response.Content 851 | } 852 | 853 | return [Text.Encoding]::UTF8.GetString($response.Content); 854 | } 855 | } 856 | } 857 | catch { 858 | if ($_.Exception | Get-Member Response) { 859 | # Powershell 860 | $response = $_.Exception.Response 861 | } else { 862 | # PowerShell Core 863 | $response = $null 864 | } 865 | 866 | if ($ResponseVariable) { 867 | Set-Variable -Name $ResponseVariable -Value $response -Scope Global 868 | } 869 | 870 | if ($response) { 871 | if ($response | Get-Member GetResponseStream) { 872 | # PowerShell 873 | $responseStream = $response.GetResponseStream() 874 | $reader = New-Object System.IO.StreamReader($responseStream) 875 | $reader.BaseStream.Position = 0 876 | $reader.DiscardBufferedData() 877 | return $reader.ReadToEnd() 878 | } else { 879 | # PowerShell Core 880 | if ($_.ErrorDetails -and $_.ErrorDetails.Message) { 881 | return $_.ErrorDetails.Message; 882 | } else { 883 | throw $_.Exception 884 | } 885 | } 886 | } 887 | else { 888 | throw $_.Exception 889 | } 890 | } 891 | finally { 892 | if ($SkipCertificateCheck -and (Get-Command Invoke-WebRequest).Parameters.ContainsKey("SkipCertificateCheck") -eq $false) { 893 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $null 894 | } 895 | } 896 | } 897 | 898 | End { 899 | 900 | } 901 | } 902 | 903 | function Get-ElasticsearchIndex 904 | { 905 | [OutputType([string[]])] 906 | param( 907 | [string] 908 | [Parameter(Position=0,ValueFromPipelineByPropertyName=$true)] 909 | $Uri, 910 | 911 | [string] 912 | [Alias("u")] 913 | [Parameter(ValueFromPipelineByPropertyName=$true)] 914 | $User, 915 | 916 | [SecureString] 917 | [Parameter(ValueFromPipelineByPropertyName=$true)] 918 | $Password 919 | ) 920 | 921 | $parsedUri = $null 922 | $authority = $null 923 | if ([Uri]::TryCreate($Uri, [UriKind]::RelativeOrAbsolute, [ref]$parsedUri) -and $parsedUri.IsAbsoluteUri) { 924 | $authority = $parsedUri.GetLeftPart([UriPartial]::Authority) 925 | } 926 | 927 | if ($authority) { 928 | $builder = New-Object System.UriBuilder -ArgumentList $authority 929 | $builder.Path = "_cat/indices" 930 | $builder.Query = "?h=index" 931 | 932 | if (($builder.Scheme -eq "http" -and $builder.Port -eq 80) -or ` 933 | ($builder.Scheme -eq "https" -and $builder.Port -eq 443)) { 934 | $builder.Port = -1 935 | } 936 | 937 | $catIndicesUri = $builder.ToString() 938 | } else { 939 | $catIndicesUri = "_cat/indices?h=index" 940 | } 941 | 942 | $parameters = @{ 943 | User = $User 944 | Password = $Password 945 | Method = "GET" 946 | Uri = $catIndicesUri 947 | } 948 | 949 | try { 950 | $indices = Invoke-Elasticsearch @parameters 951 | return $indices.Split([Environment]::NewLine, [StringSplitOptions]::RemoveEmptyEntries) 952 | } 953 | catch { 954 | Write-Warning "Unable to retrieve indices for $($parameters.Keys.ForEach({"$_ $($parameters.$_)"}) -join ','). $_.Exception" 955 | return @() 956 | } 957 | } 958 | 959 | # Set Elasticsearch version to the one being installed 960 | Set-ElasticsearchVersion -Version "7.8.1" 961 | 962 | Set-Alias -Name es -Value Invoke-Elasticsearch -Description "Sends a request to Elasticsearch" 963 | Set-Alias -Name ckc -Value ConvertFrom-KibanaConsole -Description "Converts a Kibana Console request to a request that can be passed to Invoke-Elasticsearch" 964 | Set-Alias -Name hash -Value ConvertFrom-Json -Description "Converts JSON into a dictionary/hashmap" 965 | 966 | # SIG # Begin signature block 967 | # MIIOHAYJKoZIhvcNAQcCoIIODTCCDgkCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB 968 | # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR 969 | # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUrcBZd7nxZvrIMW8sTkqh1yw+ 970 | # z/+gggtSMIIFajCCBFKgAwIBAgIRALVV9nHrVFUKcgNbgqjR2vAwDQYJKoZIhvcN 971 | # AQELBQAwfTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3Rl 972 | # cjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQx 973 | # IzAhBgNVBAMTGkNPTU9ETyBSU0EgQ29kZSBTaWduaW5nIENBMB4XDTE2MTExNDAw 974 | # MDAwMFoXDTIwMTExNDIzNTk1OVowgbExCzAJBgNVBAYTAlVTMQ4wDAYDVQQRDAU5 975 | # NDA0MDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEjAQBgNV 976 | # BAkMCVN1aXRlIDM1MDEdMBsGA1UECQwUODAwIFcgRWwgQ2FtaW5vIFJlYWwxHDAa 977 | # BgNVBAoME0VsYXN0aWNzZWFyY2gsIEluYy4xHDAaBgNVBAMME0VsYXN0aWNzZWFy 978 | # Y2gsIEluYy4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4C1kxswVi 979 | # 0N5TcMdIxoS600imWGp4LSqAYa7jz7BtkoelTpPiiuUm8/rWTiOLoJoVCN1YBvLT 980 | # 5gkmvyS3f6VPPpTV2fSmkYBr5D1OK/lu4mmpQ7Qj/B+nehxMviGyQ1BG7ry0hQ5s 981 | # TBhvdfYJrp3m2LEs9wba7xxg/lLsfSi3IjmnCAmECQ/Pn3Sb5aR3mzYGvq68QvlB 982 | # ldc3dBNJ7SoEqacqQc4cwpCWhS6X+sUH1PwwYZ5y5E3SBrf2LNt/xt7ZJjAqqHpq 983 | # us08b3nRqv7296Al0unOLFSFSSlMdadwoGFamBj+aWuNU9poSCo1IlWy3FgJM7Ct 984 | # MY0dARZQsQE7AgMBAAGjggGuMIIBqjAfBgNVHSMEGDAWgBQpkWD/ik366/mmarjP 985 | # +eZLvUnOEjAdBgNVHQ4EFgQU6cIcm0rpgdoP+EEgyx0wpx19u5swDgYDVR0PAQH/ 986 | # BAQDAgeAMAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEQYJYIZI 987 | # AYb4QgEBBAQDAgQQMEYGA1UdIAQ/MD0wOwYMKwYBBAGyMQECAQMCMCswKQYIKwYB 988 | # BQUHAgEWHWh0dHBzOi8vc2VjdXJlLmNvbW9kby5uZXQvQ1BTMEMGA1UdHwQ8MDow 989 | # OKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0NPTU9ET1JTQUNvZGVTaWdu 990 | # aW5nQ0EuY3JsMHQGCCsGAQUFBwEBBGgwZjA+BggrBgEFBQcwAoYyaHR0cDovL2Ny 991 | # dC5jb21vZG9jYS5jb20vQ09NT0RPUlNBQ29kZVNpZ25pbmdDQS5jcnQwJAYIKwYB 992 | # BQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9kb2NhLmNvbTAfBgNVHREEGDAWgRRtaWNy 993 | # b3NvZnRAZWxhc3RpYy5jbzANBgkqhkiG9w0BAQsFAAOCAQEAdIyVNTJeUYBG2pyH 994 | # /JvRXheH/v4q4foEqqlfSk6FhSluo5tHtCKlkODSGw1XLUKWann741BXPr23gWVm 995 | # fHgW5SYZIrWlnrB8EaJ14Zp6Wl3S45aDMeJKxcViObncrlTHDOiE1oDYSiF8HPIq 996 | # p0Q6v8TUUJzJHxaTtyeWL+p778ytROyEn/W09eqEewcOHiKN9Ub32abHhaFXvK5i 997 | # BXs7WhIO09EwIBlBbxpZc50TgPbPXly+Gv4jsEzL+wVUCbQokmxyokjzh+zQoJ8x 998 | # JKZb/nui59JxgTJ5NiRnxFlOogVwz388s0FBfg7MkQ9NFKHxKz9Vm0hIzgDxAXHo 999 | # mh/mVDCCBeAwggPIoAMCAQICEC58h8wOk0pS/pT9HLfNNK8wDQYJKoZIhvcNAQEM 1000 | # BQAwgYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIx 1001 | # EDAOBgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSsw 1002 | # KQYDVQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEz 1003 | # MDUwOTAwMDAwMFoXDTI4MDUwODIzNTk1OVowfTELMAkGA1UEBhMCR0IxGzAZBgNV 1004 | # BAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE 1005 | # ChMRQ09NT0RPIENBIExpbWl0ZWQxIzAhBgNVBAMTGkNPTU9ETyBSU0EgQ29kZSBT 1006 | # aWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAppiQY3eR 1007 | # NH+K0d3pZzER68we/TEds7liVz+TvFvjnx4kMhEna7xRkafPnp4ls1+BqBgPHR4g 1008 | # MA77YXuGCbPj/aJonRwsnb9y4+R1oOU1I47Jiu4aDGTH2EKhe7VSA0s6sI4jS0tj 1009 | # 4CKUN3vVeZAKFBhRLOb+wRLwHD9hYQqMotz2wzCqzSgYdUjBeVoIzbuMVYz31HaQ 1010 | # OjNGUHOYXPSFSmsPgN1e1r39qS/AJfX5eNeNXxDCRFU8kDwxRstwrgepCuOvwQFv 1011 | # kBoj4l8428YIXUezg0HwLgA3FLkSqnmSUs2HD3vYYimkfjC9G7WMcrRI8uPoIfle 1012 | # TGJ5iwIGn3/VCwIDAQABo4IBUTCCAU0wHwYDVR0jBBgwFoAUu69+Aj36pvE8hI6t 1013 | # 7jiY7NkyMtQwHQYDVR0OBBYEFCmRYP+KTfrr+aZquM/55ku9Sc4SMA4GA1UdDwEB 1014 | # /wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMBMGA1UdJQQMMAoGCCsGAQUFBwMD 1015 | # MBEGA1UdIAQKMAgwBgYEVR0gADBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3Js 1016 | # LmNvbW9kb2NhLmNvbS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNy 1017 | # bDBxBggrBgEFBQcBAQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2Rv 1018 | # Y2EuY29tL0NPTU9ET1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRw 1019 | # Oi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAAI/AjnD7vjK 1020 | # O4neDG1NsfFOkk+vwjgsBMzFYxGrCWOvq6LXAj/MbxnDPdYaCJT/JdipiKcrEBrg 1021 | # m7EHIhpRHDrU4ekJv+YkdK8eexYxbiPvVFEtUgLidQgFTPG3UeFRAMaH9mzuEER2 1022 | # V2rx31hrIapJ1Hw3Tr3/tnVUQBg2V2cRzU8C5P7z2vx1F9vst/dlCSNJH0NXg+p+ 1023 | # IHdhyE3yu2VNqPeFRQevemknZZApQIvfezpROYyoH3B5rW1CIKLPDGwDjEzNcweU 1024 | # 51qOOgS6oqF8H8tjOhWn1BUbp1JHMqn0v2RH0aofU04yMHPCb7d4gp1c/0a7ayId 1025 | # iAv4G6o0pvyM9d1/ZYyMMVcx0DbsR6HPy4uo7xwYWMUGd8pLm1GvTAhKeo/io1Li 1026 | # jo7MJuSy2OU4wqjtxoGcNWupWGFKCpe0S0K2VZ2+medwbVn4bSoMfxlgXwyaiGww 1027 | # rFIJkBYb/yud29AgyonqKH4yjhnfe0gzHtdl+K7J+IMUk3Z9ZNCOzr41ff9yMU2f 1028 | # nr0ebC+ojwwGUPuMJ7N2yfTm18M04oyHIYZh/r9VdOEhdwMKaGy75Mmp5s9ZJet8 1029 | # 7EUOeWZo6CLNuO+YhU2WETwJitB/vCgoE/tqylSNklzNwmWYBp7OSFvUtTeTRkF8 1030 | # B93P+kPvumdh/31J4LswfVyA4+YWOUunMYICNDCCAjACAQEwgZIwfTELMAkGA1UE 1031 | # BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2Fs 1032 | # Zm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxIzAhBgNVBAMTGkNPTU9E 1033 | # TyBSU0EgQ29kZSBTaWduaW5nIENBAhEAtVX2cetUVQpyA1uCqNHa8DAJBgUrDgMC 1034 | # GgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYK 1035 | # KwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG 1036 | # 9w0BCQQxFgQUCfGYItx7+MBjkJ60/K+j1qhRP8EwDQYJKoZIhvcNAQEBBQAEggEA 1037 | # sSG3UxuQJy2O4GtA9zv1yovJW4GHZX3woesd3PTaCmwkDxsl75LP1qKZc1LV+3ko 1038 | # ZEi6UiG1B7LEJT71Qj4bUK8vHGE9eCD+hJNjAvoFIKwqSrvgGE0nBGNn/7U3XtM+ 1039 | # nlA5mcOAVHRmNRe4N5azXRZnQBGsQVOSGYjtWcOaZOQxTE1V8rREmDcnteUtS75q 1040 | # kCllipGJ/0kDbrJ/sdOq1idj/HC35BcPvQcc2K+W7+bDVXwMj29SuXlQFNCDMdOa 1041 | # VE3mDVRQVyUoghJAU001G1b6qSyRCya5yifcQ+q+ggGku2SAIHwjWsh5iEz5/zg5 1042 | # D4nNZyouAwDuuDqhkQm/OQ== 1043 | # SIG # End signature block 1044 | -------------------------------------------------------------------------------- /Elastic.Console/ElasticVersion.cs: -------------------------------------------------------------------------------- 1 | // A simple version implementation based on 2 | // https://github.com/maxhauser/semver/blob/master/src/Semver/SemVersion.cs 3 | // MIT License 4 | // Copyright (c) 2013 Max Hauser 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | using System; 25 | using System.Globalization; 26 | using System.Runtime.Serialization; 27 | using System.Security.Permissions; 28 | using System.Text.RegularExpressions; 29 | 30 | namespace Elastic 31 | { 32 | /// 33 | /// An Elastic product version 34 | /// 35 | public sealed class ElasticVersion : IEquatable, IComparable, IComparable 36 | { 37 | private static Regex VersionRegex = new Regex( 38 | @"^(?\d+)(\.(?\d+))?(\.(?\d+))?(\-(?
[0-9A-Za-z]+))?$", 
 39 |             RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture);
 40 | 
 41 |         public ElasticVersion(object version) : this(version.ToString())
 42 |         {
 43 |         }
 44 | 
 45 |         public ElasticVersion(string version)
 46 |         {
 47 |             var match = VersionRegex.Match(version);
 48 |             if (!match.Success)
 49 |                 throw new ArgumentException(string.Format("Invalid version '{0}'", version), "version");
 50 | 
 51 |             var major = int.Parse(match.Groups["major"].Value, CultureInfo.InvariantCulture);
 52 | 
 53 |             var minorMatch = match.Groups["minor"];
 54 |             int minor = 0;
 55 |             if (minorMatch.Success) 
 56 |                 minor = int.Parse(minorMatch.Value, CultureInfo.InvariantCulture);
 57 | 
 58 |             var patchMatch = match.Groups["patch"];
 59 |             int patch = 0;
 60 |             if (patchMatch.Success)
 61 |                 patch = int.Parse(patchMatch.Value, CultureInfo.InvariantCulture);
 62 | 
 63 |             var prerelease = match.Groups["pre"].Value;
 64 |             
 65 |             this.Major = major;
 66 |             this.Minor = minor;
 67 |             this.Patch = patch;
 68 |             this.Prerelease = prerelease;
 69 |         }
 70 | 
 71 |         public ElasticVersion(int major, int minor, int patch, string prerelease)
 72 |         {
 73 |             this.Major = major;
 74 |             this.Minor = minor;
 75 |             this.Patch = patch;
 76 |             this.Prerelease = prerelease;
 77 |         }
 78 | 
 79 |         public static bool TryParse(string version, out ElasticVersion ElasticVersion)
 80 |         {
 81 |             try
 82 |             {
 83 |                 ElasticVersion = new ElasticVersion(version);
 84 |                 return true;
 85 |             }
 86 |             catch (Exception)
 87 |             {
 88 |                 ElasticVersion = null;
 89 |                 return false;
 90 |             }
 91 |         }
 92 | 
 93 |         public static bool Equals(ElasticVersion versionA, ElasticVersion versionB)
 94 |         {
 95 |             if (ReferenceEquals(versionA, null))
 96 |                 return ReferenceEquals(versionB, null);
 97 |             return versionA.Equals(versionB);
 98 |         }
 99 | 
100 |         public static int Compare(ElasticVersion versionA, ElasticVersion versionB)
101 |         {
102 |             if (ReferenceEquals(versionA, null))
103 |                 return ReferenceEquals(versionB, null) ? 0 : -1;
104 |             return versionA.CompareTo(versionB);
105 |         }
106 | 
107 |         public ElasticVersion Change(int? major = null, int? minor = null, int? patch = null, string prerelease = null)
108 |         {
109 |             return new ElasticVersion(
110 |                 major ?? this.Major,
111 |                 minor ?? this.Minor,
112 |                 patch ?? this.Patch,
113 |                 prerelease ?? this.Prerelease);
114 |         }
115 | 
116 |         public int Major { get; private set; }
117 | 
118 |         public int Minor { get; private set; }
119 | 
120 |         public int Patch { get; private set; }
121 | 
122 |         public string Prerelease { get; private set; }
123 | 
124 |         public override string ToString()
125 |         {
126 |             var version = "" + Major + "." + Minor + "." + Patch;
127 |             if (!String.IsNullOrEmpty(Prerelease))
128 |                 version += "-" + Prerelease;
129 |             return version;
130 |         }
131 | 
132 |         public int CompareTo(object obj)
133 |         {
134 |             return CompareTo((ElasticVersion)obj);
135 |         }
136 | 
137 |         public int CompareTo(ElasticVersion other)
138 |         {
139 |             if (ReferenceEquals(other, null))
140 |                 return 1;
141 | 
142 |             var r = this.CompareByPrecedence(other);
143 |             return r;
144 |         }
145 | 
146 |         public bool PrecedenceMatches(ElasticVersion other)
147 |         {
148 |             return CompareByPrecedence(other) == 0;
149 |         }
150 | 
151 |         public int CompareByPrecedence(ElasticVersion other)
152 |         {
153 |             if (ReferenceEquals(other, null))
154 |                 return 1;
155 | 
156 |             var r = this.Major.CompareTo(other.Major);
157 |             if (r != 0) return r;
158 | 
159 |             r = this.Minor.CompareTo(other.Minor);
160 |             if (r != 0) return r;
161 | 
162 |             r = this.Patch.CompareTo(other.Patch);
163 |             if (r != 0) return r;
164 | 
165 |             r = CompareComponent(this.Prerelease, other.Prerelease, true);
166 |             return r;
167 |         }
168 | 
169 |         static int CompareComponent(string a, string b, bool lower = false)
170 |         {
171 |             var aEmpty = String.IsNullOrEmpty(a);
172 |             var bEmpty = String.IsNullOrEmpty(b);
173 |             if (aEmpty && bEmpty)
174 |                 return 0;
175 | 
176 |             if (aEmpty)
177 |                 return lower ? 1 : -1;
178 |             if (bEmpty)
179 |                 return lower ? -1 : 1;
180 | 
181 |             var aComps = a.Split('.');
182 |             var bComps = b.Split('.');
183 | 
184 |             var minLen = Math.Min(aComps.Length, bComps.Length);
185 |             for (int i = 0; i < minLen; i++)
186 |             {
187 |                 var ac = aComps[i];
188 |                 var bc = bComps[i];
189 |                 int anum, bnum;
190 |                 var isanum = Int32.TryParse(ac, out anum);
191 |                 var isbnum = Int32.TryParse(bc, out bnum);
192 |                 int r;
193 |                 if (isanum && isbnum)
194 |                 {
195 |                     r = anum.CompareTo(bnum);
196 |                     if (r != 0) return anum.CompareTo(bnum);
197 |                 }
198 |                 else
199 |                 {
200 |                     if (isanum)
201 |                         return -1;
202 |                     if (isbnum)
203 |                         return 1;
204 |                     r = String.CompareOrdinal(ac, bc);
205 |                     if (r != 0)
206 |                         return r;
207 |                 }
208 |             }
209 | 
210 |             return aComps.Length.CompareTo(bComps.Length);
211 |         }
212 | 
213 |         public override bool Equals(object obj)
214 |         {
215 |             if (ReferenceEquals(obj, null))
216 |                 return false;
217 | 
218 |             if (ReferenceEquals(this, obj))
219 |                 return true;
220 | 
221 |             var other = (ElasticVersion)obj;
222 | 
223 |             return Equals(other);
224 |         }
225 | 
226 |         public bool Equals(ElasticVersion other)
227 |         {
228 |             if (other == null)
229 |                 return false;
230 | 
231 |             return this.Major == other.Major &&
232 |                 this.Minor == other.Minor &&
233 |                 this.Patch == other.Patch &&
234 |                 string.Equals(this.Prerelease, other.Prerelease, StringComparison.Ordinal);
235 |         }
236 | 
237 |         public override int GetHashCode()
238 |         {
239 |             unchecked
240 |             {
241 |                 int result = this.Major.GetHashCode();
242 |                 result = result * 31 + this.Minor.GetHashCode();
243 |                 result = result * 31 + this.Patch.GetHashCode();
244 |                 result = result * 31 + this.Prerelease.GetHashCode();
245 |                 return result;
246 |             }
247 |         }
248 | 
249 |         public static bool operator ==(ElasticVersion left, ElasticVersion right)
250 |         {
251 |             return ElasticVersion.Equals(left, right);
252 |         }
253 | 
254 |         public static bool operator !=(ElasticVersion left, ElasticVersion right)
255 |         {
256 |             return !ElasticVersion.Equals(left, right);
257 |         }
258 | 
259 |         public static bool operator >(ElasticVersion left, ElasticVersion right)
260 |         {
261 |             return ElasticVersion.Compare(left, right) > 0;
262 |         }
263 | 
264 |         public static bool operator >=(ElasticVersion left, ElasticVersion right)
265 |         {
266 |             return left == right || left > right;
267 |         }
268 | 
269 |         public static bool operator <(ElasticVersion left, ElasticVersion right)
270 |         {
271 |             return ElasticVersion.Compare(left, right) < 0;
272 |         }
273 | 
274 |         public static bool operator <=(ElasticVersion left, ElasticVersion right)
275 |         {
276 |             return left == right || left < right;
277 |         }
278 |     }
279 | }
280 | 


--------------------------------------------------------------------------------
/Elastic.Console/ElasticsearchRequestBody.cs:
--------------------------------------------------------------------------------
 1 | using System;
 2 | using System.Collections;
 3 | 
 4 | namespace Elastic 
 5 | {
 6 |     /// 
 7 |     /// An Elasticsearch API request body represented as a
 8 |     /// Hashtable or string
 9 |     /// 
10 |     /// 
11 |     /// Allows $Body in Invoke-Elasticsearch to *not* bind to ElasticsearchRequest
12 |     /// 
13 |     public class ElasticsearchRequestBody
14 |     {
15 |         public ElasticsearchRequestBody(Hashtable input)
16 |         {
17 |             this.Input = input;
18 |         }
19 | 
20 |         public ElasticsearchRequestBody(string input)
21 |         {
22 |             this.Input = input;
23 |         }
24 | 
25 |         public object Input { get; private set; }
26 |     }
27 | }


--------------------------------------------------------------------------------
/Elastic.Console/README.md:
--------------------------------------------------------------------------------
  1 | # Elastic.Console
  2 | 
  3 | cmdlets to simplify making requests to Elasticsearch from PowerShell.
  4 | 
  5 | Works with 
  6 | - PowerShell Desktop 3.0+ on Windows
  7 | - PowerShell Core on Windows, macOS, Linux
  8 | 
  9 | ## Installation
 10 | 
 11 | Install from the [PowerShell gallery](https://www.powershellgallery.com/packages/Elastic.Console/)
 12 | 
 13 | ```powershell
 14 | Install-Module Elastic.Console
 15 | ```
 16 | 
 17 | If installing a prerelease version, `-AllowPrerelease` switch is required
 18 | 
 19 | ```powershell
 20 | Install-Module Elastic.Console -AllowPrerelease
 21 | ```
 22 | 
 23 | Install from a local directory
 24 | 
 25 | ```
 26 | Import-Module ./Elastic.Console/Elastic.Console.psd1
 27 | ```
 28 | 
 29 | ### Including in your PowerShell profile
 30 | 
 31 | Open your PowerShell profile in your favourite text editor. You can find the location of your profile in PowerShell with
 32 | 
 33 | ```
 34 | $PROFILE
 35 | ```
 36 | 
 37 | Add the following lines to your profile and save
 38 | 
 39 | ```powershell
 40 | if (Get-Module -ListAvailable -Name Elastic.Console) {
 41 |     Import-Module Elastic.Console
 42 | } else {
 43 |     Install-Module Elastic.Console -AllowPrerelease
 44 | }
 45 | ```
 46 | 
 47 | ## Commands
 48 | 
 49 | To list the available commands in the module
 50 | 
 51 | ```powershell
 52 | Get-Command -Module Elastic.Console
 53 | ```
 54 | 
 55 | Full details of each command, including examples, can be viewed with
 56 | 
 57 | ```powershell
 58 | Get-Help  -Full
 59 | ```
 60 | 
 61 | ### `Set-ElasticsearchVersion`
 62 | 
 63 | Sets the version of Elasticsearch to work against. This will download the REST API specs for the given version
 64 | of Elasticsearch if not already downloaded, using the specs to power tab completion of API endpoints and HTTP methods.
 65 | 
 66 | `Set-ElasticsearchVersion` only needs to be called once in a PowerShell session, ideally at the start of the session,
 67 | to set the version of Elasticsearch for the session.
 68 | 
 69 | #### Example
 70 | 
 71 | ```powershell
 72 | Set-ElasticsearchVersion 7.3.0
 73 | ```
 74 | 
 75 | ### `Get-ElasticsearchVersion`
 76 | 
 77 | Gets the version of Elasticsearch that has been set with `Set-ElasticsearchVersion`, or lists the versions of Elasticsearch for which specs
 78 | have been downloaded, when using the `-ListAvailable` switch
 79 | 
 80 | #### Examples
 81 | 
 82 | ```powershell
 83 | Get-ElasticsearchVersion
 84 | ```
 85 | 
 86 | Gets the version of Elasticsearch that has been set with `Set-ElasticsearchVersion`
 87 | 
 88 | ```powershell
 89 | (Get-ElasticsearchVersion) -gt "7.3.0"
 90 | ```
 91 | 
 92 | Gets the version of Elasticsearch that has been set with `Set-ElasticsearchVersion`, and compares against version 7.3.0
 93 | 
 94 | ```powershell
 95 | Get-ElasticsearchVersion -ListAvailable
 96 | ```
 97 | 
 98 | Lists the versions of Elasticsearch for which specs have been downloaded, that can
 99 | be used to power tab completion.
100 | 
101 | ### `Invoke-Elasticsearch`
102 | 
103 | Executes a REST API request against Elasticsearch. The return type is the response body as a `string`, allowing
104 | this to be piped to other commands like `jq`, to file, etc.
105 | 
106 | If `Set-ElasticsearchVersion` is called prior to calling `Invoke-Elasticsearch`, PowerShell tab completion
107 | can be used to complete API endpoints and accepted HTTP methods.
108 | 
109 | `Invoke-Elasticsearch` is aliased to `es` to make usage more concise.
110 | 
111 | ----
112 | **NOTE**
113 | 
114 | When running on PowerShell Core on macOS or Linux, the default `EditMode` for reading lines from the terminal
115 | is `Emacs`, meaning tab completion lists available completion values starting with typed characters, when you have not
116 | typed enough characters to match a single completion. To change this behaviour to allow tab completion to
117 | cycle through available completion values, you can change the `EditMode` with
118 | 
119 | ```powershell
120 | Set-PSReadLineOption -EditMode Windows
121 | ```
122 | ----
123 | 
124 | ### Examples
125 | 
126 | #### Simple requests
127 | 
128 | ```powershell
129 | es _cat/indices
130 | ```
131 | 
132 | Sends a request to Elasticsearch to list the indices in the cluster.
133 | 
134 | The default endpoint is `http://localhost:9200` but you can connect to any host and port
135 | 
136 | ```powershell
137 | es https://example.com:9200/_cat/indices
138 | ```
139 | 
140 | #### Requests with a string body
141 | 
142 | ```powershell
143 | es twitter/_doc/1 -Pretty -Method PUT -Body @'
144 | {
145 |     "user" : "kimchy",
146 |     "post_date" : "2009-11-15T14:12:12",
147 |     "message" : "trying out Elasticsearch"
148 | }
149 | '@
150 | ```
151 | 
152 | Sends a request to Elasticsearch to create a document with id 1 in the twitter index. The document is sent as a 
153 | JSON string literal.
154 | 
155 | #### Requests with a Hashtable body
156 | 
157 | ```powershell
158 | es posts/_search?pretty -u elastic:changeme -H @{ 'X-Opaque-Id' = 'track_this_call' } -ResponseVariable response -d @{
159 |     query = @{
160 |         match = @{ 
161 |             user = "kimchy" 
162 |         }
163 |     }
164 | }
165 | 
166 | $statusCode = $response.StatusCode
167 | $responseHeaders = $response.Headers
168 | ```
169 | 
170 | Sends a request to Elasticsearch to search all indices, passing an `X-Opaque-Id` header to track the call
171 | using the tasks API. The search query is passed as a `Hashtable`. The underlying PowerShell response is captured
172 | with the `response` variable passed to `-ResponseVariable`, allowing the status code and response headers to be 
173 | inspected.
174 | 
175 | #### Requests with a body read from file
176 | 
177 | ```powershell
178 | es posts/_bulk -ContentType application/x-ndjson -d ./data.json
179 | ```
180 | 
181 | Sends a bulk request to Elasticsearch with the newline delimited `application/x-ndjson` content type. The
182 | body of the request is read from the file `./data.json`
183 | 
184 | ```powershell
185 | Get-Content C:\data.json | es posts/_bulk -ContentType application/x-ndjson
186 | ```
187 | 
188 | Sends a bulk request to Elasticsearch with the newline delimited application/x-ndjson content type. The
189 | body of the request is piped to the command.
190 | 
191 | ### `ConvertFrom-KibanaConsole`
192 | 
193 | Converts Kibana console requests to a form that can be piped to `Invoke-Elasticsearch`. Can handle multiple requests
194 | 
195 | #### Multiple console requests
196 | 
197 | ```powershell
198 | @'
199 | PUT /my_locations
200 | {
201 |     "mappings": {
202 |         "properties": {
203 |             "pin": {
204 |                 "properties": {
205 |                     "location": {
206 |                         "type": "geo_point"
207 |                     }
208 |                 }
209 |             }
210 |         }
211 |     }
212 | }
213 | 
214 | PUT /my_locations/_doc/1
215 | {
216 |     "pin" : {
217 |         "location" : {
218 |             "lat" : 40.12,
219 |             "lon" : -71.34
220 |         }
221 |     }
222 | }
223 | '@ | ConvertFrom-KibanaConsole | Invoke-Elasticsearch
224 | ```
225 | 
226 | Converts two Kibana console requests to requests that can be piped to
227 | Elasticsearch to execute
228 | 
229 | #### Pipe console strings
230 | 
231 | ```powershell
232 | 'GET /_cat/indices' | ckc | es
233 | ```
234 | 
235 | Converts a Kibana console GET request to the `_cat/indices` endpoint and pipes the
236 | resulting request to Elasticsearch to execute
237 | 
238 | ### Development
239 | 
240 | #### Building
241 | 
242 | The Elastic.Console can be built using the simple `build.ps1` script in the repository root.
243 | Take a look at the `params()` within the script to see what parameters are supported.
244 | 
245 | #### Code signing
246 | 
247 | In order to publish Elastic.Console, the module should be built using the `build.ps1` script,
248 | passing a code signing certificate to sign the module.
249 | 
250 | An example of building the module for publishing
251 | 
252 | ```powershell
253 | $cert = (dir Cert:\CurrentUser\My -CodeSigningCert)[0]
254 | .\build.ps1 -Version 7.5.0 -Prerelease rc1 -Certificate $cert
255 | ```
256 | 
257 | This retrieves the first code signing certificate from the current user's personal 
258 | certificate store (on Windows), and uses it to sign the Elastic.Console.psm1 module file.
259 | 
260 | #### Publishing
261 | 
262 | The module can be published to the [PowerShell gallery](https://www.powershellgallery.com/)
263 | with
264 | 
265 | ```powershell
266 | Publish-Module -Path .\Elastic.Console\ -NuGetApiKey '' -Exclude .\Elastic.Console\README.md
267 | ```
268 | 
269 | where `` is an API key tied to your PowerShell gallery account. 


--------------------------------------------------------------------------------
/Elastic.Console/ServerCertificateValidation.cs:
--------------------------------------------------------------------------------
 1 | using System.Net.Security;
 2 | using System.Security.Cryptography;
 3 | using System.Security.Cryptography.X509Certificates;
 4 | 
 5 | namespace Elastic
 6 | {
 7 |     /// 
 8 |     /// Handlers for certificate validation
 9 |     /// 
10 |     public class ServerCertificateValidation
11 |     {
12 |         /// 
13 |         /// Skip certificate validation
14 |         /// 
15 |         public static RemoteCertificateValidationCallback AllowAll()
16 |         {
17 |             return new RemoteCertificateValidationCallback(delegate (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors policyErrors) { return true; });
18 |         }
19 |     }
20 | }


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
  1 |                                  Apache License
  2 |                            Version 2.0, January 2004
  3 |                         http://www.apache.org/licenses/
  4 | 
  5 |    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
  6 | 
  7 |    1. Definitions.
  8 | 
  9 |       "License" shall mean the terms and conditions for use, reproduction,
 10 |       and distribution as defined by Sections 1 through 9 of this document.
 11 | 
 12 |       "Licensor" shall mean the copyright owner or entity authorized by
 13 |       the copyright owner that is granting the License.
 14 | 
 15 |       "Legal Entity" shall mean the union of the acting entity and all
 16 |       other entities that control, are controlled by, or are under common
 17 |       control with that entity. For the purposes of this definition,
 18 |       "control" means (i) the power, direct or indirect, to cause the
 19 |       direction or management of such entity, whether by contract or
 20 |       otherwise, or (ii) ownership of fifty percent (50%) or more of the
 21 |       outstanding shares, or (iii) beneficial ownership of such entity.
 22 | 
 23 |       "You" (or "Your") shall mean an individual or Legal Entity
 24 |       exercising permissions granted by this License.
 25 | 
 26 |       "Source" form shall mean the preferred form for making modifications,
 27 |       including but not limited to software source code, documentation
 28 |       source, and configuration files.
 29 | 
 30 |       "Object" form shall mean any form resulting from mechanical
 31 |       transformation or translation of a Source form, including but
 32 |       not limited to compiled object code, generated documentation,
 33 |       and conversions to other media types.
 34 | 
 35 |       "Work" shall mean the work of authorship, whether in Source or
 36 |       Object form, made available under the License, as indicated by a
 37 |       copyright notice that is included in or attached to the work
 38 |       (an example is provided in the Appendix below).
 39 | 
 40 |       "Derivative Works" shall mean any work, whether in Source or Object
 41 |       form, that is based on (or derived from) the Work and for which the
 42 |       editorial revisions, annotations, elaborations, or other modifications
 43 |       represent, as a whole, an original work of authorship. For the purposes
 44 |       of this License, Derivative Works shall not include works that remain
 45 |       separable from, or merely link (or bind by name) to the interfaces of,
 46 |       the Work and Derivative Works thereof.
 47 | 
 48 |       "Contribution" shall mean any work of authorship, including
 49 |       the original version of the Work and any modifications or additions
 50 |       to that Work or Derivative Works thereof, that is intentionally
 51 |       submitted to Licensor for inclusion in the Work by the copyright owner
 52 |       or by an individual or Legal Entity authorized to submit on behalf of
 53 |       the copyright owner. For the purposes of this definition, "submitted"
 54 |       means any form of electronic, verbal, or written communication sent
 55 |       to the Licensor or its representatives, including but not limited to
 56 |       communication on electronic mailing lists, source code control systems,
 57 |       and issue tracking systems that are managed by, or on behalf of, the
 58 |       Licensor for the purpose of discussing and improving the Work, but
 59 |       excluding communication that is conspicuously marked or otherwise
 60 |       designated in writing by the copyright owner as "Not a Contribution."
 61 | 
 62 |       "Contributor" shall mean Licensor and any individual or Legal Entity
 63 |       on behalf of whom a Contribution has been received by Licensor and
 64 |       subsequently incorporated within the Work.
 65 | 
 66 |    2. Grant of Copyright License. Subject to the terms and conditions of
 67 |       this License, each Contributor hereby grants to You a perpetual,
 68 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 69 |       copyright license to reproduce, prepare Derivative Works of,
 70 |       publicly display, publicly perform, sublicense, and distribute the
 71 |       Work and such Derivative Works in Source or Object form.
 72 | 
 73 |    3. Grant of Patent License. Subject to the terms and conditions of
 74 |       this License, each Contributor hereby grants to You a perpetual,
 75 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 76 |       (except as stated in this section) patent license to make, have made,
 77 |       use, offer to sell, sell, import, and otherwise transfer the Work,
 78 |       where such license applies only to those patent claims licensable
 79 |       by such Contributor that are necessarily infringed by their
 80 |       Contribution(s) alone or by combination of their Contribution(s)
 81 |       with the Work to which such Contribution(s) was submitted. If You
 82 |       institute patent litigation against any entity (including a
 83 |       cross-claim or counterclaim in a lawsuit) alleging that the Work
 84 |       or a Contribution incorporated within the Work constitutes direct
 85 |       or contributory patent infringement, then any patent licenses
 86 |       granted to You under this License for that Work shall terminate
 87 |       as of the date such litigation is filed.
 88 | 
 89 |    4. Redistribution. You may reproduce and distribute copies of the
 90 |       Work or Derivative Works thereof in any medium, with or without
 91 |       modifications, and in Source or Object form, provided that You
 92 |       meet the following conditions:
 93 | 
 94 |       (a) You must give any other recipients of the Work or
 95 |           Derivative Works a copy of this License; and
 96 | 
 97 |       (b) You must cause any modified files to carry prominent notices
 98 |           stating that You changed the files; and
 99 | 
100 |       (c) You must retain, in the Source form of any Derivative Works
101 |           that You distribute, all copyright, patent, trademark, and
102 |           attribution notices from the Source form of the Work,
103 |           excluding those notices that do not pertain to any part of
104 |           the Derivative Works; and
105 | 
106 |       (d) If the Work includes a "NOTICE" text file as part of its
107 |           distribution, then any Derivative Works that You distribute must
108 |           include a readable copy of the attribution notices contained
109 |           within such NOTICE file, excluding those notices that do not
110 |           pertain to any part of the Derivative Works, in at least one
111 |           of the following places: within a NOTICE text file distributed
112 |           as part of the Derivative Works; within the Source form or
113 |           documentation, if provided along with the Derivative Works; or,
114 |           within a display generated by the Derivative Works, if and
115 |           wherever such third-party notices normally appear. The contents
116 |           of the NOTICE file are for informational purposes only and
117 |           do not modify the License. You may add Your own attribution
118 |           notices within Derivative Works that You distribute, alongside
119 |           or as an addendum to the NOTICE text from the Work, provided
120 |           that such additional attribution notices cannot be construed
121 |           as modifying the License.
122 | 
123 |       You may add Your own copyright statement to Your modifications and
124 |       may provide additional or different license terms and conditions
125 |       for use, reproduction, or distribution of Your modifications, or
126 |       for any such Derivative Works as a whole, provided Your use,
127 |       reproduction, and distribution of the Work otherwise complies with
128 |       the conditions stated in this License.
129 | 
130 |    5. Submission of Contributions. Unless You explicitly state otherwise,
131 |       any Contribution intentionally submitted for inclusion in the Work
132 |       by You to the Licensor shall be under the terms and conditions of
133 |       this License, without any additional terms or conditions.
134 |       Notwithstanding the above, nothing herein shall supersede or modify
135 |       the terms of any separate license agreement you may have executed
136 |       with Licensor regarding such Contributions.
137 | 
138 |    6. Trademarks. This License does not grant permission to use the trade
139 |       names, trademarks, service marks, or product names of the Licensor,
140 |       except as required for reasonable and customary use in describing the
141 |       origin of the Work and reproducing the content of the NOTICE file.
142 | 
143 |    7. Disclaimer of Warranty. Unless required by applicable law or
144 |       agreed to in writing, Licensor provides the Work (and each
145 |       Contributor provides its Contributions) on an "AS IS" BASIS,
146 |       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 |       implied, including, without limitation, any warranties or conditions
148 |       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 |       PARTICULAR PURPOSE. You are solely responsible for determining the
150 |       appropriateness of using or redistributing the Work and assume any
151 |       risks associated with Your exercise of permissions under this License.
152 | 
153 |    8. Limitation of Liability. In no event and under no legal theory,
154 |       whether in tort (including negligence), contract, or otherwise,
155 |       unless required by applicable law (such as deliberate and grossly
156 |       negligent acts) or agreed to in writing, shall any Contributor be
157 |       liable to You for damages, including any direct, indirect, special,
158 |       incidental, or consequential damages of any character arising as a
159 |       result of this License or out of the use or inability to use the
160 |       Work (including but not limited to damages for loss of goodwill,
161 |       work stoppage, computer failure or malfunction, or any and all
162 |       other commercial damages or losses), even if such Contributor
163 |       has been advised of the possibility of such damages.
164 | 
165 |    9. Accepting Warranty or Additional Liability. While redistributing
166 |       the Work or Derivative Works thereof, You may choose to offer,
167 |       and charge a fee for, acceptance of support, warranty, indemnity,
168 |       or other liability obligations and/or rights consistent with this
169 |       License. However, in accepting such obligations, You may act only
170 |       on Your own behalf and on Your sole responsibility, not on behalf
171 |       of any other Contributor, and only if You agree to indemnify,
172 |       defend, and hold each Contributor harmless for any liability
173 |       incurred by, or claims asserted against, such Contributor by reason
174 |       of your accepting any such warranty or additional liability.
175 | 
176 |    END OF TERMS AND CONDITIONS
177 | 
178 |    APPENDIX: How to apply the Apache License to your work.
179 | 
180 |       To apply the Apache License to your work, attach the following
181 |       boilerplate notice, with the fields enclosed by brackets "[]"
182 |       replaced with your own identifying information. (Don't include
183 |       the brackets!)  The text should be enclosed in the appropriate
184 |       comment syntax for the file format. We also recommend that a
185 |       file or class name and description of purpose be included on the
186 |       same "printed page" as the copyright notice for easier
187 |       identification within third-party archives.
188 | 
189 |    Copyright [yyyy] [name of copyright owner]
190 | 
191 |    Licensed under the Apache License, Version 2.0 (the "License");
192 |    you may not use this file except in compliance with the License.
193 |    You may obtain a copy of the License at
194 | 
195 |        http://www.apache.org/licenses/LICENSE-2.0
196 | 
197 |    Unless required by applicable law or agreed to in writing, software
198 |    distributed under the License is distributed on an "AS IS" BASIS,
199 |    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 |    See the License for the specific language governing permissions and
201 |    limitations under the License.
202 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | # Elastic PowerShell modules
 2 | 
 3 | Modules for working with [the Elastic Stack](https://www.elastic.co/products/).
 4 | 
 5 | ## Modules
 6 | 
 7 | - [Elastic.Console](Elastic.Console)
 8 | 
 9 |     cmdlets to simplify making requests to Elasticsearch.
10 | 
11 | ## License
12 | 
13 | [Apache 2.0](LICENSE).


--------------------------------------------------------------------------------
/build.ps1:
--------------------------------------------------------------------------------
 1 | # Simple script to prepare Elastic.Console for publishing
 2 | param(
 3 |     [string]
 4 |     [Parameter(Mandatory = $true)]
 5 |     $Version,
 6 | 
 7 |     [string]
 8 |     [Parameter()]
 9 |     $Prerelease = "",
10 | 
11 |     [string]
12 |     [Parameter()]
13 |     $ReleaseNotes,
14 | 
15 |     [System.Security.Cryptography.X509Certificates.X509Certificate2]
16 |     [Parameter()]
17 |     $Certificate
18 | )
19 | 
20 | function Log {
21 |     param(
22 |         [string]
23 |         $Message
24 |     )
25 | 
26 |     $FormattedDate = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
27 |     $LogMessage = "[$FormattedDate] $Message"
28 | 
29 |     Write-Output $LogMessage
30 | }
31 | 
32 | $manifest = "./Elastic.Console/Elastic.Console.psd1"
33 | Log "Updating $manifest"
34 | if (-not $ReleaseNotes) {
35 |     $ReleaseNotes = "Update to version $Version"
36 | }
37 | 
38 | $updates = @{
39 |     Path = $manifest
40 |     ModuleVersion = $Version
41 |     ReleaseNotes = $ReleaseNotes
42 |     Prerelease = $Prerelease
43 |     RequireLicenseAcceptance = $false
44 | }
45 | 
46 | Update-ModuleManifest @updates
47 | 
48 | $module = "./Elastic.Console/Elastic.Console.psm1"
49 | Log "Updating $module"
50 | (Get-Content $module) -replace 'Set-ElasticsearchVersion -Version "(.*?)"',"Set-ElasticsearchVersion -Version `"$Version`"" | Set-Content -Path $module
51 | 
52 | Log "Creating autocompletion file for version $Version"
53 | Import-Module $manifest -Force
54 | Set-ElasticsearchVersion $Version
55 | Remove-Module Elastic.Console
56 | 
57 | Log "Removing all files under ./Elastic.Console/specs except autocomplete.json for version $Version"
58 | Get-ChildItem ./Elastic.Console/specs/* | ForEach-Object {   
59 |     if ($_.Name -eq $Version) {
60 |         $files = Get-ChildItem $_ | Where-Object { $_.Name -ne 'autocomplete.json' }
61 |         $files | Remove-Item -Recurse -Force
62 |     } else {
63 |         Remove-Item $_ -Recurse -Force
64 |     }
65 | }
66 | 
67 | if ($Certificate) {
68 |     Log "Signing $module with passed certificate $($cert.Subject)"
69 |     Set-AuthenticodeSignature -FilePath $module -Certificate $Certificate
70 | 
71 |     Log "Checking signature"
72 |     Get-AuthenticodeSignature -FilePath $module
73 |     Log "Done. ready for publishing"
74 | 
75 | } else {
76 |     Log "Done. Scripts should be signed for publishing with a Code Signing Certificate"
77 | }
78 | 
79 | 
80 | 
81 | 


--------------------------------------------------------------------------------
/build/elastic-cluster-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elastic/powershell/1ee6746f6d2c916dcf88c5a5836b933faabd08c2/build/elastic-cluster-icon.png


--------------------------------------------------------------------------------
/test.ps1:
--------------------------------------------------------------------------------
 1 | # Simple script to test Elastic.Console. Requires docker to run Elasticsearch
 2 | param(
 3 |     [string]
 4 |     [Parameter(Mandatory = $true)]
 5 |     $Version
 6 | 
 7 | )
 8 | 
 9 | $pesterVersion = "5.0.3"
10 | $pester = Get-Module Pester | Where-Object { $_.Version  -eq $pesterVersion }
11 | if (!($pester)) {
12 |     Install-Module Pester -RequiredVersion $pesterVersion -Force -SkipPublisherCheck
13 | }
14 | 
15 | Import-Module Pester -Version $pesterVersion
16 | 
17 | $pesterParameters = @{
18 |     Path = "./tests/*.tests.ps1"
19 |     Show = "All"
20 | }
21 | 
22 | # skip ga release tests for versions that look like prereleases
23 | if ($Version -match '-') {
24 |     $pesterParameters.ExcludeTagFilter = "ga"
25 | }
26 | 
27 | try {
28 |     Invoke-Pester @pesterParameters
29 | } finally {
30 |     Stop-Elasticsearch
31 | }
32 | 
33 | 
34 | 


--------------------------------------------------------------------------------
/tests/Integration.tests.ps1:
--------------------------------------------------------------------------------
  1 | param(
  2 |     [Parameter()]
  3 |     [ValidateNotNullOrEmpty]
  4 |     $Version
  5 | )
  6 | 
  7 | Import-Module -Name $PSScriptRoot/../Elastic.Console/Elastic.Console.psd1 -Force
  8 | Import-Module -Name $PSScriptRoot/elasticsearch.ps1 -Force | Out-Null
  9 | 
 10 | Describe "Oss distribution tests" {
 11 |     BeforeAll {
 12 |         Start-Elasticsearch -Version $Version -Distribution oss
 13 |     }
 14 | 
 15 |     Context "Set-ElasticsearchVersion" {
 16 |         It "should set the Elasticsearch version" -Tag "ga" {
 17 |             $elasticVersion = [Elastic.ElasticVersion]$Version
 18 | 
 19 |             Set-ElasticsearchVersion $Version
 20 | 
 21 |             Get-ElasticsearchVersion -ListAvailable | Should -Contain $elasticVersion
 22 |             Get-ElasticsearchVersion | Should -Be $elasticVersion
 23 |         }
 24 |     }
 25 | 
 26 |     Context "Invoke-Elasticsearch" {
 27 | 
 28 |         It "should use http://localhost:9200 by default" {
 29 |             es "/" | Should -Not -BeNullOrEmpty
 30 |         }
 31 | 
 32 |         It "should return compact json" {
 33 |             $json = es /_nodes
 34 |             $json | Should -Not -BeNullOrEmpty
 35 |             $json | Should -Not -BeLike "*`n*"
 36 |         }
 37 | 
 38 |         It "should return pretty json with -Pretty" {
 39 |             es /_nodes -Pretty | Should -BeLike "*`n*"
 40 |         }
 41 | 
 42 |         It "should accept json string input" {
 43 |             es /basic_test_index/_doc/string_input -Body '{ "message": "string test" }' | Should -Match '"result":"created"'
 44 |         }
 45 | 
 46 |         It "should accept json string from pipeline" {
 47 |             '{ "message": "string test" }' | es /basic_test_index/_doc/string_pipeline  | Should -Match '"result":"created"'
 48 |         }
 49 | 
 50 |         It "should accept hashtable input" {
 51 |             es /basic_test_index/_doc/hash_input -Body @{ message = "hashtable test" } | Should -Match '"result":"created"'
 52 |         }
 53 | 
 54 |         It "should accept hashtable from pipeline" {
 55 |             @{ message = "hashtable test" } | es /basic_test_index/_doc/hash_pipeline  | Should -Match '"result":"created"'
 56 |         }
 57 | 
 58 |         It "should return bytes with -Bytes" {
 59 |             es -X PUT "bytes_index_1" -ResponseVariable odt2
 60 |             $odt2.StatusCode | Should -Be 200
 61 | 
 62 |             $bytes = es "_cat/indices?format=json" -Bytes
 63 |             $bytes | Should -BeOfType [byte]
 64 |             $json = [System.Text.Encoding]::UTF8.GetString($bytes)
 65 |             { ConvertFrom-Json $json } | Should -Not -Throw
 66 |         }
 67 | 
 68 |         It "should capture underlying response object with -ResponseVariable" {
 69 |             es "/" -ResponseVariable odt1
 70 |             $odt1 | Should -Not -BeNullOrEmpty
 71 |         }
 72 | 
 73 |         It "should send content-type header with -ContentType" {
 74 |             es -X PUT "content_type_index_1" -ResponseVariable odt2
 75 |             $odt2.StatusCode | Should -Be 200
 76 |             es -X PUT "content_type_index_2" -ResponseVariable odt2
 77 |             $odt2.StatusCode | Should -Be 200
 78 | 
 79 |             es "_cat/indices" -ContentType "text/plain" -ResponseVariable odt2
 80 |             $odt2.StatusCode | Should -Be 200
 81 |             $odt2.Headers.Keys | Should -Contain "Content-Type" 
 82 |             $odt2.Headers["Content-Type"] | Should -Be "text/plain; charset=UTF-8"
 83 |         }
 84 | 
 85 |         It "should send headers with -Headers" {
 86 |             es -X PUT "headers_index_1" -ResponseVariable odt2
 87 |             $odt2.StatusCode | Should -Be 200
 88 |             es -X PUT "headers_index_2" -ResponseVariable odt2
 89 |             $odt2.StatusCode | Should -Be 200
 90 | 
 91 |             es "_cat/indices" -Headers @{ "Content-Type" = "text/plain"; Accept = "text/plain" } -ResponseVariable odt2
 92 |             $odt2.StatusCode | Should -Be 200
 93 |             $odt2.Headers.Keys | Should -Contain "Content-Type" 
 94 |             $odt2.Headers["Content-Type"] | Should -Be "text/plain; charset=UTF-8"
 95 |         }
 96 | 
 97 |     }
 98 | 
 99 |     Context "ConvertFrom-KibanaConsole" {
100 | 
101 |         It "should pipe ConvertFrom-KibanaConsole to Invoke-Elasticsearch" {
102 |             $json = ConvertFrom-KibanaConsole 'GET /' | es
103 |             $json | Should -Match '"tagline" : "You Know, for Search"'
104 |         }
105 |     }
106 | 
107 |     AfterAll {
108 |         Stop-Elasticsearch
109 |     }
110 | }
111 | 
112 | Describe "Default distribution tests" {
113 |     BeforeAll {
114 |         Start-Elasticsearch -Version $Version -Distribution "default"
115 |     }
116 | 
117 |     Context "Invoke-Elasticsearch" {
118 | 
119 |         It "should skip certificate check with untrusted self-signed certificate with -SkipCertificateCheck" {
120 |             es "https://localhost:9200/" -ResponseVariable ddt1 -SkipCertificateCheck
121 |             $ddt1.StatusCode | Should -Be 401
122 |         }
123 | 
124 |         It "should throw exception with untrusted self-signed certificate" {
125 |             { es "https://localhost:9200/" -User "elastic:changeme" } | Should -Throw
126 |         }
127 | 
128 |         It "should pass username and password colon separated in -User" {
129 |             es "https://localhost:9200/" -User "elastic:changeme" -ResponseVariable ddt2 -SkipCertificateCheck
130 |             $ddt2.StatusCode | Should -Be 200
131 |         }
132 | 
133 |         It "should pass username and password in -User and -Password" {
134 |             $password = ConvertTo-SecureString "changeme" -AsPlainText
135 |             es "https://localhost:9200/" -User "elastic" -Password $password -ResponseVariable ddt2 -SkipCertificateCheck
136 |             $ddt2.StatusCode | Should -Be 200
137 |         }
138 |     }
139 | 
140 |     Context "ConvertFrom-KibanaConsole" {
141 | 
142 |         It "should use the console url in Invoke-Elasticsearch" {
143 |             ConvertFrom-KibanaConsole 'GET https://localhost:9200/' | 
144 |             es -User "elastic:changeme" -SkipCertificateCheck -ResponseVariable ddt3
145 |             $ddt3.StatusCode | Should -Be 200
146 |         }
147 |     }
148 | 
149 | 
150 |     AfterAll {
151 |         Stop-Elasticsearch
152 |     }
153 | }
154 | 
155 | 


--------------------------------------------------------------------------------
/tests/Unit.tests.ps1:
--------------------------------------------------------------------------------
  1 | param(
  2 |     [Parameter()]
  3 |     [ValidateNotNullOrEmpty]
  4 |     $Version
  5 | )
  6 | 
  7 | Describe "Unit tests" {
  8 |     Import-Module -Name $PSScriptRoot/../Elastic.Console/Elastic.Console.psd1 -Force
  9 | 
 10 |     Context "Get-ElasticsearchVersion" {
 11 | 
 12 |         It "should have at least one available version" {
 13 |             $versions = Get-ElasticsearchVersion -ListAvailable 
 14 |             $versions.Count | Should -BeGreaterOrEqual 1
 15 |         }
 16 |     }
 17 | 
 18 |     Context "Aliases" {
 19 | 
 20 |         It "es should be an alias for Invoke-Elasticsearch" {
 21 |             $alias = Get-Alias es -ErrorAction Ignore
 22 |             $alias | Should -Not -BeNullOrEmpty
 23 |             $alias.ResolvedCommand.Name | Should -Be 'Invoke-Elasticsearch'
 24 |             $alias.ResolvedCommand.Module.Name | Should -Be 'Elastic.Console'
 25 |         }
 26 |     
 27 |         It "ckc should be an alias for ConvertFrom-KibanaConsole" {
 28 |             $alias = Get-Alias ckc -ErrorAction Ignore
 29 |             $alias | Should -Not -BeNullOrEmpty
 30 |             $alias.ResolvedCommand.Name | Should -Be 'ConvertFrom-KibanaConsole'
 31 |             $alias.ResolvedCommand.Module.Name | Should -Be 'Elastic.Console'
 32 |         }
 33 |     
 34 |         It "hash should be an alias for ConvertFrom-Json" {
 35 |             $alias = Get-Alias hash -ErrorAction Ignore
 36 |             $alias | Should -Not -BeNullOrEmpty
 37 |             $alias.ResolvedCommand.Name | Should -Be 'ConvertFrom-Json'
 38 |             $alias.ResolvedCommand.Module.Name | Should -Be 'Microsoft.PowerShell.Utility'
 39 |         }
 40 |     }
 41 | 
 42 |     Context "ConvertFrom-KibanaConsole" {
 43 | 
 44 |         It "should parse single console request" {
 45 |             $request = @'
 46 | PUT /my-index-000001
 47 | {
 48 |     "settings": {
 49 |     "index": {
 50 |         "number_of_shards": 3,  
 51 |         "number_of_replicas": 2 
 52 |     }
 53 |     }
 54 | }
 55 | '@ | ckc
 56 | 
 57 |             $request | Should -Not -BeNullOrEmpty
 58 |             $request | Should -BeOfType 'PSCustomObject'
 59 |             $request.Method | Should -Be PUT
 60 |             $request.Uri | Should -Be /my-index-000001
 61 |             $request.Body | Should -Be @'
 62 | {
 63 |     "settings": {
 64 |     "index": {
 65 |         "number_of_shards": 3,  
 66 |         "number_of_replicas": 2 
 67 |     }
 68 |     }
 69 | }
 70 | '@
 71 |         }
 72 | 
 73 |         It "should parse multiple console requests" {
 74 |             $request = @'
 75 | PUT /my-index-000001
 76 | {
 77 |     "settings": {
 78 |     "index": {
 79 |         "number_of_shards": 3,  
 80 |         "number_of_replicas": 2 
 81 |     }
 82 |     }
 83 | }
 84 | POST /my-index-000001/_doc/1
 85 | { "message": "foo" }
 86 | 
 87 | POST /my-index-000001/_doc/2
 88 | { "message": "bar" }
 89 | '@ | ckc
 90 | 
 91 |             $request | Should -HaveCount 3
 92 | 
 93 |             $request[0] | Should -BeOfType 'PSCustomObject'
 94 |             $request[0].Method | Should -Be PUT
 95 |             $request[0].Uri | Should -Be /my-index-000001
 96 |             $request[0].Body | Should -Be @'
 97 | {
 98 |     "settings": {
 99 |     "index": {
100 |         "number_of_shards": 3,  
101 |         "number_of_replicas": 2 
102 |     }
103 |     }
104 | }
105 | '@
106 | 
107 |             $request[1] | Should -BeOfType 'PSCustomObject'
108 |             $request[1].Method | Should -Be POST
109 |             $request[1].Uri | Should -Be /my-index-000001/_doc/1
110 |             $request[1].Body | Should -Be '{ "message": "foo" }'
111 | 
112 |             
113 |             $request[2] | Should -BeOfType 'PSCustomObject'
114 |             $request[2].Method | Should -Be POST
115 |             $request[2].Uri | Should -Be /my-index-000001/_doc/2
116 |             $request[2].Body | Should -Be '{ "message": "bar" }'
117 | 
118 |         }
119 |     }
120 | }
121 | 
122 | 


--------------------------------------------------------------------------------
/tests/elasticsearch.ps1:
--------------------------------------------------------------------------------
  1 | New-Module -Name Elasticsearch -Scriptblock {
  2 | function Log {
  3 |     [CmdletBinding()]
  4 |     param(
  5 |       [string]
  6 |       [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
  7 |       $Message,
  8 |   
  9 |       [string]
 10 |       [ValidateSet("Success", "Info", "Error")]
 11 |       $Level
 12 |     )
 13 |   
 14 |     $ESC = [char]27
 15 |   
 16 |     if (!$Level) {
 17 |       $Level = "Info"
 18 |     }
 19 |   
 20 |     switch ($Level) {
 21 |       "Success" { $levelMessage = "[32;1mSUCCESS:" }
 22 |       "Info" { $levelMessage = "[34;1mINFO:" }
 23 |       "Error" { $levelMessage = "[31;1mERROR:" }
 24 |     }
 25 |   
 26 |     Write-Host "$ESC$levelMessage$ESC[0m $message$ESC[0m"
 27 | }
 28 | 
 29 | function Test-Docker {
 30 |     try {
 31 |         docker --version | Out-Null
 32 |         return $true
 33 |     } catch {
 34 |         Log "docker not installed or running. Please install and run docker" -Level Error
 35 |         return $false
 36 |     }
 37 | }
 38 | 
 39 | function Remove-Volume {
 40 |     param(
 41 |         [string]
 42 |         $Name
 43 |     )
 44 | 
 45 |     if ("$(docker volume ls --quiet --filter name="$Name")") {
 46 |         Log "Removing volume $Name"
 47 |         docker volume rm "$Name" | Out-Null
 48 |     }
 49 | }
 50 | 
 51 | function Remove-Node {
 52 |     param(
 53 |         [string]
 54 |         $Name
 55 |     )
 56 | 
 57 |     if ("$(docker ps --quiet --filter name="$Name")" -ne "") {
 58 |         Log "Removing container $Name"
 59 |         docker container rm --force --volumes "$Name" | Out-Null
 60 |         Remove-Volume "$Name-data" 
 61 |     }
 62 | }
 63 | function Remove-Network {
 64 |     param(
 65 |         [string]
 66 |         $Name
 67 |     )
 68 | 
 69 |     if ("$(docker network ls --quiet --filter name="$Name")" -ne "") {
 70 |         Log "Removing network $Name"
 71 |         docker network rm $Name | Out-Null
 72 |     }
 73 | }
 74 | 
 75 | function Stop-Elasticsearch {
 76 |     [CmdletBinding()]
 77 |     param(
 78 |         $NetworkName = "es-net"
 79 |     )
 80 | 
 81 |     if (!(Test-Docker)) {
 82 |         return
 83 |     }
 84 | 
 85 |     if ("$(docker network ls --quiet --filter name=$NetworkName)" -eq "") {
 86 |         Log "$NetworkName is already deleted"
 87 |         return
 88 |     }
 89 | 
 90 |     $containers = $(docker network inspect --format '{{ range $key, $value := .Containers }}{{ println .Name}}{{ end }}' $NetworkName)
 91 |     
 92 |     foreach($container in $containers) {
 93 |         Remove-Node "$container"
 94 |     }
 95 |     
 96 |     Remove-Network $NetworkName
 97 |     Log "Cleaned up and exiting" -Level Success
 98 | }
 99 | 
100 | function Start-Elasticsearch {
101 |     [CmdletBinding()]
102 |     param(
103 |         [string]
104 |         [Parameter(Mandatory = $true)]
105 |         $Version,
106 | 
107 |         [string]
108 |         [ValidateSet("oss", "default")]
109 |         $Distribution = "default",
110 | 
111 |         [string]
112 |         $NodeName = "instance",
113 | 
114 |         [string]
115 |         $ClusterName = "es",
116 | 
117 |         [int]
118 |         $HttpPort = 9200,
119 | 
120 |         [string]
121 |         $ElasticPassword = "changeme",
122 | 
123 |         [string]
124 |         $SslCert = "$PWD/.ci/testnode.crt",
125 | 
126 |         [string]
127 |         $SslKey = "$PWD/.ci/testnode.key",
128 | 
129 |         [string]
130 |         $SslCa = "$PWD/.ci/ca.crt",
131 | 
132 |         [string]
133 |         $NetworkName = "es-net"
134 |     )
135 | 
136 |     if (!(Test-Docker)) {
137 |         return
138 |     }
139 | 
140 |     $VolumeName = "$NodeName-data"
141 | 
142 |     trap {
143 |         Remove-Node $NodeName
144 |         Remove-Network $NetworkName
145 |     }
146 | 
147 |     Log "Making sure previous run leftover infrastructure is removed"
148 | 
149 |     Remove-Node $NodeName
150 |     Remove-Network $NetworkName
151 | 
152 |     Log "Creating network $NetworkName"
153 | 
154 |     docker network create $NetworkName | Log
155 | 
156 |     $environment = @(
157 |         "--env", "node.name=`"$NodeName`"",
158 |         "--env", "cluster.name=`"$ClusterName`"",
159 |         "--env", "cluster.initial_master_nodes=`"$NodeName`"",
160 |         "--env", "discovery.seed_hosts=`"$NodeName`"",
161 |         "--env", "cluster.routing.allocation.disk.threshold_enabled=false",
162 |         "--env", "bootstrap.memory_lock=true",
163 |         "--env", "node.attr.testattr=test",
164 |         "--env", "path.repo=/tmp",
165 |         "--env", "repositories.url.allowed_urls=http://snapshot.test*"
166 |     )
167 | 
168 |     $volumes = @(
169 |         "--volume", "${VolumeName}:/usr/share/elasticsearch/data"
170 |     )
171 | 
172 |     $url="http://$NodeName"
173 | 
174 |     if ($Distribution -eq "default") {
175 |         $environment += @(
176 |             "--env", "ELASTIC_PASSWORD=`"$ElasticPassword`"",
177 |             "--env", "xpack.license.self_generated.type=trial",
178 |             "--env", "xpack.security.enabled=true",
179 |             "--env", "xpack.security.http.ssl.enabled=true",
180 |             "--env", "xpack.security.http.ssl.verification_mode=certificate",
181 |             "--env", "xpack.security.http.ssl.key=certs/testnode.key",
182 |             "--env", "xpack.security.http.ssl.certificate=certs/testnode.crt",
183 |             "--env", "xpack.security.http.ssl.certificate_authorities=certs/ca.crt",
184 |             "--env", "xpack.security.transport.ssl.enabled=true",
185 |             "--env", "xpack.security.transport.ssl.key=certs/testnode.key",
186 |             "--env", "xpack.security.transport.ssl.certificate=certs/testnode.crt",
187 |             "--env", "xpack.security.transport.ssl.certificate_authorities=certs/ca.crt"
188 |         )
189 | 
190 |         $volumes += @(
191 |             "--volume", "`"${SslCert}`":/usr/share/elasticsearch/config/certs/testnode.crt",
192 |             "--volume", "`"${SslKey}`":/usr/share/elasticsearch/config/certs/testnode.key",
193 |             "--volume", "`"${SslCa}`":/usr/share/elasticsearch/config/certs/ca.crt"
194 |         )
195 | 
196 |         $url="https://elastic:$ElasticPassword@$NodeName"
197 | 
198 |         $elasticsearchImage = "elasticsearch:$Version"
199 |         $curlFlags = "--insecure --cacert /usr/share/elasticsearch/config/certs/ca.crt --resolve ${NodeName}:443:127.0.0.1"
200 |     } else {
201 |         $elasticsearchImage = "elasticsearch-oss:$Version"
202 |         $curlFlags = ""
203 |     }
204 | 
205 |     Log "Starting container $NodeName"
206 | 
207 |     docker run `
208 |         --name "`"$NodeName`"" `
209 |         --network "`"$NetworkName`"" `
210 |         --env ES_JAVA_OPTS=-"`"Xms1g -Xmx1g`"" `
211 |         $environment `
212 |         $volumes `
213 |         --publish ${HttpPort}:9200 `
214 |         --ulimit nofile=65536:65536 `
215 |         --ulimit memlock=-1:-1 `
216 |         --detach="`"true`"" `
217 |         --health-cmd="`"curl $curlFlags --fail ${url}:9200/_cluster/health || exit 1`"" `
218 |         --health-interval=2s `
219 |         --health-retries=20 `
220 |         --health-timeout=2s `
221 |         docker.elastic.co/elasticsearch/$elasticsearchImage | Log
222 | 
223 | 
224 |     while("$(docker inspect -f '{{.State.Health.Status}}' $NodeName)" -eq "starting") {
225 |         Start-Sleep 2;
226 |         Log "waiting for node $NodeName to be up"
227 |     }
228 | 
229 |     docker logs $NodeName | Log
230 | 
231 |     if ("$(docker inspect -f '{{.State.Health.Status}}' $NodeName)" -ne "healthy") {
232 |         Remove-Node $NodeName
233 |         Remove-Network $NetworkName
234 |         Log "Failed to start Elasticsearch $Version in detached mode beyond health checks" -Level Error
235 |         Log "dumped the docker log before shutting the node down" -Level Error
236 |         throw "Failed to start Elasticsearch $Version"
237 |     } else {
238 |         Log "Detached and healthy: $NodeName on docker network: $NetworkName" -Level Success     
239 |         $localhost = $url.Replace("$NodeName", "localhost")
240 |         Log "Running on: ${localhost}:${HttpPort}" -Level Success
241 |         return "${localhost}:${HttpPort}"
242 |     }
243 | }
244 | 
245 | Export-ModuleMember -Function Start-Elasticsearch,Stop-Elasticsearch
246 | 
247 | }


--------------------------------------------------------------------------------