Skip to content

Commit 488db0d

Browse files
Merge pull request #13 from boxboat/feature/elasticsearch
Add es subcommand
2 parents b703fce + e4bc3a1 commit 488db0d

4 files changed

Lines changed: 370 additions & 1 deletion

File tree

README.md

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33

44
`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).
55

6+
7+
***
68
## `aws`
79

10+
811
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:
912

1013
* Access Key/Secret key
@@ -43,10 +46,78 @@ foo:
4346
keyC: '<value-of-secret/foo-charlie-c-from-aws-secrets-manager>'
4447
keyD: "<value-of-secret/root-d-from-aws-secrets-manager>"
4548
```
49+
***
50+
## `azure`
51+
52+
Azure utilities are under the `azure` sub-command. For authentication, Azure commands make use of these flags and environment variables:
53+
54+
* Client Id/Client Secret
55+
* Environment: `${AZURE_CLIENT_ID}` `${AZURE_CLIENT_SECRET}`
56+
* Args: `--client-id <access-key>` `--client-secret <secret-key>`
57+
* Tenant:
58+
* Environment: `${AZURE_TENANT_ID}`
59+
* Args: `--tenant <tenant-id>`
60+
61+
See `dockcmd azure --help` for more details on `azure` flags.
62+
63+
### `get-secrets`
64+
65+
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`
66+
67+
`dockcmd azure get-secrets --set TargetEnv=prod --input-file secret-values.yaml`
68+
69+
`secret-values.yaml`:
70+
```
71+
---
72+
foo:
73+
keyA: {{ (azureJson "foo" "a") | squote }}
74+
keyB: {{ (azureJson "foo" "b") | squote }}
75+
charlie:
76+
keyC: {{ (azureJson "foo-charlie" "c") | squote }}
77+
keyD: {{ (azureText "root" ) | quote }}
78+
```
79+
80+
output:
81+
```
82+
foo:
83+
keyA: '<value-of-secret/foo-a-frome-azure-key-vault>'
84+
keyB: '<value-of-secret/foo-b-frome-azure-key-vault>'
85+
charlie:
86+
keyC: '<value-of-secret/foo-charlie-c-frome-azure-key-vault>'
87+
keyD: "<value-of-secret/root-from-azure-key-vault>"
88+
```
89+
90+
***
91+
## `es`
92+
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:
93+
94+
#### Basic Auth
95+
`--username <username>` or `${ES_USERNAME}`
96+
`--password <password>` or `${ES_PASSWORD}`
97+
98+
#### API Key
99+
Note, if you set the `api-key` then it will override any Basic Auth parameters provided:
100+
`--api-key <base64-encoded-auth-token>` or `${ES_API_KEY}`
101+
102+
#### No Auth
103+
If authorization is not required, simply omit the above flags.
104+
105+
See `dockcmd es --help` for more details on `es` flags.
106+
107+
### `get-indices`
108+
Retrieve indices from ES, output is json payload.
109+
110+
See `dockcmd es get-indices --help` for more details.
111+
112+
### `delete-indices`
113+
Delete indices from ES.
114+
115+
See `dockcmd es delete-indices --help` for more details
46116

117+
***
47118
## `vault`
48119

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

51122
`--vault-token <vault-token>` or `${VAULT_TOKEN}`
52123
or

cmd/elastic.go

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
// Copyright © 2019 BoxBoat engineering@boxboat.com
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cmd
16+
17+
import (
18+
"encoding/json"
19+
"errors"
20+
"fmt"
21+
elasticsearch6 "github.com/elastic/go-elasticsearch/v6"
22+
elasticsearch7 "github.com/elastic/go-elasticsearch/v7"
23+
"github.com/spf13/cobra"
24+
"github.com/spf13/viper"
25+
"strconv"
26+
"time"
27+
)
28+
29+
var (
30+
esDryRun bool
31+
esPrettyPrint bool
32+
esURL string
33+
esVersion string
34+
esAPIKey string
35+
esUsername string
36+
esPassword string
37+
esRetentionDays int
38+
es6Client *elasticsearch6.Client
39+
es7Client *elasticsearch7.Client
40+
)
41+
42+
func getEs6Client() *elasticsearch6.Client {
43+
Logger.Debugln("Using v6 client")
44+
if es6Client == nil {
45+
cfg := elasticsearch6.Config{
46+
Addresses: []string{
47+
esURL,
48+
},
49+
Username: esUsername,
50+
Password: esPassword,
51+
APIKey: esAPIKey,
52+
}
53+
Logger.Debugf("Username:[%s]", cfg.Username)
54+
Logger.Debugf("Password:[%s]", cfg.Password)
55+
Logger.Debugf("ApiKey:[%s]", cfg.APIKey)
56+
var err error
57+
es6Client, err = elasticsearch6.NewClient(cfg)
58+
HandleError(err)
59+
}
60+
return es6Client
61+
}
62+
63+
func getEs7Client() *elasticsearch7.Client {
64+
Logger.Debugln("Using v7 client")
65+
if es7Client == nil {
66+
cfg := elasticsearch7.Config{
67+
Addresses: []string{
68+
esURL,
69+
},
70+
Username: esUsername,
71+
Password: esPassword,
72+
APIKey: esAPIKey,
73+
}
74+
Logger.Debugf("Username:[%s]", cfg.Username)
75+
Logger.Debugf("Password:[%s]", cfg.Password)
76+
Logger.Debugf("ApiKey:[%s]", cfg.APIKey)
77+
var err error
78+
es7Client, err = elasticsearch7.NewClient(cfg)
79+
HandleError(err)
80+
}
81+
return es7Client
82+
}
83+
84+
func deleteIndex(delete []string) {
85+
if esVersion == "v6" {
86+
esClient := getEs6Client()
87+
esClient.Indices.Delete(delete)
88+
} else if esVersion == "v7" {
89+
esClient := getEs7Client()
90+
esClient.Indices.Delete(delete)
91+
}
92+
}
93+
94+
func findIndices(search []string) map[string]interface{} {
95+
96+
var indices map[string]interface{}
97+
98+
if esVersion == "v6" {
99+
esClient := getEs6Client()
100+
response, err := esClient.Indices.Get(search)
101+
HandleError(err)
102+
if response.IsError() {
103+
HandleError(
104+
fmt.Errorf(
105+
"Error: %s",
106+
response.String()))
107+
}
108+
json.NewDecoder(response.Body).Decode(&indices)
109+
110+
} else if esVersion == "v7" {
111+
esClient := getEs7Client()
112+
response, err := esClient.Indices.Get(search)
113+
HandleError(err)
114+
if response.IsError() {
115+
HandleError(
116+
fmt.Errorf(
117+
"Error: %s",
118+
response.String()))
119+
}
120+
json.NewDecoder(response.Body).Decode(&indices)
121+
}
122+
return indices
123+
}
124+
125+
var esDeleteIndicesCmd = &cobra.Command{
126+
Use: "delete-indices",
127+
Short: "Delete matching indices from Elasticsearch",
128+
Long: `Provide an index name to delete from Elasticsearch`,
129+
Run: func(cmd *cobra.Command, args []string) {
130+
Logger.Debug("delete-indices called")
131+
132+
var search []string
133+
if len(args) == 1 {
134+
search = args
135+
} else {
136+
HandleError(errors.New("Provide delete string"))
137+
}
138+
Logger.Debugf("Deleting [%s] from elasticsearch", search)
139+
140+
indices := findIndices(search)
141+
142+
for k, v := range indices {
143+
settings := v.(map[string]interface{})["settings"].(map[string]interface{})
144+
index := settings["index"].(map[string]interface{})
145+
creationDateMs, err := strconv.ParseInt(index["creation_date"].(string), 10, 64)
146+
HandleError(err)
147+
148+
Logger.Debugf("key[%s] creationDate[%s]\n", k, index["creation_date"])
149+
150+
age := time.Now().Sub(time.Unix(0, creationDateMs*int64(time.Millisecond)))
151+
Logger.Debugf("Age[%s]\n", age)
152+
if age.Seconds() > float64(esRetentionDays*24.0*60.0*60.0) {
153+
if esDryRun == false {
154+
fmt.Printf("Deleting index [%s]\n", k)
155+
deleteIndex([]string{k})
156+
} else {
157+
fmt.Printf("[%s] would be deleted\n", k)
158+
}
159+
}
160+
}
161+
162+
},
163+
}
164+
165+
var esGetIndicesCmd = &cobra.Command{
166+
Use: "get-indices",
167+
Short: "Retrieve list of matching indices from Elasticsearch",
168+
Long: `Provide an index name to query Elasticsearch`,
169+
Run: func(cmd *cobra.Command, args []string) {
170+
Logger.Debug("get-indices called")
171+
172+
var search []string
173+
if len(args) == 1 {
174+
search = args
175+
} else {
176+
HandleError(errors.New("Provide search string"))
177+
}
178+
Logger.Debugf("Searching elasticsearch for [%s]", search)
179+
180+
indices := findIndices(search)
181+
var out []byte
182+
var err error
183+
if esPrettyPrint {
184+
out, err = json.MarshalIndent(indices, "", " ")
185+
} else {
186+
out, err = json.Marshal(indices)
187+
}
188+
HandleError(err)
189+
fmt.Println(string(out))
190+
},
191+
}
192+
193+
// esCmdPersistentPreRunE checks required persistent tokens for esCmd
194+
func esCmdPersistentPreRunE(cmd *cobra.Command, args []string) error {
195+
if err := rootCmdPersistentPreRunE(cmd, args); err != nil {
196+
return err
197+
}
198+
Logger.Debugln("esCmdPersistentPreRunE")
199+
Logger.Debugf("Using ES URL: {%s}", esURL)
200+
esURL = viper.GetString("url")
201+
esUsername = viper.GetString("username")
202+
esPassword = viper.GetString("password")
203+
esAPIKey = viper.GetString("api-key")
204+
205+
if esURL == "" {
206+
return errors.New("${ES_URL} must be set or passed in via --url")
207+
}
208+
return nil
209+
}
210+
211+
// esCmd represents the es command
212+
var esCmd = &cobra.Command{
213+
Use: "es",
214+
Short: "Elasticsearch Commands",
215+
Long: `Commands designed to facilitate interactions with Elasticsearch`,
216+
PersistentPreRunE: esCmdPersistentPreRunE,
217+
Run: func(cmd *cobra.Command, args []string) {
218+
cmd.Help()
219+
},
220+
}
221+
222+
func init() {
223+
rootCmd.AddCommand(esCmd)
224+
esCmd.AddCommand(esGetIndicesCmd)
225+
esCmd.AddCommand(esDeleteIndicesCmd)
226+
227+
esCmd.PersistentFlags().StringVarP(
228+
&esURL,
229+
"url",
230+
"",
231+
"",
232+
"Elasticsearch URL can alternatively be set using ${ES_URL}")
233+
viper.BindEnv("url", "ES_URL")
234+
235+
esCmd.PersistentFlags().StringVarP(
236+
&esVersion,
237+
"api-version",
238+
"",
239+
"",
240+
"Elasticsearch major version, currently supports v6 and v7 e.g. --api-version v6")
241+
242+
esCmd.PersistentFlags().StringVarP(
243+
&esUsername,
244+
"username",
245+
"",
246+
"",
247+
"Elasticsearch username for Basic Auth can alternatively be set using ${ES_USERNAME}")
248+
viper.BindEnv("username", "ES_USERNAME")
249+
250+
esCmd.PersistentFlags().StringVarP(
251+
&esPassword,
252+
"password",
253+
"",
254+
"",
255+
"Elasticsearch password for Basic Auth can alternatively be set using ${ES_PASSWORD}")
256+
viper.BindEnv("password", "ES_PASSWORD")
257+
258+
esCmd.PersistentFlags().StringVarP(
259+
&esAPIKey,
260+
"api-key",
261+
"",
262+
"",
263+
"Elasticsearch Base64 encoded authorization api token - will override Basic Auth can alternatively be set using ${ES_API_KEY}")
264+
viper.BindEnv("api-key", "ES_API_KEY")
265+
266+
viper.BindPFlags(esCmd.PersistentFlags())
267+
268+
esGetIndicesCmd.Flags().BoolVarP(
269+
&esPrettyPrint,
270+
"pretty-print",
271+
"",
272+
false,
273+
"Pretty print json output")
274+
275+
esDeleteIndicesCmd.Flags().IntVarP(
276+
&esRetentionDays,
277+
"retention-days",
278+
"",
279+
0,
280+
"Days to retain indexes matching delete pattern")
281+
esDeleteIndicesCmd.Flags().BoolVarP(
282+
&esDryRun,
283+
"dry-run",
284+
"",
285+
false,
286+
"Enable dry run to see what indices would be deleted")
287+
288+
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ require (
1212
github.com/Masterminds/sprig v2.20.0+incompatible
1313

1414
github.com/aws/aws-sdk-go v1.21.8
15+
github.com/elastic/go-elasticsearch/v6 v6.8.5
16+
github.com/elastic/go-elasticsearch/v7 v7.5.0
1517

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

0 commit comments

Comments
 (0)