Skip to content

Commit

Permalink
Add es subcommand
Browse files Browse the repository at this point in the history
Signed-off-by: Matthew DeVenny <[email protected]>
  • Loading branch information
matthewdevenny committed Dec 6, 2019
1 parent b703fce commit e4bc3a1
Show file tree
Hide file tree
Showing 4 changed files with 370 additions and 1 deletion.
73 changes: 72 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 3,11 @@

`dockcmd` is a tool providing a collection of [BoxOps](https://boxops.io) utility functions. Which can be used standalone or to accelerate CI/CD with BoxBoat's [dockhand](https://github.com/boxboat/dockhand).


***
## `aws`


AWS utilities are under the `aws` sub-command. For authentication, AWS commands make use of the standard AWS credentials providers and will check in order:

* Access Key/Secret key
Expand Down Expand Up @@ -43,10 46,78 @@ foo:
keyC: '<value-of-secret/foo-charlie-c-from-aws-secrets-manager>'
keyD: "<value-of-secret/root-d-from-aws-secrets-manager>"
```
***
## `azure`

Azure utilities are under the `azure` sub-command. For authentication, Azure commands make use of these flags and environment variables:

* Client Id/Client Secret
* Environment: `${AZURE_CLIENT_ID}` `${AZURE_CLIENT_SECRET}`
* Args: `--client-id <access-key>` `--client-secret <secret-key>`
* Tenant:
* Environment: `${AZURE_TENANT_ID}`
* Args: `--tenant <tenant-id>`

See `dockcmd azure --help` for more details on `azure` flags.

### `get-secrets`

Retrieve secrets stored as JSON from AWS Secrets Manager. Input files are defined using go templating and `dockcmd` supports sprig functions, as well as alternate template delimiters `<< >>` using `--use-alt-delims`. External values can be passed in using `--set key=value`

`dockcmd azure get-secrets --set TargetEnv=prod --input-file secret-values.yaml`

`secret-values.yaml`:
```
---
foo:
keyA: {{ (azureJson "foo" "a") | squote }}
keyB: {{ (azureJson "foo" "b") | squote }}
charlie:
keyC: {{ (azureJson "foo-charlie" "c") | squote }}
keyD: {{ (azureText "root" ) | quote }}
```

output:
```
foo:
keyA: '<value-of-secret/foo-a-frome-azure-key-vault>'
keyB: '<value-of-secret/foo-b-frome-azure-key-vault>'
charlie:
keyC: '<value-of-secret/foo-charlie-c-frome-azure-key-vault>'
keyD: "<value-of-secret/root-from-azure-key-vault>"
```

***
## `es`
Elasticsearch utilities are under the `es` sub-command. Currently supports Elasticsearch API major version v6 and v7. For authentication, `es` commands will use the environment or credentials passed in as arguments:

#### Basic Auth
`--username <username>` or `${ES_USERNAME}`
`--password <password>` or `${ES_PASSWORD}`

#### API Key
Note, if you set the `api-key` then it will override any Basic Auth parameters provided:
`--api-key <base64-encoded-auth-token>` or `${ES_API_KEY}`

#### No Auth
If authorization is not required, simply omit the above flags.

See `dockcmd es --help` for more details on `es` flags.

### `get-indices`
Retrieve indices from ES, output is json payload.

See `dockcmd es get-indices --help` for more details.

### `delete-indices`
Delete indices from ES.

See `dockcmd es delete-indices --help` for more details

***
## `vault`

Vault utilities are under the `aws` sub-command. For authentication, `vault` commands will use the environment or credentials passed in as arguments:
Vault utilities are under the `vault` sub-command. For authentication, `vault` commands will use the environment or credentials passed in as arguments:

`--vault-token <vault-token>` or `${VAULT_TOKEN}`
or
Expand Down
288 changes: 288 additions & 0 deletions cmd/elastic.go
Original file line number Diff line number Diff line change
@@ -0,0 1,288 @@
// Copyright © 2019 BoxBoat [email protected]
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"encoding/json"
"errors"
"fmt"
elasticsearch6 "github.com/elastic/go-elasticsearch/v6"
elasticsearch7 "github.com/elastic/go-elasticsearch/v7"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"strconv"
"time"
)

var (
esDryRun bool
esPrettyPrint bool
esURL string
esVersion string
esAPIKey string
esUsername string
esPassword string
esRetentionDays int
es6Client *elasticsearch6.Client
es7Client *elasticsearch7.Client
)

func getEs6Client() *elasticsearch6.Client {
Logger.Debugln("Using v6 client")
if es6Client == nil {
cfg := elasticsearch6.Config{
Addresses: []string{
esURL,
},
Username: esUsername,
Password: esPassword,
APIKey: esAPIKey,
}
Logger.Debugf("Username:[%s]", cfg.Username)
Logger.Debugf("Password:[%s]", cfg.Password)
Logger.Debugf("ApiKey:[%s]", cfg.APIKey)
var err error
es6Client, err = elasticsearch6.NewClient(cfg)
HandleError(err)
}
return es6Client
}

func getEs7Client() *elasticsearch7.Client {
Logger.Debugln("Using v7 client")
if es7Client == nil {
cfg := elasticsearch7.Config{
Addresses: []string{
esURL,
},
Username: esUsername,
Password: esPassword,
APIKey: esAPIKey,
}
Logger.Debugf("Username:[%s]", cfg.Username)
Logger.Debugf("Password:[%s]", cfg.Password)
Logger.Debugf("ApiKey:[%s]", cfg.APIKey)
var err error
es7Client, err = elasticsearch7.NewClient(cfg)
HandleError(err)
}
return es7Client
}

func deleteIndex(delete []string) {
if esVersion == "v6" {
esClient := getEs6Client()
esClient.Indices.Delete(delete)
} else if esVersion == "v7" {
esClient := getEs7Client()
esClient.Indices.Delete(delete)
}
}

func findIndices(search []string) map[string]interface{} {

var indices map[string]interface{}

if esVersion == "v6" {
esClient := getEs6Client()
response, err := esClient.Indices.Get(search)
HandleError(err)
if response.IsError() {
HandleError(
fmt.Errorf(
"Error: %s",
response.String()))
}
json.NewDecoder(response.Body).Decode(&indices)

} else if esVersion == "v7" {
esClient := getEs7Client()
response, err := esClient.Indices.Get(search)
HandleError(err)
if response.IsError() {
HandleError(
fmt.Errorf(
"Error: %s",
response.String()))
}
json.NewDecoder(response.Body).Decode(&indices)
}
return indices
}

var esDeleteIndicesCmd = &cobra.Command{
Use: "delete-indices",
Short: "Delete matching indices from Elasticsearch",
Long: `Provide an index name to delete from Elasticsearch`,
Run: func(cmd *cobra.Command, args []string) {
Logger.Debug("delete-indices called")

var search []string
if len(args) == 1 {
search = args
} else {
HandleError(errors.New("Provide delete string"))
}
Logger.Debugf("Deleting [%s] from elasticsearch", search)

indices := findIndices(search)

for k, v := range indices {
settings := v.(map[string]interface{})["settings"].(map[string]interface{})
index := settings["index"].(map[string]interface{})
creationDateMs, err := strconv.ParseInt(index["creation_date"].(string), 10, 64)
HandleError(err)

Logger.Debugf("key[%s] creationDate[%s]\n", k, index["creation_date"])

age := time.Now().Sub(time.Unix(0, creationDateMs*int64(time.Millisecond)))
Logger.Debugf("Age[%s]\n", age)
if age.Seconds() > float64(esRetentionDays*24.0*60.0*60.0) {
if esDryRun == false {
fmt.Printf("Deleting index [%s]\n", k)
deleteIndex([]string{k})
} else {
fmt.Printf("[%s] would be deleted\n", k)
}
}
}

},
}

var esGetIndicesCmd = &cobra.Command{
Use: "get-indices",
Short: "Retrieve list of matching indices from Elasticsearch",
Long: `Provide an index name to query Elasticsearch`,
Run: func(cmd *cobra.Command, args []string) {
Logger.Debug("get-indices called")

var search []string
if len(args) == 1 {
search = args
} else {
HandleError(errors.New("Provide search string"))
}
Logger.Debugf("Searching elasticsearch for [%s]", search)

indices := findIndices(search)
var out []byte
var err error
if esPrettyPrint {
out, err = json.MarshalIndent(indices, "", " ")
} else {
out, err = json.Marshal(indices)
}
HandleError(err)
fmt.Println(string(out))
},
}

// esCmdPersistentPreRunE checks required persistent tokens for esCmd
func esCmdPersistentPreRunE(cmd *cobra.Command, args []string) error {
if err := rootCmdPersistentPreRunE(cmd, args); err != nil {
return err
}
Logger.Debugln("esCmdPersistentPreRunE")
Logger.Debugf("Using ES URL: {%s}", esURL)
esURL = viper.GetString("url")
esUsername = viper.GetString("username")
esPassword = viper.GetString("password")
esAPIKey = viper.GetString("api-key")

if esURL == "" {
return errors.New("${ES_URL} must be set or passed in via --url")
}
return nil
}

// esCmd represents the es command
var esCmd = &cobra.Command{
Use: "es",
Short: "Elasticsearch Commands",
Long: `Commands designed to facilitate interactions with Elasticsearch`,
PersistentPreRunE: esCmdPersistentPreRunE,
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}

func init() {
rootCmd.AddCommand(esCmd)
esCmd.AddCommand(esGetIndicesCmd)
esCmd.AddCommand(esDeleteIndicesCmd)

esCmd.PersistentFlags().StringVarP(
&esURL,
"url",
"",
"",
"Elasticsearch URL can alternatively be set using ${ES_URL}")
viper.BindEnv("url", "ES_URL")

esCmd.PersistentFlags().StringVarP(
&esVersion,
"api-version",
"",
"",
"Elasticsearch major version, currently supports v6 and v7 e.g. --api-version v6")

esCmd.PersistentFlags().StringVarP(
&esUsername,
"username",
"",
"",
"Elasticsearch username for Basic Auth can alternatively be set using ${ES_USERNAME}")
viper.BindEnv("username", "ES_USERNAME")

esCmd.PersistentFlags().StringVarP(
&esPassword,
"password",
"",
"",
"Elasticsearch password for Basic Auth can alternatively be set using ${ES_PASSWORD}")
viper.BindEnv("password", "ES_PASSWORD")

esCmd.PersistentFlags().StringVarP(
&esAPIKey,
"api-key",
"",
"",
"Elasticsearch Base64 encoded authorization api token - will override Basic Auth can alternatively be set using ${ES_API_KEY}")
viper.BindEnv("api-key", "ES_API_KEY")

viper.BindPFlags(esCmd.PersistentFlags())

esGetIndicesCmd.Flags().BoolVarP(
&esPrettyPrint,
"pretty-print",
"",
false,
"Pretty print json output")

esDeleteIndicesCmd.Flags().IntVarP(
&esRetentionDays,
"retention-days",
"",
0,
"Days to retain indexes matching delete pattern")
esDeleteIndicesCmd.Flags().BoolVarP(
&esDryRun,
"dry-run",
"",
false,
"Enable dry run to see what indices would be deleted")

}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 12,8 @@ require (
github.com/Masterminds/sprig v2.20.0 incompatible

github.com/aws/aws-sdk-go v1.21.8
github.com/elastic/go-elasticsearch/v6 v6.8.5
github.com/elastic/go-elasticsearch/v7 v7.5.0

github.com/google/uuid v1.1.1 // indirect

Expand Down
Loading

0 comments on commit e4bc3a1

Please sign in to comment.