diff --git a/lib/services/databaseobjectimportrule.go b/lib/services/databaseobjectimportrule.go
index 1bc09e00f138c..6c9ad50d8e560 100644
--- a/lib/services/databaseobjectimportrule.go
+++ b/lib/services/databaseobjectimportrule.go
@@ -21,12 +21,7 @@ package services
import (
"context"
- "github.com/gravitational/trace"
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/timestamppb"
-
dbobjectimportrulev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1"
- "github.com/gravitational/teleport/lib/utils"
)
// DatabaseObjectImportRules manages DatabaseObjectImportRule resources.
@@ -49,49 +44,3 @@ type DatabaseObjectImportRules interface {
// ListDatabaseObjectImportRules will list DatabaseObjectImportRule resources.
ListDatabaseObjectImportRules(ctx context.Context, size int, pageToken string) ([]*dbobjectimportrulev1.DatabaseObjectImportRule, string, error)
}
-
-// MarshalDatabaseObjectImportRule marshals DatabaseObjectImportRule resource to JSON.
-func MarshalDatabaseObjectImportRule(rule *dbobjectimportrulev1.DatabaseObjectImportRule, opts ...MarshalOption) ([]byte, error) {
- cfg, err := CollectOptions(opts)
- if err != nil {
- return nil, trace.Wrap(err)
- }
- if !cfg.PreserveResourceID {
- rule = proto.Clone(rule).(*dbobjectimportrulev1.DatabaseObjectImportRule)
- //nolint:staticcheck // SA1019. Deprecated, but still needed.
- rule.Metadata.Id = 0
- rule.Metadata.Revision = ""
- }
- data, err := utils.FastMarshal(rule)
- if err != nil {
- return nil, trace.Wrap(err)
- }
- return data, nil
-}
-
-// UnmarshalDatabaseObjectImportRule unmarshals the DatabaseObjectImportRule resource from JSON.
-func UnmarshalDatabaseObjectImportRule(data []byte, opts ...MarshalOption) (*dbobjectimportrulev1.DatabaseObjectImportRule, error) {
- if len(data) == 0 {
- return nil, trace.BadParameter("missing DatabaseObjectImportRule data")
- }
- cfg, err := CollectOptions(opts)
- if err != nil {
- return nil, trace.Wrap(err)
- }
- var obj dbobjectimportrulev1.DatabaseObjectImportRule
- err = utils.FastUnmarshal(data, &obj)
- if err != nil {
- return nil, trace.Wrap(err)
- }
- if cfg.ID != 0 {
- //nolint:staticcheck // SA1019. Id is deprecated, but still needed.
- obj.Metadata.Id = cfg.ID
- }
- if cfg.Revision != "" {
- obj.Metadata.Revision = cfg.Revision
- }
- if !cfg.Expires.IsZero() {
- obj.Metadata.Expires = timestamppb.New(cfg.Expires)
- }
- return &obj, nil
-}
diff --git a/lib/services/databaseobjectimportrule_test.go b/lib/services/databaseobjectimportrule_test.go
deleted file mode 100644
index 97f82aece1ba6..0000000000000
--- a/lib/services/databaseobjectimportrule_test.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Teleport
-// Copyright (C) 2024 Gravitational, Inc.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-package services
-
-import (
- "testing"
-
- "github.com/stretchr/testify/require"
- "google.golang.org/protobuf/proto"
-
- "github.com/gravitational/teleport/api/defaults"
- dbobjectimportrulev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1"
- headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
- "github.com/gravitational/teleport/api/types"
- apilabels "github.com/gravitational/teleport/api/types/label"
-)
-
-func TestMarshalDatabaseObjectImportRuleRoundTrip(t *testing.T) {
- spec := &dbobjectimportrulev1.DatabaseObjectImportRuleSpec{
- Priority: 30,
- DatabaseLabels: apilabels.FromMap(map[string][]string{"env": {"staging", "prod"}, "owner_org": {"trading"}}),
- Mappings: []*dbobjectimportrulev1.DatabaseObjectImportRuleMapping{
- {
- Scope: &dbobjectimportrulev1.DatabaseObjectImportScope{
- SchemaNames: []string{"public"},
- DatabaseNames: []string{"foo", "bar", "baz"},
- },
- Match: &dbobjectimportrulev1.DatabaseObjectImportMatch{
- TableNames: []string{"*"},
- ViewNames: []string{"1", "2", "3"},
- ProcedureNames: []string{"aaa", "bbb", "ccc"},
- },
- AddLabels: map[string]string{
- "env": "staging",
- "custom_label": "my_custom_value",
- },
- },
- },
- }
- obj := &dbobjectimportrulev1.DatabaseObjectImportRule{
- Kind: types.KindDatabaseObjectImportRule,
- Version: types.V1,
- Metadata: &headerv1.Metadata{
- Name: "import_all_staging_tables",
- Namespace: defaults.Namespace,
- },
- Spec: spec,
- }
-
- out, err := MarshalDatabaseObjectImportRule(obj)
- require.NoError(t, err)
- newObj, err := UnmarshalDatabaseObjectImportRule(out)
- require.NoError(t, err)
- require.True(t, proto.Equal(obj, newObj), "messages are not equal")
-}
diff --git a/lib/services/local/databaseobjectimportrule.go b/lib/services/local/databaseobjectimportrule.go
index 613f43585ccc6..b140d325a8dc1 100644
--- a/lib/services/local/databaseobjectimportrule.go
+++ b/lib/services/local/databaseobjectimportrule.go
@@ -22,12 +22,15 @@ import (
"context"
"github.com/gravitational/trace"
+ "google.golang.org/protobuf/proto"
+ "google.golang.org/protobuf/types/known/timestamppb"
databaseobjectimportrulev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/services/local/generic"
+ "github.com/gravitational/teleport/lib/utils"
)
// databaseObjectImportRuleService manages database object import rules in the backend.
@@ -74,10 +77,54 @@ func NewDatabaseObjectImportRuleService(backend backend.Backend) (services.Datab
service, err := generic.NewServiceWrapper(backend,
types.KindDatabaseObjectImportRule,
databaseObjectImportRulePrefix,
- services.MarshalDatabaseObjectImportRule,
- services.UnmarshalDatabaseObjectImportRule)
+ marshalDatabaseObjectImportRule,
+ unmarshalDatabaseObjectImportRule)
if err != nil {
return nil, trace.Wrap(err)
}
return &databaseObjectImportRuleService{service: service}, nil
}
+
+func marshalDatabaseObjectImportRule(rule *databaseobjectimportrulev1.DatabaseObjectImportRule, opts ...services.MarshalOption) ([]byte, error) {
+ cfg, err := services.CollectOptions(opts)
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+ if !cfg.PreserveResourceID {
+ rule = proto.Clone(rule).(*databaseobjectimportrulev1.DatabaseObjectImportRule)
+ //nolint:staticcheck // SA1019. Deprecated, but still needed.
+ rule.Metadata.Id = 0
+ rule.Metadata.Revision = ""
+ }
+ data, err := utils.FastMarshal(rule)
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+ return data, nil
+}
+
+func unmarshalDatabaseObjectImportRule(data []byte, opts ...services.MarshalOption) (*databaseobjectimportrulev1.DatabaseObjectImportRule, error) {
+ if len(data) == 0 {
+ return nil, trace.BadParameter("missing DatabaseObjectImportRule data")
+ }
+ cfg, err := services.CollectOptions(opts)
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+ var obj databaseobjectimportrulev1.DatabaseObjectImportRule
+ err = utils.FastUnmarshal(data, &obj)
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+ if cfg.ID != 0 {
+ //nolint:staticcheck // SA1019. Id is deprecated, but still needed.
+ obj.Metadata.Id = cfg.ID
+ }
+ if cfg.Revision != "" {
+ obj.Metadata.Revision = cfg.Revision
+ }
+ if !cfg.Expires.IsZero() {
+ obj.Metadata.Expires = timestamppb.New(cfg.Expires)
+ }
+ return &obj, nil
+}
diff --git a/lib/services/local/databaseobjectimportrule_test.go b/lib/services/local/databaseobjectimportrule_test.go
index 4c86eb3bded68..3f00db47603fd 100644
--- a/lib/services/local/databaseobjectimportrule_test.go
+++ b/lib/services/local/databaseobjectimportrule_test.go
@@ -29,8 +29,12 @@ import (
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
+ "github.com/gravitational/teleport/api/defaults"
databaseobjectimportrulev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1"
+ headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
+ "github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/types/label"
+ apilabels "github.com/gravitational/teleport/api/types/label"
"github.com/gravitational/teleport/lib/backend/memory"
"github.com/gravitational/teleport/lib/srv/db/common/databaseobjectimportrule"
)
@@ -170,3 +174,42 @@ func TestDatabaseObjectImportRuleCRUD(t *testing.T) {
require.Empty(t, nextToken)
require.Empty(t, out)
}
+
+func TestMarshalDatabaseObjectImportRuleRoundTrip(t *testing.T) {
+ spec := &databaseobjectimportrulev1.DatabaseObjectImportRuleSpec{
+ Priority: 30,
+ DatabaseLabels: apilabels.FromMap(map[string][]string{"env": {"staging", "prod"}, "owner_org": {"trading"}}),
+ Mappings: []*databaseobjectimportrulev1.DatabaseObjectImportRuleMapping{
+ {
+ Scope: &databaseobjectimportrulev1.DatabaseObjectImportScope{
+ SchemaNames: []string{"public"},
+ DatabaseNames: []string{"foo", "bar", "baz"},
+ },
+ Match: &databaseobjectimportrulev1.DatabaseObjectImportMatch{
+ TableNames: []string{"*"},
+ ViewNames: []string{"1", "2", "3"},
+ ProcedureNames: []string{"aaa", "bbb", "ccc"},
+ },
+ AddLabels: map[string]string{
+ "env": "staging",
+ "custom_label": "my_custom_value",
+ },
+ },
+ },
+ }
+ obj := &databaseobjectimportrulev1.DatabaseObjectImportRule{
+ Kind: types.KindDatabaseObjectImportRule,
+ Version: types.V1,
+ Metadata: &headerv1.Metadata{
+ Name: "import_all_staging_tables",
+ Namespace: defaults.Namespace,
+ },
+ Spec: spec,
+ }
+
+ out, err := marshalDatabaseObjectImportRule(obj)
+ require.NoError(t, err)
+ newObj, err := unmarshalDatabaseObjectImportRule(out)
+ require.NoError(t, err)
+ require.True(t, proto.Equal(obj, newObj), "messages are not equal")
+}
diff --git a/lib/services/resource.go b/lib/services/resource.go
index ebfbd6534fba1..17df1e582d4cc 100644
--- a/lib/services/resource.go
+++ b/lib/services/resource.go
@@ -654,13 +654,6 @@ func init() {
}
return types.Resource153ToLegacy(b), nil
})
- RegisterResourceUnmarshaler(types.KindDatabaseObjectImportRule, func(bytes []byte, opts ...MarshalOption) (types.Resource, error) {
- out, err := UnmarshalDatabaseObjectImportRule(bytes, opts...)
- if err != nil {
- return nil, trace.Wrap(err)
- }
- return types.Resource153ToLegacy(out), nil
- })
}
// CheckAndSetDefaults calls [r.CheckAndSetDefaults] if r implements the method.
diff --git a/tool/tctl/common/collection.go b/tool/tctl/common/collection.go
index 69e668d1992d6..68527d6b582dc 100644
--- a/tool/tctl/common/collection.go
+++ b/tool/tctl/common/collection.go
@@ -47,6 +47,7 @@ import (
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/tool/common"
"github.com/gravitational/teleport/tool/tctl/common/databaseobject"
+ "github.com/gravitational/teleport/tool/tctl/common/databaseobjectimportrule"
"github.com/gravitational/teleport/tool/tctl/common/loginrule"
"github.com/gravitational/teleport/tool/tctl/common/oktaassignment"
)
@@ -1174,7 +1175,7 @@ type databaseObjectImportRuleCollection struct {
func (c *databaseObjectImportRuleCollection) resources() []types.Resource {
resources := make([]types.Resource, len(c.rules))
for i, b := range c.rules {
- resources[i] = types.Resource153ToLegacy(b)
+ resources[i] = databaseobjectimportrule.ProtoToResource(b)
}
return resources
}
diff --git a/tool/tctl/common/databaseobjectimportrule/resource.go b/tool/tctl/common/databaseobjectimportrule/resource.go
new file mode 100644
index 0000000000000..ceb7d458388bb
--- /dev/null
+++ b/tool/tctl/common/databaseobjectimportrule/resource.go
@@ -0,0 +1,87 @@
+// Teleport
+// Copyright (C) 2024 Gravitational, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package databaseobjectimportrule
+
+import (
+ "github.com/gravitational/trace"
+ "google.golang.org/protobuf/types/known/timestamppb"
+
+ "github.com/gravitational/teleport/api/defaults"
+ dbobjectimportrulev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1"
+ headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
+ "github.com/gravitational/teleport/api/types"
+ "github.com/gravitational/teleport/lib/utils"
+)
+
+// Resource is a type wrapper type for YAML (un)marshaling.
+type Resource struct {
+ // ResourceHeader is embedded to implement types.Resource
+ types.ResourceHeader
+ // Spec is the database object specification
+ Spec *dbobjectimportrulev1.DatabaseObjectImportRuleSpec `json:"spec"`
+}
+
+// UnmarshalJSON parses Resource and converts into an object.
+func UnmarshalJSON(raw []byte) (*dbobjectimportrulev1.DatabaseObjectImportRule, error) {
+ var resource Resource
+ if err := utils.FastUnmarshal(raw, &resource); err != nil {
+ return nil, trace.Wrap(err)
+ }
+ return ResourceToProto(&resource), nil
+}
+
+// ProtoToResource converts a *dbobjectimportrulev1.DatabaseObjectImportRule into a *Resource which
+// implements types.Resource and can be marshaled to YAML or JSON in a
+// human-friendly format.
+func ProtoToResource(rule *dbobjectimportrulev1.DatabaseObjectImportRule) *Resource {
+ r := &Resource{
+ ResourceHeader: types.ResourceHeader{
+ Kind: rule.GetKind(),
+ SubKind: rule.GetSubKind(),
+ Version: rule.GetVersion(),
+ Metadata: types.Resource153ToLegacy(rule).GetMetadata(),
+ },
+ Spec: rule.GetSpec(),
+ }
+ return r
+}
+
+func ResourceToProto(r *Resource) *dbobjectimportrulev1.DatabaseObjectImportRule {
+ md := r.Metadata
+
+ var expires *timestamppb.Timestamp
+ if md.Expires != nil {
+ expires = timestamppb.New(*md.Expires)
+ }
+
+ return &dbobjectimportrulev1.DatabaseObjectImportRule{
+ Kind: r.GetKind(),
+ SubKind: r.GetSubKind(),
+ Version: r.GetVersion(),
+ Metadata: &headerv1.Metadata{
+ Name: md.Name,
+ Description: md.Description,
+ Namespace: defaults.Namespace,
+ Labels: md.Labels,
+ Expires: expires,
+ //nolint:staticcheck // SA1019. Id is deprecated.
+ Id: md.ID,
+ Revision: md.Revision,
+ },
+ Spec: r.Spec,
+ }
+}
diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go
index 4e910bdfc7944..b54dc0c80aa0d 100644
--- a/tool/tctl/common/resource_command.go
+++ b/tool/tctl/common/resource_command.go
@@ -63,6 +63,7 @@ import (
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/tool/tctl/common/databaseobject"
+ "github.com/gravitational/teleport/tool/tctl/common/databaseobjectimportrule"
"github.com/gravitational/teleport/tool/tctl/common/loginrule"
)
@@ -596,7 +597,7 @@ func (rc *ResourceCommand) createBot(ctx context.Context, client *auth.Client, r
}
func (rc *ResourceCommand) createDatabaseObjectImportRule(ctx context.Context, client *auth.Client, raw services.UnknownResource) error {
- rule, err := services.UnmarshalDatabaseObjectImportRule(raw.Raw)
+ rule, err := databaseobjectimportrule.UnmarshalJSON(raw.Raw)
if err != nil {
return trace.Wrap(err)
}
diff --git a/tool/tctl/common/resource_command_test.go b/tool/tctl/common/resource_command_test.go
index 84f5a02f3d208..abe25daea8ff7 100644
--- a/tool/tctl/common/resource_command_test.go
+++ b/tool/tctl/common/resource_command_test.go
@@ -40,7 +40,6 @@ import (
"github.com/gravitational/teleport/api/constants"
apidefaults "github.com/gravitational/teleport/api/defaults"
- dbobjectimportrulev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1"
headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/types/discoveryconfig"
@@ -54,6 +53,7 @@ import (
"github.com/gravitational/teleport/lib/tbot/testhelpers"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/tool/tctl/common/databaseobject"
+ "github.com/gravitational/teleport/tool/tctl/common/databaseobjectimportrule"
)
// TestDatabaseServerResource tests tctl db_server rm/get commands.
@@ -1651,10 +1651,10 @@ spec:
}
func testCreateDatabaseObjectImportRule(t *testing.T, fc *config.FileConfig) {
- const resourceName = "import_all_staging_tables"
- const resourcePath = types.KindDatabaseObjectImportRule + "/" + resourceName
const resourceYAML = `kind: db_object_import_rule
metadata:
+ expires: "2034-03-22T18:06:35.161162Z"
+ id: 1711129895244889000
name: import_all_staging_tables
namespace: default
spec:
@@ -1692,45 +1692,34 @@ spec:
version: v1
`
- // Ensure that our test user does not exist
- _, err := runResourceCommand(t, fc, []string{"get", resourcePath, "--format=json"})
- require.True(t, trace.IsNotFound(err), "expected llama user to not exist prior to being created")
+ // Verify there is no matching resource
+ const resourceKey = "db_object_import_rule/import_all_staging_tables"
+ _, err := runResourceCommand(t, fc, []string{"get", resourceKey, "--format=json"})
+ require.Error(t, err)
- // Create the user
+ // Create the resource
resourceYAMLPath := filepath.Join(t.TempDir(), "resource.yaml")
require.NoError(t, os.WriteFile(resourceYAMLPath, []byte(resourceYAML), 0644))
_, err = runResourceCommand(t, fc, []string{"create", resourceYAMLPath})
require.NoError(t, err)
- // Fetch the user
- buf, err := runResourceCommand(t, fc, []string{"get", resourcePath, "--format=json"})
+ // Fetch the resource
+ buf, err := runResourceCommand(t, fc, []string{"get", resourceKey, "--format=json"})
require.NoError(t, err)
- resources := mustDecodeJSON[[]*dbobjectimportrulev1.DatabaseObjectImportRule](t, buf)
+ resources := mustDecodeJSON[[]databaseobjectimportrule.Resource](t, buf)
require.Len(t, resources, 1)
- var expected dbobjectimportrulev1.DatabaseObjectImportRule
- require.NoError(t, yaml.Unmarshal([]byte(resourceYAML), &expected))
- // verify a few expected properties
- require.Equal(t, 30, int(expected.Spec.Priority))
- require.Equal(t, "import_all_staging_tables", expected.Metadata.Name)
-
- resources[0].Metadata.Revision = expected.Metadata.Revision
- //nolint:staticcheck // SA1019. Added for backward compatibility.
- resources[0].Metadata.Id = expected.Metadata.Id
- require.Equal(t, []*dbobjectimportrulev1.DatabaseObjectImportRule{&expected}, resources)
-
- // Explicitly change the revision and try creating the user with and without
- // the force flag.
- expected.Metadata.Revision = uuid.NewString()
- data, err := services.MarshalDatabaseObjectImportRule(&expected, services.PreserveResourceID())
- require.NoError(t, err)
- require.NoError(t, os.WriteFile(resourceYAMLPath, data, 0644))
+ // Compare with baseline
+ cmpOpts := []cmp.Option{
+ protocmp.IgnoreFields(&headerv1.Metadata{}, "id", "revision"),
+ protocmp.Transform(),
+ }
- _, err = runResourceCommand(t, fc, []string{"create", resourceYAMLPath})
- require.True(t, trace.IsAlreadyExists(err))
+ var expected databaseobjectimportrule.Resource
+ require.NoError(t, yaml.Unmarshal([]byte(resourceYAML), &expected))
- _, err = runResourceCommand(t, fc, []string{"create", "-f", resourceYAMLPath})
- require.NoError(t, err)
+ require.Equal(t, "", cmp.Diff(expected, resources[0], cmpOpts...))
+ require.Equal(t, "", cmp.Diff(databaseobjectimportrule.ResourceToProto(&expected), databaseobjectimportrule.ResourceToProto(&resources[0]), cmpOpts...))
}
func testCreateClusterNetworkingConfig(t *testing.T, fc *config.FileConfig) {