# [greenlock-manager-test.js](https://git.rootprojects.org/root/greenlock-manager-test.js) A simple test suite for Greenlock v3 manager plugins. # Greenlock Manager 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. It consists of two required functions: ```js set({ subject, altnames, renewAt, deletedAt }); ``` ```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 ``` It will generate a bare bones manager that passes the tests, (skipping all optional features), and a test file:
manager.js ```js "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; }; ````
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, 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 ``` ```txt PASS: get({ servername, wildname }) PASS: set({ subject }) Optional Feature Support: ✘ (NO) Multiple Domains per Certificate ✘ (NO) Wildcard Certificates ✘ (NO) Fully Automatic Renewal ``` # Optional Features If you're publishing a module to the community, you should implement the full test suite (and it's not that hard). If you're only halfway through, you should note which features are supported and which aren't. ```js find({ subject, servernames, renewBefore }); defaults({ subscriberEmail, agreeToTerms, challenges, store, ... }); defaults(); // as getter ``` - `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™ If you want to publish a module to the community you should do a slightly better job: ```js module.exports.create = function(options) { var manager = {}; // add some things to... wherever you save things 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.subject; // Cherry pick what you like for indexing / search, and JSONify the rest return mergeOrCreateSite(subject, siteConfig); }; // find the things you've saved before 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 && site.subject === subject) { return [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; } }); } return getSitesThatShouldBeRenewedBefore(renewBefore || Infinity); }; // delete a site config manager.remove = async function({ subject }) { // set deletedAt to a value, or actually delete it - however you like return mergeOrCreateSite(subject, { deletedAt: Date.now() }); }; // get / set global things manager.defaults = async function(options) { if (!options) { return getDefaultConfigValues(); } return mergeDefaultConfigValues(options); }; // optional, if you need it manager.init = async function(deps) { // a place to do some init, if you need it return doMyInit(); // Also, `deps` will have some common dependencies // than many modules need, such as `request`. // This cuts down on stray dependencies, and helps // with browser compatibility. request = deps.request; }; }; ```