Compare commits
	
		
			4 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e159a51984 | |||
| 63f2f02da9 | |||
| 30884601c6 | |||
| 19b571f088 | 
| @ -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 | ||||||
|  | |||||||
							
								
								
									
										374
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										374
									
								
								README.md
									
									
									
									
									
								
							| @ -1,113 +1,251 @@ | |||||||
| # 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 | ```js | ||||||
| 
 | get({ servername }); | ||||||
| You write your test file, run it, |  | ||||||
| and then you get a play-by-play of what to do. |  | ||||||
| 
 |  | ||||||
| ``` | ``` | ||||||
|  | 
 | ||||||
|  | However, if you implement `find({ subject, servernames, renewBefore })` (optional), | ||||||
|  | you don't have to implement `get()`. | ||||||
|  | 
 | ||||||
|  | <details> | ||||||
|  | <summary>Usage Details</summary> | ||||||
|  | # 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. | ||||||
|  | 
 | ||||||
|  | </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 | 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: | ||||||
|  | 
 | ||||||
|  | <details> | ||||||
|  | <summary>manager.js</summary> | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| 'use strict'; | "use strict"; | ||||||
| 
 | 
 | ||||||
| var Tester = require('greenlock-manager-test'); | var Manager = module.exports; | ||||||
| var MyManager = require('./'); | var db = {}; | ||||||
| var myConfigOptions = { | 
 | ||||||
|     someApiTokenForMyManager: 'xxx' | 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(MyManager, myConfigOptions) | Tester.test(Manager, config) | ||||||
|     .then(function() { |     .then(function(features) { | ||||||
|         console.log('All Tests Passed'); |         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) { |     .catch(function(err) { | ||||||
|         console.error('Oops... something bad happened:'); |         console.error("Oops, you broke it. Here are the details:"); | ||||||
|         console.error(err); |         console.error(err.stack); | ||||||
|  |         console.error(); | ||||||
|  |         console.error("That's all I know."); | ||||||
|     }); |     }); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| You just follow the error messages and, which a little help from this README, | </details> | ||||||
| bam!, you get a working plugin. It's insane! |  | ||||||
| 
 | 
 | ||||||
| # The lazy, hacky way. | ```bash | ||||||
| 
 | node manager.test.js | ||||||
| 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`. | ```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 +260,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 +268,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]; | ||||||
|                 gotten[site.subject] = true; |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (altnames) { |         if (severnames) { | ||||||
|             var sites = await getSiteByAltnames(subject); |             return await Promise.all(servernames.map(function (altname) { | ||||||
|             sites.forEach(function() {}); |                 var site = getSiteByAltname(subject); | ||||||
|             if (site) { |                 if (site && !gotten[site.subject]) { | ||||||
|                 if (!gotten[site.subject]) { |  | ||||||
|                     results.push(site); |  | ||||||
|                     gotten[site.subject] = true; |                     gotten[site.subject] = true; | ||||||
|  |                     return site; | ||||||
|                 } |                 } | ||||||
|             } |             }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (subject || altnames) { |         return getSitesThatShouldBeRenewedBefore(renewBefore || Infinity); | ||||||
|             return results; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (renewBefore) { |  | ||||||
|             return getSitesThatShouldBeRenewedBefore(renewBefore); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return getAllSites(); |  | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     // delete a site config |     // delete a site config | ||||||
| @ -198,35 +329,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. |  | ||||||
|  | |||||||
							
								
								
									
										36
									
								
								bin/init.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										36
									
								
								bin/init.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,36 @@ | |||||||
|  | #!/usr/bin/env node
 | ||||||
|  | "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(); | ||||||
							
								
								
									
										25
									
								
								bin/tmpl/manager.test.tmpl.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								bin/tmpl/manager.test.tmpl.js
									
									
									
									
									
										Normal file
									
								
							| @ -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."); | ||||||
|  |     }); | ||||||
							
								
								
									
										78
									
								
								bin/tmpl/manager.tmpl.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								bin/tmpl/manager.tmpl.js
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | }; | ||||||
							
								
								
									
										39
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										39
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,13 +1,32 @@ | |||||||
| { | { | ||||||
|   "name": "greenlock-manager-test", |     "name": "greenlock-manager-test", | ||||||
|   "version": "3.0.0", |     "version": "3.1.1", | ||||||
|   "lockfileVersion": 1, |     "lockfileVersion": 1, | ||||||
|   "requires": true, |     "requires": true, | ||||||
|   "dependencies": { |     "dependencies": { | ||||||
|     "@root/request": { |         "@root/mkdirp": { | ||||||
|       "version": "1.4.1", |             "version": "1.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/@root/request/-/request-1.4.1.tgz", |             "resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz", | ||||||
|       "integrity": "sha512-2zSP1v9VhJ3gvm4oph0C4BYCoM3Sj84/Wx4iKdt0IbqbJzfON04EodBq5dsV65UxO/aHZciUBwY2GCZcHqaTYg==" |             "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==" | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										56
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								package.json
									
									
									
									
									
								
							| @ -1,28 +1,32 @@ | |||||||
| { | { | ||||||
|   "name": "greenlock-manager-test", |     "name": "greenlock-manager-test", | ||||||
|   "version": "3.0.0", |     "version": "3.1.1", | ||||||
|   "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" | ||||||
|   }, |     }, | ||||||
|   "files": [ |     "bin": { | ||||||
|     "*.js", |         "greenlock-manager-init": "bin/init.js" | ||||||
|     "lib" |     }, | ||||||
|   ], |     "files": [ | ||||||
|   "repository": { |         "*.js", | ||||||
|     "type": "git", |         "bin", | ||||||
|     "url": "https://git.rootprojects.org/root/greenlock-manager-test.js" |         "lib" | ||||||
|   }, |     ], | ||||||
|   "keywords": [ |     "repository": { | ||||||
|     "Greenlock", |         "type": "git", | ||||||
|     "manager", |         "url": "https://git.rootprojects.org/root/greenlock-manager-test.js" | ||||||
|     "plugin" |     }, | ||||||
|   ], |     "keywords": [ | ||||||
|   "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", |         "Greenlock", | ||||||
|   "license": "MPL-2.0", |         "manager", | ||||||
|   "dependencies": { |         "plugin" | ||||||
|     "@root/request": "^1.4.1", |     ], | ||||||
|     "greenlock-manager-fs": "^3.0.0" |     "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", | ||||||
|   } |     "license": "MPL-2.0", | ||||||
|  |     "dependencies": { | ||||||
|  |         "@root/request": "^1.4.1", | ||||||
|  |         "greenlock-manager-fs": "^3.0.0" | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										562
									
								
								tester.js
									
									
									
									
									
								
							
							
						
						
									
										562
									
								
								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) { | 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 manager = pkg.create(config); |     var features = { | ||||||
|  |         altnames: false, | ||||||
|  |         wildcard: false, | ||||||
|  |         renewal: false | ||||||
|  |     }; | ||||||
|  |     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({ |     await manager.set({ | ||||||
| 		subject: domains[0], |         subject: siteSubject, | ||||||
| 		altnames: domains |         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) { |     // Test for altname support
 | ||||||
| 			console.log(results); |     //
 | ||||||
| 			throw new Error('should have found all managed sites'); |     site = await get({ | ||||||
| 		} |         servername: siteAltname, | ||||||
| 	}); |         wildname: untame(siteAltname) | ||||||
| 	console.log('PASS: set'); |     }); | ||||||
|  |     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) { |     // Test for wildcard support
 | ||||||
| 			console.log(results); |     //
 | ||||||
| 			throw new Error( |     if (features.altnames) { | ||||||
| 				"shouldn't find what doesn't exist, exactly, by subject" |         // 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 |     var wasSet = false; | ||||||
| 		.find({ altnames: ['www.example.com'] }) |     if (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 sites matching altname'); |                 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 (manager.get) { | ||||||
| 		if (results.length) { |         await manager.get({ servername: siteSubject }).then(function(site) { | ||||||
| 			console.log(results); |             if (!site || site.subject !== siteSubject) { | ||||||
| 			throw new Error( |                 throw new Error("should have found " + siteSubject); | ||||||
| 				'should only find an exact (literal) wildcard match' |             } | ||||||
| 			); |             wasSet = true; | ||||||
| 		} |         }); | ||||||
| 	}); |         if (features.altnames) { | ||||||
| 	console.log('PASS: find'); |             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 (wasSet) { | ||||||
| 		if (result) { |         console.info("PASS:  set({ subject })"); | ||||||
| 			throw new Error( |     } else { | ||||||
| 				'should not return prior object when deleting non-existing site' |         throw new Error("neither `get()` nor `find()` was implemented"); | ||||||
| 			); |     } | ||||||
| 		} |  | ||||||
| 	}); |  | ||||||
| 
 | 
 | ||||||
| 	await manager.remove({ subject: 'www.example.com' }).then(function(result) { |     if (manager.find) { | ||||||
| 		if (result) { |         await manager.find({ subject: siteAltname }).then(function(results) { | ||||||
| 			throw new Error( |             if (results.length) { | ||||||
| 				'should not return prior object when deleting non-existing site' |                 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) { |         await manager | ||||||
| 		if (!result || !result.subject || !result.altnames) { |             .find({ servernames: [siteAltname], altnames: [siteAltname] }) | ||||||
| 			throw new Error('should return prior object when deleting site'); |             .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 |     await remove({ subject: noExistWild }).then(function(result) { | ||||||
| 		.find({ altnames: ['example.com', 'www.example.com'] }) |         if (result) { | ||||||
| 		.then(function(results) { |             console.error(siteWildname, result); | ||||||
| 			if (results.length) { |             throw new Error( | ||||||
| 				console.log(results); |                 "should not return prior object when deleting non-existing wildcard domain: " + | ||||||
| 				throw new Error('should not find deleted sites'); |                     noExistWild | ||||||
| 			} |             ); | ||||||
| 		}); |         } | ||||||
| 	console.log('PASS: remove'); |     }); | ||||||
| 
 | 
 | ||||||
| 	var originalInput = { |     await remove({ subject: noExistAlt }).then(function(result) { | ||||||
| 		serverKeyType: 'RSA-2048', |         if (result) { | ||||||
| 		accountKeyType: 'P-256', |             throw new Error( | ||||||
| 		subscriberEmail: 'jon@example.com', |                 "should not return prior object when deleting non-existing site: " + | ||||||
| 		agreeToTerms: true, |                     noExistAlt | ||||||
| 		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; |     await remove({ subject: siteWildname }).then(function(result) { | ||||||
| 	await manager.defaults().then(function(result) { |         if (result) { | ||||||
| 		internalConfig = result; |             throw new Error("should not delete by wildname: " + siteWildname); | ||||||
| 		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) { |     await remove({ subject: siteAltname }).then(function(result) { | ||||||
| 		// can't say much... what _should_ this return?
 |         if (result) { | ||||||
| 		// probably nothing? or maybe the full config object?
 |             throw new Error("should not delete by altname: " + siteAltname); | ||||||
| 		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) { |     await remove({ subject: siteSubject }).then(function(result) { | ||||||
| 		if (originalInput === result) { |         if (!result || !result.subject || !result.altnames) { | ||||||
| 			console.warn('WARN: should probably return a copy, not the prior input'); |             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() { |     await manager.set({ subject: siteSubject, altnames: domains.slice(0, 2) }); | ||||||
| 		if (originalInput.renewOffset) { |     if (manager.find) { | ||||||
| 			console.warn('WARN: should probably modify the prior input'); |         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 (manager.find) { | ||||||
| 		if (!result.subscriberEmail || !result.renewOffset) { |         await manager | ||||||
| 			throw new Error('should merge config values together'); |             .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(".") | ||||||
|  |         ); | ||||||
|  |     } | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -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 = { | 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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user