Add a tool which can be used to install the x-pack license in an Elasticsearch cluster

This commit is contained in:
Cosmin Cojocar 2018-03-15 13:32:52 +01:00
Родитель 2ea8ff9f52
Коммит 2fbcc30e83
10 изменённых файлов: 584 добавлений и 0 удалений

5
tools/elasticlicense/.gitignore поставляемый Normal file
Просмотреть файл

@ -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"
}

75
tools/elasticlicense/glide.lock сгенерированный Normal file
Просмотреть файл

@ -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))
})
})
})