Add a tool which can be used to install the x-pack license in an Elasticsearch cluster
This commit is contained in:
Родитель
2ea8ff9f52
Коммит
2fbcc30e83
|
@ -0,0 +1,5 @@
|
|||
vendor
|
||||
build
|
||||
image
|
||||
release
|
||||
elasticlicense
|
|
@ -0,0 +1,7 @@
|
|||
FROM golang:1.9.4-alpine3.7
|
||||
|
||||
ENV BIN=elasticlicense
|
||||
|
||||
COPY build/*-linux-amd64 /go/bin/$BIN
|
||||
|
||||
CMD /go/bin/$BIN
|
|
@ -0,0 +1,43 @@
|
|||
VERSION ?= $(shell git describe --always --tags)
|
||||
BIN = elasticlicense
|
||||
BUILD_CMD = go build -o build/$(BIN)-$(VERSION)-$${GOOS}-$${GOARCH} &
|
||||
IMAGE_REPO = docker.io/mse
|
||||
FMT_CMD = $(gofmt -s -l -w $(find . -type f -name '*.go' -not -path './vendor/*') | tee /dev/stderr)
|
||||
|
||||
default:
|
||||
$(MAKE) bootstrap
|
||||
$(MAKE) build
|
||||
|
||||
test: bootstrap
|
||||
test -z '$(FMT_CMD)'
|
||||
go vet $(go list ./... | grep -v /vendor/)
|
||||
golint -set_exit_status $(shell go list ./... | grep -v vendor)
|
||||
gas ./...
|
||||
ginkgo -r -v
|
||||
bootstrap:
|
||||
glide install
|
||||
build:
|
||||
go build -o $(BIN)
|
||||
clean:
|
||||
rm -rf build vendor
|
||||
rm -f release image bootstrap $(BIN)
|
||||
release: bootstrap
|
||||
@echo "Running build command..."
|
||||
bash -c '\
|
||||
export GOOS=linux; export GOARCH=amd64; export CGO_ENABLED=0; $(BUILD_CMD) \
|
||||
wait \
|
||||
'
|
||||
touch release
|
||||
|
||||
image: release
|
||||
@echo "Building the Docker image..."
|
||||
docker build -t $(IMAGE_REPO)/$(BIN):$(VERSION) .
|
||||
docker tag $(IMAGE_REPO)/$(BIN):$(VERSION) $(IMAGE_REPO)/$(BIN):latest
|
||||
touch image
|
||||
|
||||
image-push: image
|
||||
docker push $(IMAGE_REPO)/$(BIN):$(VERSION)
|
||||
docker push $(IMAGE_REPO)/$(BIN):latest
|
||||
|
||||
.PHONY: test build clean image-push
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
# elasticlicense
|
||||
|
||||
This is a tool which can be used to install the x-pack license into an Elasticsearch cluster.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get Azure/helm-elasticstack/tools/elasticlicense
|
||||
```
|
||||
|
||||
Alternatively you can build the docker image by cloning the repository and executing the following command:
|
||||
|
||||
```bash
|
||||
make image
|
||||
```
|
||||
|
||||
## Install a new license
|
||||
|
||||
Download the license form [Elasticsearch support](https://license.elastic.co/download) and store it into a `license.json` file.
|
||||
|
||||
You should also define the basic authentication credentials used by your Elasticsearch cluster in a `auth-file.json` as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "<USER NAME>",
|
||||
"password": "<PASSWORD>"
|
||||
}
|
||||
|
||||
```
|
||||
The license can be installed by executing the command:
|
||||
|
||||
```bash
|
||||
elasticlicense install -license-file=license.json -host=<ELASTICSEARCH-HOST> -port=<ELASTICSEARCH-PORT> -auth-file=auth-file.json
|
||||
```
|
||||
|
||||
or run the tool in a docker container:
|
||||
|
||||
```bash
|
||||
docker run --rm -v ${PWD}:/config -t docker.io/cosmincojocar/elasticlicense install -license-file=/config/license.json \
|
||||
-host=<ELASTICSEARCH-HOST> -port=<ELASTICSEARCH-PORT> -auth-file=/config/auth-file.json
|
||||
```
|
||||
|
||||
The installed license can be viewed with the following command:
|
||||
|
||||
```bash
|
||||
elasticlicense view -host=<ELASTICSEARCH-HOST> -port=<ELASTICSEARCH-PORT> -auth-file=auth-file.json
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
You can execute the tests and build the tool using the default make target:
|
||||
|
||||
```bash
|
||||
make
|
||||
```
|
||||
|
||||
To build and publish the docker image execute:
|
||||
|
||||
```bash
|
||||
make image
|
||||
make image-push
|
||||
```
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"username": "test",
|
||||
"password": "test"
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
hash: a03901b5a4eb317dda66ca82be2ebd70809fe28f6a1d7c508996aae234550cc4
|
||||
updated: 2018-03-15T13:30:45.446539435+01:00
|
||||
imports:
|
||||
- name: github.com/google/subcommands
|
||||
version: ce3d4cfc062faac7115d44e5befec8b5a08c3faa
|
||||
testImports:
|
||||
- name: github.com/golang/protobuf
|
||||
version: 2bba0603135d7d7f5cb73b2125beeda19c09f4ef
|
||||
subpackages:
|
||||
- proto
|
||||
- name: github.com/onsi/ginkgo
|
||||
version: 9008c7b79f9636c46a0a945141020124702f0ecf
|
||||
subpackages:
|
||||
- config
|
||||
- internal/codelocation
|
||||
- internal/containernode
|
||||
- internal/failer
|
||||
- internal/leafnodes
|
||||
- internal/remote
|
||||
- internal/spec
|
||||
- internal/spec_iterator
|
||||
- internal/specrunner
|
||||
- internal/suite
|
||||
- internal/testingtproxy
|
||||
- internal/writer
|
||||
- reporters
|
||||
- reporters/stenographer
|
||||
- reporters/stenographer/support/go-colorable
|
||||
- reporters/stenographer/support/go-isatty
|
||||
- types
|
||||
- name: github.com/onsi/gomega
|
||||
version: 49e4233a3b46c26dddd43cf84547cf31c92d0f2b
|
||||
subpackages:
|
||||
- format
|
||||
- ghttp
|
||||
- internal/assertion
|
||||
- internal/asyncassertion
|
||||
- internal/oraclematcher
|
||||
- internal/testingtsupport
|
||||
- matchers
|
||||
- matchers/support/goraph/bipartitegraph
|
||||
- matchers/support/goraph/edge
|
||||
- matchers/support/goraph/node
|
||||
- matchers/support/goraph/util
|
||||
- types
|
||||
- name: golang.org/x/net
|
||||
version: 1c05540f6879653db88113bc4a2b70aec4bd491f
|
||||
subpackages:
|
||||
- html
|
||||
- html/atom
|
||||
- html/charset
|
||||
- name: golang.org/x/sys
|
||||
version: 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9
|
||||
subpackages:
|
||||
- unix
|
||||
- name: golang.org/x/text
|
||||
version: b19bf474d317b857955b12035d2c5acb57ce8b01
|
||||
subpackages:
|
||||
- encoding
|
||||
- encoding/charmap
|
||||
- encoding/htmlindex
|
||||
- encoding/internal
|
||||
- encoding/internal/identifier
|
||||
- encoding/japanese
|
||||
- encoding/korean
|
||||
- encoding/simplifiedchinese
|
||||
- encoding/traditionalchinese
|
||||
- encoding/unicode
|
||||
- internal/tag
|
||||
- internal/utf8internal
|
||||
- language
|
||||
- runes
|
||||
- transform
|
||||
- name: gopkg.in/yaml.v2
|
||||
version: 53feefa2559fb8dfa8d81baad31be332c97d6c77
|
|
@ -0,0 +1,3 @@
|
|||
package: github.com/Azure/helm-elasticstack/tools/elasticlicense
|
||||
import:
|
||||
- package: github.com/google/subcommands
|
|
@ -0,0 +1,199 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
|
||||
func buildLicenseURL(host string, port int) string {
|
||||
return fmt.Sprintf("http://%s:%d/_xpack/license", host, port)
|
||||
}
|
||||
|
||||
func buildHTTPClient() *http.Client {
|
||||
return &http.Client{
|
||||
Timeout: time.Minute * 1,
|
||||
}
|
||||
}
|
||||
|
||||
func loadBasicAuth(authFile string) (*basicAuth, error) {
|
||||
file, err := ioutil.ReadFile(authFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`Failed to read the basic authentication
|
||||
credetials from the file: %v`, err)
|
||||
}
|
||||
|
||||
var auth basicAuth
|
||||
err = json.Unmarshal(file, &auth)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to unmarshal the basic auth: %v", err)
|
||||
}
|
||||
return &auth, nil
|
||||
}
|
||||
|
||||
func setBasicAuth(req *http.Request, authFile string) error {
|
||||
if authFile != "" {
|
||||
basicAuth, err := loadBasicAuth(authFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.SetBasicAuth(basicAuth.Username, basicAuth.Password)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type basicAuth struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type viewCmd struct {
|
||||
host string
|
||||
port int
|
||||
authFile string
|
||||
}
|
||||
|
||||
func (*viewCmd) Name() string { return "view" }
|
||||
func (*viewCmd) Synopsis() string { return "Display the installed license in Elasticsearch" }
|
||||
func (*viewCmd) Usage() string {
|
||||
return `view [-host] <host name> [-port] <port> [-auth-file] <basic auth file path>:
|
||||
Dispaly the installed license to stdout
|
||||
`
|
||||
}
|
||||
|
||||
func (v *viewCmd) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&v.host, "host", "localhost", "Host name of the Elasticsearch API")
|
||||
f.IntVar(&v.port, "port", 9200, "Port of the Elastisearch API")
|
||||
f.StringVar(&v.authFile, "auth-file", "", "File with basic authentication credentials")
|
||||
}
|
||||
|
||||
func (v *viewCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
client := buildHTTPClient()
|
||||
licenseURL := buildLicenseURL(v.host, v.port)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, licenseURL, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
err = setBasicAuth(req, v.authFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
fmt.Printf("Failed to get the license information with status code [%d]",
|
||||
resp.StatusCode)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to read the license from the HTTP response body")
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
var prettyContent bytes.Buffer
|
||||
err = json.Indent(&prettyContent, content, "", " ")
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to indent the license content: %s\n", string(content))
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
fmt.Print(string(prettyContent.Bytes()))
|
||||
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
|
||||
type installCmd struct {
|
||||
host string
|
||||
port int
|
||||
licenseFile string
|
||||
authFile string
|
||||
}
|
||||
|
||||
func (*installCmd) Name() string { return "install" }
|
||||
func (*installCmd) Synopsis() string { return "Install a new Elasticsearch license" }
|
||||
func (*installCmd) Usage() string {
|
||||
return `install [-host] <host name> [-port] <port> [-license-file] <path to license file> [-auth-file] <path to basic auth file>
|
||||
Install a new license into Elasticsearch cluster
|
||||
`
|
||||
}
|
||||
|
||||
func (i *installCmd) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&i.host, "host", "localhost", "Host name of the Elasticsearch API")
|
||||
f.IntVar(&i.port, "port", 9200, "Port of the Elastisearch API")
|
||||
f.StringVar(&i.licenseFile, "license-file", "", "Path to license file")
|
||||
f.StringVar(&i.authFile, "auth-file", "", "Path to basic auth file")
|
||||
}
|
||||
|
||||
func (i *installCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
if _, err := os.Stat(i.licenseFile); os.IsNotExist(err) {
|
||||
fmt.Printf("License file '%s' not found\n", i.licenseFile)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
file, err := os.Open(i.licenseFile)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to open the license file '%s'\n", i.licenseFile)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
licenseURL := buildLicenseURL(i.host, i.port)
|
||||
req, err := http.NewRequest(http.MethodPut, licenseURL, file)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to build the request to install the license: %v\n", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
err = setBasicAuth(req, i.authFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
client := buildHTTPClient()
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
fmt.Printf("Failed to install the license with status code [%d]\n", resp.StatusCode)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
|
||||
func main() {
|
||||
subcommands.Register(subcommands.HelpCommand(), "")
|
||||
subcommands.Register(subcommands.FlagsCommand(), "")
|
||||
subcommands.Register(subcommands.CommandsCommand(), "")
|
||||
subcommands.Register(&viewCmd{}, "")
|
||||
subcommands.Register(&installCmd{}, "")
|
||||
|
||||
flag.Parse()
|
||||
ctx := context.Background()
|
||||
os.Exit(int(subcommands.Execute(ctx)))
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRules(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Elasticlicense Suite")
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/ghttp"
|
||||
)
|
||||
|
||||
var _ = Describe("The elasticlicense client", func() {
|
||||
var server *ghttp.Server
|
||||
var elasticHost string
|
||||
var elasticPort int
|
||||
const License = "license"
|
||||
var licenseFile *os.File
|
||||
const endpoint = "/_xpack/license"
|
||||
const Username = "test"
|
||||
const Password = "test"
|
||||
var Auth = fmt.Sprintf("{\"Username\": \"%s\", \"Password\": \"%s\"}", Username, Password)
|
||||
|
||||
createAuthFile := func() string {
|
||||
auth := []byte(Auth)
|
||||
authFile, err := ioutil.TempFile("", "auth")
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
_, err = authFile.Write(auth)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
return authFile.Name()
|
||||
}
|
||||
|
||||
Context("install command", func() {
|
||||
BeforeEach(func() {
|
||||
server = ghttp.NewServer()
|
||||
|
||||
u, err := url.Parse(server.URL())
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
elasticHost = host
|
||||
elasticPort, err = strconv.Atoi(port)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
license := []byte(License)
|
||||
licenseFile, err = ioutil.TempFile("", "license")
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
_, err = licenseFile.Write(license)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
if licenseFile != nil {
|
||||
os.Remove(licenseFile.Name())
|
||||
}
|
||||
server.Close()
|
||||
})
|
||||
It("should run without basic auth", func() {
|
||||
server.AppendHandlers(
|
||||
ghttp.CombineHandlers(
|
||||
ghttp.VerifyRequest("PUT", endpoint),
|
||||
ghttp.VerifyBody([]byte(License)),
|
||||
),
|
||||
)
|
||||
|
||||
cmd := &installCmd{
|
||||
host: elasticHost,
|
||||
port: elasticPort,
|
||||
licenseFile: licenseFile.Name(),
|
||||
authFile: ""}
|
||||
|
||||
exitStatus := cmd.Execute(nil, nil)
|
||||
|
||||
Expect(exitStatus).Should(Equal(subcommands.ExitSuccess))
|
||||
Expect(server.ReceivedRequests()).Should(HaveLen(1))
|
||||
})
|
||||
|
||||
It("should run with basic auth", func() {
|
||||
server.AppendHandlers(
|
||||
ghttp.CombineHandlers(
|
||||
ghttp.VerifyRequest("PUT", endpoint),
|
||||
ghttp.VerifyBody([]byte(License)),
|
||||
ghttp.VerifyBasicAuth(Username, Password),
|
||||
),
|
||||
)
|
||||
|
||||
authFile := createAuthFile()
|
||||
defer os.Remove(authFile)
|
||||
|
||||
cmd := &installCmd{
|
||||
host: elasticHost,
|
||||
port: elasticPort,
|
||||
licenseFile: licenseFile.Name(),
|
||||
authFile: authFile}
|
||||
|
||||
exitStatus := cmd.Execute(nil, nil)
|
||||
|
||||
Expect(exitStatus).Should(Equal(subcommands.ExitSuccess))
|
||||
Expect(server.ReceivedRequests()).Should(HaveLen(1))
|
||||
})
|
||||
})
|
||||
Context("view command", func() {
|
||||
BeforeEach(func() {
|
||||
server = ghttp.NewServer()
|
||||
|
||||
u, err := url.Parse(server.URL())
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
elasticHost = host
|
||||
elasticPort, err = strconv.Atoi(port)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
server.Close()
|
||||
})
|
||||
|
||||
It("should run without basic auth", func() {
|
||||
server.AppendHandlers(
|
||||
ghttp.CombineHandlers(
|
||||
ghttp.VerifyRequest("GET", endpoint),
|
||||
ghttp.RespondWithJSONEncoded(http.StatusOK, License),
|
||||
),
|
||||
)
|
||||
|
||||
cmd := &viewCmd{
|
||||
host: elasticHost,
|
||||
port: elasticPort,
|
||||
authFile: ""}
|
||||
|
||||
exitStatus := cmd.Execute(nil, nil)
|
||||
|
||||
Expect(exitStatus).Should(Equal(subcommands.ExitSuccess))
|
||||
Expect(server.ReceivedRequests()).Should(HaveLen(1))
|
||||
})
|
||||
|
||||
It("should run with basic auth", func() {
|
||||
server.AppendHandlers(
|
||||
ghttp.CombineHandlers(
|
||||
ghttp.VerifyRequest("GET", endpoint),
|
||||
ghttp.RespondWithJSONEncoded(http.StatusOK, License),
|
||||
ghttp.VerifyBasicAuth(Username, Password),
|
||||
),
|
||||
)
|
||||
|
||||
authFile := createAuthFile()
|
||||
defer os.Remove(authFile)
|
||||
|
||||
cmd := &viewCmd{
|
||||
host: elasticHost,
|
||||
port: elasticPort,
|
||||
authFile: authFile}
|
||||
|
||||
exitStatus := cmd.Execute(nil, nil)
|
||||
|
||||
Expect(exitStatus).Should(Equal(subcommands.ExitSuccess))
|
||||
Expect(server.ReceivedRequests()).Should(HaveLen(1))
|
||||
})
|
||||
})
|
||||
})
|
Загрузка…
Ссылка в новой задаче