Merge pull request #10550 from Budibase/budi-6932/verify_couchdb
Implement couchdb connection verification
This commit is contained in:
commit
9e6f22653b
|
@ -12,7 +12,7 @@ import {
|
||||||
isDocument,
|
isDocument,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { getCouchInfo } from "./connections"
|
import { getCouchInfo } from "./connections"
|
||||||
import { directCouchCall } from "./utils"
|
import { directCouchUrlCall } from "./utils"
|
||||||
import { getPouchDB } from "./pouchDB"
|
import { getPouchDB } from "./pouchDB"
|
||||||
import { WriteStream, ReadStream } from "fs"
|
import { WriteStream, ReadStream } from "fs"
|
||||||
import { newid } from "../../docIds/newid"
|
import { newid } from "../../docIds/newid"
|
||||||
|
@ -46,6 +46,8 @@ export class DatabaseImpl implements Database {
|
||||||
private readonly instanceNano?: Nano.ServerScope
|
private readonly instanceNano?: Nano.ServerScope
|
||||||
private readonly pouchOpts: DatabaseOpts
|
private readonly pouchOpts: DatabaseOpts
|
||||||
|
|
||||||
|
private readonly couchInfo = getCouchInfo()
|
||||||
|
|
||||||
constructor(dbName?: string, opts?: DatabaseOpts, connection?: string) {
|
constructor(dbName?: string, opts?: DatabaseOpts, connection?: string) {
|
||||||
if (dbName == null) {
|
if (dbName == null) {
|
||||||
throw new Error("Database name cannot be undefined.")
|
throw new Error("Database name cannot be undefined.")
|
||||||
|
@ -53,8 +55,8 @@ export class DatabaseImpl implements Database {
|
||||||
this.name = dbName
|
this.name = dbName
|
||||||
this.pouchOpts = opts || {}
|
this.pouchOpts = opts || {}
|
||||||
if (connection) {
|
if (connection) {
|
||||||
const couchInfo = getCouchInfo(connection)
|
this.couchInfo = getCouchInfo(connection)
|
||||||
this.instanceNano = buildNano(couchInfo)
|
this.instanceNano = buildNano(this.couchInfo)
|
||||||
}
|
}
|
||||||
if (!DatabaseImpl.nano) {
|
if (!DatabaseImpl.nano) {
|
||||||
DatabaseImpl.init()
|
DatabaseImpl.init()
|
||||||
|
@ -67,7 +69,11 @@ export class DatabaseImpl implements Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
async exists() {
|
async exists() {
|
||||||
let response = await directCouchCall(`/${this.name}`, "HEAD")
|
const response = await directCouchUrlCall({
|
||||||
|
url: `${this.couchInfo.url}/${this.name}`,
|
||||||
|
method: "HEAD",
|
||||||
|
cookie: this.couchInfo.cookie,
|
||||||
|
})
|
||||||
return response.status === 200
|
return response.status === 200
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,21 +4,21 @@ export const getCouchInfo = (connection?: string) => {
|
||||||
const urlInfo = getUrlInfo(connection)
|
const urlInfo = getUrlInfo(connection)
|
||||||
let username
|
let username
|
||||||
let password
|
let password
|
||||||
if (env.COUCH_DB_USERNAME) {
|
if (urlInfo.auth?.username) {
|
||||||
// set from env
|
|
||||||
username = env.COUCH_DB_USERNAME
|
|
||||||
} else if (urlInfo.auth.username) {
|
|
||||||
// set from url
|
// set from url
|
||||||
username = urlInfo.auth.username
|
username = urlInfo.auth.username
|
||||||
|
} else if (env.COUCH_DB_USERNAME) {
|
||||||
|
// set from env
|
||||||
|
username = env.COUCH_DB_USERNAME
|
||||||
} else if (!env.isTest()) {
|
} else if (!env.isTest()) {
|
||||||
throw new Error("CouchDB username not set")
|
throw new Error("CouchDB username not set")
|
||||||
}
|
}
|
||||||
if (env.COUCH_DB_PASSWORD) {
|
if (urlInfo.auth?.password) {
|
||||||
// set from env
|
|
||||||
password = env.COUCH_DB_PASSWORD
|
|
||||||
} else if (urlInfo.auth.password) {
|
|
||||||
// set from url
|
// set from url
|
||||||
password = urlInfo.auth.password
|
password = urlInfo.auth.password
|
||||||
|
} else if (env.COUCH_DB_PASSWORD) {
|
||||||
|
// set from env
|
||||||
|
password = env.COUCH_DB_PASSWORD
|
||||||
} else if (!env.isTest()) {
|
} else if (!env.isTest()) {
|
||||||
throw new Error("CouchDB password not set")
|
throw new Error("CouchDB password not set")
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,20 @@ export async function directCouchCall(
|
||||||
) {
|
) {
|
||||||
let { url, cookie } = getCouchInfo()
|
let { url, cookie } = getCouchInfo()
|
||||||
const couchUrl = `${url}/${path}`
|
const couchUrl = `${url}/${path}`
|
||||||
|
return await directCouchUrlCall({ url: couchUrl, cookie, method, body })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function directCouchUrlCall({
|
||||||
|
url,
|
||||||
|
cookie,
|
||||||
|
method,
|
||||||
|
body,
|
||||||
|
}: {
|
||||||
|
url: string
|
||||||
|
cookie: string
|
||||||
|
method: string
|
||||||
|
body?: any
|
||||||
|
}) {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
method: method,
|
method: method,
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -19,7 +33,7 @@ export async function directCouchCall(
|
||||||
params.body = JSON.stringify(body)
|
params.body = JSON.stringify(body)
|
||||||
params.headers["Content-Type"] = "application/json"
|
params.headers["Content-Type"] = "application/json"
|
||||||
}
|
}
|
||||||
return await fetch(checkSlashesInUrl(encodeURI(couchUrl)), params)
|
return await fetch(checkSlashesInUrl(encodeURI(url)), params)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function directCouchQuery(
|
export async function directCouchQuery(
|
||||||
|
|
|
@ -63,21 +63,28 @@ const SCHEMA: Integration = {
|
||||||
}
|
}
|
||||||
|
|
||||||
class CouchDBIntegration implements IntegrationBase {
|
class CouchDBIntegration implements IntegrationBase {
|
||||||
private config: CouchDBConfig
|
private readonly client: dbCore.DatabaseImpl
|
||||||
private readonly client: any
|
|
||||||
|
|
||||||
constructor(config: CouchDBConfig) {
|
constructor(config: CouchDBConfig) {
|
||||||
this.config = config
|
|
||||||
this.client = dbCore.DatabaseWithConnection(config.database, config.url)
|
this.client = dbCore.DatabaseWithConnection(config.database, config.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async testConnection() {
|
||||||
|
try {
|
||||||
|
const result = await this.query("exists", "validation error", {})
|
||||||
|
return result === true
|
||||||
|
} catch (e: any) {
|
||||||
|
return { error: e.message as string }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async query(
|
async query(
|
||||||
command: string,
|
command: string,
|
||||||
errorMsg: string,
|
errorMsg: string,
|
||||||
query: { json?: object; id?: string }
|
query: { json?: object; id?: string }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
return await this.client[command](query.id || query.json)
|
return await (this.client as any)[command](query.id || query.json)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(errorMsg, err)
|
console.error(errorMsg, err)
|
||||||
throw err
|
throw err
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { GenericContainer } from "testcontainers"
|
||||||
|
import { generator } from "@budibase/backend-core/tests"
|
||||||
|
|
||||||
|
import couchdb from "../../../../packages/server/src/integrations/couchdb"
|
||||||
|
|
||||||
|
describe("datasource validators", () => {
|
||||||
|
describe("couchdb", () => {
|
||||||
|
let url: string
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const user = generator.first()
|
||||||
|
const password = generator.hash()
|
||||||
|
|
||||||
|
const container = await new GenericContainer("budibase/couchdb")
|
||||||
|
.withExposedPorts(5984)
|
||||||
|
.withEnv("COUCHDB_USER", user)
|
||||||
|
.withEnv("COUCHDB_PASSWORD", password)
|
||||||
|
.start()
|
||||||
|
|
||||||
|
const host = container.getContainerIpAddress()
|
||||||
|
const port = container.getMappedPort(5984)
|
||||||
|
|
||||||
|
await container.exec([
|
||||||
|
`curl`,
|
||||||
|
`-u`,
|
||||||
|
`${user}:${password}`,
|
||||||
|
`-X`,
|
||||||
|
`PUT`,
|
||||||
|
`localhost:5984/db`,
|
||||||
|
])
|
||||||
|
url = `http://${user}:${password}@${host}:${port}`
|
||||||
|
})
|
||||||
|
|
||||||
|
it("test valid connection string", async () => {
|
||||||
|
const integration = new couchdb.integration({
|
||||||
|
url,
|
||||||
|
database: "db",
|
||||||
|
})
|
||||||
|
const result = await integration.testConnection()
|
||||||
|
expect(result).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("test invalid database", async () => {
|
||||||
|
const integration = new couchdb.integration({
|
||||||
|
url,
|
||||||
|
database: "random_db",
|
||||||
|
})
|
||||||
|
const result = await integration.testConnection()
|
||||||
|
expect(result).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("test invalid url", async () => {
|
||||||
|
const integration = new couchdb.integration({
|
||||||
|
url: "http://invalid:123",
|
||||||
|
database: "any",
|
||||||
|
})
|
||||||
|
const result = await integration.testConnection()
|
||||||
|
expect(result).toEqual({
|
||||||
|
error:
|
||||||
|
"request to http://invalid:123/any failed, reason: getaddrinfo ENOTFOUND invalid",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue