wip
This commit is contained in:
parent
ab8ec8f09d
commit
b0c1d7ee6d
|
@ -3,4 +3,5 @@ MYSQL_SHA=sha256:9de9d54fecee6253130e65154b930978b1fcc336bcc86dfd06e89b72a2588eb
|
||||||
POSTGRES_SHA=sha256:bd0d8e485d1aca439d39e5ea99b931160bd28d862e74c786f7508e9d0053090e
|
POSTGRES_SHA=sha256:bd0d8e485d1aca439d39e5ea99b931160bd28d862e74c786f7508e9d0053090e
|
||||||
MONGODB_SHA=sha256:afa36bca12295b5f9dae68a493c706113922bdab520e901bd5d6c9d7247a1d8d
|
MONGODB_SHA=sha256:afa36bca12295b5f9dae68a493c706113922bdab520e901bd5d6c9d7247a1d8d
|
||||||
MARIADB_SHA=sha256:e59ba8783bf7bc02a4779f103bb0d8751ac0e10f9471089709608377eded7aa8
|
MARIADB_SHA=sha256:e59ba8783bf7bc02a4779f103bb0d8751ac0e10f9471089709608377eded7aa8
|
||||||
ELASTICSEARCH_SHA=sha256:9a6443f55243f6acbfeb4a112d15eb3b9aac74bf25e0e39fa19b3ddd3a6879d0
|
ELASTICSEARCH_SHA=sha256:9a6443f55243f6acbfeb4a112d15eb3b9aac74bf25e0e39fa19b3ddd3a6879d0
|
||||||
|
DYNAMODB_SHA=sha256:cf8cebd061f988628c02daff10fdb950a54478feff9c52f6ddf84710fe3c3906
|
|
@ -17,7 +17,7 @@ import {
|
||||||
import { DynamoDB } from "@aws-sdk/client-dynamodb"
|
import { DynamoDB } from "@aws-sdk/client-dynamodb"
|
||||||
import { AWS_REGION } from "../constants"
|
import { AWS_REGION } from "../constants"
|
||||||
|
|
||||||
interface DynamoDBConfig {
|
export interface DynamoDBConfig {
|
||||||
region: string
|
region: string
|
||||||
accessKeyId: string
|
accessKeyId: string
|
||||||
secretAccessKey: string
|
secretAccessKey: string
|
||||||
|
@ -138,9 +138,9 @@ const SCHEMA: Integration = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
class DynamoDBIntegration implements IntegrationBase {
|
export class DynamoDBIntegration implements IntegrationBase {
|
||||||
private config: DynamoDBConfig
|
private config: DynamoDBConfig
|
||||||
private client
|
private client: DynamoDBDocument
|
||||||
|
|
||||||
constructor(config: DynamoDBConfig) {
|
constructor(config: DynamoDBConfig) {
|
||||||
this.config = config
|
this.config = config
|
||||||
|
|
|
@ -1,167 +1,149 @@
|
||||||
jest.mock("@aws-sdk/lib-dynamodb", () => ({
|
import { Datasource } from "@budibase/types"
|
||||||
DynamoDBDocument: {
|
import { DynamoDBConfig, DynamoDBIntegration } from "../dynamodb"
|
||||||
from: jest.fn(() => ({
|
import { DatabaseName, datasourceDescribe } from "./utils"
|
||||||
update: jest.fn(),
|
|
||||||
put: jest.fn(),
|
|
||||||
query: jest.fn(() => ({
|
|
||||||
Items: [],
|
|
||||||
})),
|
|
||||||
scan: jest.fn(() => ({
|
|
||||||
Items: [],
|
|
||||||
})),
|
|
||||||
delete: jest.fn(),
|
|
||||||
get: jest.fn(),
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
jest.mock("@aws-sdk/client-dynamodb")
|
|
||||||
import { default as DynamoDBIntegration } from "../dynamodb"
|
|
||||||
|
|
||||||
class TestConfiguration {
|
const describes = datasourceDescribe({ only: [DatabaseName.DYNAMODB] })
|
||||||
integration: any
|
|
||||||
|
|
||||||
constructor(config: any = {}) {
|
if (describes.length > 0) {
|
||||||
this.integration = new DynamoDBIntegration.integration(config)
|
describe.each(describes)("DynamoDB Integration", ({ dsProvider }) => {
|
||||||
}
|
let tableName = "Users"
|
||||||
}
|
let rawDatasource: Datasource
|
||||||
|
let dynamodb: DynamoDBIntegration
|
||||||
|
|
||||||
describe("DynamoDB Integration", () => {
|
beforeEach(async () => {
|
||||||
let config: any
|
const ds = await dsProvider()
|
||||||
let tableName = "Users"
|
rawDatasource = ds.rawDatasource!
|
||||||
|
dynamodb = new DynamoDBIntegration(
|
||||||
beforeEach(() => {
|
rawDatasource.config! as DynamoDBConfig
|
||||||
config = new TestConfiguration()
|
)
|
||||||
})
|
|
||||||
|
|
||||||
it("calls the create method with the correct params", async () => {
|
|
||||||
await config.integration.create({
|
|
||||||
table: tableName,
|
|
||||||
json: {
|
|
||||||
Name: "John",
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
expect(config.integration.client.put).toHaveBeenCalledWith({
|
|
||||||
TableName: tableName,
|
|
||||||
Name: "John",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("calls the read method with the correct params", async () => {
|
it.only("calls the create method with the correct params", async () => {
|
||||||
const indexName = "Test"
|
await dynamodb.create({
|
||||||
|
table: tableName,
|
||||||
const response = await config.integration.read({
|
json: {
|
||||||
table: tableName,
|
Name: "John",
|
||||||
index: indexName,
|
},
|
||||||
json: {},
|
})
|
||||||
})
|
})
|
||||||
expect(config.integration.client.query).toHaveBeenCalledWith({
|
|
||||||
TableName: tableName,
|
|
||||||
IndexName: indexName,
|
|
||||||
})
|
|
||||||
expect(response).toEqual([])
|
|
||||||
})
|
|
||||||
|
|
||||||
it("calls the scan method with the correct params", async () => {
|
it("calls the read method with the correct params", async () => {
|
||||||
const indexName = "Test"
|
const indexName = "Test"
|
||||||
|
|
||||||
const response = await config.integration.scan({
|
const response = await dynamodb.read({
|
||||||
table: tableName,
|
table: tableName,
|
||||||
index: indexName,
|
index: indexName,
|
||||||
json: {},
|
json: {},
|
||||||
|
})
|
||||||
|
expect(config.integration.client.query).toHaveBeenCalledWith({
|
||||||
|
TableName: tableName,
|
||||||
|
IndexName: indexName,
|
||||||
|
})
|
||||||
|
expect(response).toEqual([])
|
||||||
})
|
})
|
||||||
expect(config.integration.client.scan).toHaveBeenCalledWith({
|
|
||||||
TableName: tableName,
|
|
||||||
IndexName: indexName,
|
|
||||||
})
|
|
||||||
expect(response).toEqual([])
|
|
||||||
})
|
|
||||||
|
|
||||||
it("calls the get method with the correct params", async () => {
|
it("calls the scan method with the correct params", async () => {
|
||||||
await config.integration.get({
|
const indexName = "Test"
|
||||||
table: tableName,
|
|
||||||
json: {
|
const response = await dynamodb.scan({
|
||||||
|
table: tableName,
|
||||||
|
index: indexName,
|
||||||
|
json: {},
|
||||||
|
})
|
||||||
|
expect(config.integration.client.scan).toHaveBeenCalledWith({
|
||||||
|
TableName: tableName,
|
||||||
|
IndexName: indexName,
|
||||||
|
})
|
||||||
|
expect(response).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls the get method with the correct params", async () => {
|
||||||
|
await dynamodb.get({
|
||||||
|
table: tableName,
|
||||||
|
json: {
|
||||||
|
Id: 123,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(config.integration.client.get).toHaveBeenCalledWith({
|
||||||
|
TableName: tableName,
|
||||||
Id: 123,
|
Id: 123,
|
||||||
},
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(config.integration.client.get).toHaveBeenCalledWith({
|
it("calls the update method with the correct params", async () => {
|
||||||
TableName: tableName,
|
await dynamodb.update({
|
||||||
Id: 123,
|
table: tableName,
|
||||||
})
|
json: {
|
||||||
})
|
Name: "John",
|
||||||
|
},
|
||||||
it("calls the update method with the correct params", async () => {
|
})
|
||||||
await config.integration.update({
|
expect(config.integration.client.update).toHaveBeenCalledWith({
|
||||||
table: tableName,
|
TableName: tableName,
|
||||||
json: {
|
|
||||||
Name: "John",
|
Name: "John",
|
||||||
},
|
})
|
||||||
})
|
})
|
||||||
expect(config.integration.client.update).toHaveBeenCalledWith({
|
|
||||||
TableName: tableName,
|
|
||||||
Name: "John",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("calls the delete method with the correct params", async () => {
|
it("calls the delete method with the correct params", async () => {
|
||||||
await config.integration.delete({
|
await dynamodb.delete({
|
||||||
table: tableName,
|
table: tableName,
|
||||||
json: {
|
json: {
|
||||||
|
Name: "John",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(config.integration.client.delete).toHaveBeenCalledWith({
|
||||||
|
TableName: tableName,
|
||||||
Name: "John",
|
Name: "John",
|
||||||
},
|
})
|
||||||
})
|
})
|
||||||
expect(config.integration.client.delete).toHaveBeenCalledWith({
|
|
||||||
TableName: tableName,
|
it("configures the dynamoDB constructor based on an empty endpoint parameter", async () => {
|
||||||
Name: "John",
|
const config = {
|
||||||
|
region: "us-east-1",
|
||||||
|
accessKeyId: "test",
|
||||||
|
secretAccessKey: "test",
|
||||||
|
}
|
||||||
|
|
||||||
|
const integration: any = new DynamoDBIntegration.integration(config)
|
||||||
|
|
||||||
|
expect(integration.config).toEqual({
|
||||||
|
currentClockSkew: true,
|
||||||
|
...config,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("configures the dynamoDB constructor based on a localhost endpoint parameter", async () => {
|
||||||
|
const config = {
|
||||||
|
region: "us-east-1",
|
||||||
|
accessKeyId: "test",
|
||||||
|
secretAccessKey: "test",
|
||||||
|
endpoint: "localhost:8080",
|
||||||
|
}
|
||||||
|
|
||||||
|
const integration: any = new DynamoDBIntegration.integration(config)
|
||||||
|
|
||||||
|
expect(integration.config).toEqual({
|
||||||
|
region: "us-east-1",
|
||||||
|
currentClockSkew: true,
|
||||||
|
endpoint: "localhost:8080",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("configures the dynamoDB constructor based on a remote endpoint parameter", async () => {
|
||||||
|
const config = {
|
||||||
|
region: "us-east-1",
|
||||||
|
accessKeyId: "test",
|
||||||
|
secretAccessKey: "test",
|
||||||
|
endpoint: "dynamodb.aws.foo.net",
|
||||||
|
}
|
||||||
|
|
||||||
|
const integration = new DynamoDBIntegration.integration(config)
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
expect(integration.config).toEqual({
|
||||||
|
currentClockSkew: true,
|
||||||
|
...config,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
}
|
||||||
it("configures the dynamoDB constructor based on an empty endpoint parameter", async () => {
|
|
||||||
const config = {
|
|
||||||
region: "us-east-1",
|
|
||||||
accessKeyId: "test",
|
|
||||||
secretAccessKey: "test",
|
|
||||||
}
|
|
||||||
|
|
||||||
const integration: any = new DynamoDBIntegration.integration(config)
|
|
||||||
|
|
||||||
expect(integration.config).toEqual({
|
|
||||||
currentClockSkew: true,
|
|
||||||
...config,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("configures the dynamoDB constructor based on a localhost endpoint parameter", async () => {
|
|
||||||
const config = {
|
|
||||||
region: "us-east-1",
|
|
||||||
accessKeyId: "test",
|
|
||||||
secretAccessKey: "test",
|
|
||||||
endpoint: "localhost:8080",
|
|
||||||
}
|
|
||||||
|
|
||||||
const integration: any = new DynamoDBIntegration.integration(config)
|
|
||||||
|
|
||||||
expect(integration.config).toEqual({
|
|
||||||
region: "us-east-1",
|
|
||||||
currentClockSkew: true,
|
|
||||||
endpoint: "localhost:8080",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("configures the dynamoDB constructor based on a remote endpoint parameter", async () => {
|
|
||||||
const config = {
|
|
||||||
region: "us-east-1",
|
|
||||||
accessKeyId: "test",
|
|
||||||
secretAccessKey: "test",
|
|
||||||
endpoint: "dynamodb.aws.foo.net",
|
|
||||||
}
|
|
||||||
|
|
||||||
const integration = new DynamoDBIntegration.integration(config)
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
expect(integration.config).toEqual({
|
|
||||||
currentClockSkew: true,
|
|
||||||
...config,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { Datasource, SourceName } from "@budibase/types"
|
||||||
|
import { GenericContainer, Wait } from "testcontainers"
|
||||||
|
import { testContainerUtils } from "@budibase/backend-core/tests"
|
||||||
|
import { startContainer } from "."
|
||||||
|
import { DYNAMODB_IMAGE } from "./images"
|
||||||
|
import { DynamoDBConfig } from "../../dynamodb"
|
||||||
|
|
||||||
|
let ports: Promise<testContainerUtils.Port[]>
|
||||||
|
|
||||||
|
export async function getDatasource(): Promise<Datasource> {
|
||||||
|
if (!ports) {
|
||||||
|
ports = startContainer(
|
||||||
|
new GenericContainer(DYNAMODB_IMAGE)
|
||||||
|
.withExposedPorts(8000)
|
||||||
|
.withWaitStrategy(
|
||||||
|
Wait.forSuccessfulCommand(
|
||||||
|
// https://stackoverflow.com/a/77373799
|
||||||
|
`if [ "$(curl -s -o /dev/null -I -w ''%{http_code}'' http://localhost:8000)" == "400" ]; then exit 0; else exit 1; fi`
|
||||||
|
).withStartupTimeout(60000)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = (await ports).find(x => x.container === 8000)?.host
|
||||||
|
if (!port) {
|
||||||
|
throw new Error("DynamoDB port not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
const config: DynamoDBConfig = {
|
||||||
|
accessKeyId: "test",
|
||||||
|
secretAccessKey: "test",
|
||||||
|
region: "us-east-1",
|
||||||
|
endpoint: `http://127.0.0.1:${port}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "datasource",
|
||||||
|
source: SourceName.DYNAMODB,
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,3 +13,4 @@ export const POSTGRES_LEGACY_IMAGE = `postgres:9.5.25`
|
||||||
export const MONGODB_IMAGE = `mongo@${process.env.MONGODB_SHA}`
|
export const MONGODB_IMAGE = `mongo@${process.env.MONGODB_SHA}`
|
||||||
export const MARIADB_IMAGE = `mariadb@${process.env.MARIADB_SHA}`
|
export const MARIADB_IMAGE = `mariadb@${process.env.MARIADB_SHA}`
|
||||||
export const ELASTICSEARCH_IMAGE = `elasticsearch@${process.env.ELASTICSEARCH_SHA}`
|
export const ELASTICSEARCH_IMAGE = `elasticsearch@${process.env.ELASTICSEARCH_SHA}`
|
||||||
|
export const DYNAMODB_IMAGE = `amazon/dynamodb-local@${process.env.DYNAMODB_SHA}`
|
||||||
|
|
|
@ -7,6 +7,7 @@ import * as mssql from "./mssql"
|
||||||
import * as mariadb from "./mariadb"
|
import * as mariadb from "./mariadb"
|
||||||
import * as oracle from "./oracle"
|
import * as oracle from "./oracle"
|
||||||
import * as elasticsearch from "./elasticsearch"
|
import * as elasticsearch from "./elasticsearch"
|
||||||
|
import * as dynamodb from "./dynamodb"
|
||||||
import { testContainerUtils } from "@budibase/backend-core/tests"
|
import { testContainerUtils } from "@budibase/backend-core/tests"
|
||||||
import { Knex } from "knex"
|
import { Knex } from "knex"
|
||||||
import TestConfiguration from "../../../tests/utilities/TestConfiguration"
|
import TestConfiguration from "../../../tests/utilities/TestConfiguration"
|
||||||
|
@ -25,6 +26,7 @@ export enum DatabaseName {
|
||||||
ORACLE = "oracle",
|
ORACLE = "oracle",
|
||||||
SQS = "sqs",
|
SQS = "sqs",
|
||||||
ELASTICSEARCH = "elasticsearch",
|
ELASTICSEARCH = "elasticsearch",
|
||||||
|
DYNAMODB = "dynamodb",
|
||||||
}
|
}
|
||||||
|
|
||||||
const DATASOURCE_PLUS = [
|
const DATASOURCE_PLUS = [
|
||||||
|
@ -50,6 +52,7 @@ const providers: Record<DatabaseName, DatasourceProvider> = {
|
||||||
// rest
|
// rest
|
||||||
[DatabaseName.ELASTICSEARCH]: elasticsearch.getDatasource,
|
[DatabaseName.ELASTICSEARCH]: elasticsearch.getDatasource,
|
||||||
[DatabaseName.MONGODB]: mongodb.getDatasource,
|
[DatabaseName.MONGODB]: mongodb.getDatasource,
|
||||||
|
[DatabaseName.DYNAMODB]: dynamodb.getDatasource,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatasourceDescribeReturnPromise {
|
export interface DatasourceDescribeReturnPromise {
|
||||||
|
|
Loading…
Reference in New Issue