Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/types/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,12 @@ const (
// See also TeleportNamespace and TeleportInternalLabelPrefix.
TeleportHiddenLabelPrefix = "teleport.hidden/"

// DiscoveredNameLabel is a resource metadata label name used to identify
// the discovered name of a resource, i.e. the name of a resource before a
// uniquely distinguishing suffix is added by the discovery service.
// See: RFD 129 - Avoid Discovery Resource Name Collisions.
DiscoveredNameLabel = TeleportInternalLabelPrefix + "discovered-name"

// BotLabel is a label used to identify a resource used by a certificate renewal bot.
BotLabel = TeleportInternalLabelPrefix + "bot"

Expand Down
1 change: 1 addition & 0 deletions docs/pages/reference/predicate-language.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ The language also supports the following functions:
| `exists(labels["env"])` | resources with a label key `env`; label value unchecked |
| `!exists(labels["env"])` | resources without a label key `env`; label value unchecked |
| `search("foo", "bar", "some phrase")` | fuzzy match against common resource fields |
| `hasPrefix(name, "foo")` | resources with a name that starts with the prefix `foo` |

See some [examples](cli.mdx#filter-examples) of the different ways you can filter resources.

Expand Down
10 changes: 10 additions & 0 deletions lib/client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,16 @@ type Config struct {
// MockHeadlessLogin is used in tests for mocking the Headless login response.
MockHeadlessLogin SSHLoginFunc

// OverrideMySQLOptionFilePath overrides the MySQL option file path to use.
// Useful in parallel tests so they don't all use the default path in the
// user home dir.
OverrideMySQLOptionFilePath string

// OverridePostgresServiceFilePath overrides the Postgres service file path.
// Useful in parallel tests so they don't all use the default path in the
// user home dir.
OverridePostgresServiceFilePath string

// HomePath is where tsh stores profiles
HomePath string

Expand Down
11 changes: 10 additions & 1 deletion lib/client/db/dbcmd/dbcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ func (c *CLICommandBuilder) getMySQLOracleCommand() (*exec.Cmd, error) {
// We save configuration to ~/.my.cnf, but on Windows that file is not read,
// see tables 4.1 and 4.2 on https://dev.mysql.com/doc/refman/8.0/en/option-files.html.
// We instruct mysql client to use use that file with --defaults-extra-file.
configPath, err := mysql.DefaultConfigPath()
configPath, err := c.getMySQLOptionFilePath()
if err != nil {
return nil, trace.Wrap(err)
}
Expand All @@ -346,6 +346,15 @@ func (c *CLICommandBuilder) getMySQLOracleCommand() (*exec.Cmd, error) {
return exec.Command(mysqlBin, args...), nil
}

// getMySQLOptionFilePath gets the filepath to .my.cnf from the default location
// in ~/.my.cnf, unless overridden by config.
func (c *CLICommandBuilder) getMySQLOptionFilePath() (string, error) {
if c.tc.OverrideMySQLOptionFilePath != "" {
return c.tc.OverrideMySQLOptionFilePath, nil
}
return mysql.DefaultConfigPath()
}

// getMySQLCommand returns mariadb command if the binary is on the path. Otherwise,
// mysql command is returned. Both mysql versions (MariaDB and Oracle) are supported.
func (c *CLICommandBuilder) getMySQLCommand() (*exec.Cmd, error) {
Expand Down
4 changes: 3 additions & 1 deletion lib/client/db/mysql/optionfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ type OptionFile struct {
path string
}

// DefaultConfigPath returns the default config path, which is .my.cnf file in
// the user's home directory. Home dir is determined by environment if not
// supplied as an argument.
func DefaultConfigPath() (string, error) {
// Default location is .my.cnf file in the user's home directory.
home, err := os.UserHomeDir()
if err != nil || home == "" {
usr, err := utils.CurrentUser()
Expand Down
24 changes: 17 additions & 7 deletions lib/client/db/postgres/servicefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,33 @@ type ServiceFile struct {
path string
}

// Load loads Postgres connection service file from the default location.
func Load() (*ServiceFile, error) {
// DefaultConfigPath returns the default config path, which is .pg_service.conf
// file in the user's home directory.
func defaultConfigPath() (string, error) {
// Default location is .pg_service.conf file in the user's home directory.
// TODO(r0mant): Check PGSERVICEFILE and PGSYSCONFDIR env vars as well.
home, err := os.UserHomeDir()
if err != nil || home == "" {
user, err := utils.CurrentUser()
usr, err := utils.CurrentUser()
if err != nil {
return nil, trace.ConvertSystemError(err)
return "", trace.ConvertSystemError(err)
}
home = user.HomeDir
home = usr.HomeDir
}

return LoadFromPath(filepath.Join(home, pgServiceFile))
return filepath.Join(home, pgServiceFile), nil
}

// Load loads Postgres connection service file from the default location.
func Load() (*ServiceFile, error) {
cnfPath, err := defaultConfigPath()
if err != nil {
return nil, trace.Wrap(err)
}
return LoadFromPath(cnfPath)
}

// LoadFromPath loads Posrtgres connection service file from the specified path.
// LoadFromPath loads Postgres connection service file from the specified path.
func LoadFromPath(path string) (*ServiceFile, error) {
// Loose load will ignore file not found error.
iniFile, err := ini.LooseLoad(path)
Expand Down
14 changes: 10 additions & 4 deletions lib/client/db/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func Add(ctx context.Context, tc *client.TeleportClient, db tlsca.RouteToDatabas
if !IsSupported(db) {
return nil
}
profileFile, err := load(db)
profileFile, err := load(tc, db)
if err != nil {
return trace.Wrap(err)
}
Expand Down Expand Up @@ -98,7 +98,7 @@ func New(tc *client.TeleportClient, db tlsca.RouteToDatabase, clientProfile clie

// Env returns environment variables for the specified database profile.
func Env(tc *client.TeleportClient, db tlsca.RouteToDatabase) (map[string]string, error) {
profileFile, err := load(db)
profileFile, err := load(tc, db)
if err != nil {
return nil, trace.Wrap(err)
}
Expand All @@ -114,7 +114,7 @@ func Delete(tc *client.TeleportClient, db tlsca.RouteToDatabase) error {
if !IsSupported(db) {
return nil
}
profileFile, err := load(db)
profileFile, err := load(tc, db)
if err != nil {
return trace.Wrap(err)
}
Expand All @@ -138,11 +138,17 @@ func IsSupported(db tlsca.RouteToDatabase) bool {
}

// load loads the appropriate database connection profile.
func load(db tlsca.RouteToDatabase) (profile.ConnectProfileFile, error) {
func load(tc *client.TeleportClient, db tlsca.RouteToDatabase) (profile.ConnectProfileFile, error) {
switch db.Protocol {
case defaults.ProtocolPostgres:
if tc.OverridePostgresServiceFilePath != "" {
return postgres.LoadFromPath(tc.OverridePostgresServiceFilePath)
}
return postgres.Load()
case defaults.ProtocolMySQL:
if tc.OverrideMySQLOptionFilePath != "" {
return mysql.LoadFromPath(tc.OverrideMySQLOptionFilePath)
}
return mysql.Load()
}
return nil, trace.BadParameter("unsupported database protocol %q",
Expand Down
19 changes: 18 additions & 1 deletion lib/services/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,22 @@ func NewResourceParser(resource types.ResourceWithLabels) (BoolPredicateParser,
return predicate.Equals(a, b)
}
}
predPrefix := func(a interface{}, prefix string) predicate.BoolPredicate {
switch aval := a.(type) {
case label:
return func() bool {
return strings.HasPrefix(aval.value, prefix)
}
case string:
return func() bool {
return strings.HasPrefix(aval, prefix)
}
default:
return func() bool {
return false
}
}
}

p, err := predicate.NewParser(predicate.Def{
Operators: predicate.Operators{
Expand All @@ -753,7 +769,8 @@ func NewResourceParser(resource types.ResourceWithLabels) (BoolPredicateParser,
},
},
Functions: map[string]interface{}{
"equals": predEquals,
"hasPrefix": predPrefix,
"equals": predEquals,
// search allows fuzzy matching against select field values.
"search": func(searchVals ...string) predicate.BoolPredicate {
return func() bool {
Expand Down
11 changes: 11 additions & 0 deletions lib/services/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ func TestNewResourceParser(t *testing.T) {
`search("os", "mac", "prod")`,
`search()`,
`!search("_")`,
// Test hasPrefix.
`hasPrefix(name, "")`,
`hasPrefix(name, "test-h")`,
`!hasPrefix(name, "foo")`,
`hasPrefix(resource.metadata.labels["env"], "pro")`,
// Test exists.
`exists(labels.env)`,
`!exists(labels.undefined)`,
Expand All @@ -206,6 +211,7 @@ func TestNewResourceParser(t *testing.T) {
`labels.os == "mac" && name == "test-hostname" && search("v8")`,
`exists(labels.env) && labels["env"] != "qa"`,
`search("does", "not", "exist") || resource.spec.addr == "_" || labels.version == "v8"`,
`hasPrefix(labels.os, "m") && !hasPrefix(labels.env, "dev") && name == "test-hostname"`,
// Test operator precedence
`exists(labels.env) || (exists(labels.os) && labels.os != "mac")`,
`exists(labels.env) || exists(labels.os) && labels.os != "mac"`,
Expand Down Expand Up @@ -233,6 +239,7 @@ func TestNewResourceParser(t *testing.T) {
`equals(resource.metadata.labels["env"], "wrong-value")`,
`equals(resource.spec.hostname, "wrong-value")`,
`search("mac", "not-found")`,
`hasPrefix(name, "x")`,
}
for _, expr := range exprs {
t.Run(expr, func(t *testing.T) {
Expand Down Expand Up @@ -269,6 +276,10 @@ func TestNewResourceParser(t *testing.T) {
`exists(labels.env, "too", "many")`,
`search(1,2)`,
`"just-string"`,
`hasPrefix(1, 2)`,
`hasPrefix(name)`,
`hasPrefix(name, 1)`,
`hasPrefix(name, "too", "many")`,
"",
}
for _, expr := range exprs {
Expand Down
Loading