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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 73 additions & 7 deletions .github/workflows/integration_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@ permissions:
contents: read # This is required for actions/checkout

jobs:
run-integration-tests:
name: Run Integration Tests
run-integration-tests-default:
name: Run Integration Tests (Default)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
versions: [ "default", "latest" ]
dbEngine: ["aurora-mysql", "aurora-postgres" ]
dbEngine: ["aurora-mysql", "aurora-postgres"]

steps:
- name: Clone repository
Expand Down Expand Up @@ -64,8 +63,75 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ steps.creds.outputs.aws-access-key-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.creds.outputs.aws-secret-access-key }}
AWS_SESSION_TOKEN: ${{ steps.creds.outputs.aws-session-token }}
AURORA_MYSQL_DB_ENGINE_VERSION: ${{ matrix.dbEngine }}
AURORA_PG_DB_ENGINE_VERSION: ${{ matrix.versions }}
AURORA_MYSQL_DB_ENGINE_VERSION: default
AURORA_PG_DB_ENGINE_VERSION: default

- name: "Get Github Action IP"
if: always()
id: ip
uses: haythem/public-ip@v1.3

- name: "Remove Github Action IP"
if: always()
run: |
aws ec2 revoke-security-group-ingress \
--group-name default \
--protocol -1 \
--port -1 \
--cidr ${{ steps.ip.outputs.ipv4 }}/32 \
2>&1 > /dev/null;

- name: Archive results
if: always()
uses: actions/upload-artifact@v4
with:
name: integration-report-default-${{ matrix.dbEngine }}
path: ./tests/integration/container/reports
retention-days: 5

run-integration-tests-latest:
name: Run Integration Tests (Latest)
runs-on: ubuntu-latest
needs: run-integration-tests-default
strategy:
fail-fast: false
matrix:
dbEngine: ["aurora-mysql", "aurora-postgres" ]

steps:
- name: Clone repository
uses: actions/checkout@v4
- name: "Set up JDK 8"
uses: actions/setup-java@v3
with:
distribution: "corretto"
java-version: 8
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"
- name: Install dependencies
run: npm install --no-save

- name: Configure AWS Credentials
id: creds
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_DEPLOY_ROLE }}
role-session-name: nodejs_int_latest_tests
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
output-credentials: true

- name: Run Integration Tests
run: |
./gradlew --no-parallel --no-daemon test-${{ matrix.dbEngine }} --info
env:
RDS_DB_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
AWS_ACCESS_KEY_ID: ${{ steps.creds.outputs.aws-access-key-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.creds.outputs.aws-secret-access-key }}
AWS_SESSION_TOKEN: ${{ steps.creds.outputs.aws-session-token }}
AURORA_MYSQL_DB_ENGINE_VERSION: latest
AURORA_PG_DB_ENGINE_VERSION: latest

- name: "Get Github Action IP"
if: always()
Expand All @@ -86,6 +152,6 @@ jobs:
if: always()
uses: actions/upload-artifact@v4
with:
name: integration-report-default-${{ matrix.dbEngine }}-${{ matrix.versions}}
name: integration-report-latest-${{ matrix.dbEngine }}
path: ./tests/integration/container/reports
retention-days: 5
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"semi": true,
"trailingComma": "none",
"printWidth": 150,
"endOfLine": "lf"
"endOfLine": "auto"
}
23 changes: 10 additions & 13 deletions common/lib/authentication/aws_secrets_manager_plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,21 +129,18 @@ export class AwsSecretsManagerPlugin extends AbstractConnectionPlugin implements
this.pluginService.updateConfigWithProperties(props);
return await connectFunc();
} catch (error) {
if (error instanceof Error) {
if ((error.message.includes("password authentication failed") || error.message.includes("Access denied")) && !secretWasFetched) {
// Login unsuccessful with cached credentials
// Try to re-fetch credentials and try again

secretWasFetched = await this.updateSecret(true);
if (secretWasFetched) {
WrapperProperties.USER.set(props, this.secret?.username ?? "");
WrapperProperties.PASSWORD.set(props, this.secret?.password ?? "");
return await connectFunc();
}
if ((error.message.includes("password authentication failed") || error.message.includes("Access denied")) && !secretWasFetched) {
// Login unsuccessful with cached credentials
// Try to re-fetch credentials and try again

secretWasFetched = await this.updateSecret(true);
if (secretWasFetched) {
WrapperProperties.USER.set(props, this.secret?.username ?? "");
WrapperProperties.PASSWORD.set(props, this.secret?.password ?? "");
return await connectFunc();
}
logger.debug(Messages.get("AwsSecretsManagerConnectionPlugin.unhandledError", error.name, error.message));
}
throw error;
logger.debug(Messages.get("AwsSecretsManagerConnectionPlugin.unhandledError", error.name, error.message));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@
*/

import { ConnectionPluginFactory } from "../plugin_factory";
import { PluginService } from "../plugin_service";
import { ConnectionPlugin } from "../connection_plugin";
import { AwsWrapperError } from "../utils/errors";
import { Messages } from "../utils/messages";
import { FullServicesContainer } from "../utils/full_services_container";

export class AwsSecretsManagerPluginFactory extends ConnectionPluginFactory {
private static awsSecretsManagerPlugin: any;

async getInstance(pluginService: PluginService, properties: Map<string, any>): Promise<ConnectionPlugin> {
async getInstance(servicesContainer: FullServicesContainer, properties: Map<string, any>): Promise<ConnectionPlugin> {
try {
if (!AwsSecretsManagerPluginFactory.awsSecretsManagerPlugin) {
AwsSecretsManagerPluginFactory.awsSecretsManagerPlugin = await import("./aws_secrets_manager_plugin");
}
return new AwsSecretsManagerPluginFactory.awsSecretsManagerPlugin.AwsSecretsManagerPlugin(pluginService, new Map(properties));
return new AwsSecretsManagerPluginFactory.awsSecretsManagerPlugin.AwsSecretsManagerPlugin(servicesContainer.pluginService, new Map(properties));
} catch (error: any) {
if (error.code === "MODULE_NOT_FOUND") {
throw new AwsWrapperError(Messages.get("ConnectionPluginChainBuilder.errorImportingPlugin", error.message, "AwsSecretsManagerPlugin"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@
*/

import { ConnectionPluginFactory } from "../plugin_factory";
import { PluginService } from "../plugin_service";
import { ConnectionPlugin } from "../connection_plugin";
import { AwsWrapperError } from "../utils/errors";
import { Messages } from "../utils/messages";
import { FullServicesContainer } from "../utils/full_services_container";

export class IamAuthenticationPluginFactory extends ConnectionPluginFactory {
private static iamAuthenticationPlugin: any;

async getInstance(pluginService: PluginService, properties: object): Promise<ConnectionPlugin> {
async getInstance(servicesContainer: FullServicesContainer, properties: object): Promise<ConnectionPlugin> {
try {
if (!IamAuthenticationPluginFactory.iamAuthenticationPlugin) {
IamAuthenticationPluginFactory.iamAuthenticationPlugin = await import("./iam_authentication_plugin");
}
return new IamAuthenticationPluginFactory.iamAuthenticationPlugin.IamAuthenticationPlugin(pluginService);
return new IamAuthenticationPluginFactory.iamAuthenticationPlugin.IamAuthenticationPlugin(servicesContainer.pluginService);
} catch (error: any) {
throw new AwsWrapperError(Messages.get("ConnectionPluginChainBuilder.errorImportingPlugin", error.message, "IamAuthenticationPlugin"));
}
Expand Down
57 changes: 33 additions & 24 deletions common/lib/aws_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,39 @@
limitations under the License.
*/

import { PluginServiceManagerContainer } from "./plugin_service_manager_container";
import { PluginService, PluginServiceImpl } from "./plugin_service";
import { PluginService } from "./plugin_service";
import { DatabaseDialect, DatabaseType } from "./database_dialect/database_dialect";
import { ConnectionUrlParser } from "./utils/connection_url_parser";
import { HostListProvider } from "./host_list_provider/host_list_provider";
import { PluginManager } from "./plugin_manager";

import pkgStream from "stream";
import { ClientWrapper } from "./client_wrapper";
import { ConnectionProviderManager } from "./connection_provider_manager";
import { DefaultTelemetryFactory } from "./utils/telemetry/default_telemetry_factory";
import { TelemetryFactory } from "./utils/telemetry/telemetry_factory";
import { DriverDialect } from "./driver_dialect/driver_dialect";
import { WrapperProperties } from "./wrapper_property";
import { DriverConfigurationProfiles } from "./profile/driver_configuration_profiles";
import { ConfigurationProfile } from "./profile/configuration_profile";
import { AwsWrapperError, TransactionIsolationLevel, ConnectionProvider } from "./";
import { AwsWrapperError, ConnectionProvider, TransactionIsolationLevel } from "./";
import { Messages } from "./utils/messages";
import { HostListProviderService } from "./host_list_provider_service";
import { SessionStateClient } from "./session_state_client";
import { DriverConnectionProvider } from "./driver_connection_provider";
import { ServiceUtils } from "./utils/service_utils";
import { StorageService } from "./utils/storage/storage_service";
import { MonitorService } from "./utils/monitoring/monitor_service";
import { CoreServicesContainer } from "./utils/core_services_container";
import { FullServicesContainer } from "./utils/full_services_container";
import { EventPublisher } from "./utils/events/event";

const { EventEmitter } = pkgStream;

export abstract class AwsClient extends EventEmitter implements SessionStateClient {
private _defaultPort: number = -1;
private readonly fullServiceContainer: FullServicesContainer;
private readonly storageService: StorageService;
private readonly monitorService: MonitorService;
private readonly eventPublisher: EventPublisher;
protected telemetryFactory: TelemetryFactory;
protected pluginManager: PluginManager;
protected pluginService: PluginService;
Expand All @@ -67,7 +71,7 @@ export abstract class AwsClient extends EventEmitter implements SessionStateClie

this.properties = new Map<string, any>(Object.entries(config));

this.storageService = CoreServicesContainer.getInstance().getStorageService();
this.storageService = CoreServicesContainer.getInstance().storageService;

const profileName = WrapperProperties.PROFILE_NAME.get(this.properties);
if (profileName && profileName.length > 0) {
Expand Down Expand Up @@ -103,22 +107,27 @@ export abstract class AwsClient extends EventEmitter implements SessionStateClie
}
}

const coreServicesContainer: CoreServicesContainer = CoreServicesContainer.getInstance();
this.storageService = coreServicesContainer.storageService;
this.monitorService = coreServicesContainer.monitorService;
this.eventPublisher = coreServicesContainer.eventPublisher;
this.telemetryFactory = new DefaultTelemetryFactory(this.properties);
const container = new PluginServiceManagerContainer();
this.pluginService = new PluginServiceImpl(
container,

this.fullServiceContainer = ServiceUtils.instance.createStandardServiceContainer(
this.storageService,
this.monitorService,
this.eventPublisher,
this,
this.properties,
dbType,
knownDialectsByCode,
this.properties,
this._configurationProfile?.getDriverDialect() ?? driverDialect
);
this.pluginManager = new PluginManager(
container,
this.properties,
new ConnectionProviderManager(connectionProvider ?? new DriverConnectionProvider(), WrapperProperties.CONNECTION_PROVIDER.get(this.properties)),
this.telemetryFactory
this._configurationProfile?.getDriverDialect() ?? driverDialect,
this.telemetryFactory,
connectionProvider
);

this.pluginService = this.fullServiceContainer.pluginService;
this.pluginManager = this.fullServiceContainer.pluginManager;
}

private async setup() {
Expand All @@ -130,12 +139,12 @@ export abstract class AwsClient extends EventEmitter implements SessionStateClie
await this.setup();
const hostListProvider: HostListProvider = this.pluginService
.getDialect()
.getHostListProvider(this.properties, this.properties.get("host"), <HostListProviderService>(<unknown>this.pluginService));
.getHostListProvider(this.properties, this.properties.get("host"), this.fullServiceContainer);
this.pluginService.setHostListProvider(hostListProvider);
await this.pluginService.refreshHostList();
const initialHostInfo = this.pluginService.getInitialConnectionHostInfo();
if (initialHostInfo != null) {
await this.pluginManager.initHostProvider(initialHostInfo, this.properties, <HostListProviderService>(<unknown>this.pluginService));
await this.pluginManager.initHostProvider(initialHostInfo, this.properties, this.fullServiceContainer.hostListProviderService);
await this.pluginService.refreshHostList();
}
}
Expand All @@ -159,23 +168,23 @@ export abstract class AwsClient extends EventEmitter implements SessionStateClie

abstract setReadOnly(readOnly: boolean): Promise<any | void>;

abstract isReadOnly(): boolean;
abstract isReadOnly(): boolean | undefined;

abstract setAutoCommit(autoCommit: boolean): Promise<any | void>;

abstract getAutoCommit(): boolean;
abstract getAutoCommit(): boolean | undefined;

abstract setTransactionIsolation(level: TransactionIsolationLevel): Promise<any | void>;

abstract getTransactionIsolation(): TransactionIsolationLevel;
abstract getTransactionIsolation(): TransactionIsolationLevel | undefined;

abstract setSchema(schema: any): Promise<any | void>;

abstract getSchema(): string;
abstract getSchema(): string | undefined;

abstract setCatalog(catalog: string): Promise<any | void>;

abstract getCatalog(): string;
abstract getCatalog(): string | undefined;

abstract end(): Promise<any>;

Expand Down
28 changes: 14 additions & 14 deletions common/lib/connection_info.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License").
You may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { ClientWrapper } from "./client_wrapper";

Expand Down
Loading
Loading