Skip to content

Commit 98a658e

Browse files
committed
Add options for API key in clients
1 parent 2fc5051 commit 98a658e

File tree

7 files changed

+114
-37
lines changed

7 files changed

+114
-37
lines changed

internal/elasticsearch/client.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type ClusterStateRequest = esapi.ClusterStateRequest
3535
// clientOptions are used to configure a client.
3636
type clientOptions struct {
3737
address string
38+
apiKey string
3839
username string
3940
password string
4041

@@ -47,6 +48,13 @@ type clientOptions struct {
4748

4849
type ClientOption func(*clientOptions)
4950

51+
// OptionWithAPIKey sets the API key to be used by the client for authentication.
52+
func OptionWithAPIKey(apiKey string) ClientOption {
53+
return func(opts *clientOptions) {
54+
opts.apiKey = apiKey
55+
}
56+
}
57+
5058
// OptionWithAddress sets the address to be used by the client.
5159
func OptionWithAddress(address string) ClientOption {
5260
return func(opts *clientOptions) {
@@ -109,6 +117,7 @@ func NewConfig(customOptions ...ClientOption) (elasticsearch.Config, error) {
109117

110118
config := elasticsearch.Config{
111119
Addresses: []string{options.address},
120+
APIKey: options.apiKey,
112121
Username: options.username,
113122
Password: options.password,
114123
}

internal/kibana/client.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var ErrUndefinedHost = errors.New("missing kibana host")
2727
// Client is responsible for exporting dashboards from Kibana.
2828
type Client struct {
2929
host string
30+
apiKey string
3031
username string
3132
password string
3233

@@ -94,6 +95,13 @@ func Address(address string) ClientOption {
9495
}
9596
}
9697

98+
// APIKey option sets the API key to be used by the client for authentication.
99+
func APIKey(apiKey string) ClientOption {
100+
return func(c *Client) {
101+
c.apiKey = apiKey
102+
}
103+
}
104+
97105
// TLSSkipVerify option disables TLS verification.
98106
func TLSSkipVerify() ClientOption {
99107
return func(c *Client) {
@@ -182,7 +190,11 @@ func (c *Client) newRequest(ctx context.Context, method, resourcePath string, re
182190
return nil, fmt.Errorf("could not create %v request to Kibana API resource: %s: %w", method, resourcePath, err)
183191
}
184192

185-
req.SetBasicAuth(c.username, c.password)
193+
if c.apiKey != "" {
194+
req.Header.Set("Authorization", "ApiKey "+c.apiKey)
195+
} else {
196+
req.SetBasicAuth(c.username, c.password)
197+
}
186198
req.Header.Add("content-type", "application/json")
187199
req.Header.Add("kbn-xsrf", install.DefaultStackVersion)
188200

internal/stack/clients.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
func NewElasticsearchClient(customOptions ...elasticsearch.ClientOption) (*elasticsearch.Client, error) {
2222
options := []elasticsearch.ClientOption{
2323
elasticsearch.OptionWithAddress(os.Getenv(ElasticsearchHostEnv)),
24+
elasticsearch.OptionWithAPIKey(os.Getenv(ElasticsearchAPIKeyEnv)),
2425
elasticsearch.OptionWithPassword(os.Getenv(ElasticsearchPasswordEnv)),
2526
elasticsearch.OptionWithUsername(os.Getenv(ElasticsearchUsernameEnv)),
2627
elasticsearch.OptionWithCertificateAuthority(os.Getenv(CACertificateEnv)),
@@ -58,6 +59,10 @@ func NewElasticsearchClientFromProfile(profile *profile.Profile, customOptions .
5859
elasticsearchHost = profileConfig.ElasticsearchHostPort
5960
logger.Debugf("Connecting with Elasticsearch host from current profile (profile: %s, host: %q)", profile.ProfileName, elasticsearchHost)
6061
}
62+
elasticsearchAPIKey, found := os.LookupEnv(ElasticsearchAPIKeyEnv)
63+
if !found {
64+
elasticsearchAPIKey = profileConfig.ElasticsearchAPIKey
65+
}
6166
elasticsearchPassword, found := os.LookupEnv(ElasticsearchPasswordEnv)
6267
if !found {
6368
elasticsearchPassword = profileConfig.ElasticsearchPassword
@@ -73,6 +78,7 @@ func NewElasticsearchClientFromProfile(profile *profile.Profile, customOptions .
7378

7479
options := []elasticsearch.ClientOption{
7580
elasticsearch.OptionWithAddress(elasticsearchHost),
81+
elasticsearch.OptionWithAPIKey(elasticsearchAPIKey),
7682
elasticsearch.OptionWithPassword(elasticsearchPassword),
7783
elasticsearch.OptionWithUsername(elasticsearchUsername),
7884
elasticsearch.OptionWithCertificateAuthority(caCertificate),
@@ -86,6 +92,7 @@ func NewElasticsearchClientFromProfile(profile *profile.Profile, customOptions .
8692
func NewKibanaClient(customOptions ...kibana.ClientOption) (*kibana.Client, error) {
8793
options := []kibana.ClientOption{
8894
kibana.Address(os.Getenv(KibanaHostEnv)),
95+
kibana.APIKey(os.Getenv(ElasticsearchAPIKeyEnv)),
8996
kibana.Password(os.Getenv(ElasticsearchPasswordEnv)),
9097
kibana.Username(os.Getenv(ElasticsearchUsernameEnv)),
9198
kibana.CertificateAuthority(os.Getenv(CACertificateEnv)),
@@ -123,6 +130,10 @@ func NewKibanaClientFromProfile(profile *profile.Profile, customOptions ...kiban
123130
kibanaHost = profileConfig.KibanaHostPort
124131
logger.Debugf("Connecting with Kibana host from current profile (profile: %s, host: %q)", profile.ProfileName, kibanaHost)
125132
}
133+
elasticsearchAPIKey, found := os.LookupEnv(ElasticsearchAPIKeyEnv)
134+
if !found {
135+
elasticsearchAPIKey = profileConfig.ElasticsearchAPIKey
136+
}
126137
elasticsearchPassword, found := os.LookupEnv(ElasticsearchPasswordEnv)
127138
if !found {
128139
elasticsearchPassword = profileConfig.ElasticsearchPassword
@@ -138,6 +149,7 @@ func NewKibanaClientFromProfile(profile *profile.Profile, customOptions ...kiban
138149

139150
options := []kibana.ClientOption{
140151
kibana.Address(kibanaHost),
152+
kibana.APIKey(elasticsearchAPIKey),
141153
kibana.Password(elasticsearchPassword),
142154
kibana.Username(elasticsearchUsername),
143155
kibana.CertificateAuthority(caCertificate),
@@ -158,5 +170,10 @@ func FindCACertificate(profile *profile.Profile) (string, error) {
158170
caCertPath = profileConfig.CACertificatePath
159171
}
160172

173+
// Avoid returning an empty certificate path, fallback to the default path.
174+
if caCertPath == "" {
175+
caCertPath = profile.Path(CACertificateFile)
176+
}
177+
161178
return caCertPath, nil
162179
}

internal/stack/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type Config struct {
2020
Provider string `json:"provider,omitempty"`
2121
Parameters map[string]string `json:"parameters,omitempty"`
2222

23+
ElasticsearchAPIKey string `json:"elasticsearch_api_key,omitempty"`
2324
ElasticsearchHost string `json:"elasticsearch_host,omitempty"`
2425
ElasticsearchUsername string `json:"elasticsearch_username,omitempty"`
2526
ElasticsearchPassword string `json:"elasticsearch_password,omitempty"`

internal/stack/initconfig.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
)
1010

1111
type InitConfig struct {
12+
ElasticsearchAPIKey string
1213
ElasticsearchHostPort string
1314
ElasticsearchUsername string
1415
ElasticsearchPassword string
@@ -23,6 +24,7 @@ func StackInitConfig(profile *profile.Profile) (*InitConfig, error) {
2324
}
2425

2526
return &InitConfig{
27+
ElasticsearchAPIKey: config.ElasticsearchAPIKey,
2628
ElasticsearchHostPort: config.ElasticsearchHost,
2729
ElasticsearchUsername: config.ElasticsearchUsername,
2830
ElasticsearchPassword: config.ElasticsearchPassword,

internal/stack/shellinit.go

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ import (
2020

2121
// Environment variables describing the stack.
2222
var (
23+
ElasticsearchAPIKeyEnv = environment.WithElasticPackagePrefix("ELASTICSEARCH_API_KEY")
2324
ElasticsearchHostEnv = environment.WithElasticPackagePrefix("ELASTICSEARCH_HOST")
24-
ElasticsearchUsernameEnv = environment.WithElasticPackagePrefix("ELASTICSEARCH_USERNAME")
2525
ElasticsearchPasswordEnv = environment.WithElasticPackagePrefix("ELASTICSEARCH_PASSWORD")
26+
ElasticsearchUsernameEnv = environment.WithElasticPackagePrefix("ELASTICSEARCH_USERNAME")
2627
KibanaHostEnv = environment.WithElasticPackagePrefix("KIBANA_HOST")
2728
CACertificateEnv = environment.WithElasticPackagePrefix("CA_CERT")
2829
)
@@ -38,60 +39,70 @@ func ShellInit(elasticStackProfile *profile.Profile, shellType string) (string,
3839
if err != nil {
3940
return "", nil
4041
}
42+
return shellInitWithConfig(config, shellType)
43+
}
4144

42-
// NOTE: to add new env vars, the template need to be adjusted
43-
t, err := initTemplate(shellType)
45+
func shellInitWithConfig(config *InitConfig, shellType string) (string, error) {
46+
pattern, err := selectPattern(shellType)
4447
if err != nil {
4548
return "", fmt.Errorf("cannot get shell init template: %w", err)
4649
}
4750

48-
return fmt.Sprintf(t,
49-
ElasticsearchHostEnv, config.ElasticsearchHostPort,
50-
ElasticsearchUsernameEnv, config.ElasticsearchUsername,
51-
ElasticsearchPasswordEnv, config.ElasticsearchPassword,
52-
KibanaHostEnv, config.KibanaHostPort,
53-
CACertificateEnv, config.CACertificatePath,
54-
), nil
51+
template := genTemplate(pattern)
52+
return template([]generatorEnvVar{
53+
{ElasticsearchAPIKeyEnv, config.ElasticsearchAPIKey},
54+
{ElasticsearchHostEnv, config.ElasticsearchHostPort},
55+
{ElasticsearchUsernameEnv, config.ElasticsearchUsername},
56+
{ElasticsearchPasswordEnv, config.ElasticsearchPassword},
57+
{KibanaHostEnv, config.KibanaHostPort},
58+
{CACertificateEnv, config.CACertificatePath},
59+
}), nil
60+
}
61+
62+
type generatorEnvVar struct {
63+
name string
64+
value string
5565
}
5666

5767
const (
5868
// shell init code for POSIX compliant shells.
5969
// IEEE POSIX Shell and Tools portion of the IEEE POSIX specification (IEEE Standard 1003.1)
60-
posixTemplate = `export %s=%s
61-
export %s=%s
62-
export %s=%s
63-
export %s=%s
64-
export %s=%s`
70+
posixPattern = `export %s=%s`
6571

6672
// fish shell init code.
6773
// fish shell is similar but not compliant to POSIX.
68-
fishTemplate = `set -x %s %s;
69-
set -x %s %s;
70-
set -x %s %s;
71-
set -x %s %s;
72-
set -x %s %s;`
74+
fishPattern = `set -x %s %s;`
7375

7476
// PowerShell init code.
7577
// Output to be evaluated with `elastic-package stack shellinit | Invoke-Expression
76-
powershellTemplate = `$Env:%s="%s";
77-
$Env:%s="%s";
78-
$Env:%s="%s";
79-
$Env:%s="%s";
80-
$Env:%s="%s";`
78+
powershellPattern = `$Env:%s="%s";`
8179
)
8280

81+
func genTemplate(pattern string) func([]generatorEnvVar) string {
82+
return func(vars []generatorEnvVar) string {
83+
var builder strings.Builder
84+
for i, v := range vars {
85+
fmt.Fprintf(&builder, pattern, v.name, v.value)
86+
if i < len(vars)-1 {
87+
builder.WriteString("\n")
88+
}
89+
}
90+
return builder.String()
91+
}
92+
}
93+
8394
// availableShellTypes list all available values for s in initTemplate
8495
var availableShellTypes = []string{"bash", "dash", "fish", "sh", "zsh", "pwsh", "powershell"}
8596

86-
// InitTemplate returns code templates for shell initialization
87-
func initTemplate(s string) (string, error) {
97+
// SelectPattern returns the patterns to generate list of environment variables for each shell.
98+
func selectPattern(s string) (string, error) {
8899
switch s {
89100
case "bash", "dash", "sh", "zsh":
90-
return posixTemplate, nil
101+
return posixPattern, nil
91102
case "fish":
92-
return fishTemplate, nil
103+
return fishPattern, nil
93104
case "pwsh", "powershell":
94-
return powershellTemplate, nil
105+
return powershellPattern, nil
95106
default:
96107
return "", errors.New("shell type is unknown, should be one of " + strings.Join(availableShellTypes, ", "))
97108
}

internal/stack/shellinit_internal_test.go renamed to internal/stack/shellinit_test.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,47 @@ func TestCodeTemplate(t *testing.T) {
2222
args args
2323
want string
2424
}{
25-
{"bash code template", args{"bash"}, posixTemplate},
26-
{"fish code template", args{"fish"}, fishTemplate},
27-
{"sh code template", args{"sh"}, posixTemplate},
28-
{"zsh code template", args{"zsh"}, posixTemplate},
25+
{"bash code template", args{"bash"}, posixPattern},
26+
{"fish code template", args{"fish"}, fishPattern},
27+
{"sh code template", args{"sh"}, posixPattern},
28+
{"zsh code template", args{"zsh"}, posixPattern},
29+
{"pwsh code template", args{"pwsh"}, powershellPattern},
30+
{"powershell code template", args{"powershell"}, powershellPattern},
2931
}
3032
for _, tt := range tests {
3133
t.Run(tt.name, func(t *testing.T) {
32-
if got, _ := initTemplate(tt.args.s); got != tt.want {
34+
if got, _ := selectPattern(tt.args.s); got != tt.want {
3335
t.Errorf("CodeTemplate() = %v, want %v", got, tt.want)
3436
}
3537
})
3638
}
3739
}
3840

41+
func TestShellInit(t *testing.T) {
42+
config := InitConfig{
43+
ElasticsearchHostPort: "https://elastic.example.com:9200",
44+
ElasticsearchUsername: "admin",
45+
ElasticsearchPassword: "secret",
46+
KibanaHostPort: "https://kibana.example.com:5601",
47+
}
48+
49+
expected := strings.TrimSpace(`
50+
export ELASTIC_PACKAGE_ELASTICSEARCH_API_KEY=
51+
export ELASTIC_PACKAGE_ELASTICSEARCH_HOST=https://elastic.example.com:9200
52+
export ELASTIC_PACKAGE_ELASTICSEARCH_USERNAME=admin
53+
export ELASTIC_PACKAGE_ELASTICSEARCH_PASSWORD=secret
54+
export ELASTIC_PACKAGE_KIBANA_HOST=https://kibana.example.com:5601
55+
export ELASTIC_PACKAGE_CA_CERT=
56+
`)
57+
58+
result, err := shellInitWithConfig(&config, "bash")
59+
require.NoError(t, err)
60+
61+
assert.Equal(t, expected, result)
62+
}
63+
3964
func TestCodeTemplate_wrongInput(t *testing.T) {
40-
_, err := initTemplate("invalid shell type")
65+
_, err := selectPattern("invalid shell type")
4166
assert.Error(t, err, "shell type is unknown, should be one of "+strings.Join(availableShellTypes, ", "))
4267
}
4368

0 commit comments

Comments
 (0)