v3.1.0: reduce scope of manager API
This commit is contained in:
parent
19b571f088
commit
30884601c6
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"bracketSpacing": true,
|
"bracketSpacing": true,
|
||||||
"printWidth": 80,
|
"printWidth": 80,
|
||||||
"singleQuote": true,
|
"singleQuote": false,
|
||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"useTabs": false
|
"useTabs": false
|
||||||
|
|
385
README.md
385
README.md
|
@ -1,113 +1,246 @@
|
||||||
# [greenlock-manager-test.js](https://git.rootprojects.org/root/greenlock-manager-test.js)
|
# [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
|
# 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
|
It consists of two required functions:
|
||||||
- `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`:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
'use strict';
|
set({ subject, altnames, renewAt, deletedAt });
|
||||||
|
|
||||||
var MyManager = module.exports;
|
|
||||||
|
|
||||||
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
|
```js
|
||||||
'use strict';
|
get({ servername });
|
||||||
|
```
|
||||||
|
|
||||||
var Tester = require('greenlock-manager-test');
|
However, if you implement `find({ subject, servernames, renewBefore })` (optional),
|
||||||
var MyManager = require('./');
|
you don't have to implement `get()`.
|
||||||
var myConfigOptions = {
|
|
||||||
someApiTokenForMyManager: 'xxx'
|
|
||||||
};
|
|
||||||
|
|
||||||
Tester.test(MyManager, myConfigOptions)
|
<details>
|
||||||
.then(function() {
|
<summary>Usage Details</summary>
|
||||||
console.log('All Tests Passed');
|
# How to use your plugin
|
||||||
})
|
|
||||||
.catch(function(err) {
|
The **Right Way**:
|
||||||
console.error('Oops... something bad happened:');
|
|
||||||
console.error(err);
|
```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
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
You just follow the error messages and, which a little help from this README,
|
## Why no require?
|
||||||
bam!, you get a working plugin. It's insane!
|
|
||||||
|
|
||||||
# The lazy, hacky way.
|
Okay, so you **expect** it to look like this:
|
||||||
|
|
||||||
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
|
```js
|
||||||
function find(argsToIgnore) {
|
var Greenlock = require("greenlock");
|
||||||
return Promise.resolve([
|
var greenlock = Greenlock.create({
|
||||||
{ subject: 'example.com', altnames: ['example.com', 'www.example.com'] }
|
// WRONG!!
|
||||||
]);
|
manager: require("./path-or-npm-name.js").create({
|
||||||
}
|
someOptionYouWant: true
|
||||||
|
})
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
If that's absolutely all that you do, all of the other methods will be implemented around `greenlock-manager-fs`.
|
**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.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>manager.js</summary>
|
||||||
|
```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;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
````
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>manager.test.js</summary>
|
||||||
|
```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.");
|
||||||
|
});
|
||||||
|
````
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
```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 });
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Full Implementation</summary>
|
||||||
|
|
||||||
# The Right Way™
|
# The Right Way™
|
||||||
|
|
||||||
|
@ -122,7 +255,7 @@ module.exports.create = function(options) {
|
||||||
manager.set = async function(siteConfig) {
|
manager.set = async function(siteConfig) {
|
||||||
// You can see in the tests a sample of common values,
|
// You can see in the tests a sample of common values,
|
||||||
// but you don't really need to worry about it.
|
// 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
|
// Cherry pick what you like for indexing / search, and JSONify the rest
|
||||||
return mergeOrCreateSite(subject, siteConfig);
|
return mergeOrCreateSite(subject, siteConfig);
|
||||||
|
@ -130,38 +263,31 @@ module.exports.create = function(options) {
|
||||||
|
|
||||||
// find the things you've saved before
|
// 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 results = [];
|
||||||
var gotten = {};
|
var gotten = {};
|
||||||
|
|
||||||
if (subject) {
|
if (subject) {
|
||||||
var site = await getSiteBySubject(subject);
|
var site = await getSiteBySubject(subject);
|
||||||
if (site) {
|
if (site && site.subject === subject) {
|
||||||
results.push(site);
|
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;
|
gotten[site.subject] = true;
|
||||||
|
return site;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (altnames) {
|
return getSitesThatShouldBeRenewedBefore(renewBefore || Infinity);
|
||||||
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
|
// delete a site config
|
||||||
|
@ -198,35 +324,4 @@ module.exports.create = function(options) {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
# How to use your plugin
|
</details>
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
|
@ -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();
|
|
@ -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.");
|
||||||
|
});
|
|
@ -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;
|
||||||
|
};
|
|
@ -1,13 +1,32 @@
|
||||||
{
|
{
|
||||||
"name": "greenlock-manager-test",
|
"name": "greenlock-manager-test",
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"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": {
|
"@root/request": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@root/request/-/request-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@root/request/-/request-1.4.1.tgz",
|
||||||
"integrity": "sha512-2zSP1v9VhJ3gvm4oph0C4BYCoM3Sj84/Wx4iKdt0IbqbJzfON04EodBq5dsV65UxO/aHZciUBwY2GCZcHqaTYg=="
|
"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=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
{
|
{
|
||||||
"name": "greenlock-manager-test",
|
"name": "greenlock-manager-test",
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"description": "A simple test suite for Greenlock manager plugins.",
|
"description": "A simple test suite for Greenlock manager plugins.",
|
||||||
"main": "tester.js",
|
"main": "tester.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node tests"
|
"test": "node tests"
|
||||||
},
|
},
|
||||||
|
"bin": {
|
||||||
|
"greenlock-manager-init": "bin/init.js"
|
||||||
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"*.js",
|
"*.js",
|
||||||
|
"bin",
|
||||||
"lib"
|
"lib"
|
||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
388
tester.js
388
tester.js
|
@ -1,43 +1,208 @@
|
||||||
'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) {
|
module.exports.test = async function(pkg, config) {
|
||||||
if ('function' !== typeof pkg.create) {
|
if ("function" !== typeof pkg.create) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'must have a create function that accepts a single options object'
|
"must have a create function that accepts a single options object"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var features = {
|
||||||
|
altnames: false,
|
||||||
|
wildcard: false,
|
||||||
|
renewal: false
|
||||||
|
};
|
||||||
var manager = pkg.create(config);
|
var manager = pkg.create(config);
|
||||||
|
var initVal;
|
||||||
|
|
||||||
if (manager.init) {
|
if (manager.init) {
|
||||||
await manager.init({
|
initVal = await manager.init({
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
} else {
|
if (!initVal && initVal !== null) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'WARN: should have an init(deps) function which returns a promise'
|
"WARN: `init()` returned `undefined`, but should return `null`"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.info("PASS: init(deps)");
|
||||||
|
|
||||||
|
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.set({
|
//
|
||||||
subject: domains[0],
|
// Test for altname support
|
||||||
altnames: domains
|
//
|
||||||
|
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(" ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// 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 });
|
||||||
|
|
||||||
|
var wasSet = false;
|
||||||
|
if (manager.find) {
|
||||||
await manager.find({}).then(function(results) {
|
await manager.find({}).then(function(results) {
|
||||||
if (!results.length) {
|
if (!results.length) {
|
||||||
console.log(results);
|
//console.error(results);
|
||||||
throw new Error('should have found all managed sites');
|
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log('PASS: set');
|
}
|
||||||
|
|
||||||
await manager.find({ subject: 'www.example.com' }).then(function(results) {
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wasSet) {
|
||||||
|
console.info("PASS: set({ subject })");
|
||||||
|
} else {
|
||||||
|
throw new Error("neither `get()` nor `find()` was implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manager.find) {
|
||||||
|
await manager.find({ subject: siteAltname }).then(function(results) {
|
||||||
if (results.length) {
|
if (results.length) {
|
||||||
console.log(results);
|
console.error(results);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"shouldn't find what doesn't exist, exactly, by subject"
|
"shouldn't find what doesn't exist, exactly, by subject"
|
||||||
);
|
);
|
||||||
|
@ -45,85 +210,136 @@ module.exports.test = async function(pkg, config) {
|
||||||
});
|
});
|
||||||
|
|
||||||
await manager
|
await manager
|
||||||
.find({ altnames: ['www.example.com'] })
|
.find({ servernames: [siteAltname], altnames: [siteAltname] })
|
||||||
.then(function(results) {
|
.then(function(results) {
|
||||||
if (!results.length) {
|
if (!results.length) {
|
||||||
console.log(results);
|
console.error(results);
|
||||||
throw new Error('should have found sites matching altname');
|
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'] }).then(function(results) {
|
await remove({ subject: noExistWild }).then(function(result) {
|
||||||
if (results.length) {
|
if (result) {
|
||||||
console.log(results);
|
console.error(siteWildname, result);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'should only find an exact (literal) wildcard match'
|
"should not return prior object when deleting non-existing wildcard domain: " +
|
||||||
|
noExistWild
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log('PASS: find');
|
|
||||||
|
|
||||||
await manager.remove({ subject: '*.example.com' }).then(function(result) {
|
await remove({ subject: noExistAlt }).then(function(result) {
|
||||||
if (result) {
|
if (result) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'should not return prior object when deleting non-existing site'
|
"should not return prior object when deleting non-existing site: " +
|
||||||
|
noExistAlt
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await manager.remove({ subject: 'www.example.com' }).then(function(result) {
|
await remove({ subject: siteWildname }).then(function(result) {
|
||||||
if (result) {
|
if (result) {
|
||||||
throw new Error(
|
throw new Error("should not delete by wildname: " + siteWildname);
|
||||||
'should not return prior object when deleting non-existing site'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await manager.remove({ subject: 'example.com' }).then(function(result) {
|
await remove({ subject: siteAltname }).then(function(result) {
|
||||||
|
if (result) {
|
||||||
|
throw new Error("should not delete by altname: " + siteAltname);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await remove({ subject: siteSubject }).then(function(result) {
|
||||||
if (!result || !result.subject || !result.altnames) {
|
if (!result || !result.subject || !result.altnames) {
|
||||||
throw new Error('should return prior object when deleting site');
|
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.set({ subject: siteSubject, altnames: domains.slice(0, 2) });
|
||||||
|
if (manager.find) {
|
||||||
await manager
|
await manager
|
||||||
.find({ altnames: ['example.com', 'www.example.com'] })
|
.find({ servernames: [noExistWild], altnames: [noExistWild] })
|
||||||
.then(function(results) {
|
.then(function(results) {
|
||||||
if (results.length) {
|
if (results.length) {
|
||||||
console.log(results);
|
console.error(results);
|
||||||
throw new Error('should not find deleted sites');
|
throw new Error(
|
||||||
|
"should only find an exact (literal) wildcard match"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log('PASS: remove');
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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 })");
|
||||||
|
|
||||||
var originalInput = {
|
var originalInput = {
|
||||||
serverKeyType: 'RSA-2048',
|
serverKeyType: "RSA-2048",
|
||||||
accountKeyType: 'P-256',
|
accountKeyType: "P-256",
|
||||||
subscriberEmail: 'jon@example.com',
|
subscriberEmail: "jon@example.com",
|
||||||
agreeToTerms: true,
|
agreeToTerms: true,
|
||||||
store: { module: '/path/to/store-module', foo: 'foo' },
|
store: { module: "/path/to/store-module", foo: "foo" },
|
||||||
challenges: {
|
challenges: {
|
||||||
'http-01': { module: '/path/to/http-01-module', bar: 'bar' },
|
"http-01": { module: "/path/to/http-01-module", bar: "bar" },
|
||||||
'dns-01': { module: '/path/to/dns-01-module', baz: 'baz' },
|
"dns-01": { module: "/path/to/dns-01-module", baz: "baz" },
|
||||||
'tls-alpn-01': {
|
"tls-alpn-01": {
|
||||||
module: '/path/to/tls-alpn-01-module',
|
module: "/path/to/tls-alpn-01-module",
|
||||||
qux: 'quux'
|
qux: "quux"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
customerEmail: 'jane@example.com'
|
customerEmail: "jane@example.com"
|
||||||
};
|
};
|
||||||
//var backup = JSON.parse(JSON.stringify(originalInput));
|
//var backup = JSON.parse(JSON.stringify(originalInput));
|
||||||
var configUpdate = {
|
var configUpdate = {
|
||||||
renewOffset: '45d',
|
renewOffset: "45d",
|
||||||
renewStagger: '12h',
|
renewStagger: "12h",
|
||||||
subscriberEmail: 'pat@example.com'
|
subscriberEmail: "pat@example.com"
|
||||||
};
|
};
|
||||||
|
|
||||||
var internalConfig;
|
var internalConfig;
|
||||||
|
if (manager.defaults) {
|
||||||
await manager.defaults().then(function(result) {
|
await manager.defaults().then(function(result) {
|
||||||
internalConfig = result;
|
internalConfig = result;
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'should at least return an empty object, perhaps one with some defaults set'
|
"should at least return an empty object, perhaps one with some defaults set"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -133,33 +349,93 @@ module.exports.test = async function(pkg, config) {
|
||||||
// probably nothing? or maybe the full config object?
|
// probably nothing? or maybe the full config object?
|
||||||
if (internalConfig === result) {
|
if (internalConfig === result) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'WARN: should return a new copy, not the same internal object'
|
"WARN: should return a new copy, not the same internal object"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (originalInput === result) {
|
if (originalInput === result) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'WARN: should probably return a copy, not the original input'
|
"WARN: should probably return a copy, not the original input"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await manager.defaults().then(function(result) {
|
await manager.defaults().then(function(result) {
|
||||||
if (originalInput === result) {
|
if (originalInput === result) {
|
||||||
console.warn('WARN: should probably return a copy, not the prior input');
|
console.warn(
|
||||||
|
"WARN: should probably return a copy, not the prior input"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await manager.defaults(configUpdate).then(function() {
|
await manager.defaults(configUpdate).then(function() {
|
||||||
if (originalInput.renewOffset) {
|
if (originalInput.renewOffset) {
|
||||||
console.warn('WARN: should probably modify the prior input');
|
console.warn("WARN: should probably modify the prior input");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
console.info("PASS: defaults(conf)");
|
||||||
|
|
||||||
await manager.defaults().then(function(result) {
|
await manager.defaults().then(function(result) {
|
||||||
if (!result.subscriberEmail || !result.renewOffset) {
|
if (!result.subscriberEmail || !result.renewOffset) {
|
||||||
throw new Error('should merge config values together');
|
throw new Error("should merge config values together");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
console.info("PASS: defaults()");
|
||||||
|
} else {
|
||||||
|
console.info(
|
||||||
|
"[skip] defaults({ store, challenges, ... }) not implemented"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
console.log('PASS: defaults');
|
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(".")
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
var Tester = require('../');
|
var Tester = require("../");
|
||||||
|
|
||||||
var Manager = require('greenlock-manager-fs');
|
var Manager = require("greenlock-manager-fs");
|
||||||
var config = {
|
var config = {
|
||||||
configFile: 'greenlock-manager-test.delete-me.json'
|
configFile: "greenlock-manager-test.delete-me.json"
|
||||||
};
|
};
|
||||||
|
|
||||||
Tester.test(Manager, config)
|
Tester.test(Manager, config)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
console.log('PASS: Known-good test module passes');
|
console.log("PASS: Known-good test module passes");
|
||||||
})
|
})
|
||||||
.catch(function(err) {
|
.catch(function(err) {
|
||||||
console.error('Oops, you broke it. Here are the details:');
|
console.error("Oops, you broke it. Here are the details:");
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
console.error();
|
console.error();
|
||||||
console.error("That's all I know.");
|
console.error("That's all I know.");
|
||||||
|
|
Loading…
Reference in New Issue