diff --git a/cloud-sql/postgres/knex/README.md b/cloud-sql/postgres/knex/README.md index 857bc2a337..3efd6a1651 100644 --- a/cloud-sql/postgres/knex/README.md +++ b/cloud-sql/postgres/knex/README.md @@ -106,20 +106,13 @@ Then use this command to launch the proxy in the background: ### Testing the application -2. Next, install the Node.js packages necessary to run the app locally by running the following command: +1. Next, install the Node.js packages necessary to run the app locally by running the following command: ``` npm install ``` -3. Run `createTable.js` to create the database table the app requires and to ensure that the database is properly configured. -With the Cloud SQL proxy running, run the following command to create the sample app's table in your Cloud SQL PostgreSQL database: - - ``` - node createTable.js $DB_USER $DB_PW $DB_NAME $CLOUD_SQL_CONNECTION_NAME - ``` - -4. Run the sample app locally with the following command: +2. Run the sample app locally with the following command: ``` npm start diff --git a/cloud-sql/postgres/knex/createTable.js b/cloud-sql/postgres/knex/createTable.js deleted file mode 100644 index 4f515b82c4..0000000000 --- a/cloud-sql/postgres/knex/createTable.js +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2018 Google LLC -// -// 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. - -'use strict'; - -const Knex = require('knex'); - -const createTable = async config => { - const socketPath = process.env.DB_SOCKET_PATH || '/cloudsql'; - const tableName = config.tableName; - - // Connect to the database - if (config.dbHost) { - const dbSocketAddr = config.dbHost.split(':'); - config.host = dbSocketAddr[0]; - config.port = dbSocketAddr[1]; - } else { - config.host = `${socketPath}/${config.connectionName}`; - } - - const knex = Knex({client: 'pg', connection: config}); - - // Create the "votes" table - try { - await knex.schema.createTable(tableName, table => { - table.bigIncrements('vote_id').notNull(); - table.timestamp('time_cast').notNull(); - table.specificType('candidate', 'CHAR(6) NOT NULL'); - }); - - console.log(`Successfully created '${tableName}' table.`); - return knex.destroy(); - } catch (err) { - console.log(`Failed to create '${tableName}' table: ${err}`); - console.error(`Failed to create '${tableName}' table:`, err); - if (knex) { - knex.destroy(); - } - } -}; - -require('yargs') - .command( - '* [dbHost]', - 'Create a table', - {}, - createTable - ) - .help().argv; diff --git a/cloud-sql/postgres/knex/server.js b/cloud-sql/postgres/knex/server.js index 3ed432d64b..06389a3b40 100644 --- a/cloud-sql/postgres/knex/server.js +++ b/cloud-sql/postgres/knex/server.js @@ -49,8 +49,21 @@ const logger = winston.createLogger({ // testing different configurations. let pool; +app.use(async (req, res, next) => { + if (pool) { + return next(); + } + try { + pool = await createPoolAndEnsureSchema(); + next(); + } catch (err) { + logger.error(err); + return next(err); + } +}); + // [START cloud_sql_postgres_knex_create_tcp] -const createTcpPool = config => { +const createTcpPool = async config => { // Extract host and port from socket address const dbSocketAddr = process.env.DB_HOST.split(':'); // e.g. '127.0.0.1:5432' @@ -71,7 +84,7 @@ const createTcpPool = config => { // [END cloud_sql_postgres_knex_create_tcp] // [START cloud_sql_postgres_knex_create_socket] -const createUnixSocketPool = config => { +const createUnixSocketPool = async config => { const dbSocketPath = process.env.DB_SOCKET_PATH || '/cloudsql'; // Establish a connection to the database @@ -90,7 +103,7 @@ const createUnixSocketPool = config => { // [END cloud_sql_postgres_knex_create_socket] // Initialize Knex, a Node.js SQL query builder library with built-in connection pooling. -const createPool = () => { +const createPool = async () => { // Configure which instance and what database user to connect with. // Remember - storing secrets in plaintext is potentially unsafe. Consider using // something like https://cloud.google.com/kms/ to help keep secrets secret. @@ -133,6 +146,29 @@ const createPool = () => { } }; +const ensureSchema = async pool => { + const hasTable = await pool.schema.hasTable('votes'); + if (!hasTable) { + return pool.schema.createTable('votes', table => { + table.increments('vote_id').primary(); + table.timestamp('time_cast', 30).notNullable(); + table.specificType('candidate', 'CHAR(6)').notNullable(); + }); + } + logger.info("Ensured that table 'votes' exists"); +}; + +const createPoolAndEnsureSchema = async () => + await createPool() + .then(async pool => { + await ensureSchema(pool); + return pool; + }) + .catch(err => { + logger.error(err); + throw err; + }); + // [START cloud_sql_postgres_knex_connection] /** * Insert a vote record into the database. @@ -177,7 +213,7 @@ const getVoteCount = async (pool, candidate) => { }; app.get('/', async (req, res) => { - pool = pool || createPool(); + pool = pool || (await createPoolAndEnsureSchema()); try { // Query the total count of "TABS" from the database. const tabsResult = await getVoteCount(pool, 'TABS'); @@ -222,7 +258,7 @@ app.get('/', async (req, res) => { }); app.post('/', async (req, res) => { - pool = pool || createPool(); + pool = pool || (await createPoolAndEnsureSchema()); // Get the team from the request and record the time of the vote. const {team} = req.body; const timestamp = new Date(); diff --git a/cloud-sql/postgres/knex/test/createTable.test.js b/cloud-sql/postgres/knex/test/createTable.test.js deleted file mode 100644 index 9df6f2d9d2..0000000000 --- a/cloud-sql/postgres/knex/test/createTable.test.js +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2018 Google LLC -// -// 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. - -'use strict'; - -const assert = require('assert'); -const path = require('path'); -const Knex = require('knex'); -const {exec} = require('child_process'); - -const cwd = path.join(__dirname, '..'); - -const {DB_USER, DB_PASS, DB_NAME, INSTANCE_CONNECTION_NAME, DB_HOST} = - process.env; -const SOCKET_PATH = process.env.DB_SOCKET_PATH || '/cloudsql'; - -let knex; - -before(async () => { - const connection = { - user: DB_USER, - password: DB_PASS, - database: DB_NAME, - }; - - if (DB_HOST) { - const dbSocketAddr = process.env.DB_HOST.split(':'); - connection.host = dbSocketAddr[0]; - connection.port = dbSocketAddr[1]; - } else { - connection.host = `${SOCKET_PATH}/${INSTANCE_CONNECTION_NAME}`; - } - - try { - knex = Knex({ - client: 'pg', - connection, - }); - } catch (err) { - console.log(err.message); - } -}); - -after(async () => { - await knex.schema.dropTable('votes_tcp'); - await knex.schema.dropTable('votes_unix'); - knex.destroy(); -}); - -it('should create a table over tcp', done => { - assert.notStrictEqual(DB_USER, undefined); - assert.notStrictEqual(DB_PASS, undefined); - assert.notStrictEqual(DB_NAME, undefined); - assert.notStrictEqual(DB_HOST, undefined); - exec( - `node createTable.js ${DB_USER} ${DB_PASS} ${DB_NAME} ${INSTANCE_CONNECTION_NAME} votes_tcp ${DB_HOST}`, - {cwd}, - (err, stdout) => { - assert.ok(stdout.startsWith("Successfully created 'votes_tcp' table.")); - done(); - } - ); -}); - -it('should create a table via unix', done => { - assert.notStrictEqual(DB_USER, undefined); - assert.notStrictEqual(DB_PASS, undefined); - assert.notStrictEqual(DB_NAME, undefined); - assert.notStrictEqual(INSTANCE_CONNECTION_NAME, undefined); - exec( - `node createTable.js ${DB_USER} ${DB_PASS} ${DB_NAME} ${INSTANCE_CONNECTION_NAME} votes_unix`, - {cwd}, - (err, stdout) => { - assert.ok(stdout.includes("Successfully created 'votes_unix' table.")); - done(); - } - ); -}); - -it('should handle existing tables', done => { - exec( - `node createTable.js ${DB_USER} ${DB_PASS} ${DB_NAME} ${INSTANCE_CONNECTION_NAME} votes ${DB_HOST}`, - {cwd}, - (err, stdout, stderr) => { - assert.ok(stderr.includes("Failed to create 'votes' table:")); - assert.ok(stderr.includes('already exists')); - done(); - } - ); -});