internal/stack/shellinit.go (130 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.
package stack
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/shirou/gopsutil/v3/process"
"github.com/elastic/elastic-package/internal/environment"
"github.com/elastic/elastic-package/internal/logger"
"github.com/elastic/elastic-package/internal/profile"
)
// Environment variables describing the stack.
var (
ElasticsearchAPIKeyEnv = environment.WithElasticPackagePrefix("ELASTICSEARCH_API_KEY")
ElasticsearchHostEnv = environment.WithElasticPackagePrefix("ELASTICSEARCH_HOST")
ElasticsearchPasswordEnv = environment.WithElasticPackagePrefix("ELASTICSEARCH_PASSWORD")
ElasticsearchUsernameEnv = environment.WithElasticPackagePrefix("ELASTICSEARCH_USERNAME")
KibanaHostEnv = environment.WithElasticPackagePrefix("KIBANA_HOST")
CACertificateEnv = environment.WithElasticPackagePrefix("CA_CERT")
)
// AutodetectShell returns the detected shell used.
func AutodetectShell() string {
return detectShell()
}
// ShellInit method exposes environment variables that can be used for testing purposes.
func ShellInit(elasticStackProfile *profile.Profile, shellType string) (string, error) {
config, err := StackInitConfig(elasticStackProfile)
if err != nil {
return "", nil
}
return shellInitWithConfig(config, shellType)
}
func shellInitWithConfig(config *InitConfig, shellType string) (string, error) {
pattern, err := selectPattern(shellType)
if err != nil {
return "", fmt.Errorf("cannot get shell init template: %w", err)
}
template := genTemplate(pattern)
return template([]generatorEnvVar{
{ElasticsearchAPIKeyEnv, config.ElasticsearchAPIKey},
{ElasticsearchHostEnv, config.ElasticsearchHostPort},
{ElasticsearchUsernameEnv, config.ElasticsearchUsername},
{ElasticsearchPasswordEnv, config.ElasticsearchPassword},
{KibanaHostEnv, config.KibanaHostPort},
{CACertificateEnv, config.CACertificatePath},
}), nil
}
type generatorEnvVar struct {
name string
value string
}
const (
// shell init code for POSIX compliant shells.
// IEEE POSIX Shell and Tools portion of the IEEE POSIX specification (IEEE Standard 1003.1)
posixPattern = `export %s=%s`
// fish shell init code.
// fish shell is similar but not compliant to POSIX.
fishPattern = `set -x %s %s;`
// PowerShell init code.
// Output to be evaluated with `elastic-package stack shellinit | Invoke-Expression
powershellPattern = `$Env:%s="%s";`
)
func genTemplate(pattern string) func([]generatorEnvVar) string {
return func(vars []generatorEnvVar) string {
var builder strings.Builder
for i, v := range vars {
fmt.Fprintf(&builder, pattern, v.name, v.value)
if i < len(vars)-1 {
builder.WriteString("\n")
}
}
return builder.String()
}
}
// availableShellTypes list all available values for s in initTemplate
var availableShellTypes = []string{"bash", "dash", "fish", "sh", "zsh", "pwsh", "powershell"}
// SelectPattern returns the patterns to generate list of environment variables for each shell.
func selectPattern(s string) (string, error) {
switch s {
case "bash", "dash", "sh", "zsh":
return posixPattern, nil
case "fish":
return fishPattern, nil
case "pwsh", "powershell":
return powershellPattern, nil
default:
return "", errors.New("shell type is unknown, should be one of " + strings.Join(availableShellTypes, ", "))
}
}
// helpText returns the instrutions about how to set environment variables with shellinit
func helpText(shell string) string {
switch shell {
case "pwsh", "powershell":
return `elastic-package stack shellinit | Invoke-Expression`
default:
return `eval "$(elastic-package stack shellinit)"`
}
}
func getShellName(exe string) string {
shell := filepath.Base(exe)
cleanSuffixes := []string{
// Remove .exe extension from executable names present in Windows.
".exe",
// Remove " (deleted)", that can appear here if the shell process has been
// replaced by an upgrade in Linux while the terminal was open.
" (deleted)",
}
for _, suffix := range cleanSuffixes {
shell = strings.TrimSuffix(shell, suffix)
}
return shell
}
func detectShell() string {
shell, err := getParentShell()
if err != nil {
logger.Debugf("Failed to determine parent process info while detecting shell, will assume bash: %v", err)
return "bash"
}
return shell
}
func getParentShell() (string, error) {
ppid := os.Getppid()
p, err := process.NewProcess(int32(ppid))
if err != nil {
return "", err
}
exe, err := p.Exe()
if err != nil {
return "", err
}
shell := getShellName(exe)
if shell == "go" {
parent, err := p.Parent()
if err != nil {
return "", err
}
exe, err := parent.Exe()
if err != nil {
return "", err
}
return getShellName(exe), nil
}
return shell, nil
}