Compare commits

...

729 Commits

Author SHA1 Message Date
Julien Fontanet
db3728f9e4 feat(xo-server-auth-ldap): 0.6.2 2017-02-13 17:45:53 +01:00
Julien Fontanet
ca2d3f5b48 fix(xo-server-auth-ldap): correctly use previous values for booleans 2017-02-13 17:44:34 +01:00
Julien Fontanet
f3a992a55f chore(xo-server-auth-ldap): use lodash and promise-toolbox utils 2017-02-13 17:44:09 +01:00
Julien Fontanet
b5d1e7c459 chore(xo-server-auth-ldap): clarify bind.dn description 2017-02-13 17:09:41 +01:00
Julien Fontanet
eecd89772f chore(xo-server-auth-ldap): remember optional inputs 2017-02-13 16:58:27 +01:00
Julien Fontanet
5fd42bf216 feat: new vhd-cli package 2017-02-07 19:24:07 +01:00
Julien Fontanet
3e55d8d9df fix(README): issues should be reported on xo-web's tracker 2017-02-02 16:00:21 +01:00
Julien Fontanet
d9f4a196d0 feat(xo-server-auth-ldap): 0.6.1 2017-02-02 15:55:19 +01:00
Julien Fontanet
768ef8a316 chore(xo-server-auth-ldap): update all deps 2017-02-02 15:49:29 +01:00
Julien Fontanet
a478d02e5d fix(xo-server-auth-ldap): handle connection failures 2017-02-02 15:49:27 +01:00
Julien Fontanet
b98a7de3ae chore(xo-server-auth-ldap): use promise-toolbox 2017-02-02 15:49:25 +01:00
Julien Fontanet
45dd2c6519 chore: limit concurrency at 1 for installation 2017-01-27 17:05:48 +01:00
Julien Fontanet
5176d2000e chore: upgrade husky to v0.13.1 and lerna to v2.0.0-beta.34 2017-01-27 17:01:53 +01:00
Julien Fontanet
6f606761e4 chore(xo-server-test-plugin): add yarn.lock 2017-01-27 16:55:02 +01:00
Julien Fontanet
cfabadffe4 chore: exclusively use yarn instead of npm 2017-01-27 16:54:32 +01:00
Julien Fontanet
7523cb3489 chore: improve filter description 2017-01-25 12:43:29 +01:00
Julien Fontanet
505a9d7c70 chore(package): update inquirer to v3.0.1 2017-01-25 11:12:08 +01:00
Julien Fontanet
9581764cc8 feat: add normalize-package script 2017-01-16 12:01:32 +01:00
Julien Fontanet
f03493a252 chore: integration of imported packages 2017-01-13 18:54:27 +01:00
Julien Fontanet
22f2a05c8a Merge remote-tracking branch 'xo-server-test-plugin/master' 2017-01-13 14:00:47 +01:00
Julien Fontanet
a703ecc7e1 Merge remote-tracking branch 'xo-cli/master' 2017-01-13 14:00:45 +01:00
Julien Fontanet
c90a687179 Merge remote-tracking branch 'xo-server-load-balancer/master' 2017-01-13 14:00:41 +01:00
Julien Fontanet
e08689ff0e Merge remote-tracking branch 'xo-server-auth-ldap/master' 2017-01-13 14:00:37 +01:00
Julien Fontanet
80ffd811c9 Merge remote-tracking branch 'xo-server-auth-saml/master' 2017-01-13 14:00:33 +01:00
Julien Fontanet
56b583fc99 Merge remote-tracking branch 'xo-server-backup-reports/master' 2017-01-13 14:00:30 +01:00
Julien Fontanet
37a4e108be Merge remote-tracking branch 'xo-server-transport-nagios/master' 2017-01-13 14:00:26 +01:00
Julien Fontanet
9ca531541d Merge remote-tracking branch 'xo-server-transport-email/master' 2017-01-13 14:00:23 +01:00
Julien Fontanet
e304c554d0 Merge remote-tracking branch 'xo-server-auth-github/master' 2017-01-13 14:00:19 +01:00
Julien Fontanet
a7dfa7a381 Merge remote-tracking branch 'xo-server-transport-xmpp/master' 2017-01-13 14:00:09 +01:00
Julien Fontanet
090d48b636 0.3.2 2017-01-12 17:13:55 +01:00
Julien Fontanet
55567bf666 fix(package): use nodemailer 2.6.4
nodemailer 2.7.0 breaks Node 4 compatibility
2017-01-12 17:13:51 +01:00
Julien Fontanet
5867d84eaa 0.3.1 2017-01-12 16:24:45 +01:00
Julien Fontanet
4d8f1ab169 chore(package): update all dependencies 2017-01-12 16:23:21 +01:00
Julien Fontanet
6a2963be41 chore(configuration schema): add description for from.name and from.address 2017-01-12 16:17:55 +01:00
Julien Fontanet
b10f7b35ee chore(xo-collection): update all deps 2017-01-11 16:50:48 +01:00
Julien Fontanet
dc1bb8992f chore(xo-collection): use jest instead of mocha/must/sinon 2017-01-11 16:32:10 +01:00
Julien Fontanet
5eb7119821 chore: global gitignore 2017-01-11 14:07:49 +01:00
Julien Fontanet
3fa8fc19dc chore: global EditorConfig 2017-01-11 12:03:14 +01:00
Julien Fontanet
341d98e00d chore(xo-acl-resolver): minor fixes regarding monorepo 2017-01-10 18:29:52 +01:00
Julien Fontanet
7eee0f4341 Merge remote-tracking branch 'xo-acl-resolver/master' 2017-01-10 18:11:47 +01:00
Julien Fontanet
0b71b7862a feat: basic README 2017-01-10 18:07:05 +01:00
Julien Fontanet
e2893a0eba chore: fix packages URLs 2017-01-10 18:05:01 +01:00
Julien Fontanet
0c39a4e193 chore: add yarn.lock for all packages 2017-01-10 17:51:50 +01:00
Julien Fontanet
066072b22d chore: configure Travis globally 2017-01-10 17:51:31 +01:00
Julien Fontanet
d548503590 chore(xo-remote-parser): update all deps 2017-01-10 16:24:28 +01:00
Julien Fontanet
3a02fc99a2 chore: global husky instead of local ghooks 2017-01-10 16:13:37 +01:00
Julien Fontanet
d8ae943d8a chore: lerna is a dev dep 2017-01-10 15:54:29 +01:00
Julien Fontanet
63dae9ed70 chore: ignore log files
- lerna-debug.log{,.*}
- yerna-error.log{,.*}
2017-01-10 15:54:14 +01:00
Julien Fontanet
97f57f1f2b Merge remote-tracking branch 'xo-remote-parser/master' 2017-01-10 15:20:28 +01:00
Julien Fontanet
060ba6423e Merge remote-tracking branch 'xo-lib/master' 2017-01-10 15:20:25 +01:00
Julien Fontanet
f31bbcdaab Merge remote-tracking branch 'xo-common/master' 2017-01-10 15:20:14 +01:00
Julien Fontanet
2a4bae54ab Merge remote-tracking branch 'xo-collection/master' 2017-01-10 15:20:08 +01:00
Julien Fontanet
95df2f66a3 initial commit 2017-01-10 12:21:59 +01:00
greenkeeper[bot]
fdd33abe91 fix(package): update promise-toolbox to version 0.8.0 (#14)
https://greenkeeper.io/
2017-01-06 11:31:53 +01:00
Julien Fontanet
044c9bed4c Update README.md 2016-12-23 11:01:25 +01:00
badrAZ
81062638eb feat: attachments support (#13) 2016-12-22 10:09:15 +01:00
Julien Fontanet
8c0028055a chore(package): use husky instead of ghooks 2016-12-20 15:06:29 +01:00
Julien Fontanet
74f060b309 fix(package): rename to xo-server-transport-nagios 2016-12-20 11:50:49 +01:00
Julien Fontanet
12de0ca463 0.1.0 2016-12-20 11:45:11 +01:00
Julien Fontanet
72ed59b65b chore(package): make package publishable 2016-12-20 11:45:05 +01:00
Julien Fontanet
fe369bfa18 chore(package): update all dependencies 2016-12-20 11:43:45 +01:00
badrAZ
c708fd65d7 feat: integration with xo-server-transport-nagios (#17) 2016-12-14 17:24:55 +01:00
badrAZ
0c497900a2 implementation (#1) 2016-12-14 16:33:40 +01:00
greenkeeper[bot]
0b92ceec90 chore(package): update babel-preset-env to version 1.0.1 (#23)
https://greenkeeper.io/
2016-12-10 17:36:47 +01:00
greenkeeper[bot]
7e5131c4d2 chore(package): update babel-preset-env to version 1.0.0 (#6)
https://greenkeeper.io/
2016-12-09 22:55:37 +01:00
greenkeeper[bot]
d585b47360 chore(package): update babel-preset-env to version 1.0.0 (#16)
https://greenkeeper.io/
2016-12-09 22:55:06 +01:00
greenkeeper[bot]
4530d95f48 chore(package): update babel-preset-env to version 1.0.0 (#11)
https://greenkeeper.io/
2016-12-09 22:52:58 +01:00
greenkeeper[bot]
013d4b9411 fix(package): update inquirer to version 2.0.0 (#23)
https://greenkeeper.io/
2016-12-06 23:18:15 +01:00
Julien Fontanet
0a8fed1950 0.2.3 2016-12-02 14:57:24 +01:00
Julien Fontanet
6dc4b4dc1b fix: workaround for VDI snapshots with $snaphot_of which point to themselves 2016-12-02 14:56:19 +01:00
greenkeeper[bot]
b25f418411 chore(package): update dependencies (#20)
https://greenkeeper.io/
2016-11-29 13:41:32 +01:00
greenkeeper[bot]
985a4780f5 chore(package): update dependencies (#26)
https://greenkeeper.io/
2016-11-29 13:41:04 +01:00
Olivier Lambert
92a1f2c6d5 feat(description): better explanation of checkCertificate (#22) 2016-11-29 10:51:24 +01:00
greenkeeper[bot]
81b9348e50 chore(package): update dependencies (#5)
https://greenkeeper.io/
2016-11-28 15:06:32 +01:00
Julien Fontanet
04e7d54620 v0.8.2 2016-11-25 11:50:31 +01:00
Julien Fontanet
729dbe16c0 fix(list objects): fix without filtering
Fixes #19
2016-11-25 11:49:28 +01:00
greenkeeper[bot]
974650bc56 chore(package): update babel-preset-env to version 0.0.9 (#22)
https://greenkeeper.io/
2016-11-24 22:42:22 +01:00
greenkeeper[bot]
8c9ed833c3 chore(package): update babel-preset-env to version 0.0.9 (#5)
https://greenkeeper.io/
2016-11-24 22:05:47 +01:00
greenkeeper[bot]
5e86e64e18 chore(package): update babel-preset-env to version 0.0.9 (#10)
https://greenkeeper.io/
2016-11-24 22:04:27 +01:00
greenkeeper[bot]
235c789c5e chore(package): update babel-preset-env to version 0.0.9 (#15)
https://greenkeeper.io/
2016-11-24 22:04:18 +01:00
Julien Fontanet
ab48d06967 feat(README): update usage 2016-11-24 12:39:14 +01:00
Julien Fontanet
18ca950cd2 0.8.1 2016-11-24 12:35:02 +01:00
Julien Fontanet
82489b36c8 0.8.0 2016-11-24 12:34:04 +01:00
Julien Fontanet
a67b6130f8 feat(list objects): ability to select displayed properties
Fixes #18
2016-11-24 12:33:24 +01:00
Julien Fontanet
eab007db6e 0.6.0 2016-11-23 11:15:55 +01:00
Julien Fontanet
889eae276e 0.6.0 2016-11-23 10:17:38 +01:00
Julien Fontanet
2b2a72252b chore(package): update all dependencies 2016-11-23 10:12:48 +01:00
Olivier Lambert
13e4568d3b feat(Slack): support Slack transport (#14) 2016-11-23 10:02:01 +01:00
Julien Fontanet
92c4dda801 0.3.0 2016-11-23 09:57:41 +01:00
Julien Fontanet
3e59ba4563 chore(package): update all dependencies 2016-11-23 09:57:23 +01:00
Julien Fontanet
99c95626df chore: pass options directly to the constructor 2016-11-23 09:49:27 +01:00
Julien Fontanet
20a9fc2497 chore(package): add description 2016-11-23 09:48:07 +01:00
Julien Fontanet
fb32aeeeb6 0.8.2 2016-11-18 18:32:35 +01:00
Julien Fontanet
76cb4037d4 fix: build for > 2% browsers
Fixes #21
2016-11-18 18:32:27 +01:00
Julien Fontanet
af1530db36 0.8.1 2016-11-18 11:51:23 +01:00
Julien Fontanet
9d6560aece chore(package): update all dependencies 2016-11-18 11:50:26 +01:00
Julien Fontanet
caba246e0b fix(README): update doc 2016-11-18 10:46:51 +01:00
Julien Fontanet
731e2dc4c4 0.7.4 2016-11-17 17:33:08 +01:00
Julien Fontanet
815bdf3454 fix(package): downgrade nice-pipe to 0.0.0 2016-11-17 17:33:04 +01:00
Julien Fontanet
b36ef9fdb1 0.7.3 2016-11-17 15:40:29 +01:00
Julien Fontanet
fee3f7a716 fix(list objects): writes one objects at a time
Fixes vatesfr/xo-web#1356
2016-11-17 14:56:43 +01:00
Julien Fontanet
dd00d6581f 0.1.0 2016-11-16 11:52:36 +01:00
Pierre Donias
f4fc7acf4d feat(api-errors): 10 new errors (#3)
See vatesfr/xo-web#1481
2016-11-16 11:21:46 +01:00
Julien Fontanet
5b8cdf06b3 feat(test): new protocol (#2)
Plugins are now testable.
2016-11-15 11:01:50 +01:00
Julien Fontanet
9b3668423e 0.2.0 2016-11-10 10:47:43 +01:00
greenkeeper[bot]
7227af9aac fix(package): update fs-promise to version 1.0.0 (#21)
https://greenkeeper.io/
2016-11-10 08:57:20 +01:00
greenkeeper[bot]
7c8194307e fix(package): update fs-promise to version 1.0.0 (#17)
https://greenkeeper.io/
2016-11-10 08:56:51 +01:00
Julien Fontanet
9b5fac9e2b feat(test): throw an error when authentication fails 2016-11-09 17:08:17 +01:00
badrAZ
47991b7d1a feat(test): this plugin is now testable (#20)
See vatesfr/xo-web#1749
2016-11-09 16:41:53 +01:00
Badr AZIZBI
a5ea24311a Errors fixed 2016-11-09 15:53:16 +01:00
badrAZ
3c11f0acda feat(test): this plugin is now testable (#9)
See vatesfr/xo-web#1749
2016-11-09 14:55:08 +01:00
greenkeeper[bot]
4294dfd8fe chore(package): update dependencies (#18)
https://greenkeeper.io/
2016-11-07 21:20:54 +01:00
Pierre Donias
e7b739bb3b feat(api-errors): error factories (#1) 2016-11-07 12:30:29 +01:00
Julien Fontanet
baf5f7491d 0.7.2 2016-11-04 17:24:00 +01:00
Badr AZIZBI
a2afe2fa1a Error correction 2016-11-04 15:47:45 +01:00
Badr AZIZBI
8496a9bebd Initial commit 2016-11-04 13:42:23 +01:00
greenkeeper[bot]
0d6b7d6f04 chore(package): update babel-preset-env to version 0.0.7 (#2)
https://greenkeeper.io/
2016-11-03 08:59:51 +01:00
Julien Fontanet
59d6a51635 fix(package): fix depcheck (again) 2016-11-02 17:23:40 +01:00
Julien Fontanet
b4693019f7 fix(package): fix depcheck 2016-11-02 17:02:14 +01:00
Julien Fontanet
320d7a89ca fix(package): fix build 2016-11-02 17:02:07 +01:00
Julien Fontanet
1016f5f26f initial commit 2016-11-02 16:43:19 +01:00
Julien Fontanet
9284aee3fa 0.4.1 2016-11-02 15:12:23 +01:00
Julien Fontanet
32726efe88 Revert "Temporary work around for eslint/eslint#3946."
This reverts commit 63426818e6d43181ae635819fd1697fc9ea0a3d8.
2016-11-02 15:10:29 +01:00
Julien Fontanet
87daf0271c chore(package): update all dependencies 2016-11-02 15:03:45 +01:00
Julien Fontanet
70e73a5a65 fix(Collection#remove): properly report the key in related event 2016-11-02 15:00:04 +01:00
Julien Fontanet
588c369615 fix(package): fix URLs 2016-11-02 11:45:31 +01:00
greenkeeper[bot]
9aa9d4452c chore(package): update dependencies (#16)
https://greenkeeper.io/
2016-11-02 09:30:20 +01:00
greenkeeper[bot]
d7fe25c4fc chore(package): update dependencies (#17)
https://greenkeeper.io/
2016-11-01 22:01:39 +01:00
greenkeeper[bot]
990c5e9f08 chore(package): update dependencies (#25)
https://greenkeeper.io/
2016-11-01 22:01:31 +01:00
Julien Fontanet
7e2f2f6102 chore(package): various updates 2016-10-28 13:47:21 +02:00
Julien Fontanet
51def6535f chore(package): various updates 2016-10-28 12:10:31 +02:00
Julien Fontanet
14156b0911 chore(package): update all dependencies 2016-10-28 11:54:51 +02:00
Julien Fontanet
e2ba1fa7f8 initial commit 2016-10-28 11:48:06 +02:00
Julien Fontanet
cdf1a5fe47 fix(package): do not use babel-preset-babili 2016-10-28 11:14:17 +02:00
Julien Fontanet
abf146707f chore(package): update all dependencies 2016-10-28 11:08:32 +02:00
Julien Fontanet
c24a4009c8 chore(package): use full lodash package 2016-10-24 17:29:34 +02:00
Julien Fontanet
7b928c4d41 chore(package): add Travis CI conf 2016-10-24 16:03:04 +02:00
Julien Fontanet
4d50eae3c9 0.7.1 2016-10-24 15:58:32 +02:00
Julien Fontanet
c3a5e0592d chore(package): add --unregister doc 2016-10-24 15:58:14 +02:00
Julien Fontanet
207aef7cb3 fix(--list-commands): fix --json flag 2016-10-24 15:55:22 +02:00
Julien Fontanet
fbbd9ae249 chore(package): update (almost) all dependencies 2016-10-24 15:55:01 +02:00
Julien Fontanet
7aa591ffbd fix(package): handle multiline params 2016-10-24 15:33:40 +02:00
Greenkeeper
1fd91de50d chore(package): update promise-toolbox to version 0.7.0 (#8)
https://greenkeeper.io/
2016-10-24 15:03:35 +02:00
Julien Fontanet
b87ad2df54 0.5.1 2016-10-20 16:18:00 +02:00
Julien Fontanet
c9e2f94daf fix(markdown): insert a blank line after lists 2016-10-20 15:19:17 +02:00
Julien Fontanet
52774c7d6d 0.3.1 2016-10-19 13:17:10 +02:00
Julien Fontanet
01686b8e60 fix(package): behave with missing thresholds object 2016-10-18 17:34:06 +02:00
Julien Fontanet
0317d6a862 chore(package): remove unused test env 2016-10-18 17:33:29 +02:00
Julien Fontanet
2a316b1ffa chore(package): use constants in configuration schema 2016-10-18 17:31:46 +02:00
Julien Fontanet
b120146cdc chore(package): lots of comments 2016-10-12 17:21:45 +02:00
Greenkeeper
7587458f99 Update promise-toolbox to version 0.6.0 🚀 (#7)
https://greenkeeper.io/
2016-10-12 09:12:14 +02:00
Julien Fontanet
f5fb066975 chore(package): babel-preset-es2015 → babel-preset-latest 2016-10-11 10:51:21 +02:00
Julien Fontanet
cd6a0fa678 fix(package): use babel-plugin-transform-runtime 2016-10-11 10:51:04 +02:00
Julien Fontanet
e735420dd8 chore(package): babel-preset-es2015 → babel-preset-latest 2016-10-11 10:49:37 +02:00
Julien Fontanet
bc2f17c840 fix(package): use babel-plugin-transform-runtime 2016-10-11 10:47:05 +02:00
Julien Fontanet
8c459cac0f fix(package): remove babel-plugin-lodash 2016-10-11 10:43:28 +02:00
Julien Fontanet
878d2d9260 fix(package): remove babel-plugin-lodash 2016-10-11 10:38:39 +02:00
Julien Fontanet
14bca4bbf7 0.2.2 2016-10-04 15:16:10 +02:00
Julien Fontanet
ea55c10c4d chore(package): pnpm compatibility 2016-10-04 15:15:39 +02:00
Julien Fontanet
75cde40b0e fix(VM-template): normal permissions handling
Fixes vatesfr/xo-web#1620
2016-10-04 15:13:08 +02:00
Julien Fontanet
5434b4987f chore(package): remove unused deps 2016-10-04 15:09:12 +02:00
Julien Fontanet
74fba76b85 chore(package): update all dependencies 2016-09-29 14:16:47 +02:00
Julien Fontanet
2b8996e965 chore(package): update all dependencies 2016-09-29 14:00:25 +02:00
Julien Fontanet
4673af6fd8 chore(package): update all dependencies 2016-09-29 12:54:46 +02:00
Greenkeeper
10fddc51bb chore(package): update babel-eslint to version 7.0.0 (#11)
https://greenkeeper.io/
2016-09-27 23:40:44 +02:00
Greenkeeper
2d14fde671 chore(package): update babel-eslint to version 7.0.0 (#23)
https://greenkeeper.io/
2016-09-27 23:39:57 +02:00
Greenkeeper
98cd2746ef chore(package): update babel-eslint to version 7.0.0 (#15)
https://greenkeeper.io/
2016-09-27 23:39:13 +02:00
Greenkeeper
ea18e4129c chore(package): update babel-eslint to version 7.0.0 (#14)
https://greenkeeper.io/
2016-09-27 23:37:26 +02:00
Greenkeeper
6980e2b959 chore(package): update babel-eslint to version 7.0.0 (#4)
https://greenkeeper.io/
2016-09-27 23:37:03 +02:00
Greenkeeper
7e7ec83c12 chore(package): update babel-eslint to version 7.0.0 (#19)
https://greenkeeper.io/
2016-09-27 23:27:37 +02:00
Julien Fontanet
a8340c24c3 chore(package): update all dependencies 2016-09-27 15:29:23 +02:00
Greenkeeper
f49f3fb2a6 Update all dependencies 🌴 (#3)
https://greenkeeper.io/
2016-09-25 16:20:17 +02:00
Greenkeeper
3c9ef8d199 chore(package): update dependencies (#10)
https://greenkeeper.io/
2016-09-25 15:54:44 +02:00
Greenkeeper
16dde5c772 chore(package): update standard to version 8.1.0 (#12)
https://greenkeeper.io/
2016-09-18 05:17:03 +02:00
Julien Fontanet
96635a98f5 0.5.0 2016-09-09 15:50:38 +02:00
Julien Fontanet
54ef65ced9 feat: display error message
Fixes vatesfr/xo-web#1462
2016-09-05 10:39:54 +02:00
Julien Fontanet
4fb6bef04c feat: express global status in mail subject
New format is: `[Xen Orchestra][Success | Failure] Backup report for $tag`

Fixes vatesfr/xo-web#1463
2016-09-05 10:39:11 +02:00
Greenkeeper
224a79840d chore(package): update standard to version 8.0.0 (#14)
https://greenkeeper.io/
2016-08-24 12:23:09 -04:00
Greenkeeper
99ae3e0f7f chore(package): update standard to version 8.0.0 (#18)
https://greenkeeper.io/
2016-08-24 11:37:01 -04:00
Greenkeeper
02a4161ecb chore(package): update standard to version 8.0.0 (#22)
https://greenkeeper.io/
2016-08-24 11:33:05 -04:00
Greenkeeper
1a68c3947d chore(package): update standard to version 8.0.0 (#13)
https://greenkeeper.io/
2016-08-24 11:26:48 -04:00
Greenkeeper
d56590c6e6 chore(package): update nyc to version 8.0.0 (#12)
https://greenkeeper.io/
2016-08-16 23:41:08 +02:00
Julien Fontanet
f7b6fcf684 Merge pull request #17 from vatesfr/greenkeeper-nyc-8.1.0
Update nyc to version 8.1.0 🚀
2016-08-14 19:07:04 +02:00
Greenkeeper
df3ec9a629 chore(package): update nyc to version 8.1.0 (#21)
https://greenkeeper.io/
2016-08-14 19:06:15 +02:00
Greenkeeper
6bc4bf308b chore(package): update nyc to version 8.1.0 (#13)
https://greenkeeper.io/
2016-08-14 19:05:44 +02:00
greenkeeperio-bot
2ddb84f457 chore(package): update nyc to version 8.1.0
https://greenkeeper.io/
2016-08-14 10:26:33 +02:00
Olivier Lambert
6b82cc7510 fix(readme): typo 2016-08-12 17:22:28 +02:00
Greenkeeper
ac6d14113a chore(package): update mocha to version 3.0.0 (#9)
https://greenkeeper.io/
2016-08-02 19:21:37 +02:00
Greenkeeper
398db72b44 chore(package): update mocha to version 3.0.0 (#17)
https://greenkeeper.io/
2016-08-02 19:21:01 +02:00
Julien Fontanet
a3da3299fa Merge pull request #15 from vatesfr/greenkeeper-mocha-3.0.0
Update mocha to version 3.0.0 🚀
2016-08-02 16:20:59 +02:00
Greenkeeper
e763db7102 chore(package): update mocha to version 3.0.0 (#11)
https://greenkeeper.io/
2016-08-02 16:16:53 +02:00
greenkeeperio-bot
2b504ce5ab chore(package): update mocha to version 3.0.0
https://greenkeeper.io/
2016-08-01 07:33:49 +02:00
Julien Fontanet
39a16f9a7f 0.8.0 2016-07-20 16:53:56 +02:00
Julien Fontanet
f3ea8d012f feat(Xo#refreshUser) 2016-07-20 16:53:56 +02:00
Julien Fontanet
1b9aa63096 Merge pull request #14 from vatesfr/greenkeeper-nyc-7.0.0
Update nyc to version 7.0.0 🚀
2016-07-15 11:29:20 +02:00
Greenkeeper
11bde53069 chore(package): update nyc to version 7.0.0 (#16)
https://greenkeeper.io/
2016-07-15 11:27:31 +02:00
Greenkeeper
f115ee18c4 chore(package): update nyc to version 7.0.0 (#10)
https://greenkeeper.io/
2016-07-15 11:27:11 +02:00
Greenkeeper
162a56232c chore(package): update nyc to version 7.0.0 (#8)
https://greenkeeper.io/
2016-07-15 11:26:53 +02:00
greenkeeperio-bot
de6f0ef8eb chore(package): update nyc to version 7.0.0
https://greenkeeper.io/
2016-07-09 09:13:32 +02:00
wescoeur
cd8a92c30b 0.3.0 2016-07-07 12:00:24 +02:00
wescoeur
10030c4959 Use default value on cpu/memoryFree. 2016-07-07 11:58:12 +02:00
wescoeur
ffc155c341 0.2.1 2016-06-30 14:12:19 +02:00
wescoeur
42ea76eb2a Supports ui schema builder of xo-web. 2016-06-30 14:10:59 +02:00
Julien Fontanet
be503f1341 0.4.1 2016-06-30 10:12:30 +02:00
Julien Fontanet
6f4509c260 fix: report only on failures with xo-web 5 2016-06-30 10:12:07 +02:00
Julien Fontanet
b2331084d1 0.4.0 2016-06-29 11:40:58 +02:00
Julien Fontanet
ab3a594884 chore(package): update all dependencies 2016-06-29 11:34:43 +02:00
Julien Fontanet
592adcf42e 0.1.0 2016-06-29 10:40:03 +02:00
Julien Fontanet
978c881ab7 chore(package): update all dependencies 2016-06-29 10:39:05 +02:00
Julien Fontanet
99727447ef 0.2.1 2016-06-24 14:32:25 +02:00
Julien Fontanet
e02cb56ee0 feat(VM-controller): inherits from its container 2016-06-24 14:32:16 +02:00
Julien Fontanet
02f75fb2e1 0.3.0 2016-06-08 17:17:45 +02:00
Fabrice Marsaud
6cced719dc Handling a missing leading slash in nfs path 2016-06-08 17:08:54 +02:00
Fabrice Marsaud
45ec0b9b01 No more mount matters in nfs remotes + tests 2016-06-08 15:05:09 +02:00
Julien Fontanet
2451ac3ade feat(parse): local type is now file 2016-06-08 14:38:31 +02:00
Julien Fontanet
45a94fe73d feat(parse): only accepts a string 2016-06-08 14:36:10 +02:00
Julien Fontanet
e1173e6565 0.2.0 2016-06-07 16:27:34 +02:00
Julien Fontanet
46f6911ef8 feat(README): add usage 2016-06-07 16:27:05 +02:00
Julien Fontanet
7629bf5be2 feat: simplify use for single object 2016-06-07 16:26:52 +02:00
Julien Fontanet
fb17de7988 0.2.3 2016-06-07 10:10:51 +02:00
Fabrice Marsaud
32f4d42e59 fix(parse): revert to 0.2 API (#10) 2016-06-07 10:10:20 +02:00
Julien Fontanet
0031c2a9b7 0.2.2 2016-06-06 17:57:22 +02:00
Fabrice Marsaud
509e99913a fix(parse): fix tests and many issues (#9) 2016-06-06 17:56:59 +02:00
Julien Fontanet
c639cfcfd9 fix(parse): do not edit the remote object inplace 2016-06-06 17:17:57 +02:00
Julien Fontanet
b140a2ca3e chore(tests): refactor data 2016-06-06 17:17:19 +02:00
Julien Fontanet
aa448e7a41 0.2.1 2016-06-06 13:33:33 +02:00
Fabrice Marsaud
220750f887 fix(SMB): use of @ and : chars in passwords (#8)
Fixes #4.
2016-06-06 12:11:29 +02:00
Fabrice Marsaud
808cc5d8d0 fix: file protocol starts with 3 / (#6)
Fixes #7
2016-06-06 10:57:27 +02:00
Julien Fontanet
02e1a32fae feat(README) 2016-05-27 17:03:03 +02:00
Julien Fontanet
d1690bda81 Initial commit 2016-05-27 17:00:49 +02:00
Julien Fontanet
4d4a2897a5 0.7.0 2016-05-11 12:59:14 +02:00
Julien Fontanet
cd73c8f82f 0.2.0 2016-05-11 12:58:59 +02:00
Julien Fontanet
934e67d146 Document JSON syntax 2016-05-11 12:57:53 +02:00
Julien Fontanet
4c96e44b9b fix(format): use correct type for local 2016-05-11 12:49:29 +02:00
Julien Fontanet
189900549a feat(tests): basic tests for local and smb types 2016-05-11 12:49:06 +02:00
Julien Fontanet
6d18420a5d fix(tests): make them pass 2016-05-11 12:22:28 +02:00
Julien Fontanet
ab3d307393 chore(package): update all dependencies 2016-05-11 12:22:26 +02:00
ABHAMON Ronan
89b2156f61 chore(package): remove unused babel-runtime (#3) 2016-05-11 12:19:09 +02:00
ABHAMON Ronan
2f95da1892 fix(package): lodash.xxx are prod dependencies (#2) 2016-05-11 12:08:34 +02:00
Julien Fontanet
306d5d8fc7 feat(event): authenticationFailure 2016-05-06 12:33:56 +02:00
Julien Fontanet
5553d5fefa Merge pull request #8 from vatesfr/greenkeeper-update-all
Update all dependencies 🌴
2016-05-04 16:21:06 +02:00
Julien Fontanet
9328518bbc chore: various updates 2016-05-04 16:14:50 +02:00
Julien Fontanet
75bb7d5a2d fix(prompt-schema): adapt to inquirer version 1.0.0 2016-05-04 14:48:47 +02:00
Julien Fontanet
ceab4e37cd style(prompt-schema): handle ESLint special cases 2016-05-04 14:45:01 +02:00
greenkeeperio-bot
afb6974cc0 chore(package): update dependencies
https://greenkeeper.io/
2016-05-04 14:35:22 +02:00
Greenkeeper
a4dc965c23 chore(package): update standard to version 7.0.0 (#3)
https://greenkeeper.io/
2016-05-03 09:16:47 +02:00
Greenkeeper
8b65c280a8 chore(package): update standard to version 7.0.0 (#4)
https://greenkeeper.io/
2016-05-03 09:05:49 +02:00
Julien Fontanet
4ab24d2fe5 0.1.0 2016-04-27 16:11:12 +02:00
Julien Fontanet
c70ca2ff64 style: add missing parentheses 2016-04-27 15:58:22 +02:00
Julien Fontanet
649ab26da8 feat: gracefully handle missing objects 2016-04-27 15:52:19 +02:00
Julien Fontanet
c921ea6eb7 Merge pull request #1 from vatesfr/greenkeeper-update-all
Update all dependencies 🌴
2016-04-27 15:49:02 +02:00
greenkeeperio-bot
58aed76aa3 chore(package): update dependencies
https://greenkeeper.io/
2016-04-27 15:44:41 +02:00
Julien Fontanet
e286c57ce4 Merge pull request #2 from vatesfr/v0.8.x
Refactor on top of jsonrpc-websocket-client
2016-04-27 15:26:39 +02:00
Julien Fontanet
840f0b6379 chore(package): update babel-eslint to version 6.0.4 2016-04-27 15:18:09 +02:00
Julien Fontanet
538025edd5 chore: remove irrelevant comment 2016-04-27 15:14:33 +02:00
Julien Fontanet
1e7d1b1628 0.0.0 2016-04-08 11:05:41 +02:00
Julien Fontanet
defd42f74e VDI: fix fallback on VM. 2016-04-08 10:46:57 +02:00
wescoeur
aa54ab6e51 0.2.0 2016-04-06 16:01:24 +02:00
wescoeur
f0c28c74d8 Try to power on a hosts set. (Performance mode) 2016-04-06 16:00:01 +02:00
wescoeur
3e285d6131 Search destination host per pool. (Performance mode) 2016-04-06 15:24:34 +02:00
Julien Fontanet
c96d94329e Fix using / as url. 2016-03-30 11:34:26 +02:00
wescoeur
627227f2f9 0.1.0 2016-03-24 11:48:49 +01:00
wescoeur
42cef0da88 Performance and density mode work. 2016-03-24 11:44:23 +01:00
wescoeur
06f60b7d92 Make code modular. 2016-03-24 09:46:14 +01:00
wescoeur
3ddb4d2b23 Log VM migrations (density mode). 2016-03-23 17:37:41 +01:00
wescoeur
5a825bd459 Density mode test. 2016-03-23 16:52:05 +01:00
Olivier Lambert
ab1f08f687 0.3.4 2016-03-23 10:35:41 +01:00
Pierre
2f89e3658a Backup type in report e-mail (See vatesfr/xo-web#822) 2016-03-23 10:11:01 +01:00
Olivier Lambert
f08ab729bd 0.3.3 2016-03-17 14:18:18 +01:00
Pierre
052c974369 Fixed get tag 2016-03-17 14:15:19 +01:00
Olivier Lambert
e760e868c1 0.3.2 2016-03-17 13:47:45 +01:00
Pierre
5e3831a1a4 Added tag in report e-mail 2016-03-17 12:11:49 +01:00
wescoeur
99e046ddea Performance mode: The worst host is removed of hosts array after optimization. 2016-03-17 12:11:34 +01:00
wescoeur
12e0759711 Optimize hosts in order priority. 2016-03-17 11:21:15 +01:00
wescoeur
da0c1cec22 Fix cpu average. Use the cpu number of host and vm. 2016-03-17 11:01:12 +01:00
Julien Fontanet
d23df2ab15 0.0.6 2016-03-16 14:04:14 +01:00
Julien Fontanet
dbe828097c Do not set credentials if they are undefined. 2016-03-16 14:03:28 +01:00
Julien Fontanet
48a0623ded Fix handling of ignoreUnauthorized option. 2016-03-16 14:03:01 +01:00
Julien Fontanet
3527b86ec5 Add empty test file. 2016-03-15 16:25:46 +01:00
Julien Fontanet
fe7a9104a8 Minor package.json changes. 2016-03-15 16:13:49 +01:00
Julien Fontanet
cbfb94afcb Update deps. 2016-03-15 15:42:14 +01:00
Julien Fontanet
74d8eff6d8 Add support for JSON encoded values. 2016-03-14 10:20:15 +01:00
wescoeur
d7ed9ab64e Remove masters from toOptimize and hosts. 2016-03-11 16:48:36 +01:00
wescoeur
3b6c5898fe Plugin can use enum for modes now. 2016-03-11 15:57:29 +01:00
wescoeur
ae22adc920 Density mode in progress. 2016-03-11 15:45:23 +01:00
wescoeur
5dacf9c3f5 The configuration object must not be modified. 2016-03-11 14:42:40 +01:00
wescoeur
9129bfa284 Excluded hosts in options. 2016-03-11 11:32:37 +01:00
wescoeur
96190c21d6 threshold 0 can by used with cpu. 2016-03-09 17:53:38 +01:00
wescoeur
aa117a0ee3 Fixes hosts top optimize. 2016-03-09 17:29:57 +01:00
wescoeur
39b0ea381b Debug exec plans 2016-03-09 17:24:57 +01:00
wescoeur
021cea0b34 Thresholds are exposed in configuration. 2016-03-09 16:59:36 +01:00
wescoeur
eaad41fe55 Load balancer config use a checkbox. 2016-03-09 16:06:06 +01:00
wescoeur
e25d58d70a Fixes. 2016-03-09 15:34:57 +01:00
wescoeur
9c0967170a Schema update. 2016-03-09 12:23:02 +01:00
wescoeur
abd89df365 Three class: Plan, PerfomancePlan, DensityPlan. 2016-03-03 14:47:56 +01:00
wescoeur
651e4bb775 Fixes (cpus to cpu). Add Low/Critical thresholds. 2016-03-03 11:17:04 +01:00
wescoeur
7f06d6e68c Low threshold are automatically computed. 2016-03-02 16:56:59 +01:00
wescoeur
e5146f7def Many fixes. Cron Job is in a job structure. 2016-03-02 12:16:30 +01:00
wescoeur
d9bf7c7d12 Many fixes. Configure must operate if cron job is running. 2016-03-02 11:20:28 +01:00
wescoeur
b0bf18e235 Debug messages. 2016-03-02 10:36:43 +01:00
wescoeur
f001b2c68f Unstable, plugin can migrate vm. (to test) 2016-03-02 10:24:56 +01:00
wescoeur
296141ad3d Unsable: _computeOptimizations 2016-02-26 16:42:11 +01:00
wescoeur
7abba0a69b Many fixes. Compute real cpu usage of vms. 2016-02-26 15:28:00 +01:00
wescoeur
dee7767427 Get only the vms stats of the worst host. 2016-02-26 11:57:41 +01:00
wescoeur
087a71367d Optimizations only on one host ! 2016-02-26 11:07:22 +01:00
wescoeur
bec2e3b4a0 getVms & getVmsStats functions. 2016-02-26 10:20:15 +01:00
wescoeur
274884ef4d Reorder exceeded hosts by priority. 2016-02-25 16:06:46 +01:00
wescoeur
d3f52cdd1a Compute exceeded hosts. 2016-02-25 13:10:27 +01:00
wescoeur
cb97e37c15 Get stats for host. 2016-02-24 15:27:11 +01:00
wescoeur
3013fa86b6 Configure implem. 2016-02-24 12:50:03 +01:00
wescoeur
2593743746 Json schema (prototype). 2016-02-24 10:53:48 +01:00
wescoeur
ab6bd56006 Initial commit. 2016-02-24 10:05:20 +01:00
Julien Fontanet
e02b8522d6 0.0.5 2016-02-22 16:03:54 +01:00
Julien Fontanet
8cf74c88ce Add ignoreUnauthorized option. 2016-02-22 16:03:33 +01:00
Julien Fontanet
4095d6dcf6 0.3.1 2016-02-22 15:45:51 +01:00
Julien Fontanet
cd4221e4f2 0.3.0 2016-02-22 15:44:38 +01:00
Julien Fontanet
e4e65e4576 0.0.4 2016-02-22 15:42:05 +01:00
Julien Fontanet
72e93786df Log errors. 2016-02-22 15:41:03 +01:00
Julien Fontanet
548754821e Merge pull request #8 from vatesfr/conditional-reporting
Implement conditional reporting.
2016-02-19 10:32:17 +01:00
Danp2
77f85579e3 Implement conditional reporting. 2016-02-19 10:23:48 +01:00
Julien Fontanet
05c325d686 Fetch the correct VM even when the param is vm and not id (fix #3). 2016-02-12 09:09:39 +01:00
Julien Fontanet
adb71ad174 FIx my previous commit -_-". 2016-02-10 11:06:47 +01:00
Julien Fontanet
4a95f5cd9d Coding style. 2016-02-10 10:48:02 +01:00
Julien Fontanet
f3b368fae4 Merge pull request #2 from Danp2/Danp2-deltacopy
Enable reporting on additional backup types.
2016-02-10 10:45:54 +01:00
Julien Fontanet
20919a8a39 Absence of tests should not prevent CI linting. 2016-02-10 10:31:30 +01:00
Danp2
f2e86efc4d Update index.js 2016-02-09 21:22:17 -06:00
Danp2
98b27d647e Add logic for processing delta copy backup types 2016-02-09 21:10:00 -06:00
Julien Fontanet
739641b857 0.0.3 2016-02-04 18:12:31 +01:00
Julien Fontanet
d02a110f99 Minor comment. 2016-02-04 18:12:16 +01:00
Julien Fontanet
bf586d0837 0.0.0-0 2016-02-03 11:41:01 +01:00
Julien Fontanet
ed45a9b156 VDI-snapshot inherits from its VDI. 2016-02-03 11:35:48 +01:00
Julien Fontanet
f4ea39b602 Initial commit. 2016-02-03 10:02:39 +01:00
Julien Fontanet
a6dca12bad user and password config entries are optional (fix #2). 2016-02-02 16:49:45 +01:00
Fabrice Marsaud
f26fc9a0f0 0.1.0 2016-01-26 17:32:49 +01:00
Fabrice Marsaud
3fd637b3c7 method rename 2016-01-26 17:32:35 +01:00
Fabrice Marsaud
ea605383d5 Various info 2016-01-26 16:08:40 +01:00
Fabrice Marsaud
a4fa670dc5 Initial commit 2016-01-26 16:01:03 +01:00
Julien Fontanet
7d7cc56527 0.8.0-1 2016-01-22 17:47:41 +01:00
Julien Fontanet
85d0271b86 Xo#call(method, args).retry(predicate) 2016-01-22 17:47:35 +01:00
Julien Fontanet
749d5e22bb Update jsonrpc-websocket-client to 0.0.1-4. 2016-01-22 17:46:38 +01:00
Julien Fontanet
61c61adea1 Minor code dedup. 2016-01-21 15:42:34 +01:00
Julien Fontanet
c8a6fd19a7 0.8.0-0 2016-01-07 18:04:52 +01:00
Julien Fontanet
0b143b580a Various updates. 2016-01-07 18:01:11 +01:00
Julien Fontanet
ed69fedc0a Initial commit. 2016-01-05 18:13:43 +01:00
Julien Fontanet
ea0db57388 0.6.0 2016-01-05 18:03:58 +01:00
Julien Fontanet
e6ef6ccccf 0.5.1 2015-12-30 19:16:05 +01:00
Julien Fontanet
4826e14cad Log bind failures. 2015-12-30 19:15:58 +01:00
Julien Fontanet
5515f90147 README updates. 2015-12-30 19:12:24 +01:00
Julien Fontanet
2193c26acb 0.5.0 2015-12-30 19:03:13 +01:00
Julien Fontanet
1974a2c0e4 Use my Vates email. 2015-12-30 19:01:41 +01:00
Julien Fontanet
84fbe9ee06 Do not depend on Bluebird. 2015-12-30 19:00:32 +01:00
Julien Fontanet
b8e2cfc47f CLI for testing. 2015-12-30 17:46:21 +01:00
Julien Fontanet
553fc6f5d9 0.7.4 2015-12-30 12:01:12 +01:00
Julien Fontanet
f5d790b264 Do not automatically retry on connection errors! 2015-12-30 12:00:58 +01:00
Julien Fontanet
641e13496e Return null instead of throwing when no matches. 2015-12-28 10:18:47 +01:00
Julien Fontanet
a6e18819d4 Use arrow funcs where possible. 2015-12-28 10:18:21 +01:00
Julien Fontanet
faf5ff6aa4 Reading certs is now async. 2015-12-28 10:11:17 +01:00
Julien Fontanet
ae20ca5558 Update deps. 2015-12-28 09:20:11 +01:00
Julien Fontanet
22bd87c965 Update ldapjs to 1.0. 2015-12-28 09:20:11 +01:00
Julien Fontanet
2129645f39 Babel 6. 2015-12-28 09:20:11 +01:00
Julien Fontanet
93a07b6207 Typo. 2015-12-28 09:20:09 +01:00
Julien Fontanet
d98a457271 0.0.2 2015-12-03 16:34:23 +01:00
Julien Fontanet
ef3a0d881f Add empty file to make Travis not fail. 2015-12-03 16:34:13 +01:00
Julien Fontanet
669a9e5cc3 Merge pull request #1 from Phlogi/patch-1
Fix required field, changed from pass to password
2015-12-03 16:32:31 +01:00
Phlogi
5eb58ae6cc Fix required field, changed from pass to password
this one was missed on 7af5964f13
2015-12-03 16:29:26 +01:00
wescoeur
7fa1923400 0.1.1 2015-12-03 16:24:14 +01:00
wescoeur
4c165bd620 Package is not private. 2015-12-03 16:24:05 +01:00
wescoeur
7c04a455b4 0.1.0 2015-12-03 16:22:16 +01:00
ABHAMON Ronan
06b6061518 Update README.md 2015-12-03 16:19:49 +01:00
wescoeur
3821ee3dcd Update package.json description. 2015-12-03 16:11:58 +01:00
wescoeur
03a33646d6 0.2.1 2015-12-03 15:54:24 +01:00
wescoeur
791183553e Update README 2015-12-03 15:53:19 +01:00
wescoeur
de6ef49043 0.2.0 2015-12-03 15:43:35 +01:00
wescoeur
28bf7ee90b Update schema with titles/descriptions on mails/xmpp attributes. 2015-12-02 15:47:33 +01:00
wescoeur
4d1ca7ede4 Merge remote-tracking branch 'refs/remotes/origin/master' 2015-12-02 15:42:27 +01:00
wescoeur
f3b46515c5 Update for Xmpp support 2015-12-02 15:40:40 +01:00
wescoeur
0aa5e7ba63 Use {...conf} instead of conf (Frozen object) 2015-12-02 15:39:47 +01:00
wescoeur
8d8bf43b46 to parameter is now an array of xmpp address 2015-12-02 15:27:51 +01:00
wescoeur
9983407c8b Make the client in load. 2015-12-02 15:00:38 +01:00
wescoeur
2471ad4215 Plugin works with few options 2015-12-02 14:33:50 +01:00
wescoeur
f266982560 Use node-xmpp-client for the transport protocol 2015-12-02 10:46:24 +01:00
Julien Fontanet
c059a416f7 0.1.1 2015-12-01 15:54:42 +01:00
Olivier Lambert
82dc0c0593 Update README.md 2015-12-01 14:43:17 +01:00
Olivier Lambert
5faad3db92 Update the documentation 2015-11-30 19:15:01 +01:00
Olivier Lambert
099db6792a Update the documentatioh 2015-11-30 18:58:56 +01:00
wescoeur
74a31f3301 Add schema 2015-11-27 16:47:16 +01:00
wescoeur
f88c0b9b67 Init 2015-11-27 14:51:54 +01:00
wescoeur
61ef313b1c 0.1.0 2015-11-27 14:45:04 +01:00
Julien Fontanet
048cea19b7 0.0.1 2015-11-27 10:59:41 +01:00
Julien Fontanet
3e77c76c34 Make config entry from required. 2015-11-27 10:59:08 +01:00
Julien Fontanet
9b6c5d2ea3 Rename config entry pass to password. 2015-11-27 10:58:56 +01:00
wescoeur
2fa081a4ba Plugin supports now Disaster recovery jobs 2015-11-26 15:47:25 +01:00
Julien Fontanet
5b9a3ca1cb This module is no longer private. 2015-11-26 15:22:32 +01:00
Julien Fontanet
bf7c56a269 Remove unnecessary new line. 2015-11-26 15:21:40 +01:00
Julien Fontanet
d33af742dd Initial commit. 2015-11-26 15:19:09 +01:00
wescoeur
823879e9f9 0.0.2 2015-11-26 11:12:08 +01:00
ABHAMON Ronan
98eb285e14 Update README.md 2015-11-26 11:11:16 +01:00
wescoeur
37fd2e1103 0.0.1 2015-11-26 10:57:16 +01:00
ABHAMON Ronan
56db5dc341 Update README 2015-11-26 10:39:48 +01:00
ABHAMON Ronan
d48fa235b1 Update README 2015-11-25 17:41:17 +01:00
wescoeur
06a111495b Update package.json with plugin infos 2015-11-25 17:23:52 +01:00
wescoeur
f3fb0797bf Schema description fix 2015-11-25 17:14:58 +01:00
wescoeur
561b8f4eed Coding style fixes 2015-11-25 17:13:29 +01:00
wescoeur
8cfc6f0b1d Add schema + Plugin use now an array of receivers 2015-11-25 16:36:08 +01:00
wescoeur
7e04f26f78 Send email to one address 2015-11-25 16:11:27 +01:00
Julien Fontanet
348c30b61e Use Babel 5. 2015-11-25 15:00:41 +01:00
Julien Fontanet
ad1e60e137 Fix transport config. 2015-11-25 14:21:29 +01:00
Julien Fontanet
97e0a983f1 Fix schema. 2015-11-25 14:19:41 +01:00
Julien Fontanet
45681e645b Revert to Babel 5. 2015-11-25 10:21:02 +01:00
Julien Fontanet
a4e9f1a683 Remove unused code. 2015-11-24 11:48:31 +01:00
Julien Fontanet
f8c74daef5 Use object spread instead of altering passed object. 2015-11-24 11:47:53 +01:00
Julien Fontanet
b3a593afd7 Test on Node stable. 2015-11-24 11:47:31 +01:00
Julien Fontanet
d45461bc47 Various updates. 2015-11-23 16:24:47 +01:00
wescoeur
58aa2b6a49 Check backup calls 2015-11-20 11:30:01 +01:00
wescoeur
fb06905c86 Fix NoSuchObject exception 2015-11-20 11:07:47 +01:00
wescoeur
4a2911557d Fix remove listener 2015-11-20 10:31:29 +01:00
wescoeur
99caa5dddc Load/unload implem. 2015-11-20 10:13:12 +01:00
Julien Fontanet
86b42a3716 Initial commit. 2015-11-19 18:22:57 +01:00
wescoeur
12c4680501 Initial commit 2015-11-19 17:22:55 +01:00
Julien Fontanet
b7e05c236f Initial commit. 2015-11-19 16:59:06 +01:00
Julien Fontanet
0f03208aa1 Commands filtering. 2015-10-30 11:30:22 +01:00
Julien Fontanet
d58add18fc Document --list-objects 2015-10-28 14:47:24 +01:00
Julien Fontanet
3a0413d8bb Fix coding style. 2015-10-28 12:54:52 +01:00
Julien Fontanet
9122f9b291 Always use session.signIn(). 2015-10-28 12:46:51 +01:00
Julien Fontanet
d279db2a0e Update deps. 2015-10-28 12:42:20 +01:00
Julien Fontanet
c6657b9619 Sets the length when/if known on download. 2015-10-27 18:47:44 +01:00
Julien Fontanet
80d8388eb6 Update deps. 2015-10-27 16:12:09 +01:00
Julien Fontanet
b1ee4bdc09 Standard coding style. 2015-10-27 12:27:57 +01:00
Julien Fontanet
5b782993fd Fix the license. 2015-10-12 15:39:26 +02:00
Julien Fontanet
138e60e77c 0.4.2 2015-10-12 15:38:50 +02:00
Julien Fontanet
9771402c54 No need to reload the plugin after a configure. 2015-10-12 15:38:34 +02:00
Julien Fontanet
30dcb4d8d2 Should be globally installed. 2015-10-12 15:31:09 +02:00
Julien Fontanet
c418c766d8 This test is no longer relevant. 2015-10-12 10:26:45 +02:00
Julien Fontanet
334d843955 Move defaults to the schema. 2015-10-12 10:16:45 +02:00
Julien Fontanet
2e5169eb46 0.4.1 2015-10-09 18:55:23 +02:00
Julien Fontanet
733c619b1f 0.2.1 2015-10-09 18:53:13 +02:00
Julien Fontanet
2021b644c0 0.4.1 2015-10-09 18:52:57 +02:00
Julien Fontanet
f55ed13bd2 0.2.0 2015-10-09 17:57:27 +02:00
Julien Fontanet
98395abc17 0.4.0 2015-10-09 17:56:37 +02:00
Julien Fontanet
5db5c4e52c 0.4.0 2015-10-09 17:56:00 +02:00
Julien Fontanet
71e77ad45a New plugin API. 2015-10-08 13:55:57 +02:00
Julien Fontanet
25873e0e02 New plugin API. 2015-10-08 13:55:26 +02:00
Julien Fontanet
22638a8147 New plugin API. 2015-10-08 13:54:33 +02:00
Julien Fontanet
ce7bc9f438 Correctly export isObject(). 2015-10-02 12:57:51 +02:00
Julien Fontanet
43a362d0eb Use constants for code robustness & performance. 2015-10-02 12:56:39 +02:00
Julien Fontanet
7d7e6e10b9 Properly use Object.prototype.hasOwnProperty(). 2015-10-02 12:27:18 +02:00
Julien Fontanet
73821b0f12 Isolate isObject(). 2015-10-02 12:26:54 +02:00
Julien Fontanet
2071a7d308 Use kindof instead of typeof where appropriate. 2015-10-01 20:47:32 +02:00
Julien Fontanet
c439daadad Minor opt. 2015-10-01 20:43:27 +02:00
Julien Fontanet
083f325076 Temporary work around for eslint/eslint#3946. 2015-10-01 20:31:43 +02:00
Julien Fontanet
ee53433dcc Use const wherever possible. 2015-10-01 18:25:01 +02:00
Julien Fontanet
ad10d13a75 Update standard. 2015-10-01 18:23:17 +02:00
Julien Fontanet
4fd9639457 0.7.3 2015-09-24 16:40:31 +02:00
Julien Fontanet
2f2ee1f431 Update ws 0.8.0 to support Node 4. 2015-09-24 16:39:54 +02:00
Julien Fontanet
a2f5f1cb0e 0.5.0 2015-09-21 18:13:13 +02:00
Julien Fontanet
1fbe7d92eb Add --list-objects (fix #8 and fix #9). 2015-09-21 18:13:04 +02:00
Julien Fontanet
760974c7c7 Clearer explanation for the callback URL. 2015-09-21 10:30:53 +02:00
Julien Fontanet
e1587d11b1 FIXME: certificates reading should be async. 2015-09-21 10:23:48 +02:00
Julien Fontanet
0595360808 Update deps. 2015-09-18 14:51:34 +02:00
Julien Fontanet
1a8149e456 This package should be installed globally. 2015-09-18 14:51:18 +02:00
Julien Fontanet
fd6f92f6b5 0.3.1 2015-09-16 11:57:52 +02:00
Julien Fontanet
ddf7226ba8 Minor fixes in the README. 2015-09-16 11:57:44 +02:00
Julien Fontanet
a1cd95752a 0.3.0 2015-09-16 11:35:32 +02:00
Julien Fontanet
d131a26a41 Fix the license. 2015-09-16 11:35:26 +02:00
Julien Fontanet
10a7c75001 Enable tests on Node 4. 2015-09-16 11:13:25 +02:00
Julien Fontanet
1f454ababf Support custom username field. 2015-09-16 11:12:42 +02:00
Julien Fontanet
84c9532456 0.1.0 2015-09-11 10:45:02 +02:00
Julien Fontanet
aa7c9bca46 Test with Node 4. 2015-09-11 10:44:32 +02:00
Julien Fontanet
3d00b4ffbe 0.2.0 2015-09-11 10:43:36 +02:00
Julien Fontanet
0304d6079d GitHub screenshot. 2015-09-07 17:24:27 +02:00
Julien Fontanet
ae41e64999 Fix passport-github links. 2015-09-07 17:02:57 +02:00
Julien Fontanet
8c9f32c927 Initial commit. 2015-09-07 17:01:37 +02:00
Julien Fontanet
b4c612ff6d Remove unnecessary ESLint directive. 2015-09-07 14:16:10 +02:00
Julien Fontanet
e708268067 Test on iojs 3 and use Travis containers. 2015-09-07 14:15:55 +02:00
Julien Fontanet
e61873f335 username is not define in passport-saml. 2015-08-28 18:23:46 +02:00
Julien Fontanet
6c3719b9b8 0.1.0 2015-08-28 17:47:53 +02:00
Julien Fontanet
8c9ea7885a First working version. 2015-08-28 16:16:32 +02:00
Julien Fontanet
20cbf0c710 Initial commit. 2015-07-15 15:51:43 +02:00
Julien Fontanet
8de2066634 0.4.0 2015-06-26 12:01:51 +02:00
Julien Fontanet
dfc312c092 Various updates. 2015-06-26 11:57:41 +02:00
Julien Fontanet
ce15dbf31b New Collection#unset(). 2015-06-26 11:20:50 +02:00
Julien Fontanet
ea1afb260a Update README.md 2015-06-22 14:00:35 +02:00
Julien Fontanet
e9e0fdae37 Run linter with npm test. 2015-06-16 17:53:47 +02:00
Julien Fontanet
124f7f43ab Minor changes. 2015-06-16 17:53:47 +02:00
Julien Fontanet
27df44bf44 Use more ES6/7 features. 2015-06-16 16:09:40 +02:00
Julien Fontanet
b934a7de6a npm run lint 2015-06-16 16:00:29 +02:00
Julien Fontanet
d521c75085 Minor changes. 2015-06-16 16:00:17 +02:00
Julien Fontanet
5e18b6b878 Improve Babel configuration. 2015-06-16 15:57:12 +02:00
Julien Fontanet
3183ca02b3 Improve tests execution. 2015-06-16 15:56:36 +02:00
Julien Fontanet
60a278490f Fix coding style. 2015-06-16 12:49:22 +02:00
Julien Fontanet
b78e74cdf6 Use a valid SPDX license. 2015-06-16 12:46:58 +02:00
Julien Fontanet
f61a16074b Update deps. 2015-06-16 12:46:11 +02:00
Julien Fontanet
82766d1645 Update README.md 2015-06-16 12:39:54 +02:00
Julien Fontanet
725f471a6a 0.7.2 2015-05-28 15:36:49 +02:00
Julien Fontanet
0b01a79d9d Implements object.messages. 2015-05-28 15:36:33 +02:00
Julien Fontanet
2653ff6536 Comment currently unused declarations. 2015-05-28 15:36:04 +02:00
Julien Fontanet
0f30cc8e59 Work around linter issue (eslint/espree#136). 2015-05-28 15:35:27 +02:00
Julien Fontanet
e3879cd4d1 Minor style fix. 2015-05-28 15:34:41 +02:00
Julien Fontanet
7a4cdf8688 Fix connection errors handling in Api#connect(). 2015-05-28 15:33:49 +02:00
Julien Fontanet
1839bf938a 0.7.1 2015-05-27 17:02:25 +02:00
Julien Fontanet
44d4096a79 Remove a console.log(). 2015-05-27 14:07:14 +02:00
Julien Fontanet
41280c9d38 Various updates. 2015-05-27 12:18:41 +02:00
Julien Fontanet
7c54adec9d 0.3.2 2015-05-24 14:41:22 +02:00
Julien Fontanet
68abd91fc2 Api#close() returns a promise. 2015-05-24 14:41:01 +02:00
Julien Fontanet
4d2e42d244 One more Angular work around. 2015-05-24 14:39:48 +02:00
Julien Fontanet
5a87a6c502 This lib is not ES6! 2015-05-22 16:41:33 +02:00
Julien Fontanet
d8ca15ceb3 Fix objects removal. 2015-05-22 15:12:22 +02:00
Julien Fontanet
a17f718517 Update xo-collection to 0.3.1. 2015-05-22 14:53:52 +02:00
Julien Fontanet
3589dda8ee Remove ugly hack. 2015-05-22 14:52:27 +02:00
Julien Fontanet
21f8e4d55b Remove unused UUID index. 2015-05-22 14:52:19 +02:00
Julien Fontanet
811e0123c9 0.3.1 2015-05-22 14:46:46 +02:00
Julien Fontanet
47c4516060 Index & UniqueIndex: Correctly updates even if the hash has not changed. 2015-05-22 14:46:20 +02:00
Julien Fontanet
13913334b6 0.7.0 2015-05-19 16:48:19 +02:00
Julien Fontanet
7f60725c88 Make it work with current version of xo-web. 2015-05-19 16:48:03 +02:00
Julien Fontanet
d55fb36182 0.3.0 2015-05-19 16:39:54 +02:00
Julien Fontanet
41205aef20 Work around xo-web bug due to Angular. 2015-05-19 16:38:02 +02:00
Julien Fontanet
aeadbc1d58 Minor standardization. 2015-05-19 16:35:23 +02:00
Julien Fontanet
bd12ade426 UniqueIndex only expose the values of contained items. 2015-05-19 16:34:48 +02:00
Julien Fontanet
f9c26089cd Document exceptions. 2015-05-19 15:25:44 +02:00
Julien Fontanet
7ddb57078c Finish event. 2015-05-19 15:25:30 +02:00
Julien Fontanet
3e7f1275d8 Remove unnecessary array. 2015-05-19 14:43:16 +02:00
Julien Fontanet
e963938016 Do not attach collection to index if duplicate. 2015-05-19 14:42:11 +02:00
Julien Fontanet
312fcea5f1 Auto links. 2015-05-15 18:08:21 +02:00
Julien Fontanet
9d05653f5b Use xo-collection. 2015-05-14 17:19:28 +02:00
Julien Fontanet
644ebd0a4f 0.2.1 2015-05-14 17:18:12 +02:00
Julien Fontanet
1033bfcfe5 Add missing files. 2015-05-14 17:16:35 +02:00
Julien Fontanet
a688310b95 Remove thisArg param. 2015-05-04 17:34:02 +02:00
Julien Fontanet
e3ffc8784e Use shared module instead of custom isNotEmpty(). 2015-04-30 11:45:14 +02:00
Julien Fontanet
bb35fc3801 Fix README title. 2015-04-30 11:44:53 +02:00
Julien Fontanet
c804630576 0.2.0 2015-04-29 18:17:39 +02:00
Julien Fontanet
e5f3ca1623 Initial UniqueIndex. 2015-04-29 17:51:18 +02:00
Julien Fontanet
0880787d68 new Index() now accepts a computeHash() callback-ish. 2015-04-29 14:33:37 +02:00
Julien Fontanet
cd582e2e3a Rename Index#itemsByHash to Index#items. 2015-04-29 14:12:22 +02:00
Julien Fontanet
aebd9319f5 Comments in Collection#_onUpdate(). 2015-04-29 13:22:36 +02:00
Julien Fontanet
de6cbb0f45 Fix Index#sweep(). 2015-04-29 13:22:36 +02:00
Julien Fontanet
e14dcd0184 Put some utils in their own modules. 2015-04-29 13:22:30 +02:00
Julien Fontanet
17ef653903 0.1.1 2015-04-28 17:10:58 +02:00
Julien Fontanet
f5d5b5efc0 Make View and Index requireable. 2015-04-28 17:10:31 +02:00
Julien Fontanet
59dbee8f28 Minor doc about views. 2015-04-28 17:06:37 +02:00
Julien Fontanet
4db6971cc4 0.3.2 2015-04-28 14:06:08 +02:00
Julien Fontanet
71482bd06c Correctly build the package. 2015-04-28 14:05:47 +02:00
Julien Fontanet
c3acf8341b 0.3.1 2015-04-28 13:25:03 +02:00
Julien Fontanet
1bc48fbf96 0.1.0 2015-04-27 15:27:12 +02:00
Julien Fontanet
d45348c167 Minor fixes. 2015-04-27 15:09:07 +02:00
Julien Fontanet
22caa0ee66 Initial Index implementation (see #1). 2015-04-27 15:05:02 +02:00
Julien Fontanet
e6e8ccc855 Remove standard from npm test.
Currently broken du to due to eslint/espree#123
2015-04-27 15:04:52 +02:00
Julien Fontanet
d78522f5e1 Comment doc for Collection#getId(). 2015-04-27 12:14:30 +02:00
Julien Fontanet
3da2a618b9 Coding style. 2015-04-27 10:55:22 +02:00
Julien Fontanet
047fa5b2db Fix checkCertificate (thx @wrigby). 2015-04-26 11:16:15 +02:00
Julien Fontanet
c763794ef3 Install json-rpc from the npm repository. 2015-04-24 11:21:15 +02:00
Julien Fontanet
99694161e1 The REPL waits for promise completion. 2015-04-21 17:17:00 +02:00
Julien Fontanet
00f944f3f4 Remove unused import. 2015-04-19 00:42:02 +02:00
Julien Fontanet
1269411771 Add iteration to the README. 2015-04-18 22:33:32 +02:00
Julien Fontanet
d4d8ea6cf2 Collection #getId() renamed to getKey(). 2015-04-18 22:20:57 +02:00
Julien Fontanet
160522c520 Collection iteration more similar to Map. 2015-04-18 22:04:37 +02:00
Julien Fontanet
7024b5ec1b Reorganization of Collection code. 2015-04-18 21:58:17 +02:00
Julien Fontanet
5b020035d6 Collection is iterable. 2015-04-18 21:34:36 +02:00
Julien Fontanet
fcea7fd4bf mkdir -p is more portable. 2015-04-18 19:37:04 +02:00
Julien Fontanet
c1db993b92 Fix missing quotes. 2015-04-17 16:25:18 +02:00
Julien Fontanet
c19916ff1c Add CLI. 2015-04-17 14:05:49 +02:00
Julien Fontanet
6fa2e79c1c Use standard code style. 2015-04-17 13:35:33 +02:00
Julien Fontanet
7941a24d51 Force npm test to be successful before committing. 2015-04-15 15:16:28 +02:00
Julien Fontanet
e004ba63f8 Remove unused JSHint conf. 2015-04-15 10:46:09 +02:00
Julien Fontanet
1f30a19566 Improved view example. 2015-04-14 12:18:29 +02:00
Julien Fontanet
51f4578a41 View handles existing items. 2015-04-14 12:18:24 +02:00
Julien Fontanet
bd3954a5f1 Rename index.* to collection.*. 2015-04-14 10:47:47 +02:00
Julien Fontanet
94967add7c Merge branch 'views' 2015-04-14 10:25:49 +02:00
Julien Fontanet
783ab0b611 Initial view implementation (fix #3). 2015-04-13 20:23:43 +02:00
Julien Fontanet
653a9526f5 0.0.1 2015-04-13 14:54:19 +02:00
Julien Fontanet
34ac4b25af Test and fix #clear(). 2015-04-13 14:53:25 +02:00
Julien Fontanet
e072ff2d77 Update README.md 2015-04-10 11:51:54 +02:00
Julien Fontanet
41dfbc2709 Publish to npm as xo-collection. 2015-04-10 11:41:02 +02:00
Julien Fontanet
964e461597 Better Collection#_resolveItem(). 2015-04-10 10:51:01 +02:00
Julien Fontanet
ef2eec4c4a Minor build updates. 2015-04-10 10:50:23 +02:00
Julien Fontanet
bf1d76d853 Source map support for tests. 2015-04-10 10:49:25 +02:00
Julien Fontanet
0682cbd554 Test for Collection#touch(). 2015-04-10 10:49:08 +02:00
Julien Fontanet
f5191cdd42 Better error classes. 2015-04-10 10:48:47 +02:00
Julien Fontanet
b1c73208c5 Do not return this. 2015-04-08 16:33:28 +02:00
Julien Fontanet
ab221a465b Use lodash.foreach instead of native implementation. 2015-04-08 16:32:46 +02:00
Julien Fontanet
4ecfa0477d Optimize Collection#_touch(). 2015-04-08 14:30:16 +02:00
Julien Fontanet
bab2de36ad Tests for events deduplication. 2015-04-08 14:24:07 +02:00
Julien Fontanet
f479e914bb #getId() should behave even if the item is null or undefined. 2015-04-08 12:00:52 +02:00
Julien Fontanet
45441653f6 #bufferChanges() → #bufferEvents(). 2015-04-08 11:51:18 +02:00
Julien Fontanet
0303558ae1 Prefer the term of item over entry. 2015-04-08 11:49:25 +02:00
Julien Fontanet
b70e0e3e2b Tests for #add(), #update(), #set() and #remove() with an object. 2015-04-08 11:40:07 +02:00
Julien Fontanet
5e0c4d7b7a Avoid Function#apply() for performance. 2015-04-08 10:50:04 +02:00
Julien Fontanet
6c83308451 Optimize Collection#get(). 2015-04-08 10:48:27 +02:00
Julien Fontanet
eeb898179e Minor simplification of buffer flushing. 2015-04-08 10:42:00 +02:00
Julien Fontanet
0ea662d8fe Replace let by const where possible. 2015-04-08 10:05:41 +02:00
Julien Fontanet
ea3219fa10 Typo. 2015-04-08 10:04:58 +02:00
Julien Fontanet
fb9203d396 Fix doc: unset() → remove(). 2015-04-07 18:49:20 +02:00
Julien Fontanet
23e7542871 Document Collection#all. 2015-04-07 18:15:38 +02:00
Julien Fontanet
a40832dffd Typo. 2015-04-07 18:06:17 +02:00
Julien Fontanet
5ba7493613 Better repo architecture. 2015-04-07 18:01:33 +02:00
Fabrice Marsaud
dd1d16f91c Fixes and enhancements 2015-04-02 16:49:40 +02:00
Fabrice Marsaud
cbd93f450e Implementation seems ok. Deeper tests to come 2015-04-02 15:32:08 +02:00
Fabrice Marsaud
35c64be3d7 All tests for "simple" buffer 2015-04-02 10:14:32 +02:00
Fabrice Marsaud
96ea70c027 Buffer implemented and tested 2015-04-01 18:06:23 +02:00
Fabrice Marsaud
a3d7e541d3 Events implemented and tested, without buffering 2015-04-01 15:06:39 +02:00
Fabrice Marsaud
92d7d61926 Various fixes and enhancements 2015-03-31 17:08:41 +02:00
Fabrice Marsaud
265d77d776 First main methods. No events, no buffer yet 2015-03-31 16:09:05 +02:00
Fabrice Marsaud
48d9fde3b6 Left over solution 2015-03-30 09:31:42 +02:00
Fabrice Marsaud
a9ea1a02ed Use let and const 2015-03-27 16:43:43 +01:00
Fabrice Marsaud
19cd5c8881 Minor renaming 2015-03-27 15:55:37 +01:00
Fabrice Marsaud
cdb9c661bd A first version without unit tests, but a gross test script 2015-03-27 15:55:37 +01:00
Fabrice Marsaud
c3a01c240b First base 2015-03-27 15:55:37 +01:00
Fabrice Marsaud
e2f748e63d Initial commit 2015-03-27 15:51:06 +01:00
Julien Fontanet
c539dd5570 0.6.3 2015-03-04 18:23:21 +01:00
Julien Fontanet
de76afea99 New implementation of Xo (fixes many issues). 2015-03-04 18:22:11 +01:00
Julien Fontanet
1d3616ae71 Minor changes. 2015-03-04 17:42:22 +01:00
Julien Fontanet
d76cb440f9 Move BackOff to its own module. 2015-03-04 17:42:18 +01:00
Julien Fontanet
7c89d658f7 Fix setScheduler(). 2015-03-04 17:37:12 +01:00
Julien Fontanet
292c929117 Split code in to multiple files. 2015-03-04 17:31:14 +01:00
Julien Fontanet
daf42b63c8 Better back off implementation. 2015-03-04 16:18:57 +01:00
Julien Fontanet
07da03618f Use specific version of json-rpc. 2015-03-04 15:51:35 +01:00
Julien Fontanet
dda51f2801 Xo::call() should never through synchronously. 2015-03-04 15:01:30 +01:00
Julien Fontanet
25472bcfa6 Correctly catch some errors. 2015-03-04 15:01:03 +01:00
Julien Fontanet
6ff17d16f0 0.6.2 2015-02-23 14:48:39 +01:00
Julien Fontanet
06b7116692 Never break object refs for Collection:all & Collection:indexes[*]. 2015-02-23 14:48:32 +01:00
Julien Fontanet
3c3ea0f3e1 Fix Collection::data. 2015-02-23 14:47:11 +01:00
Julien Fontanet
db4d6511d6 0.6.1 2015-02-23 12:25:53 +01:00
Julien Fontanet
6e42a67268 Update README. 2015-02-23 12:25:30 +01:00
Julien Fontanet
fd066e5eef Handle credentials directly in constructor. 2015-02-23 12:25:17 +01:00
Julien Fontanet
3dd0c44410 Handle string parameter in Xo constructor. 2015-02-23 12:24:47 +01:00
Julien Fontanet
12b42854e4 Delete useless example. 2015-02-23 12:23:42 +01:00
Julien Fontanet
2fcb6d0c7c 0.6.0 2015-02-23 12:11:53 +01:00
Julien Fontanet
68e863723a Explicitely handle sign in/out. 2015-02-23 12:10:45 +01:00
Julien Fontanet
d0b37d0f9a Update example. 2015-02-20 17:15:00 +01:00
Julien Fontanet
a0a1353445 Update collection API. 2015-02-20 17:14:44 +01:00
Julien Fontanet
6725cc6f61 0.5.2 2015-02-12 12:23:56 +01:00
Julien Fontanet
7e9639052b getCurrentUrl() must not ignore protocol and search parts. 2015-02-12 12:23:35 +01:00
Julien Fontanet
21bd5ba376 fixUrl() properly handles search and hash parts. 2015-02-12 12:22:58 +01:00
Julien Fontanet
28d5fb1822 0.5.1 2015-02-11 15:17:51 +01:00
Julien Fontanet
cd3c031df1 Fix back off reset. 2015-02-11 15:17:42 +01:00
Julien Fontanet
a6db0f6fd9 Fix reconnection. 2015-02-11 15:11:23 +01:00
Julien Fontanet
c80f6e8285 Better status during reconnection. 2015-02-11 15:11:03 +01:00
Julien Fontanet
c6d779853a Remove unused require. 2015-02-11 15:10:52 +01:00
Julien Fontanet
1b720a504c 0.5.0 2015-02-11 14:40:19 +01:00
Julien Fontanet
72f8854a7a Remove factory. 2015-02-11 14:39:52 +01:00
Julien Fontanet
097d195f00 Disable session.*() from high level API. 2015-02-11 14:16:38 +01:00
Julien Fontanet
807da8f696 Make Xo.connect() private. 2015-02-11 14:13:01 +01:00
Julien Fontanet
a8ca6b6fcb Do not use assign for methods. 2015-02-11 14:11:38 +01:00
Julien Fontanet
85e2e14c81 README update. 2015-02-11 14:11:10 +01:00
Julien Fontanet
5ae45ddd55 Do not clear objects on disconnect. 2015-02-10 20:13:11 +01:00
Julien Fontanet
4e0a3da01e 0.4.1 2015-02-10 18:57:03 +01:00
Julien Fontanet
541a99bbc5 Fix item removal. 2015-02-10 18:56:32 +01:00
Julien Fontanet
f62008aba4 0.4.0 2015-02-10 18:22:11 +01:00
Julien Fontanet
74f7415f84 Minor changes. 2015-02-10 18:21:53 +01:00
Julien Fontanet
1e1e079b65 Use current location if URL not provided (for browsers). 2015-02-10 18:21:07 +01:00
Julien Fontanet
0b4f808b2d Only emit disconnected event if previously connected. 2015-02-10 18:13:01 +01:00
Julien Fontanet
ae4af99c59 Xo cannot be closed. 2015-02-10 17:53:56 +01:00
Julien Fontanet
0b17556fa4 Auto reconnect. 2015-02-10 17:53:43 +01:00
Julien Fontanet
e6ebc347e5 setScheduler() to ease integration. 2015-02-10 17:53:33 +01:00
Julien Fontanet
f2323a9d19 Various updates. 2015-02-10 17:12:10 +01:00
Julien Fontanet
ce53fe5e31 Fix module exports. 2015-02-10 14:47:16 +01:00
Julien Fontanet
f1d359b3e7 Smart objects collection. 2015-02-10 14:42:56 +01:00
Julien Fontanet
da99f3bc2a High level interface. 2015-02-10 11:40:11 +01:00
Julien Fontanet
fec8dd74af Use json-rpc. 2015-02-05 17:05:49 +01:00
Julien Fontanet
f598e0d0d5 Not available via Bower. 2015-02-05 14:13:06 +01:00
Julien Fontanet
656d2494b0 Travis CI. 2015-02-05 13:51:50 +01:00
Julien Fontanet
6d07d58f37 0.3.0 2015-02-05 13:45:52 +01:00
Julien Fontanet
dd5270f620 Minimalist browser example. 2015-02-05 13:33:21 +01:00
Julien Fontanet
216f895953 Update deps. 2015-02-05 13:33:21 +01:00
Julien Fontanet
6acb87b7ea Update Bluebird. 2015-02-05 13:33:21 +01:00
Julien Fontanet
acdccd697c Fix browser compatibility. 2015-02-05 13:33:21 +01:00
Julien Fontanet
cecc4b1f6d Fix secure URL check. 2015-02-05 13:33:21 +01:00
Julien Fontanet
f93f115e13 Better and tested fixUrl(). 2015-02-05 13:33:16 +01:00
Julien Fontanet
debcf086b5 Code style. 2015-02-05 11:57:05 +01:00
Julien Fontanet
c6db974962 Update jshintrc. 2015-02-05 11:55:00 +01:00
Julien Fontanet
e9b0b0c42e ws supports browser natively. 2015-02-05 11:53:27 +01:00
Julien Fontanet
ebee1a02fd Promise is already defined in ES6. 2015-02-05 11:46:56 +01:00
Julien Fontanet
de05139dfc Use xdg-basedir instead of xdg. 2014-10-08 09:50:34 +02:00
Julien Fontanet
e88d0579b0 Use nice-pipe instead of own code. 2014-09-29 14:53:11 +02:00
Julien Fontanet
5564e4daa2 Minor update. 2014-09-25 15:23:53 +02:00
Julien Fontanet
4742cd4a03 0.4.0 2014-09-25 14:59:54 +02:00
Julien Fontanet
99985f4fab Better progress stats. 2014-09-24 18:18:17 +02:00
Julien Fontanet
42bb3b5aca Always use the POST method. 2014-09-24 17:22:44 +02:00
Olivier Lambert
70bedaf8dd fix by using PUT instead of POST 2014-09-24 17:09:59 +02:00
Julien Fontanet
38683a7fea Syntax update. 2014-09-23 19:16:11 +02:00
Julien Fontanet
ae1a68500c Remove erroneous require. 2014-09-23 17:57:04 +02:00
Julien Fontanet
578f842eed vm.export. 2014-09-23 17:31:29 +02:00
Julien Fontanet
a7dea95f90 Named functions for better stack traces. 2014-09-22 15:35:12 +02:00
Julien Fontanet
1080562d96 Sort requires. 2014-09-22 15:29:45 +02:00
Julien Fontanet
b8cf5a2347 Use per function lodash modules. 2014-09-22 15:29:28 +02:00
Julien Fontanet
e9706d605a Promise is already defined in ES6. 2014-09-22 15:12:14 +02:00
Julien Fontanet
4b338c56b5 Remove dead code. 2014-09-22 15:11:04 +02:00
Julien Fontanet
2974a96e96 0.3.2 2014-07-28 15:34:19 +02:00
Julien Fontanet
ab7fd3d019 README update. 2014-07-28 15:32:55 +02:00
Julien Fontanet
54c5659496 0.3.1 2014-07-28 15:09:12 +02:00
Julien Fontanet
8c0811885f Handle -h as an alias for --help. 2014-07-28 15:09:00 +02:00
Julien Fontanet
291570dfd7 Add colors to the help message. 2014-07-28 15:06:15 +02:00
Julien Fontanet
f6b9b4cc19 0.3.0 2014-07-28 14:47:08 +02:00
Julien Fontanet
a06403ab7c Update to latest xo-lib. 2014-07-28 13:37:07 +02:00
Julien Fontanet
e64a95d1d7 Handle boolean params. 2014-07-28 13:35:30 +02:00
Julien Fontanet
6939e49643 Enable async long traces. 2014-07-28 13:35:19 +02:00
Julien Fontanet
bb18586484 Update JSHint config. 2014-07-28 13:35:05 +02:00
Julien Fontanet
09b39a4bf9 0.2.0 2014-07-28 13:33:45 +02:00
Julien Fontanet
1d7d639654 Set rejectUnauthorized only for secure connection. 2014-07-28 13:33:29 +02:00
Julien Fontanet
42ec509574 Fix URL fixing. 2014-07-28 13:30:57 +02:00
Julien Fontanet
dcef864c1c Fix URL if necessary. 2014-07-28 13:21:40 +02:00
Julien Fontanet
e8cb4f90f4 Update JSHint conf. 2014-07-28 13:21:40 +02:00
Julien Fontanet
4e0b2e4f77 Update deps. 2014-07-28 12:42:55 +02:00
Julien Fontanet
a2bcadeb7c Remove now useless code & Update deps. 2014-07-26 23:51:23 +02:00
Julien Fontanet
84a4242e27 0.1.1 2014-07-26 23:48:53 +02:00
Julien Fontanet
6373d667d9 Work around XO-Server incomplete TLS 2014-07-26 23:42:04 +02:00
Julien Fontanet
7840d601e1 Use xo-lib. 2014-07-26 10:19:32 +02:00
Julien Fontanet
1893061c0d 0.1.0 2014-07-26 10:15:38 +02:00
Julien Fontanet
0289cc3ee5 Initial commit. 2014-07-26 10:15:29 +02:00
Julien Fontanet
a35c9d2d9e 0.2.3 2014-06-09 15:36:44 +02:00
Julien Fontanet
7f739a1371 Update deps. 2014-06-09 15:36:17 +02:00
Olivier Lambert
62df78f329 fix error in doc 2014-06-03 15:11:39 +02:00
Julien Fontanet
be4511d95b 0.2.2 2014-05-27 19:17:53 +01:00
Julien Fontanet
b17e6058d1 Minor fixes. 2014-05-27 19:17:47 +01:00
Julien Fontanet
99c28a184f 0.2.1 2014-05-27 18:55:33 +01:00
Julien Fontanet
7ed9adaf49 Allows connection to incorrect SSL certificates. 2014-05-27 18:55:25 +01:00
Julien Fontanet
c3ef051657 0.2.0 2014-05-26 14:26:55 +02:00
Julien Fontanet
ba149efa4a Merge branch 'dev' 2014-05-26 14:26:41 +02:00
Julien Fontanet
e30a7b3849 README update. 2014-05-26 14:22:08 +02:00
Julien Fontanet
97ad0483ec Minor change. 2014-05-26 14:01:55 +02:00
Julien Fontanet
3981c772a2 --register and --unregister. 2014-05-26 13:57:18 +02:00
Julien Fontanet
e762002560 Remove unused deps. 2014-05-26 13:42:36 +02:00
Julien Fontanet
061d8dc94f Use API introspection. 2014-05-26 13:33:56 +02:00
Julien Fontanet
84e6228f90 Various updates. 2014-05-24 17:09:10 +02:00
Julien Fontanet
301cc22985 Various updates. 2014-04-14 16:38:29 +02:00
Julien Fontanet
29398b9869 Various updates. 2014-04-03 10:19:31 +02:00
Julien Fontanet
8b65a75235 Fixes register in README. 2014-03-28 15:19:53 +01:00
Julien Fontanet
313f7e8173 Minor fixes and new command: whoami. 2014-03-25 17:18:02 +01:00
Julien Fontanet
d9e615e696 First working version. 2014-03-25 15:34:07 +01:00
Julien Fontanet
b526067eeb Initial commit. 2014-03-24 17:44:06 +01:00
121 changed files with 51531 additions and 228 deletions

View File

@@ -11,7 +11,7 @@ root = true
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespaces = true
trim_trailing_whitespace = true
# CoffeeScript
#
@@ -28,12 +28,12 @@ indent_style = space
# Package.json
#
# This indentation style is the one used by npm.
[/package.json]
[package.json]
indent_size = 2
indent_style = space
# Jade
[*.jade]
# Pug (Jade)
[*.{jade,pug}]
indent_size = 2
indent_style = space
@@ -41,7 +41,7 @@ indent_style = space
#
# Two spaces seems to be the standard most common style, at least in
# Node.js (http://nodeguide.com/style.html#tabs-vs-spaces).
[*.js]
[*.{js,jsx,ts,tsx}]
indent_size = 2
indent_style = space

13
.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
/node_modules/
/lerna-debug.log
/lerna-debug.log.*
/packages/*/dist/
/packages/*/node_modules/
npm-debug.log
npm-debug.log.*
pnpm-debug.log
pnpm-debug.log.*
yarn-error.log
yarn-error.log.*

9
.travis.yml Normal file
View File

@@ -0,0 +1,9 @@
language: node_js
node_js:
- stable
- 6
- 4
# Use containers.
# http://docs.travis-ci.com/user/workers/container-based-infrastructure/
sudo: false

5
README.md Normal file
View File

@@ -0,0 +1,5 @@
Almost all dev for Xen Orchestra is happening in this repository.
Because transition is still underway, [xo-web](https://github.com/vatesfr/xo-web) and [xo-server](https://github.com/vatesfr/xo-server) are still developped in their own repositories.
For now, all issues are still to be reported in [xo-web's tracker](https://github.com/vatesfr/xo-web/issues).

7
lerna.json Normal file
View File

@@ -0,0 +1,7 @@
{
"lerna": "2.0.0-beta.34",
"packages": [
"packages/*"
],
"version": "0.0.0"
}

12
package.json Normal file
View File

@@ -0,0 +1,12 @@
{
"devDependencies": {
"husky": "^0.13.1",
"lerna": "^2.0.0-beta.34",
"promise-toolbox": "^0.8.0"
},
"scripts": {
"commit-msg": "yarn test",
"install": "lerna exec --concurrency 1 -- yarn",
"test": "lerna exec -- yarn test"
}
}

View File

@@ -0,0 +1,24 @@
/benchmark/
/benchmarks/
*.bench.js
*.bench.js.map
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/fixture/
/fixtures/
*.fixture.js
*.fixture.js.map
*.fixtures.js
*.fixtures.js.map
/test/
/tests/
*.spec.js
*.spec.js.map
__snapshots__/

View File

@@ -0,0 +1,51 @@
# vhd-cli [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
> ${pkg.description}
## Install
Installation of the [npm package](https://npmjs.org/package/vhd-cli):
```
> npm install --global vhd-cli
```
## Usage
```
> vhd-cli <VHD file>
```
## Development
```
# Install dependencies
> npm install
# Run the tests
> npm test
# Continuously compile
> npm run dev
# Continuously run the tests
> npm run dev-test
# Build for production (automatically called by npm install)
> npm run build
```
## Contributions
Contributions are *very* welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xo-web/issues)
you've encountered;
- fork and create a pull request.
## License
ISC © [Vates SAS](https://vates.fr)

View File

@@ -0,0 +1,87 @@
{
"name": "vhd-cli",
"version": "0.0.0",
"license": "ISC",
"description": "",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/vhd-cli",
"bugs": "https://github.com/vatesfr/xo-web/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@isonoe.net"
},
"preferGlobal": false,
"main": "dist/",
"bin": {
"vhd-cli": "dist/index.js"
},
"files": [
"dist/"
],
"engines": {
"node": ">=4"
},
"dependencies": {
"@nraynaud/struct-fu": "^1.0.1",
"@nraynaud/xo-fs": "^0.0.5",
"babel-runtime": "^6.22.0",
"exec-promise": "^0.6.1"
},
"devDependencies": {
"babel-cli": "^6.22.2",
"babel-eslint": "^7.1.1",
"babel-plugin-lodash": "^3.2.11",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-env": "^1.1.8",
"babel-preset-stage-3": "^6.22.0",
"cross-env": "^3.1.4",
"dependency-check": "^2.8.0",
"jest": "^18.1.0",
"rimraf": "^2.5.4",
"standard": "^8.6.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"commitmsg": "npm test",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"dev-test": "jest --bail --watch",
"posttest": "standard && dependency-check ./package.json",
"prebuild": "rimraf dist/",
"predev": "npm run prebuild",
"prepublish": "npm run build",
"test": "jest"
},
"babel": {
"plugins": [
"lodash",
"transform-runtime"
],
"presets": [
[
"env",
{
"targets": {
"node": 4
}
}
],
"stage-3"
]
},
"jest": {
"testPathDirs": [
"<rootDir>/src"
],
"testRegex": "\\.spec\\.js$"
},
"standard": {
"ignore": [
"dist"
],
"parser": "babel-eslint"
}
}

19
packages/vhd-cli/src/index.js Executable file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env node
import execPromise from 'exec-promise'
import { RemoteHandlerLocal } from '@nraynaud/xo-fs'
import { resolve } from 'path'
import Vhd from './vhd'
execPromise(async args => {
const vhd = new Vhd(
new RemoteHandlerLocal({ url: 'file:///' }),
resolve(args[0])
)
await vhd.readHeaderAndFooter()
console.log(vhd._header)
console.log(vhd._footer)
})

441
packages/vhd-cli/src/vhd.js Normal file
View File

@@ -0,0 +1,441 @@
import assert from 'assert'
import fu from '@nraynaud/struct-fu'
import { dirname } from 'path'
// ===================================================================
//
// Spec:
// https://www.microsoft.com/en-us/download/details.aspx?id=23850
//
// C implementation:
// https://github.com/rubiojr/vhd-util-convert
//
// ===================================================================
/* eslint-disable no-unused-vars */
const HARD_DISK_TYPE_DIFFERENCING = 4
const HARD_DISK_TYPE_DYNAMIC = 3
const HARD_DISK_TYPE_FIXED = 2
const PLATFORM_CODE_NONE = 0
export const SECTOR_SIZE = 512
/* eslint-enable no-unused vars */
// ===================================================================
const fuFooter = fu.struct([
fu.char('cookie', 8), // 0
fu.uint32('features'), // 8
fu.uint32('fileFormatVersion'), // 12
fu.struct('dataOffset', [
fu.uint32('high'), // 16
fu.uint32('low') // 20
]),
fu.uint32('timestamp'), // 24
fu.char('creatorApplication', 4), // 28
fu.uint32('creatorVersion'), // 32
fu.uint32('creatorHostOs'), // 36
fu.struct('originalSize', [ // At the creation, current size of the hard disk.
fu.uint32('high'), // 40
fu.uint32('low') // 44
]),
fu.struct('currentSize', [ // Current size of the virtual disk. At the creation: currentSize = originalSize.
fu.uint32('high'), // 48
fu.uint32('low') // 52
]),
fu.struct('diskGeometry', [
fu.uint16('cylinders'), // 56
fu.uint8('heads'), // 58
fu.uint8('sectorsPerTrackCylinder') // 59
]),
fu.uint32('diskType'), // 60 Disk type, must be equal to HARD_DISK_TYPE_DYNAMIC/HARD_DISK_TYPE_DIFFERENCING.
fu.uint32('checksum'), // 64
fu.uint8('uuid', 16), // 68
fu.char('saved'), // 84
fu.char('hidden'), // 85
fu.byte('reserved', 426) // 86
])
const FOOTER_SIZE = fuFooter.size
const fuHeader = fu.struct([
fu.char('cookie', 8),
fu.struct('dataOffset', [
fu.uint32('high'),
fu.uint32('low')
]),
fu.struct('tableOffset', [ // Absolute byte offset of the Block Allocation Table.
fu.uint32('high'),
fu.uint32('low')
]),
fu.uint32('headerVersion'),
fu.uint32('maxTableEntries'), // Max entries in the Block Allocation Table.
fu.uint32('blockSize'), // Block size (without bitmap) in bytes.
fu.uint32('checksum'),
fu.uint8('parentUuid', 16),
fu.uint32('parentTimestamp'),
fu.byte('reserved1', 4),
fu.char16be('parentUnicodeName', 512),
fu.struct('parentLocatorEntry', [
fu.uint32('platformCode'),
fu.uint32('platformDataSpace'),
fu.uint32('platformDataLength'),
fu.uint32('reserved'),
fu.struct('platformDataOffset', [ // Absolute byte offset of the locator data.
fu.uint32('high'),
fu.uint32('low')
])
], 8),
fu.byte('reserved2', 256)
])
const HEADER_SIZE = fuHeader.size
// ===================================================================
// Helpers
// ===================================================================
const SIZE_OF_32_BITS = Math.pow(2, 32)
const uint32ToUint64 = fu => fu.high * SIZE_OF_32_BITS + fu.low
// Returns a 32 bits integer corresponding to a Vhd version.
const getVhdVersion = (major, minor) => (major << 16) | (minor & 0x0000FFFF)
// bytes[] bit manipulation
const testBit = (map, bit) => map[bit >> 3] & 1 << (bit & 7)
const setBit = (map, bit) => {
map[bit >> 3] |= 1 << (bit & 7)
}
const unsetBit = (map, bit) => {
map[bit >> 3] &= ~(1 << (bit & 7))
}
const addOffsets = (...offsets) => offsets.reduce(
(a, b) => b == null
? a
: typeof b === 'object'
? { bytes: a.bytes + b.bytes, bits: a.bits + b.bits }
: { bytes: a.bytes + b, bits: a.bits },
{ bytes: 0, bits: 0 }
)
const pack = (field, value, buf, offset) => {
field.pack(
value,
buf,
addOffsets(field.offset, offset)
)
}
const unpack = (field, buf, offset) =>
field.unpack(
buf,
addOffsets(field.offset, offset)
)
// ===================================================================
const streamToNewBuffer = stream => new Promise((resolve, reject) => {
const chunks = []
let length = 0
const onData = chunk => {
chunks.push(chunk)
length += chunk.length
}
stream.on('data', onData)
const clean = () => {
stream.removeListener('data', onData)
stream.removeListener('end', onEnd)
stream.removeListener('error', onError)
}
const onEnd = () => {
resolve(Buffer.concat(chunks, length))
clean()
}
stream.on('end', onEnd)
const onError = error => {
reject(error)
clean()
}
stream.on('error', onError)
})
const streamToExistingBuffer = (
stream,
buffer,
offset = 0,
end = buffer.length
) => new Promise((resolve, reject) => {
assert(offset >= 0)
assert(end > offset)
assert(end <= buffer.length)
let i = offset
const onData = chunk => {
const prev = i
i += chunk.length
if (i > end) {
return onError(new Error('too much data'))
}
chunk.copy(buffer, prev)
}
stream.on('data', onData)
const clean = () => {
stream.removeListener('data', onData)
stream.removeListener('end', onEnd)
stream.removeListener('error', onError)
}
const onEnd = () => {
resolve(i - offset)
clean()
}
stream.on('end', onEnd)
const onError = error => {
reject(error)
clean()
}
stream.on('error', onError)
})
// ===================================================================
// Returns the checksum of a raw struct.
const computeChecksum = (struct, buf, offset = 0) => {
let sum = 0
// Do not use the stored checksum to compute the new checksum.
const checksumField = struct.fields.checksum
const checksumOffset = offset + checksumField.offset
for (let i = offset, n = checksumOffset; i < n; ++i) {
sum += buf[i]
}
for (let i = checksumOffset + checksumField.size, n = offset + struct.size; i < n; ++i) {
sum += buf[i]
}
return ~sum >>> 0
}
const verifyChecksum = (struct, buf, offset) =>
unpack(struct.fields.checksum, buf, offset) === computeChecksum(struct, buf, offset)
const getParentLocatorSize = parentLocatorEntry => {
const { platformDataSpace } = parentLocatorEntry
if (platformDataSpace < SECTOR_SIZE) {
return platformDataSpace * SECTOR_SIZE
}
return (platformDataSpace % SECTOR_SIZE === 0)
? platformDataSpace
: 0
}
// ===================================================================
// Euclidean division, returns the quotient and the remainder of a / b.
const div = (a, b) => [ Math.floor(a / b), a % b ]
export default class Vhd {
constructor (handler, path) {
this._handler = handler
this._path = path
this._blockAllocationTable = null
this._blockBitmapSize = null
this._footer = null
this._header = null
this._parent = null
this._sectorsPerBlock = null
}
// Read `length` bytes starting from `begin`.
//
// - if `buffer`: it is filled starting from `offset`, and the
// number of written bytes is returned;
// - otherwise: a new buffer is allocated and returned.
_read (begin, length, buf, offset) {
assert(begin >= 0)
assert(length > 0)
return this._handler.createReadStream(this._path, {
end: begin + length - 1,
start: begin
}).then(buf
? stream => streamToExistingBuffer(stream, buf, offset, (offset || 0) + length)
: streamToNewBuffer
)
}
// - if `buffer`: it is filled with 0 starting from `offset`, and
// the number of written bytes is returned;
// - otherwise: a new buffer is allocated and returned.
_zeroes (length, buf, offset = 0) {
if (buf) {
assert(offset >= 0)
assert(length > 0)
const end = offset + length
assert(end <= buf.length)
buf.fill(0, offset, end)
return Promise.resolve(length)
}
return Promise.resolve(Buffer.alloc(length))
}
// Return the position of a block in the VHD or undefined if not found.
_getBlockAddress (block) {
assert(block >= 0)
assert(block < this._header.maxTableEntries)
const blockAddr = this._blockAllocationTable[block]
if (blockAddr !== 0xFFFFFFFF) {
return blockAddr * SECTOR_SIZE
}
}
// -----------------------------------------------------------------
async readHeaderAndFooter () {
const buf = await this._read(0, FOOTER_SIZE + HEADER_SIZE)
if (!verifyChecksum(fuFooter, buf)) {
throw new Error('footer checksum does not match')
}
if (!verifyChecksum(fuHeader, buf, FOOTER_SIZE)) {
throw new Error('header checksum does not match')
}
return this._initMetadata(
unpack(fuHeader, buf, FOOTER_SIZE),
unpack(fuFooter, buf)
)
}
async _initMetadata (header, footer) {
const sectorsPerBlock = header.blockSize / SECTOR_SIZE
assert(sectorsPerBlock % 1 === 0)
// 1 bit per sector, rounded up to full sectors
this._blockBitmapSize = Math.ceil(sectorsPerBlock / 8 / SECTOR_SIZE) * SECTOR_SIZE
assert(this._blockBitmapSize === SECTOR_SIZE)
this._footer = footer
this._header = header
this.size = uint32ToUint64(this._footer.currentSize)
if (footer.diskType === HARD_DISK_TYPE_DIFFERENCING) {
const parent = new Vhd(
this._handler,
`${dirname(this._path)}/${header.parentUnicodeName}`
)
await parent.readHeaderAndFooter()
await parent.readBlockAllocationTable()
this._parent = parent
}
}
// -----------------------------------------------------------------
async readBlockAllocationTable () {
const { maxTableEntries, tableOffset } = this._header
const fuTable = fu.uint32(maxTableEntries)
this._blockAllocationTable = unpack(
fuTable,
await this._read(uint32ToUint64(tableOffset), fuTable.size)
)
}
// -----------------------------------------------------------------
// read a single sector in a block
async _readBlockSector (block, sector, begin, length, buf, offset) {
assert(begin >= 0)
assert(length > 0)
assert(begin + length <= SECTOR_SIZE)
const blockAddr = this._getBlockAddress(block)
const blockBitmapSize = this._blockBitmapSize
const parent = this._parent
if (blockAddr && (
!parent ||
testBit(await this._read(blockAddr, blockBitmapSize), sector)
)) {
return this._read(
blockAddr + blockBitmapSize + sector * SECTOR_SIZE + begin,
length,
buf,
offset
)
}
return parent
? parent._readBlockSector(block, sector, begin, length, buf, offset)
: this._zeroes(length, buf, offset)
}
_readBlock (block, begin, length, buf, offset) {
assert(begin >= 0)
assert(length > 0)
const { blockSize } = this._header
assert(begin + length <= blockSize)
const blockAddr = this._getBlockAddress(block)
const parent = this._parent
if (!blockAddr) {
return parent
? parent._readBlock(block, begin, length, buf, offset)
: this._zeroes(length, buf, offset)
}
if (!parent) {
return this._read(blockAddr + this._blockBitmapSize + begin, length, buf, offset)
}
// FIXME: we should read as many sectors in a single pass as
// possible for maximum perf.
const [ sector, beginInSector ] = div(begin, SECTOR_SIZE)
return this._readBlockSector(
block,
sector,
beginInSector,
Math.min(length, SECTOR_SIZE - beginInSector),
buf,
offset
)
}
read (buf, begin, length = buf.length, offset) {
assert(Buffer.isBuffer(buf))
assert(begin >= 0)
const { size } = this
if (begin >= size) {
return Promise.resolve(0)
}
const { blockSize } = this._header
const [ block, beginInBlock ] = div(begin, blockSize)
return this._readBlock(
block,
beginInBlock,
Math.min(length, blockSize - beginInBlock, size - begin),
buf,
offset
)
}
}

3592
packages/vhd-cli/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
{
"comments": false,
"compact": true,
"presets": [
"stage-0",
"es2015"
]
}

View File

@@ -0,0 +1,10 @@
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/test/
/tests/
*.spec.js
*.spec.js.map

View File

@@ -0,0 +1,74 @@
# xo-acl-resolver [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
> [Xen-Orchestra](http://xen-orchestra.com/) internal: do ACLs resolution.
## Install
Installation of the [npm package](https://npmjs.org/package/xo-acl-resolver):
```
> npm install --save xo-acl-resolver
```
## Usage
```js
import check from 'xo-acl-resolver'
// This object contains a list of permissions returned from
// xo-server's acl.getCurrentPermissions.
const permissions = { /* ... */ }
// This function should returns synchronously an object from an id.
const getObject = id => { /* ... */ }
// For a single object:
if (check(permissions, getObject, objectId, permission)) {
console.log(`${permission} set for object ${objectId}`)
}
// For multiple objects/permissions:
if (check(permissions, getObject, [
[ object1Id, permission1 ],
[ object12d, permission2 ],
])) {
console.log('all permissions checked')
}
```
## Development
### Installing dependencies
```
> npm install
```
### Compilation
The sources files are watched and automatically recompiled on changes.
```
> npm run dev
```
### Tests
```
> npm run test-dev
```
## Contributions
Contributions are *very* welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xo-web/issues)
you've encountered;
- fork and create a pull request.
## License
ISC © [Vates SAS](https://vates.fr)

View File

@@ -0,0 +1,48 @@
{
"name": "xo-acl-resolver",
"version": "0.2.3",
"license": "ISC",
"description": "Xen-Orchestra internal: do ACLs resolution",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-acl-resolver",
"bugs": "https://github.com/vatesfr/xo-web/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@vates.fr"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"engines": {
"node": ">=0.12"
},
"devDependencies": {
"babel-cli": "^6.4.5",
"babel-eslint": "^7.0.0",
"babel-preset-es2015": "^6.3.13",
"babel-preset-stage-0": "^6.3.13",
"dependency-check": "^2.5.1",
"standard": "^8.0.0"
},
"scripts": {
"build": "babel --source-maps --out-dir=dist/ src/",
"depcheck": "dependency-check ./package.json",
"dev": "babel --watch --source-maps --out-dir=dist/ src/",
"lint": "standard",
"posttest": "yarn run lint && yarn run depcheck",
"prepublish": "yarn run build"
},
"standard": {
"ignore": [
"dist/**"
],
"parser": "babel-eslint"
}
}

View File

@@ -0,0 +1,131 @@
// These global variables are not a problem because the algorithm is
// synchronous.
let permissionsByObject
let getObject
// -------------------------------------------------------------------
const authorized = () => true // eslint-disable-line no-unused-vars
const forbiddden = () => false // eslint-disable-line no-unused-vars
const and = (...checkers) => (object, permission) => { // eslint-disable-line no-unused-vars
for (const checker of checkers) {
if (!checker(object, permission)) {
return false
}
}
return true
}
const or = (...checkers) => (object, permission) => { // eslint-disable-line no-unused-vars
for (const checker of checkers) {
if (checker(object, permission)) {
return true
}
}
return false
}
// -------------------------------------------------------------------
const checkMember = (memberName) => (object, permission) => {
const member = object[memberName]
return member !== object.id && checkAuthorization(member, permission)
}
const checkSelf = ({ id }, permission) => {
const permissionsForObject = permissionsByObject[id]
return (
permissionsForObject &&
permissionsForObject[permission]
)
}
// ===================================================================
const checkAuthorizationByTypes = {
host: or(checkSelf, checkMember('$pool')),
message: checkMember('$object'),
network: or(checkSelf, checkMember('$pool')),
SR: or(checkSelf, checkMember('$pool')),
task: checkMember('$host'),
VBD: checkMember('VDI'),
// Access to a VDI is granted if the user has access to the
// containing SR or to a linked VM.
VDI (vdi, permission) {
// Check authorization for the containing SR.
if (checkAuthorization(vdi.$SR, permission)) {
return true
}
// Check authorization for each of the connected VMs.
for (const vbdId of vdi.$VBDs) {
if (checkAuthorization(getObject(vbdId).VM, permission)) {
return true
}
}
return false
},
'VDI-snapshot': checkMember('$snapshot_of'),
VIF: or(checkMember('$network'), checkMember('$VM')),
VM: or(checkSelf, checkMember('$container')),
'VM-controller': checkMember('$container'),
'VM-snapshot': checkMember('$snapshot_of'),
'VM-template': or(checkSelf, checkMember('$pool'))
}
// Hoisting is important for this function.
function checkAuthorization (objectId, permission) {
const object = getObject(objectId)
if (!object) {
return false
}
const checker = checkAuthorizationByTypes[object.type] || checkSelf
return checker(object, permission)
}
// -------------------------------------------------------------------
export default (
permissionsByObject_,
getObject_,
permissions,
permission
) => {
// Assign global variables.
permissionsByObject = permissionsByObject_
getObject = getObject_
try {
if (permission) {
return checkAuthorization(permissions, permission)
} else {
for (const [objectId, permission] of permissions) {
if (!checkAuthorization(objectId, permission)) {
return false
}
}
}
return true
} finally {
// Free the global variables.
permissionsByObject = getObject = null
}
}

File diff suppressed because it is too large Load Diff

90
packages/xo-cli/.jshintrc Normal file
View File

@@ -0,0 +1,90 @@
{
// Julien Fontanet JSHint configuration
//
// Changes from defaults:
// - all enforcing options (except `++` & `--`) enabled
// - single quotes
// - indentation set to 2 instead of 4
// - almost all relaxing options disabled
// - allow expression statements (necessary for chai.expect())
// - allow global strict (most of my devs are in Node.js or Browserify)
// - environments are set to Browserify, mocha & Node.js
//
// See http://jshint.com/docs/ for more details
"maxerr" : 50, // {int} Maximum error before stopping
// Enforcing
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
"camelcase" : true, // true: Identifiers must be in camelCase
"curly" : true, // true: Require {} for every new block or scope
"eqeqeq" : true, // true: Require triple equals (===) for comparison
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
"immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
"indent" : 2, // {int} Number of spaces to use for indentation
"latedef" : true, // true: Require variables/functions to be defined before being used
"newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()`
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
"noempty" : true, // true: Prohibit use of empty blocks
"nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment)
"plusplus" : false, // true: Prohibit use of `++` & `--`
"quotmark" : "single", // Quotation mark consistency:
// false : do nothing (default)
// true : ensure whatever is used is consistent
// "single" : require single quotes
// "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : true, // true: Require all defined variables be used
"strict" : true, // true: Requires all functions run in ES5 Strict Mode
"maxparams" : 4, // {int} Max number of formal params allowed per function
"maxdepth" : 3, // {int} Max depth of nested blocks (within functions)
"maxstatements" : 20, // {int} Max number statements per function
"maxcomplexity" : 7, // {int} Max cyclomatic complexity per function
"maxlen" : 80, // {int} Max number of characters per line
// Relaxing
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
"boss" : false, // true: Tolerate assignments where comparisons would be expected
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // true: Tolerate use of `== null`
"es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
"esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// (ex: `for each`, multiple try/catch, function expression…)
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
"expr" : true, // true: Tolerate `ExpressionStatement` as Programs
"funcscope" : false, // true: Tolerate defining variables inside control statements
"globalstrict" : true, // true: Allow global "use strict" (also enables 'strict')
"iterator" : false, // true: Tolerate using the `__iterator__` property
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
"laxcomma" : false, // true: Tolerate comma-first style coding
"loopfunc" : false, // true: Tolerate functions being defined in loops
"multistr" : false, // true: Tolerate multi-line strings
"proto" : false, // true: Tolerate using the `__proto__` property
"scripturl" : false, // true: Tolerate script-targeted URLs
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
"validthis" : false, // true: Tolerate using this in a non-constructor function
// Environments
"browser" : false, // Web Browser (window, document, etc)
"browserify" : true, // Browserify (node.js code in the browser)
"couch" : false, // CouchDB
"devel" : true, // Development/debugging (alert, confirm, etc)
"dojo" : false, // Dojo Toolkit
"jquery" : false, // jQuery
"mocha" : true, // mocha
"mootools" : false, // MooTools
"node" : true, // Node.js
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
"prototypejs" : false, // Prototype and Scriptaculous
"rhino" : false, // Rhino
"worker" : false, // Web Workers
"wsh" : false, // Windows Scripting Host
"yui" : false, // Yahoo User Interface
// Custom Globals
"globals" : {} // additional predefined global variables
}

130
packages/xo-cli/README.md Normal file
View File

@@ -0,0 +1,130 @@
# XO-CLI
[![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](http://travis-ci.org/vatesfr/xen-orchestra)
[![Dependency Status](https://david-dm.org/vatesfr/xo-cli/status.svg?theme=shields.io)](https://david-dm.org/vatesfr/xo-cli)
[![devDependency Status](https://david-dm.org/vatesfr/xo-cli/dev-status.svg?theme=shields.io)](https://david-dm.org/vatesfr/xo-cli#info=devDependencies)
> Basic CLI for Xen-Orchestra
## Installation
#### [npm](https://npmjs.org/package/xo-cli)
```
npm install -g xo-cli
```
## Usage
```
> xo-cli --help
Usage:
xo-cli --register [<XO-Server URL>] [<username>] [<password>]
Registers the XO instance to use.
xo-cli --unregister
Remove stored credentials.
xo-cli --list-commands [--json] [<pattern>]...
Returns the list of available commands on the current XO instance.
The patterns can be used to filter on command names.
xo-cli --list-objects [--<property>]… [<property>=<value>]...
Returns a list of XO objects.
--<property>
Restricts displayed properties to those listed.
<property>=<value>
Restricted displayed objects to those matching the patterns.
xo-cli <command> [<name>=<value>]...
Executes a command on the current XO instance.
```
#### Register your XO instance
```
> xo-cli --register http://xo.my-company.net admin@admin.net admin
Successfully logged with admin@admin.net
```
Note: only a token will be saved in the configuration file.
#### List available objects
Prints all objects:
```
> xo-cli --list-objects
```
It is possible to filter on object properties, for instance to prints
all VM templates:
```
> xo-cli --list-objects type=VM-template
```
#### List available commands
```
> xo-cli --list-commands
```
Commands can be filtered using patterns:
```
> xo-cli --list-commands '{user,group}.*'
```
#### Execute a command
The same syntax is used for all commands: `xo-cli <command> <param
name>=<value>...`
E.g., adding a new server:
```
> xo-cli server.add host=my.server.net username=root password=secret-password
42
```
The return value is the identifier of this new server in XO.
Parameters (except `true` and `false` which are correctly parsed as
booleans) are assumed to be strings, for other types, you may use JSON
encoding by prefixing with `json:`:
```
> xo-cli foo.bar baz='json:[1, 2, 3]'
```
##### VM export
```
> xo-cli vm.export vm=a01667e0-8e29-49fc-a550-17be4226783c @=vm.xva
```
##### VM import
```
> xo-cli vm.import host=60a6939e-8b0a-4352-9954-5bde44bcdf7d @=vm.xva
```
## Contributing
Contributions are *very* welcome, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xo-web/issues)
you've encountered;
- fork and create a pull request.
## License
XO-CLI is released under the [AGPL
v3](http://www.gnu.org/licenses/agpl-3.0-standalone.html).

54
packages/xo-cli/config.js Normal file
View File

@@ -0,0 +1,54 @@
'use strict'
// ===================================================================
var promisify = require('bluebird').promisify
var readFile = promisify(require('fs').readFile)
var writeFile = promisify(require('fs').writeFile)
var assign = require('lodash/assign')
var l33t = require('l33teral')
var mkdirp = promisify(require('mkdirp'))
var xdgBasedir = require('xdg-basedir')
// ===================================================================
var configPath = xdgBasedir.config + '/xo-cli'
var configFile = configPath + '/config.json'
// ===================================================================
var load = exports.load = function () {
return readFile(configFile).then(JSON.parse).catch(function () {
return {}
})
}
exports.get = function (path) {
return load().then(function (config) {
return l33t(config).tap(path)
})
}
var save = exports.save = function (config) {
return mkdirp(configPath).then(function () {
return writeFile(configFile, JSON.stringify(config))
})
}
exports.set = function (data) {
return load().then(function (config) {
return save(assign(config, data))
})
}
exports.unset = function (paths) {
return load().then(function (config) {
var l33tConfig = l33t(config)
;[].concat(paths).forEach(function (path) {
l33tConfig.purge(path, true)
})
return save(config)
})
}

397
packages/xo-cli/index.js Executable file
View File

@@ -0,0 +1,397 @@
#!/usr/bin/env node
'use strict'
var Bluebird = require('bluebird')
Bluebird.longStackTraces()
var createReadStream = require('fs').createReadStream
var createWriteStream = require('fs').createWriteStream
var resolveUrl = require('url').resolve
var stat = require('fs-promise').stat
var chalk = require('chalk')
var eventToPromise = require('event-to-promise')
var filter = require('lodash/filter')
var forEach = require('lodash/forEach')
var getKeys = require('lodash/keys')
var got = require('got')
var humanFormat = require('human-format')
var identity = require('lodash/identity')
var isObject = require('lodash/isObject')
var micromatch = require('micromatch')
var multiline = require('multiline')
var nicePipe = require('nice-pipe')
var pairs = require('lodash/toPairs')
var pick = require('lodash/pick')
var prettyMs = require('pretty-ms')
var progressStream = require('progress-stream')
var Xo = require('xo-lib').default
// -------------------------------------------------------------------
var config = require('./config')
// ===================================================================
function connect () {
return config.load().bind({}).then(function (config) {
if (!config.server) {
throw new Error('no server to connect to!')
}
if (!config.token) {
throw new Error('no token available')
}
var xo = new Xo({ url: config.server })
return xo.open().then(function () {
return xo.signIn({ token: config.token })
}).then(function () {
return xo
})
})
}
function _startsWith (string, search) {
return string.lastIndexOf(search, 0) === 0
}
var FLAG_RE = /^--([^=]+)(?:=([^]*))?$/
function extractFlags (args) {
var flags = {}
var i = 0
var n = args.length
var matches
while (i < n && (matches = args[i].match(FLAG_RE))) {
var value = matches[2]
flags[matches[1]] = value === undefined ? true : value
++i
}
args.splice(0, i)
return flags
}
var PARAM_RE = /^([^=]+)=([^]*)$/
function parseParameters (args) {
var params = {}
forEach(args, function (arg) {
var matches
if (!(matches = arg.match(PARAM_RE))) {
throw new Error('invalid arg: ' + arg)
}
var name = matches[1]
var value = matches[2]
if (_startsWith(value, 'json:')) {
value = JSON.parse(value.slice(5))
}
if (name === '@') {
params['@'] = value
return
}
if (value === 'true') {
value = true
} else if (value === 'false') {
value = false
}
params[name] = value
})
return params
}
var humanFormatOpts = {
unit: 'B',
scale: 'binary'
}
function printProgress (progress) {
if (progress.length) {
console.warn('%s% of %s @ %s/s - ETA %s',
Math.round(progress.percentage),
humanFormat(progress.length, humanFormatOpts),
humanFormat(progress.speed, humanFormatOpts),
prettyMs(progress.eta * 1e3)
)
} else {
console.warn('%s @ %s/s',
humanFormat(progress.transferred, humanFormatOpts),
humanFormat(progress.speed, humanFormatOpts)
)
}
}
function wrap (val) {
return function wrappedValue () {
return val
}
}
// ===================================================================
var help = wrap((function (pkg) {
return multiline.stripIndent(function () { /*
Usage:
$name --register [<XO-Server URL>] [<username>] [<password>]
Registers the XO instance to use.
$name --unregister
Remove stored credentials.
$name --list-commands [--json] [<pattern>]...
Returns the list of available commands on the current XO instance.
The patterns can be used to filter on command names.
$name --list-objects [--<property>]… [<property>=<value>]...
Returns a list of XO objects.
--<property>
Restricts displayed properties to those listed.
<property>=<value>
Restricted displayed objects to those matching the patterns.
$name <command> [<name>=<value>]...
Executes a command on the current XO instance.
$name v$version
*/ }).replace(/<([^>]+)>|\$(\w+)/g, function (_, arg, key) {
if (arg) {
return '<' + chalk.yellow(arg) + '>'
}
if (key === 'name') {
return chalk.bold(pkg[key])
}
return pkg[key]
})
})(require('./package')))
// -------------------------------------------------------------------
function main (args) {
if (!args || !args.length || args[0] === '-h') {
return help()
}
var fnName = args[0].replace(/^--|-\w/g, function (match) {
if (match === '--') {
return ''
}
return match[1].toUpperCase()
})
if (fnName in exports) {
return exports[fnName](args.slice(1))
}
return exports.call(args)
}
exports = module.exports = main
// -------------------------------------------------------------------
exports.help = help
function register (args) {
var xo = new Xo({ url: args[0] })
return xo.open().then(function () {
return xo.signIn({
email: args[1],
password: args[2]
})
}).then(function () {
console.log('Successfully logged with', xo.user.email)
return xo.call('token.create')
}).then(function (token) {
return config.set({
server: args[0],
token: token
})
})
}
exports.register = register
function unregister () {
return config.unset([
'server',
'token'
])
}
exports.unregister = unregister
function listCommands (args) {
return connect().then(function getMethodsInfo (xo) {
return xo.call('system.getMethodsInfo')
}).then(function formatMethodsInfo (methods) {
var json = false
var patterns = []
forEach(args, function (arg) {
if (arg === '--json') {
json = true
} else {
patterns.push(arg)
}
})
if (patterns.length) {
methods = pick(methods, micromatch(Object.keys(methods), patterns))
}
if (json) {
return methods
}
methods = pairs(methods)
methods.sort(function (a, b) {
a = a[0]
b = b[0]
if (a < b) {
return -1
}
return +(a > b)
})
var str = []
forEach(methods, function (method) {
var name = method[0]
var info = method[1]
str.push(chalk.bold.blue(name))
forEach(info.params || [], function (info, name) {
str.push(' ')
if (info.optional) {
str.push('[')
}
str.push(name, '=<', info.type || 'unknown', '>')
if (info.optional) {
str.push(']')
}
})
str.push('\n')
if (info.description) {
str.push(' ', info.description, '\n')
}
})
return str.join('')
})
}
exports.listCommands = listCommands
function listObjects (args) {
var properties = getKeys(extractFlags(args))
var filterProperties = properties.length
? function (object) {
return pick(object, properties)
}
: identity
var sieve = args.length ? parseParameters(args) : null
return connect().then(function getXoObjects (xo) {
return xo.call('xo.getAllObjects')
}).then(function filterObjects (objects) {
objects = filter(objects, sieve)
const stdout = process.stdout
stdout.write('[\n')
for (var i = 0, n = objects.length; i < n;) {
stdout.write(JSON.stringify(filterProperties(objects[i]), null, 2))
stdout.write(++i < n ? ',\n' : '\n')
}
stdout.write(']')
})
}
exports.listObjects = listObjects
function call (args) {
if (!args.length) {
throw new Error('missing command name')
}
var method = args.shift()
var params = parseParameters(args)
var file = params['@']
delete params['@']
var baseUrl
return connect().then(function (xo) {
// FIXME: do not use private properties.
baseUrl = xo._url.replace(/^ws/, 'http')
return xo.call(method, params)
}).then(function handleResult (result) {
var keys, key, url
if (
isObject(result) &&
(keys = getKeys(result)).length === 1
) {
key = keys[0]
if (key === '$getFrom') {
url = resolveUrl(baseUrl, result[key])
var output = createWriteStream(file)
var progress = progressStream({ time: 1e3 }, printProgress)
return eventToPromise(nicePipe([
got.stream(url).on('response', function (response) {
var length = response.headers['content-length']
if (length) {
progress.length(length)
}
}),
progress,
output
]), 'finish')
}
if (key === '$sendTo') {
url = resolveUrl(baseUrl, result[key])
return stat(file).then(function (stats) {
var length = stats.size
var input = nicePipe([
createReadStream(file),
progressStream({
length: length,
time: 1e3
}, printProgress)
])
return got.post(url, {
body: input,
headers: {
'content-length': length
},
method: 'POST'
}).then(function (response) {
return response.body
})
})
}
}
return result
})
}
exports.call = call
// ===================================================================
if (!module.parent) {
require('exec-promise')(exports)
}

View File

@@ -0,0 +1,57 @@
{
"name": "xo-cli",
"version": "0.8.2",
"license": "AGPL-3.0",
"description": "Basic CLI for Xen-Orchestra",
"keywords": [
"xo",
"xen-orchestra",
"xen",
"orchestra"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-cli",
"bugs": "https://github.com/vatesfr/xo-web/issues",
"author": "Julien Fontanet <julien.fontanet@vates.fr>",
"preferGlobal": true,
"bin": {
"xo-cli": "index.js"
},
"files": [
"*.js"
],
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"dependencies": {
"bluebird": "^3.4.6",
"chalk": "^1.1.1",
"event-to-promise": "^0.7.0",
"exec-promise": "^0.6.1",
"fs-promise": "^1.0.0",
"got": "^6.5.0",
"human-format": "^0.7.0",
"l33teral": "^3.0.2",
"lodash": "^4.16.4",
"micromatch": "^2.2.0",
"mkdirp": "^0.5.0",
"multiline": "^1.0.2",
"nice-pipe": "0.0.0",
"pretty-ms": "^2.1.0",
"progress-stream": "^1.1.1",
"xdg-basedir": "^2.0.0",
"xo-lib": "^0.8.0"
},
"devDependencies": {
"standard": "^8.1.0"
},
"scripts": {
"lint": "standard",
"posttest": "yarn run lint"
},
"greenkeeper": {
"ignore": [
"nice-pipe"
]
}
}

1413
packages/xo-cli/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
/benchmark/
/benchmarks/
*.bench.js
*.bench.js.map
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/fixture/
/fixtures/
*.fixture.js
*.fixture.js.map
*.fixtures.js
*.fixtures.js.map
/test/
/tests/
*.spec.js
*.spec.js.map
__snapshots__/

View File

@@ -0,0 +1,662 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.

View File

@@ -0,0 +1,265 @@
# xo-collection [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
> Generic in-memory collection with events
## Install
Installation of the [npm package](https://npmjs.org/package/xo-collection):
```
> npm install --save xo-collection
```
## Usage
```javascript
var Collection = require('xo-collection')
```
### Creation
```javascript
// Creates a new collection.
var col = new Collection()
```
### Manipulation
**Inserting a new item**
```javascript
col.add('foo', true)
```
- **Throws** `DuplicateItem` if the item is already in the collection.
**Updating an existing item**
```javascript
col.update('foo', false)
```
- **Throws** `NoSuchItem` if the item is not in the collection.
**Inserting or updating an item**
```javascript
col.set('bar', true)
```
**Notifying an external update**
> If an item is an object, it can be updated directly without using
> the `set`/`update` methods.
>
> To make sure the collection stays in sync and the correct events are
> sent, the `touch` method can be used to notify the change.
```javascript
var baz = {}
col.add('baz', baz)
baz.prop = true
col.touch('baz')
```
> Because this is a much used pattern, `touch` returns the item to
> allow its direct modification.
```javascript
col.touch('baz').prop = false
```
- **Throws** `NoSuchItem` if the item is not in the collection.
- **Throws** `IllegalTouch` if the item is not an object.
**Removing an existing item**
```javascript
col.remove('bar')
```
- **Throws** `NoSuchItem` if the item is not in the collection.
**Removing an item without error**
This is the symmetric method of `set()`: it removes the item if it
exists otherwise does nothing.
```javascript
col.unset('bar')
```
**Removing all items**
```javascript
col.clear()
```
### Query
**Checking the existence of an item**
```javascript
var hasBar = col.has('bar')
```
**Getting an existing item**
```javascript
var foo = col.get('foo')
// The second parameter can be used to specify a fallback in case the
// item does not exist.
var bar = col.get('bar', 6.28)
```
- **Throws** `NoSuchItem` if the item is not in the collection and no
fallback has been passed.
**Getting a read-only view of the collection**
> This property is useful for example to iterate over the collection
> or to make advanced queries with the help of utility libraries such
> as lodash.
```javascript
var _ = require('lodash')
// Prints all the items.
_.forEach(col.all, function (value, key) {
console.log('- %s: %j', key, value)
})
// Finds all the items which are objects and have a property
// `active` which equals `true`.
var results = _.where(col.all, { active: true })
```
**Getting the number of items**
```javascript
var size = col.size
```
### Events
> The events are emitted asynchronously (at the next turn/tick of the
> event loop) and are deduplicated which means, for instance, that an
> addition followed by an update will result only in a single
> addition.
**New items**
```javascript
col.on('add', (added) => {
forEach(added, (value, key) => {
console.log('+ %s: %j', key, value)
})
})
```
**Updated items**
```javascript
col.on('update', (updated) => {
forEach(updated, (value, key) => {
console.log('± %s: %j', key, value)
})
})
```
**Removed items**
```javascript
col.on('remove', (removed) => {
// For consistency, `removed` is also a map but contrary to `added`
// and `updated`, the values associated to the keys are not
// significant since the items have already be removed.
forEach(removed, (value, key) => {
console.log('- %s', key)
})
})
```
**End of update**
> Emitted when all the update process is finished and all the update
> events has been emitted.
```javascript
col.on('finish', () => {
console.log('the collection has been updated')
})
```
### Iteration
```javascript
for (const [key, value] of col) {
console.log('- %s: %j', key, value)
}
for (const key of col.keys()) {
console.log('- %s', key)
}
for (const value of col.values()) {
console.log('- %j', value)
}
```
### Views
```javascript
const View = require('xo-collection/view')
```
> A view is a read-only collection which contains only the items of a
> parent collection which satisfy a predicate.
>
> It is updated at most once per turn of the event loop and therefore
> can be briefly invalid.
```javascript
const myView = new View(parentCollection, function predicate (value, key) {
// This function should return a boolean indicating whether the
// current item should be in this view.
})
```
## Development
```
# Install dependencies
> npm install
# Run the tests
> npm test
# Continuously compile
> npm run dev
# Continuously run the tests
> npm run dev-test
# Build for production (automatically called by npm install)
> npm run build
```
## Contributions
Contributions are *very* welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xo-web/issues)
you've encountered;
- fork and create a pull request.
## License
ISC © [Vates SAS](http://vates.fr)

View File

@@ -0,0 +1 @@
module.exports = require('./dist/index')

View File

@@ -0,0 +1,86 @@
{
"name": "xo-collection",
"version": "0.4.1",
"license": "ISC",
"description": "Generic in-memory collection with events",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-collection",
"bugs": "https://github.com/vatesfr/xo-web/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Fabrice Marsaud",
"email": "fabrice.marsaud@vates.fr"
},
"preferGlobal": false,
"main": "dist/collection",
"bin": {},
"files": [
"dist/",
"*.js"
],
"engines": {
"node": ">=4"
},
"dependencies": {
"babel-runtime": "^6.18.0",
"kindof": "^2.0.0",
"lodash": "^4.17.2",
"make-error": "^1.0.2"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-eslint": "^7.1.0",
"babel-plugin-lodash": "^3.2.11",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-preset-env": "^1.1.8",
"babel-preset-stage-3": "^6.17.0",
"cross-env": "^3.1.4",
"dependency-check": "^2.7.0",
"event-to-promise": "^0.7.0",
"jest": "^18.1.0",
"rimraf": "^2.5.4",
"standard": "^8.6.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"dev-test": "jest --bail --watch",
"posttest": "standard && dependency-check ./package.json --entry dist/collection.js index.js unique-index.js view.js",
"prebuild": "rimraf dist/",
"predev": "yarn run prebuild",
"prepublish": "yarn run build",
"test": "jest"
},
"babel": {
"plugins": [
"lodash",
"transform-runtime"
],
"presets": [
[
"env",
{
"targets": {
"node": 4
}
}
],
"stage-3"
]
},
"jest": {
"testPathDirs": [
"<rootDir>/src"
],
"testRegex": "\\.spec\\.js$"
},
"standard": {
"ignore": [
"dist"
],
"parser": "babel-eslint"
}
}

View File

@@ -0,0 +1,5 @@
export default function clearObject (object) {
for (const key in object) {
delete object[key]
}
}

View File

@@ -0,0 +1,363 @@
import kindOf from 'kindof'
import {BaseError} from 'make-error'
import {EventEmitter} from 'events'
import {forEach} from 'lodash'
import isEmpty from './is-empty'
import isObject from './is-object'
// ===================================================================
const {
create: createObject,
prototype: { hasOwnProperty }
} = Object
export const ACTION_ADD = 'add'
export const ACTION_UPDATE = 'update'
export const ACTION_REMOVE = 'remove'
// ===================================================================
export class BufferAlreadyFlushed extends BaseError {
constructor () {
super('buffer flush already requested')
}
}
export class DuplicateIndex extends BaseError {
constructor (name) {
super('there is already an index with the name ' + name)
}
}
export class DuplicateItem extends BaseError {
constructor (key) {
super('there is already a item with the key ' + key)
}
}
export class IllegalTouch extends BaseError {
constructor (value) {
super('only an object value can be touched (found a ' + kindOf(value) + ')')
}
}
export class InvalidKey extends BaseError {
constructor (key) {
super('invalid key of type ' + kindOf(key))
}
}
export class NoSuchIndex extends BaseError {
constructor (name) {
super('there is no index with the name ' + name)
}
}
export class NoSuchItem extends BaseError {
constructor (key) {
super('there is no item with the key ' + key)
}
}
// -------------------------------------------------------------------
export default class Collection extends EventEmitter {
constructor () {
super()
this._buffer = createObject(null)
this._buffering = 0
this._indexes = createObject(null)
this._indexedItems = createObject(null)
this._items = {} // createObject(null)
this._size = 0
}
// Overridable method used to compute the key of an item when
// unspecified.
//
// Default implementation returns the `id` property.
getKey (value) {
return value && value.id
}
// -----------------------------------------------------------------
// Properties
// -----------------------------------------------------------------
get all () {
return this._items
}
get indexes () {
return this._indexedItems
}
get size () {
return this._size
}
// -----------------------------------------------------------------
// Manipulation
// -----------------------------------------------------------------
add (keyOrObjectWithId, valueIfKey = undefined) {
const [key, value] = this._resolveItem(keyOrObjectWithId, valueIfKey)
this._assertHasNot(key)
this._items[key] = value
this._size++
this._touch(ACTION_ADD, key)
}
clear () {
forEach(this._items, (_, key) => this._remove(key))
}
remove (keyOrObjectWithId) {
const [key] = this._resolveItem(keyOrObjectWithId)
this._assertHas(key)
this._remove(key)
}
set (keyOrObjectWithId, valueIfKey = undefined) {
const [key, value] = this._resolveItem(keyOrObjectWithId, valueIfKey)
const action = this.has(key) ? ACTION_UPDATE : ACTION_ADD
this._items[key] = value
if (action === ACTION_ADD) {
this._size++
}
this._touch(action, key)
}
touch (keyOrObjectWithId) {
const [key] = this._resolveItem(keyOrObjectWithId)
this._assertHas(key)
const value = this.get(key)
if (!isObject(value)) {
throw new IllegalTouch(value)
}
this._touch(ACTION_UPDATE, key)
return this.get(key)
}
unset (keyOrObjectWithId) {
const [key] = this._resolveItem(keyOrObjectWithId)
if (this.has(key)) {
this._remove(key)
}
}
update (keyOrObjectWithId, valueIfKey = undefined) {
const [key, value] = this._resolveItem(keyOrObjectWithId, valueIfKey)
this._assertHas(key)
this._items[key] = value
this._touch(ACTION_UPDATE, key)
}
// -----------------------------------------------------------------
// Query
// -----------------------------------------------------------------
get (key, defaultValue) {
if (this.has(key)) {
return this._items[key]
}
if (arguments.length > 1) {
return defaultValue
}
// Throws a NoSuchItem.
this._assertHas(key)
}
has (key) {
return hasOwnProperty.call(this._items, key)
}
// -----------------------------------------------------------------
// Indexes
// -----------------------------------------------------------------
createIndex (name, index) {
const {_indexes: indexes} = this
if (hasOwnProperty.call(indexes, name)) {
throw new DuplicateIndex(name)
}
indexes[name] = index
this._indexedItems[name] = index.items
index._attachCollection(this)
}
deleteIndex (name) {
const {_indexes: indexes} = this
if (!hasOwnProperty.call(indexes, name)) {
throw new NoSuchIndex(name)
}
const index = indexes[name]
delete indexes[name]
delete this._indexedItems[name]
index._detachCollection(this)
}
// -----------------------------------------------------------------
// Iteration
// -----------------------------------------------------------------
* [Symbol.iterator] () {
const {_items: items} = this
for (const key in items) {
yield [key, items[key]]
}
}
* keys () {
const {_items: items} = this
for (const key in items) {
yield key
}
}
* values () {
const {_items: items} = this
for (const key in items) {
yield items[key]
}
}
// -----------------------------------------------------------------
// Events buffering
// -----------------------------------------------------------------
bufferEvents () {
++this._buffering
let called = false
return () => {
if (called) {
throw new BufferAlreadyFlushed()
}
called = true
if (--this._buffering) {
return
}
const {_buffer: buffer} = this
// Due to deduplication there could be nothing in the buffer.
if (isEmpty(buffer)) {
return
}
const data = {
add: createObject(null),
remove: createObject(null),
update: createObject(null)
}
for (const key in this._buffer) {
data[buffer[key]][key] = this._items[key]
}
forEach(data, (items, action) => {
if (!isEmpty(items)) {
this.emit(action, items)
}
})
// Indicates the end of the update.
//
// This name has been chosen because it is used in Node writable
// streams when the data has been successfully committed.
this.emit('finish')
this._buffer = createObject(null)
}
}
// =================================================================
_assertHas (key) {
if (!this.has(key)) {
throw new NoSuchItem(key)
}
}
_assertHasNot (key) {
if (this.has(key)) {
throw new DuplicateItem(key)
}
}
_assertValidKey (key) {
if (!this._isValidKey(key)) {
throw new InvalidKey(key)
}
}
_isValidKey (key) {
return typeof key === 'number' || typeof key === 'string'
}
_remove (key) {
delete this._items[key]
this._size--
this._touch(ACTION_REMOVE, key)
}
_resolveItem (keyOrObjectWithId, valueIfKey = undefined) {
if (valueIfKey !== undefined) {
this._assertValidKey(keyOrObjectWithId)
return [keyOrObjectWithId, valueIfKey]
}
if (this._isValidKey(keyOrObjectWithId)) {
return [keyOrObjectWithId]
}
const key = this.getKey(keyOrObjectWithId)
this._assertValidKey(key)
return [key, keyOrObjectWithId]
}
_touch (action, key) {
if (this._buffering === 0) {
const flush = this.bufferEvents()
process.nextTick(flush)
}
if (action === ACTION_ADD) {
this._buffer[key] = this._buffer[key] ? ACTION_UPDATE : ACTION_ADD
} else if (action === ACTION_REMOVE) {
if (this._buffer[key] === ACTION_ADD) {
delete this._buffer[key]
} else {
this._buffer[key] = ACTION_REMOVE
}
} else { // update
if (!this._buffer[key]) {
this._buffer[key] = ACTION_UPDATE
}
}
}
}

View File

@@ -0,0 +1,341 @@
/* eslint-env jest */
import eventToPromise from 'event-to-promise'
import { forEach } from 'lodash'
import Collection, {DuplicateItem, NoSuchItem} from '..'
// ===================================================================
function waitTicks (n = 2) {
const {nextTick} = process
return new Promise(function (resolve) {
(function waitNextTick () {
// The first tick is handled by Promise#then()
if (--n) {
nextTick(waitNextTick)
} else {
resolve()
}
})()
})
}
describe('Collection', function () {
beforeEach(function () {
this.col = new Collection()
this.col.add('bar', 0)
return waitTicks()
})
it('is iterable', function () {
const iterator = this.col[Symbol.iterator]()
expect(iterator.next()).toEqual({done: false, value: ['bar', 0]})
expect(iterator.next()).toEqual({done: true, value: undefined})
})
describe('#keys()', function () {
it('returns an iterator over the keys', function () {
const iterator = this.col.keys()
expect(iterator.next()).toEqual({done: false, value: 'bar'})
expect(iterator.next()).toEqual({done: true, value: undefined})
})
})
describe('#values()', function () {
it('returns an iterator over the values', function () {
const iterator = this.col.values()
expect(iterator.next()).toEqual({done: false, value: 0})
expect(iterator.next()).toEqual({done: true, value: undefined})
})
})
describe('#add()', function () {
it('adds item to the collection', function () {
const spy = jest.fn()
this.col.on('add', spy)
this.col.add('foo', true)
expect(this.col.get('foo')).toBe(true)
// No sync events.
expect(spy).not.toHaveBeenCalled()
// Async event.
return eventToPromise(this.col, 'add').then(function (added) {
expect(Object.keys(added)).toEqual([ 'foo' ])
expect(added.foo).toBe(true)
})
})
it('throws an exception if the item already exists', function () {
expect(() => this.col.add('bar', true)).toThrowError(DuplicateItem)
})
it('accepts an object with an id property', function () {
const foo = { id: 'foo' }
this.col.add(foo)
expect(this.col.get(foo.id)).toBe(foo)
})
})
describe('#update()', function () {
it('updates an item of the collection', function () {
const spy = jest.fn()
this.col.on('update', spy)
this.col.update('bar', 1)
expect(this.col.get('bar')).toBe(1) // Will be forgotten by de-duplication
this.col.update('bar', 2)
expect(this.col.get('bar')).toBe(2)
// No sync events.
expect(spy).not.toHaveBeenCalled()
// Async event.
return eventToPromise(this.col, 'update').then(function (updated) {
expect(Object.keys(updated)).toEqual(['bar'])
expect(updated.bar).toBe(2)
})
})
it('throws an exception if the item does not exist', function () {
expect(() => this.col.update('baz', true)).toThrowError(NoSuchItem)
})
it('accepts an object with an id property', function () {
const bar = { id: 'bar' }
this.col.update(bar)
expect(this.col.get(bar.id)).toBe(bar)
})
})
describe('#remove()', function () {
it('removes an item of the collection', function () {
const spy = jest.fn()
this.col.on('remove', spy)
this.col.update('bar', 1)
expect(this.col.get('bar')).toBe(1) // Will be forgotten by de-duplication
this.col.remove('bar')
// No sync events.
expect(spy).not.toHaveBeenCalled()
// Async event.
return eventToPromise(this.col, 'remove').then(function (removed) {
expect(Object.keys(removed)).toEqual(['bar'])
expect(removed.bar).toBeUndefined()
})
})
it('throws an exception if the item does not exist', function () {
expect(() => this.col.remove('baz', true)).toThrowError(NoSuchItem)
})
it('accepts an object with an id property', function () {
const bar = { id: 'bar' }
this.col.remove(bar)
expect(this.col.has(bar.id)).toBe(false)
})
})
describe('#set()', function () {
it('adds item if collection has not key', function () {
const spy = jest.fn()
this.col.on('add', spy)
this.col.set('foo', true)
expect(this.col.get('foo')).toBe(true)
// No sync events.
expect(spy).not.toHaveBeenCalled()
// Async events.
return eventToPromise(this.col, 'add').then(function (added) {
expect(Object.keys(added)).toEqual(['foo'])
expect(added.foo).toBe(true)
})
})
it('updates item if collection has key', function () {
const spy = jest.fn()
this.col.on('udpate', spy)
this.col.set('bar', 1)
expect(this.col.get('bar')).toBe(1)
// No sync events.
expect(spy).not.toHaveBeenCalled()
// Async events.
return eventToPromise(this.col, 'update').then(function (updated) {
expect(Object.keys(updated)).toEqual(['bar'])
expect(updated.bar).toBe(1)
})
})
it('accepts an object with an id property', function () {
const foo = { id: 'foo' }
this.col.set(foo)
expect(this.col.get(foo.id)).toBe(foo)
})
})
describe('#unset()', function () {
it('removes an existing item', function () {
this.col.unset('bar')
expect(this.col.has('bar')).toBe(false)
return eventToPromise(this.col, 'remove').then(function (removed) {
expect(Object.keys(removed)).toEqual(['bar'])
expect(removed.bar).toBeUndefined()
})
})
it('does not throw if the item does not exists', function () {
this.col.unset('foo')
})
it('accepts an object with an id property', function () {
this.col.unset({id: 'bar'})
expect(this.col.has('bar')).toBe(false)
return eventToPromise(this.col, 'remove').then(function (removed) {
expect(Object.keys(removed)).toEqual(['bar'])
expect(removed.bar).toBeUndefined()
})
})
})
describe('touch()', function () {
it('can be used to signal an indirect update', function () {
const foo = { id: 'foo' }
this.col.add(foo)
return waitTicks().then(() => {
this.col.touch(foo)
return eventToPromise(this.col, 'update', (items) => {
expect(Object.keys(items)).toEqual(['foo'])
expect(items.foo).toBe(foo)
})
})
})
})
describe('clear()', function () {
it('removes all items from the collection', function () {
this.col.clear()
expect(this.col.size).toBe(0)
return eventToPromise(this.col, 'remove').then((items) => {
expect(Object.keys(items)).toEqual(['bar'])
expect(items.bar).toBeUndefined()
})
})
})
describe('deduplicates events', function () {
forEach({
'add & update → add': [
[
['add', 'foo', 0],
['update', 'foo', 1]
],
{
add: {
foo: 1
}
}
],
'add & remove → ∅': [
[
['add', 'foo', 0],
['remove', 'foo']
],
{}
],
'update & update → update': [
[
['update', 'bar', 1],
['update', 'bar', 2]
],
{
update: {
bar: 2
}
}
],
'update & remove → remove': [
[
['update', 'bar', 1],
['remove', 'bar']
],
{
remove: {
bar: undefined
}
}
],
'remove & add → update': [
[
['remove', 'bar'],
['add', 'bar', 0]
],
{
update: {
bar: 0
}
}
]
}, ([operations, results], label) => {
it(label, function () {
const {col} = this
forEach(operations, ([method, ...args]) => {
col[method](...args)
})
const spies = Object.create(null)
forEach(['add', 'update', 'remove'], event => {
col.on(event, (spies[event] = jest.fn()))
})
return waitTicks().then(() => {
forEach(spies, (spy, event) => {
const items = results[event]
if (items) {
expect(spy.mock.calls).toEqual([ [ items ] ])
} else {
expect(spy).not.toHaveBeenCalled()
}
})
})
})
})
})
})

View File

@@ -0,0 +1,149 @@
import { bind, iteratee } from 'lodash'
import clearObject from './clear-object'
import isEmpty from './is-empty'
import NotImplemented from './not-implemented'
import {
ACTION_ADD,
ACTION_UPDATE,
ACTION_REMOVE
} from './collection'
// ===================================================================
export default class Index {
constructor (computeHash) {
if (computeHash) {
this.computeHash = iteratee(computeHash)
}
this._itemsByHash = Object.create(null)
this._keysToHash = Object.create(null)
// Bound versions of listeners.
this._onAdd = bind(this._onAdd, this)
this._onUpdate = bind(this._onUpdate, this)
this._onRemove = bind(this._onRemove, this)
}
// This method is used to compute the hash under which an item must
// be saved.
computeHash (value, key) {
throw new NotImplemented('this method must be overridden')
}
// Remove empty items lists.
sweep () {
const {_itemsByHash: itemsByHash} = this
for (const hash in itemsByHash) {
if (isEmpty(itemsByHash[hash])) {
delete itemsByHash[hash]
}
}
}
// -----------------------------------------------------------------
get items () {
return this._itemsByHash
}
// -----------------------------------------------------------------
_attachCollection (collection) {
// Add existing entries.
//
// FIXME: I think there may be a race condition if the `add` event
// has not been emitted yet.
this._onAdd(collection.all)
collection.on(ACTION_ADD, this._onAdd)
collection.on(ACTION_UPDATE, this._onUpdate)
collection.on(ACTION_REMOVE, this._onRemove)
}
_detachCollection (collection) {
collection.removeListener(ACTION_ADD, this._onAdd)
collection.removeListener(ACTION_UPDATE, this._onUpdate)
collection.removeListener(ACTION_REMOVE, this._onRemove)
clearObject(this._itemsByHash)
clearObject(this._keysToHash)
}
// -----------------------------------------------------------------
_onAdd (items) {
const {
computeHash,
_itemsByHash: itemsByHash,
_keysToHash: keysToHash
} = this
for (const key in items) {
const value = items[key]
const hash = computeHash(value, key)
if (hash != null) {
(
itemsByHash[hash] ||
// FIXME: We do not use objects without prototype for now
// because it breaks Angular in xo-web, change it back when
// this is fixed.
(itemsByHash[hash] = {})
)[key] = value
keysToHash[key] = hash
}
}
}
_onUpdate (items) {
const {
computeHash,
_itemsByHash: itemsByHash,
_keysToHash: keysToHash
} = this
for (const key in items) {
const value = items[key]
const prev = keysToHash[key]
const hash = computeHash(value, key)
// Removes item from the previous hash's list if any.
if (prev != null) delete itemsByHash[prev][key]
// Inserts item into the new hash's list if any.
if (hash != null) {
(
itemsByHash[hash] ||
// FIXME: idem: change back to Object.create(null)
(itemsByHash[hash] = {})
)[key] = value
keysToHash[key] = hash
} else {
delete keysToHash[key]
}
}
}
_onRemove (items) {
const {
_itemsByHash: itemsByHash,
_keysToHash: keysToHash
} = this
for (const key in items) {
const prev = keysToHash[key]
if (prev != null) {
delete itemsByHash[prev][key]
delete keysToHash[key]
}
}
}
}

View File

@@ -0,0 +1,180 @@
/* eslint-env jest */
import eventToPromise from 'event-to-promise'
import { forEach } from 'lodash'
import Collection from '..'
import Index from '../index'
// ===================================================================
const waitTicks = (n = 2) => {
const {nextTick} = process
return new Promise(resolve => {
(function waitNextTick () {
// The first tick is handled by Promise#then()
if (--n) {
nextTick(waitNextTick)
} else {
resolve()
}
})()
})
}
// ===================================================================
describe('Index', function () {
let col, byGroup
const item1 = {
id: '2ccb8a72-dc65-48e4-88fe-45ef541f2cba',
group: 'foo'
}
const item2 = {
id: '7d21dc51-4da8-4538-a2e9-dd6f4784eb76',
group: 'bar'
}
const item3 = {
id: '668c1274-4442-44a6-b99a-512188e0bb09',
group: 'foo'
}
const item4 = {
id: 'd90b7335-e540-4a44-ad22-c4baae9cd0a9'
}
beforeEach(function () {
col = new Collection()
forEach([item1, item2, item3, item4], item => {
col.add(item)
})
byGroup = new Index('group')
col.createIndex('byGroup', byGroup)
return waitTicks()
})
it('works with existing items', function () {
expect(col.indexes).toEqual({
byGroup: {
foo: {
[item1.id]: item1,
[item3.id]: item3
},
bar: {
[item2.id]: item2
}
}
})
})
it('works with added items', function () {
const item5 = {
id: '823b56c4-4b96-4f3a-9533-5d08177167ac',
group: 'baz'
}
col.add(item5)
return waitTicks().then(() => {
expect(col.indexes).toEqual({
byGroup: {
foo: {
[item1.id]: item1,
[item3.id]: item3
},
bar: {
[item2.id]: item2
},
baz: {
[item5.id]: item5
}
}
})
})
})
it('works with updated items', function () {
const item1bis = {
id: item1.id,
group: 'bar'
}
col.update(item1bis)
return waitTicks().then(() => {
expect(col.indexes).toEqual({
byGroup: {
foo: {
[item3.id]: item3
},
bar: {
[item1.id]: item1bis,
[item2.id]: item2
}
}
})
})
})
it('works with removed items', function () {
col.remove(item2)
return waitTicks().then(() => {
expect(col.indexes).toEqual({
byGroup: {
foo: {
[item1.id]: item1,
[item3.id]: item3
},
bar: {}
}
})
})
})
it('correctly updates the value even the same object has the same hash', function () {
const item1bis = {
id: item1.id,
group: item1.group,
newProp: true
}
col.update(item1bis)
return eventToPromise(col, 'finish').then(() => {
expect(col.indexes).toEqual({
byGroup: {
foo: {
[item1.id]: item1bis,
[item3.id]: item3
},
bar: {
[item2.id]: item2
}
}
})
})
})
describe('#sweep()', function () {
it('removes empty items lists', function () {
col.remove(item2)
return waitTicks().then(() => {
byGroup.sweep()
expect(col.indexes).toEqual({
byGroup: {
foo: {
[item1.id]: item1,
[item3.id]: item3
}
}
})
})
})
})
})

View File

@@ -0,0 +1,7 @@
export default function isEmpty (object) {
/* eslint no-unused-vars: 0 */
for (const key in object) {
return false
}
return true
}

View File

@@ -0,0 +1,3 @@
export default function isObject (value) {
return (value !== null) && (typeof value === 'object')
}

View File

@@ -0,0 +1,7 @@
import {BaseError} from 'make-error'
export default class NotImplemented extends BaseError {
constructor (message) {
super(message || 'this method is not implemented')
}
}

View File

@@ -0,0 +1,124 @@
import { bind, iteratee } from 'lodash'
import clearObject from './clear-object'
import NotImplemented from './not-implemented'
import {
ACTION_ADD,
ACTION_UPDATE,
ACTION_REMOVE
} from './collection'
// ===================================================================
export default class UniqueIndex {
constructor (computeHash) {
if (computeHash) {
this.computeHash = iteratee(computeHash)
}
this._itemByHash = Object.create(null)
this._keysToHash = Object.create(null)
// Bound versions of listeners.
this._onAdd = bind(this._onAdd, this)
this._onUpdate = bind(this._onUpdate, this)
this._onRemove = bind(this._onRemove, this)
}
// This method is used to compute the hash under which an item must
// be saved.
computeHash (value, key) {
throw new NotImplemented('this method must be overridden')
}
// -----------------------------------------------------------------
get items () {
return this._itemByHash
}
// -----------------------------------------------------------------
_attachCollection (collection) {
// Add existing entries.
//
// FIXME: I think there may be a race condition if the `add` event
// has not been emitted yet.
this._onAdd(collection.all)
collection.on(ACTION_ADD, this._onAdd)
collection.on(ACTION_UPDATE, this._onUpdate)
collection.on(ACTION_REMOVE, this._onRemove)
}
_detachCollection (collection) {
collection.removeListener(ACTION_ADD, this._onAdd)
collection.removeListener(ACTION_UPDATE, this._onUpdate)
collection.removeListener(ACTION_REMOVE, this._onRemove)
clearObject(this._itemByHash)
clearObject(this._keysToHash)
}
// -----------------------------------------------------------------
_onAdd (items) {
const {
computeHash,
_itemByHash: itemByHash,
_keysToHash: keysToHash
} = this
for (const key in items) {
const value = items[key]
const hash = computeHash(value, key)
if (hash != null) {
itemByHash[hash] = value
keysToHash[key] = hash
}
}
}
_onUpdate (items) {
const {
computeHash,
_itemByHash: itemByHash,
_keysToHash: keysToHash
} = this
for (const key in items) {
const value = items[key]
const prev = keysToHash[key]
const hash = computeHash(value, key)
// Removes item from the previous hash's list if any.
if (prev != null) delete itemByHash[prev]
// Inserts item into the new hash's list if any.
if (hash != null) {
itemByHash[hash] = value
keysToHash[key] = hash
} else {
delete keysToHash[key]
}
}
}
_onRemove (items) {
const {
_itemByHash: itemByHash,
_keysToHash: keysToHash
} = this
for (const key in items) {
const prev = keysToHash[key]
if (prev != null) {
delete itemByHash[prev]
delete keysToHash[key]
}
}
}
}

View File

@@ -0,0 +1,131 @@
/* eslint-env jest */
import eventToPromise from 'event-to-promise'
import { forEach } from 'lodash'
import Collection from '..'
import Index from '../unique-index'
// ===================================================================
const waitTicks = (n = 2) => {
const {nextTick} = process
return new Promise(resolve => {
(function waitNextTick () {
// The first tick is handled by Promise#then()
if (--n) {
nextTick(waitNextTick)
} else {
resolve()
}
})()
})
}
// ===================================================================
describe('UniqueIndex', function () {
let col, byKey
const item1 = {
id: '2ccb8a72-dc65-48e4-88fe-45ef541f2cba',
key: '036dee1b-9a3b-4fb5-be8a-4f535b355581'
}
const item2 = {
id: '7d21dc51-4da8-4538-a2e9-dd6f4784eb76',
key: '103cd893-d2cc-4d37-96fd-c259ad04c0d4'
}
const item3 = {
id: '668c1274-4442-44a6-b99a-512188e0bb09'
}
beforeEach(function () {
col = new Collection()
forEach([item1, item2, item3], item => {
col.add(item)
})
byKey = new Index('key')
col.createIndex('byKey', byKey)
return waitTicks()
})
it('works with existing items', function () {
expect(col.indexes).toEqual({
byKey: {
[item1.key]: item1,
[item2.key]: item2
}
})
})
it('works with added items', function () {
const item4 = {
id: '823b56c4-4b96-4f3a-9533-5d08177167ac',
key: '1437af14-429a-40db-8a51-8a2f5ed03201'
}
col.add(item4)
return waitTicks().then(() => {
expect(col.indexes).toEqual({
byKey: {
[item1.key]: item1,
[item2.key]: item2,
[item4.key]: item4
}
})
})
})
it('works with updated items', function () {
const item1bis = {
id: item1.id,
key: 'e03d4a3a-0331-4aca-97a2-016bbd43a29b'
}
col.update(item1bis)
return waitTicks().then(() => {
expect(col.indexes).toEqual({
byKey: {
[item1bis.key]: item1bis,
[item2.key]: item2
}
})
})
})
it('works with removed items', function () {
col.remove(item2)
return waitTicks().then(() => {
expect(col.indexes).toEqual({
byKey: {
[item1.key]: item1
}
})
})
})
it('correctly updates the value even the same object has the same hash', function () {
const item1bis = {
id: item1.id,
key: item1.key,
newProp: true
}
col.update(item1bis)
return eventToPromise(col, 'finish').then(() => {
expect(col.indexes).toEqual({
byKey: {
[item1.key]: item1bis,
[item2.key]: item2
}
})
})
})
})

View File

@@ -0,0 +1,56 @@
import { forEach } from 'lodash'
import Collection from '..'
import View from '../view'
// ===================================================================
// Create the collection.
const users = new Collection()
users.getKey = (user) => user.name
// Inserts some data.
users.add({
name: 'bob'
})
users.add({
name: 'clara',
active: true
})
users.add({
name: 'ophelia'
})
users.add({
name: 'Steve',
active: true
})
// -------------------------------------------------------------------
// Create the view.
const activeUsers = new View(users, 'active')
// Register some event listeners to see the changes.
activeUsers.on('add', users => {
forEach(users, (_, id) => {
console.log('+ active user:', id)
})
})
activeUsers.on('remove', users => {
forEach(users, (_, id) => {
console.log('- active user:', id)
})
})
// Make some changes in the future.
setTimeout(function () {
console.log('-----')
users.set({
name: 'ophelia',
active: true
})
users.set({
name: 'Steve'
})
}, 10)

View File

@@ -0,0 +1,88 @@
import { bind, forEach, iteratee as createCallback } from 'lodash'
import Collection, {
ACTION_ADD,
ACTION_UPDATE,
ACTION_REMOVE
} from './collection'
// ===================================================================
export default class View extends Collection {
constructor (collection, predicate) {
super()
this._collection = collection
this._predicate = createCallback(predicate)
// Handles initial items.
this._onAdd(this._collection.all)
// Bound versions of listeners.
this._onAdd = bind(this._onAdd, this)
this._onUpdate = bind(this._onUpdate, this)
this._onRemove = bind(this._onRemove, this)
// Register listeners.
this._collection.on(ACTION_ADD, this._onAdd)
this._collection.on(ACTION_UPDATE, this._onUpdate)
this._collection.on(ACTION_REMOVE, this._onRemove)
}
// This method is necessary to free the memory of the view if its
// life span is shorter than the collection.
destroy () {
this._collection.removeListener(ACTION_ADD, this._onAdd)
this._collection.removeListener(ACTION_UPDATE, this._onUpdate)
this._collection.removeListener(ACTION_REMOVE, this._onRemove)
}
add () {
throw new Error('a view is read only')
}
clear () {
throw new Error('a view is read only')
}
set () {
throw new Error('a view is read only')
}
update () {
throw new Error('a view is read only')
}
_onAdd (items) {
const {_predicate: predicate} = this
forEach(items, (value, key) => {
if (predicate(value, key, this)) {
// super.add() cannot be used because the item may already be
// in the view if it was already present at the creation of
// the view and its event not already emitted.
super.set(key, value)
}
})
}
_onUpdate (items) {
const {_predicate: predicate} = this
forEach(items, (value, key) => {
if (predicate(value, key, this)) {
super.set(key, value)
} else if (super.has(key)) {
super.remove(key)
}
})
}
_onRemove (items) {
forEach(items, (value, key) => {
if (super.has(key)) {
super.remove(key)
}
})
}
}

View File

@@ -0,0 +1 @@
module.exports = require('./dist/unique-index')

View File

@@ -0,0 +1 @@
module.exports = require('./dist/view')

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/test/
/tests/
*.spec.js
*.spec.js.map

View File

@@ -0,0 +1,49 @@
# xo-common [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
> Code shared between [XO](https://xen-orchestra.com) server and clients
## Install
Installation of the [npm package](https://npmjs.org/package/xo-common):
```
> npm install --save xo-common
```
## Usage
**TODO**
## Development
```
# Install dependencies
> npm install
# Run the tests
> npm test
# Continuously compile
> npm run dev
# Continuously run the tests
> npm run dev-test
# Build for production (automatically called by npm install)
> npm run build
```
## Contributions
Contributions are *very* welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xo-web/issues)
you've encountered;
- fork and create a pull request.
## License
AGPL3 © [Vates SAS](https://vates.fr)

View File

@@ -0,0 +1 @@
module.exports = require('./dist/api-errors')

View File

@@ -0,0 +1,77 @@
{
"name": "xo-common",
"version": "0.1.0",
"license": "AGPL-3.0",
"description": "Code shared between [XO](https://xen-orchestra.com) server and clients",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-common",
"bugs": "https://github.com/vatesfr/xo-web/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@isonoe.net"
},
"preferGlobal": false,
"bin": {},
"files": [
"dist/",
"*.js"
],
"engines": {
"node": ">=4"
},
"dependencies": {
"babel-runtime": "^6.18.0",
"lodash": "^4.16.6",
"make-error": "^1.2.1"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-eslint": "^7.1.0",
"babel-plugin-lodash": "^3.2.9",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-preset-env": "^1.0.0",
"babel-preset-stage-0": "^6.16.0",
"cross-env": "^3.1.3",
"dependency-check": "^2.6.0",
"rimraf": "^2.5.4",
"standard": "^8.5.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"depcheck": "dependency-check ./package.json --entry api-errors.js",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"lint": "standard",
"posttest": "yarn run lint && yarn run depcheck",
"prebuild": "yarn run clean",
"predev": "yarn run clean",
"prepublish": "yarn run build"
},
"babel": {
"plugins": [
"lodash"
],
"presets": [
[
"env",
{
"targets": {
"browsers": "> 1%",
"node": 4
}
}
],
"stage-0"
]
},
"standard": {
"ignore": [
"dist"
],
"parser": "babel-eslint"
}
}

View File

@@ -0,0 +1,166 @@
import { BaseError } from 'make-error'
import { isArray, iteratee } from 'lodash'
class XoError extends BaseError {
constructor ({ code, message, data }) {
super(message)
this.code = code
this.data = data
}
toJsonRpcError () {
return {
message: this.message,
code: this.code,
data: this.data
}
}
}
const create = (code, getProps) => {
const factory = args => new XoError({ ...getProps(args), code })
factory.is = (error, predicate) =>
error.code === code && iteratee(predicate)(error)
return factory
}
// =============================================================================
export const notImplemented = create(0, () => ({
message: 'not implemented'
}))
export const noSuchObject = create(1, (id, type) => ({
data: { id, type },
message: 'no such object'
}))
export const unauthorized = create(2, () => ({
message: 'not authenticated or not enough permissions'
}))
export const invalidCredentials = create(3, () => ({
message: 'invalid credentials'
}))
// Deprecated alreadyAuthenticated (4)
export const forbiddenOperation = create(5, (operation, reason) => ({
data: { operation, reason },
message: `forbidden operation: ${operation}`
}))
// Deprecated GenericError (6)
export const noHostsAvailable = create(7, () => ({
message: 'no hosts available'
}))
export const authenticationFailed = create(8, () => ({
message: 'authentication failed'
}))
export const serverUnreachable = create(9, objectId => ({
data: {
objectId
},
message: 'server unreachable'
}))
export const invalidParameters = create(10, (message, errors) => {
if (isArray(message)) {
errors = message
message = undefined
}
return {
data: { errors },
message: message || 'invalid parameters'
}
})
export const vmMissingPvDrivers = create(11, ({ vm }) => ({
data: {
objectId: vm
},
message: 'missing PV drivers'
}))
export const vmIsTemplate = create(12, ({ vm }) => ({
data: {
objectId: vm
},
message: 'VM is a template'
}))
// TODO: We should probably create a more generic error which gathers all incorrect state errors.
// e.g.:
// incorrectState {
// data: {
// objectId: 'af43e227-3deb-4822-a79b-968825de72eb',
// property: 'power_state',
// actual: 'Running',
// expected: 'Halted'
// },
// message: 'incorrect state'
// }
export const vmBadPowerState = create(13, ({ vm, expected, actual }) => ({
data: {
objectId: vm,
expected,
actual
},
message: `VM state is ${actual} but should be ${expected}`
}))
export const vmLacksFeature = create(14, ({ vm, feature }) => ({
data: {
objectId: vm,
feature
},
message: `VM lacks feature ${feature || ''}`
}))
export const notSupportedDuringUpgrade = create(15, () => ({
message: 'not supported during upgrade'
}))
export const objectAlreadyExists = create(16, ({ objectId, objectType }) => ({
data: {
objectId,
objectType
},
message: `${objectType || 'object'} already exists`
}))
export const vdiInUse = create(17, ({ vdi, operation }) => ({
data: {
objectId: vdi,
operation
},
message: 'VDI in use'
}))
export const hostOffline = create(18, ({ host }) => ({
data: {
objectId: host
},
message: 'host offline'
}))
export const operationBlocked = create(19, ({ objectId, code }) => ({
data: {
objectId,
code
},
message: 'operation blocked'
}))
export const patchPrecheckFailed = create(20, ({ errorType, patch }) => ({
data: {
objectId: patch,
errorType
},
message: `patch precheck failed: ${errorType}`
}))

2580
packages/xo-common/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/test/
/tests/
*.spec.js
*.spec.js.map

168
packages/xo-lib/README.md Normal file
View File

@@ -0,0 +1,168 @@
# xo-lib [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
> Library to connect to XO-Server.
## Install
Installation of the [npm package](https://npmjs.org/package/xo-lib):
```
npm install --save xo-lib
```
Then require the package:
```javascript
import Xo from 'xo-lib'
```
## Usage
> If the URL is not provided and the current environment is a web
> browser, the location of the current page will be used.
```javascript
// Connect to XO.
const xo = new Xo({ url: 'https://xo.company.tld' })
// Let's start by opening the connection.
await xo.open()
// Must sign in before being able to call any methods (all calls will
// be buffered until signed in).
await xo.signIn({
email: 'admin@admin.net',
password: 'admin'
})
console('signed as', xo.user)
```
The credentials can also be passed directly to the constructor:
```javascript
const xo = Xo({
url: 'https://xo.company.tld',
credentials: {
email: 'admin@admin.net',
password: 'admin',
}
})
xo.open()
xo.on('authenticated', () => {
console.log(xo.user)
})
```
> If the URL is not provided and the current environment is a web
> browser, the location of the current page will be used.
### Connection
```javascript
await xo.open()
console.log('connected')
```
### Disconnection
```javascript
xo.close()
console.log('disconnected')
```
### Method call
```javascript
const token = await xo.call('token.create')
console.log('Token created', token)
```
### Status
The connection status is available through the status property which
is *open*, *connecting* or *closed*.
```javascript
console.log('%s to xo-server', xo.status)
```
### Current user
Information about the user account used to sign in is available
through the `user` property.
```javascript
console.log('Current user is', xo.user)
```
> This property is null when the status is not connected.
### Events
```javascript
xo.on('open', () => {
console.log('connected')
})
```
```javascript
xo.on('closed', () => {
console.log('disconnected')
})
```
```javascript
xo.on('notification', function (notif) {
console.log('notification:', notif.method, notif.params)
})
```
```javascript
xo.on('authenticated', () => {
console.log('authenticated as', xo.user)
})
xo.on('authenticationFailure', () => {
console.log('failed to authenticate')
})
```
## Development
```
# Install dependencies
> npm install
# Run the tests
> npm test
# Continuously compile
> npm run dev
# Continuously run the tests
> npm run dev-test
# Build for production (automatically called by npm install)
> npm run build
```
## Contributions
Contributions are *very* welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xo-web/issues)
you've encountered;
- fork and create a pull request.
## License
ISC © [Vates SAS](http://vates.fr)

View File

@@ -0,0 +1,45 @@
'use strict'
process.on('unhandledRejection', function (error) {
console.log(error)
})
var Xo = require('./').default
var xo = new Xo({
url: 'localhost:9000'
})
xo.open().then(function () {
return xo.call('acl.get', {}).then(function (result) {
console.log('success:', result)
}).catch(function (error) {
console.log('failure:', error)
})
}).then(function () {
return xo.signIn({
email: 'admin@admin.net',
password: 'admin'
}).then(function () {
console.log('connected as ', xo.user)
}).catch(function (error) {
console.log('failure:', error)
})
}).then(function () {
return xo.signIn({
email: 'tom',
password: 'tom'
}).then(function () {
console.log('connected as', xo.user)
return xo.call('acl.get', {}).then(function (result) {
console.log('success:', result)
}).catch(function (error) {
console.log('failure:', error)
})
}).catch(function (error) {
console.log('failure', error)
})
}).then(function () {
return xo.close()
})

View File

@@ -0,0 +1,80 @@
{
"name": "xo-lib",
"version": "0.8.2",
"license": "ISC",
"description": "Library to connect to XO-Server",
"keywords": [
"xen",
"orchestra",
"xen-orchestra"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-lib",
"bugs": "https://github.com/vatesfr/xo-web/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@vates.fr"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"engines": {
"node": ">=4"
},
"dependencies": {
"jsonrpc-websocket-client": "^0.1.2",
"lodash": "^4.17.2",
"make-error": "^1.0.4"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-eslint": "^7.1.1",
"babel-plugin-lodash": "^3.2.9",
"babel-preset-env": "^1.0.1",
"babel-preset-stage-0": "^6.16.0",
"cross-env": "^3.1.3",
"dependency-check": "^2.6.0",
"rimraf": "^2.5.4",
"standard": "^8.5.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"depcheck": "dependency-check ./package.json",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"lint": "standard",
"posttest": "yarn run lint && yarn run depcheck",
"prebuild": "yarn run clean",
"predev": "yarn run clean",
"prepublish": "yarn run build"
},
"babel": {
"plugins": [
"lodash"
],
"presets": [
[
"env",
{
"targets": {
"browsers": "> 2%",
"node": 4
}
}
],
"stage-0"
]
},
"standard": {
"ignore": [
"dist"
],
"parser": "babel-eslint"
}
}

View File

@@ -0,0 +1,83 @@
import JsonRpcWebSocketClient, {
OPEN,
CLOSED
} from 'jsonrpc-websocket-client'
import { BaseError } from 'make-error'
import { startsWith } from 'lodash'
// ===================================================================
const noop = () => {}
// ===================================================================
export class XoError extends BaseError {}
// -------------------------------------------------------------------
export default class Xo extends JsonRpcWebSocketClient {
constructor (opts) {
const url = opts && opts.url || '.'
super(`${url === '/' ? '' : url}/api/`)
this._credentials = opts && opts.credentials || null
this._user = null
this.on(OPEN, () => {
if (this._credentials) {
this._signIn(this._credentials).catch(noop)
}
})
this.on(CLOSED, () => {
this._user = null
})
}
get user () {
return this._user
}
call (method, args, i) {
if (startsWith(method, 'session.')) {
return Promise.reject(
new XoError('session.*() methods are disabled from this interface')
)
}
const promise = super.call(method, args)
promise.retry = (predicate) => promise.catch((error) => {
i = (i || 0) + 1
if (predicate(error, i)) {
return this.call(method, args, i)
}
})
return promise
}
refreshUser () {
return super.call('session.getUser').then(user => {
return (this._user = user)
})
}
signIn (credentials) {
// Register this credentials for future use.
this._credentials = credentials
return this._signIn(credentials)
}
_signIn (credentials) {
return super.call('session.signIn', credentials).then(
user => {
this._user = user
this.emit('authenticated')
},
error => {
this.emit('authenticationFailure', error)
throw error
}
)
}
}

2619
packages/xo-lib/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
/benchmark/
/benchmarks/
*.bench.js
*.bench.js.map
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/fixture/
/fixtures/
*.fixture.js
*.fixture.js.map
*.fixtures.js
*.fixtures.js.map
/test/
/tests/
*.spec.js
*.spec.js.map
__snapshots__/

View File

@@ -0,0 +1,49 @@
# ${pkg.name} [![Build Status](https://travis-ci.org/${pkg.shortGitHubPath}.png?branch=master)](https://travis-ci.org/${pkg.shortGitHubPath})
> ${pkg.description}
## Install
Installation of the [npm package](https://npmjs.org/package/${pkg.name}):
```
> npm install --save ${pkg.name}
```
## Usage
**TODO**
## Development
```
# Install dependencies
> npm install
# Run the tests
> npm test
# Continuously compile
> npm run dev
# Continuously run the tests
> npm run dev-test
# Build for production (automatically called by npm install)
> npm run build
```
## Contributions
Contributions are *very* welcomed, either on the documentation or on
the code.
You may:
- report any [issue](${pkg.bugs})
you've encountered;
- fork and create a pull request.
## License
${pkg.license} © [${pkg.author.name}](${pkg.author.url})

View File

@@ -0,0 +1,81 @@
{
"name": "xo-remote-parser",
"version": "0.3.0",
"license": "AGPL-3.0",
"description": "",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-remote-parser",
"bugs": "https://github.com/vatesfr/xo-web/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Fabrice Marsaud",
"email": "fabrice.marsaud@vates.fr"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"engines": {
"node": ">=4"
},
"dependencies": {
"lodash": "^4.13.1"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-eslint": "^7.1.1",
"babel-plugin-lodash": "^3.2.11",
"babel-preset-env": "^1.1.7",
"babel-preset-stage-3": "^6.17.0",
"cross-env": "^3.1.4",
"deep-freeze": "^0.0.1",
"dependency-check": "^2.7.0",
"jest": "^18.1.0",
"rimraf": "^2.5.4",
"standard": "^8.6.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"dev-test": "jest --bail --watch",
"posttest": "standard && dependency-check ./package.json",
"prebuild": "rimraf dist/",
"predev": "yarn run prebuild",
"prepublish": "yarn run build",
"test": "jest"
},
"babel": {
"plugins": [
"lodash"
],
"presets": [
[
"env",
{
"targets": {
"browsers": "> 5%",
"node": 4
}
}
],
"stage-3"
]
},
"jest": {
"testPathDirs": [
"<rootDir>/src"
],
"testRegex": "\\.spec\\.js$"
},
"standard": {
"ignore": [
"dist"
],
"parser": "babel-eslint"
}
}

View File

@@ -0,0 +1,57 @@
import filter from 'lodash/filter'
import map from 'lodash/map'
import trim from 'lodash/trim'
import trimStart from 'lodash/trimStart'
const sanitizePath = (...paths) => filter(map(paths, s => s && filter(map(s.split('/'), trim)).join('/'))).join('/')
export const parse = string => {
const object = { }
const [type, rest] = string.split('://')
if (type === 'file') {
object.type = 'file'
object.path = `/${trimStart(rest, '/')}` // the leading slash has been forgotten on client side first implementation
} else if (type === 'nfs') {
object.type = 'nfs'
const [host, path] = rest.split(':')
object.host = host
object.path = `/${trimStart(path, '/')}` // takes care of a missing leading slash coming from previous version format
} else if (type === 'smb') {
object.type = 'smb'
const lastAtSign = rest.lastIndexOf('@')
const smb = rest.slice(lastAtSign + 1)
const auth = rest.slice(0, lastAtSign)
const firstColon = auth.indexOf(':')
const username = auth.slice(0, firstColon)
const password = auth.slice(firstColon + 1)
const [domain, sh] = smb.split('\\\\')
const [host, path] = sh.split('\0')
object.host = host
object.path = path
object.domain = domain
object.username = username
object.password = password
}
return object
}
export const format = ({type, host, path, username, password, domain}) => {
type === 'local' && (type = 'file')
let string = `${type}://`
if (type === 'nfs') {
string += `${host}:`
}
if (type === 'smb') {
string += `${username}:${password}@${domain}\\\\${host}`
}
path = sanitizePath(path)
if (type === 'smb') {
path = path.split('/')
path = '\0' + path.join('\\') // FIXME saving with the windows fashion \ was a bad idea :,(
} else {
path = `/${path}`
}
string += path
return string
}

View File

@@ -0,0 +1,89 @@
/* eslint-env jest */
import deepFreeze from 'deep-freeze'
import { parse, format } from './'
// ===================================================================
// Data used for both parse and format (i.e. correctly formatted).
const data = deepFreeze({
file: {
string: 'file:///var/lib/xoa/backup',
object: {
type: 'file',
path: '/var/lib/xoa/backup'
}
},
SMB: {
string: 'smb://Administrator:pas:sw@ord@toto\\\\192.168.100.225\\smb\0',
object: {
type: 'smb',
host: '192.168.100.225\\smb',
path: '',
domain: 'toto',
username: 'Administrator',
password: 'pas:sw@ord'
}
},
NFS: {
string: 'nfs://192.168.100.225:/media/nfs',
object: {
type: 'nfs',
host: '192.168.100.225',
path: '/media/nfs'
}
}
})
const parseData = deepFreeze({
...data,
'file with missing leading slash (#7)': {
string: 'file://var/lib/xoa/backup',
object: {
type: 'file',
path: '/var/lib/xoa/backup'
}
},
'nfs with missing leading slash': {
string: 'nfs://192.168.100.225:media/nfs',
object: {
type: 'nfs',
host: '192.168.100.225',
path: '/media/nfs'
}
}
})
const formatData = deepFreeze({
...data,
'file with local type': {
string: 'file:///var/lib/xoa/backup',
object: {
type: 'local',
path: '/var/lib/xoa/backup'
}
}
})
// -------------------------------------------------------------------
describe('format', () => {
for (const name in formatData) {
const datum = formatData[name]
it(name, () => {
expect(format(datum.object)).toBe(datum.string)
})
}
})
describe('parse', () => {
for (const name in parseData) {
const datum = parseData[name]
it(name, () => {
expect(parse(datum.string)).toEqual(datum.object)
})
}
})

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
Error.stackTraceLimit = 100
try { require('trace') } catch (_) {}
try { require('clarify') } catch (_) {}
try { require('source-map-support/register') } catch (_) {}

View File

@@ -0,0 +1 @@
--require ./.mocha.js

View File

@@ -0,0 +1,10 @@
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/test/
/tests/
*.spec.js
*.spec.js.map

View File

@@ -0,0 +1,64 @@
# xo-server-auth-github [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
> GitHub authentication plugin for XO-Server
This plugin allows GitHub users to authenticate to Xen-Orchestra.
The first time a user signs in, XO will create a new XO user with the
same identifier.
## Install
Installation of the [npm package](https://npmjs.org/package/xo-server-auth-github):
```
> npm install --global xo-server-auth-github
```
## Usage
> This plugin is based on [passport-github](https://github.com/jaredhanson/passport-github),
> see [its documentation](https://github.com/jaredhanson/passport-github#configure-strategy)
> for more information about the configuration.
Like all other xo-server plugins, it can be configured directly via
the web iterface, see [the plugin documentation](https://xen-orchestra.com/docs/plugins.html).
![Registering XO instance in GitHub](github.png)
## Development
### Installing dependencies
```
> npm install
```
### Compilation
The sources files are watched and automatically recompiled on changes.
```
> npm run dev
```
### Tests
```
> npm run test-dev
```
## Contributions
Contributions are *very* welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xo-web/issues)
you've encountered;
- fork and create a pull request.
## License
AGPL3 © [Vates SAS](http://vates.fr)

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,74 @@
{
"name": "xo-server-auth-github",
"version": "0.2.1",
"license": "AGPL-3.0",
"description": "GitHub authentication plugin for XO-Server",
"keywords": [
"xo-server",
"xo-server",
"authentication",
"github"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-auth-github",
"bugs": "https://github.com/vatesfr/xo-web/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@isonoe.net"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"engines": {
"node": ">=4"
},
"dependencies": {
"babel-runtime": "^6.11.6",
"passport-github": "^1.1.0"
},
"devDependencies": {
"babel-cli": "^6.16.0",
"babel-eslint": "^7.0.0",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-preset-latest": "^6.16.0",
"babel-preset-stage-0": "^6.16.0",
"clarify": "^2.0.0",
"dependency-check": "^2.6.0",
"mocha": "^3.1.0",
"must": "^0.13.2",
"source-map-support": "^0.4.3",
"standard": "^8.2.0",
"trace": "^2.3.3"
},
"scripts": {
"build": "NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"depcheck": "dependency-check ./package.json",
"dev": "NODE_DEV=development babel --watch --source-maps --out-dir=dist/ src/",
"dev-test": "mocha --opts .mocha.opts --watch --reporter=min \"dist/**/*.spec.js\"",
"lint": "standard",
"posttest": "yarn run lint && yarn run depcheck",
"prepublish": "yarn run build",
"test": "mocha --opts .mocha.opts \"dist/**/*.spec.js\""
},
"babel": {
"plugins": [
"transform-runtime"
],
"presets": [
"latest",
"stage-0"
]
},
"standard": {
"ignore": [
"dist"
],
"parser": "babel-eslint"
}
}

View File

@@ -0,0 +1,44 @@
import {Strategy} from 'passport-github'
// ===================================================================
export const configurationSchema = {
type: 'object',
properties: {
clientID: {
type: 'string'
},
clientSecret: {
type: 'string'
}
},
required: ['clientID', 'clientSecret']
}
// ===================================================================
class AuthGitHubXoPlugin {
constructor (xo) {
this._xo = xo
}
configure (conf) {
this._conf = conf
}
load () {
const {_xo: xo} = this
xo.registerPassportStrategy(new Strategy(this._conf, async (accessToken, refreshToken, profile, done) => {
try {
done(null, await xo.registerUser('github', profile.username))
} catch (error) {
done(error.message)
}
}))
}
}
// ===================================================================
export default ({xo}) => new AuthGitHubXoPlugin(xo)

View File

@@ -0,0 +1,17 @@
/* eslint-env mocha */
import expect from 'must'
// ===================================================================
import myLib from './'
// ===================================================================
describe.skip('myLib', () => {
it('does something', () => {
// TODO: some real tests.
expect(myLib).to.exists()
})
})

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1 @@
/bower_components/
/dist/
npm-debug.log
!node_modules/*
node_modules/*/
/ldap.cache.conf

View File

@@ -1,2 +1,24 @@
/benchmark/
/benchmarks/
*.bench.js
*.bench.js.map
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/fixture/
/fixtures/
*.fixture.js
*.fixture.js.map
*.fixtures.js
*.fixtures.js.map
/test/
/tests/
*.spec.js
*.spec.js.map
__snapshots__/

View File

@@ -1,5 +0,0 @@
language: node_js
node_js:
- 'iojs'
- '0.12'
- '0.10'

View File

@@ -1,4 +1,4 @@
# xo-server-auth-ldap [![Build Status](https://travis-ci.org/vatesfr/xo-server-auth-ldap.png?branch=master)](https://travis-ci.org/vatesfr/xo-server-auth-ldap)
# xo-server-auth-ldap [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
> LDAP authentication plugin for XO-Server
@@ -12,64 +12,31 @@ same identifier.
Installation of the [npm package](https://npmjs.org/package/xo-server-auth-ldap):
```
> npm install xo-server-auth-ldap
> npm install --global xo-server-auth-ldap
```
## Usage
To enable this plugin you have to add it into the configuration file
of XO-Server:
Like all other xo-server plugins, it can be configured directly via
the web iterface, see [the plugin documentation](https://xen-orchestra.com/docs/plugins.html).
```yaml
plugins:
If you have issues, you can use the provided CLI to gather more
information:
auth-ldap:
uri: "ldap://ldap.example.org"
# Path to CA certificates to use when connecting to SSL-secured
# LDAP servers.
#
# If not specified, it will use a default set of well-known CAs.
certificateAuthorities:
- /path/to/ca_cert.pem
- /path/to/another/ca_cert.pem
# Check the validity of the server's certificate. Useful when
# connecting to servers that use a self-signed certificate.
#
# Default to true
checkCertificate: true
# Credentials to use before looking for the user record.
#
# Default to anonymous.
bind:
# Distinguished name of the user permitted to search the LDAP
# directory for the user to authenticate.
#
# For Microsoft Active Directory, it can also be
# '<user>@<domain>'
dn: 'cn=admin,ou=people,dc=example,dc=org'
# Password of the user permitted to search the LDAP directory.
password: 'secret'
# The base is the part of the directory tree where the users are
# looked for.
base: "ou=people,dc=example,dc=org"
# Filter used to find the user.
#
# For Microsoft Active Directory, you can try one of the following
# filters:
#
# - '(cn={{name}})'
# - '(sAMAccountName={{name}}@<domain>)'
# - '(userPrincipalName={{name}})'
#
# Default is '(uid={{name}})'
#filter: '(uid={{name}})'
```
> xo-server-auth-ldap
? uri ldap://ldap.company.net
? fill optional certificateAuthorities? No
? fill optional checkCertificate? No
? fill optional bind? No
? base ou=people,dc=company,dc=net
? fill optional filter? No
configuration saved in ./ldap.cache.conf
? Username john.smith
? Password *****
searching for entries...
0 entries found
could not authenticate john.smith
```
## Algorithm
@@ -82,24 +49,21 @@ plugins:
## Development
### Installing dependencies
```
# Install dependencies
> npm install
```
### Compilation
# Run the tests
> npm test
The sources files are watched and automatically recompiled on changes.
```
# Continuously compile
> npm run dev
```
### Tests
# Continuously run the tests
> npm run dev-test
```
> npm run test-dev
# Build for production (automatically called by npm install)
> npm run build
```
## Contributions
@@ -109,7 +73,7 @@ the code.
You may:
- report any [issue](https://github.com/vatesfr/xo-server-auth-ldap/issues)
- report any [issue](https://github.com/vatesfr/xo-web/issues)
you've encountered;
- fork and create a pull request.

View File

@@ -1,51 +1,97 @@
{
"name": "xo-server-auth-ldap",
"version": "0.3.0",
"license": "AGPL3",
"version": "0.6.2",
"license": "AGPL-3.0",
"description": "LDAP authentication plugin for XO-Server",
"keywords": [
"xo-server",
"ldap"
"ldap",
"orchestra",
"plugin",
"xen",
"xen-orchestra",
"xo-server"
],
"homepage": "https://github.com/vatesfr/xo-server-auth-ldap",
"bugs": "https://github.com/vatesfr/xo-server-auth-ldap/issues",
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-auth-ldap",
"bugs": "https://github.com/vatesfr/xo-web/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xo-server-auth-ldap"
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@isonoe.net"
"email": "julien.fontanet@vates.fr"
},
"preferGlobal": false,
"main": "dist/",
"bin": {
"xo-server-auth-ldap": "dist/test-cli.js"
},
"files": [
"dist/"
],
"engines": {
"node": ">=4"
},
"dependencies": {
"babel-runtime": "^4",
"bluebird": "^2.9.21",
"event-to-promise": "^0.3.2",
"ldapjs": "^0.7.1"
"babel-runtime": "^6.22.0",
"event-to-promise": "^0.7.0",
"exec-promise": "^0.6.1",
"inquirer": "^3.0.1",
"ldapjs": "^1.0.1",
"lodash": "^4.17.4",
"promise-toolbox": "^0.8.0"
},
"devDependencies": {
"babel": "^4",
"mocha": "*",
"must": "*",
"sinon": "*",
"source-map-support": "^0.2.10",
"standard": "*"
"babel-cli": "^6.22.2",
"babel-eslint": "^7.1.1",
"babel-plugin-lodash": "^3.2.11",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-env": "^1.1.8",
"babel-preset-stage-3": "^6.22.0",
"cross-env": "^3.1.4",
"dependency-check": "^2.8.0",
"jest": "^18.1.0",
"rimraf": "^2.5.4",
"standard": "^8.6.0"
},
"scripts": {
"build": "mkdir -p dist && babel --optional=runtime --compact=true --source-maps --out-dir=dist/ src/",
"dev": "mkdir -p dist && babel --watch --optional=runtime --compact=true --source-maps --out-dir=dist/ src/",
"prepublish": "npm build",
"test": "standard && npm run build && mocha 'dist/**/*.spec.js'",
"test-dev": "standard && mocha --watch --reporter=min 'dist/**/*.spec.js'"
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"commitmsg": "npm test",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"dev-test": "jest --bail --watch",
"posttest": "standard && dependency-check ./package.json",
"prebuild": "rimraf dist/",
"predev": "npm run prebuild",
"prepublish": "npm run build",
"test": "jest"
},
"babel": {
"plugins": [
"lodash",
"transform-runtime"
],
"presets": [
[
"env",
{
"targets": {
"node": 4
}
}
],
"stage-3"
]
},
"jest": {
"testPathDirs": [
"<rootDir>/src"
],
"testRegex": "\\.spec\\.js$"
},
"standard": {
"ignore": [
"dist/**"
]
"dist"
],
"parser": "babel-eslint"
}
}

View File

@@ -1,31 +1,117 @@
/* eslint no-lone-blocks: 0, no-throw-literal: 0 */
/* eslint no-throw-literal: 0 */
import Bluebird, {coroutine, promisify} from 'bluebird'
import eventToPromise from 'event-to-promise'
import {createClient} from 'ldapjs'
import {escape} from 'ldapjs/lib/filters/escape'
import {readFileSync} from 'fs'
import { bind, noop } from 'lodash'
import { createClient } from 'ldapjs'
import { escape } from 'ldapjs/lib/filters/escape'
import { promisify } from 'promise-toolbox'
import { readFile } from 'fs'
// ===================================================================
const VAR_RE = /\{\{([^}]+)\}\}/g
function evalFilter (filter, vars) {
return filter.replace(VAR_RE, (_, name) => {
const value = vars[name]
const evalFilter = (filter, vars) => filter.replace(VAR_RE, (_, name) => {
const value = vars[name]
if (value === undefined) {
throw new Error('invalid variable: ' + name)
if (value === undefined) {
throw new Error('invalid variable: ' + name)
}
return escape(value)
})
export const configurationSchema = {
type: 'object',
properties: {
uri: {
description: 'URI of the LDAP server.',
type: 'string'
},
certificateAuthorities: {
description: `
Paths to CA certificates to use when connecting to SSL-secured LDAP servers.
If not specified, it will use a default set of well-known CAs.
`.trim(),
type: 'array',
items: {
type: 'string'
}
},
checkCertificate: {
description: 'Enforce the validity of the server\'s certificates. You can disable it when connecting to servers that use a self-signed certificate.',
type: 'boolean',
default: true
},
bind: {
description: 'Credentials to use before looking for the user record.',
type: 'object',
properties: {
dn: {
description: `
Full distinguished name of the user permitted to search the LDAP directory for the user to authenticate.
Example: uid=xoa-auth,ou=people,dc=company,dc=net
For Microsoft Active Directory, it can also be \`<user>@<domain>\`.
`.trim(),
type: 'string'
},
password: {
description: 'Password of the user permitted of search the LDAP directory.',
type: 'string'
}
},
required: ['dn', 'password']
},
base: {
description: 'The base is the part of the description tree where the users are looked for.',
type: 'string'
},
filter: {
description: `
Filter used to find the user.
For Microsoft Active Directory, you can try one of the following filters:
- \`(cn={{name}})\`
- \`(sAMAccountName={{name}})\`
- \`(sAMAccountName={{name}}@<domain>)\` (replace \`<domain>\` by your own domain)
- \`(userPrincipalName={{name}})\`
`.trim(),
type: 'string',
default: '(uid={{name}})'
}
},
required: ['uri', 'base']
}
return escape(value)
})
export const testSchema = {
type: 'object',
properties: {
username: {
description: 'LDAP username',
type: 'string'
},
password: {
description: 'LDAP password',
type: 'string'
}
},
required: ['username', 'password']
}
// ===================================================================
class AuthLdap {
constructor (conf) {
const clientOpts = {
constructor (xo) {
this._xo = xo
this._authenticate = bind(this._authenticate, this)
}
async configure (conf) {
const clientOpts = this._clientOpts = {
url: conf.uri,
maxConnections: 5,
tlsOptions: {}
@@ -45,79 +131,114 @@ class AuthLdap {
const {tlsOptions} = clientOpts
tlsOptions.rejectUnauthorized = !checkCertificate
tlsOptions.rejectUnauthorized = checkCertificate
if (certificateAuthorities) {
tlsOptions.ca = certificateAuthorities.map(path => readFileSync(path))
tlsOptions.ca = await Promise.all(
certificateAuthorities.map(path => readFile(path))
)
}
}
const {base: searchBase} = conf
const searchFilter = conf.filter || '(uid={{name}})'
const {
bind: credentials,
base: searchBase,
filter: searchFilter = '(uid={{name}})'
} = conf
this._provider = coroutine(function * ({username, password}) {
if (username === undefined || password === undefined) {
throw null
}
this._credentials = credentials
this._searchBase = searchBase
this._searchFilter = searchFilter
}
const client = createClient(clientOpts)
load () {
this._xo.registerAuthenticationProvider(this._authenticate)
}
try {
// Promisify some methods.
const bind = promisify(client.bind, client)
const search = promisify(client.search, client)
unload () {
this._xo.unregisterAuthenticationProvider(this._authenticate)
}
// Bind if necessary.
{
const {bind: credentials} = conf
if (credentials) {
yield bind(credentials.dn, credentials.password)
}
}
// Search for the user.
const entries = []
{
const response = yield search(searchBase, {
scope: 'sub',
filter: evalFilter(searchFilter, {
name: username
})
})
response.on('searchEntry', entry => {
entries.push(entry.json)
})
const {status} = yield eventToPromise(response, 'end')
if (status) {
throw new Error('unexpected search response status: ' + status)
}
}
// Try to find an entry which can be bind with the given password.
for (let entry of entries) {
try {
yield bind(entry.objectName, password)
return { username }
} catch (error) {}
}
throw null
} finally {
client.unbind()
test ({ username, password }) {
return this._authenticate({
username,
password
}).then(result => {
if (result === null) {
throw new Error('could not authenticate user')
}
})
}
load (xo) {
xo.registerAuthenticationProvider(this._provider)
}
async _authenticate ({ username, password }, logger = noop) {
if (username === undefined || password === undefined) {
logger('require `username` and `password` to authenticate!')
unload (xo) {
xo.unregisterAuthenticationProvider(this._provider)
return null
}
const client = createClient(this._clientOpts)
try {
// Promisify some methods.
const bind = promisify(client.bind, client)
const search = promisify(client.search, client)
await eventToPromise(client, 'connect')
// Bind if necessary.
{
const {_credentials: credentials} = this
if (credentials) {
logger(`attempting to bind with as ${credentials.dn}...`)
await bind(credentials.dn, credentials.password)
logger(`successfully bound as ${credentials.dn}`)
}
}
// Search for the user.
const entries = []
{
logger('searching for entries...')
const response = await search(this._searchBase, {
scope: 'sub',
filter: evalFilter(this._searchFilter, {
name: username
})
})
response.on('searchEntry', entry => {
logger('.')
entries.push(entry.json)
})
const {status} = await eventToPromise(response, 'end')
if (status) {
throw new Error('unexpected search response status: ' + status)
}
logger(`${entries.length} entries found`)
}
// Try to find an entry which can be bind with the given password.
for (const entry of entries) {
try {
logger(`attempting to bind as ${entry.objectName}`)
await bind(entry.objectName, password)
logger(`successfully bound as ${entry.objectName} => ${username} authenticated`)
return { username }
} catch (error) {
logger(`failed to bind as ${entry.objectName}: ${error.message}`)
}
}
logger(`could not authenticate ${username}`)
return null
} finally {
client.unbind()
}
}
}
// ===================================================================
export default (conf) => new AuthLdap(conf)
export default ({xo}) => new AuthLdap(xo)

View File

@@ -1,49 +0,0 @@
/* eslint-env mocha */
import authLdap from './'
import expect from 'must'
import {assert, spy} from 'sinon'
import sourceMapSupport from 'source-map-support'
sourceMapSupport.install()
// ===================================================================
describe('authLdap()', function () {
before(function () {
this.instance = authLdap({})
this.xo = {
registerAuthenticationProvider: spy(),
unregisterAuthenticationProvider: spy()
}
})
it('is a function', function () {
expect(authLdap).to.be.a.function()
})
it('returns an object', function () {
expect(this.instance).to.be.an.object()
})
it('#load(xo) register the auth provider', function () {
this.instance.load(this.xo)
const spy = this.xo.registerAuthenticationProvider
assert.calledOnce(spy)
this.provider = spy.args[0][0]
expect(this.provider).to.be.a.function()
})
it('#unload(xo) unregister the auth provider', function () {
this.instance.unload(this.xo)
const spy = this.xo.unregisterAuthenticationProvider
assert.calledOnce(spy)
expect(spy.args[0][0]).to.be(this.provider)
})
})

View File

@@ -0,0 +1,166 @@
import { forEach, isFinite, isInteger } from 'lodash'
import { forOwn as forOwnAsync } from 'promise-toolbox'
import { prompt } from 'inquirer'
// ===================================================================
const EMPTY_OBJECT = Object.freeze({ __proto__: null })
const _extractValue = ({ value }) => value
export const confirm = (message, {
default: defaultValue = null
} = EMPTY_OBJECT) => prompt({
default: defaultValue,
message,
name: 'value',
type: 'confirm'
}).then(_extractValue)
export const input = (message, {
default: defaultValue = null,
filter = undefined,
validate = undefined
} = EMPTY_OBJECT) => prompt({
default: defaultValue,
message,
name: 'value',
type: 'input',
validate
}).then(_extractValue)
export const list = (message, choices, {
default: defaultValue = null
} = EMPTY_OBJECT) => prompt({
default: defaultValue,
choices,
message,
name: 'value',
type: 'list'
}).then(_extractValue)
export const password = (message, {
default: defaultValue = null,
filter = undefined,
validate = undefined
} = EMPTY_OBJECT) => prompt({
default: defaultValue,
message,
name: 'value',
type: 'password',
validate
}).then(_extractValue)
// ===================================================================
const promptByType = {
__proto__: null,
array: async (schema, defaultValue, path) => {
const items = []
if (defaultValue == null) {
defaultValue = items
}
let i = 0
const itemSchema = schema.items
const promptItem = async () => {
items[i] = await promptGeneric(
itemSchema,
defaultValue[i],
path
? `${path} [${i}]`
: `[${i}]`
)
++i
}
let n = schema.minItems || 0
while (i < n) { // eslint-disable-line no-unmodified-loop-condition
await promptItem()
}
n = schema.maxItems || Infinity
while (
i < n && // eslint-disable-line no-unmodified-loop-condition
await confirm('additional item?', {
default: false
})
) {
await promptItem()
}
return items
},
boolean: (schema, defaultValue, path) => confirm(path, {
default: defaultValue != null ? defaultValue : schema.default
}),
enum: (schema, defaultValue, path) => list(path, schema.enum, {
defaultValue: defaultValue || schema.defaultValue
}),
integer: (schema, defaultValue, path) => input(path, {
default: defaultValue || schema.default,
filter: input => +input,
validate: input => isInteger(+input)
}),
number: (schema, defaultValue, path) => input(path, {
default: defaultValue || schema.default,
filter: input => +input,
validate: input => isFinite(+input)
}),
object: async (schema, defaultValue, path) => {
const value = {}
const required = {}
schema.required && forEach(schema.required, name => {
required[name] = true
})
const promptProperty = async (schema, name) => {
const subpath = path
? `${path} > ${schema.title || name}`
: schema.title || name
if (
required[name] ||
await confirm(`fill optional ${subpath}?`, {
default: Boolean(defaultValue && name in defaultValue)
})
) {
value[name] = await promptGeneric(
schema,
defaultValue && defaultValue[name],
subpath
)
}
}
await forOwnAsync.call(schema.properties || {}, promptProperty)
return value
},
string: (schema, defaultValue, path) => input(path, {
default: defaultValue || schema.default
})
}
export default function promptGeneric (schema, defaultValue, path) {
const type = schema.enum
? 'enum'
: schema.type
const prompt = promptByType[type.toLowerCase()]
if (!prompt) {
throw new Error(`unsupported type: ${type}`)
}
return prompt(schema, defaultValue, path)
}

View File

@@ -0,0 +1,49 @@
#!/usr/bin/env node
import execPromise from 'exec-promise'
import { bind } from 'lodash'
import { fromCallback } from 'promise-toolbox'
import { readFile, writeFile } from 'fs'
import promptSchema, {
input,
password
} from './prompt-schema'
import createPlugin, {
configurationSchema
} from './'
// ===================================================================
const CACHE_FILE = './ldap.cache.conf'
// -------------------------------------------------------------------
execPromise(async args => {
const config = await promptSchema(
configurationSchema,
await fromCallback(cb => readFile(CACHE_FILE, 'utf-8', cb)).then(
JSON.parse,
() => ({})
)
)
await fromCallback(cb => writeFile(CACHE_FILE, JSON.stringify(config, null, 2), cb)).then(
() => {
console.log('configuration saved in %s', CACHE_FILE)
},
error => {
console.warn('failed to save configuration in %s', CACHE_FILE)
console.warn(error.message)
}
)
const plugin = createPlugin({})
await plugin.configure(config)
await plugin._authenticate({
username: await input('Username', {
validate: input => !!input.length
}),
password: await password('Password')
}, bind(console.log, console))
})

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/test/
/tests/
*.spec.js
*.spec.js.map

View File

@@ -0,0 +1,63 @@
# xo-server-auth-saml [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
> SAML authentication plugin for XO-Server
This plugin allows SAML users to authenticate to Xen-Orchestra.
The first time a user signs in, XO will create a new XO user with the
same identifier.
## Install
Installation of the [npm package](https://npmjs.org/package/xo-server-auth-saml):
```
> npm install --global xo-server-auth-saml
```
## Usage
> This plugin is based on [passport-saml](https://github.com/bergie/passport-saml),
> see [its documentation](https://github.com/bergie/passport-saml#configure-strategy)
> for more information about the configuration.
Like all other xo-server plugins, it can be configured directly via
the web iterface, see [the plugin documentation](https://xen-orchestra.com/docs/plugins.html).
> Important: When registering your instance to your identity provider,
> you must configure its callback URL to
> `http://xo.company.net/signin/saml/callback`!
## Development
```
# Install dependencies
> npm install
# Run the tests
> npm test
# Continuously compile
> npm run dev
# Continuously run the tests
> npm run dev-test
# Build for production (automatically called by npm install)
> npm run build
```
## Contributions
Contributions are *very* welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xo-web/issues)
you've encountered;
- fork and create a pull request.
## License
AGPL3 © [Vates SAS](http://vates.fr)

View File

@@ -0,0 +1,75 @@
{
"name": "xo-server-auth-saml",
"version": "0.4.1",
"license": "AGPL-3.0",
"description": "SAML authentication plugin for XO-Server",
"keywords": [
"authentication",
"orchestra",
"plugin",
"saml",
"xen",
"xen-orchestra",
"xo-server"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-auth-saml",
"bugs": "https://github.com/vatesfr/xo-web/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@isonoe.net"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"engines": {
"node": ">=4"
},
"dependencies": {
"babel-runtime": "^6.11.6",
"passport-saml": "^0.15.0"
},
"devDependencies": {
"babel-cli": "^6.14.0",
"babel-eslint": "^7.1.0",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-preset-latest": "^6.16.0",
"babel-preset-stage-0": "^6.5.0",
"cross-env": "^3.1.3",
"dependency-check": "^2.6.0",
"rimraf": "^2.5.4",
"standard": "^8.2.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"depcheck": "dependency-check ./package.json",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"lint": "standard",
"posttest": "yarn run lint && yarn run depcheck",
"prebuild": "yarn run clean",
"predev": "yarn run clean",
"prepublish": "yarn run build"
},
"babel": {
"plugins": [
"transform-runtime"
],
"presets": [
"latest",
"stage-0"
]
},
"standard": {
"ignore": [
"dist"
],
"parser": "babel-eslint"
}
}

View File

@@ -0,0 +1,59 @@
import {Strategy} from 'passport-saml'
// ===================================================================
export const configurationSchema = {
type: 'object',
properties: {
cert: {
type: 'string'
},
entryPoint: {
type: 'string'
},
issuer: {
type: 'string'
},
usernameField: {
type: 'string'
}
},
required: ['cert', 'entryPoint', 'issuer']
}
// ===================================================================
class AuthSamlXoPlugin {
constructor ({ xo }) {
this._conf = null
this._usernameField = null
this._xo = xo
}
configure ({ usernameField, ...conf }) {
this._usernameField = usernameField
this._conf = conf
}
load () {
const xo = this._xo
xo.registerPassportStrategy(new Strategy(this._conf, async (profile, done) => {
const name = profile[this._usernameField]
if (!name) {
done('no name found for this user')
return
}
try {
done(null, await xo.registerUser('saml', name))
} catch (error) {
done(error.message)
}
}))
}
}
// ===================================================================
export default opts => new AuthSamlXoPlugin(opts)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/test/
/tests/
*.spec.js
*.spec.js.map

View File

@@ -0,0 +1,52 @@
# xo-server-backup-reports [![Build Status](https://api.travis-ci.org/vatesfr/xo-server-backup-reports.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
> Backup reports plugin for XO-Server
XO-Server plugin which sends email reports and Xmpp messages when backup jobs are done.
## Install
Installation of the [npm package](https://npmjs.org/package/xo-server-backup-reports):
```
> npm install --global xo-server-backup-reports
```
## Usage
Like all other xo-server plugins, it can be configured directly via
the web iterface, see [the plugin documentation](https://xen-orchestra.com/docs/plugins.html).
## Development
```
# Install dependencies
> npm install
# Run the tests
> npm test
# Continuously compile
> npm run dev
# Continuously run the tests
> npm run dev-test
# Build for production (automatically called by npm install)
> npm run build
```
## Contributions
Contributions are *very* welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xo-web/issues)
you've encountered;
- fork and create a pull request.
## License
AGPL3 © [Vates SAS](http://vates.fr)

View File

@@ -0,0 +1,85 @@
{
"name": "xo-server-backup-reports",
"version": "0.6.0",
"license": "AGPL-3.0",
"description": "Backup reports plugin for XO-Server",
"keywords": [
"backup",
"email",
"mail",
"orchestra",
"plugin",
"report",
"reports",
"xen",
"xen-orchestra",
"xo-server"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-backup-reports",
"bugs": "https://github.com/vatesfr/xo-web/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@isonoe.net"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"engines": {
"node": ">=4"
},
"dependencies": {
"lodash": "^4.13.1",
"moment": "^2.13.0"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-eslint": "^7.1.1",
"babel-plugin-lodash": "^3.2.10",
"babel-preset-env": "^1.0.0",
"babel-preset-stage-0": "^6.16.0",
"cross-env": "^3.1.3",
"dependency-check": "^2.6.0",
"rimraf": "^2.5.4",
"standard": "^8.5.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"depcheck": "dependency-check ./package.json",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"lint": "standard",
"posttest": "yarn run lint && yarn run depcheck",
"prebuild": "yarn run clean",
"predev": "yarn run clean",
"prepublish": "yarn run build"
},
"babel": {
"plugins": [
"lodash"
],
"presets": [
[
"env",
{
"targets": {
"node": 4
}
}
],
"stage-0"
]
},
"standard": {
"ignore": [
"dist"
],
"parser": "babel-eslint"
}
}

View File

@@ -0,0 +1,178 @@
import moment from 'moment'
import { forEach } from 'lodash'
export const configurationSchema = {
type: 'object',
properties: {
toMails: {
type: 'array',
title: 'mails',
description: 'an array of recipients (mails)',
items: {
type: 'string'
},
minItems: 1
},
toXmpp: {
type: 'array',
title: 'xmpp address',
description: 'an array of recipients (xmpp)',
items: {
type: 'string'
},
minItems: 1
}
}
}
// ===================================================================
const logError = e => {
console.error('backup report error:', e)
}
class BackupReportsXoPlugin {
constructor (xo) {
this._xo = xo
this._report = ::this._wrapper
}
configure ({ toMails, toXmpp }) {
this._mailsReceivers = toMails
this._xmppReceivers = toXmpp
}
load () {
this._xo.on('job:terminated', this._report)
}
unload () {
this._xo.removeListener('job:terminated', this._report)
}
_wrapper (status) {
return new Promise(resolve => resolve(this._listener(status))).catch(logError)
}
_listener (status) {
let nSuccess = 0
let nCalls = 0
let reportWhen
const text = []
const nagiosText = []
forEach(status.calls, call => {
// Ignore call if it's not a Backup a Snapshot or a Disaster Recovery.
if (call.method !== 'vm.deltaCopy' &&
call.method !== 'vm.rollingDeltaBackup' &&
call.method !== 'vm.rollingDrCopy' &&
call.method !== 'vm.rollingSnapshot' &&
call.method !== 'vm.rollingBackup') {
return
}
reportWhen = call.params._reportWhen
if (reportWhen === 'never') {
return
}
nCalls++
if (!call.error) {
nSuccess++
}
let vm
try {
vm = this._xo.getObject(call.params.id || call.params.vm)
} catch (e) {}
const start = moment(call.start)
const end = moment(call.end)
const duration = moment.duration(end - start).humanize()
text.push([
`### VM : ${vm ? vm.name_label : 'undefined'}`,
` - UUID: ${vm ? vm.uuid : 'undefined'}`,
call.error
? ` - Status: Failure\n - Error: ${call.error.message}`
: ' - Status: Success',
` - Start time: ${String(start)}`,
` - End time: ${String(end)}`,
` - Duration: ${duration}`
].join('\n'))
if (call.error) {
nagiosText.push(
`[ ${vm ? vm.name_label : 'undefined'} : ${call.error.message} ]`
)
}
})
// No backup calls.
if (nCalls === 0) {
return
}
const globalSuccess = nSuccess === nCalls
if (globalSuccess && (
reportWhen === 'fail' || // xo-web < 5
reportWhen === 'failure' // xo-web >= 5
)) {
return
}
const start = moment(status.start)
const end = moment(status.end)
const duration = moment.duration(end - start).humanize()
let method = status.calls[Object.keys(status.calls)[0]].method
method = method.slice(method.indexOf('.') + 1)
.replace(/([A-Z])/g, ' $1').replace(/^./, letter => letter.toUpperCase()) // humanize
const tag = status.calls[Object.keys(status.calls)[0]].params.tag
// Global status.
text.unshift([
`## Global status for "${tag}" (${method}): ${globalSuccess ? 'Success' : 'Fail'}`,
` - Start time: ${String(start)}`,
` - End time: ${String(end)}`,
` - Duration: ${duration}`,
` - Successful backed up VM number: ${nSuccess}`,
` - Failed backed up VM: ${nCalls - nSuccess}`,
''
].join('\n'))
const markdown = text.join('\n')
const markdownNagios = nagiosText.join(' ')
// TODO : Handle errors when `sendEmail` isn't present. (Plugin dependencies)
const xo = this._xo
return Promise.all([
xo.sendEmail && xo.sendEmail({
to: this._mailsReceivers,
subject: `[Xen Orchestra][${globalSuccess ? 'Success' : 'Failure'}] Backup report for ${tag}`,
markdown
}),
xo.sendToXmppClient && xo.sendToXmppClient({
to: this._xmppReceivers,
message: markdown
}),
xo.sendSlackMessage && xo.sendSlackMessage({
message: markdown
}),
xo.sendPassiveCheck && xo.sendPassiveCheck({
status: globalSuccess ? 0 : 2,
message: globalSuccess ? `[Xen Orchestra] [Success] Backup report for ${tag}` : `[Xen Orchestra] [Failure] Backup report for ${tag} - VMs : ${markdownNagios}`
})
])
}
}
// ===================================================================
export default ({ xo }) => new BackupReportsXoPlugin(xo)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/test/
/tests/
*.spec.js
*.spec.js.map

View File

@@ -0,0 +1,53 @@
# xo-server-load-balancer [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
XO-Server plugin that allows load balancing.
## Install
Go inside your `xo-server` folder and install it:
```
> npm install --global xo-server-load-balancer
```
## Usage
Like all other xo-server plugins, it can be configured directly via
the web iterface, see [the plugin documentation](https://xen-orchestra.com/docs/plugins.html).
## Development
### Installing dependencies
```
> npm install
```
### Compilation
The sources files are watched and automatically recompiled on changes.
```
> npm run dev
```
### Tests
```
> npm run test-dev
```
## Contributions
Contributions are *very* welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xo-web/issues)
you've encountered;
- fork and create a pull request.
## License
AGPL3 © [Vates SAS](http://vates.fr)

View File

@@ -0,0 +1,72 @@
{
"name": "xo-server-load-balancer",
"version": "0.3.1",
"license": "AGPL-3.0",
"description": "Load balancer for XO-Server",
"keywords": [
"load",
"balancer",
"server",
"pool",
"host"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-load-balancer",
"bugs": "https://github.com/vatesfr/xo-web/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@isonoe.net"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"engines": {
"node": ">=4"
},
"dependencies": {
"babel-runtime": "^6.11.6",
"cron": "^1.1.0",
"event-to-promise": "^0.7.0",
"lodash": "^4.16.2"
},
"devDependencies": {
"babel-cli": "^6.16.0",
"babel-eslint": "^7.0.0",
"babel-plugin-lodash": "^3.2.9",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-preset-es2015": "^6.16.0",
"babel-preset-stage-0": "^6.16.0",
"dependency-check": "^2.5.1",
"standard": "^8.2.0"
},
"scripts": {
"build": "NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"depcheck": "dependency-check ./package.json",
"dev": "NODE_DEV=development babel --watch --source-maps --out-dir=dist/ src/",
"lint": "standard",
"posttest": "yarn run lint && yarn run depcheck",
"prepublish": "yarn run build"
},
"babel": {
"plugins": [
"transform-runtime",
"lodash"
],
"presets": [
"es2015",
"stage-0"
]
},
"standard": {
"ignore": [
"dist"
],
"parser": "babel-eslint"
}
}

View File

@@ -0,0 +1,225 @@
import { clone, filter, map as mapToArray } from 'lodash'
import Plan from './plan'
import { debug } from './utils'
// ===================================================================
export default class DensityPlan extends Plan {
_checkRessourcesThresholds (objects, averages) {
return filter(objects, object =>
averages[object.id].memoryFree > this._thresholds.memoryFree.low
)
}
async execute () {
const results = await this._findHostsToOptimize()
if (!results) {
return
}
const {
hosts,
toOptimize
} = results
let {
averages: hostsAverages
} = results
const pools = await this._getPlanPools()
let optimizationsCount = 0
for (const hostToOptimize of toOptimize) {
const {
id: hostId,
$poolId: poolId
} = hostToOptimize
const {
master: masterId
} = pools[poolId]
// Avoid master optimization.
if (masterId === hostId) {
continue
}
// A host to optimize needs the ability to be restarted.
if (hostToOptimize.powerOnMode === '') {
debug(`Host (${hostId}) does not have a power on mode.`)
continue
}
let poolMaster // Pool master.
const poolHosts = [] // Without master.
const masters = [] // Without the master of this loop.
const otherHosts = []
for (const dest of hosts) {
const {
id: destId,
$poolId: destPoolId
} = dest
// Destination host != Host to optimize!
if (destId === hostId) {
continue
}
if (destPoolId === poolId) {
if (destId === masterId) {
poolMaster = dest
} else {
poolHosts.push(dest)
}
} else if (destId === pools[destPoolId].master) {
masters.push(dest)
} else {
otherHosts.push(dest)
}
}
const simulResults = await this._simulate({
host: hostToOptimize,
destinations: [
[ poolMaster ],
poolHosts,
masters,
otherHosts
],
hostsAverages: clone(hostsAverages)
})
if (simulResults) {
// Update stats.
hostsAverages = simulResults.hostsAverages
// Migrate.
await this._migrate(hostId, simulResults.moves)
optimizationsCount++
}
}
debug(`Density mode: ${optimizationsCount} optimizations.`)
}
async _simulate ({ host, destinations, hostsAverages }) {
const { id: hostId } = host
debug(`Try to optimize Host (${hostId}).`)
const vms = await this._getVms(hostId)
const vmsAverages = await this._getVmsAverages(vms, host)
for (const vm of vms) {
if (!vm.xenTools) {
debug(`VM (${vm.id}) of Host (${hostId}) does not support pool migration.`)
return
}
}
// Sort vms by amount of memory. (+ -> -)
vms.sort((a, b) =>
vmsAverages[b.id].memory - vmsAverages[a.id].memory
)
const simulResults = {
hostsAverages,
moves: []
}
// Try to find a destination for each VM.
for (const vm of vms) {
let move
// Simulate the VM move on a destinations set.
for (const subDestinations of destinations) {
move = this._testMigration({
vm,
destinations: subDestinations,
hostsAverages,
vmsAverages
})
// Destination found.
if (move) {
simulResults.moves.push(move)
break
}
}
// Unable to move a VM.
if (!move) {
return
}
}
// Done.
return simulResults
}
// Test if a VM migration on a destination (of a destinations set) is possible.
_testMigration ({ vm, destinations, hostsAverages, vmsAverages }) {
const {
_thresholds: {
critical: criticalThreshold
}
} = this
// Sort the destinations by available memory. (- -> +)
destinations.sort((a, b) =>
hostsAverages[a.id].memoryFree - hostsAverages[b.id].memoryFree
)
for (const destination of destinations) {
const destinationAverages = hostsAverages[destination.id]
const vmAverages = vmsAverages[vm.id]
// Unable to move the VM.
if (
destinationAverages.cpu + vmAverages.cpu >= criticalThreshold ||
destinationAverages.memoryFree - vmAverages.memory <= criticalThreshold
) {
continue
}
// Move ok. Update stats.
destinationAverages.cpu += vmAverages.cpu
destinationAverages.memoryFree -= vmAverages.memory
// Available movement.
return {
vm,
destination
}
}
}
// Migrate the VMs of one host.
// Try to shutdown the VMs host.
async _migrate (hostId, moves) {
const xapiSrc = this.xo.getXapi(hostId)
await Promise.all(
mapToArray(moves, move => {
const {
vm,
destination
} = move
const xapiDest = this.xo.getXapi(destination)
debug(`Migrate VM (${vm.id}) to Host (${destination.id}) from Host (${vm.$container}).`)
return xapiDest.migrateVm(vm._xapiId, this.xo.getXapi(destination), destination._xapiId)
})
)
debug(`Shutdown Host (${hostId}).`)
try {
await xapiSrc.shutdownHost(hostId)
} catch (error) {
debug(`Unable to shutdown Host (${hostId}).`, error)
}
}
}

View File

@@ -0,0 +1,203 @@
import EventEmitter from 'events'
import eventToPromise from 'event-to-promise'
import { CronJob } from 'cron'
import { intersection, map as mapToArray, uniq } from 'lodash'
import DensityPlan from './density-plan'
import PerformancePlan from './performance-plan'
import {
DEFAULT_CRITICAL_THRESHOLD_CPU,
DEFAULT_CRITICAL_THRESHOLD_MEMORY_FREE
} from './plan'
import {
EXECUTION_DELAY,
debug
} from './utils'
class Emitter extends EventEmitter {}
// ===================================================================
const PERFORMANCE_MODE = 0
const DENSITY_MODE = 1
// ===================================================================
export const configurationSchema = {
type: 'object',
properties: {
plans: {
type: 'array',
description: 'an array of plans',
title: 'Plans',
items: {
type: 'object',
title: 'Plan',
properties: {
name: {
type: 'string',
title: 'Name'
},
mode: {
enum: [ 'Performance mode', 'Density mode' ],
title: 'Mode'
},
pools: {
type: 'array',
description: 'list of pools where to apply the policy',
items: {
type: 'string',
$type: 'Pool'
}
},
thresholds: {
type: 'object',
title: 'Critical thresholds',
properties: {
cpu: {
type: 'integer',
title: 'CPU (%)',
default: DEFAULT_CRITICAL_THRESHOLD_CPU
},
memoryFree: {
type: 'integer',
title: 'RAM, Free memory (MB)',
default: DEFAULT_CRITICAL_THRESHOLD_MEMORY_FREE
}
}
},
excludedHosts: {
type: 'array',
title: 'Excluded hosts',
description: 'list of hosts that are not affected by the plan',
items: {
type: 'string',
$type: 'Host'
}
}
},
required: [ 'name', 'mode', 'pools' ]
},
minItems: 1
}
},
additionalProperties: false
}
// ===================================================================
// Create a job not enabled by default.
// A job is a cron task, a running and enabled state.
const makeJob = (cronPattern, fn) => {
const job = {
running: false,
emitter: new Emitter()
}
job.cron = new CronJob(cronPattern, async () => {
if (job.running) {
return
}
job.running = true
try {
await fn()
} catch (error) {
console.error('[WARN] scheduled function:', error && error.stack || error)
} finally {
job.running = false
job.emitter.emit('finish')
}
})
job.isEnabled = () => job.cron.running
return job
}
// ===================================================================
// ===================================================================
class LoadBalancerPlugin {
constructor (xo) {
this.xo = xo
this._job = makeJob(`*/${EXECUTION_DELAY} * * * *`, ::this._executePlans)
this._emitter
}
async configure ({ plans }) {
const job = this._job
const enabled = job.isEnabled()
if (enabled) {
job.cron.stop()
}
// Wait until all old plans stopped running.
if (job.running) {
await eventToPromise(job.emitter, 'finish')
}
this._plans = []
this._poolIds = [] // Used pools.
if (plans) {
for (const plan of plans) {
this._addPlan(plan.mode === 'Performance mode' ? PERFORMANCE_MODE : DENSITY_MODE, plan)
}
}
if (enabled) {
job.cron.start()
}
}
load () {
this._job.cron.start()
}
unload () {
this._job.cron.stop()
}
_addPlan (mode, { name, pools, ...options }) {
pools = uniq(pools)
// Check already used pools.
if (intersection(pools, this._poolIds).length > 0) {
throw new Error(`Pool(s) already included in an other plan: ${pools}`)
}
this._poolIds = this._poolIds.concat(pools)
this._plans.push(mode === PERFORMANCE_MODE
? new PerformancePlan(this.xo, name, pools, options)
: new DensityPlan(this.xo, name, pools, options)
)
}
_executePlans () {
debug('Execute plans!')
return Promise.all(
mapToArray(this._plans, plan => plan.execute())
)
}
}
// ===================================================================
export default ({ xo }) => new LoadBalancerPlugin(xo)

View File

@@ -0,0 +1,138 @@
import { filter, find, map as mapToArray } from 'lodash'
import Plan from './plan'
import { debug } from './utils'
// Compare a list of objects and give the best.
function searchBestObject (objects, fun) {
let object = objects[0]
for (let i = 1; i < objects.length; i++) {
if (fun(object, objects[i]) > 0) {
object = objects[i]
}
}
return object
}
// ===================================================================
export default class PerformancePlan extends Plan {
_checkRessourcesThresholds (objects, averages) {
return filter(objects, object => {
const objectAverages = averages[object.id]
return (
objectAverages.cpu >= this._thresholds.cpu.high ||
objectAverages.memoryFree <= this._thresholds.memoryFree.high
)
})
}
async execute () {
// Try to power on a hosts set.
try {
await Promise.all(
mapToArray(
filter(this._getHosts({ powerState: 'Halted' }), host => host.powerOnMode !== ''),
host => {
const { id } = host
return this.xo.getXapi(id).powerOnHost(id)
}
)
)
} catch (error) {
console.error(error)
}
const results = await this._findHostsToOptimize()
if (!results) {
return
}
const {
averages,
toOptimize
} = results
let { hosts } = results
toOptimize.sort((a, b) => {
a = averages[a.id]
b = averages[b.id]
return (b.cpu - a.cpu) || (a.memoryFree - b.memoryFree)
})
for (const exceededHost of toOptimize) {
const { id } = exceededHost
debug(`Try to optimize Host (${exceededHost.id}).`)
hosts = filter(hosts, host => host.id !== id)
// Search bests combinations for the worst host.
await this._optimize({
exceededHost,
hosts,
hostsAverages: averages
})
}
}
async _optimize ({ exceededHost, hosts, hostsAverages }) {
const vms = await this._getVms(exceededHost.id)
const vmsAverages = await this._getVmsAverages(vms, exceededHost)
// Sort vms by cpu usage. (lower to higher)
vms.sort((a, b) =>
vmsAverages[b.id].cpu - vmsAverages[a.id].cpu
)
const exceededAverages = hostsAverages[exceededHost.id]
const promises = []
const xapiSrc = this.xo.getXapi(exceededHost)
let optimizationsCount = 0
const searchFunction = (a, b) => hostsAverages[b.id].cpu - hostsAverages[a.id].cpu
for (const vm of vms) {
// Search host with lower cpu usage in the same pool first. In other pool if necessary.
let destination = searchBestObject(find(hosts, host => host.$poolId === vm.$poolId), searchFunction)
if (!destination) {
destination = searchBestObject(hosts, searchFunction)
}
const destinationAverages = hostsAverages[destination.id]
const vmAverages = vmsAverages[vm.id]
// Unable to move the vm.
if (
exceededAverages.cpu - vmAverages.cpu < destinationAverages.cpu + vmAverages.cpu ||
destinationAverages.memoryFree > vmAverages.memory
) {
continue
}
exceededAverages.cpu -= vmAverages.cpu
destinationAverages.cpu += vmAverages.cpu
exceededAverages.memoryFree += vmAverages.memory
destinationAverages.memoryFree -= vmAverages.memory
debug(`Migrate VM (${vm.id}) to Host (${destination.id}) from Host (${exceededHost.id}).`)
optimizationsCount++
promises.push(
xapiSrc.migrateVm(vm._xapiId, this.xo.getXapi(destination), destination._xapiId)
)
}
await Promise.all(promises)
debug(`Performance mode: ${optimizationsCount} optimizations for Host (${exceededHost.id}).`)
return
}
}

View File

@@ -0,0 +1,253 @@
import { filter, includes, map as mapToArray } from 'lodash'
import {
EXECUTION_DELAY,
debug
} from './utils'
const MINUTES_OF_HISTORICAL_DATA = 30
// CPU threshold in percent.
export const DEFAULT_CRITICAL_THRESHOLD_CPU = 90.0
// Memory threshold in MB.
export const DEFAULT_CRITICAL_THRESHOLD_MEMORY_FREE = 64.0
// Thresholds factors.
const HIGH_THRESHOLD_FACTOR = 0.85
const LOW_THRESHOLD_FACTOR = 0.25
const HIGH_THRESHOLD_MEMORY_FREE_FACTOR = 1.25
const LOW_THRESHOLD_MEMORY_FREE_FACTOR = 20.0
const numberOrDefault = (value, def) => (value >= 0) ? value : def
// ===================================================================
// Averages.
// ===================================================================
function computeAverage (values, nPoints = values.length) {
let sum = 0
let tot = 0
const { length } = values
for (let i = length - nPoints; i < length; i++) {
const value = values[i]
sum += value || 0
if (value) {
tot += 1
}
}
return sum / tot
}
function computeRessourcesAverage (objects, objectsStats, nPoints) {
const averages = {}
for (const object of objects) {
const { id } = object
const { stats } = objectsStats[id]
averages[id] = {
cpu: computeAverage(
mapToArray(stats.cpus, cpu => computeAverage(cpu, nPoints))
),
nCpus: stats.cpus.length,
memoryFree: computeAverage(stats.memoryFree, nPoints),
memory: computeAverage(stats.memory, nPoints)
}
}
return averages
}
function computeRessourcesAverageWithWeight (averages1, averages2, ratio) {
const averages = {}
for (const id in averages1) {
const objectAverages = averages[id] = {}
for (const averageName in averages1[id]) {
objectAverages[averageName] = averages1[id][averageName] * ratio + averages2[id][averageName] * (1 - ratio)
}
}
return averages
}
function setRealCpuAverageOfVms (vms, vmsAverages, nCpus) {
for (const vm of vms) {
const averages = vmsAverages[vm.id]
averages.cpu *= averages.nCpus / nCpus
}
}
// ===================================================================
export default class Plan {
constructor (xo, name, poolIds, {
excludedHosts,
thresholds
} = {}) {
this.xo = xo
this._name = name
this._poolIds = poolIds
this._excludedHosts = excludedHosts
this._thresholds = {
cpu: {
critical: numberOrDefault(thresholds && thresholds.cpu, DEFAULT_CRITICAL_THRESHOLD_CPU)
},
memoryFree: {
critical: numberOrDefault(thresholds && thresholds.memoryFree, DEFAULT_CRITICAL_THRESHOLD_MEMORY_FREE) * 1024
}
}
for (const key in this._thresholds) {
const attr = this._thresholds[key]
const { critical } = attr
if (key === 'memoryFree') {
attr.high = critical * HIGH_THRESHOLD_MEMORY_FREE_FACTOR
attr.low = critical * LOW_THRESHOLD_MEMORY_FREE_FACTOR
} else {
attr.high = critical * HIGH_THRESHOLD_FACTOR
attr.low = critical * LOW_THRESHOLD_FACTOR
}
}
}
execute () {
throw new Error('Not implemented')
}
// ===================================================================
// Get hosts to optimize.
// ===================================================================
async _findHostsToOptimize () {
const hosts = this._getHosts()
const hostsStats = await this._getHostsStats(hosts, 'minutes')
// Check if a ressource's utilization exceeds threshold.
const avgNow = computeRessourcesAverage(hosts, hostsStats, EXECUTION_DELAY)
let toOptimize = this._checkRessourcesThresholds(hosts, avgNow)
// No ressource's utilization problem.
if (toOptimize.length === 0) {
debug('No hosts to optimize.')
return
}
// Check in the last 30 min interval with ratio.
const avgBefore = computeRessourcesAverage(hosts, hostsStats, MINUTES_OF_HISTORICAL_DATA)
const avgWithRatio = computeRessourcesAverageWithWeight(avgNow, avgBefore, 0.75)
toOptimize = this._checkRessourcesThresholds(toOptimize, avgWithRatio)
// No ressource's utilization problem.
if (toOptimize.length === 0) {
debug('No hosts to optimize.')
return
}
return {
toOptimize,
averages: avgWithRatio,
hosts
}
}
_checkRessourcesThresholds () {
throw new Error('Not implemented')
}
// ===================================================================
// Get objects.
// ===================================================================
_getPlanPools () {
const pools = {}
try {
for (const poolId of this._poolIds) {
pools[poolId] = this.xo.getObject(poolId)
}
} catch (_) {
return {}
}
return pools
}
// Compute hosts for each pool. They can change over time.
_getHosts ({ powerState = 'Running' } = {}) {
return filter(this.xo.getObjects(), object => (
object.type === 'host' &&
includes(this._poolIds, object.$poolId) &&
object.power_state === powerState &&
!includes(this._excludedHosts, object.id)
))
}
async _getVms (hostId) {
return filter(this.xo.getObjects(), object =>
object.type === 'VM' &&
object.power_state === 'Running' &&
object.$container === hostId
)
}
// ===================================================================
// Get stats.
// ===================================================================
async _getHostsStats (hosts, granularity) {
const hostsStats = {}
await Promise.all(mapToArray(hosts, host =>
this.xo.getXapiHostStats(host, granularity).then(hostStats => {
hostsStats[host.id] = {
nPoints: hostStats.stats.cpus[0].length,
stats: hostStats.stats,
averages: {}
}
})
))
return hostsStats
}
async _getVmsStats (vms, granularity) {
const vmsStats = {}
await Promise.all(mapToArray(vms, vm =>
this.xo.getXapiVmStats(vm, granularity).then(vmStats => {
vmsStats[vm.id] = {
nPoints: vmStats.stats.cpus[0].length,
stats: vmStats.stats,
averages: {}
}
})
))
return vmsStats
}
async _getVmsAverages (vms, host) {
const vmsStats = await this._getVmsStats(vms, 'minutes')
const vmsAverages = computeRessourcesAverageWithWeight(
computeRessourcesAverage(vms, vmsStats, EXECUTION_DELAY),
computeRessourcesAverage(vms, vmsStats, MINUTES_OF_HISTORICAL_DATA),
0.75
)
// Compute real CPU usage. Virtuals cpus to reals cpus.
setRealCpuAverageOfVms(vms, vmsAverages, host.CPUs.cpu_count)
return vmsAverages
}
}

View File

@@ -0,0 +1,13 @@
const noop = () => {}
const LOAD_BALANCER_DEBUG = 1
// Delay between each ressources evaluation in minutes.
// Must be less than MINUTES_OF_HISTORICAL_DATA.
export const EXECUTION_DELAY = 1
// ===================================================================
export const debug = LOAD_BALANCER_DEBUG
? str => console.log(`[load-balancer]${str}`)
: noop

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More