diff --git a/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.test.tsx b/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.test.tsx
index 5beeed370a38c..74f63d9ee3d08 100644
--- a/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.test.tsx
+++ b/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.test.tsx
@@ -26,12 +26,14 @@ import DatabaseService from 'teleport/services/databases/databases';
import * as discoveryService from 'teleport/services/discovery/discovery';
import { ComponentWrapper } from 'teleport/Discover/Fixtures/databases';
import cfg from 'teleport/config';
+import { DISCOVERY_GROUP_CLOUD } from 'teleport/services/discovery/discovery';
import { EnrollRdsDatabase } from './EnrollRdsDatabase';
const defaultIsCloud = cfg.isCloud;
describe('test EnrollRdsDatabase.tsx', () => {
+ let createDiscoveryConfig;
beforeEach(() => {
cfg.isCloud = true;
jest
@@ -43,11 +45,13 @@ describe('test EnrollRdsDatabase.tsx', () => {
jest
.spyOn(userEventService, 'captureDiscoverEvent')
.mockResolvedValue(undefined as never);
- jest.spyOn(discoveryService, 'createDiscoveryConfig').mockResolvedValue({
- name: '',
- discoveryGroup: '',
- aws: [],
- });
+ createDiscoveryConfig = jest
+ .spyOn(discoveryService, 'createDiscoveryConfig')
+ .mockResolvedValue({
+ name: '',
+ discoveryGroup: '',
+ aws: [],
+ });
jest
.spyOn(DatabaseService.prototype, 'fetchDatabaseServices')
.mockResolvedValue({ services: [] });
@@ -98,7 +102,7 @@ describe('test EnrollRdsDatabase.tsx', () => {
expect(DatabaseService.prototype.fetchDatabases).toHaveBeenCalledTimes(1);
});
- test('auto enroll is on by default with no database services', async () => {
+ test('auto enroll (cloud) is on by default', async () => {
jest.spyOn(integrationService, 'fetchAwsRdsDatabases').mockResolvedValue({
databases: mockAwsDbs,
});
@@ -116,16 +120,28 @@ describe('test EnrollRdsDatabase.tsx', () => {
// Rds results renders result.
await screen.findByText(/rds-1/i);
+ // Cloud uses a default discovery group name.
+ expect(
+ screen.queryByText(/define a discovery group name/i)
+ ).not.toBeInTheDocument();
act(() => screen.getByText('Next').click());
await screen.findByText(/Creating Auto Discovery Config/i);
- expect(discoveryService.createDiscoveryConfig).toHaveBeenCalledTimes(1);
expect(integrationService.fetchAwsRdsRequiredVpcs).toHaveBeenCalledTimes(1);
+ expect(discoveryService.createDiscoveryConfig).toHaveBeenCalledTimes(1);
+
+ // 2D array:
+ // First array is the array of calls, we are only interested in the first.
+ // Second array are the parameters that this api got called with,
+ // we are interested in the second parameter.
+ expect(createDiscoveryConfig.mock.calls[0][1]['discoveryGroup']).toEqual(
+ DISCOVERY_GROUP_CLOUD
+ );
expect(DatabaseService.prototype.createDatabase).not.toHaveBeenCalled();
});
- test('auto enroll disabled, creates database', async () => {
+ test('auto enroll disabled (cloud), creates database', async () => {
jest.spyOn(integrationService, 'fetchAwsRdsDatabases').mockResolvedValue({
databases: mockAwsDbs,
});
@@ -156,6 +172,76 @@ describe('test EnrollRdsDatabase.tsx', () => {
).toHaveBeenCalledTimes(1);
expect(DatabaseService.prototype.createDatabase).toHaveBeenCalledTimes(1);
});
+
+ test('auto enroll (self-hosted) is on by default', async () => {
+ cfg.isCloud = false;
+ jest.spyOn(integrationService, 'fetchAwsRdsDatabases').mockResolvedValue({
+ databases: mockAwsDbs,
+ });
+ jest
+ .spyOn(integrationService, 'fetchAwsRdsRequiredVpcs')
+ .mockResolvedValue({});
+
+ render();
+
+ // select a region from selector.
+ const selectEl = screen.getByLabelText(/aws region/i);
+ fireEvent.focus(selectEl);
+ fireEvent.keyDown(selectEl, { key: 'ArrowDown', keyCode: 40 });
+ fireEvent.click(screen.getByText('us-east-2'));
+
+ // Only self-hosted need to define a discovery group name.
+ await screen.findByText(/define a discovery group name/i);
+ // There should be no talbe rendered.
+ expect(screen.queryByText(/rds-1/i)).not.toBeInTheDocument();
+
+ act(() => screen.getByText('Next').click());
+ await screen.findByText(/Creating Auto Discovery Config/i);
+ expect(integrationService.fetchAwsRdsRequiredVpcs).toHaveBeenCalledTimes(1);
+ expect(discoveryService.createDiscoveryConfig).toHaveBeenCalledTimes(1);
+
+ // 2D array:
+ // First array is the array of calls, we are only interested in the first.
+ // Second array are the parameters that this api got called with,
+ // we are interested in the second parameter.
+ expect(createDiscoveryConfig.mock.calls[0][1]['discoveryGroup']).toBe(
+ 'aws-prod'
+ );
+
+ expect(DatabaseService.prototype.createDatabase).not.toHaveBeenCalled();
+ });
+
+ test('auto enroll disabled (self-hosted), creates database', async () => {
+ cfg.isCloud = false;
+ jest.spyOn(integrationService, 'fetchAwsRdsDatabases').mockResolvedValue({
+ databases: mockAwsDbs,
+ });
+
+ render();
+
+ // select a region from selector.
+ const selectEl = screen.getByLabelText(/aws region/i);
+ fireEvent.focus(selectEl);
+ fireEvent.keyDown(selectEl, { key: 'ArrowDown', keyCode: 40 });
+ fireEvent.click(screen.getByText('us-east-2'));
+
+ await screen.findByText(/define a discovery group name/i);
+
+ // disable auto enroll
+ act(() => screen.getByText(/auto-enroll all/i).click());
+ expect(screen.getByText('Next')).toBeDisabled();
+
+ act(() => screen.getByRole('radio').click());
+
+ act(() => screen.getByText('Next').click());
+ await screen.findByText(/Database "rds-1" successfully registered/i);
+
+ expect(discoveryService.createDiscoveryConfig).not.toHaveBeenCalled();
+ expect(
+ DatabaseService.prototype.fetchDatabaseServices
+ ).toHaveBeenCalledTimes(1);
+ expect(DatabaseService.prototype.createDatabase).toHaveBeenCalledTimes(1);
+ });
});
const mockAwsDbs: AwsRdsDatabase[] = [
diff --git a/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.tsx b/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.tsx
index 69919bc78b2eb..0b9c6dbc60df5 100644
--- a/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.tsx
+++ b/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.tsx
@@ -15,14 +15,14 @@
*/
import React, { useState } from 'react';
-import { Box, Text, Toggle } from 'design';
+import { Box, Flex, Input, Text, Toggle } from 'design';
import { FetchStatus } from 'design/DataTable/types';
import { Danger } from 'design/Alert';
-
import useAttempt, { Attempt } from 'shared/hooks/useAttemptNext';
import { ToolTipInfo } from 'shared/components/ToolTip';
import { getErrMessage } from 'shared/utils/errorType';
+import { TextSelectCopyMulti } from 'teleport/components/TextSelectCopy';
import { DbMeta, useDiscover } from 'teleport/Discover/useDiscover';
import {
AwsRdsDatabase,
@@ -42,8 +42,9 @@ import {
createDiscoveryConfig,
} from 'teleport/services/discovery';
import useTeleport from 'teleport/useTeleport';
+import { Tabs } from 'teleport/components/Tabs';
-import { ActionButtons, Header, Mark } from '../../Shared';
+import { ActionButtons, Header, Mark, StyledBox } from '../../Shared';
import { useCreateDatabase } from '../CreateDatabase/useCreateDatabase';
import { CreateDatabaseDialog } from '../CreateDatabase/CreateDatabaseDialog';
@@ -99,9 +100,12 @@ export function EnrollRdsDatabase() {
fetchStatus: 'disabled',
});
const [selectedDb, setSelectedDb] = useState();
- const [wantAutoDiscover, setWantAutoDiscover] = useState(() => cfg.isCloud);
+ const [wantAutoDiscover, setWantAutoDiscover] = useState(true);
const [autoDiscoveryCfg, setAutoDiscoveryCfg] = useState();
const [requiredVpcs, setRequiredVpcs] = useState>();
+ const [discoveryGroupName, setDiscoveryGroupName] = useState(() =>
+ cfg.isCloud ? '' : 'aws-prod'
+ );
function fetchDatabasesWithNewRegion(region: Regions) {
// Clear table when fetching with new region.
@@ -238,7 +242,9 @@ export function EnrollRdsDatabase() {
try {
discoveryConfig = await createDiscoveryConfig(clusterId, {
name: crypto.randomUUID(),
- discoveryGroup: DISCOVERY_GROUP_CLOUD,
+ discoveryGroup: cfg.isCloud
+ ? DISCOVERY_GROUP_CLOUD
+ : discoveryGroupName,
aws: [
{
types: ['rds'],
@@ -333,7 +339,17 @@ export function EnrollRdsDatabase() {
}
const hasIamPermError = isIamPermError(fetchDbAttempt);
- const showTable = !hasIamPermError && tableData.currRegion;
+ const showContent = !hasIamPermError && tableData.currRegion;
+ const showAutoEnrollToggle = fetchDbAttempt.status === 'success';
+
+ // (Temp)
+ // Self hosted auto enroll is different from cloud.
+ // For cloud, we already run the discovery service for customer.
+ // For on-prem, user has to run their own discovery service.
+ // We hide the RDS table for on-prem if they are wanting auto discover
+ // because it takes up so much space to give them instructions.
+ // Future work will simply provide user a script so we can show the table then.
+ const showTable = cfg.isCloud || !wantAutoDiscover;
return (
@@ -350,23 +366,28 @@ export function EnrollRdsDatabase() {
clear={clear}
disableSelector={fetchDbAttempt.status === 'processing'}
/>
- {showTable && (
+ {showContent && (
<>
- {cfg.isCloud && (
+ {showAutoEnrollToggle && (
setWantAutoDiscover(b => !b)}
isDisabled={tableData.items.length === 0}
+ discoveryGroupName={discoveryGroupName}
+ setDiscoveryGroupName={setDiscoveryGroupName}
+ clusterPublicUrl={ctx.storeUser.state.cluster.publicURL}
+ />
+ )}
+ {showTable && (
+
)}
-
>
)}
{hasIamPermError && (
@@ -378,7 +399,7 @@ export function EnrollRdsDatabase() {
/>
)}
- {showTable && wantAutoDiscover && (
+ {showContent && showAutoEnrollToggle && wantAutoDiscover && (
Note: Auto-enroll will enroll all database engines
in this region (e.g. PostgreSQL, MySQL, Aurora).
@@ -390,7 +411,8 @@ export function EnrollRdsDatabase() {
fetchDbAttempt.status === 'processing' ||
(!wantAutoDiscover && !selectedDb) ||
hasIamPermError ||
- fetchDbAttempt.status === 'failed'
+ fetchDbAttempt.status === 'failed' ||
+ (!cfg.isCloud && !discoveryGroupName)
}
/>
{DialogComponent}
@@ -411,14 +433,28 @@ function getRdsEngineIdentifier(engine: DatabaseEngine): RdsEngineIdentifier {
}
}
+const discoveryGroupToolTip = `Discovery group name is used to group discovered resources into different sets. \
+This parameter is used to prevent Discovery Agents watching different sets of cloud resources from \
+colliding against each other and deleting resources created by another services.`;
+
+const discoveryServiceToolTip = `The Discovery Service, is responsible for watching your \
+cloud provider and checking if there are any new databases or if there have been any \
+modifications to previously discovered databases.`;
+
function ToggleSection({
wantAutoDiscover,
toggleWantAutoDiscover,
isDisabled,
+ discoveryGroupName,
+ setDiscoveryGroupName,
+ clusterPublicUrl,
}: {
wantAutoDiscover: boolean;
isDisabled: boolean;
toggleWantAutoDiscover(): void;
+ discoveryGroupName: string;
+ setDiscoveryGroupName(n: string): void;
+ clusterPublicUrl: string;
}) {
return (
@@ -436,6 +472,157 @@ function ToggleSection({
infrastructure.
+ {!cfg.isCloud && wantAutoDiscover && (
+
+ )}
);
}
+
+const SelfHostedAutoDiscoverDirections = ({
+ clusterPublicUrl,
+ discoveryGroupName,
+ setDiscoveryGroupName,
+}: {
+ clusterPublicUrl: string;
+ discoveryGroupName: string;
+ setDiscoveryGroupName(n: string): void;
+}) => {
+ const yamlContent = `version: v3
+teleport:
+ join_params:
+ token_name: ""
+ method: token
+ proxy_server: "${clusterPublicUrl}"
+auth_service:
+ enabled: off
+proxy_service:
+ enabled: off
+ssh_service:
+ enabled: off
+discovery_service:
+ enabled: "yes"
+ discovery_group: "${discoveryGroupName}"`;
+
+ return (
+
+
+
+ Auto-enrolling requires you to configure a{' '}
+ Discovery Service
+
+
+
+
+
+ Step 1: Create a Join Token
+
+ Run the following command against your Teleport Auth Service and save
+ it in /tmp/token on the host that will run the Discovery
+ Service.
+
+
+
+
+
+
+ Step 2: Define a Discovery Group name{' '}
+
+
+
+
+ setDiscoveryGroupName(e.target.value)}
+ hasError={discoveryGroupName.length == 0}
+ />
+
+
+
+
+ Step 3: Create a teleport.yaml file
+
+
+ Use this template to create a teleport.yaml on the host
+ that will run the Discovery Service.
+
+
+
+
+
+ Step 4: Start Discovery Service
+
+
+ Configure the Discovery Service to start automatically when the host
+ boots up by creating a systemd service for it. The instructions depend
+ on how you installed the Discovery Service.
+
+
+
+ On the host where you will run the Discovery Service, enable
+ and start Teleport:
+
+
+
+ ),
+ },
+ {
+ title: `TAR Archive`,
+ content: (
+
+
+ On the host where you will run the Discovery Service, create
+ a systemd service configuration for Teleport, enable the
+ Teleport service, and start Teleport:
+
+
+
+ ),
+ },
+ ]}
+ />
+
+ You can check the status of the Discovery Service with{' '}
+ systemctl status teleport and view its logs with{' '}
+ journalctl -fu teleport.
+
+
+
+ );
+};
diff --git a/web/packages/teleport/src/Discover/Fixtures/databases.tsx b/web/packages/teleport/src/Discover/Fixtures/databases.tsx
index 5902959d9ee04..4a1d735ed6b9d 100644
--- a/web/packages/teleport/src/Discover/Fixtures/databases.tsx
+++ b/web/packages/teleport/src/Discover/Fixtures/databases.tsx
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React, { PropsWithChildren } from 'react';
+import React from 'react';
import {
DatabaseEngine,
@@ -94,7 +94,7 @@ export function getDbMeta(): DbMeta {
};
}
-export const ComponentWrapper: React.FC = ({ children }) => (
+export const ComponentWrapper: React.FC = ({ children }) => (