github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/database.go (about)

     1  /*
     2  Copyright 2021 Gravitational, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package types
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"log/slog"
    24  	"regexp"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/gravitational/trace"
    29  
    30  	"github.com/gravitational/teleport/api/types/compare"
    31  	"github.com/gravitational/teleport/api/utils"
    32  	atlasutils "github.com/gravitational/teleport/api/utils/atlas"
    33  	awsutils "github.com/gravitational/teleport/api/utils/aws"
    34  	azureutils "github.com/gravitational/teleport/api/utils/azure"
    35  )
    36  
    37  var _ compare.IsEqual[Database] = (*DatabaseV3)(nil)
    38  
    39  // Database represents a single database proxied by a database server.
    40  type Database interface {
    41  	// ResourceWithLabels provides common resource methods.
    42  	ResourceWithLabels
    43  	// GetNamespace returns the database namespace.
    44  	GetNamespace() string
    45  	// GetStaticLabels returns the database static labels.
    46  	GetStaticLabels() map[string]string
    47  	// SetStaticLabels sets the database static labels.
    48  	SetStaticLabels(map[string]string)
    49  	// GetDynamicLabels returns the database dynamic labels.
    50  	GetDynamicLabels() map[string]CommandLabel
    51  	// SetDynamicLabels sets the database dynamic labels.
    52  	SetDynamicLabels(map[string]CommandLabel)
    53  	// String returns string representation of the database.
    54  	String() string
    55  	// GetDescription returns the database description.
    56  	GetDescription() string
    57  	// GetProtocol returns the database protocol.
    58  	GetProtocol() string
    59  	// GetURI returns the database connection endpoint.
    60  	GetURI() string
    61  	// SetURI sets the database connection endpoint.
    62  	SetURI(string)
    63  	// GetCA returns the database CA certificate.
    64  	GetCA() string
    65  	// SetCA sets the database CA certificate in the Spec.TLS field.
    66  	SetCA(string)
    67  	// GetTLS returns the database TLS configuration.
    68  	GetTLS() DatabaseTLS
    69  	// SetStatusCA sets the database CA certificate in the status field.
    70  	SetStatusCA(string)
    71  	// GetStatusCA gets the database CA certificate in the status field.
    72  	GetStatusCA() string
    73  	// GetMySQL returns the database options from spec.
    74  	GetMySQL() MySQLOptions
    75  	// GetOracle returns the database options from spec.
    76  	GetOracle() OracleOptions
    77  	// GetMySQLServerVersion returns the MySQL server version either from configuration or
    78  	// reported by the database.
    79  	GetMySQLServerVersion() string
    80  	// SetMySQLServerVersion sets the runtime MySQL server version.
    81  	SetMySQLServerVersion(version string)
    82  	// GetAWS returns the database AWS metadata.
    83  	GetAWS() AWS
    84  	// SetStatusAWS sets the database AWS metadata in the status field.
    85  	SetStatusAWS(AWS)
    86  	// SetAWSExternalID sets the database AWS external ID in the Spec.AWS field.
    87  	SetAWSExternalID(id string)
    88  	// SetAWSAssumeRole sets the database AWS assume role arn in the Spec.AWS field.
    89  	SetAWSAssumeRole(roleARN string)
    90  	// GetGCP returns GCP information for Cloud SQL databases.
    91  	GetGCP() GCPCloudSQL
    92  	// GetAzure returns Azure database server metadata.
    93  	GetAzure() Azure
    94  	// SetStatusAzure sets the database Azure metadata in the status field.
    95  	SetStatusAzure(Azure)
    96  	// GetAD returns Active Directory database configuration.
    97  	GetAD() AD
    98  	// GetType returns the database authentication type: self-hosted, RDS, Redshift or Cloud SQL.
    99  	GetType() string
   100  	// GetSecretStore returns secret store configurations.
   101  	GetSecretStore() SecretStore
   102  	// GetManagedUsers returns a list of database users that are managed by Teleport.
   103  	GetManagedUsers() []string
   104  	// SetManagedUsers sets a list of database users that are managed by Teleport.
   105  	SetManagedUsers(users []string)
   106  	// GetMongoAtlas returns Mongo Atlas database metadata.
   107  	GetMongoAtlas() MongoAtlas
   108  	// IsRDS returns true if this is an RDS/Aurora database.
   109  	IsRDS() bool
   110  	// IsRDSProxy returns true if this is an RDS Proxy database.
   111  	IsRDSProxy() bool
   112  	// IsRedshift returns true if this is a Redshift database.
   113  	IsRedshift() bool
   114  	// IsCloudSQL returns true if this is a Cloud SQL database.
   115  	IsCloudSQL() bool
   116  	// IsAzure returns true if this is an Azure database.
   117  	IsAzure() bool
   118  	// IsElastiCache returns true if this is an AWS ElastiCache database.
   119  	IsElastiCache() bool
   120  	// IsMemoryDB returns true if this is an AWS MemoryDB database.
   121  	IsMemoryDB() bool
   122  	// IsAWSHosted returns true if database is hosted by AWS.
   123  	IsAWSHosted() bool
   124  	// IsCloudHosted returns true if database is hosted in the cloud (AWS, Azure or Cloud SQL).
   125  	IsCloudHosted() bool
   126  	// RequireAWSIAMRolesAsUsers returns true for database types that require
   127  	// AWS IAM roles as database users.
   128  	RequireAWSIAMRolesAsUsers() bool
   129  	// SupportAWSIAMRoleARNAsUsers returns true for database types that support
   130  	// AWS IAM roles as database users.
   131  	SupportAWSIAMRoleARNAsUsers() bool
   132  	// Copy returns a copy of this database resource.
   133  	Copy() *DatabaseV3
   134  	// GetAdminUser returns database privileged user information.
   135  	GetAdminUser() DatabaseAdminUser
   136  	// SupportsAutoUsers returns true if this database supports automatic
   137  	// user provisioning.
   138  	SupportsAutoUsers() bool
   139  	// GetEndpointType returns the endpoint type of the database, if available.
   140  	GetEndpointType() string
   141  	// GetCloud gets the cloud this database is running on, or an empty string if it
   142  	// isn't running on a cloud provider.
   143  	GetCloud() string
   144  }
   145  
   146  // NewDatabaseV3 creates a new database resource.
   147  func NewDatabaseV3(meta Metadata, spec DatabaseSpecV3) (*DatabaseV3, error) {
   148  	database := &DatabaseV3{
   149  		Metadata: meta,
   150  		Spec:     spec,
   151  	}
   152  	if err := database.CheckAndSetDefaults(); err != nil {
   153  		return nil, trace.Wrap(err)
   154  	}
   155  	return database, nil
   156  }
   157  
   158  // GetVersion returns the database resource version.
   159  func (d *DatabaseV3) GetVersion() string {
   160  	return d.Version
   161  }
   162  
   163  // GetKind returns the database resource kind.
   164  func (d *DatabaseV3) GetKind() string {
   165  	return d.Kind
   166  }
   167  
   168  // GetSubKind returns the database resource subkind.
   169  func (d *DatabaseV3) GetSubKind() string {
   170  	return d.SubKind
   171  }
   172  
   173  // SetSubKind sets the database resource subkind.
   174  func (d *DatabaseV3) SetSubKind(sk string) {
   175  	d.SubKind = sk
   176  }
   177  
   178  // GetResourceID returns the database resource ID.
   179  func (d *DatabaseV3) GetResourceID() int64 {
   180  	return d.Metadata.ID
   181  }
   182  
   183  // SetResourceID sets the database resource ID.
   184  func (d *DatabaseV3) SetResourceID(id int64) {
   185  	d.Metadata.ID = id
   186  }
   187  
   188  // GetRevision returns the revision
   189  func (d *DatabaseV3) GetRevision() string {
   190  	return d.Metadata.GetRevision()
   191  }
   192  
   193  // SetRevision sets the revision
   194  func (d *DatabaseV3) SetRevision(rev string) {
   195  	d.Metadata.SetRevision(rev)
   196  }
   197  
   198  // GetMetadata returns the database resource metadata.
   199  func (d *DatabaseV3) GetMetadata() Metadata {
   200  	return d.Metadata
   201  }
   202  
   203  // Origin returns the origin value of the resource.
   204  func (d *DatabaseV3) Origin() string {
   205  	return d.Metadata.Origin()
   206  }
   207  
   208  // SetOrigin sets the origin value of the resource.
   209  func (d *DatabaseV3) SetOrigin(origin string) {
   210  	d.Metadata.SetOrigin(origin)
   211  }
   212  
   213  // GetNamespace returns the database resource namespace.
   214  func (d *DatabaseV3) GetNamespace() string {
   215  	return d.Metadata.Namespace
   216  }
   217  
   218  // SetExpiry sets the database resource expiration time.
   219  func (d *DatabaseV3) SetExpiry(expiry time.Time) {
   220  	d.Metadata.SetExpiry(expiry)
   221  }
   222  
   223  // Expiry returns the database resource expiration time.
   224  func (d *DatabaseV3) Expiry() time.Time {
   225  	return d.Metadata.Expiry()
   226  }
   227  
   228  // GetName returns the database resource name.
   229  func (d *DatabaseV3) GetName() string {
   230  	return d.Metadata.Name
   231  }
   232  
   233  // SetName sets the database resource name.
   234  func (d *DatabaseV3) SetName(name string) {
   235  	d.Metadata.Name = name
   236  }
   237  
   238  // GetStaticLabels returns the database static labels.
   239  func (d *DatabaseV3) GetStaticLabels() map[string]string {
   240  	return d.Metadata.Labels
   241  }
   242  
   243  // SetStaticLabels sets the database static labels.
   244  func (d *DatabaseV3) SetStaticLabels(sl map[string]string) {
   245  	d.Metadata.Labels = sl
   246  }
   247  
   248  // GetDynamicLabels returns the database dynamic labels.
   249  func (d *DatabaseV3) GetDynamicLabels() map[string]CommandLabel {
   250  	if d.Spec.DynamicLabels == nil {
   251  		return nil
   252  	}
   253  	return V2ToLabels(d.Spec.DynamicLabels)
   254  }
   255  
   256  // SetDynamicLabels sets the database dynamic labels
   257  func (d *DatabaseV3) SetDynamicLabels(dl map[string]CommandLabel) {
   258  	d.Spec.DynamicLabels = LabelsToV2(dl)
   259  }
   260  
   261  // GetLabel retrieves the label with the provided key. If not found
   262  // value will be empty and ok will be false.
   263  func (d *DatabaseV3) GetLabel(key string) (value string, ok bool) {
   264  	if cmd, ok := d.Spec.DynamicLabels[key]; ok {
   265  		return cmd.Result, ok
   266  	}
   267  
   268  	v, ok := d.Metadata.Labels[key]
   269  	return v, ok
   270  }
   271  
   272  // GetAllLabels returns the database combined static and dynamic labels.
   273  func (d *DatabaseV3) GetAllLabels() map[string]string {
   274  	return CombineLabels(d.Metadata.Labels, d.Spec.DynamicLabels)
   275  }
   276  
   277  // GetDescription returns the database description.
   278  func (d *DatabaseV3) GetDescription() string {
   279  	return d.Metadata.Description
   280  }
   281  
   282  // GetProtocol returns the database protocol.
   283  func (d *DatabaseV3) GetProtocol() string {
   284  	return d.Spec.Protocol
   285  }
   286  
   287  // GetURI returns the database connection address.
   288  func (d *DatabaseV3) GetURI() string {
   289  	return d.Spec.URI
   290  }
   291  
   292  // SetURI sets the database connection address.
   293  func (d *DatabaseV3) SetURI(uri string) {
   294  	d.Spec.URI = uri
   295  }
   296  
   297  // GetAdminUser returns database privileged user information.
   298  func (d *DatabaseV3) GetAdminUser() (ret DatabaseAdminUser) {
   299  	// First check the spec.
   300  	if d.Spec.AdminUser != nil {
   301  		ret = *d.Spec.AdminUser
   302  	}
   303  
   304  	// If it's not in the spec, check labels (for auto-discovered databases).
   305  	// TODO Azure will require different labels.
   306  	if d.Origin() == OriginCloud {
   307  		if ret.Name == "" {
   308  			ret.Name = d.Metadata.Labels[DatabaseAdminLabel]
   309  		}
   310  		if ret.DefaultDatabase == "" {
   311  			ret.DefaultDatabase = d.Metadata.Labels[DatabaseAdminDefaultDatabaseLabel]
   312  		}
   313  	}
   314  	return
   315  }
   316  
   317  // GetOracle returns the Oracle options from spec.
   318  func (d *DatabaseV3) GetOracle() OracleOptions {
   319  	return d.Spec.Oracle
   320  }
   321  
   322  // SupportsAutoUsers returns true if this database supports automatic user
   323  // provisioning.
   324  func (d *DatabaseV3) SupportsAutoUsers() bool {
   325  	switch d.GetProtocol() {
   326  	case DatabaseProtocolPostgreSQL:
   327  		switch d.GetType() {
   328  		case DatabaseTypeSelfHosted, DatabaseTypeRDS, DatabaseTypeRedshift:
   329  			return true
   330  		}
   331  	case DatabaseProtocolMySQL:
   332  		switch d.GetType() {
   333  		case DatabaseTypeSelfHosted, DatabaseTypeRDS:
   334  			return true
   335  		}
   336  
   337  	case DatabaseProtocolMongoDB:
   338  		switch d.GetType() {
   339  		case DatabaseTypeSelfHosted:
   340  			return true
   341  		}
   342  	}
   343  	return false
   344  }
   345  
   346  // GetCA returns the database CA certificate. If more than one CA is set, then
   347  // the user provided CA is returned first (Spec field).
   348  // Auto-downloaded CA certificate is returned otherwise.
   349  func (d *DatabaseV3) GetCA() string {
   350  	if d.Spec.TLS.CACert != "" {
   351  		return d.Spec.TLS.CACert
   352  	}
   353  	if d.Spec.CACert != "" {
   354  		return d.Spec.CACert
   355  	}
   356  	return d.Status.CACert
   357  }
   358  
   359  // SetCA sets the database CA certificate in the Spec.TLS.CACert field.
   360  func (d *DatabaseV3) SetCA(caCert string) {
   361  	d.Spec.TLS.CACert = caCert
   362  }
   363  
   364  // GetTLS returns Database TLS configuration.
   365  func (d *DatabaseV3) GetTLS() DatabaseTLS {
   366  	return d.Spec.TLS
   367  }
   368  
   369  // SetStatusCA sets the database CA certificate in the status field.
   370  func (d *DatabaseV3) SetStatusCA(ca string) {
   371  	d.Status.CACert = ca
   372  }
   373  
   374  // GetStatusCA gets the database CA certificate in the status field.
   375  func (d *DatabaseV3) GetStatusCA() string {
   376  	return d.Status.CACert
   377  }
   378  
   379  // GetMySQL returns the MySQL options from spec.
   380  func (d *DatabaseV3) GetMySQL() MySQLOptions {
   381  	return d.Spec.MySQL
   382  }
   383  
   384  // GetMySQLServerVersion returns the MySQL server version reported by the database or the value from configuration
   385  // if the first one is not available.
   386  func (d *DatabaseV3) GetMySQLServerVersion() string {
   387  	if d.Status.MySQL.ServerVersion != "" {
   388  		return d.Status.MySQL.ServerVersion
   389  	}
   390  
   391  	return d.Spec.MySQL.ServerVersion
   392  }
   393  
   394  // SetMySQLServerVersion sets the runtime MySQL server version.
   395  func (d *DatabaseV3) SetMySQLServerVersion(version string) {
   396  	d.Status.MySQL.ServerVersion = version
   397  }
   398  
   399  // IsEmpty returns true if AWS metadata is empty.
   400  func (a AWS) IsEmpty() bool {
   401  	return protoKnownFieldsEqual(&a, &AWS{})
   402  }
   403  
   404  // Partition returns the AWS partition based on the region.
   405  func (a AWS) Partition() string {
   406  	return awsutils.GetPartitionFromRegion(a.Region)
   407  }
   408  
   409  // GetAWS returns the database AWS metadata.
   410  func (d *DatabaseV3) GetAWS() AWS {
   411  	if !d.Status.AWS.IsEmpty() {
   412  		return d.Status.AWS
   413  	}
   414  	return d.Spec.AWS
   415  }
   416  
   417  // SetStatusAWS sets the database AWS metadata in the status field.
   418  func (d *DatabaseV3) SetStatusAWS(aws AWS) {
   419  	d.Status.AWS = aws
   420  }
   421  
   422  // SetAWSExternalID sets the database AWS external ID in the Spec.AWS field.
   423  func (d *DatabaseV3) SetAWSExternalID(id string) {
   424  	d.Spec.AWS.ExternalID = id
   425  }
   426  
   427  // SetAWSAssumeRole sets the database AWS assume role arn in the Spec.AWS field.
   428  func (d *DatabaseV3) SetAWSAssumeRole(roleARN string) {
   429  	d.Spec.AWS.AssumeRoleARN = roleARN
   430  }
   431  
   432  // GetGCP returns GCP information for Cloud SQL databases.
   433  func (d *DatabaseV3) GetGCP() GCPCloudSQL {
   434  	return d.Spec.GCP
   435  }
   436  
   437  // IsEmpty returns true if Azure metadata is empty.
   438  func (a Azure) IsEmpty() bool {
   439  	return protoKnownFieldsEqual(&a, &Azure{})
   440  }
   441  
   442  // GetAzure returns Azure database server metadata.
   443  func (d *DatabaseV3) GetAzure() Azure {
   444  	if !d.Status.Azure.IsEmpty() {
   445  		return d.Status.Azure
   446  	}
   447  	return d.Spec.Azure
   448  }
   449  
   450  // SetStatusAzure sets the database Azure metadata in the status field.
   451  func (d *DatabaseV3) SetStatusAzure(azure Azure) {
   452  	d.Status.Azure = azure
   453  }
   454  
   455  // GetAD returns Active Directory database configuration.
   456  func (d *DatabaseV3) GetAD() AD {
   457  	return d.Spec.AD
   458  }
   459  
   460  // IsRDS returns true if this is an AWS RDS/Aurora instance.
   461  func (d *DatabaseV3) IsRDS() bool {
   462  	return d.GetType() == DatabaseTypeRDS
   463  }
   464  
   465  // IsRDSProxy returns true if this is an AWS RDS Proxy database.
   466  func (d *DatabaseV3) IsRDSProxy() bool {
   467  	return d.GetType() == DatabaseTypeRDSProxy
   468  }
   469  
   470  // IsRedshift returns true if this is a Redshift database instance.
   471  func (d *DatabaseV3) IsRedshift() bool {
   472  	return d.GetType() == DatabaseTypeRedshift
   473  }
   474  
   475  // IsCloudSQL returns true if this database is a Cloud SQL instance.
   476  func (d *DatabaseV3) IsCloudSQL() bool {
   477  	return d.GetType() == DatabaseTypeCloudSQL
   478  }
   479  
   480  // IsAzure returns true if this is Azure hosted database.
   481  func (d *DatabaseV3) IsAzure() bool {
   482  	return d.GetType() == DatabaseTypeAzure
   483  }
   484  
   485  // IsElastiCache returns true if this is an AWS ElastiCache database.
   486  func (d *DatabaseV3) IsElastiCache() bool {
   487  	return d.GetType() == DatabaseTypeElastiCache
   488  }
   489  
   490  // IsMemoryDB returns true if this is an AWS MemoryDB database.
   491  func (d *DatabaseV3) IsMemoryDB() bool {
   492  	return d.GetType() == DatabaseTypeMemoryDB
   493  }
   494  
   495  // IsAWSKeyspaces returns true if this is an AWS hosted Cassandra database.
   496  func (d *DatabaseV3) IsAWSKeyspaces() bool {
   497  	return d.GetType() == DatabaseTypeAWSKeyspaces
   498  }
   499  
   500  // IsDynamoDB returns true if this is an AWS hosted DynamoDB database.
   501  func (d *DatabaseV3) IsDynamoDB() bool {
   502  	return d.GetType() == DatabaseTypeDynamoDB
   503  }
   504  
   505  // IsOpenSearch returns true if this is an AWS hosted OpenSearch instance.
   506  func (d *DatabaseV3) IsOpenSearch() bool {
   507  	return d.GetType() == DatabaseTypeOpenSearch
   508  }
   509  
   510  // IsAWSHosted returns true if database is hosted by AWS.
   511  func (d *DatabaseV3) IsAWSHosted() bool {
   512  	_, ok := d.getAWSType()
   513  	return ok
   514  }
   515  
   516  // IsCloudHosted returns true if database is hosted in the cloud (AWS, Azure or
   517  // Cloud SQL).
   518  func (d *DatabaseV3) IsCloudHosted() bool {
   519  	return d.IsAWSHosted() || d.IsCloudSQL() || d.IsAzure()
   520  }
   521  
   522  // GetCloud gets the cloud this database is running on, or an empty string if it
   523  // isn't running on a cloud provider.
   524  func (d *DatabaseV3) GetCloud() string {
   525  	switch {
   526  	case d.IsAWSHosted():
   527  		return CloudAWS
   528  	case d.IsCloudSQL():
   529  		return CloudGCP
   530  	case d.IsAzure():
   531  		return CloudAzure
   532  	default:
   533  		return ""
   534  	}
   535  }
   536  
   537  // getAWSType returns the database type.
   538  func (d *DatabaseV3) getAWSType() (string, bool) {
   539  	aws := d.GetAWS()
   540  	switch d.Spec.Protocol {
   541  	case DatabaseTypeCassandra:
   542  		if !aws.IsEmpty() {
   543  			return DatabaseTypeAWSKeyspaces, true
   544  		}
   545  	case DatabaseTypeDynamoDB:
   546  		return DatabaseTypeDynamoDB, true
   547  	case DatabaseTypeOpenSearch:
   548  		return DatabaseTypeOpenSearch, true
   549  	}
   550  	if aws.Redshift.ClusterID != "" {
   551  		return DatabaseTypeRedshift, true
   552  	}
   553  	if aws.RedshiftServerless.WorkgroupName != "" || aws.RedshiftServerless.EndpointName != "" {
   554  		return DatabaseTypeRedshiftServerless, true
   555  	}
   556  	if aws.ElastiCache.ReplicationGroupID != "" {
   557  		return DatabaseTypeElastiCache, true
   558  	}
   559  	if aws.MemoryDB.ClusterName != "" {
   560  		return DatabaseTypeMemoryDB, true
   561  	}
   562  	if aws.RDSProxy.Name != "" || aws.RDSProxy.CustomEndpointName != "" {
   563  		return DatabaseTypeRDSProxy, true
   564  	}
   565  	if aws.Region != "" || aws.RDS.InstanceID != "" || aws.RDS.ResourceID != "" || aws.RDS.ClusterID != "" {
   566  		return DatabaseTypeRDS, true
   567  	}
   568  	return "", false
   569  }
   570  
   571  // GetType returns the database type.
   572  func (d *DatabaseV3) GetType() string {
   573  	if d.GetMongoAtlas().Name != "" {
   574  		return DatabaseTypeMongoAtlas
   575  	}
   576  
   577  	if awsType, ok := d.getAWSType(); ok {
   578  		return awsType
   579  	}
   580  
   581  	if d.GetGCP().ProjectID != "" {
   582  		return DatabaseTypeCloudSQL
   583  	}
   584  	if d.GetAzure().Name != "" {
   585  		return DatabaseTypeAzure
   586  	}
   587  
   588  	return DatabaseTypeSelfHosted
   589  }
   590  
   591  // String returns the database string representation.
   592  func (d *DatabaseV3) String() string {
   593  	return fmt.Sprintf("Database(Name=%v, Type=%v, Labels=%v)",
   594  		d.GetName(), d.GetType(), d.GetAllLabels())
   595  }
   596  
   597  // Copy returns a copy of this database resource.
   598  func (d *DatabaseV3) Copy() *DatabaseV3 {
   599  	return utils.CloneProtoMsg(d)
   600  }
   601  
   602  // MatchSearch goes through select field values and tries to
   603  // match against the list of search values.
   604  func (d *DatabaseV3) MatchSearch(values []string) bool {
   605  	fieldVals := append(utils.MapToStrings(d.GetAllLabels()), d.GetName(), d.GetDescription(), d.GetProtocol(), d.GetType())
   606  
   607  	var custom func(string) bool
   608  	switch d.GetType() {
   609  	case DatabaseTypeCloudSQL:
   610  		custom = func(val string) bool {
   611  			return strings.EqualFold(val, "cloud") || strings.EqualFold(val, "cloud sql")
   612  		}
   613  	}
   614  
   615  	return MatchSearch(fieldVals, values, custom)
   616  }
   617  
   618  // setStaticFields sets static resource header and metadata fields.
   619  func (d *DatabaseV3) setStaticFields() {
   620  	d.Kind = KindDatabase
   621  	d.Version = V3
   622  }
   623  
   624  // validDatabaseNameRegexp filters the allowed characters in database names.
   625  // This is the (almost) the same regexp used to check for valid DNS 1035 labels,
   626  // except we allow uppercase chars.
   627  var validDatabaseNameRegexp = regexp.MustCompile(`^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$`)
   628  
   629  // ValidateDatabaseName returns an error if a given string is not a valid
   630  // Database name.
   631  // Unlike application access proxy, database name doesn't necessarily
   632  // need to be a valid subdomain but use the same validation logic for the
   633  // simplicity and consistency, except two differences: don't restrict names to
   634  // 63 chars in length and allow upper case chars.
   635  func ValidateDatabaseName(name string) error {
   636  	return ValidateResourceName(validDatabaseNameRegexp, name)
   637  }
   638  
   639  // CheckAndSetDefaults checks and sets default values for any missing fields.
   640  func (d *DatabaseV3) CheckAndSetDefaults() error {
   641  	d.setStaticFields()
   642  	if err := d.Metadata.CheckAndSetDefaults(); err != nil {
   643  		return trace.Wrap(err)
   644  	}
   645  
   646  	if err := ValidateDatabaseName(d.GetName()); err != nil {
   647  		return trace.Wrap(err, "invalid database name")
   648  	}
   649  
   650  	for key := range d.Spec.DynamicLabels {
   651  		if !IsValidLabelKey(key) {
   652  			return trace.BadParameter("database %q invalid label key: %q", d.GetName(), key)
   653  		}
   654  	}
   655  	if d.Spec.Protocol == "" {
   656  		return trace.BadParameter("database %q protocol is empty", d.GetName())
   657  	}
   658  	if d.Spec.URI == "" {
   659  		switch d.GetType() {
   660  		case DatabaseTypeAWSKeyspaces:
   661  			if d.Spec.AWS.Region != "" {
   662  				// In case of AWS Hosted Cassandra allow to omit URI.
   663  				// The URL will be constructed from the database resource based on the region and account ID.
   664  				d.Spec.URI = awsutils.CassandraEndpointURLForRegion(d.Spec.AWS.Region)
   665  			} else {
   666  				return trace.BadParameter("AWS Keyspaces database %q URI is empty and cannot be derived without a configured AWS region",
   667  					d.GetName())
   668  			}
   669  		case DatabaseTypeDynamoDB:
   670  			if d.Spec.AWS.Region != "" {
   671  				d.Spec.URI = awsutils.DynamoDBURIForRegion(d.Spec.AWS.Region)
   672  			} else {
   673  				return trace.BadParameter("DynamoDB database %q URI is empty and cannot be derived without a configured AWS region",
   674  					d.GetName())
   675  			}
   676  		default:
   677  			return trace.BadParameter("database %q URI is empty", d.GetName())
   678  		}
   679  	}
   680  	if d.Spec.MySQL.ServerVersion != "" && d.Spec.Protocol != "mysql" {
   681  		return trace.BadParameter("database %q MySQL ServerVersion can be only set for MySQL database",
   682  			d.GetName())
   683  	}
   684  
   685  	// In case of RDS, Aurora or Redshift, AWS information such as region or
   686  	// cluster ID can be extracted from the endpoint if not provided.
   687  	switch {
   688  	case d.IsDynamoDB():
   689  		if err := d.handleDynamoDBConfig(); err != nil {
   690  			return trace.Wrap(err)
   691  		}
   692  	case d.IsOpenSearch():
   693  		if err := d.handleOpenSearchConfig(); err != nil {
   694  			return trace.Wrap(err)
   695  		}
   696  	case awsutils.IsRDSEndpoint(d.Spec.URI):
   697  		details, err := awsutils.ParseRDSEndpoint(d.Spec.URI)
   698  		if err != nil {
   699  			slog.WarnContext(context.Background(), "Failed to parse RDS endpoint.", "uri", d.Spec.URI, "error", err)
   700  			break
   701  		}
   702  		if d.Spec.AWS.RDS.InstanceID == "" {
   703  			d.Spec.AWS.RDS.InstanceID = details.InstanceID
   704  		}
   705  		if d.Spec.AWS.RDS.ClusterID == "" {
   706  			d.Spec.AWS.RDS.ClusterID = details.ClusterID
   707  		}
   708  		if d.Spec.AWS.RDSProxy.Name == "" {
   709  			d.Spec.AWS.RDSProxy.Name = details.ProxyName
   710  		}
   711  		if d.Spec.AWS.RDSProxy.CustomEndpointName == "" {
   712  			d.Spec.AWS.RDSProxy.CustomEndpointName = details.ProxyCustomEndpointName
   713  		}
   714  		if d.Spec.AWS.Region == "" {
   715  			d.Spec.AWS.Region = details.Region
   716  		}
   717  		if details.ClusterCustomEndpointName != "" && d.Spec.AWS.RDS.ClusterID == "" {
   718  			return trace.BadParameter("database %q missing RDS ClusterID for RDS Aurora custom endpoint %v",
   719  				d.GetName(), d.Spec.URI)
   720  		}
   721  	case awsutils.IsRedshiftEndpoint(d.Spec.URI):
   722  		clusterID, region, err := awsutils.ParseRedshiftEndpoint(d.Spec.URI)
   723  		if err != nil {
   724  			return trace.Wrap(err)
   725  		}
   726  		if d.Spec.AWS.Redshift.ClusterID == "" {
   727  			d.Spec.AWS.Redshift.ClusterID = clusterID
   728  		}
   729  		if d.Spec.AWS.Region == "" {
   730  			d.Spec.AWS.Region = region
   731  		}
   732  	case awsutils.IsRedshiftServerlessEndpoint(d.Spec.URI):
   733  		details, err := awsutils.ParseRedshiftServerlessEndpoint(d.Spec.URI)
   734  		if err != nil {
   735  			slog.WarnContext(context.Background(), "Failed to parse Redshift Serverless endpoint.", "uri", d.Spec.URI, "error", err)
   736  			break
   737  		}
   738  		if d.Spec.AWS.RedshiftServerless.WorkgroupName == "" {
   739  			d.Spec.AWS.RedshiftServerless.WorkgroupName = details.WorkgroupName
   740  		}
   741  		if d.Spec.AWS.RedshiftServerless.EndpointName == "" {
   742  			d.Spec.AWS.RedshiftServerless.EndpointName = details.EndpointName
   743  		}
   744  		if d.Spec.AWS.AccountID == "" {
   745  			d.Spec.AWS.AccountID = details.AccountID
   746  		}
   747  		if d.Spec.AWS.Region == "" {
   748  			d.Spec.AWS.Region = details.Region
   749  		}
   750  	case awsutils.IsElastiCacheEndpoint(d.Spec.URI):
   751  		endpointInfo, err := awsutils.ParseElastiCacheEndpoint(d.Spec.URI)
   752  		if err != nil {
   753  			slog.WarnContext(context.Background(), "Failed to parse ElastiCache endpoint", "uri", d.Spec.URI, "error", err)
   754  			break
   755  		}
   756  		if d.Spec.AWS.ElastiCache.ReplicationGroupID == "" {
   757  			d.Spec.AWS.ElastiCache.ReplicationGroupID = endpointInfo.ID
   758  		}
   759  		if d.Spec.AWS.Region == "" {
   760  			d.Spec.AWS.Region = endpointInfo.Region
   761  		}
   762  		d.Spec.AWS.ElastiCache.TransitEncryptionEnabled = endpointInfo.TransitEncryptionEnabled
   763  		d.Spec.AWS.ElastiCache.EndpointType = endpointInfo.EndpointType
   764  	case awsutils.IsMemoryDBEndpoint(d.Spec.URI):
   765  		endpointInfo, err := awsutils.ParseMemoryDBEndpoint(d.Spec.URI)
   766  		if err != nil {
   767  			slog.WarnContext(context.Background(), "Failed to parse MemoryDB endpoint", "uri", d.Spec.URI, "error", err)
   768  			break
   769  		}
   770  		if d.Spec.AWS.MemoryDB.ClusterName == "" {
   771  			d.Spec.AWS.MemoryDB.ClusterName = endpointInfo.ID
   772  		}
   773  		if d.Spec.AWS.Region == "" {
   774  			d.Spec.AWS.Region = endpointInfo.Region
   775  		}
   776  		d.Spec.AWS.MemoryDB.TLSEnabled = endpointInfo.TransitEncryptionEnabled
   777  		d.Spec.AWS.MemoryDB.EndpointType = endpointInfo.EndpointType
   778  
   779  	case azureutils.IsDatabaseEndpoint(d.Spec.URI):
   780  		// For Azure MySQL and PostgresSQL.
   781  		name, err := azureutils.ParseDatabaseEndpoint(d.Spec.URI)
   782  		if err != nil {
   783  			return trace.Wrap(err)
   784  		}
   785  		if d.Spec.Azure.Name == "" {
   786  			d.Spec.Azure.Name = name
   787  		}
   788  	case awsutils.IsKeyspacesEndpoint(d.Spec.URI):
   789  		if d.Spec.AWS.AccountID == "" {
   790  			return trace.BadParameter("database %q AWS account ID is empty",
   791  				d.GetName())
   792  		}
   793  		if d.Spec.AWS.Region == "" {
   794  			switch {
   795  			case d.IsAWSKeyspaces():
   796  				region, err := awsutils.CassandraEndpointRegion(d.Spec.URI)
   797  				if err != nil {
   798  					return trace.Wrap(err)
   799  				}
   800  				d.Spec.AWS.Region = region
   801  			default:
   802  				return trace.BadParameter("database %q AWS region is empty",
   803  					d.GetName())
   804  			}
   805  		}
   806  	case azureutils.IsCacheForRedisEndpoint(d.Spec.URI):
   807  		// ResourceID is required for fetching Redis tokens.
   808  		if d.Spec.Azure.ResourceID == "" {
   809  			return trace.BadParameter("database %q Azure resource ID is empty",
   810  				d.GetName())
   811  		}
   812  
   813  		name, err := azureutils.ParseCacheForRedisEndpoint(d.Spec.URI)
   814  		if err != nil {
   815  			return trace.Wrap(err)
   816  		}
   817  
   818  		if d.Spec.Azure.Name == "" {
   819  			d.Spec.Azure.Name = name
   820  		}
   821  	case azureutils.IsMSSQLServerEndpoint(d.Spec.URI):
   822  		if d.Spec.Azure.Name == "" {
   823  			name, err := azureutils.ParseMSSQLEndpoint(d.Spec.URI)
   824  			if err != nil {
   825  				return trace.Wrap(err)
   826  			}
   827  			d.Spec.Azure.Name = name
   828  		}
   829  	case atlasutils.IsAtlasEndpoint(d.Spec.URI):
   830  		name, err := atlasutils.ParseAtlasEndpoint(d.Spec.URI)
   831  		if err != nil {
   832  			return trace.Wrap(err)
   833  		}
   834  		d.Spec.MongoAtlas.Name = name
   835  	}
   836  
   837  	// Validate AWS Specific configuration
   838  	if d.Spec.AWS.AccountID != "" {
   839  		if err := awsutils.IsValidAccountID(d.Spec.AWS.AccountID); err != nil {
   840  			return trace.BadParameter("database %q has invalid AWS account ID: %v",
   841  				d.GetName(), err)
   842  		}
   843  	}
   844  
   845  	if d.Spec.AWS.ExternalID != "" && d.Spec.AWS.AssumeRoleARN == "" && !d.RequireAWSIAMRolesAsUsers() {
   846  		// Databases that use database username to assume an IAM role do not
   847  		// need assume_role_arn in configuration when external_id is set.
   848  		return trace.BadParameter("AWS database %q has external_id %q, but assume_role_arn is empty",
   849  			d.GetName(), d.Spec.AWS.ExternalID)
   850  	}
   851  
   852  	// Validate Cloud SQL specific configuration.
   853  	switch {
   854  	case d.Spec.GCP.ProjectID != "" && d.Spec.GCP.InstanceID == "":
   855  		return trace.BadParameter("database %q missing Cloud SQL instance ID",
   856  			d.GetName())
   857  	case d.Spec.GCP.ProjectID == "" && d.Spec.GCP.InstanceID != "":
   858  		return trace.BadParameter("database %q missing Cloud SQL project ID",
   859  			d.GetName())
   860  	}
   861  
   862  	// Admin user should only be specified for databases that support automatic
   863  	// user provisioning.
   864  	if d.GetAdminUser().Name != "" && !d.SupportsAutoUsers() {
   865  		return trace.BadParameter("cannot set admin user on database %q: %v/%v databases don't support automatic user provisioning yet",
   866  			d.GetName(), d.GetProtocol(), d.GetType())
   867  	}
   868  
   869  	switch protocol := d.GetProtocol(); protocol {
   870  	case DatabaseProtocolClickHouseHTTP, DatabaseProtocolClickHouse:
   871  		const (
   872  			clickhouseNativeSchema = "clickhouse"
   873  			clickhouseHTTPSchema   = "https"
   874  		)
   875  		parts := strings.Split(d.GetURI(), ":")
   876  		if len(parts) == 3 {
   877  			break
   878  		} else if len(parts) != 2 {
   879  			return trace.BadParameter("invalid ClickHouse URL %s", d.GetURI())
   880  		}
   881  
   882  		if !strings.HasPrefix(d.Spec.URI, clickhouseHTTPSchema) && protocol == DatabaseProtocolClickHouseHTTP {
   883  			d.Spec.URI = fmt.Sprintf("%s://%s", clickhouseHTTPSchema, d.Spec.URI)
   884  		}
   885  		if protocol == DatabaseProtocolClickHouse {
   886  			d.Spec.URI = fmt.Sprintf("%s://%s", clickhouseNativeSchema, d.Spec.URI)
   887  		}
   888  	}
   889  
   890  	return nil
   891  }
   892  
   893  // IsEqual determines if two database resources are equivalent to one another.
   894  func (d *DatabaseV3) IsEqual(i Database) bool {
   895  	if other, ok := i.(*DatabaseV3); ok {
   896  		return deriveTeleportEqualDatabaseV3(d, other)
   897  	}
   898  	return false
   899  }
   900  
   901  // handleDynamoDBConfig handles DynamoDB configuration checking.
   902  func (d *DatabaseV3) handleDynamoDBConfig() error {
   903  	if d.Spec.AWS.AccountID == "" {
   904  		return trace.BadParameter("database %q AWS account ID is empty", d.GetName())
   905  	}
   906  
   907  	info, err := awsutils.ParseDynamoDBEndpoint(d.Spec.URI)
   908  	switch {
   909  	case err != nil:
   910  		// when region parsing returns an error but the region is set, it's ok because we can just construct the URI using the region,
   911  		// so we check if the region is configured to see if this is really a configuration error.
   912  		if d.Spec.AWS.Region == "" {
   913  			// the AWS region is empty and we can't derive it from the URI, so this is a config error.
   914  			return trace.BadParameter("database %q AWS region is empty and cannot be derived from the URI %q",
   915  				d.GetName(), d.Spec.URI)
   916  		}
   917  		if awsutils.IsAWSEndpoint(d.Spec.URI) {
   918  			// The user configured an AWS URI that doesn't look like a DynamoDB endpoint.
   919  			// The URI must look like <service>.<region>.<partition> or <region>.<partition>
   920  			return trace.Wrap(err)
   921  		}
   922  	case d.Spec.AWS.Region == "":
   923  		// if the AWS region is empty we can just use the region extracted from the URI.
   924  		d.Spec.AWS.Region = info.Region
   925  	case d.Spec.AWS.Region != info.Region:
   926  		// if the AWS region is not empty but doesn't match the URI, this may indicate a user configuration mistake.
   927  		return trace.BadParameter("database %q AWS region %q does not match the configured URI region %q,"+
   928  			" omit the URI and it will be derived automatically for the configured AWS region",
   929  			d.GetName(), d.Spec.AWS.Region, info.Region)
   930  	}
   931  
   932  	if d.Spec.URI == "" {
   933  		d.Spec.URI = awsutils.DynamoDBURIForRegion(d.Spec.AWS.Region)
   934  	}
   935  	return nil
   936  }
   937  
   938  // handleOpenSearchConfig handles OpenSearch configuration checks.
   939  func (d *DatabaseV3) handleOpenSearchConfig() error {
   940  	if d.Spec.AWS.AccountID == "" {
   941  		return trace.BadParameter("database %q AWS account ID is empty", d.GetName())
   942  	}
   943  
   944  	info, err := awsutils.ParseOpensearchEndpoint(d.Spec.URI)
   945  	switch {
   946  	case err != nil:
   947  		// parsing the endpoint can return an error, especially if the custom endpoint feature is in use.
   948  		// this is fine as long as we have the region explicitly configured.
   949  		if d.Spec.AWS.Region == "" {
   950  			// the AWS region is empty, and we can't derive it from the URI, so this is a config error.
   951  			return trace.BadParameter("database %q AWS region is missing and cannot be derived from the URI %q",
   952  				d.GetName(), d.Spec.URI)
   953  		}
   954  		if awsutils.IsAWSEndpoint(d.Spec.URI) {
   955  			// The user configured an AWS URI that doesn't look like a OpenSearch endpoint.
   956  			// The URI must look like: <region>.<service>.<partition>.
   957  			return trace.Wrap(err)
   958  		}
   959  	case d.Spec.AWS.Region == "":
   960  		// if the AWS region is empty we can just use the region extracted from the URI.
   961  		d.Spec.AWS.Region = info.Region
   962  	case d.Spec.AWS.Region != info.Region:
   963  		// if the AWS region is not empty but doesn't match the URI, this may indicate a user configuration mistake.
   964  		return trace.BadParameter("database %q AWS region %q does not match the configured URI region %q,"+
   965  			" omit the URI and it will be derived automatically for the configured AWS region",
   966  			d.GetName(), d.Spec.AWS.Region, info.Region)
   967  	}
   968  
   969  	return nil
   970  }
   971  
   972  // GetSecretStore returns secret store configurations.
   973  func (d *DatabaseV3) GetSecretStore() SecretStore {
   974  	return d.Spec.AWS.SecretStore
   975  }
   976  
   977  // GetManagedUsers returns a list of database users that are managed by Teleport.
   978  func (d *DatabaseV3) GetManagedUsers() []string {
   979  	return d.Status.ManagedUsers
   980  }
   981  
   982  // SetManagedUsers sets a list of database users that are managed by Teleport.
   983  func (d *DatabaseV3) SetManagedUsers(users []string) {
   984  	d.Status.ManagedUsers = users
   985  }
   986  
   987  // GetMongoAtlas returns Mongo Atlas database metadata.
   988  func (d *DatabaseV3) GetMongoAtlas() MongoAtlas {
   989  	return d.Spec.MongoAtlas
   990  }
   991  
   992  // RequireAWSIAMRolesAsUsers returns true for database types that require AWS
   993  // IAM roles as database users.
   994  // IMPORTANT: if you add a database that requires AWS IAM Roles as users,
   995  // and that database supports discovery, be sure to update RequireAWSIAMRolesAsUsersMatchers
   996  // in matchers_aws.go as well.
   997  func (d *DatabaseV3) RequireAWSIAMRolesAsUsers() bool {
   998  	awsType, ok := d.getAWSType()
   999  	if !ok {
  1000  		return false
  1001  	}
  1002  
  1003  	switch awsType {
  1004  	case DatabaseTypeAWSKeyspaces,
  1005  		DatabaseTypeDynamoDB,
  1006  		DatabaseTypeOpenSearch,
  1007  		DatabaseTypeRedshiftServerless:
  1008  		return true
  1009  	default:
  1010  		return false
  1011  	}
  1012  }
  1013  
  1014  // SupportAWSIAMRoleARNAsUsers returns true for database types that support AWS
  1015  // IAM roles as database users.
  1016  func (d *DatabaseV3) SupportAWSIAMRoleARNAsUsers() bool {
  1017  	switch d.GetType() {
  1018  	// Note that databases in this list use IAM auth when:
  1019  	// - the database user is a full AWS role ARN role
  1020  	// - or the database user starts with "role/"
  1021  	//
  1022  	// Other database users will fallback to default auth methods (e.g X.509 for
  1023  	// MongoAtlas, regular auth token for Redshift).
  1024  	//
  1025  	// Therefore it is important to make sure "/" is an invalid character for
  1026  	// regular in-database usernames so that "role/" can be differentiated from
  1027  	// regular usernames.
  1028  	case DatabaseTypeMongoAtlas,
  1029  		DatabaseTypeRedshift:
  1030  		return true
  1031  	default:
  1032  		return false
  1033  	}
  1034  }
  1035  
  1036  // GetEndpointType returns the endpoint type of the database, if available.
  1037  func (d *DatabaseV3) GetEndpointType() string {
  1038  	if endpointType, ok := d.GetStaticLabels()[DiscoveryLabelEndpointType]; ok {
  1039  		return endpointType
  1040  	}
  1041  	switch d.GetType() {
  1042  	case DatabaseTypeElastiCache:
  1043  		return d.GetAWS().ElastiCache.EndpointType
  1044  	case DatabaseTypeMemoryDB:
  1045  		return d.GetAWS().MemoryDB.EndpointType
  1046  	case DatabaseTypeOpenSearch:
  1047  		return d.GetAWS().OpenSearch.EndpointType
  1048  	case DatabaseTypeRDS:
  1049  		// If not available from discovery tags, get the endpoint type from the
  1050  		// URL.
  1051  		if details, err := awsutils.ParseRDSEndpoint(d.GetURI()); err == nil {
  1052  			return details.EndpointType
  1053  		}
  1054  	}
  1055  	return ""
  1056  }
  1057  
  1058  const (
  1059  	// DatabaseProtocolPostgreSQL is the PostgreSQL database protocol.
  1060  	DatabaseProtocolPostgreSQL = "postgres"
  1061  	// DatabaseProtocolClickHouseHTTP is the ClickHouse database HTTP protocol.
  1062  	DatabaseProtocolClickHouseHTTP = "clickhouse-http"
  1063  	// DatabaseProtocolClickHouse is the ClickHouse database native write protocol.
  1064  	DatabaseProtocolClickHouse = "clickhouse"
  1065  	// DatabaseProtocolMySQL is the MySQL database protocol.
  1066  	DatabaseProtocolMySQL = "mysql"
  1067  	// DatabaseProtocolMongoDB is the MongoDB database protocol.
  1068  	DatabaseProtocolMongoDB = "mongodb"
  1069  
  1070  	// DatabaseTypeSelfHosted is the self-hosted type of database.
  1071  	DatabaseTypeSelfHosted = "self-hosted"
  1072  	// DatabaseTypeRDS is AWS-hosted RDS or Aurora database.
  1073  	DatabaseTypeRDS = "rds"
  1074  	// DatabaseTypeRDSProxy is an AWS-hosted RDS Proxy.
  1075  	DatabaseTypeRDSProxy = "rdsproxy"
  1076  	// DatabaseTypeRedshift is AWS Redshift database.
  1077  	DatabaseTypeRedshift = "redshift"
  1078  	// DatabaseTypeRedshiftServerless is AWS Redshift Serverless database.
  1079  	DatabaseTypeRedshiftServerless = "redshift-serverless"
  1080  	// DatabaseTypeCloudSQL is GCP-hosted Cloud SQL database.
  1081  	DatabaseTypeCloudSQL = "gcp"
  1082  	// DatabaseTypeAzure is Azure-hosted database.
  1083  	DatabaseTypeAzure = "azure"
  1084  	// DatabaseTypeElastiCache is AWS-hosted ElastiCache database.
  1085  	DatabaseTypeElastiCache = "elasticache"
  1086  	// DatabaseTypeMemoryDB is AWS-hosted MemoryDB database.
  1087  	DatabaseTypeMemoryDB = "memorydb"
  1088  	// DatabaseTypeAWSKeyspaces is AWS-hosted Keyspaces database (Cassandra).
  1089  	DatabaseTypeAWSKeyspaces = "keyspace"
  1090  	// DatabaseTypeCassandra is AWS-hosted Keyspace database.
  1091  	DatabaseTypeCassandra = "cassandra"
  1092  	// DatabaseTypeDynamoDB is a DynamoDB database.
  1093  	DatabaseTypeDynamoDB = "dynamodb"
  1094  	// DatabaseTypeOpenSearch is AWS-hosted OpenSearch instance.
  1095  	DatabaseTypeOpenSearch = "opensearch"
  1096  	// DatabaseTypeMongoAtlas
  1097  	DatabaseTypeMongoAtlas = "mongo-atlas"
  1098  )
  1099  
  1100  // GetServerName returns the GCP database project and instance as "<project-id>:<instance-id>".
  1101  func (gcp GCPCloudSQL) GetServerName() string {
  1102  	return fmt.Sprintf("%s:%s", gcp.ProjectID, gcp.InstanceID)
  1103  }
  1104  
  1105  // DeduplicateDatabases deduplicates databases by name.
  1106  func DeduplicateDatabases(databases []Database) (result []Database) {
  1107  	seen := make(map[string]struct{})
  1108  	for _, database := range databases {
  1109  		if _, ok := seen[database.GetName()]; ok {
  1110  			continue
  1111  		}
  1112  		seen[database.GetName()] = struct{}{}
  1113  		result = append(result, database)
  1114  	}
  1115  	return result
  1116  }
  1117  
  1118  // Databases is a list of database resources.
  1119  type Databases []Database
  1120  
  1121  // ToMap returns these databases as a map keyed by database name.
  1122  func (d Databases) ToMap() map[string]Database {
  1123  	m := make(map[string]Database)
  1124  	for _, database := range d {
  1125  		m[database.GetName()] = database
  1126  	}
  1127  	return m
  1128  }
  1129  
  1130  // AsResources returns these databases as resources with labels.
  1131  func (d Databases) AsResources() (resources ResourcesWithLabels) {
  1132  	for _, database := range d {
  1133  		resources = append(resources, database)
  1134  	}
  1135  	return resources
  1136  }
  1137  
  1138  // Len returns the slice length.
  1139  func (d Databases) Len() int { return len(d) }
  1140  
  1141  // Less compares databases by name.
  1142  func (d Databases) Less(i, j int) bool { return d[i].GetName() < d[j].GetName() }
  1143  
  1144  // Swap swaps two databases.
  1145  func (d Databases) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
  1146  
  1147  // UnmarshalJSON supports parsing DatabaseTLSMode from number or string.
  1148  func (d *DatabaseTLSMode) UnmarshalJSON(data []byte) error {
  1149  	type loopBreaker DatabaseTLSMode
  1150  	var val loopBreaker
  1151  	// try as number first.
  1152  	if err := json.Unmarshal(data, &val); err == nil {
  1153  		*d = DatabaseTLSMode(val)
  1154  		return nil
  1155  	}
  1156  
  1157  	// fallback to string.
  1158  	var s string
  1159  	if err := json.Unmarshal(data, &s); err != nil {
  1160  		return trace.Wrap(err)
  1161  	}
  1162  	return d.decodeName(s)
  1163  }
  1164  
  1165  // UnmarshalYAML supports parsing DatabaseTLSMode from number or string.
  1166  func (d *DatabaseTLSMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
  1167  	// try as number first.
  1168  	type loopBreaker DatabaseTLSMode
  1169  	var val loopBreaker
  1170  	if err := unmarshal(&val); err == nil {
  1171  		*d = DatabaseTLSMode(val)
  1172  		return nil
  1173  	}
  1174  
  1175  	// fallback to string.
  1176  	var s string
  1177  	if err := unmarshal(&s); err != nil {
  1178  		return trace.Wrap(err)
  1179  	}
  1180  	return d.decodeName(s)
  1181  }
  1182  
  1183  // decodeName decodes DatabaseTLSMode from a string. This is necessary for
  1184  // allowing tctl commands to work with the same names as documented in Teleport
  1185  // configuration, rather than requiring it be specified as an unreadable enum
  1186  // number.
  1187  func (d *DatabaseTLSMode) decodeName(name string) error {
  1188  	switch name {
  1189  	case "verify-full", "":
  1190  		*d = DatabaseTLSMode_VERIFY_FULL
  1191  		return nil
  1192  	case "verify-ca":
  1193  		*d = DatabaseTLSMode_VERIFY_CA
  1194  		return nil
  1195  	case "insecure":
  1196  		*d = DatabaseTLSMode_INSECURE
  1197  		return nil
  1198  	}
  1199  	return trace.BadParameter("DatabaseTLSMode invalid value %v", d)
  1200  }
  1201  
  1202  // MarshalJSON supports marshaling enum value into it's string value.
  1203  func (s *IAMPolicyStatus) MarshalJSON() ([]byte, error) {
  1204  	return json.Marshal(s.String())
  1205  }
  1206  
  1207  // UnmarshalJSON supports unmarshaling enum string value back to number.
  1208  func (s *IAMPolicyStatus) UnmarshalJSON(data []byte) error {
  1209  	if len(data) == 0 {
  1210  		return nil
  1211  	}
  1212  
  1213  	var stringVal string
  1214  	if err := json.Unmarshal(data, &stringVal); err != nil {
  1215  		return err
  1216  	}
  1217  
  1218  	*s = IAMPolicyStatus(IAMPolicyStatus_value[stringVal])
  1219  	return nil
  1220  }
  1221  
  1222  // IsAuditLogEnabled returns if Oracle Audit Log was enabled
  1223  func (o OracleOptions) IsAuditLogEnabled() bool {
  1224  	return o.AuditUser != ""
  1225  }