diff --git a/.prettierrc b/.prettierrc
index 2c5ec4a..bb2db00 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,7 +1,7 @@
{
"bracketSpacing": true,
"printWidth": 80,
- "singleQuote": true,
+ "singleQuote": false,
"tabWidth": 4,
"trailingComma": "none",
"useTabs": false
diff --git a/README.md b/README.md
index d43196c..c113dfa 100644
--- a/README.md
+++ b/README.md
@@ -1,113 +1,246 @@
# [greenlock-manager-test.js](https://git.rootprojects.org/root/greenlock-manager-test.js)
-A simple test suite for Greenlock manager plugins.
+A simple test suite for Greenlock v3 manager plugins.
# Greenlock Manager
-A greenlock manager is just a set of a few callbacks to keeps track of:
+A Greenlock Manager is responsible for tracking which domains
+belong on a certificate, when they are scheduled for renewal,
+and if they have been deleted.
-- **Default settings** that apply to all sites such as
- - `subscriberEmail`
- - `agreeToTerms`
- - `store` (the account key and ssl certificate store)
-- **Site settings** such as
- - `subject` (ex: example.com)
- - `altnames` (ex: example.com,www.example.com)
- - `renewAt` (ex: '45d')
- - `challenges` (plugins for 'http-01', 'dns-01', etc)
-
-The **callbacks** are:
-
-- `set({ subject, altnames, renewAt })` to save site details
-- `find({ subject, altnames, renewBefore })` which returns a list of matching sites (perhaps all sites)
-- `remove({ subject })` which marks a site as deleted
-- `defaults()` which either **gets** or **sets** the global configs that apply to all sites
-
-When do they get called? Well, whenever they need to.
-
-# Some Terminology
-
-- `subject` refers to the **primary domain** on an SSL certificate
-- `altnames` refers to the list of **domain names** on the certificate (including the subject)
-- `renewAt` is a pre-calculated value based on `expiresAt` or `issuedAt` on the certificate
-
-Those are the only values you really have to worry about.
-
-The rest you can make up for your own needs, or they're just opaque values you'll get from Greenlock.
-
-# Do you want to build a plugin?
-
-You can start _really_ simple: just make a file that exports a `create()` function:
-
-## A great first, failing plugin:
-
-`my-plugin.js`:
+It consists of two required functions:
```js
-'use strict';
-
-var MyManager = module.exports;
-
-MyManager.create = function(options) {
- console.log('The tests will make me stronger');
- return {};
-};
+set({ subject, altnames, renewAt, deletedAt });
```
-## The test suite from heaven
-
-You write your test file, run it,
-and then you get a play-by-play of what to do.
-
+```js
+get({ servername });
```
+
+However, if you implement `find({ subject, servernames, renewBefore })` (optional),
+you don't have to implement `get()`.
+
+
+Usage Details
+# How to use your plugin
+
+The **Right Way**:
+
+```bash
+npm install --save greenlack
+npx greenlock init --manager ./path-or-npm-name.js --manager-xxxx 'sets xxxx' --manager-yyyy 'set yyyy'
+```
+
+That creates a `.greenlockrc`, which is essentially the same as doing this:
+
+```js
+var Greenlock = require("greenlock");
+var greenlock = Greenlock.create({
+ // ...
+
+ manager: "./path-or-npm-name.js",
+ xxxx: "sets xxxx",
+ yyyy: "sets yyyy",
+ packageRoot: __dirname
+});
+```
+
+## Why no require?
+
+Okay, so you **expect** it to look like this:
+
+```js
+var Greenlock = require("greenlock");
+var greenlock = Greenlock.create({
+ // WRONG!!
+ manager: require("./path-or-npm-name.js").create({
+ someOptionYouWant: true
+ })
+});
+```
+
+**NOPE**!
+
+Greenlock is designed to so that the CLI tools, Web API, and JavaScript API
+can all work interdepedently, indpendently.
+
+Therefore the configuration has to go into serializable JSON rather than
+executable JavaScript.
+
+
+
+# Quick Start
+
+If you want to write a manager,
+the best way to start is by using one of the provided templates.
+
+```bash
npm install --save-dev greenlock-manager-test
+npx greenlock-manager-init
```
-`test.js`:
+It will generate a bare bones manager that passes the tests,
+(skipping all optional features), and a test file:
+
+manager.js
```js
-'use strict';
+"use strict";
+
+var Manager = module.exports;
+var db = {};
+
+Manager.create = function(opts) {
+var manager = {};
+
+ //
+ // REQUIRED (basic issuance)
+ //
+
+ // Get
+ manager.get = async function({ servername, wildname }) {
+ // Required: find the certificate with the subject of `servername`
+ // Optional (multi-domain certs support): find a certificate with `servername` as an altname
+ // Optional (wildcard support): find a certificate with `wildname` as an altname
+
+ // { subject, altnames, renewAt, deletedAt, challenges, ... }
+ return db[servername] || db[wildname];
+ };
+
+ // Set
+ manager.set = async function(opts) {
+ // { subject, altnames, renewAt, deletedAt }
+ // Required: updated `renewAt` and `deletedAt` for certificate matching `subject`
+
+ var site = db[opts.subject] || {};
+ db[opts.subject] = Object.assign(site, opts);
+ return null;
+ };
+
+ //
+ // Optional (Fully Automatic Renewal)
+ //
+ /*
+ manager.find = async function(opts) {
+ // { subject, servernames, altnames, renewBefore }
+
+ return [{ subject, altnames, renewAt, deletedAt }];
+ };
+ //*/
+
+ //
+ // Optional (Special Remove Functionality)
+ // The default behavior is to set `deletedAt`
+ //
+ /*
+ manager.remove = async function(opts) {
+ return mfs.remove(opts);
+ };
+ //*/
+
+ //
+ // Optional (special settings save)
+ // Implemented here because this module IS the fallback
+ //
+ /*
+ manager.defaults = async function(opts) {
+ if (opts) {
+ return setDefaults(opts);
+ }
+ return getDefaults();
+ };
+ //*/
+
+ //
+ // Optional (for common deps and/or async initialization)
+ //
+ /*
+ manager.init = async function(deps) {
+ manager.request = deps.request;
+ return null;
+ };
+ //*/
+
+ return manager;
-var Tester = require('greenlock-manager-test');
-var MyManager = require('./');
-var myConfigOptions = {
- someApiTokenForMyManager: 'xxx'
};
-Tester.test(MyManager, myConfigOptions)
- .then(function() {
- console.log('All Tests Passed');
+````
+
+
+manager.test.js
+```js
+"use strict";
+
+var Tester = require("greenlock-manager-test");
+
+var Manager = require("./manager.js");
+var config = {
+ configFile: "greenlock-manager-test.delete-me.json"
+};
+
+Tester.test(Manager, config)
+ .then(function(features) {
+ console.info("PASS");
+ console.info();
+ console.info("Optional Feature Support:");
+ features.forEach(function(feature) {
+ console.info(feature.supported ? "✓ (YES)" : "✘ (NO) ", feature.description);
+ });
+ console.info();
})
.catch(function(err) {
- console.error('Oops... something bad happened:');
- console.error(err);
+ console.error("Oops, you broke it. Here are the details:");
+ console.error(err.stack);
+ console.error();
+ console.error("That's all I know.");
});
+````
+
+
+
+```bash
+node manager.test.js
```
-You just follow the error messages and, which a little help from this README,
-bam!, you get a working plugin. It's insane!
+```txt
+PASS: get({ servername, wildname })
+PASS: set({ subject })
-# The lazy, hacky way.
+Optional Feature Support:
+✘ (NO) Multiple Domains per Certificate
+✘ (NO) Wildcard Certificates
+✘ (NO) Fully Automatic Renewal
+```
-If you're going to publish a module, you should pass the full test suite.
+# Optional Features
-If not, eh, you can be lazy.
+If you're publishing a module to the community,
+you should implement the full test suite (and it's not that hard).
-## Bare minimum...
-
-At a bare minimum, you must implement `find()` to return an array of `{ subject, altnames }`.
-
-For example:
+If you're only halfway through, you should note
+which features are supported and which aren't.
```js
-function find(argsToIgnore) {
- return Promise.resolve([
- { subject: 'example.com', altnames: ['example.com', 'www.example.com'] }
- ]);
-}
+find({ subject, servernames, renewBefore });
+defaults({ subscriberEmail, agreeToTerms, challenges, store, ... });
+defaults(); // as getter
```
-If that's absolutely all that you do, all of the other methods will be implemented around `greenlock-manager-fs`.
+- `find()` is used to get the full list of sites, for continuous fully automatic renewal.
+- `defaults()` exists so that the global config can be saved in the same place as the per-site config.
+- a proper `get()` should be able to search not just primary domains, but altnames as well.
+
+Additionally, you're manager may need an init or a _real_ delete - rather than just using `set({ deletedAt })`:
+
+```js
+init({ request });
+remove({ subject });
+```
+
+
+Full Implementation
# The Right Way™
@@ -122,7 +255,7 @@ module.exports.create = function(options) {
manager.set = async function(siteConfig) {
// You can see in the tests a sample of common values,
// but you don't really need to worry about it.
- var subject = siteConfig;
+ var subject = siteConfig.subject;
// Cherry pick what you like for indexing / search, and JSONify the rest
return mergeOrCreateSite(subject, siteConfig);
@@ -130,38 +263,31 @@ module.exports.create = function(options) {
// find the things you've saved before
- manager.find = async function({ subject, altnames, renewBefore }) {
+ manager.get = async function({ servername }) {
+ return getSiteByAltname(servername);
+ }
+ manager.find = async function({ subject, servernames, renewBefore }) {
var results = [];
var gotten = {};
if (subject) {
var site = await getSiteBySubject(subject);
- if (site) {
- results.push(site);
- gotten[site.subject] = true;
+ if (site && site.subject === subject) {
+ return [site];
}
}
- if (altnames) {
- var sites = await getSiteByAltnames(subject);
- sites.forEach(function() {});
- if (site) {
- if (!gotten[site.subject]) {
- results.push(site);
+ if (severnames) {
+ return await Promise.all(servernames.map(function (altname) {
+ var site = getSiteByAltname(subject);
+ if (site && !gotten[site.subject]) {
gotten[site.subject] = true;
+ return site;
}
- }
+ });
}
- if (subject || altnames) {
- return results;
- }
-
- if (renewBefore) {
- return getSitesThatShouldBeRenewedBefore(renewBefore);
- }
-
- return getAllSites();
+ return getSitesThatShouldBeRenewedBefore(renewBefore || Infinity);
};
// delete a site config
@@ -198,35 +324,4 @@ module.exports.create = function(options) {
};
```
-# How to use your plugin
-
-The **Right Way**:
-
-```js
-var Greenlock = require('greenlock');
-var greenlock = Greenlock.create({
- manager: '/absolute/path/to/manager'
- someOptionYouWant: true,
-});
-```
-
-## Why no require?
-
-Okay, so you **expect** it to look like this:
-
-```js
-var Greenlock = require('greenlock');
-var greenlock = Greenlock.create({
- // WRONG!!
- manager: require('./relative/path/to/manager').create({
- someOptionYouWant: true
- })
-});
-```
-
-**NOPE**!
-
-It just has to do with some plugin architecture decisions around making the configuration
-serializable.
-
-I may go back and add the other way, but this is how it is right now.
+
diff --git a/bin/init.js b/bin/init.js
new file mode 100644
index 0000000..eda5cd1
--- /dev/null
+++ b/bin/init.js
@@ -0,0 +1,35 @@
+"use strict";
+
+var fs = require("fs");
+var path = require("path");
+
+function tmpl() {
+ var src = path.join(__dirname, "tmpl/manager.tmpl.js");
+ var dst = path.join(process.cwd(), "./manager.js");
+
+ try {
+ fs.accessSync(dst);
+ console.warn("skip 'manager.js': already exists");
+ return;
+ } catch (e) {
+ fs.writeFileSync(dst, fs.readFileSync(src, "utf8"), "utf8");
+ console.info("wrote 'manager.js'");
+ }
+}
+
+function tmplTest() {
+ var srcTest = path.join(__dirname, "tmpl/manager.test.tmpl.js");
+ var dstTest = path.join(process.cwd(), "./manager.test.js");
+
+ try {
+ fs.accessSync(dstTest);
+ console.warn("skip 'manager.test.js': already exists");
+ return;
+ } catch (e) {
+ fs.writeFileSync(dstTest, fs.readFileSync(srcTest, "utf8"), "utf8");
+ console.info("wrote 'manager.test.js'");
+ }
+}
+
+tmpl();
+tmplTest();
diff --git a/bin/tmpl/manager.test.tmpl.js b/bin/tmpl/manager.test.tmpl.js
new file mode 100644
index 0000000..048b627
--- /dev/null
+++ b/bin/tmpl/manager.test.tmpl.js
@@ -0,0 +1,25 @@
+"use strict";
+
+var Tester = require("greenlock-manager-test");
+
+var Manager = require("./manager.js");
+var config = {
+ configFile: "greenlock-manager-test.delete-me.json"
+};
+
+Tester.test(Manager, config)
+ .then(function(features) {
+ console.info("PASS");
+ console.info();
+ console.info("Optional Feature Support:");
+ features.forEach(function(feature) {
+ console.info(feature.supported ? "✓ (YES)" : "✘ (NO) ", feature.description);
+ });
+ console.info();
+ })
+ .catch(function(err) {
+ console.error("Oops, you broke it. Here are the details:");
+ console.error(err.stack);
+ console.error();
+ console.error("That's all I know.");
+ });
diff --git a/bin/tmpl/manager.tmpl.js b/bin/tmpl/manager.tmpl.js
new file mode 100644
index 0000000..2c09d3e
--- /dev/null
+++ b/bin/tmpl/manager.tmpl.js
@@ -0,0 +1,78 @@
+"use strict";
+
+var Manager = module.exports;
+var db = {};
+
+Manager.create = function(opts) {
+ var manager = {};
+
+ //
+ // REQUIRED (basic issuance)
+ //
+
+ // Get
+ manager.get = async function({ servername, wildname }) {
+ // Required: find the certificate with the subject of `servername`
+ // Optional (multi-domain certs support): find a certificate with `servername` as an altname
+ // Optional (wildcard support): find a certificate with `wildname` as an altname
+
+ // { subject, altnames, renewAt, deletedAt, challenges, ... }
+ return db[servername] || db[wildname];
+ };
+
+ // Set
+ manager.set = async function(opts) {
+ // { subject, altnames, renewAt, deletedAt }
+ // Required: updated `renewAt` and `deletedAt` for certificate matching `subject`
+
+ var site = db[opts.subject] || {};
+ db[opts.subject] = Object.assign(site, opts);
+ return null;
+ };
+
+ //
+ // Optional (Fully Automatic Renewal)
+ //
+ /*
+ manager.find = async function(opts) {
+ // { subject, servernames, altnames, renewBefore }
+
+ return [{ subject, altnames, renewAt, deletedAt }];
+ };
+ //*/
+
+ //
+ // Optional (Special Remove Functionality)
+ // The default behavior is to set `deletedAt`
+ //
+ /*
+ manager.remove = async function(opts) {
+ return mfs.remove(opts);
+ };
+ //*/
+
+ //
+ // Optional (special settings save)
+ // Implemented here because this module IS the fallback
+ //
+ /*
+ manager.defaults = async function(opts) {
+ if (opts) {
+ return setDefaults(opts);
+ }
+ return getDefaults();
+ };
+ //*/
+
+ //
+ // Optional (for common deps and/or async initialization)
+ //
+ /*
+ manager.init = async function(deps) {
+ manager.request = deps.request;
+ return null;
+ };
+ //*/
+
+ return manager;
+};
diff --git a/package-lock.json b/package-lock.json
index b32efa6..7e5fa0b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,13 +1,32 @@
{
- "name": "greenlock-manager-test",
- "version": "3.0.0",
- "lockfileVersion": 1,
- "requires": true,
- "dependencies": {
- "@root/request": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@root/request/-/request-1.4.1.tgz",
- "integrity": "sha512-2zSP1v9VhJ3gvm4oph0C4BYCoM3Sj84/Wx4iKdt0IbqbJzfON04EodBq5dsV65UxO/aHZciUBwY2GCZcHqaTYg=="
+ "name": "greenlock-manager-test",
+ "version": "3.1.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@root/mkdirp": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz",
+ "integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA=="
+ },
+ "@root/request": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@root/request/-/request-1.4.1.tgz",
+ "integrity": "sha512-2zSP1v9VhJ3gvm4oph0C4BYCoM3Sj84/Wx4iKdt0IbqbJzfON04EodBq5dsV65UxO/aHZciUBwY2GCZcHqaTYg=="
+ },
+ "greenlock-manager-fs": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/greenlock-manager-fs/-/greenlock-manager-fs-3.0.5.tgz",
+ "integrity": "sha512-r/q+tEFuDwklfzPfiGhcIrHuJxMrppC+EseESpu5f0DMokh+1iZVm9nGC/VE7/7GETdOYfEYhhQkmspsi8Gr/A==",
+ "requires": {
+ "@root/mkdirp": "^1.0.0",
+ "safe-replace": "^1.1.0"
+ }
+ },
+ "safe-replace": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz",
+ "integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw=="
+ }
}
- }
}
diff --git a/package.json b/package.json
index 3154783..14a6a47 100644
--- a/package.json
+++ b/package.json
@@ -1,28 +1,32 @@
{
- "name": "greenlock-manager-test",
- "version": "3.0.0",
- "description": "A simple test suite for Greenlock manager plugins.",
- "main": "tester.js",
- "scripts": {
- "test": "node tests"
- },
- "files": [
- "*.js",
- "lib"
- ],
- "repository": {
- "type": "git",
- "url": "https://git.rootprojects.org/root/greenlock-manager-test.js"
- },
- "keywords": [
- "Greenlock",
- "manager",
- "plugin"
- ],
- "author": "AJ ONeal (https://coolaj86.com/)",
- "license": "MPL-2.0",
- "dependencies": {
- "@root/request": "^1.4.1",
- "greenlock-manager-fs": "^3.0.0"
- }
+ "name": "greenlock-manager-test",
+ "version": "3.1.0",
+ "description": "A simple test suite for Greenlock manager plugins.",
+ "main": "tester.js",
+ "scripts": {
+ "test": "node tests"
+ },
+ "bin": {
+ "greenlock-manager-init": "bin/init.js"
+ },
+ "files": [
+ "*.js",
+ "bin",
+ "lib"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "https://git.rootprojects.org/root/greenlock-manager-test.js"
+ },
+ "keywords": [
+ "Greenlock",
+ "manager",
+ "plugin"
+ ],
+ "author": "AJ ONeal (https://coolaj86.com/)",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "@root/request": "^1.4.1",
+ "greenlock-manager-fs": "^3.0.0"
+ }
}
diff --git a/tester.js b/tester.js
index 10e16ad..2522651 100644
--- a/tester.js
+++ b/tester.js
@@ -1,165 +1,441 @@
-'use strict';
+"use strict";
-var request = require('@root/request');
+var request = require("@root/request");
+
+// For most tests
+var siteSubject = "xx.com";
+var siteAltname = "www.foo.xx.com";
+var siteWildname = "*.xx.com";
+var siteMatch = "foo.xx.com";
+var domains = [siteSubject, siteAltname, siteWildname];
+
+// Similar, but non-matching subjects
+var noExistWild = "*.foo.xx.com";
+var noExistAlt = "bar.xx.com";
+
+// For wildcard-as-subject test
+var siteWildnameNet = "*.xx.net";
+var siteMatchNet = "foo.xx.net";
-var domains = ['example.com', 'www.example.com'];
module.exports.test = async function(pkg, config) {
- if ('function' !== typeof pkg.create) {
- throw new Error(
- 'must have a create function that accepts a single options object'
- );
- }
+ if ("function" !== typeof pkg.create) {
+ throw new Error(
+ "must have a create function that accepts a single options object"
+ );
+ }
- var manager = pkg.create(config);
+ var features = {
+ altnames: false,
+ wildcard: false,
+ renewal: false
+ };
+ var manager = pkg.create(config);
+ var initVal;
- if (manager.init) {
- await manager.init({
- request: request
- });
- } else {
- console.warn(
- 'WARN: should have an init(deps) function which returns a promise'
- );
- }
+ if (manager.init) {
+ initVal = await manager.init({
+ request: request
+ });
+ if (!initVal && initVal !== null) {
+ console.warn(
+ "WARN: `init()` returned `undefined`, but should return `null`"
+ );
+ }
+ }
+ console.info("PASS: init(deps)");
- await manager.set({
- subject: domains[0],
- altnames: domains
- });
+ await manager.set({
+ subject: siteSubject,
+ altnames: domains
+ });
+ var site = await manager.get({
+ servername: siteSubject
+ // *.com is an invalid wildname
+ });
+ if (!site || site.subject !== siteSubject) {
+ throw new Error(
+ "set({ subject: '" +
+ siteSubject +
+ "'}), but could not `get()` or `find()` it"
+ );
+ }
- await manager.find({}).then(function(results) {
- if (!results.length) {
- console.log(results);
- throw new Error('should have found all managed sites');
- }
- });
- console.log('PASS: set');
+ //
+ // Test for altname support
+ //
+ site = await get({
+ servername: siteAltname,
+ wildname: untame(siteAltname)
+ });
+ if (site) {
+ if (site.subject !== siteSubject) {
+ throw new Error("found incorrect site");
+ }
+ features.altnames = true;
+ } else {
+ console.warn("WARN: Does not support altnames.");
+ console.warn(
+ " (searched for %s but did not find site '%s')",
+ siteAltname,
+ domains.join(" ")
+ );
+ }
- await manager.find({ subject: 'www.example.com' }).then(function(results) {
- if (results.length) {
- console.log(results);
- throw new Error(
- "shouldn't find what doesn't exist, exactly, by subject"
- );
- }
- });
+ //
+ // Test for wildcard support
+ //
+ if (features.altnames) {
+ // Set the wildcard as an altname
+ site = await get({
+ servername: siteMatch,
+ wildname: siteWildname
+ });
+ if (site) {
+ if (site.subject !== siteSubject) {
+ throw new Error(
+ "found %s when looking for %s",
+ site.subject,
+ siteSubject
+ );
+ }
+ features.wildcard = true;
+ } else {
+ console.warn("WARN: Does not support wildcard domains.");
+ console.warn(
+ " (searched for %s but did not find site %s)",
+ siteMatch,
+ siteSubject
+ );
+ }
+ }
+ // Set the wildcard as the subject
+ await manager.set({
+ subject: siteWildnameNet,
+ altnames: [siteWildnameNet]
+ });
+ site = await get({
+ servername: siteMatchNet,
+ wildname: siteWildnameNet
+ });
+ if (site) {
+ if (site.subject !== siteWildnameNet) {
+ throw new Error("found incorrect site");
+ }
+ features.wildcard = true;
+ } else {
+ if (features.wildcard) {
+ throw new Error(
+ "searched for wildcard subject " +
+ siteWildnameNet +
+ " but did not find it"
+ );
+ }
+ if (!features.altnames) {
+ console.warn(
+ "WARN: Does not support wildcard domains as certificate subjects."
+ );
+ console.warn(
+ " (searched for %s as %s but did not find site %s)",
+ siteMatchNet,
+ siteWildnameNet,
+ siteWildnameNet
+ );
+ }
+ }
+ await remove({ subject: siteWildnameNet });
- await manager
- .find({ altnames: ['www.example.com'] })
- .then(function(results) {
- if (!results.length) {
- console.log(results);
- throw new Error('should have found sites matching altname');
- }
- });
+ var wasSet = false;
+ if (manager.find) {
+ await manager.find({}).then(function(results) {
+ if (!results.length) {
+ //console.error(results);
+ throw new Error("should have found all managed sites");
+ }
+ wasSet = results.some(function(site) {
+ return site.subject === siteSubject;
+ });
+ if (!wasSet) {
+ throw new Error("should have found " + siteSubject);
+ }
+ });
+ }
- await manager.find({ altnames: ['*.example.com'] }).then(function(results) {
- if (results.length) {
- console.log(results);
- throw new Error(
- 'should only find an exact (literal) wildcard match'
- );
- }
- });
- console.log('PASS: find');
+ if (manager.get) {
+ await manager.get({ servername: siteSubject }).then(function(site) {
+ if (!site || site.subject !== siteSubject) {
+ throw new Error("should have found " + siteSubject);
+ }
+ wasSet = true;
+ });
+ if (features.altnames) {
+ wasSet = false;
+ await manager.get({ servername: siteAltname }).then(function(site) {
+ if (!site || site.subject !== siteSubject) {
+ throw new Error("should have found " + siteAltname);
+ }
+ });
+ await manager
+ .get({ servername: siteMatch, wildname: siteWildname })
+ .then(function(site) {
+ if (!site || site.subject !== siteSubject) {
+ throw new Error(
+ "did not find " +
+ siteMatch +
+ ", which matches " +
+ siteWildname
+ );
+ }
+ wasSet = true;
+ });
+ }
+ console.info("PASS: get({ servername, wildname })");
+ } else {
+ console.info("[skip] get({ servername, wildname }) not implemented");
+ }
- await manager.remove({ subject: '*.example.com' }).then(function(result) {
- if (result) {
- throw new Error(
- 'should not return prior object when deleting non-existing site'
- );
- }
- });
+ if (wasSet) {
+ console.info("PASS: set({ subject })");
+ } else {
+ throw new Error("neither `get()` nor `find()` was implemented");
+ }
- await manager.remove({ subject: 'www.example.com' }).then(function(result) {
- if (result) {
- throw new Error(
- 'should not return prior object when deleting non-existing site'
- );
- }
- });
+ if (manager.find) {
+ await manager.find({ subject: siteAltname }).then(function(results) {
+ if (results.length) {
+ console.error(results);
+ throw new Error(
+ "shouldn't find what doesn't exist, exactly, by subject"
+ );
+ }
+ });
- await manager.remove({ subject: 'example.com' }).then(function(result) {
- if (!result || !result.subject || !result.altnames) {
- throw new Error('should return prior object when deleting site');
- }
- });
+ await manager
+ .find({ servernames: [siteAltname], altnames: [siteAltname] })
+ .then(function(results) {
+ if (!results.length) {
+ console.error(results);
+ throw new Error("should have found sites matching altname");
+ }
+ });
+ console.info("PASS: find({ servernames, renewBefore })");
+ } else {
+ console.info(
+ "[skip] find({ servernames, renewBefore }) not implemented"
+ );
+ }
- await manager
- .find({ altnames: ['example.com', 'www.example.com'] })
- .then(function(results) {
- if (results.length) {
- console.log(results);
- throw new Error('should not find deleted sites');
- }
- });
- console.log('PASS: remove');
+ await remove({ subject: noExistWild }).then(function(result) {
+ if (result) {
+ console.error(siteWildname, result);
+ throw new Error(
+ "should not return prior object when deleting non-existing wildcard domain: " +
+ noExistWild
+ );
+ }
+ });
- var originalInput = {
- serverKeyType: 'RSA-2048',
- accountKeyType: 'P-256',
- subscriberEmail: 'jon@example.com',
- agreeToTerms: true,
- store: { module: '/path/to/store-module', foo: 'foo' },
- challenges: {
- 'http-01': { module: '/path/to/http-01-module', bar: 'bar' },
- 'dns-01': { module: '/path/to/dns-01-module', baz: 'baz' },
- 'tls-alpn-01': {
- module: '/path/to/tls-alpn-01-module',
- qux: 'quux'
- }
- },
- customerEmail: 'jane@example.com'
- };
- //var backup = JSON.parse(JSON.stringify(originalInput));
- var configUpdate = {
- renewOffset: '45d',
- renewStagger: '12h',
- subscriberEmail: 'pat@example.com'
- };
+ await remove({ subject: noExistAlt }).then(function(result) {
+ if (result) {
+ throw new Error(
+ "should not return prior object when deleting non-existing site: " +
+ noExistAlt
+ );
+ }
+ });
- var internalConfig;
- await manager.defaults().then(function(result) {
- internalConfig = result;
- if (!result) {
- throw new Error(
- 'should at least return an empty object, perhaps one with some defaults set'
- );
- }
- });
+ await remove({ subject: siteWildname }).then(function(result) {
+ if (result) {
+ throw new Error("should not delete by wildname: " + siteWildname);
+ }
+ });
- await manager.defaults(originalInput).then(function(result) {
- // can't say much... what _should_ this return?
- // probably nothing? or maybe the full config object?
- if (internalConfig === result) {
- console.warn(
- 'WARN: should return a new copy, not the same internal object'
- );
- }
- if (originalInput === result) {
- console.warn(
- 'WARN: should probably return a copy, not the original input'
- );
- }
- });
+ await remove({ subject: siteAltname }).then(function(result) {
+ if (result) {
+ throw new Error("should not delete by altname: " + siteAltname);
+ }
+ });
- await manager.defaults().then(function(result) {
- if (originalInput === result) {
- console.warn('WARN: should probably return a copy, not the prior input');
- }
- });
+ await remove({ subject: siteSubject }).then(function(result) {
+ if (!result || !result.subject || !result.altnames) {
+ throw new Error("should return prior object when deleting site");
+ }
+ });
+ if (!manager.remove) {
+ console.info(
+ "[skip] remove() not implemented - using set({ deletedAt }) instead"
+ );
+ }
- await manager.defaults(configUpdate).then(function() {
- if (originalInput.renewOffset) {
- console.warn('WARN: should probably modify the prior input');
- }
- });
+ await manager.set({ subject: siteSubject, altnames: domains.slice(0, 2) });
+ if (manager.find) {
+ await manager
+ .find({ servernames: [noExistWild], altnames: [noExistWild] })
+ .then(function(results) {
+ if (results.length) {
+ console.error(results);
+ throw new Error(
+ "should only find an exact (literal) wildcard match"
+ );
+ }
+ });
+ }
+ await remove({ subject: siteSubject }).then(function(result) {
+ if (!result || !result.subject || !result.altnames) {
+ console.error(
+ "Could not find",
+ siteSubject,
+ "to delete it:",
+ result
+ );
+ throw new Error("should return prior object when deleting site");
+ }
+ });
- await manager.defaults().then(function(result) {
- if (!result.subscriberEmail || !result.renewOffset) {
- throw new Error('should merge config values together');
- }
- });
+ if (manager.find) {
+ await manager
+ .find({ servernames: domains, altnames: domains })
+ .then(function(results) {
+ if (results.length) {
+ console.error(results);
+ throw new Error("should not find() deleted sites");
+ }
+ });
+ } else {
+ await get({ servername: siteAltname }).then(function(result) {
+ if (result) {
+ console.error(result);
+ throw new Error("should not get() deleted sites");
+ }
+ });
+ }
+ console.info("PASS: remove({ subject })");
- console.log('PASS: defaults');
+ var originalInput = {
+ serverKeyType: "RSA-2048",
+ accountKeyType: "P-256",
+ subscriberEmail: "jon@example.com",
+ agreeToTerms: true,
+ store: { module: "/path/to/store-module", foo: "foo" },
+ challenges: {
+ "http-01": { module: "/path/to/http-01-module", bar: "bar" },
+ "dns-01": { module: "/path/to/dns-01-module", baz: "baz" },
+ "tls-alpn-01": {
+ module: "/path/to/tls-alpn-01-module",
+ qux: "quux"
+ }
+ },
+ customerEmail: "jane@example.com"
+ };
+ //var backup = JSON.parse(JSON.stringify(originalInput));
+ var configUpdate = {
+ renewOffset: "45d",
+ renewStagger: "12h",
+ subscriberEmail: "pat@example.com"
+ };
+
+ var internalConfig;
+ if (manager.defaults) {
+ await manager.defaults().then(function(result) {
+ internalConfig = result;
+ if (!result) {
+ throw new Error(
+ "should at least return an empty object, perhaps one with some defaults set"
+ );
+ }
+ });
+
+ await manager.defaults(originalInput).then(function(result) {
+ // can't say much... what _should_ this return?
+ // probably nothing? or maybe the full config object?
+ if (internalConfig === result) {
+ console.warn(
+ "WARN: should return a new copy, not the same internal object"
+ );
+ }
+ if (originalInput === result) {
+ console.warn(
+ "WARN: should probably return a copy, not the original input"
+ );
+ }
+ });
+
+ await manager.defaults().then(function(result) {
+ if (originalInput === result) {
+ console.warn(
+ "WARN: should probably return a copy, not the prior input"
+ );
+ }
+ });
+
+ await manager.defaults(configUpdate).then(function() {
+ if (originalInput.renewOffset) {
+ console.warn("WARN: should probably modify the prior input");
+ }
+ });
+ console.info("PASS: defaults(conf)");
+
+ await manager.defaults().then(function(result) {
+ if (!result.subscriberEmail || !result.renewOffset) {
+ throw new Error("should merge config values together");
+ }
+ });
+ console.info("PASS: defaults()");
+ } else {
+ console.info(
+ "[skip] defaults({ store, challenges, ... }) not implemented"
+ );
+ }
+
+ features.renewal = !!manager.find;
+ var featureNames = {
+ altnames: "Multiple Domains per Certificate",
+ wildcard:
+ "Wildcard Certificates" +
+ (features.altnames ? "" : " (subject only)"),
+ renewal: "Fully Automatic Renewal"
+ };
+ return Object.keys(features).map(function(k) {
+ return {
+ name: k,
+ description: featureNames[k],
+ supported: features[k]
+ };
+ });
+
+ function get(opts) {
+ if (manager.get) {
+ opts.servername = opts.servername || opts.subject;
+ delete opts.subject;
+ return manager.get(opts);
+ } else {
+ return manager.find(opts);
+ }
+ }
+
+ function remove(opts) {
+ if (manager.remove) {
+ return manager.remove(opts);
+ } else {
+ return get(opts).then(function(site) {
+ // get matches servername, but remove should only match subject
+ if (site && site.subject === opts.servername) {
+ site.deletedAt = Date.now();
+ return manager.set(site).then(function() {
+ return site;
+ });
+ }
+ return null;
+ });
+ }
+ }
+
+ function untame(str) {
+ return (
+ "*." +
+ str
+ .split(".")
+ .slice(1)
+ .join(".")
+ );
+ }
};
diff --git a/tests/index.js b/tests/index.js
index 515b615..30d97e6 100644
--- a/tests/index.js
+++ b/tests/index.js
@@ -1,19 +1,19 @@
-'use strict';
+"use strict";
-var Tester = require('../');
+var Tester = require("../");
-var Manager = require('greenlock-manager-fs');
+var Manager = require("greenlock-manager-fs");
var config = {
- configFile: 'greenlock-manager-test.delete-me.json'
+ configFile: "greenlock-manager-test.delete-me.json"
};
Tester.test(Manager, config)
- .then(function() {
- console.log('PASS: Known-good test module passes');
- })
- .catch(function(err) {
- console.error('Oops, you broke it. Here are the details:');
- console.error(err.stack);
- console.error();
- console.error("That's all I know.");
- });
+ .then(function() {
+ console.log("PASS: Known-good test module passes");
+ })
+ .catch(function(err) {
+ console.error("Oops, you broke it. Here are the details:");
+ console.error(err.stack);
+ console.error();
+ console.error("That's all I know.");
+ });