2019-10-31 06:12:01 +00:00
|
|
|
# [greenlock-manager-test.js](https://git.rootprojects.org/root/greenlock-manager-test.js)
|
2019-10-31 04:56:52 +00:00
|
|
|
|
2019-10-31 06:07:33 +00:00
|
|
|
A simple test suite for Greenlock manager plugins.
|
|
|
|
|
|
|
|
# Greenlock Manager
|
|
|
|
|
|
|
|
A greenlock manager is just a set of a few callbacks to keeps track of:
|
|
|
|
|
|
|
|
- **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)
|
|
|
|
|
2019-10-31 06:09:09 +00:00
|
|
|
The **callbacks** are:
|
2019-10-31 06:07:33 +00:00
|
|
|
|
|
|
|
- `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
|
|
|
|
|
2019-10-31 06:09:09 +00:00
|
|
|
When do they get called? Well, whenever they need to.
|
|
|
|
|
2019-10-31 06:07:33 +00:00
|
|
|
# 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`:
|
|
|
|
|
|
|
|
```js
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var MyManager = module.exports;
|
2019-10-31 06:09:09 +00:00
|
|
|
|
2019-10-31 06:07:33 +00:00
|
|
|
MyManager.create = function(options) {
|
|
|
|
console.log('The tests will make me stronger');
|
|
|
|
return {};
|
|
|
|
};
|
|
|
|
```
|
|
|
|
|
|
|
|
## The test suite from heaven
|
|
|
|
|
|
|
|
You write your test file, run it,
|
|
|
|
and then you get a play-by-play of what to do.
|
|
|
|
|
|
|
|
```
|
|
|
|
npm install --save-dev greenlock-manager-test
|
|
|
|
```
|
|
|
|
|
|
|
|
`test.js`:
|
|
|
|
|
|
|
|
```js
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var Tester = require('greenlock-manager-test');
|
|
|
|
var MyManager = require('./');
|
|
|
|
var myConfigOptions = {
|
|
|
|
someApiTokenForMyManager: 'xxx'
|
|
|
|
};
|
|
|
|
|
|
|
|
Tester.test(MyManager, myConfigOptions)
|
|
|
|
.then(function() {
|
|
|
|
console.log('All Tests Passed');
|
|
|
|
})
|
|
|
|
.catch(function(err) {
|
|
|
|
console.error('Oops... something bad happened:');
|
|
|
|
console.error(err);
|
|
|
|
});
|
|
|
|
```
|
|
|
|
|
|
|
|
You just follow the error messages and, which a little help from this README,
|
|
|
|
bam!, you get a working plugin. It's insane!
|
|
|
|
|
|
|
|
# The lazy, hacky way.
|
|
|
|
|
|
|
|
If you're going to publish a module, you should pass the full test suite.
|
|
|
|
|
|
|
|
If not, eh, you can be lazy.
|
|
|
|
|
|
|
|
## Bare minimum...
|
|
|
|
|
|
|
|
At a bare minimum, you must implement `find()` to return an array of `{ subject, altnames }`.
|
|
|
|
|
|
|
|
For example:
|
|
|
|
|
|
|
|
```js
|
|
|
|
function find(argsToIgnore) {
|
|
|
|
return Promise.resolve([
|
|
|
|
{ subject: 'example.com', altnames: ['example.com', 'www.example.com'] }
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
If that's absolutely all that you do, all of the other methods will be implemented around `greenlock-manager-fs`.
|
|
|
|
|
|
|
|
# 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;
|
|
|
|
|
|
|
|
// Cherry pick what you like for indexing / search, and JSONify the rest
|
|
|
|
return mergeOrCreateSite(subject, siteConfig);
|
|
|
|
};
|
|
|
|
|
|
|
|
// find the things you've saved before
|
|
|
|
|
|
|
|
manager.find = async function({ subject, altnames, renewBefore }) {
|
|
|
|
var results = [];
|
|
|
|
var gotten = {};
|
|
|
|
|
|
|
|
if (subject) {
|
|
|
|
var site = await getSiteBySubject(subject);
|
|
|
|
if (site) {
|
|
|
|
results.push(site);
|
|
|
|
gotten[site.subject] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (altnames) {
|
|
|
|
var sites = await getSiteByAltnames(subject);
|
|
|
|
sites.forEach(function() {});
|
|
|
|
if (site) {
|
|
|
|
if (!gotten[site.subject]) {
|
|
|
|
results.push(site);
|
|
|
|
gotten[site.subject] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (subject || altnames) {
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (renewBefore) {
|
|
|
|
return getSitesThatShouldBeRenewedBefore(renewBefore);
|
|
|
|
}
|
|
|
|
|
|
|
|
return getAllSites();
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
```
|
|
|
|
|
|
|
|
# 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.
|