Skip to content

Commit

Permalink
Sync from server repo (6917f3b9030)
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Spilchen committed Nov 2, 2023
1 parent 82f2d75 commit e463d84
Show file tree
Hide file tree
Showing 83 changed files with 1,846 additions and 488 deletions.
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ linters-settings:
- $gostd
- github.com
- vertica.com
- k8s.io
dupl:
threshold: 100
funlen:
Expand Down
2 changes: 1 addition & 1 deletion commands/cluster_command_launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const defaultLogPath = "/opt/vertica/log/vcluster.log"
func MakeClusterCommandLauncher() (ClusterCommandLauncher, vclusterops.VClusterCommands) {
// setup logs for command launcher initialization
userCommandString := os.Args[1]
log := vlog.Printer{}
log := vlog.Printer{ForCli: true}
logPath := parseLogPathArg(os.Args, defaultLogPath)
log.SetupOrDie(logPath)
vcc := vclusterops.VClusterCommands{
Expand Down
4 changes: 2 additions & 2 deletions commands/cmd_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type CmdBase struct {
// convert a host string into a list of hosts,
// save the list into options.RawHosts;
// the hosts should be separated by comma, and will be converted to lower case
func (c *CmdBase) ParseHostList(options *vclusterops.DatabaseOptions) error {
func (c *CmdBase) parseHostList(options *vclusterops.DatabaseOptions) error {
inputHostList, err := util.SplitHosts(*c.hostListStr)
if err != nil {
return err
Expand Down Expand Up @@ -82,7 +82,7 @@ func (c *CmdBase) ValidateParseArgvHelper(commandType string) error {
func (c *CmdBase) ValidateParseBaseOptions(opt *vclusterops.DatabaseOptions) error {
if *opt.HonorUserInput {
// parse raw host str input into a []string
err := c.ParseHostList(opt)
err := c.parseHostList(opt)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion commands/cmd_list_all_nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (c *CmdListAllNodes) validateParse(log vlog.Printer) error {
log.Info("Called validateParse()")

// parse raw host str input into a []string
err := c.ParseHostList(&c.fetchNodeStateOptions.DatabaseOptions)
err := c.parseHostList(&c.fetchNodeStateOptions.DatabaseOptions)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion commands/cmd_re_ip.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (c *CmdReIP) validateParse(log vlog.Printer) error {
log.Info("Called validateParse()")

// parse raw host str input into a []string
err := c.ParseHostList(&c.reIPOptions.DatabaseOptions)
err := c.parseHostList(&c.reIPOptions.DatabaseOptions)
if err != nil {
return err
}
Expand Down
249 changes: 212 additions & 37 deletions commands/cmd_scrutinize.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,43 @@
package commands

import (
"context"
"errors"
"flag"
"fmt"
"os"
"strings"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"

"github.com/vertica/vcluster/vclusterops"
"github.com/vertica/vcluster/vclusterops/util"
"github.com/vertica/vcluster/vclusterops/vlog"
)

const (
// Environment variable names storing paths to PEM text.
secretNameSpaceEnvVar = "NMA_SECRET_NAMESPACE"
secretNameEnvVar = "NMA_SECRET_NAME"
)

const (
kubernetesPort = "KUBERNETES_PORT"
databaseName = "DATABASE_NAME"
catalogPathPref = "CATALOG_PATH"
)

// secretRetriever is an interface for retrieving secrets.
type secretRetriever interface {
RetrieveSecret(namespace, secretName string) ([]byte, []byte, []byte, error)
}

// k8sSecretRetrieverStruct is an implementation of secretRetriever.
type k8sSecretRetrieverStruct struct {
}

/* CmdScrutinize
*
* Implements ClusterCommand interface
Expand All @@ -34,77 +63,223 @@ import (
*/
type CmdScrutinize struct {
CmdBase
sOptions vclusterops.VScrutinizeOptions
k8secretRetreiver secretRetriever
sOptions vclusterops.VScrutinizeOptions
}

func makeCmdScrutinize() *CmdScrutinize {
newCmd := &CmdScrutinize{}
newCmd.parser = flag.NewFlagSet("scrutinize", flag.ExitOnError)
newCmd.sOptions = vclusterops.VScrutinizOptionsFactory()
newCmd.k8secretRetreiver = k8sSecretRetrieverStruct{}
// required flags
newCmd.sOptions.DBName = newCmd.parser.String("db-name", "", "The name of the database to run scrutinize. May be omitted on k8s.")

newCmd.hostListStr = newCmd.parser.String("hosts", "", "Comma-separated host list")

// Parse one variable and store it in a arbitrary location
newCmd.sOptions.Password = newCmd.parser.String("password",
"",
// optional flags
newCmd.sOptions.Password = newCmd.parser.String("password", "",
util.GetOptionalFlagMsg("Database password. Consider using in single quotes to avoid shell substitution."))

newCmd.sOptions.UserName = newCmd.parser.String("db-user",
"",
newCmd.sOptions.UserName = newCmd.parser.String("db-user", "",
util.GetOptionalFlagMsg("Database username. Consider using single quotes to avoid shell substitution."))

newCmd.sOptions.HonorUserInput = newCmd.parser.Bool("honor-user-input",
false,
newCmd.sOptions.HonorUserInput = newCmd.parser.Bool("honor-user-input", false,
util.GetOptionalFlagMsg("Forcefully use the user's input instead of reading the options from "+vclusterops.ConfigFileName))

newCmd.hostListStr = newCmd.parser.String("hosts", "", util.GetOptionalFlagMsg("Comma-separated host list"))
newCmd.sOptions.ConfigDirectory = newCmd.parser.String("config-directory", "",
util.GetOptionalFlagMsg("Directory where "+vclusterops.ConfigFileName+" is located"))

newCmd.ipv6 = newCmd.parser.Bool("ipv6", false, util.GetOptionalFlagMsg("Scrutinize database with IPv6 hosts"))

return newCmd
}

func (c *CmdScrutinize) CommandType() string {
return "scrutinize"
return vclusterops.VScrutinizeTypeName
}

func (c *CmdScrutinize) Parse(inputArgv []string, log vlog.Printer) error {
log.PrintInfo("Parsing scrutinize command input")
c.argv = inputArgv
// from now on we use the internal copy of argv
return c.parseInternal(log)
}
err := c.ValidateParseMaskedArgv(c.CommandType(), log)
if err != nil {
return err
}

func (c *CmdScrutinize) parseInternal(log vlog.Printer) error {
if c.parser == nil {
return fmt.Errorf("unexpected nil for CmdScrutinize.parser")
// for some options, we do not want to use their default values,
// if they are not provided in cli,
// reset the value of those options to nil
if !util.IsOptionSet(c.parser, "password") {
c.sOptions.Password = nil
}
log.PrintInfo("Parsing scrutinize command input")
parseError := c.ParseArgv()
if parseError != nil {
return parseError
if !util.IsOptionSet(c.parser, "config-directory") {
c.sOptions.ConfigDirectory = nil
}

log.Info("Parsing host list")
var hostParseError error
c.sOptions.RawHosts, hostParseError = util.SplitHosts(*c.hostListStr)
if hostParseError != nil {
return hostParseError
if !util.IsOptionSet(c.parser, "ipv6") {
c.CmdBase.ipv6 = nil
}
log.Info("Host list size and values", "size", len(c.sOptions.RawHosts), "values", c.sOptions.RawHosts)
return nil
// just so generic parsing works - not relevant for functionality
if !util.IsOptionSet(c.parser, "eon-mode") {
c.CmdBase.isEon = nil
}

// parses host list and ipv6 - eon is irrelevant but handled
return c.validateParse(log)
}

// all validations of the arguments should go in here
func (c *CmdScrutinize) validateParse(log vlog.Printer) error {
log.Info("Called validateParse()")
return c.ValidateParseBaseOptions(&c.sOptions.DatabaseOptions)
}

func (c *CmdScrutinize) Analyze(log vlog.Printer) error {
log.Info("Called method Analyze()")

var resolveError error
c.sOptions.Hosts, resolveError = util.ResolveRawHostsToAddresses(c.sOptions.RawHosts, false /*ipv6?*/)
if resolveError != nil {
return resolveError
// set cert/key values from env k8s
err := c.updateCertTextsFromk8s(log)
if err != nil {
return err
}

var allErrs error
port, found := os.LookupEnv(kubernetesPort)
if found && port != "" && *c.sOptions.HonorUserInput {
log.Info(kubernetesPort, " is set, k8s environment detected", found)
dbName, found := os.LookupEnv(databaseName)
if !found || dbName == "" {
allErrs = errors.Join(allErrs, fmt.Errorf("unable to get database name from environment variable. "))
} else {
c.sOptions.DBName = &dbName
log.Info("Setting database name from env as", "DBName", *c.sOptions.DBName)
}

catPrefix, found := os.LookupEnv(catalogPathPref)
if !found || catPrefix == "" {
allErrs = errors.Join(allErrs, fmt.Errorf("unable to get catalog path from environment variable. "))
} else {
c.sOptions.CatalogPrefix = &catPrefix
log.Info("Setting catalog path from env as", "CatalogPrefix", *c.sOptions.CatalogPrefix)
}
if allErrs != nil {
return allErrs
}
}
log.Info("Resolved host list to IPs", "hosts", c.sOptions.Hosts)
return nil
}

func (c *CmdScrutinize) Run(vcc vclusterops.VClusterCommands) error {
vcc.Log.PrintInfo("Running scrutinize")
vcc.Log.V(0).Info("Calling method Run() for command " + c.CommandType())
err := vcc.VScrutinize(&c.sOptions)
vcc.Log.PrintInfo("Completed method Run() for command " + c.CommandType())
vcc.Log.PrintInfo("Running scrutinize") // TODO remove when no longer needed for tests
vcc.Log.Info("Calling method Run()")

// get config from vertica_cluster.yaml (if exists)
config, err := c.sOptions.GetDBConfig(vcc)
if err != nil {
return err
}
c.sOptions.Config = config

err = vcc.VScrutinize(&c.sOptions)
if err != nil {
vcc.Log.Error(err, "scrutinize run failed")
return err
}
vcc.Log.PrintInfo("Successfully completed scrutinize run for the database %s", *c.sOptions.DBName)
return err
}

// RetrieveSecret retrieves a secret from Kubernetes and returns its data.
func (k8sSecretRetrieverStruct) RetrieveSecret(namespace, secretName string) (ca, cert, key []byte, err error) {
config, err := rest.InClusterConfig()
if err != nil {
return nil, nil, nil, err
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, nil, nil, err
}
secretsClient := clientset.CoreV1().Secrets(namespace)
certData, err := secretsClient.Get(context.Background(), secretName, metav1.GetOptions{})
if err != nil {
return nil, nil, nil, err
}
const (
CACertName = "ca.crt"
TLSCertName = "tls.crt"
TLSKeyName = "tls.key"
)
caCertVal, exists := certData.Data[CACertName]
if !exists {
return nil, nil, nil, fmt.Errorf("missing key %s in secret", CACertName)
}
tlsCertVal, exists := certData.Data[TLSCertName]
if !exists {
return nil, nil, nil, fmt.Errorf("missing key %s in secret", TLSCertName)
}
tlsKeyVal, exists := certData.Data[TLSKeyName]
if !exists {
return nil, nil, nil, fmt.Errorf("missing key %s in secret", TLSKeyName)
}
return caCertVal, tlsCertVal, tlsKeyVal, nil
}

// updateCertTextsFromk8s retrieves PEM-encoded text of CA certs, the server cert, and
// the server key from kubernetes.
func (c *CmdScrutinize) updateCertTextsFromk8s(log vlog.Printer) error {
_, portSet := os.LookupEnv(kubernetesPort)
if !portSet {
return nil
}
log.Info("K8s environment")
secretNameSpace, nameSpaceSet := os.LookupEnv(secretNameSpaceEnvVar)
secretName, nameSet := os.LookupEnv(secretNameEnvVar)

// either secret namespace/name must be set, or none at all
if !((nameSpaceSet && nameSet) || (!nameSpaceSet && !nameSet)) {
missingParamError := constructMissingParamsMsg([]bool{nameSpaceSet, nameSet}, []string{kubernetesPort,
secretNameSpaceEnvVar, secretNameEnvVar})
return fmt.Errorf("all or none of the environment variables %s and %s must be set. %s",
secretNameSpaceEnvVar, secretNameEnvVar, missingParamError)
}

if !nameSpaceSet {
log.Info("Secret name not set in env. Failback to other cert retieval methods.")
return nil
}

caCert, cert, key, err := c.k8secretRetreiver.RetrieveSecret(secretNameSpace, secretName)
if err != nil {
return fmt.Errorf("failed to read certs from k8s secret %s in namespace %s: %w", secretName, secretNameSpace, err)
}
if len(caCert) != 0 && len(cert) != 0 && len(key) != 0 {
log.Info("Successfully read cert from k8s secret ", "secretName", secretName, "secretNameSpace", secretNameSpace)
} else {
return fmt.Errorf("failed to read CA, cert or key (sizes = %d/%d/%d)",
len(caCert), len(cert), len(key))
}
c.sOptions.CaCert = string(caCert)
c.sOptions.Cert = string(cert)
c.sOptions.Key = string(key)

return nil
}

// constructMissingParamsMsg builds a warning string listing each
// param in params flagged as not existing in exists. The input
// slice order is assumed to match.
func constructMissingParamsMsg(exists []bool, params []string) string {
needComma := false
var warningBuilder strings.Builder
warningBuilder.WriteString("Missing parameters: ")
for paramIndex, paramExists := range exists {
if !paramExists {
if needComma {
warningBuilder.WriteString(", ")
}
warningBuilder.WriteString(params[paramIndex])
needComma = true
}
}
return warningBuilder.String()
}
Loading

0 comments on commit e463d84

Please sign in to comment.