update database and kube name validation (#28841)

* update database name validation

* move name validation into DatabaseV3 CheckAndSetDefaults
* replace use of DNS1305 name validation
  * remove 63 char length restriction
  * remove lowercase restriction

* fix failing tests

* update more tests

* update CHANGELOG.md
* explain breaking change to database discovery name validation
This commit is contained in:
Gavin Frazar 2023-07-12 14:24:25 -07:00 committed by GitHub
parent a9467c9539
commit 2496e37124
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 147 additions and 79 deletions

View file

@ -24,6 +24,19 @@ Prior versions of Teleport would return an error from `tsh db` commands if those
flags were empty (the only exception is Redis, where it would choose the Redis flags were empty (the only exception is Redis, where it would choose the Redis
username "default" for `--db-user`). username "default" for `--db-user`).
#### Auto-Discovered Database Names
In Teleport 14, database discovery via `db_service` config will enforce the same
name validation that we enforce for databases created via `tctl`, static config,
and `discovery_service`.
The regex used to constrain database names is: `^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$`
So databases in AWS and Azure must:
1. start with a letter (uppercase and lowercase are both allowed).
2. contain only letters, digits, and hyphens.
3. end with a letter or digit (no trailing hyphens).
## 13.0.1 (05/xx/23) ## 13.0.1 (05/xx/23)
* Helm Charts * Helm Charts

View file

@ -19,6 +19,7 @@ package types
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"regexp"
"strings" "strings"
"time" "time"
@ -565,6 +566,21 @@ func (d *DatabaseV3) setStaticFields() {
d.Version = V3 d.Version = V3
} }
// validDatabaseNameRegexp filters the allowed characters in database names.
// This is the (almost) the same regexp used to check for valid DNS 1035 labels,
// except we allow uppercase chars.
var validDatabaseNameRegexp = regexp.MustCompile(`^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$`)
// ValidateDatabaseName returns an error if a given string is not a valid
// Database name.
// Unlike application access proxy, database name doesn't necessarily
// need to be a valid subdomain but use the same validation logic for the
// simplicity and consistency, except two differences: don't restrict names to
// 63 chars in length and allow upper case chars.
func ValidateDatabaseName(name string) error {
return ValidateResourceName(validDatabaseNameRegexp, name)
}
// CheckAndSetDefaults checks and sets default values for any missing fields. // CheckAndSetDefaults checks and sets default values for any missing fields.
func (d *DatabaseV3) CheckAndSetDefaults() error { func (d *DatabaseV3) CheckAndSetDefaults() error {
d.setStaticFields() d.setStaticFields()
@ -572,6 +588,10 @@ func (d *DatabaseV3) CheckAndSetDefaults() error {
return trace.Wrap(err) return trace.Wrap(err)
} }
if err := ValidateDatabaseName(d.GetName()); err != nil {
return trace.Wrap(err, "invalid database name")
}
for key := range d.Spec.DynamicLabels { for key := range d.Spec.DynamicLabels {
if !IsValidLabelKey(key) { if !IsValidLabelKey(key) {
return trace.BadParameter("database %q invalid label key: %q", d.GetName(), key) return trace.BadParameter("database %q invalid label key: %q", d.GetName(), key)

View file

@ -17,6 +17,7 @@ limitations under the License.
package types package types
import ( import (
"strings"
"testing" "testing"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
@ -890,3 +891,40 @@ func TestAWSIsEmpty(t *testing.T) {
}) })
} }
} }
func TestValidateDatabaseName(t *testing.T) {
t.Parallel()
tests := []struct {
name string
dbName string
expectErrContains string
}{
{
name: "valid long name and uppercase chars",
dbName: strings.Repeat("aA", 100),
},
{
name: "invalid trailing hyphen",
dbName: "invalid-database-name-",
expectErrContains: `"invalid-database-name-" does not match regex`,
},
{
name: "invalid first character",
dbName: "1-invalid-database-name",
expectErrContains: `"1-invalid-database-name" does not match regex`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := ValidateDatabaseName(test.dbName)
if test.expectErrContains != "" {
require.Error(t, err)
require.ErrorContains(t, err, test.expectErrContains)
return
}
require.NoError(t, err)
})
}
}

View file

@ -108,31 +108,6 @@ func TestDatabaseServerSorter(t *testing.T) {
}, },
} }
makeServers := func(testVals []string, testField string) []DatabaseServer {
servers := make([]DatabaseServer, len(testVals))
for i := 0; i < len(testVals); i++ {
testVal := testVals[i]
dbSpec := dbSpecs[i%len(dbSpecs)]
var err error
servers[i], err = NewDatabaseServerV3(Metadata{
Name: "_",
}, DatabaseServerSpecV3{
HostID: "_",
Hostname: "_",
Database: &DatabaseV3{
Metadata: Metadata{
Name: getTestVal(testField == ResourceMetadataName, testVal),
Description: getTestVal(testField == ResourceSpecDescription, testVal),
},
Spec: dbSpec,
},
})
require.NoError(t, err)
}
return servers
}
cases := []struct { cases := []struct {
name string name string
wantErr bool wantErr bool
@ -156,7 +131,7 @@ func TestDatabaseServerSorter(t *testing.T) {
c := c c := c
t.Run(fmt.Sprintf("%s desc", c.name), func(t *testing.T) { t.Run(fmt.Sprintf("%s desc", c.name), func(t *testing.T) {
sortBy := SortBy{Field: c.fieldName, IsDesc: true} sortBy := SortBy{Field: c.fieldName, IsDesc: true}
servers := DatabaseServers(makeServers(testValsUnordered, c.fieldName)) servers := DatabaseServers(makeServers(t, testValsUnordered, dbSpecs, c.fieldName))
require.NoError(t, servers.SortByCustom(sortBy)) require.NoError(t, servers.SortByCustom(sortBy))
targetVals, err := servers.GetFieldVals(c.fieldName) targetVals, err := servers.GetFieldVals(c.fieldName)
require.NoError(t, err) require.NoError(t, err)
@ -165,7 +140,7 @@ func TestDatabaseServerSorter(t *testing.T) {
t.Run(fmt.Sprintf("%s asc", c.name), func(t *testing.T) { t.Run(fmt.Sprintf("%s asc", c.name), func(t *testing.T) {
sortBy := SortBy{Field: c.fieldName} sortBy := SortBy{Field: c.fieldName}
servers := DatabaseServers(makeServers(testValsUnordered, c.fieldName)) servers := DatabaseServers(makeServers(t, testValsUnordered, dbSpecs, c.fieldName))
require.NoError(t, servers.SortByCustom(sortBy)) require.NoError(t, servers.SortByCustom(sortBy))
targetVals, err := servers.GetFieldVals(c.fieldName) targetVals, err := servers.GetFieldVals(c.fieldName)
require.NoError(t, err) require.NoError(t, err)
@ -175,6 +150,32 @@ func TestDatabaseServerSorter(t *testing.T) {
// Test error. // Test error.
sortBy := SortBy{Field: "unsupported"} sortBy := SortBy{Field: "unsupported"}
servers := makeServers(testValsUnordered, "does-not-matter") servers := makeServers(t, testValsUnordered, dbSpecs, "does-not-matter")
require.True(t, trace.IsNotImplemented(DatabaseServers(servers).SortByCustom(sortBy))) require.True(t, trace.IsNotImplemented(DatabaseServers(servers).SortByCustom(sortBy)))
} }
func makeServers(t *testing.T, testVals []string, dbSpecs []DatabaseSpecV3, testField string) []DatabaseServer {
t.Helper()
servers := make([]DatabaseServer, len(testVals))
for i := 0; i < len(testVals); i++ {
testVal := testVals[i]
dbSpec := dbSpecs[i%len(dbSpecs)]
var err error
servers[i], err = NewDatabaseServerV3(Metadata{
Name: "foo",
}, DatabaseServerSpecV3{
HostID: "_",
Hostname: "_",
Database: &DatabaseV3{
Metadata: Metadata{
Name: getTestVal(testField == ResourceMetadataName, testVal),
Description: getTestVal(testField == ResourceSpecDescription, testVal),
},
Spec: dbSpec,
},
})
require.NoError(t, err)
}
return servers
}

View file

@ -334,6 +334,12 @@ func (k *KubernetesClusterV3) setStaticFields() {
// sneaky cluster names being used for client directory traversal and exploits. // sneaky cluster names being used for client directory traversal and exploits.
var validKubeClusterName = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`) var validKubeClusterName = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
// ValidateKubeClusterName returns an error if a given string is not a valid
// KubeCluster name.
func ValidateKubeClusterName(name string) error {
return ValidateResourceName(validKubeClusterName, name)
}
// CheckAndSetDefaults checks and sets default values for any missing fields. // CheckAndSetDefaults checks and sets default values for any missing fields.
func (k *KubernetesClusterV3) CheckAndSetDefaults() error { func (k *KubernetesClusterV3) CheckAndSetDefaults() error {
k.setStaticFields() k.setStaticFields()
@ -346,8 +352,8 @@ func (k *KubernetesClusterV3) CheckAndSetDefaults() error {
} }
} }
if !validKubeClusterName.MatchString(k.Metadata.Name) { if err := ValidateKubeClusterName(k.Metadata.Name); err != nil {
return trace.BadParameter("invalid kubernetes cluster name: %q", k.Metadata.Name) return trace.Wrap(err, "invalid kubernetes cluster name")
} }
if err := k.Spec.Azure.CheckAndSetDefaults(); err != nil && k.IsAzure() { if err := k.Spec.Azure.CheckAndSetDefaults(); err != nil && k.IsAzure() {

View file

@ -17,6 +17,7 @@ limitations under the License.
package types package types
import ( import (
"regexp"
"strings" "strings"
"time" "time"
@ -531,3 +532,14 @@ type ListResourcesResponse struct {
// TotalCount is the total number of resources available as a whole. // TotalCount is the total number of resources available as a whole.
TotalCount int TotalCount int
} }
// ValidateResourceName validates a resource name using a given regexp.
func ValidateResourceName(validationRegex *regexp.Regexp, name string) error {
if validationRegex.MatchString(name) {
return nil
}
return trace.BadParameter(
"%q does not match regex used for validation %q",
name, validationRegex.String(),
)
}

View file

@ -128,12 +128,12 @@ func TestMatchSearch_ResourceSpecific(t *testing.T) {
// searchNotDefined refers to resources where the searcheable field values are not defined. // searchNotDefined refers to resources where the searcheable field values are not defined.
searchNotDefined bool searchNotDefined bool
matchingSearchVals []string matchingSearchVals []string
newResource func() ResourceWithLabels newResource func(*testing.T) ResourceWithLabels
}{ }{
{ {
name: "node", name: "node",
matchingSearchVals: []string{"foo", "bar", "prod", "os"}, matchingSearchVals: []string{"foo", "bar", "prod", "os"},
newResource: func() ResourceWithLabels { newResource: func(t *testing.T) ResourceWithLabels {
server, err := NewServerWithLabels("_", KindNode, ServerSpecV2{ server, err := NewServerWithLabels("_", KindNode, ServerSpecV2{
Hostname: "foo", Hostname: "foo",
Addr: "bar", Addr: "bar",
@ -146,7 +146,7 @@ func TestMatchSearch_ResourceSpecific(t *testing.T) {
{ {
name: "node using tunnel", name: "node using tunnel",
matchingSearchVals: []string{"tunnel"}, matchingSearchVals: []string{"tunnel"},
newResource: func() ResourceWithLabels { newResource: func(t *testing.T) ResourceWithLabels {
server, err := NewServer("_", KindNode, ServerSpecV2{ server, err := NewServer("_", KindNode, ServerSpecV2{
UseTunnel: true, UseTunnel: true,
}) })
@ -158,7 +158,7 @@ func TestMatchSearch_ResourceSpecific(t *testing.T) {
{ {
name: "windows desktop", name: "windows desktop",
matchingSearchVals: []string{"foo", "bar", "env", "prod", "os"}, matchingSearchVals: []string{"foo", "bar", "env", "prod", "os"},
newResource: func() ResourceWithLabels { newResource: func(t *testing.T) ResourceWithLabels {
desktop, err := NewWindowsDesktopV3("foo", labels, WindowsDesktopSpecV3{ desktop, err := NewWindowsDesktopV3("foo", labels, WindowsDesktopSpecV3{
Addr: "bar", Addr: "bar",
}) })
@ -170,7 +170,7 @@ func TestMatchSearch_ResourceSpecific(t *testing.T) {
{ {
name: "application", name: "application",
matchingSearchVals: []string{"foo", "bar", "baz", "mac"}, matchingSearchVals: []string{"foo", "bar", "baz", "mac"},
newResource: func() ResourceWithLabels { newResource: func(t *testing.T) ResourceWithLabels {
app, err := NewAppV3(Metadata{ app, err := NewAppV3(Metadata{
Name: "foo", Name: "foo",
Description: "bar", Description: "bar",
@ -187,7 +187,7 @@ func TestMatchSearch_ResourceSpecific(t *testing.T) {
{ {
name: "kube cluster", name: "kube cluster",
matchingSearchVals: []string{"foo", "prod", "env"}, matchingSearchVals: []string{"foo", "prod", "env"},
newResource: func() ResourceWithLabels { newResource: func(t *testing.T) ResourceWithLabels {
kc, err := NewKubernetesClusterV3FromLegacyCluster("_", &KubernetesCluster{ kc, err := NewKubernetesClusterV3FromLegacyCluster("_", &KubernetesCluster{
Name: "foo", Name: "foo",
StaticLabels: labels, StaticLabels: labels,
@ -200,7 +200,7 @@ func TestMatchSearch_ResourceSpecific(t *testing.T) {
{ {
name: "database", name: "database",
matchingSearchVals: []string{"foo", "bar", "baz", "prod", DatabaseTypeRedshift}, matchingSearchVals: []string{"foo", "bar", "baz", "prod", DatabaseTypeRedshift},
newResource: func() ResourceWithLabels { newResource: func(t *testing.T) ResourceWithLabels {
db, err := NewDatabaseV3(Metadata{ db, err := NewDatabaseV3(Metadata{
Name: "foo", Name: "foo",
Description: "bar", Description: "bar",
@ -222,9 +222,9 @@ func TestMatchSearch_ResourceSpecific(t *testing.T) {
{ {
name: "database with gcp keywords", name: "database with gcp keywords",
matchingSearchVals: []string{"cloud", "cloud sql"}, matchingSearchVals: []string{"cloud", "cloud sql"},
newResource: func() ResourceWithLabels { newResource: func(t *testing.T) ResourceWithLabels {
db, err := NewDatabaseV3(Metadata{ db, err := NewDatabaseV3(Metadata{
Name: "_", Name: "foo",
Labels: labels, Labels: labels,
}, DatabaseSpecV3{ }, DatabaseSpecV3{
Protocol: "_", Protocol: "_",
@ -242,9 +242,9 @@ func TestMatchSearch_ResourceSpecific(t *testing.T) {
{ {
name: "app server", name: "app server",
searchNotDefined: true, searchNotDefined: true,
newResource: func() ResourceWithLabels { newResource: func(t *testing.T) ResourceWithLabels {
appServer, err := NewAppServerV3(Metadata{ appServer, err := NewAppServerV3(Metadata{
Name: "_", Name: "foo",
}, AppServerSpecV3{ }, AppServerSpecV3{
HostID: "_", HostID: "_",
App: &AppV3{Metadata: Metadata{Name: "_"}, Spec: AppSpecV3{URI: "_"}}, App: &AppV3{Metadata: Metadata{Name: "_"}, Spec: AppSpecV3{URI: "_"}},
@ -257,9 +257,9 @@ func TestMatchSearch_ResourceSpecific(t *testing.T) {
{ {
name: "db server", name: "db server",
searchNotDefined: true, searchNotDefined: true,
newResource: func() ResourceWithLabels { newResource: func(t *testing.T) ResourceWithLabels {
dbServer, err := NewDatabaseServerV3(Metadata{ dbServer, err := NewDatabaseServerV3(Metadata{
Name: "_", Name: "foo",
}, DatabaseServerSpecV3{ }, DatabaseServerSpecV3{
HostID: "_", HostID: "_",
Hostname: "_", Hostname: "_",
@ -272,10 +272,10 @@ func TestMatchSearch_ResourceSpecific(t *testing.T) {
{ {
name: "kube server", name: "kube server",
searchNotDefined: true, searchNotDefined: true,
newResource: func() ResourceWithLabels { newResource: func(t *testing.T) ResourceWithLabels {
kubeServer, err := NewKubernetesServerV3( kubeServer, err := NewKubernetesServerV3(
Metadata{ Metadata{
Name: "_", Name: "foo",
}, KubernetesServerSpecV3{ }, KubernetesServerSpecV3{
HostID: "_", HostID: "_",
Hostname: "_", Hostname: "_",
@ -293,7 +293,7 @@ func TestMatchSearch_ResourceSpecific(t *testing.T) {
{ {
name: "desktop service", name: "desktop service",
searchNotDefined: true, searchNotDefined: true,
newResource: func() ResourceWithLabels { newResource: func(t *testing.T) ResourceWithLabels {
desktopService, err := NewWindowsDesktopServiceV3(Metadata{ desktopService, err := NewWindowsDesktopServiceV3(Metadata{
Name: "foo", Name: "foo",
}, WindowsDesktopServiceSpecV3{ }, WindowsDesktopServiceSpecV3{
@ -312,7 +312,7 @@ func TestMatchSearch_ResourceSpecific(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Parallel() t.Parallel()
resource := tc.newResource() resource := tc.newResource(t)
// Nil search values, should always return true // Nil search values, should always return true
match := resource.MatchSearch(nil) match := resource.MatchSearch(nil)

View file

@ -31,7 +31,7 @@ func getTestVal(isTestField bool, testVal string) string {
return testVal return testVal
} }
return "_" return "foo"
} }
func TestServerSorter(t *testing.T) { func TestServerSorter(t *testing.T) {

View file

@ -24,7 +24,6 @@ import (
"github.com/aws/aws-sdk-go-v2/service/rds" "github.com/aws/aws-sdk-go-v2/service/rds"
rdsTypes "github.com/aws/aws-sdk-go-v2/service/rds/types" rdsTypes "github.com/aws/aws-sdk-go-v2/service/rds/types"
"github.com/google/uuid"
"github.com/gravitational/trace" "github.com/gravitational/trace"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -99,7 +98,7 @@ func TestListDatabases(t *testing.T) {
for i := 0; i < totalDBs; i++ { for i := 0; i < totalDBs; i++ {
allInstances = append(allInstances, rdsTypes.DBInstance{ allInstances = append(allInstances, rdsTypes.DBInstance{
DBInstanceStatus: stringPointer("available"), DBInstanceStatus: stringPointer("available"),
DBInstanceIdentifier: stringPointer(uuid.NewString()), DBInstanceIdentifier: stringPointer(fmt.Sprintf("db-%v", i)),
DbiResourceId: stringPointer("db-123"), DbiResourceId: stringPointer("db-123"),
DBInstanceArn: stringPointer("arn:aws:iam::123456789012:role/MyARN"), DBInstanceArn: stringPointer("arn:aws:iam::123456789012:role/MyARN"),
Engine: stringPointer("postgres"), Engine: stringPointer("postgres"),

View file

@ -44,7 +44,6 @@ import (
"go.mongodb.org/mongo-driver/mongo/readpref" "go.mongodb.org/mongo-driver/mongo/readpref"
"go.mongodb.org/mongo-driver/x/mongo/driver/connstring" "go.mongodb.org/mongo-driver/x/mongo/driver/connstring"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"k8s.io/apimachinery/pkg/util/validation"
"github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types"
apiawsutils "github.com/gravitational/teleport/api/utils/aws" apiawsutils "github.com/gravitational/teleport/api/utils/aws"
@ -147,13 +146,6 @@ func ValidateDatabase(db types.Database) error {
return trace.Wrap(err) return trace.Wrap(err)
} }
// Unlike application access proxy, database proxy name doesn't necessarily
// need to be a valid subdomain but use the same validation logic for the
// simplicity and consistency.
if errs := validation.IsDNS1035Label(db.GetName()); len(errs) > 0 {
return trace.BadParameter("invalid database %q name: %v", db.GetName(), errs)
}
if !slices.Contains(defaults.DatabaseProtocols, db.GetProtocol()) { if !slices.Contains(defaults.DatabaseProtocols, db.GetProtocol()) {
return trace.BadParameter("unsupported database %q protocol %q, supported are: %v", db.GetName(), db.GetProtocol(), defaults.DatabaseProtocols) return trace.BadParameter("unsupported database %q protocol %q, supported are: %v", db.GetName(), db.GetProtocol(), defaults.DatabaseProtocols)
} }

View file

@ -122,19 +122,6 @@ func TestValidateDatabase(t *testing.T) {
inputSpec types.DatabaseSpecV3 inputSpec types.DatabaseSpecV3
expectError bool expectError bool
}{ }{
{
// Captured error:
// a DNS-1035 label must consist of lower case alphanumeric
// characters or '-', start with an alphabetic character, and end
// with an alphanumeric character (e.g. 'my-name', or 'abc-123',
// regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')
inputName: "invalid-database-name-",
inputSpec: types.DatabaseSpecV3{
Protocol: defaults.ProtocolPostgres,
URI: "localhost:5432",
},
expectError: true,
},
{ {
inputName: "invalid-database-protocol", inputName: "invalid-database-protocol",
inputSpec: types.DatabaseSpecV3{ inputSpec: types.DatabaseSpecV3{

View file

@ -18,13 +18,13 @@ import (
"context" "context"
_ "embed" _ "embed"
"errors" "errors"
"fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"testing" "testing"
"time" "time"
"github.com/google/uuid"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/client/proto"
@ -48,7 +48,7 @@ func TestConnectorSelection(t *testing.T) {
connector := &connector{DBAuth: &mockDBAuth{}} connector := &connector{DBAuth: &mockDBAuth{}}
for _, tt := range []struct { for i, tt := range []struct {
desc string desc string
databaseSpec types.DatabaseSpecV3 databaseSpec types.DatabaseSpecV3
errAssertion require.ErrorAssertionFunc errAssertion require.ErrorAssertionFunc
@ -118,7 +118,7 @@ func TestConnectorSelection(t *testing.T) {
} { } {
t.Run(tt.desc, func(t *testing.T) { t.Run(tt.desc, func(t *testing.T) {
database, err := types.NewDatabaseV3(types.Metadata{ database, err := types.NewDatabaseV3(types.Metadata{
Name: uuid.NewString(), Name: fmt.Sprintf("db-%v", i),
}, tt.databaseSpec) }, tt.databaseSpec)
require.NoError(t, err) require.NoError(t, err)
@ -240,7 +240,7 @@ func TestConnectorKInitClient(t *testing.T) {
err := os.WriteFile(krbConfPath, []byte(krb5Conf), 0664) err := os.WriteFile(krbConfPath, []byte(krb5Conf), 0664)
require.NoError(t, err) require.NoError(t, err)
for _, tt := range []struct { for i, tt := range []struct {
desc string desc string
databaseSpec types.DatabaseSpecV3 databaseSpec types.DatabaseSpecV3
errAssertion require.ErrorAssertionFunc errAssertion require.ErrorAssertionFunc
@ -301,7 +301,7 @@ func TestConnectorKInitClient(t *testing.T) {
} { } {
t.Run(tt.desc, func(t *testing.T) { t.Run(tt.desc, func(t *testing.T) {
database, err := types.NewDatabaseV3(types.Metadata{ database, err := types.NewDatabaseV3(types.Metadata{
Name: uuid.NewString(), Name: fmt.Sprintf("db-%v", i),
}, tt.databaseSpec) }, tt.databaseSpec)
require.NoError(t, err) require.NoError(t, err)

View file

@ -368,7 +368,7 @@ func makeAzureSQLServer(t *testing.T, name, group string) (*armsql.Server, types
server := &armsql.Server{ server := &armsql.Server{
ID: to.Ptr(fmt.Sprintf("/subscriptions/sub-id/resourceGroups/%v/providers/Microsoft.Sql/servers/%v", group, name)), ID: to.Ptr(fmt.Sprintf("/subscriptions/sub-id/resourceGroups/%v/providers/Microsoft.Sql/servers/%v", group, name)),
Name: to.Ptr(fmt.Sprintf("%s.database.windows.net", name)), Name: to.Ptr(fmt.Sprintf("%s-database-windows-net", name)),
Properties: &armsql.ServerProperties{ Properties: &armsql.ServerProperties{
FullyQualifiedDomainName: to.Ptr("localhost"), FullyQualifiedDomainName: to.Ptr("localhost"),
}, },

View file

@ -3512,7 +3512,7 @@ func TestSerializeDatabases(t *testing.T) {
"kind": "db", "kind": "db",
"version": "v3", "version": "v3",
"metadata": { "metadata": {
"name": "my db", "name": "my-db",
"description": "this is the description", "description": "this is the description",
"labels": {"a": "1", "b": "2"} "labels": {"a": "1", "b": "2"}
}, },
@ -3566,7 +3566,7 @@ func TestSerializeDatabases(t *testing.T) {
}] }]
` `
db, err := types.NewDatabaseV3(types.Metadata{ db, err := types.NewDatabaseV3(types.Metadata{
Name: "my db", Name: "my-db",
Description: "this is the description", Description: "this is the description",
Labels: map[string]string{"a": "1", "b": "2"}, Labels: map[string]string{"a": "1", "b": "2"},
}, types.DatabaseSpecV3{ }, types.DatabaseSpecV3{