From b0c8905bf69363fb3ee4c843764040fc5033efa5 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 9 May 2019 00:35:02 -0600 Subject: [PATCH 1/7] WIP: reduce abstraction --- README.md | 172 +++++++++++++++++++++++++---------- db/index.js | 46 +++------- greenlock-store-sequelize.js | 31 ++++--- sync.js | 9 +- 4 files changed, 154 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index 35b9411..d52245d 100644 --- a/README.md +++ b/README.md @@ -2,70 +2,142 @@ > A database-driven Greenlock storage plugin with wildcard support. +## Features + +* Many [Supported SQL Databases](http://docs.sequelizejs.com/manual/getting-started.html) + * [x] PostgreSQL (**best**) + * [x] SQLite3 (**easiest**) + * [x] Microsoft SQL Server (mssql) + * [x] MySQL, MariaDB +* Works on all platforms + * [x] Mac, Linux, VPS + * [x] AWS, Heroku, Akkeris, Docker + * [x] Windows + ## Usage -To use, provide this Greenlock storage plugin as the `store` attribute when you -invoke `create`. +To use, provide this Greenlock storage plugin as the `store` option when you +invoke `create`: ```js -greenlock.create({ - store: require('le-store-sequelize') +Greenlock.create({ + store: require('greenlock-store-sequelize') + ... }); ``` ## Configuration -### Defaults +* SQLite3 (default) +* Database URLs / Connection Strings +* Environment variables +* Table Prefixes -No configuration is required. By default, you'll get a baked-in Sequelize -database running [sqlite3](https://www.npmjs.com/package/sqlite3). +### SQLite3 (default) -### Database Connection +SQLite3 is the default database, however, since it has a large number of dependencies +and may require a native module to be built, you must explicitly install +[sqlite3](https://www.npmjs.com/package/sqlite3): -Without `config.dbOptions`, the baked-in sequelize object uses sqlite3 with -defaults. If `config.dbOptions` is provided, you can configure the database -connection per the Sequelize documentation. +```bash +npm install --save sqlite3 +``` -If a dialect other than sqlite3 is used, dependencies will need to be -installed. +The default db file will be written wherever Greenlock's `configDir` is set to, +which is probably `~/acme` or `~/letsencrypt`. -```javascript -greenlock.create({ - store: require('le-store-sequelize')({ - dbConfig: { - username: 'mysqluser', - password: 'mysqlpassword', - database: 'mysqldatabase, - host: '127.0.0.1', - dialect: 'mysql' +```bash +~/acme/db.sqlite3 +``` + +If you wish to set special options you may do so by passing a pre-configured `Sequelize` instance: + +```js +var Sequelize = require('sequelize'); +var db = new Sequelize({ dialect: 'sqlite', storage: '/Users/me/acme/db.sqlite3' }); + +Greenlock.create({ + store: require('greenlock-store-sequelize').create({ db: db }) + ... +}); +``` + +### Database URL Connection String + +You may use database URLs (also known as 'connection strings') to initialize sequelize: + +```js +var Sequelize = require('sequelize'); +var db = new Sequelize('postgres://user:pass@hostname:port/database'); + +Greenlock.create({ + store: require('greenlock-store-sequelize').create({ db: db }) + ... +}); +``` + +If you need to use **custom options**, just instantiate sequelize directly: + +```js +var dbUrl = 'postgres://user:pass@hostname:port/database'; + +Greenlock.create({ + store: require('greenlock-store-sequelize').create({ storeDatabaseUrl: dbUrl }) + ... +}); +``` + +For more information, see the [Sequelize Getting Started](http://docs.sequelizejs.com/manual/getting-started.html) docs. + +### ENVs (i.e. for Docker, Heroku, Akkeris) + +If your database connection string is in an environment variable, +you would use the usual standard for your platform. + +For example, if you're using Heroku, Akkeris, or Docker you're +database connection string is probably `DATABASE_URL`, so you'd do something like this: + +```js +var Sequelize = require('sequelize'); +var databaseUrl = process.env['DATABASE_URL']; +var db = new Sequelize(databaseUrl); + +Greenlock.create({ + store: require('greenlock-store-sequelize').create({ db: db }) + ... +}); +``` + +### Table Prefixes + +The default table names are as follows: + +* Keypair +* Domain +* Certificate +* Chain + +If you'd like to add a table name prefix or define a specific schema within the database (PostgreSQL, SQL Server), +you can do so like this: + +```js +var Sequelize = require('sequelize'); +var databaseUrl = process.env['DATABASE_URL']; +var db = new Sequelize(databaseUrl, { + define: { + hooks: { + beforeDefine: function (name, attrs) { + console.log(name); + console.log(attrs); + attrs.tableName = 'MyPrefix' + attrs.tableName; + //attrs.schema = 'public'; + } + } } - }) -}); -``` - -The database can also be configured using an env variable. - -```javascript -greenlock.create({ - store: require('greenlock-store-sequelize')({ - dbConfig: { - use_env_variable: 'DB_URL' - } - }) -}); -``` - -### Custom Database Object - -If you already have a Sequelize object, you can pass that in as `config.db`, -circumventing the baked-in database entirely. - -```javascript -var db = require('./db'); // your db - -greenlock.create({ - store: require('le-store-sequelize')({ - db - }) +}); + +Greenlock.create({ + store: require('greenlock-store-sequelize').create({ db: db }) + ... }); ``` diff --git a/db/index.js b/db/index.js index 89495db..a804e0d 100644 --- a/db/index.js +++ b/db/index.js @@ -1,49 +1,25 @@ 'use strict'; -var fs = require('fs'); var path = require('path'); -var basename = path.basename(__filename); -var Sequelize = require('sequelize'); var sync = require('../sync.js'); -module.exports = function (config) { +module.exports = function (sequelize) { var db = {}; - db.Sequelize = Sequelize; - - if (!config) { - config = { - dialect: "sqlite", - storage: "./db.sqlite" - }; - } - - if (config.use_env_variable) { - db.sequelize = new db.Sequelize(process.env[config.use_env_variable], config); - } - else { - db.sequelize = new db.Sequelize(config.database, config.username, config.password, config); - } - - fs.readdirSync(__dirname).filter(function (file) { - return ('.' !== file[0]) && (file !== basename) && (file.slice(-3) === '.js'); - }).forEach(function (file) { - var model = db.sequelize['import'](path.join(__dirname, file)); + [ 'keypair.js' + , 'domain.js' + , 'certificate.js' + , 'chain.js' + ].forEach(function (file) { + var model = sequelize['import'](path.join(__dirname, file)); db[model.name] = model; }); Object.keys(db).forEach(function (modelName) { - if (db[modelName].associate) { - db[modelName].associate(db); - } + db[modelName].associate(db); }); - var synced = false; - if (!synced) { - return sync(db).then(function () { - synced = true; - return db; - }); - } - return Promise.resolve(db); + return sync(db).then(function () { + return db; + }); }; diff --git a/greenlock-store-sequelize.js b/greenlock-store-sequelize.js index 2edd794..5631e7d 100644 --- a/greenlock-store-sequelize.js +++ b/greenlock-store-sequelize.js @@ -6,21 +6,30 @@ module.exports.create = function (config={}) { accounts: {}, certificates: {} }; + var Sequelize; + var sequelize = config.db; + var confDir = config.configDir || (require('os').homedir() + '/acme'); // The user can provide their own db, but if they don't, we'll use the // baked-in db. - if (!config.db) { + if (!sequelize) { // If the user provides options for the baked-in db, we'll use them. If // they don't, we'll use the baked-in db with its defaults. - config.db = require('./db')(config.dbConfig || null); - } - else { - // This library expects config.db to resolve the db object. We'll ensure - // that this is the case with the provided db, as it was with the baked-in - // db. - config.db = Promise.resolve(config.db); + Sequelize = require('sequelize'); + if (config.storeDatabaseUrl) { + sequelize = new Sequelize(config.storeDatabaseUrl); + } else { + sequelize = new Sequelize({ dialect: 'sqlite', storage: confDir + '/db.sqlite3' }); + } } + // This library expects config.db to resolve the db object. We'll ensure + // that this is the case with the provided db, as it was with the baked-in + // db. + config.db = Promise.resolve(sequelize).then(function (sequelize) { + return require('./db')(sequelize); + }); + store.certificates.check = function (opts) { return config.db.then(function (db) { return db.Certificate.findOne({ @@ -49,7 +58,7 @@ module.exports.create = function (config={}) { err.code = 'ENOENT'; throw err; }).catch(function (err) { - if (err.code == 'ENOENT') { + if (err.code === 'ENOENT') { return null; } throw err; @@ -77,7 +86,7 @@ module.exports.create = function (config={}) { err.code = 'ENOENT'; throw err; }).catch(function (err) { - if (err.code == 'ENOENT') { + if (err.code === 'ENOENT') { return null; } throw err; @@ -119,7 +128,7 @@ module.exports.create = function (config={}) { err.code = 'ENOENT'; throw err; }).catch(function (err) { - if (err.code == 'ENOENT') { + if (err.code === 'ENOENT') { return null; } throw err; diff --git a/sync.js b/sync.js index 7544b20..7155806 100644 --- a/sync.js +++ b/sync.js @@ -6,17 +6,10 @@ function sync(db) { function next() { var modelName = keys.shift(); if (!modelName) { return; } - if (isModel(modelName)) { - return db[modelName].sync().then(next); - } - return next(); + return db[modelName].sync().then(next); } return Promise.resolve().then(next); } -function isModel(key) { - return !(['sequelize','Sequelize'].includes(key)); -} - module.exports = sync; -- 2.38.5 From 191ed410e491d2f1da1dcb76b512bc335ff6fb1d Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 9 May 2019 00:48:57 -0600 Subject: [PATCH 2/7] document table --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/README.md b/README.md index d52245d..c7fb4ac 100644 --- a/README.md +++ b/README.md @@ -141,3 +141,43 @@ Greenlock.create({ ... }); ``` + +## Table Structure + +This is the table structure that's created. + +```sql +CREATE TABLE `Keypairs` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `xid` VARCHAR(255) UNIQUE, + `content` TEXT, + `createdAt` DATETIME NOT NULL, + `updatedAt` DATETIME NOT NULL); + +CREATE TABLE `Domains` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `subject` VARCHAR(255) UNIQUE, + `altnames` TEXT, + `createdAt` DATETIME NOT NULL, + `updatedAt` DATETIME NOT NULL); + +CREATE TABLE `Certificates` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `subject` VARCHAR(255) UNIQUE, + `cert` TEXT, + `issuedAt` DATETIME, + `expiresAt` DATETIME, + `altnames` TEXT, + `chain` TEXT, + `createdAt` DATETIME NOT NULL, + `updatedAt` DATETIME NOT NULL); + +CREATE TABLE `Chains` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `xid` VARCHAR(255) UNIQUE, + `content` TEXT, + `createdAt` DATETIME NOT NULL, + `updatedAt` DATETIME NOT NULL, + `CertificateId` INTEGER REFERENCES + `Certificates` (`id`) ON DELETE SET NULL ON UPDATE CASCADE); +``` -- 2.38.5 From 25013e78649a8af03354ca5b7346bbdd41fa867d Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 9 May 2019 01:04:08 -0600 Subject: [PATCH 3/7] singularity strikes again! --- README.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c7fb4ac..64a1d18 100644 --- a/README.md +++ b/README.md @@ -124,14 +124,10 @@ you can do so like this: var Sequelize = require('sequelize'); var databaseUrl = process.env['DATABASE_URL']; var db = new Sequelize(databaseUrl, { - define: { - hooks: { - beforeDefine: function (name, attrs) { - console.log(name); - console.log(attrs); - attrs.tableName = 'MyPrefix' + attrs.tableName; - //attrs.schema = 'public'; - } + hooks: { + beforeDefine: function (columns, model) { + model.tableName = 'MyPrefix' + model.name.plural; + //model.schema = 'public'; } } }); -- 2.38.5 From 5cf90c1109ee801584096ac3f0963c014c65d767 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 9 May 2019 01:07:16 -0600 Subject: [PATCH 4/7] example swap --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 64a1d18..f49ca22 100644 --- a/README.md +++ b/README.md @@ -67,11 +67,10 @@ Greenlock.create({ You may use database URLs (also known as 'connection strings') to initialize sequelize: ```js -var Sequelize = require('sequelize'); -var db = new Sequelize('postgres://user:pass@hostname:port/database'); +var dbUrl = 'postgres://user:pass@hostname:port/database'; Greenlock.create({ - store: require('greenlock-store-sequelize').create({ db: db }) + store: require('greenlock-store-sequelize').create({ storeDatabaseUrl: dbUrl }) ... }); ``` @@ -79,10 +78,11 @@ Greenlock.create({ If you need to use **custom options**, just instantiate sequelize directly: ```js -var dbUrl = 'postgres://user:pass@hostname:port/database'; +var Sequelize = require('sequelize'); +var db = new Sequelize('postgres://user:pass@hostname:port/database'); Greenlock.create({ - store: require('greenlock-store-sequelize').create({ storeDatabaseUrl: dbUrl }) + store: require('greenlock-store-sequelize').create({ db: db }) ... }); ``` -- 2.38.5 From f8c78271c2173ac6b4e2309a072926fea1f92dc1 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 9 May 2019 01:11:10 -0600 Subject: [PATCH 5/7] toc --- README.md | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index f49ca22..33d61a0 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,7 @@ Greenlock.create({ ## Configuration -* SQLite3 (default) -* Database URLs / Connection Strings -* Environment variables -* Table Prefixes - -### SQLite3 (default) +
SQLite3 (default) SQLite3 is the default database, however, since it has a large number of dependencies and may require a native module to be built, you must explicitly install @@ -61,9 +56,9 @@ Greenlock.create({ ... }); ``` +
-### Database URL Connection String - +
Database URLs / Connection Strings You may use database URLs (also known as 'connection strings') to initialize sequelize: ```js @@ -88,9 +83,9 @@ Greenlock.create({ ``` For more information, see the [Sequelize Getting Started](http://docs.sequelizejs.com/manual/getting-started.html) docs. +
-### ENVs (i.e. for Docker, Heroku, Akkeris) - +
Environment variables (AWS, Docker, Heroku, Akkeris) If your database connection string is in an environment variable, you would use the usual standard for your platform. @@ -107,9 +102,9 @@ Greenlock.create({ ... }); ``` +
-### Table Prefixes - +
Table Prefixes The default table names are as follows: * Keypair @@ -137,6 +132,7 @@ Greenlock.create({ ... }); ``` +
## Table Structure -- 2.38.5 From 0e75d96a8f46286ab8043fbbf5e491cafc3b1711 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 9 May 2019 01:25:32 -0600 Subject: [PATCH 6/7] clarify --- README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 33d61a0..765a592 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,29 @@ Greenlock.create({ ``` +
PostgreSQL, SQL Server, and lesser databases... + +The general format of a DATABASE_URL is something like this: + +> `schema://user:pass@server:port/service?option=foo` + +For example: + +> `postgres://aj:secret123@127.0.0.1:5432/greenlock` + +For each database the exact format may be slightly different: + +* `postgres://user:pass@hostname:port/database?option=foo` +* `sqlserver://user:pass@datasource:port/instance/catalog?database=dbname` +* `mssql://user:pass@hostname:port/database?option=foo` +* `mysql://user:pass@hostname:port/database?option=foo` +* `mariadb://user:pass@hostname:port/database?option=foo` + +There's also a way to specify objects instead of using the standard connection strings. + +See the next section for more information. +
+
Database URLs / Connection Strings You may use database URLs (also known as 'connection strings') to initialize sequelize: @@ -82,7 +105,8 @@ Greenlock.create({ }); ``` -For more information, see the [Sequelize Getting Started](http://docs.sequelizejs.com/manual/getting-started.html) docs. +See the [Sequelize Getting Started](http://docs.sequelizejs.com/manual/getting-started.html) docs for more info +on database options for sequelize.
Environment variables (AWS, Docker, Heroku, Akkeris) -- 2.38.5 From a470a72e6bbfc7aff40bc472e6ef5867784ed6a2 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 9 May 2019 01:26:24 -0600 Subject: [PATCH 7/7] clarify --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 765a592..b12b3c4 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,7 @@ For example: For each database the exact format may be slightly different: * `postgres://user:pass@hostname:port/database?option=foo` -* `sqlserver://user:pass@datasource:port/instance/catalog?database=dbname` -* `mssql://user:pass@hostname:port/database?option=foo` +* `sqlserver://user:pass@datasource:port/instance/catalog?database=dbname` (mssql) * `mysql://user:pass@hostname:port/database?option=foo` * `mariadb://user:pass@hostname:port/database?option=foo` -- 2.38.5